From 510528bb68ef17d6627285384ba38cf8d8b33a8c Mon Sep 17 00:00:00 2001
From: Pirate Praveen
Date: Thu, 26 Dec 2019 22:10:19 +0530
Subject: [PATCH] New upstream version 12.5.4
---
.gitignore | 2 +
.gitlab-ci.yml | 4 +-
.gitlab/CODEOWNERS | 8 +-
.gitlab/ci/cng.gitlab-ci.yml | 3 +-
.gitlab/ci/docs.gitlab-ci.yml | 15 +-
.gitlab/ci/frontend.gitlab-ci.yml | 10 +-
.gitlab/ci/global.gitlab-ci.yml | 217 +-
.gitlab/ci/memory.gitlab-ci.yml | 2 +-
.gitlab/ci/notifications.gitlab-ci.yml | 29 -
.gitlab/ci/pages.gitlab-ci.yml | 5 +-
.gitlab/ci/qa.gitlab-ci.yml | 29 +-
.gitlab/ci/rails.gitlab-ci.yml | 36 +-
.gitlab/ci/releases.gitlab-ci.yml | 22 +
.gitlab/ci/reports.gitlab-ci.yml | 8 +-
.gitlab/ci/review.gitlab-ci.yml | 100 +-
.gitlab/ci/setup.gitlab-ci.yml | 7 +-
.gitlab/ci/test-metadata.gitlab-ci.yml | 4 +-
.../Security developer workflow.md | 2 +-
.../merge_request_templates/Documentation.md | 2 +-
.haml-lint_todo.yml | 4 -
.overcommit.yml.example | 17 +-
.rubocop.yml | 5 +-
.rubocop_todo.yml | 7 -
CHANGELOG-EE.md | 99 +-
CHANGELOG.md | 379 +-
GITALY_SERVER_VERSION | 2 +-
GITLAB_ELASTICSEARCH_INDEXER_VERSION | 2 +-
GITLAB_PAGES_VERSION | 2 +-
Gemfile | 47 +-
Gemfile.lock | 196 +-
Guardfile | 43 +
PROCESS.md | 2 +-
VERSION | 2 +-
.../images/cluster_app_logos/crossplane.png | Bin 0 -> 1850 bytes
.../cluster_app_logos/elastic_stack.png | Bin 0 -> 2919 bytes
app/assets/javascripts/api.js | 25 +-
.../javascripts/behaviors/preview_markdown.js | 40 +-
.../blob/file_template_mediator.js | 6 +-
.../boards/components/board_form.vue | 4 +-
.../boards/components/board_list.vue | 3 +-
.../boards/components/boards_selector.vue | 4 +-
.../boards/components/issue_card_inner.vue | 4 +-
.../boards/components/modal/index.vue | 3 +-
app/assets/javascripts/boards/index.js | 4 +
app/assets/javascripts/boards/models/list.js | 4 +-
.../javascripts/boards/stores/getters.js | 3 +
app/assets/javascripts/boards/stores/index.js | 6 +-
app/assets/javascripts/boards/stores/state.js | 2 +-
.../javascripts/boards/toggle_labels.js | 1 +
.../javascripts/clusters/clusters_bundle.js | 52 +-
.../clusters/components/applications.vue | 191 +-
.../components/crossplane_provider_stack.vue | 93 +
...install_application_confirmation_modal.vue | 12 +-
app/assets/javascripts/clusters/constants.js | 13 +-
.../clusters/services/clusters_service.js | 2 +
.../clusters/stores/clusters_store.js | 43 +-
app/assets/javascripts/commit/image_file.js | 236 +-
.../javascripts/compare_autocomplete.js | 18 +-
.../components/project_form_group.vue | 2 +-
.../contributors/components/contributors.vue | 227 +
app/assets/javascripts/contributors/index.js | 23 +
.../services/contributors_service.js | 7 +
.../contributors/stores/actions.js | 20 +
.../contributors/stores/getters.js | 33 +
.../javascripts/contributors/stores/index.js | 18 +
.../contributors/stores/mutation_types.js | 3 +
.../contributors/stores/mutations.js | 17 +
.../javascripts/contributors/stores/state.js | 5 +
app/assets/javascripts/contributors/utils.js | 30 +
.../components/cluster_form_dropdown.vue | 99 +-
.../components/create_eks_cluster.vue | 24 +
.../eks_cluster_configuration_form.vue | 182 +-
.../components/region_dropdown.vue | 63 -
.../components/service_credentials_form.vue | 140 +-
.../create_cluster/eks_cluster/constants.js | 7 +-
.../create_cluster/eks_cluster/index.js | 45 +-
.../services/aws_services_facade.js | 132 +-
.../eks_cluster/store/actions.js | 84 +-
.../create_cluster/eks_cluster/store/index.js | 15 +-
.../eks_cluster/store/mutation_types.js | 9 +
.../eks_cluster/store/mutations.js | 32 +
.../create_cluster/eks_cluster/store/state.js | 19 +-
.../gke_cluster_namespace/index.js | 0
.../create_cluster/init_create_cluster.js | 37 +
.../components/stage_card_list_item.vue | 44 -
.../components/stage_nav_item.vue | 36 +-
.../diffs/components/diff_content.vue | 2 +
.../diffs/components/diff_file.vue | 13 +-
app/assets/javascripts/dropzone_input.js | 2 +-
.../components/error_details.vue | 141 +
.../components/error_tracking_list.vue | 56 +-
.../error_tracking/components/stacktrace.vue | 33 +
.../components/stacktrace_entry.vue | 110 +
.../javascripts/error_tracking/details.js | 25 +
.../error_tracking/{index.js => list.js} | 0
.../error_tracking/services/index.js | 2 +-
.../error_tracking/store/details/actions.js | 63 +
.../error_tracking/store/details/getters.js | 3 +
.../store/details/mutation_types.js | 4 +
.../error_tracking/store/details/mutations.js | 16 +
.../error_tracking/store/details/state.js | 6 +
.../javascripts/error_tracking/store/index.js | 33 +-
.../store/{ => list}/actions.js | 4 +-
.../error_tracking/store/list/getters.js | 4 +
.../store/{ => list}/mutation_types.js | 0
.../store/{ => list}/mutations.js | 0
.../error_tracking/store/list/state.js | 5 +
.../components/app.vue | 31 +-
.../components/error_tracking_form.vue | 45 +-
.../error_tracking_settings/store/actions.js | 3 +
.../store/mutation_types.js | 1 +
.../store/mutations.js | 3 +
.../error_tracking_settings/store/state.js | 1 +
.../available_dropdown_mappings.js | 19 +
.../filtered_search_dropdown_manager.js | 11 +-
.../filtered_search_manager.js | 1 +
.../issuable_filtered_search_token_keys.js | 44 +-
app/assets/javascripts/flash.js | 2 +-
.../frequent_items/store/mutations.js | 3 +-
app/assets/javascripts/gl_dropdown.js | 4 +-
.../components/grafana_integration.vue | 103 +
.../javascripts/grafana_integration/index.js | 14 +
.../grafana_integration/store/actions.js | 42 +
.../grafana_integration/store/index.js | 16 +
.../store/mutation_types.js | 3 +
.../grafana_integration/store/mutations.js | 13 +
.../grafana_integration/store/state.js | 8 +
app/assets/javascripts/group.js | 24 +
.../javascripts/helpers/monitor_helper.js | 44 +-
.../javascripts/ide/components/jobs/stage.vue | 3 +-
.../ide/components/preview/clientside.vue | 3 +
.../ide/components/repo_editor.vue | 1 +
app/assets/javascripts/ide/services/index.js | 31 +-
.../javascripts/ide/stores/actions/file.js | 27 +-
.../ide/stores/actions/merge_request.js | 16 +-
.../javascripts/ide/stores/actions/tree.js | 7 +-
app/assets/javascripts/ide/stores/getters.js | 12 +-
app/assets/javascripts/ide/stores/index.js | 2 +
.../ide/stores/modules/clientside/actions.js | 12 +
.../ide/stores/modules/clientside/index.js | 6 +
app/assets/javascripts/ide/stores/utils.js | 9 +
.../issuable_bulk_update_sidebar.js | 19 +-
.../issuables_list/components/issuable.vue | 335 +
.../components/issuables_list_app.vue | 277 +
.../javascripts/issuables_list/constants.js | 33 +
.../javascripts/issuables_list/eventhub.js | 5 +
.../javascripts/issuables_list/index.js | 24 +
app/assets/javascripts/issue.js | 15 +-
.../javascripts/jobs/components/log/log.vue | 33 +-
app/assets/javascripts/jobs/store/utils.js | 2 +-
app/assets/javascripts/labels_select.js | 159 +-
app/assets/javascripts/lib/graphql.js | 6 +-
.../javascripts/lib/utils/chart_utils.js | 17 +
.../javascripts/lib/utils/datetime_utility.js | 58 +-
app/assets/javascripts/lib/utils/notify.js | 8 +-
.../javascripts/lib/utils/number_utils.js | 33 +
.../javascripts/lib/utils/text_utility.js | 23 +-
.../javascripts/lib/utils/tick_formats.js | 39 -
app/assets/javascripts/line_highlighter.js | 17 +-
app/assets/javascripts/main.js | 20 -
app/assets/javascripts/manual_ordering.js | 6 +-
app/assets/javascripts/merge_request.js | 26 +-
.../monitoring/components/charts/anomaly.vue | 227 +
.../monitoring/components/charts/heatmap.vue | 73 +
.../components/charts/time_series.vue | 96 +-
.../monitoring/components/dashboard.vue | 232 +-
.../date_time_picker/date_time_picker.vue | 28 +-
.../monitoring/components/embed.vue | 7 +-
.../monitoring/components/graph_group.vue | 2 +-
.../monitoring/components/panel_type.vue | 27 +-
.../components/shared/prometheus_header.vue | 15 +
.../javascripts/monitoring/constants.js | 15 +
.../monitoring/monitoring_bundle.js | 7 -
.../javascripts/monitoring/stores/actions.js | 54 +-
.../monitoring/stores/mutation_types.js | 3 +-
.../monitoring/stores/mutations.js | 38 +-
.../javascripts/monitoring/stores/state.js | 6 +-
.../javascripts/monitoring/stores/utils.js | 6 -
app/assets/javascripts/monitoring/utils.js | 19 +-
.../javascripts/network/branch_graph.js | 193 +-
app/assets/javascripts/new_branch_form.js | 42 +-
app/assets/javascripts/new_commit_form.js | 5 +-
app/assets/javascripts/notes.js | 108 +-
.../components/diff_discussion_header.vue | 133 +
.../notes/components/note_header.vue | 1 +
.../notes/components/noteable_discussion.vue | 111 +-
.../notes/components/notes_app.vue | 13 +-
.../mixins/description_version_history.js | 12 +
.../javascripts/notes/stores/actions.js | 16 +
.../notes/stores/collapse_utils.js | 44 +-
.../pages/admin/abuse_reports/index.js | 7 +-
.../javascripts/pages/admin/clusters/index.js | 20 +-
app/assets/javascripts/pages/groups/index.js | 20 +-
.../javascripts/pages/groups/issues/index.js | 3 +
.../new/fetch_group_path_availability.js | 7 +
.../pages/groups/new/group_path_validator.js | 91 +
.../javascripts/pages/groups/new/index.js | 6 +
.../pages/projects/blob/show/index.js | 1 +
.../pages/projects/clusters/new/index.js | 13 -
.../pages/projects/clusters/show/index.js | 2 +-
.../pages/projects/commit/show/index.js | 1 +
.../projects/error_tracking/details/index.js | 5 +
.../pages/projects/error_tracking/index.js | 5 -
.../projects/error_tracking/index/index.js | 5 +
.../pages/projects/graphs/show/index.js | 26 +-
.../graphs/show/stat_graph_contributors.js | 140 -
.../show/stat_graph_contributors_graph.js | 379 -
.../show/stat_graph_contributors_util.js | 143 -
.../javascripts/pages/projects/index.js | 19 +-
.../merge_requests/init_merge_request_show.js | 2 +
.../pages/projects/network/network.js | 31 +-
.../pages/projects/pages_domains/form.js | 20 +-
.../projects/pipelines/test_report/index.js | 2 +
.../javascripts/pages/projects/project.js | 5 -
.../settings/operations/show/index.js | 2 +
.../permissions/components/settings_panel.vue | 83 +-
.../javascripts/pages/projects/show/index.js | 2 +-
.../pages/projects/tree/show/index.js | 2 +-
.../javascripts/pages/sessions/new/index.js | 14 +
.../components/add_request.vue | 48 +
.../components/performance_bar_app.vue | 3 +
.../javascripts/performance_bar/index.js | 14 +
.../components/graph/action_component.vue | 2 +-
.../components/pipelines_table_row.vue | 6 +-
.../components/test_reports/test_reports.vue | 81 +
.../test_reports/test_suite_table.vue | 108 +
.../components/test_reports/test_summary.vue | 116 +
.../test_reports/test_summary_table.vue | 129 +
app/assets/javascripts/pipelines/constants.js | 6 +
.../pipelines/pipeline_details_bundle.js | 25 +-
.../pipelines/stores/test_reports/actions.js | 30 +
.../pipelines/stores/test_reports/getters.js | 23 +
.../pipelines/stores/test_reports/index.js | 15 +
.../stores/test_reports/mutation_types.js | 4 +
.../stores/test_reports/mutations.js | 19 +
.../pipelines/stores/test_reports/state.js | 6 +
.../pipelines/stores/test_reports/utils.js | 36 +
.../privacy_policy_update_callout.js | 8 -
app/assets/javascripts/profile/gl_crop.js | 29 +-
app/assets/javascripts/project_find_file.js | 49 +-
app/assets/javascripts/project_select.js | 109 +-
.../javascripts/projects/project_new.js | 4 +
.../components/collapsible_container.vue | 33 +-
.../registry/components/table_registry.vue | 95 +-
app/assets/javascripts/registry/constants.js | 19 +-
.../javascripts/registry/stores/actions.js | 6 +-
.../javascripts/registry/stores/mutations.js | 32 +-
.../releases/detail/components/app.vue | 46 +-
.../javascripts/releases/detail/index.js | 2 +-
.../releases/detail/store/state.js | 1 +
.../list/components/release_block.vue | 22 +-
.../list/components/release_block_footer.vue | 112 +
.../reports/components/issue_status_icon.vue | 2 +-
.../reports/components/report_item.vue | 1 +
.../components/directory_download_links.vue | 47 +
.../repository/components/last_commit.vue | 58 +-
.../repository/components/preview/index.vue | 49 +
.../repository/components/table/index.vue | 109 +-
.../repository/components/table/row.vue | 41 +-
.../components/tree_action_link.vue | 28 +
.../repository/components/tree_content.vue | 115 +
app/assets/javascripts/repository/graphql.js | 6 +
app/assets/javascripts/repository/index.js | 84 +-
app/assets/javascripts/repository/log_tree.js | 22 +-
.../javascripts/repository/pages/index.vue | 21 +-
.../javascripts/repository/pages/tree.vue | 24 +-
.../queries/commit.fragment.graphql | 8 +
.../queries/getCommit.query.graphql | 9 +-
.../queries/getCommits.query.graphql | 9 +-
.../repository/queries/getFiles.query.graphql | 1 +
.../queries/getReadme.query.graphql | 5 +
.../queries/pathLastCommit.query.graphql | 21 +-
app/assets/javascripts/repository/router.js | 2 +-
.../javascripts/repository/utils/commit.js | 13 +
.../javascripts/repository/utils/dom.js | 4 +
.../javascripts/repository/utils/readme.js | 21 +
.../javascripts/repository/utils/title.js | 8 +-
app/assets/javascripts/right_sidebar.js | 52 +-
app/assets/javascripts/search_autocomplete.js | 82 +-
.../javascripts/{raven => sentry}/index.js | 8 +-
.../sentry_config.js} | 60 +-
.../subscriptions/sidebar_subscriptions.vue | 2 +
.../subscriptions/subscriptions.vue | 26 +-
.../sidebar/stores/sidebar_store.js | 4 +
app/assets/javascripts/sourcegraph/index.js | 28 +
app/assets/javascripts/sourcegraph/load.js | 6 +
app/assets/javascripts/tree.js | 14 +-
app/assets/javascripts/user_popovers.js | 5 +
.../components/states/mr_widget_rebase.vue | 8 +-
.../content_viewer/content_viewer.vue | 6 +
.../viewers/markdown_viewer.vue | 6 +
.../components/diff_viewer/diff_viewer.vue | 12 +
.../viewers/image_diff/two_up_viewer.vue | 12 +
.../diff_viewer/viewers/image_diff_viewer.vue | 10 +
.../vue_shared/components/file_row.vue | 2 +-
.../vue_shared/components/icon.vue | 2 +-
.../components/issue/issue_assignees.vue | 51 +-
.../vue_shared/components/markdown/header.vue | 4 +-
.../markdown/suggestion_diff_header.vue | 2 +-
.../components/notes/system_note.vue | 29 +-
.../project_selector/project_selector.vue | 42 +-
.../vue_shared/components/slot_switch.vue | 35 +
.../vue_shared/components/split_button.vue | 76 +
.../components/time_ago_tooltip.vue | 2 +-
.../components/user_popover/user_popover.vue | 4 +-
app/assets/javascripts/zen_mode.js | 32 +-
.../stylesheets/bootstrap_migration.scss | 7 +-
app/assets/stylesheets/framework.scss | 2 +-
.../framework/ci_variable_list.scss | 10 +
app/assets/stylesheets/framework/common.scss | 16 +
.../stylesheets/framework/dropdowns.scss | 3 +-
app/assets/stylesheets/framework/files.scss | 4 +
app/assets/stylesheets/framework/filters.scss | 18 +-
app/assets/stylesheets/framework/flash.scss | 88 +-
app/assets/stylesheets/framework/layout.scss | 3 +-
.../stylesheets/framework/memory_graph.scss | 6 +-
app/assets/stylesheets/framework/modal.scss | 13 +-
.../framework/responsive_tables.scss | 11 +
.../stylesheets/framework/snippets.scss | 7 +-
.../stylesheets/framework/variables.scss | 27 +
.../framework/vue_transitions.scss | 24 +
app/assets/stylesheets/mailer.scss | 117 +
.../stylesheets/mailer_client_specific.scss | 65 +
app/assets/stylesheets/pages/boards.scss | 10 +-
.../stylesheets/pages/error_details.scss | 18 +
.../pages/experimental_separate_sign_up.scss | 1 +
app/assets/stylesheets/pages/graph.scss | 15 -
app/assets/stylesheets/pages/milestone.scss | 7 +-
app/assets/stylesheets/pages/notes.scss | 11 +
app/assets/stylesheets/pages/pipelines.scss | 12 +-
app/assets/stylesheets/pages/projects.scss | 1 +
app/assets/stylesheets/pages/reports.scss | 1 -
app/assets/stylesheets/pages/stat_graph.scss | 62 -
app/assets/stylesheets/performance_bar.scss | 14 +-
app/assets/stylesheets/utilities.scss | 10 +
.../admin/abuse_reports_controller.rb | 5 +-
.../admin/applications_controller.rb | 2 +-
app/controllers/admin/groups_controller.rb | 2 +-
.../admin/identities_controller.rb | 4 +-
app/controllers/admin/keys_controller.rb | 4 +-
app/controllers/admin/labels_controller.rb | 2 +-
app/controllers/admin/projects_controller.rb | 2 +-
app/controllers/admin/spam_logs_controller.rb | 2 +-
app/controllers/admin/users_controller.rb | 2 +-
app/controllers/application_controller.rb | 46 +-
app/controllers/boards/issues_controller.rb | 5 +-
.../clusters/applications_controller.rb | 2 +-
.../clusters/clusters_controller.rb | 100 +-
.../concerns/confirm_email_warning.rb | 2 +-
.../concerns/issuable_collections.rb | 2 +-
app/controllers/concerns/lfs_request.rb | 4 +-
app/controllers/concerns/metrics_dashboard.rb | 39 +-
app/controllers/concerns/milestone_actions.rb | 8 +-
app/controllers/concerns/preview_markdown.rb | 30 +-
.../redirects_for_missing_path_on_tree.rb | 17 +
app/controllers/concerns/renders_commits.rb | 13 +-
app/controllers/concerns/routable_actions.rb | 2 +-
app/controllers/concerns/sourcegraph_gon.rb | 30 +
app/controllers/dashboard/todos_controller.rb | 4 +-
app/controllers/groups/boards_controller.rb | 2 +-
.../groups/group_links_controller.rb | 34 +
app/controllers/groups/labels_controller.rb | 2 +-
.../registry/repositories_controller.rb | 2 +-
app/controllers/groups_controller.rb | 12 +-
app/controllers/health_controller.rb | 12 +-
app/controllers/help_controller.rb | 2 +-
.../ldap/omniauth_callbacks_controller.rb | 4 +-
.../notification_settings_controller.rb | 18 +-
.../oauth/applications_controller.rb | 2 +-
.../authorized_applications_controller.rb | 2 +-
.../omniauth_callbacks_controller.rb | 2 +-
.../profiles/preferences_controller.rb | 3 +-
.../profiles/u2f_registrations_controller.rb | 2 +-
app/controllers/projects/blame_controller.rb | 5 +-
app/controllers/projects/blob_controller.rb | 5 +-
app/controllers/projects/boards_controller.rb | 2 +-
app/controllers/projects/commit_controller.rb | 1 +
.../projects/environments_controller.rb | 7 +-
.../projects/error_tracking_controller.rb | 72 +-
.../projects/grafana_api_controller.rb | 5 +
app/controllers/projects/issues_controller.rb | 1 +
app/controllers/projects/labels_controller.rb | 2 +-
.../projects/lfs_api_controller.rb | 2 +-
.../merge_requests/creations_controller.rb | 8 +-
.../merge_requests/diffs_controller.rb | 5 +-
.../projects/merge_requests_controller.rb | 28 +-
app/controllers/projects/pages_controller.rb | 2 +-
.../projects/pages_domains_controller.rb | 19 +-
.../projects/pipelines_controller.rb | 22 +-
.../projects/releases_controller.rb | 40 +-
.../settings/operations_controller.rb | 2 +-
app/controllers/projects/tags_controller.rb | 2 +-
app/controllers/projects/tree_controller.rb | 8 +-
.../projects/usage_ping_controller.rb | 13 +
app/controllers/projects/wikis_controller.rb | 2 +-
app/controllers/projects_controller.rb | 3 +-
app/controllers/registrations_controller.rb | 22 +-
app/controllers/sessions_controller.rb | 14 +-
app/controllers/users_controller.rb | 10 +-
app/finders/abuse_reports_finder.rb | 18 +
app/finders/admin/projects_finder.rb | 4 +-
app/finders/branches_finder.rb | 51 +-
app/finders/container_repositories_finder.rb | 40 +-
app/finders/git_refs_finder.rb | 56 +
app/finders/group_descendants_finder.rb | 2 +-
app/finders/issuable_finder.rb | 4 +
app/finders/projects_finder.rb | 5 +-
app/finders/prometheus_metrics_finder.rb | 129 +
app/finders/releases_finder.rb | 6 +-
app/finders/tags_finder.rb | 28 +-
app/finders/todos_finder.rb | 28 +-
app/graphql/gitlab_schema.rb | 4 +-
.../mutations/merge_requests/set_assignees.rb | 48 +
.../mutations/merge_requests/set_labels.rb | 53 +
.../mutations/merge_requests/set_locked.rb | 29 +
.../mutations/merge_requests/set_milestone.rb | 30 +
.../merge_requests/set_subscription.rb | 26 +
app/graphql/mutations/todos/base.rb | 17 +
app/graphql/mutations/todos/mark_done.rb | 38 +
app/graphql/resolvers/base_resolver.rb | 8 +
.../resolvers/commit_pipelines_resolver.rb | 13 +
app/graphql/types/base_enum.rb | 13 +
app/graphql/types/commit_type.rb | 42 +-
app/graphql/types/extended_issue_type.rb | 14 -
app/graphql/types/group_type.rb | 15 +-
app/graphql/types/issue_sort_enum.rb | 4 +
app/graphql/types/issue_type.rb | 84 +-
app/graphql/types/label_type.rb | 14 +-
app/graphql/types/merge_request_type.rb | 156 +-
app/graphql/types/metadata_type.rb | 6 +-
app/graphql/types/milestone_type.rb | 23 +-
.../types/mutation_operation_mode_enum.rb | 14 +
app/graphql/types/mutation_type.rb | 6 +
app/graphql/types/namespace_type.rb | 34 +-
app/graphql/types/project_statistics_type.rb | 21 +-
app/graphql/types/project_type.rb | 141 +-
app/graphql/types/repository_type.rb | 12 +-
app/graphql/types/task_completion_status.rb | 6 +-
app/graphql/types/todo_target_enum.rb | 8 +-
app/graphql/types/todo_type.rb | 3 +-
app/graphql/types/tree/entry_type.rb | 1 +
app/graphql/types/user_type.rb | 14 +-
app/helpers/application_helper.rb | 9 +
app/helpers/application_settings_helper.rb | 25 +-
app/helpers/auth_helper.rb | 14 +
app/helpers/blob_helper.rb | 12 +-
app/helpers/builds_helper.rb | 4 +-
app/helpers/clusters_helper.rb | 24 +-
app/helpers/dashboard_helper.rb | 13 +-
app/helpers/environments_helper.rb | 1 +
app/helpers/gitlab_routing_helper.rb | 2 +-
app/helpers/issuables_helper.rb | 5 +-
app/helpers/markup_helper.rb | 13 +-
app/helpers/milestones_helper.rb | 67 +-
app/helpers/projects/error_tracking_helper.rb | 9 +
app/helpers/projects_helper.rb | 4 +
app/helpers/releases_helper.rb | 3 +-
app/helpers/repository_languages_helper.rb | 2 +-
app/helpers/search_helper.rb | 12 +-
app/helpers/services_helper.rb | 2 +-
app/helpers/sessions_helper.rb | 16 +
app/helpers/snippets_helper.rb | 81 +-
app/helpers/sourcegraph_helper.rb | 27 +
app/helpers/tab_helper.rb | 18 -
app/helpers/tree_helper.rb | 18 +
app/helpers/users_helper.rb | 9 +
app/helpers/visibility_level_helper.rb | 4 +-
app/mailers/emails/members.rb | 37 +-
app/mailers/emails/pipelines.rb | 5 +-
app/mailers/emails/releases.rb | 8 +-
app/mailers/previews/notify_preview.rb | 6 +-
app/models/abuse_report.rb | 4 +
.../cycle_analytics/project_stage.rb | 19 +
app/models/application_setting.rb | 54 +-
.../application_setting_implementation.rb | 14 +-
app/models/award_emoji.rb | 5 +-
app/models/aws/role.rb | 6 +
app/models/ci/build.rb | 63 +-
app/models/ci/build_metadata.rb | 1 +
app/models/ci/pipeline.rb | 91 +-
.../clusters/applications/cert_manager.rb | 2 +-
.../clusters/applications/crossplane.rb | 60 +
.../clusters/applications/elastic_stack.rb | 108 +
app/models/clusters/applications/ingress.rb | 73 +-
app/models/clusters/applications/runner.rb | 2 +-
app/models/clusters/cluster.rb | 67 +-
app/models/clusters/clusters_hierarchy.rb | 41 +-
.../clusters/concerns/application_core.rb | 18 +
app/models/clusters/instance.rb | 4 +
app/models/clusters/providers/aws.rb | 26 +-
app/models/clusters/providers/gcp.rb | 4 +
app/models/commit_status_enums.rb | 4 +-
.../analytics/cycle_analytics/stage.rb | 66 +-
app/models/concerns/ci/metadatable.rb | 4 +
app/models/concerns/ci/processable.rb | 8 +
app/models/concerns/deployment_platform.rb | 2 +-
app/models/concerns/issuable.rb | 4 +-
app/models/concerns/milestoneish.rb | 4 +
app/models/concerns/noteable.rb | 4 -
app/models/concerns/protected_ref.rb | 4 +-
.../concerns/storage/legacy_namespace.rb | 40 +-
app/models/concerns/subscribable.rb | 8 +
app/models/concerns/worker_attributes.rb | 46 +
app/models/container_repository.rb | 5 +-
app/models/dashboard_group_milestone.rb | 4 +
app/models/deployment.rb | 33 +
app/models/deployment_merge_request.rb | 6 +
app/models/description_version.rb | 4 +
app/models/environment.rb | 16 +
.../project_error_tracking_setting.rb | 23 +
app/models/global_milestone.rb | 6 +-
app/models/grafana_integration.rb | 6 +
app/models/group.rb | 51 +-
app/models/group_group_link.rb | 23 +
app/models/import_export_upload.rb | 1 +
app/models/issue.rb | 24 +-
app/models/lfs_object.rb | 5 +
app/models/merge_request.rb | 114 +-
app/models/merge_request_diff.rb | 17 +-
app/models/milestone.rb | 3 +-
app/models/notification_reason.rb | 4 +-
app/models/pages_domain.rb | 2 +
app/models/project.rb | 43 +-
app/models/project_ci_cd_setting.rb | 3 +
.../chat_message/pipeline_message.rb | 30 +-
.../chat_message/push_message.rb | 16 +-
app/models/project_services/data_fields.rb | 2 +-
.../project_services/prometheus_service.rb | 25 +-
app/models/project_snippet.rb | 7 -
app/models/project_wiki.rb | 6 -
app/models/prometheus_metric.rb | 6 +
app/models/protected_branch.rb | 2 +-
app/models/release.rb | 13 +
app/models/releases/source.rb | 4 +-
app/models/service.rb | 1 +
app/models/todo.rb | 7 +-
app/models/user.rb | 12 +-
app/models/wiki_page.rb | 5 -
app/models/zoom_meeting.rb | 26 +
app/policies/base_policy.rb | 8 +-
app/policies/group_policy.rb | 6 +-
app/policies/personal_snippet_policy.rb | 4 +-
app/policies/project_policy.rb | 15 +-
app/policies/project_snippet_policy.rb | 2 +-
app/policies/todo_policy.rb | 1 +
app/presenters/clusterable_presenter.rb | 16 +
app/presenters/commit_status_presenter.rb | 6 +-
.../instance_clusterable_presenter.rb | 20 +
app/presenters/project_presenter.rb | 22 +-
app/presenters/release_presenter.rb | 57 +
app/presenters/todo_presenter.rb | 2 -
app/serializers/cluster_application_entity.rb | 2 +
.../container_repositories_serializer.rb | 4 +
app/serializers/diff_file_base_entity.rb | 8 +
app/serializers/diff_file_entity.rb | 20 +-
.../error_tracking/detailed_error_entity.rb | 27 +
.../detailed_error_serializer.rb | 7 +
.../error_tracking/error_event_entity.rb | 7 +
.../error_tracking/error_event_serializer.rb | 7 +
.../issuable_sidebar_extras_entity.rb | 9 +
app/serializers/issue_board_entity.rb | 1 -
app/serializers/job_artifact_report_entity.rb | 2 +-
app/serializers/merge_request_diff_entity.rb | 2 +
.../merge_request_poll_widget_entity.rb | 6 +
app/serializers/note_entity.rb | 2 +
.../projects/serverless/service_entity.rb | 42 +-
app/services/base_service.rb | 12 +-
.../ci/compare_reports_base_service.rb | 5 +
app/services/ci/create_pipeline_service.rb | 7 +-
.../ci/find_exposed_artifacts_service.rb | 70 +
...nerate_exposed_artifacts_report_service.rb | 30 +
app/services/ci/register_job_service.rb | 79 +-
.../clusters/applications/base_service.rb | 16 +-
.../clusters/aws/fetch_credentials_service.rb | 56 +
.../clusters/aws/finalize_creation_service.rb | 139 +
.../clusters/aws/provision_service.rb | 85 +
app/services/clusters/aws/proxy_service.rb | 134 +
.../aws/verify_provision_status_service.rb | 50 +
app/services/clusters/destroy_service.rb | 34 +
...reate_or_update_service_account_service.rb | 32 +
.../clusters/kubernetes/kubernetes.rb | 2 +
app/services/clusters/update_service.rb | 50 +-
app/services/cohorts_service.rb | 2 +-
app/services/commits/change_service.rb | 5 +-
app/services/commits/create_service.rb | 13 +-
app/services/concerns/git/logger.rb | 10 +
app/services/create_branch_service.rb | 2 +-
.../deployments/after_create_service.rb | 9 +
.../link_merge_requests_service.rb | 66 +
app/services/deployments/update_service.rb | 17 +-
app/services/error_tracking/base_service.rb | 66 +
.../error_tracking/issue_details_service.rb | 15 +
.../issue_latest_event_service.rb | 15 +
.../error_tracking/list_issues_service.rb | 42 +-
.../error_tracking/list_projects_service.rb | 52 +-
.../groups/group_links/create_service.rb | 29 +
.../groups/group_links/destroy_service.rb | 26 +
.../groups/import_export/export_service.rb | 71 +
app/services/groups/transfer_service.rb | 4 +-
app/services/groups/update_service.rb | 5 +-
app/services/issuable_base_service.rb | 2 +-
app/services/issues/update_service.rb | 2 -
app/services/issues/zoom_link_service.rb | 75 +-
app/services/merge_requests/base_service.rb | 13 +
app/services/merge_requests/build_service.rb | 8 +
.../merge_requests/ff_merge_service.rb | 14 +-
.../merge_requests/merge_base_service.rb | 26 +-
app/services/merge_requests/merge_service.rb | 16 +
.../push_options_handler_service.rb | 43 +-
app/services/merge_requests/rebase_service.rb | 6 +-
.../merge_requests/refresh_service.rb | 7 +-
app/services/merge_requests/squash_service.rb | 4 +-
app/services/merge_requests/update_service.rb | 4 +-
.../working_copy_base_service.rb | 26 -
.../dashboard/custom_metric_embed_service.rb | 9 +-
.../dashboard/grafana_metric_embed_service.rb | 160 +
.../dashboard/project_dashboard_service.rb | 3 +-
.../dashboard/system_dashboard_service.rb | 3 +-
app/services/notes/create_service.rb | 15 +
app/services/notes/post_process_service.rb | 2 +
.../notification_recipient_service.rb | 4 +-
app/services/preview_markdown_service.rb | 10 +-
.../delete_tags_service.rb | 36 +-
.../hashed_storage/base_attachment_service.rb | 33 +-
.../hashed_storage/base_repository_service.rb | 2 +-
.../migrate_attachments_service.rb | 48 +-
.../hashed_storage/migration_service.rb | 12 +-
.../rollback_attachments_service.rb | 7 +-
.../hashed_storage/rollback_service.rb | 18 +-
.../projects/import_export/export_service.rb | 2 +-
.../projects/lfs_pointers/lfs_link_service.rb | 26 +-
app/services/projects/transfer_service.rb | 2 +
app/services/quick_actions/target_service.rb | 2 +
app/services/system_note_service.rb | 87 +-
.../system_notes/merge_requests_service.rb | 145 +
app/services/users/signup_service.rb | 34 +
app/services/zoom_notes_service.rb | 42 -
.../same_project_association_validator.rb | 21 +
app/validators/zoom_url_validator.rb | 13 +
app/views/admin/abuse_reports/index.html.haml | 18 +-
.../application_settings/_ci_cd.html.haml | 6 +
.../admin/application_settings/_eks.html.haml | 31 +
.../application_settings/_outbound.html.haml | 4 +-
.../application_settings/_plantuml.html.haml | 41 +-
.../_repository_mirrors_form.html.haml | 4 +-
.../application_settings/_snowplow.html.haml | 11 +-
.../_sourcegraph.html.haml | 38 +
.../_third_party_offers.html.haml | 28 +-
.../integrations.html.haml | 33 +-
.../application_settings/network.html.haml | 2 +-
.../application_settings/repository.html.haml | 4 +-
app/views/admin/dashboard/index.html.haml | 37 +-
app/views/admin/sessions/_new_base.html.haml | 2 +-
.../admin/sessions/_signin_box.html.haml | 2 +-
.../admin/sessions/_tabs_normal.html.haml | 2 +-
app/views/admin/sessions/new.html.haml | 2 +-
app/views/admin/users/show.html.haml | 2 +-
.../ci/group_variables/_content.html.haml | 1 +
.../ci/group_variables/_header.html.haml | 5 +
app/views/ci/group_variables/_index.html.haml | 13 +
.../_variable_header.html.haml | 5 +
app/views/ci/variables/_header.html.haml | 2 +-
app/views/ci/variables/_index.html.haml | 5 +
.../_url_query_variable_row.html.haml | 28 +
.../clusters/_advanced_settings.html.haml | 21 +
app/views/clusters/clusters/_banner.html.haml | 6 +-
.../_gcp_signup_offer_banner.html.haml | 2 +-
.../clusters/clusters/aws/_new.html.haml | 23 +
.../_cloud_provider_button.html.haml | 6 +-
.../_cloud_provider_selector.html.haml | 6 +-
.../clusters/clusters/eks/_index.html.haml | 2 -
.../clusters/clusters/gcp/_form.html.haml | 13 +-
.../clusters/clusters/gcp/_new.html.haml | 7 +
app/views/clusters/clusters/index.html.haml | 2 +-
app/views/clusters/clusters/new.html.haml | 33 +-
app/views/clusters/clusters/show.html.haml | 2 +
.../clusters/clusters/user/_form.html.haml | 8 +-
.../clusters/clusters/user/_header.html.haml | 2 +-
.../_zero_authorized_projects.html.haml | 2 +-
app/views/devise/sessions/new.html.haml | 6 +-
app/views/devise/shared/_signin_box.html.haml | 2 +-
app/views/devise/shared/_tabs_ldap.html.haml | 2 +-
app/views/errors/not_found.html.haml | 2 +-
app/views/groups/issues.html.haml | 8 +-
app/views/groups/milestones/show.html.haml | 2 +-
.../groups/settings/ci_cd/show.html.haml | 3 +-
app/views/help/_shortcuts.html.haml | 2 +-
app/views/import/manifest/_form.html.haml | 2 +-
app/views/layouts/_flash.html.haml | 2 +-
app/views/layouts/_head.html.haml | 4 +-
app/views/layouts/_mailer.html.haml | 72 +-
app/views/layouts/_page.html.haml | 2 -
.../header/_current_user_dropdown.html.haml | 9 +-
app/views/layouts/header/_default.html.haml | 4 +-
.../layouts/header/_help_dropdown.html.haml | 4 +-
app/views/layouts/nav/_dashboard.html.haml | 27 +-
.../layouts/nav/sidebar/_group.html.haml | 10 +-
.../layouts/nav/sidebar/_project.html.haml | 12 +-
.../member_access_denied_email.html.haml | 11 +-
.../member_access_granted_email.html.haml | 16 +-
.../member_access_requested_email.html.haml | 9 +-
.../member_invite_accepted_email.html.haml | 13 +-
.../member_invite_declined_email.html.haml | 11 +-
.../notify/member_invited_email.html.haml | 27 +-
.../preferences/_sourcegraph.html.haml | 26 +
app/views/profiles/preferences/show.html.haml | 3 +
app/views/profiles/show.html.haml | 2 +-
app/views/projects/_export.html.haml | 2 +-
app/views/projects/_files.html.haml | 6 +-
app/views/projects/_home_panel.html.haml | 2 +-
...e_request_merge_options_settings.html.haml | 6 +
app/views/projects/blame/show.html.haml | 2 +-
app/views/projects/blob/_header.html.haml | 15 +-
.../projects/blob/_markdown_buttons.html.haml | 2 +-
app/views/projects/buttons/_clone.html.haml | 2 -
.../projects/buttons/_download.html.haml | 13 +-
.../buttons/_download_links.html.haml | 6 +-
app/views/projects/compare/_form.html.haml | 10 +-
.../_confirm_rollback_modal.html.haml | 2 +-
.../environments/empty_logs.html.haml | 14 +
...mpty.html.haml => empty_metrics.html.haml} | 4 +-
.../projects/error_tracking/details.html.haml | 4 +
app/views/projects/graphs/show.html.haml | 28 +-
app/views/projects/issues/_issue.html.haml | 3 +-
app/views/projects/labels/index.html.haml | 2 +-
.../merge_requests/_how_to_merge.html.haml | 2 +-
.../creations/_new_submit.html.haml | 10 -
.../projects/merge_requests/edit.html.haml | 1 -
app/views/projects/milestones/show.html.haml | 53 +-
.../mirrors/_authentication_method.html.haml | 10 +-
.../mirrors/_mirror_repos_form.html.haml | 4 +-
app/views/projects/pages/_access.html.haml | 6 +
app/views/projects/pages/_list.html.haml | 4 +-
app/views/projects/pages/show.html.haml | 36 +-
.../pages_domains/_certificate.html.haml | 79 +-
.../projects/pages_domains/_dns.html.haml | 33 +
.../projects/pages_domains/_form.html.haml | 73 +-
.../_lets_encrypt_callout.html.haml | 13 +
.../projects/pages_domains/edit.html.haml | 15 +-
.../projects/pages_domains/new.html.haml | 1 -
.../projects/pages_domains/show.html.haml | 2 +-
.../projects/pipelines/_with_tabs.html.haml | 14 +-
app/views/projects/pipelines/new.html.haml | 7 +
app/views/projects/pipelines/show.html.haml | 3 +-
.../shared/_branches_list.html.haml | 3 +-
app/views/projects/services/_form.html.haml | 1 -
app/views/projects/services/_index.html.haml | 2 +-
app/views/projects/services/edit.html.haml | 3 +-
.../projects/settings/ci_cd/_form.html.haml | 2 +-
.../projects/settings/ci_cd/show.html.haml | 3 +-
.../operations/_grafana_integration.html.haml | 2 +
.../settings/operations/show.html.haml | 1 +
app/views/projects/show.html.haml | 2 +-
app/views/projects/tree/_readme.html.haml | 2 +-
.../projects/tree/_tree_header.html.haml | 12 +-
app/views/projects/tree/show.html.haml | 3 +-
app/views/registrations/welcome.html.haml | 15 +-
app/views/search/_category.html.haml | 2 +-
app/views/search/_results.html.haml | 1 +
app/views/search/results/_blob.html.haml | 4 +-
app/views/search/results/_blob_data.html.haml | 2 +-
.../search/results/_snippet_blob.html.haml | 2 +-
.../search/results/_snippet_title.html.haml | 5 +-
app/views/search/results/_wiki_blob.html.haml | 2 +-
app/views/shared/_clone_panel.html.haml | 3 -
app/views/shared/_field.html.haml | 4 +-
app/views/shared/_group_form.html.haml | 7 +-
.../shared/_mobile_clone_panel.html.haml | 2 +-
.../_personal_access_tokens_form.html.haml | 1 -
app/views/shared/_service_settings.html.haml | 2 +-
.../form_elements/_description.html.haml | 3 +-
app/views/shared/issuable/_form.html.haml | 4 +-
.../shared/issuable/_search_bar.html.haml | 307 +-
app/views/shared/issuable/_sidebar.html.haml | 8 +-
.../issuable/form/_branch_chooser.html.haml | 31 +-
.../issuable/form/_merge_params.html.haml | 14 +-
.../members/_access_request_links.html.haml | 2 +-
.../shared/milestones/_description.html.haml | 8 +
app/views/shared/milestones/_header.html.haml | 38 +
.../shared/milestones/_milestone.html.haml | 14 +-
.../shared/milestones/_sidebar.html.haml | 21 +
app/views/shared/milestones/_tabs.html.haml | 47 +-
app/views/shared/milestones/_top.html.haml | 56 +-
.../shared/notifications/_button.html.haml | 6 +-
.../_custom_notifications.html.haml | 3 +
.../notifications/_new_button.html.haml | 2 +-
app/views/shared/projects/_list.html.haml | 2 +-
.../runners/_runner_description.html.haml | 2 +-
app/views/shared/snippets/_blob.html.haml | 3 +-
app/views/shared/snippets/_embed.html.haml | 2 +-
app/views/shared/snippets/_header.html.haml | 2 +-
app/views/shared/snippets/_snippet.html.haml | 5 +-
app/workers/all_queues.yml | 4 +
app/workers/authorized_projects_worker.rb | 1 +
app/workers/build_finished_worker.rb | 2 +
app/workers/build_hooks_worker.rb | 1 +
app/workers/build_queue_worker.rb | 2 +
app/workers/build_success_worker.rb | 1 +
app/workers/chat_notification_worker.rb | 5 +
app/workers/ci/build_schedule_worker.rb | 1 +
app/workers/cluster_install_app_worker.rb | 2 +
app/workers/cluster_patch_app_worker.rb | 2 +
.../cluster_project_configure_worker.rb | 2 +
app/workers/cluster_provision_worker.rb | 8 +-
app/workers/cluster_upgrade_app_worker.rb | 2 +
...luster_wait_for_app_installation_worker.rb | 3 +
...ster_wait_for_ingress_ip_address_worker.rb | 2 +
.../clusters/applications/uninstall_worker.rb | 2 +
.../wait_for_uninstall_app_worker.rb | 3 +
app/workers/clusters/cleanup/app_worker.rb | 16 +
.../cleanup/project_namespace_worker.rb | 16 +
.../cleanup/service_account_worker.rb | 16 +
.../gitlab/github_import/object_importer.rb | 1 +
app/workers/create_pipeline_worker.rb | 2 +
app/workers/deployments/finished_worker.rb | 1 +
app/workers/deployments/success_worker.rb | 1 +
app/workers/email_receiver_worker.rb | 1 +
app/workers/emails_on_push_worker.rb | 2 +
app/workers/expire_build_artifacts_worker.rb | 19 -
app/workers/expire_job_cache_worker.rb | 1 +
app/workers/expire_pipeline_cache_worker.rb | 2 +
app/workers/gitlab_shell_worker.rb | 5 +-
app/workers/group_export_worker.rb | 15 +
.../hashed_storage/project_migrate_worker.rb | 2 +-
app/workers/import_issues_csv_worker.rb | 1 +
.../notification_service_worker.rb | 1 +
app/workers/merge_worker.rb | 1 +
.../prune_aggregation_schedules_worker.rb | 1 +
app/workers/new_issue_worker.rb | 2 +
app/workers/new_merge_request_worker.rb | 2 +
app/workers/new_note_worker.rb | 2 +
app/workers/new_release_worker.rb | 2 +-
app/workers/object_pool/join_worker.rb | 2 +
.../pages_domain_removal_cron_worker.rb | 1 +
app/workers/pipeline_hooks_worker.rb | 2 +
app/workers/pipeline_metrics_worker.rb | 2 +
app/workers/pipeline_notification_worker.rb | 3 +
app/workers/pipeline_process_worker.rb | 1 +
app/workers/pipeline_schedule_worker.rb | 1 +
app/workers/pipeline_success_worker.rb | 1 +
app/workers/pipeline_update_worker.rb | 1 +
app/workers/post_receive.rb | 2 +
app/workers/process_commit_worker.rb | 1 +
app/workers/project_cache_worker.rb | 3 +
app/workers/project_export_worker.rb | 1 +
app/workers/project_service_worker.rb | 1 +
app/workers/reactive_caching_worker.rb | 8 +
.../remove_expired_group_links_worker.rb | 4 +
app/workers/remove_expired_members_worker.rb | 1 +
app/workers/repository_import_worker.rb | 1 +
.../repository_update_remote_mirror_worker.rb | 2 +
app/workers/stage_update_worker.rb | 1 +
app/workers/stuck_ci_jobs_worker.rb | 15 +
app/workers/stuck_import_jobs_worker.rb | 1 +
..._head_pipeline_for_merge_request_worker.rb | 2 +
app/workers/update_merge_requests_worker.rb | 2 +
.../wait_for_cluster_creation_worker.rb | 8 +-
app/workers/web_hook_worker.rb | 2 +
bin/secpick | 2 +-
...g-push-events-in-chat-msg-integration.yaml | 5 +
config/application.rb | 11 +-
config/dependency_decisions.yml | 6 +
config/gitlab.yml.example | 24 +-
config/initializers/0_inflections.rb | 19 +-
config/initializers/1_settings.rb | 15 +-
config/initializers/7_prometheus_metrics.rb | 11 +-
config/initializers/database_config.rb | 18 +
config/initializers/health_check.rb | 12 +
config/initializers/lograge.rb | 10 +
.../rack_attack_git_basic_auth.rb | 22 +-
config/initializers/rack_attack_logging.rb | 6 +-
config/initializers/rack_attack_new.rb | 65 +-
config/initializers/validate_puma.rb | 5 +
config/locales/en.yml | 1 +
config/puma.example.development.rb | 10 +-
config/routes.rb | 9 +-
config/routes/group.rb | 2 +
config/routes/project.rb | 83 +-
config/routes/user.rb | 3 +-
config/sidekiq_queues.yml | 3 +
config/webpack.config.js | 7 +-
core-js/internals/shared.js | 2 +-
core-js/internals/use-symbol-as-uid.js | 2 +-
core-js/internals/well-known-symbol.js | 2 +-
core-js/modules/es.symbol.js | 16 +-
core-js/package.json | 2 +-
danger/commit_messages/Dangerfile | 10 +-
db/fixtures/development/02_users.rb | 77 +
db/fixtures/development/03_project.rb | 303 +-
db/fixtures/development/04_labels.rb | 2 +-
db/fixtures/development/05_users.rb | 34 -
db/fixtures/development/06_teams.rb | 6 +-
db/fixtures/development/07_milestones.rb | 2 +-
db/fixtures/development/10_merge_requests.rb | 8 +-
db/fixtures/development/11_keys.rb | 2 +-
db/fixtures/development/12_snippets.rb | 2 +-
db/fixtures/development/14_pipelines.rb | 2 +-
.../development/16_protected_branches.rb | 2 +-
db/fixtures/development/17_cycle_analytics.rb | 2 +-
db/fixtures/development/19_environments.rb | 2 +-
db/fixtures/development/23_spam_logs.rb | 2 +-
db/fixtures/development/24_forks.rb | 4 +-
.../20180215181245_users_name_lower_index.rb | 6 +-
...20180504195842_project_name_lower_index.rb | 6 +-
...20180902070406_create_group_group_links.rb | 32 +
.../20190703171157_add_sourcing_epic_dates.rb | 10 +
...90703171555_add_sourcing_epic_dates_fks.rb | 25 +
...3_remove_rendundant_index_from_releases.rb | 7 +-
...h_configuration_to_application_settings.rb | 21 +
...526_create_packages_conan_file_metadata.rb | 18 +
...918104731_add_cleanup_status_to_cluster.rb | 21 +
...35_add_cleanup_status_reason_to_cluster.rb | 12 +
.../20190930153535_create_zoom_meetings.rb | 24 +
...ate_clusters_applications_elastic_stack.rb | 22 +
...5155_add_self_managed_prometheus_alerts.rb | 1 -
...61031_add_mark_for_deletion_to_projects.rb | 11 +
...d_mark_for_deletion_indexes_to_projects.rb | 19 +
...d_pendo_enabled_to_application_settings.rb | 15 +
...0_add_pendo_url_to_application_settings.rb | 9 +
...8_add_productivity_analytics_start_date.rb | 11 +
..._fill_productivity_analytics_start_date.rb | 31 +
...0244_add_geo_design_repository_counters.rb | 16 +
...exposed_artifacts_to_ci_builds_metadata.rb | 13 +
...i_builds_metadata_has_exposed_artifacts.rb | 17 +
...lu_registry_url_to_application_settings.rb | 9 +
...djourned_period_to_application_settings.rb | 11 +
...add_squash_commit_sha_to_merge_requests.rb | 9 +
...esign_management_version_user_to_author.rb | 17 +
...hor_index_to_design_management_versions.rb | 17 +
...931_remove_index_on_snippets_project_id.rb | 7 +-
...133352_create_ci_subscriptions_projects.rb | 21 +
...reate_users_security_dashboard_projects.rb | 15 +
...e_source_branch_after_merge_to_projects.rb | 17 +
...017134513_add_deployment_merge_requests.rb | 33 +
...create_clusters_applications_crossplane.rb | 19 +
...ests_index_on_target_project_and_branch.rb | 19 +
...191023152913_add_default_and_free_plans.rb | 29 +
...191024134020_add_index_to_zoom_meetings.rb | 17 +
..._default_project_and_snippet_visibility.rb | 15 +
...d_setup_for_company_to_user_preferences.rb | 9 +
...ame_snowplow_site_id_to_snowplow_app_id.rb | 17 +
...29125305_create_packages_conan_metadata.rb | 16 +
...901_add_enabled_to_grafana_integrations.rb | 23 +
.../20191030135044_create_plan_limits.rb | 14 +
.../20191030152934_move_limits_from_plans.rb | 17 +
...2917_replace_index_on_metrics_merged_at.rb | 19 +
...eks_credentials_to_application_settings.rb | 14 +
...license_details_to_application_settings.rb | 11 +
...4558_add_report_type_to_vulnerabilities.rb | 9 +
...652_add_index_on_deployments_updated_at.rb | 18 +
..._sourcegraph_admin_and_user_preferences.rb | 17 +
...ndex_to_projects_on_marked_for_deletion.rb | 17 +
...9_add_group_id_to_import_export_uploads.rb | 9 +
...1_add_group_fk_to_import_export_uploads.rb | 19 +
.../20191111121500_default_ci_config_path.rb | 13 +
...hed_markdown_version_to_vulnerabilities.rb | 9 +
...indexes_for_projects_api_default_params.rb | 19 +
...ojects_api_default_params_authenticated.rb | 19 +
...232338_ensure_no_empty_milestone_titles.rb | 18 +
..._resolved_attributes_to_vulnerabilities.rb | 15 +
...ey_on_resolved_by_id_to_vulnerabilities.rb | 19 +
...091425_create_vulnerability_issue_links.rb | 23 +
..._self_monitoring_project_alerting_token.rb | 68 +-
...chedule_productivity_analytics_backfill.rb | 2 +-
...3_schedule_epic_issues_after_epics_move.rb | 35 +
...3850_fix_any_approver_rule_for_projects.rb | 51 +
...anagement_version_user_to_author_rename.rb | 17 +
...edule_fix_gitlab_com_pages_access_level.rb | 17 +
...7180026_drop_ci_build_trace_sections_id.rb | 19 +
...2_remove_empty_github_service_templates.rb | 28 +
...5_nullify_feature_flag_plaintext_tokens.rb | 39 +
...cation_settings_snowplow_site_id_rename.rb | 17 +
..._remove_pendo_from_application_settings.rb | 19 +
...20191031112603_remove_limits_from_plans.rb | 17 +
...625_set_report_type_for_vulnerabilities.rb | 26 +
...1105140942_add_indices_to_abuse_reports.rb | 17 +
..._vulnerabilities_title_html_to_nullable.rb | 9 +
...4_set_resolved_state_on_vulnerabilities.rb | 31 +
db/schema.rb | 197 +-
doc/README.md | 33 +-
doc/administration/audit_events.md | 4 +
doc/administration/auth/ldap.md | 34 +
.../background_verification.md | 5 +-
.../geo/replication/configuration.md | 12 +-
.../geo/replication/database.md | 5 +-
doc/administration/geo/replication/index.md | 8 +-
.../geo/replication/object_storage.md | 2 +-
.../geo/replication/troubleshooting.md | 143 +-
.../geo/replication/using_a_geo_server.md | 8 +-
.../replication/version_specific_updates.md | 18 +
doc/administration/git_annex.md | 242 +
doc/administration/gitaly/index.md | 56 +-
doc/administration/gitaly/praefect.md | 173 +-
.../high_availability/README.md | 220 +-
.../high_availability/consul.md | 17 +
.../high_availability/database.md | 87 +-
.../high_availability/pgbouncer.md | 49 +-
doc/administration/high_availability/redis.md | 2 +-
doc/administration/incoming_email.md | 4 +-
doc/administration/index.md | 20 +-
doc/administration/integration/plantuml.md | 2 +-
doc/administration/job_logs.md | 25 +-
.../lfs/img}/git-annex-branches.png | Bin
.../lfs/img/lfs-icon.png | Bin
doc/administration/lfs/lfs_administration.md | 273 +
.../lfs/manage_large_binaries_with_git_lfs.md | 266 +
.../lfs/migrate_from_git_annex_to_git_lfs.md | 254 +
doc/administration/logs.md | 40 +
.../index.md | 3 +-
.../performance/img/performance_bar.png | Bin 33642 -> 71317 bytes
.../monitoring/performance/performance_bar.md | 13 +-
.../monitoring/prometheus/index.md | 25 +
doc/administration/operations/index.md | 3 +
.../operations/sidekiq_memory_killer.md | 4 +-
.../packages/container_registry.md | 8 +-
doc/administration/pages/index.md | 119 +-
.../repository_storage_paths.md | 4 +-
.../repository_storage_types.md | 14 +-
doc/administration/timezone.md | 37 +
.../gitlab_rails_cheat_sheet.md | 4 +-
doc/administration/uploads.md | 4 +-
doc/api/api_resources.md | 6 +-
doc/api/audit_events.md | 116 +-
doc/api/branches.md | 2 +-
doc/api/commits.md | 29 +
doc/api/deployments.md | 29 +-
doc/api/epic_links.md | 32 +-
doc/api/epics.md | 40 +-
doc/api/feature_flag_specs.md | 291 +
doc/api/feature_flags.md | 308 +
doc/api/geo_nodes.md | 12 +
doc/api/graphql/index.md | 5 +
.../graphql/reference/gitlab_schema.graphql | 5422 +++++
doc/api/graphql/reference/gitlab_schema.json | 18749 ++++++++++++++++
doc/api/graphql/reference/index.md | 496 +-
doc/api/group_clusters.md | 34 +-
doc/api/groups.md | 4 +
doc/api/issues.md | 21 +-
doc/api/license.md | 5 +
doc/api/merge_requests.md | 24 +-
doc/api/packages.md | 45 +-
doc/api/pages_domains.md | 71 +-
doc/api/project_clusters.md | 39 +-
doc/api/projects.md | 20 +
doc/api/releases/index.md | 2 +-
doc/api/scim.md | 5 +-
doc/api/search.md | 22 +
doc/api/services.md | 5 +-
doc/api/settings.md | 16 +-
doc/api/sidekiq_metrics.md | 6 +-
doc/api/tags.md | 2 +-
doc/api/users.md | 4 +-
doc/api/visual_review_discussions.md | 40 +
doc/api/vulnerabilities.md | 114 +-
doc/api/vulnerability_findings.md | 128 +
doc/ci/README.md | 2 +-
doc/ci/chatops/README.md | 5 +
.../bitbucket_integration.md | 2 +-
.../github_integration.md | 4 +-
doc/ci/ci_cd_for_external_repos/index.md | 4 +-
doc/ci/docker/using_docker_build.md | 25 +-
doc/ci/environments.md | 11 +-
doc/ci/environments/environments_dashboard.md | 51 +
.../img/environments_dashboard_v12_5.png | Bin 0 -> 30989 bytes
doc/ci/examples/deployment/README.md | 4 +-
.../index.md | 4 +-
.../laravel_with_gitlab_and_envoy/index.md | 2 +-
.../pipelines_junit_test_report_ui_v12_5.png | Bin 0 -> 15957 bytes
doc/ci/interactive_web_terminal/index.md | 2 +-
doc/ci/introduction/index.md | 14 +-
doc/ci/junit_test_reports.md | 24 +
.../merge_trains/index.md | 4 +-
doc/ci/multi_project_pipelines.md | 7 +-
doc/ci/pipelines.md | 34 +-
doc/ci/quick_start/README.md | 4 +-
doc/ci/variables/README.md | 33 +-
doc/ci/variables/deprecated_variables.md | 14 +-
.../img/inherited_group_variables_v12_5.png | Bin 0 -> 73965 bytes
doc/ci/variables/predefined_variables.md | 210 +-
doc/ci/yaml/README.md | 250 +-
doc/development/README.md | 3 +-
doc/development/api_graphql_styleguide.md | 94 +-
doc/development/architecture.md | 2 +-
doc/development/changelog.md | 5 +-
doc/development/chatops_on_gitlabcom.md | 2 +-
doc/development/code_review.md | 6 +-
doc/development/contributing/index.md | 4 +
.../contributing/issue_workflow.md | 2 +-
.../contributing/merge_request_workflow.md | 6 +-
doc/development/creating_enums.md | 15 +
doc/development/database_debugging.md | 2 +-
doc/development/database_review.md | 7 +
doc/development/documentation/index.md | 4 +-
.../documentation/site_architecture/index.md | 155 +-
.../site_architecture/release_process.md | 241 +
doc/development/documentation/styleguide.md | 19 +-
doc/development/documentation/workflow.md | 2 +-
doc/development/ee_features.md | 40 +-
doc/development/event_tracking/index.md | 4 -
.../fe_guide/development_process.md | 2 +-
doc/development/fe_guide/graphql.md | 8 +
doc/development/fe_guide/style_guide_js.md | 12 +
doc/development/fe_guide/style_guide_scss.md | 2 +-
doc/development/feature_flags/controls.md | 8 +
doc/development/feature_flags/development.md | 19 +-
doc/development/geo.md | 29 +-
doc/development/git_object_deduplication.md | 2 +-
doc/development/gitaly.md | 7 +-
doc/development/gotchas.md | 70 +
doc/development/i18n/externalization.md | 5 +-
doc/development/i18n/merging_translations.md | 28 +-
doc/development/i18n/translation.md | 2 +-
doc/development/kubernetes.md | 51 +
doc/development/lfs.md | 2 +-
.../merge_request_performance_guidelines.md | 179 +-
doc/development/migration_style_guide.md | 2 +-
doc/development/packages.md | 108 +-
doc/development/pipelines.md | 71 +-
doc/development/policies.md | 15 +
doc/development/profiling.md | 4 +
doc/development/rake_tasks.md | 20 +-
doc/development/repository_mirroring.md | 2 +-
doc/development/sidekiq_style_guide.md | 228 +-
.../testing_guide/best_practices.md | 29 +-
.../end_to_end/best_practices.md | 28 +
.../testing_guide/end_to_end/feature_flags.md | 27 +
.../testing_guide/end_to_end/flows.md | 56 +
.../testing_guide/end_to_end/index.md | 2 +
.../testing_guide/end_to_end/page_objects.md | 59 +
doc/development/testing_guide/flaky_tests.md | 1 +
.../testing_guide/frontend_testing.md | 77 +
doc/development/testing_guide/review_apps.md | 8 +-
.../understanding_explain_plans.md | 37 +
doc/development/utilities.md | 114 +-
doc/gitlab-basics/README.md | 3 +-
doc/gitlab-basics/feature_branch_workflow.md | 35 +
doc/gitlab-basics/fork-project.md | 2 +-
doc/gitlab-basics/start-using-git.md | 2 +-
doc/install/README.md | 2 +-
doc/install/aws/index.md | 2 +-
doc/install/installation.md | 28 +-
doc/install/openshift_and_gitlab/index.md | 2 -
doc/install/requirements.md | 2 +-
doc/integration/README.md | 88 +-
doc/integration/bitbucket.md | 7 +-
doc/integration/elasticsearch.md | 9 +
.../img/sourcegraph_admin_v12_5.png | Bin 0 -> 61520 bytes
.../img/sourcegraph_demo_v12_5.png | Bin 0 -> 97025 bytes
.../img/sourcegraph_popover_v12_5.png | Bin 0 -> 27925 bytes
.../sourcegraph_user_preferences_v12_5.png | Bin 0 -> 37710 bytes
doc/integration/saml.md | 2 +-
doc/integration/slash_commands.md | 1 +
doc/integration/sourcegraph.md | 129 +
doc/integration/ultra_auth.md | 2 +-
doc/intro/README.md | 4 +-
doc/policy/maintenance.md | 67 +-
doc/raketasks/backup_restore.md | 2 +-
doc/raketasks/cleanup.md | 2 +-
doc/security/webhooks.md | 19 +-
doc/ssh/README.md | 2 +-
doc/topics/autodevops/index.md | 301 +-
doc/topics/autodevops/quick_start_guide.md | 6 +-
doc/topics/git/index.md | 8 +-
doc/topics/git/migrate_to_git_lfs/index.md | 6 +-
doc/topics/gitlab_flow.md | 330 +
doc/{workflow => topics}/img/gitlab_flow.png | Bin
.../img/gitlab_flow_ci_mr.png} | Bin
.../img/gitlab_flow_close_issue_mr.png} | Bin
.../img/gitlab_flow_environment_branches.png} | Bin
.../img/gitlab_flow_four_stages.png} | Bin
.../img/gitlab_flow_git_pull.png} | Bin
.../img/gitlab_flow_gitdashflow.png} | Bin
.../img/gitlab_flow_github_flow.png} | Bin
.../img/gitlab_flow_good_commit.png} | Bin
.../img/gitlab_flow_merge_commits.png} | Bin
.../img/gitlab_flow_merge_request.png} | Bin
.../img/gitlab_flow_messy_flow.png} | Bin
.../img/gitlab_flow_mr_inline_comments.png} | Bin
.../img/gitlab_flow_production_branch.png} | Bin
.../img/gitlab_flow_rebase.png} | Bin
.../img/gitlab_flow_release_branches.png} | Bin
.../img/gitlab_flow_remove_checkbox.png} | Bin
doc/topics/index.md | 1 +
doc/university/README.md | 2 +-
doc/university/support/README.md | 2 +-
doc/university/training/gitlab_flow.md | 2 +-
doc/update/README.md | 8 +-
.../activating_deactivating_users.md | 66 +
.../admin_area/blocking_unblocking_users.md | 48 +
doc/user/admin_area/diff_limits.md | 2 +-
doc/user/admin_area/index.md | 4 +-
.../admin_area/monitoring/health_check.md | 63 +-
.../settings/account_and_limit_settings.md | 2 +-
.../settings/continuous_integration.md | 18 +
doc/user/admin_area/settings/email.md | 2 +-
.../settings/img/two_factor_grace_period.png | Bin 0 -> 17591 bytes
doc/user/admin_area/settings/index.md | 1 +
.../settings/sign_in_restrictions.md | 56 +
.../admin_area/settings/usage_statistics.md | 4 +-
.../visibility_and_access_controls.md | 2 +-
doc/user/analytics/cycle_analytics.md | 54 +-
doc/user/analytics/productivity_analytics.md | 11 +-
.../container_scanning/index.md | 71 +-
doc/user/application_security/dast/index.md | 30 +
.../img/dependency_list_v12_4.png | Bin 0 -> 137591 bytes
.../dependency_list/index.md | 2 +-
.../dependency_scanning/index.md | 71 +-
doc/user/application_security/index.md | 38 +-
.../license_compliance/index.md | 16 +-
.../application_security/sast/analyzers.md | 7 +-
doc/user/application_security/sast/index.md | 45 +-
.../img/group_security_dashboard_v12_3.png | Bin 61667 -> 0 bytes
.../img/group_security_dashboard_v12_4.png | Bin 0 -> 62965 bytes
.../security_dashboard/index.md | 4 +-
doc/user/clusters/applications.md | 207 +-
doc/user/clusters/crossplane.md | 292 +
...tings-cluster-management-project-v12_5.png | Bin 0 -> 66251 bytes
doc/user/clusters/management_project.md | 46 +-
doc/user/group/clusters/index.md | 17 +-
.../epics/img/epic_view_roadmap_v12.3.png | Bin
doc/user/group/epics/img/epic_view_v12.3.png | Bin
.../group/epics/img/epics_list_view_v12.3.png | Bin 39450 -> 0 bytes
.../group/epics/img/epics_list_view_v12.5.png | Bin 0 -> 442541 bytes
doc/user/group/epics/index.md | 67 +-
doc/user/group/index.md | 21 +-
doc/user/group/roadmap/index.md | 2 +-
doc/user/group/saml_sso/index.md | 57 +-
doc/user/group/saml_sso/scim_setup.md | 31 +-
doc/user/group/subgroups/index.md | 2 +-
.../img/todos_add_todo_sidebar.png | Bin
doc/{workflow => user}/img/todos_icon.png | Bin
doc/{workflow => user}/img/todos_index.png | Bin
.../img/todos_mark_done_sidebar.png | Bin
.../img/todos_todo_list_item.png} | Bin
.../img/incident_management_settings.png | Bin 0 -> 45533 bytes
doc/user/incident_management/index.md | 131 +
doc/user/index.md | 19 +-
doc/user/markdown.md | 8 +-
...ndex_operations_dashboard_top_bar_icon.png | Bin 3922 -> 0 bytes
doc/user/operations_dashboard/index.md | 11 +-
doc/user/packages/dependency_proxy/index.md | 2 +-
doc/user/packages/npm_registry/index.md | 18 +
doc/user/permissions.md | 10 +-
doc/user/profile/account/delete_account.md | 57 -
.../img/notification_global_settings.png | Bin
.../img/notification_group_settings.png | Bin
.../img/notification_project_settings.png | Bin
doc/user/profile/index.md | 2 +-
doc/user/profile/notifications.md | 231 +
doc/user/profile/preferences.md | 15 +-
.../project/clusters/add_remove_clusters.md | 767 +
.../eks_and_gitlab/img/create_dns.png | Bin 23923 -> 0 bytes
.../eks_and_gitlab/img/create_project.png | Bin 30568 -> 0 bytes
.../eks_and_gitlab/img/deploy_apps.png | Bin 82157 -> 0 bytes
.../project/clusters/eks_and_gitlab/index.md | 281 +-
.../{eks_and_gitlab => }/img/add_cluster.png | Bin
.../{eks_and_gitlab => }/img/environment.png | Bin
.../img/kubernetes_pod_logs_v12_4.png | Bin 393690 -> 0 bytes
.../img/kubernetes_pod_logs_v12_5.png | Bin 0 -> 183707 bytes
.../{eks_and_gitlab => }/img/pipeline.png | Bin
.../{eks_and_gitlab => }/img/rbac.png | Bin
.../img/sidebar_menu_pod_logs_v12_5.png | Bin 0 -> 13681 bytes
doc/user/project/clusters/index.md | 442 +-
.../project/clusters/kubernetes_pod_logs.md | 32 +-
doc/user/project/clusters/runbooks/index.md | 4 +-
doc/user/project/clusters/serverless/index.md | 140 +-
...rs_approval_new_protected_branch_v12_4.png | Bin
...owners_approval_protected_branch_v12_4.png | Bin
.../img/time_tracking_example_v12_2.png | Bin
.../img/time_tracking_sidebar_v8_16.png | Bin
doc/user/project/import/gitea.md | 2 -
doc/user/project/import/github.md | 2 +-
doc/user/project/index.md | 3 +-
.../project/integrations/generic_alerts.md | 8 +-
.../integrations/gitlab_slack_application.md | 3 +-
.../img/embed_metrics_issue_template.png | Bin 0 -> 146220 bytes
.../integrations/img/grafana_panel_v12_5.png | Bin 0 -> 44193 bytes
.../img/grafana_sharing_dialog_v12_5.png | Bin 0 -> 41203 bytes
.../integrations/img/heatmap_panel_type.png | Bin 0 -> 8272 bytes
.../img/http_proxy_access_v12_5.png | Bin 0 -> 47813 bytes
...rometheus_dashboard_anomaly_panel_type.png | Bin 0 -> 41015 bytes
.../img/rendered_grafana_embed_v12_5.png | Bin 0 -> 61194 bytes
.../img/select_query_variables_v12_5.png | Bin 0 -> 7368 bytes
doc/user/project/integrations/jira.md | 3 +-
.../project/integrations/project_services.md | 2 +-
doc/user/project/integrations/prometheus.md | 156 +-
.../prometheus_library/nginx_ingress.md | 2 +-
.../prometheus_library/nginx_ingress_vts.md | 2 +-
doc/user/project/issue_board.md | 2 +-
.../project/issues/associate_zoom_meeting.md | 42 +
doc/user/project/issues/csv_export.md | 4 +-
doc/user/project/issues/design_management.md | 22 +-
doc/user/project/issues/due_dates.md | 2 +-
.../project/issues/img/issue_weight.png} | Bin
.../issues/img/select_all_designs_v12_4.png | Bin 1325569 -> 0 bytes
.../issues/img/zoom-quickaction-button.png | Bin 117097 -> 53418 bytes
.../project/issues/issue_data_and_actions.md | 18 +-
doc/user/project/issues/issue_weight.md | 25 +
doc/user/project/labels.md | 4 +-
.../project/merge_requests/code_quality.md | 14 +-
.../merge_requests/creating_merge_requests.md | 156 +
.../approvals_premium_project_edit_v12_3.png | Bin 21908 -> 0 bytes
.../approvals_premium_project_edit_v12_5.png | Bin 0 -> 21293 bytes
.../img/mr_approvals_by_code_owners_v12_4.png | Bin
doc/user/project/merge_requests/index.md | 658 +-
.../merge_requests/merge_request_approvals.md | 6 +-
.../merge_when_pipeline_succeeds.md | 5 +
.../reviewing_and_managing_merge_requests.md | 251 +
doc/user/project/merge_requests/versions.md | 11 +
doc/user/project/milestones/index.md | 61 +-
doc/user/project/operations/error_tracking.md | 19 +-
doc/user/project/operations/feature_flags.md | 31 +-
.../operations/img/error_details_v12_5.png | Bin 0 -> 522760 bytes
.../operations/img/error_tracking_list.png | Bin 60762 -> 760603 bytes
.../index.md | 16 +
.../lets_encrypt_integration.md | 19 +-
.../getting_started/fork_sample_project.md | 61 +
.../new_or_existing_website.md | 45 +
.../getting_started/pages_bundled_template.md | 29 +
.../project/pages/getting_started_part_one.md | 53 +-
.../project/pages/getting_started_part_two.md | 171 +-
.../pages/img/new_project_for_pages_v12_5.png | Bin 0 -> 71618 bytes
.../pages/img/pages_workflow_v12_5.png | Bin 0 -> 29541 bytes
doc/user/project/pages/index.md | 93 +-
doc/user/project/pages/introduction.md | 35 +-
.../pages/lets_encrypt_for_gitlab_pages.md | 4 +-
.../project/pages/pages_access_control.md | 48 +
doc/user/project/protected_branches.md | 11 +-
doc/user/project/push_options.md | 30 +
.../releases/img/edit_release_page_v12_5.png | Bin 0 -> 150927 bytes
.../milestone_list_with_releases_v12_5.png | Bin 0 -> 45454 bytes
.../img/milestone_with_releases_v12_5.png | Bin 0 -> 67529 bytes
.../project/releases/img/new_tag_12_5.png} | Bin
.../img/release_edit_button_v12_5.png | Bin 0 -> 87472 bytes
.../img/release_with_milestone_v12_5.png | Bin 0 -> 20197 bytes
.../project/releases/img/tags_12_5.png} | Bin
doc/user/project/releases/index.md | 131 +
doc/user/project/repository/file_finder.md | 45 +
.../project/repository/forking_workflow.md | 55 +
.../img/file_finder_find_button.png | Bin
.../repository}/img/file_finder_find_file.png | Bin
.../img/forking_workflow_branch_select.png} | Bin
.../img/forking_workflow_choose_namespace.png | Bin
.../img/forking_workflow_fork_button.png | Bin
.../img/forking_workflow_merge_request.png} | Bin
.../img/forking_workflow_path_taken_error.png | Bin
..._mirroring_copy_ssh_public_key_button.png} | Bin
.../img/repository_mirroring_force_update.png | Bin
...pository_mirroring_pull_settings_lower.png | Bin
...pository_mirroring_pull_settings_upper.png | Bin
.../repository_mirroring_push_settings.png | Bin
doc/user/project/repository/index.md | 5 +-
.../repository/repository_mirroring.md | 430 +
doc/user/project/settings/index.md | 7 +-
doc/user/project/time_tracking.md | 92 +
doc/user/search/advanced_search_syntax.md | 2 +
.../search/img/issue_search_filter_v12_5.png | Bin 0 -> 504590 bytes
doc/user/search/index.md | 11 +-
doc/user/shortcuts.md | 135 +
doc/user/todos.md | 142 +
doc/workflow/README.md | 55 +-
doc/workflow/file_finder.md | 44 +-
doc/workflow/forking_workflow.md | 54 +-
doc/workflow/git_annex.md | 239 +-
doc/workflow/git_lfs.md | 4 +-
doc/workflow/gitlab_flow.md | 329 +-
doc/workflow/issue_weight.md | 24 +-
doc/workflow/lfs/lfs_administration.md | 272 +-
.../lfs/manage_large_binaries_with_git_lfs.md | 265 +-
.../lfs/migrate_from_git_annex_to_git_lfs.md | 255 +-
doc/workflow/notifications.md | 175 +-
doc/workflow/releases.md | 25 +-
doc/workflow/repository_mirroring.md | 427 +-
doc/workflow/shortcuts.md | 133 +-
doc/workflow/time_tracking.md | 90 +-
doc/workflow/timezone.md | 40 +-
doc/workflow/todos.md | 141 +-
doc/workflow/workflow.md | 34 +-
jest.config.js | 39 +-
lib/api/api.rb | 2 +
lib/api/branches.rb | 2 +-
lib/api/commits.rb | 6 +-
lib/api/deployments.rb | 2 +-
lib/api/entities.rb | 66 +-
lib/api/group_clusters.rb | 1 +
lib/api/group_container_repositories.rb | 4 +-
lib/api/group_export.rb | 34 +
lib/api/helpers.rb | 17 +
lib/api/helpers/internal_helpers.rb | 3 +-
lib/api/helpers/pagination.rb | 249 +-
lib/api/helpers/projects_helpers.rb | 3 +-
lib/api/internal/base.rb | 2 +-
lib/api/merge_requests.rb | 12 +-
lib/api/pages_domains.rb | 14 +-
lib/api/project_clusters.rb | 1 +
lib/api/project_container_repositories.rb | 11 +-
lib/api/projects.rb | 6 +-
lib/api/releases.rb | 2 +-
lib/api/settings.rb | 15 +-
lib/api/sidekiq_metrics.rb | 3 +-
.../filter/inline_grafana_metrics_filter.rb | 76 +
.../filter/inline_metrics_redactor_filter.rb | 89 +-
lib/banzai/filter/video_link_filter.rb | 2 +-
lib/banzai/pipeline/ascii_doc_pipeline.rb | 3 +
lib/banzai/pipeline/gfm_pipeline.rb | 1 +
lib/bitbucket/representation/pull_request.rb | 5 +-
lib/container_registry/client.rb | 2 +-
lib/declarative_policy.rb | 9 +-
lib/feature/gitaly.rb | 3 +-
.../post_deployment_migration_generator.rb | 2 +-
lib/gitlab.rb | 12 +
.../cycle_analytics/base_query_builder.rb | 2 +
.../cycle_analytics/data_collector.rb | 2 +
.../cycle_analytics/records_fetcher.rb | 2 +
.../analytics/cycle_analytics/stage_events.rb | 18 +-
.../stage_events/code_stage_start.rb | 2 +-
.../stage_events/issue_created.rb | 2 +-
.../issue_first_mentioned_in_commit.rb | 8 +-
.../stage_events/issue_stage_end.rb | 4 +-
.../stage_events/merge_request_created.rb | 2 +-
...ge_request_first_deployed_to_production.rb | 4 +-
.../merge_request_last_build_finished.rb | 8 +-
.../merge_request_last_build_started.rb | 8 +-
.../stage_events/merge_request_merged.rb | 8 +-
.../stage_events/metrics_based_stage_event.rb | 17 +
.../stage_events/plan_stage_start.rb | 5 +-
.../stage_events/production_stage_end.rb | 2 +-
.../stage_events/simple_stage_event.rb | 13 -
.../stage_events/stage_event.rb | 4 +
lib/gitlab/auth/ip_rate_limiter.rb | 19 +-
lib/gitlab/auth/ldap/config.rb | 8 +
.../legacy_upload_mover.rb | 1 +
.../legacy_uploads_migrator.rb | 2 +-
...populate_untracked_uploads_dependencies.rb | 2 +-
lib/gitlab/ci/ansi2json/converter.rb | 97 +-
lib/gitlab/ci/ansi2json/line.rb | 7 +-
lib/gitlab/ci/ansi2json/state.rb | 4 +-
lib/gitlab/ci/ansi2json/style.rb | 21 +-
lib/gitlab/ci/build/context/base.rb | 35 +
lib/gitlab/ci/build/context/build.rb | 41 +
lib/gitlab/ci/build/context/global.rb | 41 +
lib/gitlab/ci/build/policy/changes.rb | 2 +-
lib/gitlab/ci/build/policy/kubernetes.rb | 2 +-
lib/gitlab/ci/build/policy/refs.rb | 2 +-
lib/gitlab/ci/build/policy/specification.rb | 2 +-
lib/gitlab/ci/build/policy/variables.rb | 4 +-
lib/gitlab/ci/build/rules.rb | 14 +-
lib/gitlab/ci/build/rules/rule.rb | 4 +-
lib/gitlab/ci/build/rules/rule/clause.rb | 2 +-
.../ci/build/rules/rule/clause/changes.rb | 2 +-
.../ci/build/rules/rule/clause/exists.rb | 2 +-
lib/gitlab/ci/build/rules/rule/clause/if.rb | 7 +-
lib/gitlab/ci/config/entry/artifacts.rb | 17 +-
lib/gitlab/ci/config/entry/boolean.rb | 20 +
lib/gitlab/ci/config/entry/commands.rb | 4 +-
lib/gitlab/ci/config/entry/default.rb | 34 +-
lib/gitlab/ci/config/entry/files.rb | 26 +
lib/gitlab/ci/config/entry/job.rb | 67 +-
lib/gitlab/ci/config/entry/key.rb | 45 +-
lib/gitlab/ci/config/entry/need.rb | 44 +
lib/gitlab/ci/config/entry/needs.rb | 55 +
lib/gitlab/ci/config/entry/prefix.rb | 20 +
lib/gitlab/ci/config/entry/root.rb | 5 +-
lib/gitlab/ci/config/entry/rules/rule.rb | 15 +-
lib/gitlab/ci/config/entry/script.rb | 6 +-
lib/gitlab/ci/config/entry/workflow.rb | 25 +
lib/gitlab/ci/config/normalizer.rb | 20 +-
lib/gitlab/ci/pipeline/chain/base.rb | 2 +-
lib/gitlab/ci/pipeline/chain/build.rb | 2 -
lib/gitlab/ci/pipeline/chain/command.rb | 4 +-
.../ci/pipeline/chain/config/content.rb | 59 +
.../ci/pipeline/chain/config/process.rb | 41 +
.../pipeline/chain/evaluate_workflow_rules.rb | 50 +
lib/gitlab/ci/pipeline/chain/populate.rb | 21 +-
.../chain/remove_unwanted_chat_jobs.rb | 6 +-
lib/gitlab/ci/pipeline/chain/seed.rb | 64 +
.../ci/pipeline/chain/validate/config.rb | 33 -
lib/gitlab/ci/pipeline/seed/build.rb | 47 +-
lib/gitlab/ci/pipeline/seed/build/cache.rb | 77 +
lib/gitlab/ci/status/build/failed.rb | 4 +-
.../templates/Jobs/Code-Quality.gitlab-ci.yml | 2 +-
.../DAST-Default-Branch-Deploy.gitlab-ci.yml | 10 +-
.../ci/templates/Jobs/Deploy.gitlab-ci.yml | 2 +-
.../Security/Container-Scanning.gitlab-ci.yml | 13 +-
.../Dependency-Scanning.gitlab-ci.yml | 67 +
.../ci/templates/Security/SAST.gitlab-ci.yml | 69 +-
lib/gitlab/ci/yaml_processor.rb | 46 +-
.../cleanup/orphan_job_artifact_files.rb | 11 +-
lib/gitlab/cluster/lifecycle_events.rb | 77 +-
lib/gitlab/cluster/mixins/puma_cluster.rb | 6 +-
.../cluster/mixins/unicorn_http_server.rb | 19 +-
.../cluster/puma_worker_killer_initializer.rb | 13 +-
lib/gitlab/config/entry/configurable.rb | 29 +-
lib/gitlab/config/entry/factory.rb | 20 +-
lib/gitlab/config/entry/inheritable.rb | 40 +
lib/gitlab/config/entry/node.rb | 4 +
lib/gitlab/config/entry/simplifiable.rb | 11 +-
lib/gitlab/config/entry/validatable.rb | 21 +-
lib/gitlab/config/entry/validators.rb | 39 +-
.../cycle_analytics/group_stage_summary.rb | 7 +-
.../cycle_analytics/summary/group/base.rb | 5 +-
.../cycle_analytics/summary/group/deploy.rb | 3 +-
.../cycle_analytics/summary/group/issue.rb | 16 +-
lib/gitlab/daemon.rb | 9 +-
lib/gitlab/danger/helper.rb | 16 +-
lib/gitlab/danger/teammate.rb | 5 +-
lib/gitlab/data_builder/deployment.rb | 8 +-
lib/gitlab/data_builder/push.rb | 30 +-
lib/gitlab/database/migration_helpers.rb | 17 +-
.../v1/rename_namespaces.rb | 10 +-
.../self_monitoring/project/create_service.rb | 53 +-
lib/gitlab/devise_failure.rb | 8 +
lib/gitlab/error_tracking/detailed_error.rb | 31 +
lib/gitlab/error_tracking/error_event.rb | 11 +
lib/gitlab/etag_caching/router.rb | 4 +-
lib/gitlab/exception_log_formatter.rb | 20 +
lib/gitlab/experimentation.rb | 69 +-
lib/gitlab/favicon.rb | 2 +-
lib/gitlab/file_finder.rb | 17 +-
lib/gitlab/git/commit.rb | 4 -
lib/gitlab/git/repository.rb | 11 +-
lib/gitlab/git/wiki.rb | 8 -
lib/gitlab/git_access_result/custom_action.rb | 6 +-
lib/gitlab/gitaly_client.rb | 27 +-
lib/gitlab/gitaly_client/commit_service.rb | 45 +-
lib/gitlab/gitaly_client/namespace_service.rb | 19 +-
lib/gitlab/gitaly_client/operation_service.rb | 2 +-
lib/gitlab/gitaly_client/wiki_service.rb | 12 -
lib/gitlab/gon_helper.rb | 3 -
lib/gitlab/gpg.rb | 49 +-
.../grape_logging/loggers/exception_logger.rb | 25 +
.../graphql/authorize/instrumentation.rb | 6 +-
lib/gitlab/graphql/connections.rb | 6 +-
.../filterable_array_connection.rb | 17 +
.../keyset/conditions/base_condition.rb | 40 +
.../keyset/conditions/not_null_condition.rb | 57 +
.../keyset/conditions/null_condition.rb | 41 +
.../graphql/connections/keyset/connection.rb | 153 +
.../keyset/legacy_keyset_connection.rb | 66 +
.../graphql/connections/keyset/order_info.rb | 78 +
.../connections/keyset/query_builder.rb | 68 +
.../graphql/connections/keyset_connection.rb | 85 -
lib/gitlab/graphql/filterable_array.rb | 14 +
.../loaders/pipeline_for_sha_loader.rb | 25 -
lib/gitlab/health_checks/master_check.rb | 66 +
lib/gitlab/import_export.rb | 14 +-
lib/gitlab/import_export/config.rb | 5 +-
lib/gitlab/import_export/file_importer.rb | 2 +-
.../import_export/group_import_export.yml | 36 +
.../group_project_object_builder.rb | 11 +-
lib/gitlab/import_export/group_tree_saver.rb | 55 +
lib/gitlab/import_export/import_export.yml | 3 +
.../import_export/project_tree_restorer.rb | 228 +-
.../import_export/project_tree_saver.rb | 31 +-
lib/gitlab/import_export/reader.rb | 27 +-
lib/gitlab/import_export/relation_factory.rb | 20 +-
.../import_export/relation_rename_service.rb | 2 +-
.../import_export/relation_tree_saver.rb | 27 +
lib/gitlab/import_export/saver.rb | 18 +-
lib/gitlab/import_export/shared.rb | 40 +-
lib/gitlab/instrumentation_helper.rb | 44 +
.../kubernetes/config_maps/aws_node_auth.rb | 46 +
lib/gitlab/kubernetes/helm.rb | 4 +-
lib/gitlab/kubernetes/helm/install_command.rb | 2 +-
lib/gitlab/metrics/dashboard/errors.rb | 5 +
lib/gitlab/metrics/dashboard/finder.rb | 3 +
lib/gitlab/metrics/dashboard/processor.rb | 3 +
.../metrics/dashboard/service_selector.rb | 5 +
.../stages/common_metrics_inserter.rb | 2 +-
.../dashboard/stages/grafana_formatter.rb | 224 +
.../stages/project_metrics_inserter.rb | 2 +-
lib/gitlab/metrics/dashboard/url.rb | 46 +-
lib/gitlab/metrics/exporter/web_exporter.rb | 25 +-
.../metrics/requests_rack_middleware.rb | 4 +-
lib/gitlab/pagination/base.rb | 32 +
lib/gitlab/pagination/offset_pagination.rb | 77 +
lib/gitlab/project_authorizations.rb | 30 +-
lib/gitlab/project_template.rb | 3 +-
lib/gitlab/prometheus/internal.rb | 45 +
lib/gitlab/prometheus/metric_group.rb | 16 +-
.../queries/knative_invocation_query.rb | 13 +-
lib/gitlab/quick_actions/issuable_actions.rb | 2 +-
lib/gitlab/quick_actions/issue_actions.rb | 22 +-
lib/gitlab/redis/wrapper.rb | 2 +
lib/gitlab/regex.rb | 15 +-
lib/gitlab/search/found_blob.rb | 54 +-
lib/gitlab/seeder.rb | 65 +
lib/gitlab/serializer/pagination.rb | 5 +-
lib/gitlab/setup_helper.rb | 5 +
lib/gitlab/shell.rb | 12 -
lib/gitlab/sidekiq_daemon/monitor.rb | 6 +
.../sidekiq_logging/structured_logger.rb | 11 +-
lib/gitlab/sidekiq_middleware/metrics.rb | 49 +-
lib/gitlab/slash_commands/command.rb | 1 +
lib/gitlab/slash_commands/issue_comment.rb | 55 +
.../slash_commands/presenters/access.rb | 4 +
.../presenters/issue_comment.rb | 43 +
.../slash_commands/presenters/note_base.rb | 48 +
lib/gitlab/sourcegraph.rb | 26 +
lib/gitlab/sql/union.rb | 2 +-
lib/gitlab/task_helpers.rb | 18 +-
lib/gitlab/tracking.rb | 7 +-
lib/gitlab/usage_data.rb | 24 +-
.../usage_data_counters/web_ide_counter.rb | 14 +-
lib/gitlab/utils/deep_size.rb | 4 +
lib/gitlab/wiki_file_finder.rb | 6 +-
lib/gitlab/workhorse.rb | 1 +
lib/google_api/cloud_platform/client.rb | 4 +-
lib/grafana/client.rb | 14 +-
lib/prometheus/pid_provider.rb | 10 +-
lib/quality/kubernetes_client.rb | 14 +-
lib/quality/test_level.rb | 8 +
lib/sentry/client.rb | 128 +-
lib/tasks/dev.rake | 4 +
lib/tasks/gitlab/graphql.rake | 42 +-
lib/tasks/gitlab/seed.rake | 2 +-
lib/tasks/gitlab/shell.rake | 2 +-
lib/tasks/gitlab/uploads/legacy.rake | 2 +-
locale/ar_SA/gitlab.po | 4 +-
locale/bg/gitlab.po | 4 +-
locale/bn_BD/gitlab.po | 4 +-
locale/bn_IN/gitlab.po | 4 +-
locale/ca_ES/gitlab.po | 4 +-
locale/cs_CZ/gitlab.po | 4 +-
locale/cy_GB/gitlab.po | 4 +-
locale/da_DK/gitlab.po | 4 +-
locale/de/gitlab.po | 4 +-
locale/el_GR/gitlab.po | 4 +-
locale/eo/gitlab.po | 4 +-
locale/es/gitlab.po | 4 +-
locale/et_EE/gitlab.po | 4 +-
locale/fa_IR/gitlab.po | 4 +-
locale/fil_PH/gitlab.po | 4 +-
locale/fr/gitlab.po | 4 +-
locale/gitlab.pot | 1135 +-
locale/gl_ES/gitlab.po | 4 +-
locale/he_IL/gitlab.po | 4 +-
locale/hi_IN/gitlab.po | 4 +-
locale/hr_HR/gitlab.po | 4 +-
locale/hu_HU/gitlab.po | 4 +-
locale/id_ID/gitlab.po | 4 +-
locale/it/gitlab.po | 4 +-
locale/ja/gitlab.po | 4 +-
locale/ka_GE/gitlab.po | 4 +-
locale/ko/gitlab.po | 4 +-
locale/mn_MN/gitlab.po | 4 +-
locale/nb_NO/gitlab.po | 4 +-
locale/nl_NL/gitlab.po | 4 +-
locale/pa_IN/gitlab.po | 4 +-
locale/pl_PL/gitlab.po | 4 +-
locale/pt_BR/gitlab.po | 4 +-
locale/pt_PT/gitlab.po | 4 +-
locale/ro_RO/gitlab.po | 4 +-
locale/ru/gitlab.po | 4 +-
locale/sk_SK/gitlab.po | 4 +-
locale/sq_AL/gitlab.po | 4 +-
locale/sr_CS/gitlab.po | 4 +-
locale/sr_SP/gitlab.po | 4 +-
locale/sv_SE/gitlab.po | 4 +-
locale/sw_KE/gitlab.po | 4 +-
locale/tr_TR/gitlab.po | 4 +-
locale/uk/gitlab.po | 4 +-
locale/vi_VN/gitlab.po | 4 +-
locale/zh_CN/gitlab.po | 4 +-
locale/zh_HK/gitlab.po | 4 +-
locale/zh_TW/gitlab.po | 4 +-
package.json | 20 +-
qa/Gemfile | 9 +-
qa/Gemfile.lock | 13 +-
qa/qa.rb | 24 +
qa/qa/flow/login.rb | 38 +
qa/qa/page/admin/overview/users/show.rb | 10 +
.../settings/component/outbound_requests.rb | 33 +
qa/qa/page/admin/settings/network.rb | 7 +
qa/qa/page/base.rb | 52 +-
qa/qa/page/component/select2.rb | 8 +
qa/qa/page/dashboard/projects.rb | 4 +
qa/qa/page/dashboard/welcome.rb | 17 +
qa/qa/page/element.rb | 10 +-
qa/qa/page/file/shared/commit_button.rb | 4 +
qa/qa/page/group/show.rb | 10 +
qa/qa/page/main/menu.rb | 2 +-
qa/qa/page/project/issue/index.rb | 4 +
qa/qa/page/project/issue/show.rb | 14 +-
qa/qa/page/project/pipeline/index.rb | 12 +
qa/qa/page/project/settings/ci_cd.rb | 2 +
qa/qa/page/project/sub_menus/settings.rb | 9 +
qa/qa/resource/base.rb | 11 +-
qa/qa/resource/group.rb | 5 +-
qa/qa/resource/issue.rb | 4 +
qa/qa/resource/members.rb | 4 +
qa/qa/resource/merge_request.rb | 2 -
qa/qa/resource/project.rb | 5 +-
qa/qa/resource/runner.rb | 1 -
qa/qa/resource/sandbox.rb | 2 +
qa/qa/resource/user.rb | 19 +-
qa/qa/runtime/api/client.rb | 19 +-
qa/qa/runtime/browser.rb | 20 +-
qa/qa/runtime/env.rb | 4 +
qa/qa/runtime/feature.rb | 40 +-
qa/qa/runtime/fixtures.rb | 2 +-
qa/qa/runtime/user.rb | 4 +
qa/qa/scenario/shared_attributes.rb | 1 +
qa/qa/service/docker_run/jenkins.rb | 43 +
qa/qa/service/docker_run/maven.rb | 44 +
.../login_via_instance_wide_saml_sso_spec.rb | 4 +-
.../1_manage/project/dashboard_images_spec.rb | 57 +
.../issue/check_mentions_for_xss_spec.rb | 11 +-
.../2_plan/issue/close_issue_spec.rb | 3 +-
.../collapse_comments_in_discussions_spec.rb | 3 +-
.../2_plan/issue/comment_issue_spec.rb | 3 +-
.../2_plan/issue/create_issue_spec.rb | 22 +-
.../issue/filter_issue_comments_spec.rb | 3 +-
.../2_plan/issue/issue_suggestions_spec.rb | 3 +-
.../browser_ui/2_plan/issue/mentions_spec.rb | 3 +-
.../view_merge_request_diff_patch_spec.rb | 7 +-
.../repository/add_file_template_spec.rb | 19 +-
.../add_list_delete_branches_spec.rb | 26 +-
.../3_create/repository/clone_spec.rb | 6 +-
.../user_views_commit_diff_patch_spec.rb | 9 +-
.../web_ide/add_file_template_spec.rb | 17 +-
.../deploy_key/clone_using_deploy_key_spec.rb | 5 +
.../create_project_with_auto_devops_spec.rb | 3 +-
.../non_devops/performance_bar_spec.rb | 2 +-
qa/qa/specs/loop_runner.rb | 21 +
qa/qa/specs/runner.rb | 2 +
qa/qa/support/api.rb | 16 +-
qa/qa/vendor/github/page/login.rb | 10 +-
qa/qa/vendor/jenkins/page/base.rb | 24 +
qa/qa/vendor/jenkins/page/configure.rb | 48 +
qa/qa/vendor/jenkins/page/configure_job.rb | 62 +
qa/qa/vendor/jenkins/page/login.rb | 31 +
qa/qa/vendor/jenkins/page/new_credentials.rb | 50 +
qa/qa/vendor/jenkins/page/new_job.rb | 38 +
qa/qa/vendor/saml_idp/page/login.rb | 16 +-
qa/spec/page/element_spec.rb | 18 +
qa/spec/resource/base_spec.rb | 2 +
qa/spec/spec_helper.rb | 20 +-
rubocop/cop/rspec/any_instance_of.rb | 78 +
rubocop/rubocop.rb | 1 +
scripts/lint-doc.sh | 3 +-
scripts/notify-slack | 14 -
scripts/review_apps/automated_cleanup.rb | 36 +-
scripts/review_apps/base-config.yaml | 70 +-
scripts/review_apps/review-apps.sh | 98 +-
scripts/static-analysis | 38 +-
scripts/sync-stable-branch.sh | 32 +
scripts/trigger-build | 35 +-
.../abuse_reports_controller_spec.rb | 4 +-
.../admin/clusters_controller_spec.rb | 156 +-
.../admin/identities_controller_spec.rb | 8 +-
.../admin/spam_logs_controller_spec.rb | 6 +-
.../admin/users_controller_spec.rb | 2 +-
.../application_controller_spec.rb | 34 +-
.../concerns/confirm_email_warning_spec.rb | 2 +-
.../concerns/metrics_dashboard_spec.rb | 47 +-
...redirects_for_missing_path_on_tree_spec.rb | 33 +
.../concerns/renders_commits_spec.rb | 60 +
.../concerns/sourcegraph_gon_spec.rb | 118 +
.../authorizations_controller_spec.rb | 5 +-
.../groups/clusters_controller_spec.rb | 146 +-
.../groups/group_links_controller_spec.rb | 114 +
.../groups/milestones_controller_spec.rb | 18 +
spec/controllers/groups_controller_spec.rb | 8 +-
spec/controllers/health_controller_spec.rb | 134 -
.../import/gitlab_controller_spec.rb | 5 +-
.../import/phabricator_controller_spec.rb | 2 +-
.../omniauth_callbacks_controller_spec.rb | 8 +
spec/controllers/metrics_controller_spec.rb | 4 +-
.../projects/blame_controller_spec.rb | 19 +-
.../projects/blob_controller_spec.rb | 15 +-
.../projects/clusters_controller_spec.rb | 146 +-
.../projects/discussions_controller_spec.rb | 16 +-
.../projects/environments_controller_spec.rb | 4 +-
.../error_tracking_controller_spec.rb | 202 +-
.../projects/grafana_api_controller_spec.rb | 71 +
.../projects/issues_controller_spec.rb | 2 +-
.../projects/jobs_controller_spec.rb | 18 +-
.../projects/labels_controller_spec.rb | 18 +
.../projects/mattermosts_controller_spec.rb | 13 +-
.../creations_controller_spec.rb | 4 +-
.../merge_requests/diffs_controller_spec.rb | 22 +-
.../merge_requests_controller_spec.rb | 246 +-
.../projects/mirrors_controller_spec.rb | 4 -
.../projects/notes_controller_spec.rb | 10 +-
.../projects/pages_domains_controller_spec.rb | 62 +-
.../projects/pipelines_controller_spec.rb | 6 +-
.../project_members_controller_spec.rb | 8 +-
.../prometheus/metrics_controller_spec.rb | 4 +-
.../projects/releases_controller_spec.rb | 167 +-
.../serverless/functions_controller_spec.rb | 94 +-
.../settings/ci_cd_controller_spec.rb | 8 +-
.../settings/operations_controller_spec.rb | 3 +-
.../projects/snippets_controller_spec.rb | 12 +-
.../projects/tree_controller_spec.rb | 20 +-
.../projects/usage_ping_controller_spec.rb | 64 +
spec/controllers/projects_controller_spec.rb | 28 +-
.../registrations_controller_spec.rb | 94 +
spec/controllers/sessions_controller_spec.rb | 58 +
spec/controllers/snippets_controller_spec.rb | 12 +-
spec/controllers/users_controller_spec.rb | 46 +-
spec/db/schema_spec.rb | 48 +-
spec/dependencies/omniauth_saml_spec.rb | 4 +-
spec/factories/ci/pipelines.rb | 44 +-
spec/factories/clusters/applications/helm.rb | 9 +
spec/factories/clusters/clusters.rb | 20 +
.../clusters/platforms/kubernetes.rb | 2 +-
spec/factories/clusters/providers/aws.rb | 3 +-
spec/factories/clusters/providers/gcp.rb | 2 +-
spec/factories/commit_statuses.rb | 2 +-
spec/factories/deployments.rb | 4 +
.../error_tracking/detailed_error.rb | 29 +
spec/factories/error_tracking/error_event.rb | 18 +
spec/factories/grafana_integrations.rb | 3 +-
spec/factories/group_group_links.rb | 9 +
spec/factories/issues.rb | 1 +
spec/factories/merge_requests.rb | 13 +
spec/factories/projects.rb | 5 +-
spec/factories/zoom_meetings.rb | 18 +
.../admin/admin_abuse_reports_spec.rb | 24 +
spec/features/admin/admin_projects_spec.rb | 5 +-
spec/features/admin/admin_settings_spec.rb | 31 +-
spec/features/admin/admin_users_spec.rb | 4 +-
spec/features/admin/clusters/eks_spec.rb | 29 +
spec/features/calendar_spec.rb | 10 +-
...installing_applications_shared_examples.rb | 31 +
spec/features/commits_spec.rb | 37 +-
spec/features/container_registry_spec.rb | 2 +-
spec/features/cycle_analytics_spec.rb | 10 +-
spec/features/dashboard/projects_spec.rb | 3 +-
spec/features/explore/groups_spec.rb | 4 +
spec/features/global_search_spec.rb | 4 +-
spec/features/groups/clusters/eks_spec.rb | 35 +
spec/features/groups/clusters/user_spec.rb | 8 +-
...ith_external_authorization_service_spec.rb | 4 +-
spec/features/groups/issues_spec.rb | 4 +
spec/features/groups/milestone_spec.rb | 141 +-
spec/features/groups_spec.rb | 20 +-
spec/features/import/manifest_import_spec.rb | 2 +-
.../internal_references_spec.rb | 4 +-
.../markdown_references/jira_spec.rb | 6 +-
spec/features/issuables/sorting_list_spec.rb | 24 +-
.../filtered_search/dropdown_hint_spec.rb | 11 +-
.../filtered_search/dropdown_release_spec.rb | 55 +
spec/features/issues/notes_on_issues_spec.rb | 2 +-
...r_creates_branch_and_merge_request_spec.rb | 4 +-
...creates_confidential_merge_request_spec.rb | 2 +-
.../issues/user_creates_issue_spec.rb | 13 -
.../issues/user_toggles_subscription_spec.rb | 1 -
spec/features/markdown/metrics_spec.rb | 74 +-
.../maintainer_edits_fork_spec.rb | 6 +-
.../user_accepts_merge_request_spec.rb | 2 +-
...ommits_from_memebers_who_can_merge_spec.rb | 6 +-
.../user_comments_on_diff_spec.rb | 3 +
.../user_creates_image_diff_notes_spec.rb | 3 +
.../user_creates_merge_request_spec.rb | 7 +-
.../user_edits_merge_request_spec.rb | 2 +-
.../merge_request/user_expands_diff_spec.rb | 4 +
.../user_merges_merge_request_spec.rb | 2 +-
...r_merges_only_if_pipeline_succeeds_spec.rb | 4 +-
...user_merges_when_pipeline_succeeds_spec.rb | 2 +-
.../user_posts_diff_notes_spec.rb | 3 +
.../user_resolves_conflicts_spec.rb | 5 +-
...diff_notes_and_discussions_resolve_spec.rb | 6 +
.../user_reverts_merge_request_spec.rb | 6 +-
.../user_sees_avatar_on_diff_notes_spec.rb | 7 +-
.../user_sees_cherry_pick_modal_spec.rb | 2 +-
.../user_sees_deployment_widget_spec.rb | 4 +-
.../merge_request/user_sees_diff_spec.rb | 10 +-
.../user_sees_merge_request_pipelines_spec.rb | 10 +-
.../user_sees_merge_widget_spec.rb | 63 +-
...sees_mr_with_deleted_source_branch_spec.rb | 3 +
...ser_sees_notes_from_forked_project_spec.rb | 2 +-
...sees_pipelines_from_forked_project_spec.rb | 2 +-
.../merge_request/user_sees_pipelines_spec.rb | 2 +-
.../merge_request/user_sees_versions_spec.rb | 4 +
.../user_suggests_changes_on_diff_spec.rb | 3 +
.../user_toggles_whitespace_changes_spec.rb | 3 +
.../merge_request/user_views_diffs_spec.rb | 3 +
.../user_squashes_merge_request_spec.rb | 8 +-
.../milestones/user_views_milestones_spec.rb | 27 +
...late_new_pipeline_vars_with_params_spec.rb | 32 +
spec/features/profile_spec.rb | 2 +-
.../profiles/user_edit_profile_spec.rb | 4 +-
spec/features/project_group_variables_spec.rb | 60 +
.../projects/badges/pipeline_badge_spec.rb | 8 +-
spec/features/projects/blobs/edit_spec.rb | 21 +-
spec/features/projects/clusters/eks_spec.rb | 3 +-
spec/features/projects/clusters/gcp_spec.rb | 6 +-
spec/features/projects/clusters/user_spec.rb | 8 +-
.../projects/commit/cherry_pick_spec.rb | 6 +-
.../commits/user_browses_commits_spec.rb | 10 +-
spec/features/projects/compare_spec.rb | 8 +-
.../projects/environments/environment_spec.rb | 5 +-
.../environments/environments_spec.rb | 4 +-
.../projects/features_visibility_spec.rb | 8 +-
...files_sort_submodules_with_folders_spec.rb | 4 +-
...project_owner_creates_license_file_spec.rb | 3 +-
...eate_license_file_in_empty_project_spec.rb | 2 +-
.../projects/files/user_browses_files_spec.rb | 24 +-
.../files/user_browses_lfs_files_spec.rb | 2 -
.../files/user_creates_directory_spec.rb | 4 +-
.../projects/files/user_creates_files_spec.rb | 20 +-
.../projects/files/user_deletes_files_spec.rb | 4 +-
.../projects/files/user_edits_files_spec.rb | 9 +-
.../files/user_reads_pipeline_status_spec.rb | 4 +-
.../files/user_replaces_files_spec.rb | 4 +-
.../projects/files/user_uploads_files_spec.rb | 4 +-
spec/features/projects/fork_spec.rb | 4 +-
.../features/projects/forks/fork_list_spec.rb | 2 +-
spec/features/projects/graph_spec.rb | 6 -
.../import_export/export_file_spec.rb | 6 +-
.../import_export/import_file_spec.rb | 8 +-
spec/features/projects/jobs_spec.rb | 4 +-
.../projects/labels/search_labels_spec.rb | 2 +-
.../members/member_leaves_project_spec.rb | 2 +-
.../members/user_requests_access_spec.rb | 2 -
.../projects/milestones/milestone_spec.rb | 106 +-
.../projects/pages_lets_encrypt_spec.rb | 49 +-
spec/features/projects/pages_spec.rb | 115 +-
.../projects/pipelines/pipeline_spec.rb | 30 +-
.../projects/pipelines/pipelines_spec.rb | 26 +-
.../settings/operations_settings_spec.rb | 25 +
...er_manages_merge_requests_settings_spec.rb | 23 +
.../user_sees_collaboration_links_spec.rb | 23 +-
.../user_sees_last_commit_ci_status_spec.rb | 8 +-
.../user_sees_setup_shortcut_buttons_spec.rb | 6 +-
spec/features/projects/tree/tree_show_spec.rb | 1 -
spec/features/projects/view_on_env_spec.rb | 10 +-
spec/features/projects_spec.rb | 20 +-
spec/features/raven_js_spec.rb | 27 -
.../user_uses_header_search_field_spec.rb | 26 +-
.../security/project/internal_access_spec.rb | 8 +-
.../security/project/private_access_spec.rb | 8 +-
.../security/project/public_access_spec.rb | 8 +-
spec/features/sentry_js_spec.rb | 28 +
spec/features/signed_commits_spec.rb | 26 +-
.../tags/developer_deletes_tag_spec.rb | 6 +-
spec/features/unsubscribe_links_spec.rb | 2 +-
spec/features/user_sees_revert_modal_spec.rb | 2 +-
.../features/users/anonymous_sessions_spec.rb | 41 +
spec/features/users/login_spec.rb | 2 +-
spec/features/users/signup_spec.rb | 10 +-
spec/finders/abuse_reports_finder_spec.rb | 27 +
spec/finders/branches_finder_spec.rb | 58 +-
.../container_repositories_finder_spec.rb | 58 +-
spec/finders/issues_finder_spec.rb | 14 +
spec/finders/merge_requests_finder_spec.rb | 12 +
spec/finders/projects_finder_spec.rb | 45 +-
.../finders/prometheus_metrics_finder_spec.rb | 144 +
spec/finders/releases_finder_spec.rb | 33 +-
spec/finders/tags_finder_spec.rb | 38 +
spec/finders/todos_finder_spec.rb | 88 +-
spec/fixtures/api/schemas/cluster_status.json | 2 +
.../merge_request_sidebar_extras.json | 2 +
.../api/schemas/error_tracking/error.json | 20 +-
.../error_tracking/error_detailed.json | 45 +
.../error_tracking/error_stack_trace.json | 14 +
.../error_tracking/issue_detailed.json | 11 +
.../error_tracking/issue_stack_trace.json | 11 +
.../api/schemas/public_api/v4/blobs.json | 3 +-
.../public_api/v4/pages_domain/basic.json | 3 +-
.../public_api/v4/pages_domain/detail.json | 3 +-
.../api/schemas/public_api/v4/release.json | 5 +-
.../v4/release/release_for_guest.json | 5 +-
spec/fixtures/api/schemas/release.json | 3 +-
spec/fixtures/grafana/dashboard_response.json | 764 +
.../fixtures/grafana/datasource_response.json | 21 +
.../grafana/expected_grafana_embed.json | 27 +
spec/fixtures/grafana/proxy_response.json | 459 +
.../simplified_dashboard_response.json | 40 +
spec/fixtures/group_export.tar.gz | Bin 0 -> 4551 bytes
.../import_export/{ => complex}/project.json | 79 +-
.../project.json} | 0
.../project.json} | 0
.../project.json} | 0
.../metrics/dashboard/schemas/metrics.json | 1 -
.../metrics/dashboard/schemas/panels.json | 1 -
spec/frontend/api_spec.js | 20 +
.../components/issue_time_estimate_spec.js | 81 +
spec/frontend/boards/issue_card_spec.js | 307 +
spec/frontend/boards/stores/getters_spec.js | 21 +
.../frontend/clusters/clusters_bundle_spec.js | 26 +-
.../clusters/components/applications_spec.js | 157 +-
.../crossplane_provider_stack_spec.js | 78 +
spec/frontend/clusters/services/mock_data.js | 27 +
.../clusters/stores/clusters_store_spec.js | 43 +
.../project_form_group_spec.js.snap | 4 +-
.../__snapshots__/contributors_spec.js.snap | 47 +
.../component/contributors_spec.js | 69 +
.../contributors/store/actions_spec.js | 60 +
.../contributors/store/getters_spec.js | 73 +
.../contributors/store/mutations_spec.js | 40 +
spec/frontend/contributors/utils_spec.js | 21 +
.../components/cluster_form_dropdown_spec.js | 44 +-
.../components/create_eks_cluster_spec.js | 91 +
.../eks_cluster_configuration_form_spec.js | 181 +-
.../components/region_dropdown_spec.js | 55 -
.../service_credentials_form_spec.js | 117 +
.../services/aws_services_facade_spec.js | 152 +
.../eks_cluster/store/actions_spec.js | 248 +-
.../eks_cluster/store/mutations_spec.js | 113 +-
.../gke_cluster_namespace_spec.js | 4 +-
.../init_create_cluster_spec.js | 73 +
.../cycle_analytics/stage_nav_item_spec.js | 44 +-
spec/frontend/environment.js | 6 +
.../components/error_details_spec.js | 105 +
.../components/error_tracking_list_spec.js | 15 +-
.../components/stacktrace_entry_spec.js | 49 +
.../components/stacktrace_spec.js | 45 +
.../store/details/actions_spec.js | 94 +
.../store/details/getters_spec.js | 13 +
.../error_tracking/store/list/getters_spec.js | 33 +
.../store/{ => list}/mutation_spec.js | 4 +-
.../components/error_tracking_form_spec.js | 25 +-
.../store/actions_spec.js | 16 +-
spec/frontend/fixtures/merge_requests.rb | 18 +-
.../fixtures/static/environments_logs.html | 4 +-
.../frontend/fixtures/static/signin_tabs.html | 3 +
spec/frontend/fixtures/u2f.rb | 4 +-
.../grafana_integration_spec.js.snap | 101 +
.../components/grafana_integration_spec.js | 125 +
.../store/mutations_spec.js | 35 +
spec/frontend/helpers/monitor_helper_spec.js | 82 +
.../jobs/__snapshots__/stage_spec.js.snap | 61 +
.../ide/components/jobs/stage_spec.js | 86 +
.../ide/components/preview/clientside_spec.js | 20 +-
spec/frontend/ide/services/index_spec.js | 83 +
.../stores/modules/clientside/actions_spec.js | 39 +
.../issuables_list_app_spec.js.snap | 15 +
.../components/issuable_spec.js | 345 +
.../components/issuables_list_app_spec.js | 410 +
.../issuables_list/issuable_list_test_data.js | 72 +
spec/frontend/issue_show/helpers.js | 10 +
spec/frontend/jobs/components/log/log_spec.js | 4 +-
spec/frontend/jobs/store/utils_spec.js | 20 +-
spec/frontend/lib/utils/chart_utils_spec.js | 11 +
.../lib/utils/datetime_utility_spec.js | 47 +-
.../frontend/lib/utils/number_utility_spec.js | 40 +
spec/frontend/lib/utils/text_utility_spec.js | 13 +
.../monitoring/charts/time_series_spec.js | 154 +-
.../components/charts/anomaly_spec.js | 303 +
.../date_time_picker/date_time_picker_spec.js | 10 +
spec/frontend/monitoring/embed/embed_spec.js | 4 +-
spec/frontend/monitoring/embed/mock_data.js | 4 +-
spec/frontend/monitoring/mock_data.js | 465 +
spec/frontend/monitoring/panel_type_spec.js | 166 +
.../monitoring/store/actions_spec.js | 277 +-
.../monitoring/store/mutations_spec.js | 127 +-
.../monitoring/store/utils_spec.js | 0
.../notes/components/comment_form_spec.js | 331 +
.../components/diff_discussion_header_spec.js | 141 +
.../components/discussion_actions_spec.js | 2 +-
.../notes/components/discussion_notes_spec.js | 6 +-
.../notes/components/note_app_spec.js | 26 +-
spec/frontend/notes/mock_data.js | 1255 ++
.../components/add_request_spec.js | 62 +
.../pipelines/graph/action_component_spec.js | 75 +
.../pipelines/pipeline_triggerer_spec.js | 5 +-
.../pipelines/pipelines_table_row_spec.js | 136 +-
.../pipelines/test_reports/mock_data.js | 123 +
.../test_reports/stores/actions_spec.js | 109 +
.../test_reports/stores/getters_spec.js | 54 +
.../test_reports/stores/mutations_spec.js | 63 +
.../test_reports/test_reports_spec.js | 64 +
.../test_reports/test_suite_table_spec.js | 77 +
.../test_reports/test_summary_spec.js | 78 +
.../test_reports/test_summary_table_spec.js | 54 +
spec/frontend/project_find_file_spec.js | 37 +-
.../components/collapsible_container_spec.js | 53 +-
.../components/table_registry_spec.js | 119 +-
.../releases/detail/components/app_spec.js | 19 +-
.../__snapshots__/release_block_spec.js.snap | 332 -
.../components/release_block_footer_spec.js | 163 +
.../list/components/release_block_spec.js | 33 +-
spec/frontend/releases/mock_data.js | 4 +-
.../directory_download_links_spec.js.snap | 75 +
.../__snapshots__/last_commit_spec.js.snap | 56 +-
.../directory_download_links_spec.js | 29 +
.../repository/components/last_commit_spec.js | 4 +-
.../preview/__snapshots__/index_spec.js.snap | 36 +
.../components/preview/index_spec.js | 49 +
.../table/__snapshots__/row_spec.js.snap | 2 +
.../repository/components/table/index_spec.js | 72 +-
.../repository/components/table/row_spec.js | 25 +-
.../components/tree_content_spec.js | 71 +
spec/frontend/repository/log_tree_spec.js | 27 +-
spec/frontend/repository/pages/index_spec.js | 42 +
spec/frontend/repository/pages/tree_spec.js | 60 +
spec/frontend/repository/utils/commit_spec.js | 30 +
spec/frontend/repository/utils/dom_spec.js | 20 +
spec/frontend/repository/utils/readme_spec.js | 33 +
spec/frontend/repository/utils/title_spec.js | 4 +-
.../raven => frontend/sentry}/index_spec.js | 20 +-
spec/frontend/sentry/sentry_config_spec.js | 214 +
.../assignees/assignee_avatar_link_spec.js | 1 +
.../assignees/collapsed_assignee_list_spec.js | 1 +
.../uncollapsed_assignee_list_spec.js | 1 +
.../__snapshots__/split_button_spec.js.snap | 37 +
.../vue_shared/components/commit_spec.js | 113 +-
.../viewers/image_viewer_spec.js | 45 +
.../components/issue/issue_assignees_spec.js | 191 +-
.../components/notes/placeholder_note_spec.js | 2 +-
.../components/notes/system_note_spec.js | 2 +-
.../vue_shared/components/slot_switch_spec.js | 56 +
.../components/split_button_spec.js | 104 +
.../components/table_pagination_spec.js | 175 +-
.../user_avatar/user_avatar_image_spec.js | 108 +
.../user_popover/user_popover_spec.js | 186 +
spec/graphql/features/authorization_spec.rb | 3 +-
spec/graphql/gitlab_schema_spec.rb | 2 +-
.../merge_requests/set_assignees_spec.rb | 106 +
.../merge_requests/set_labels_spec.rb | 77 +
.../merge_requests/set_locked_spec.rb | 49 +
.../merge_requests/set_milestone_spec.rb | 53 +
.../merge_requests/set_subscription_spec.rb | 42 +
.../graphql/mutations/todos/mark_done_spec.rb | 66 +
spec/graphql/resolvers/base_resolver_spec.rb | 24 +
.../commit_pipelines_resolver_spec.rb | 53 +
.../graphql/resolvers/issues_resolver_spec.rb | 42 +-
spec/graphql/types/base_enum_spec.rb | 24 +
spec/graphql/types/commit_type_spec.rb | 2 +-
.../graphql/types/extended_issue_type_spec.rb | 21 -
spec/graphql/types/issue_sort_enum_spec.rb | 13 +
spec/graphql/types/issue_type_spec.rb | 2 +-
spec/graphql/types/label_type_spec.rb | 2 +-
spec/graphql/types/project_type_spec.rb | 3 +-
spec/graphql/types/tree/blob_type_spec.rb | 2 +-
.../graphql/types/tree/submodule_type_spec.rb | 2 +-
.../types/tree/tree_entry_type_spec.rb | 2 +-
spec/helpers/application_helper_spec.rb | 4 +-
.../application_settings_helper_spec.rb | 23 +
spec/helpers/auth_helper_spec.rb | 17 +
spec/helpers/clusters_helper_spec.rb | 56 +
spec/helpers/dashboard_helper_spec.rb | 63 +-
spec/helpers/environments_helper_spec.rb | 1 +
spec/helpers/gitlab_routing_helper_spec.rb | 6 +
spec/helpers/issuables_helper_spec.rb | 55 +-
spec/helpers/markup_helper_spec.rb | 37 +-
.../projects/error_tracking_helper_spec.rb | 17 +
spec/helpers/projects_helper_spec.rb | 18 +
spec/helpers/releases_helper_spec.rb | 14 +
spec/helpers/search_helper_spec.rb | 47 +
spec/helpers/snippets_helper_spec.rb | 210 +-
spec/helpers/sourcegraph_helper_spec.rb | 64 +
spec/helpers/users_helper_spec.rb | 4 +
spec/initializers/6_validations_spec.rb | 2 +
spec/initializers/action_mailer_hooks_spec.rb | 2 +
spec/initializers/asset_proxy_setting_spec.rb | 2 +
.../attr_encrypted_no_db_connection_spec.rb | 2 +
spec/initializers/database_config_spec.rb | 73 +
.../direct_upload_support_spec.rb | 2 +
spec/initializers/doorkeeper_spec.rb | 2 +
.../fog_google_https_private_urls_spec.rb | 2 +
spec/initializers/lograge_spec.rb | 48 +
.../rest-client-hostname_override_spec.rb | 2 +
spec/initializers/secret_token_spec.rb | 2 +
spec/initializers/settings_spec.rb | 2 +
spec/initializers/trusted_proxies_spec.rb | 2 +
spec/initializers/zz_metrics_spec.rb | 2 +
spec/javascripts/boards/board_card_spec.js | 2 +
.../boards/board_list_common_spec.js | 15 +-
spec/javascripts/boards/board_list_spec.js | 320 +-
.../boards/components/boards_selector_spec.js | 7 +-
.../components/issue_time_estimate_spec.js | 70 -
spec/javascripts/boards/issue_card_spec.js | 292 -
spec/javascripts/bootstrap_jquery_spec.js | 14 +-
.../ajax_variable_list_spec.js | 2 +-
.../diffs/components/diff_file_spec.js | 49 +-
.../diffs/mock_data/diff_file_unreadable.js | 244 +
spec/javascripts/dropzone_input_spec.js | 88 +-
.../frequent_items/components/app_spec.js | 2 +-
spec/javascripts/frequent_items/mock_data.js | 4 +-
.../frequent_items/store/actions_spec.js | 7 +-
.../stat_graph_contributors_graph_spec.js | 152 -
.../graphs/stat_graph_contributors_spec.js | 28 -
.../stat_graph_contributors_util_spec.js | 298 -
.../ide/components/jobs/stage_spec.js | 95 -
.../ide/components/repo_editor_spec.js | 4 +-
.../ide/stores/actions/file_spec.js | 56 +-
.../ide/stores/actions/merge_request_spec.js | 28 +-
.../ide/stores/actions/tree_spec.js | 18 +-
spec/javascripts/ide/stores/getters_spec.js | 57 +-
.../ide/stores/modules/commit/actions_spec.js | 6 +-
spec/javascripts/ide/stores/utils_spec.js | 30 +
spec/javascripts/issue_show/helpers.js | 11 +-
.../lib/utils/tick_formats_spec.js | 40 -
spec/javascripts/merge_request_spec.js | 32 +-
spec/javascripts/merge_request_tabs_spec.js | 14 +-
.../monitoring/charts/heatmap_spec.js | 69 +
.../monitoring/components/dashboard_spec.js | 444 +-
spec/javascripts/monitoring/mock_data.js | 1097 +-
.../javascripts/monitoring/panel_type_spec.js | 79 -
.../shared/prometheus_header_spec.js | 26 +
spec/javascripts/monitoring/utils_spec.js | 38 +-
.../notes/components/comment_form_spec.js | 301 -
.../components/noteable_discussion_spec.js | 114 +-
spec/javascripts/notes/mock_data.js | 1256 +-
.../notes/stores/collapse_utils_spec.js | 10 -
.../pipelines/graph/action_component_spec.js | 81 -
spec/javascripts/raven/raven_config_spec.js | 254 -
spec/javascripts/search_autocomplete_spec.js | 68 +-
.../javascripts/sidebar/subscriptions_spec.js | 21 +
spec/javascripts/signin_tabs_memoizer_spec.js | 46 +
spec/javascripts/syntax_highlight_spec.js | 8 +-
spec/javascripts/test_bundle.js | 33 +-
spec/javascripts/u2f/mock_u2f_device.js | 18 +-
.../content_viewer/content_viewer_spec.js | 26 +
.../diff_viewer/diff_viewer_spec.js | 74 +-
.../viewers/image_diff_viewer_spec.js | 45 +-
.../vue_shared/components/icon_spec.js | 13 +
.../project_selector/project_selector_spec.js | 9 +-
.../user_avatar/user_avatar_image_spec.js | 120 -
.../user_popover/user_popover_spec.js | 167 -
spec/lib/api/helpers/pagination_spec.rb | 401 +-
spec/lib/api/helpers_spec.rb | 14 +
spec/lib/backup/repository_spec.rb | 2 +-
.../banzai/filter/asset_proxy_filter_spec.rb | 2 +
.../inline_grafana_metrics_filter_spec.rb | 71 +
.../inline_metrics_redactor_filter_spec.rb | 54 +-
.../banzai/filter/video_link_filter_spec.rb | 2 +-
.../representation/pull_request_spec.rb | 1 +
spec/lib/container_registry/client_spec.rb | 12 +-
spec/lib/gitlab/asciidoc_spec.rb | 92 +-
spec/lib/gitlab/auth/ldap/auth_hash_spec.rb | 4 +-
spec/lib/gitlab/auth/ldap/config_spec.rb | 19 +
spec/lib/gitlab/auth/ldap/person_spec.rb | 4 +-
.../legacy_upload_mover_spec.rb | 15 +-
.../legacy_uploads_migrator_spec.rb | 12 +-
.../schedule_calculate_wiki_sizes_spec.rb | 2 +-
spec/lib/gitlab/badge/pipeline/status_spec.rb | 2 +-
.../bare_repository_import/importer_spec.rb | 2 +-
spec/lib/gitlab/checks/lfs_integrity_spec.rb | 2 +-
spec/lib/gitlab/ci/ansi2json/style_spec.rb | 1 +
spec/lib/gitlab/ci/ansi2json_spec.rb | 71 +-
.../lib/gitlab/ci/build/context/build_spec.rb | 26 +
.../gitlab/ci/build/context/global_spec.rb | 25 +
.../gitlab/ci/build/policy/variables_spec.rb | 4 +-
spec/lib/gitlab/ci/build/rules/rule_spec.rb | 4 +-
spec/lib/gitlab/ci/build/rules_spec.rb | 14 +-
.../gitlab/ci/config/entry/artifacts_spec.rb | 86 +
spec/lib/gitlab/ci/config/entry/cache_spec.rb | 77 +-
.../gitlab/ci/config/entry/commands_spec.rb | 67 +-
.../gitlab/ci/config/entry/default_spec.rb | 16 +-
spec/lib/gitlab/ci/config/entry/files_spec.rb | 54 +
spec/lib/gitlab/ci/config/entry/job_spec.rb | 31 +-
spec/lib/gitlab/ci/config/entry/key_spec.rb | 98 +-
spec/lib/gitlab/ci/config/entry/need_spec.rb | 36 +
spec/lib/gitlab/ci/config/entry/needs_spec.rb | 84 +
.../lib/gitlab/ci/config/entry/prefix_spec.rb | 28 +
spec/lib/gitlab/ci/config/entry/root_spec.rb | 16 +-
.../gitlab/ci/config/entry/rules/rule_spec.rb | 120 +-
spec/lib/gitlab/ci/config/entry/rules_spec.rb | 57 +-
.../lib/gitlab/ci/config/entry/script_spec.rb | 67 +-
.../gitlab/ci/config/entry/workflow_spec.rb | 76 +
spec/lib/gitlab/ci/config/normalizer_spec.rb | 104 +-
.../gitlab/ci/pipeline/chain/build_spec.rb | 7 +-
.../chain/evaluate_workflow_rules_spec.rb | 60 +
.../gitlab/ci/pipeline/chain/populate_spec.rb | 57 +-
.../chain/remove_unwanted_chat_jobs_spec.rb | 34 +-
.../lib/gitlab/ci/pipeline/chain/seed_spec.rb | 161 +
.../ci/pipeline/chain/validate/config_spec.rb | 148 -
.../ci/pipeline/seed/build/cache_spec.rb | 261 +
.../lib/gitlab/ci/pipeline/seed/build_spec.rb | 104 +-
spec/lib/gitlab/ci/status/composite_spec.rb | 2 +
spec/lib/gitlab/ci/trace/stream_spec.rb | 10 +-
spec/lib/gitlab/ci/yaml_processor_spec.rb | 346 +-
.../cleanup/orphan_job_artifact_files_spec.rb | 9 +-
.../cluster/mixins/puma_cluster_spec.rb | 29 +-
.../mixins/unicorn_http_server_spec.rb | 37 +-
.../lib/gitlab/cycle_analytics/events_spec.rb | 6 +-
.../group_stage_summary_spec.rb | 16 +
.../gitlab/cycle_analytics/usage_data_spec.rb | 2 +-
spec/lib/gitlab/danger/helper_spec.rb | 13 +-
spec/lib/gitlab/danger/teammate_spec.rb | 18 +-
.../gitlab/data_builder/deployment_spec.rb | 7 +
spec/lib/gitlab/data_builder/push_spec.rb | 26 +
.../gitlab/database/migration_helpers_spec.rb | 1 -
.../project/create_service_spec.rb | 9 -
spec/lib/gitlab/devise_failure_spec.rb | 35 +
.../hook/smime_signature_interceptor_spec.rb | 2 +
.../gitlab/exclusive_lease_helpers_spec.rb | 2 +
spec/lib/gitlab/exclusive_lease_spec.rb | 2 +
spec/lib/gitlab/experimentation_spec.rb | 277 +-
.../external_authorization/access_spec.rb | 2 +
.../external_authorization/cache_spec.rb | 2 +
.../external_authorization/client_spec.rb | 2 +
.../external_authorization/logger_spec.rb | 2 +
.../external_authorization/response_spec.rb | 2 +
.../lib/gitlab/external_authorization_spec.rb | 2 +
.../gitlab/fake_application_settings_spec.rb | 2 +
spec/lib/gitlab/favicon_spec.rb | 2 +
spec/lib/gitlab/file_detector_spec.rb | 2 +
spec/lib/gitlab/file_finder_spec.rb | 6 +-
spec/lib/gitlab/fogbugz_import/client_spec.rb | 2 +
.../lib/gitlab/gfm/reference_rewriter_spec.rb | 2 +
spec/lib/gitlab/gfm/uploads_rewriter_spec.rb | 2 +
spec/lib/gitlab/git/commit_spec.rb | 18 +-
spec/lib/gitlab/git_access_spec.rb | 2 +
spec/lib/gitlab/git_access_wiki_spec.rb | 2 +
spec/lib/gitlab/git_ref_validator_spec.rb | 2 +
spec/lib/gitlab/git_spec.rb | 2 +
.../gitlab/gitaly_client/blob_service_spec.rb | 2 +
.../gitaly_client/blobs_stitcher_spec.rb | 2 +
.../gitaly_client/cleanup_service_spec.rb | 2 +
.../gitaly_client/commit_service_spec.rb | 2 +
.../conflict_files_stitcher_spec.rb | 2 +
.../gitaly_client/conflicts_service_spec.rb | 2 +
spec/lib/gitlab/gitaly_client/diff_spec.rb | 2 +
.../gitaly_client/diff_stitcher_spec.rb | 2 +
.../health_check_service_spec.rb | 2 +
.../gitaly_client/operation_service_spec.rb | 8 +-
.../gitlab/gitaly_client/ref_service_spec.rb | 2 +
.../gitaly_client/remote_service_spec.rb | 2 +
.../gitaly_client/repository_service_spec.rb | 2 +
.../gitaly_client/storage_settings_spec.rb | 2 +
spec/lib/gitlab/gitaly_client/util_spec.rb | 2 +
.../gitlab/gitaly_client/wiki_service_spec.rb | 2 +
spec/lib/gitlab/gitaly_client_spec.rb | 4 +
.../github_import/bulk_importing_spec.rb | 2 +
spec/lib/gitlab/github_import/caching_spec.rb | 2 +
spec/lib/gitlab/github_import/client_spec.rb | 2 +
.../importer/diff_note_importer_spec.rb | 2 +
.../importer/diff_notes_importer_spec.rb | 2 +
.../issue_and_label_links_importer_spec.rb | 2 +
.../importer/issue_importer_spec.rb | 2 +
.../importer/issues_importer_spec.rb | 2 +
.../importer/label_links_importer_spec.rb | 2 +
.../importer/labels_importer_spec.rb | 2 +
.../importer/lfs_object_importer_spec.rb | 2 +
.../importer/lfs_objects_importer_spec.rb | 2 +
.../importer/milestones_importer_spec.rb | 2 +
.../importer/note_importer_spec.rb | 2 +
.../importer/notes_importer_spec.rb | 2 +
.../importer/pull_request_importer_spec.rb | 2 +
.../importer/pull_requests_importer_spec.rb | 2 +
.../importer/releases_importer_spec.rb | 2 +
.../importer/repository_importer_spec.rb | 2 +
.../github_import/issuable_finder_spec.rb | 2 +
.../gitlab/github_import/label_finder_spec.rb | 2 +
.../github_import/markdown_text_spec.rb | 2 +
.../github_import/milestone_finder_spec.rb | 2 +
.../gitlab/github_import/page_counter_spec.rb | 2 +
.../github_import/parallel_importer_spec.rb | 2 +
.../github_import/parallel_scheduling_spec.rb | 2 +
.../representation/diff_note_spec.rb | 2 +
.../representation/expose_attribute_spec.rb | 2 +
.../representation/issue_spec.rb | 2 +
.../github_import/representation/note_spec.rb | 2 +
.../representation/pull_request_spec.rb | 2 +
.../representation/to_hash_spec.rb | 2 +
.../github_import/representation/user_spec.rb | 2 +
.../github_import/representation_spec.rb | 2 +
.../github_import/sequential_importer_spec.rb | 2 +
.../gitlab/github_import/user_finder_spec.rb | 2 +
spec/lib/gitlab/github_import_spec.rb | 2 +
spec/lib/gitlab/gl_repository_spec.rb | 2 +
.../gpg/invalid_gpg_signature_updater_spec.rb | 10 +-
spec/lib/gitlab/gpg_spec.rb | 98 +-
.../loggers/exception_logger_spec.rb | 49 +
.../filterable_array_connection_spec.rb | 26 +
.../conditions/not_null_condition_spec.rb | 56 +
.../keyset/conditions/null_condition_spec.rb | 42 +
.../connections/keyset/connection_spec.rb | 281 +
.../keyset/legacy_keyset_connection_spec.rb | 127 +
.../connections/keyset/order_info_spec.rb | 81 +
.../connections/keyset/query_builder_spec.rb | 108 +
.../connections/keyset_connection_spec.rb | 117 -
.../loaders/pipeline_for_sha_loader_spec.rb | 20 -
spec/lib/gitlab/group_search_results_spec.rb | 2 +
.../gitlab/hashed_storage/migrator_spec.rb | 8 +-
.../gitlab/health_checks/master_check_spec.rb | 49 +
spec/lib/gitlab/highlight_spec.rb | 2 +
spec/lib/gitlab/http_io_spec.rb | 2 +
spec/lib/gitlab/http_spec.rb | 2 +
spec/lib/gitlab/i18n_spec.rb | 2 +
spec/lib/gitlab/identifier_spec.rb | 2 +
spec/lib/gitlab/import_export/all_models.yml | 13 +
.../fast_hash_serializer_spec.rb | 4 -
spec/lib/gitlab/import_export/fork_spec.rb | 2 +-
.../group_project_object_builder_spec.rb | 36 +-
.../import_export/group_tree_saver_spec.rb | 180 +
.../import_export/import_export_spec.rb | 6 +-
.../project_tree_restorer_spec.rb | 130 +-
.../import_export/project_tree_saver_spec.rb | 1 -
.../relation_rename_service_spec.rb | 17 +-
.../import_export/relation_tree_saver_spec.rb | 42 +
.../import_export/safe_model_attributes.yml | 13 +-
spec/lib/gitlab/import_export/saver_spec.rb | 2 +-
spec/lib/gitlab/import_export/shared_spec.rb | 2 +-
spec/lib/gitlab/import_sources_spec.rb | 2 +
spec/lib/gitlab/incoming_email_spec.rb | 2 +
.../gitlab/insecure_key_fingerprint_spec.rb | 2 +
.../lib/gitlab/instrumentation_helper_spec.rb | 37 +
spec/lib/gitlab/issuable_metadata_spec.rb | 2 +
spec/lib/gitlab/issuable_sorter_spec.rb | 2 +
.../gitlab/issuables_count_for_state_spec.rb | 2 +
spec/lib/gitlab/job_waiter_spec.rb | 2 +
spec/lib/gitlab/json_logger_spec.rb | 2 +
.../config_maps/aws_node_auth_spec.rb | 33 +
.../kubernetes/helm/install_command_spec.rb | 27 -
spec/lib/gitlab/kubernetes/helm/pod_spec.rb | 2 +-
spec/lib/gitlab/kubernetes_spec.rb | 2 +
spec/lib/gitlab/language_detection_spec.rb | 2 +
spec/lib/gitlab/lazy_spec.rb | 2 +
.../gitlab/metrics/dashboard/finder_spec.rb | 4 +-
.../metrics/dashboard/processor_spec.rb | 8 +
.../dashboard/service_selector_spec.rb | 11 +
.../stages/grafana_formatter_spec.rb | 106 +
spec/lib/gitlab/metrics/dashboard/url_spec.rb | 85 +-
.../metrics/exporter/web_exporter_spec.rb | 78 +-
.../metrics/requests_rack_middleware_spec.rb | 4 +-
.../pagination/offset_pagination_spec.rb | 215 +
.../project_creator_spec.rb | 2 +-
.../lib/gitlab/project_authorizations_spec.rb | 167 +-
.../lib/gitlab/project_search_results_spec.rb | 14 +-
spec/lib/gitlab/project_template_spec.rb | 3 +-
spec/lib/gitlab/prometheus/internal_spec.rb | 108 +
.../queries/knative_invocation_query_spec.rb | 13 +-
spec/lib/gitlab/regex_spec.rb | 19 +
spec/lib/gitlab/search/found_blob_spec.rb | 24 +-
spec/lib/gitlab/shell_spec.rb | 78 +-
.../sidekiq_logging/structured_logger_spec.rb | 2 +-
.../correlation_logger_spec.rb | 2 +-
.../gitlab/sidekiq_middleware/metrics_spec.rb | 113 +-
.../lib/gitlab/slash_commands/command_spec.rb | 5 +
.../slash_commands/issue_comment_spec.rb | 117 +
.../slash_commands/presenters/access_spec.rb | 10 +
.../presenters/issue_comment_spec.rb | 37 +
spec/lib/gitlab/sourcegraph_spec.rb | 66 +
spec/lib/gitlab/sql/recursive_cte_spec.rb | 2 +-
spec/lib/gitlab/sql/union_spec.rb | 4 +-
spec/lib/gitlab/tracking_spec.rb | 19 +-
.../web_ide_counter_spec.rb | 34 +-
spec/lib/gitlab/usage_data_spec.rb | 52 +-
spec/lib/gitlab/user_access_spec.rb | 2 +-
spec/lib/gitlab/utils/deep_size_spec.rb | 6 +
.../gitlab/visibility_level_checker_spec.rb | 2 +
spec/lib/gitlab/wiki_file_finder_spec.rb | 2 +-
spec/lib/gitlab_spec.rb | 42 +
.../google_api/cloud_platform/client_spec.rb | 3 +-
spec/lib/grafana/client_spec.rb | 26 +-
spec/lib/omni_auth/strategies/saml_spec.rb | 2 +
spec/lib/prometheus/pid_provider_spec.rb | 12 +-
spec/lib/quality/helm_client_spec.rb | 20 +
spec/lib/quality/kubernetes_client_spec.rb | 52 +-
spec/lib/quality/test_level_spec.rb | 26 +
spec/lib/sentry/client_spec.rb | 9 +
spec/mailers/abuse_report_mailer_spec.rb | 2 +
spec/mailers/emails/merge_requests_spec.rb | 2 +
spec/mailers/emails/pages_domains_spec.rb | 2 +
spec/mailers/emails/profile_spec.rb | 2 +
spec/mailers/emails/releases_spec.rb | 1 +
spec/mailers/notify_spec.rb | 2 +
spec/mailers/repository_check_mailer_spec.rb | 2 +
...nfidential_note_events_on_services_spec.rb | 4 +-
spec/migrations/active_record/schema_spec.rb | 2 +
.../add_default_and_free_plans_spec.rb | 34 +
.../add_foreign_keys_to_todos_spec.rb | 2 +
...to_project_mirror_data_foreign_key_spec.rb | 2 +
...es_access_level_to_project_feature_spec.rb | 2 +
.../add_pipeline_build_foreign_key_spec.rb | 2 +
...int_to_project_features_project_id_spec.rb | 2 +
...mmits_count_for_merge_request_diff_spec.rb | 2 +
...ll_store_project_full_path_in_repo_spec.rb | 8 +-
...ault_value_for_dsa_key_restriction_spec.rb | 2 +
.../cleanup_build_stage_migration_spec.rb | 2 +
.../cleanup_environments_external_url_spec.rb | 2 +
.../cleanup_stages_position_migration_spec.rb | 2 +
...ssing_namespace_for_internal_users_spec.rb | 2 +
.../drop_duplicate_protected_tags_spec.rb | 2 +
...nqueue_verify_pages_domain_workers_spec.rb | 2 +
...l_empty_finished_at_in_deployments_spec.rb | 2 +
spec/migrations/fill_file_store_spec.rb | 4 +-
..._productivity_analytics_start_date_spec.rb | 39 +
.../fix_wrong_pages_access_level_spec.rb | 4 +-
.../generate_lets_encrypt_private_key_spec.rb | 2 +
.../generate_missing_routes_spec.rb | 2 +
...ter_configure_worker_sidekiq_queue_spec.rb | 2 +
...reate_trace_artifact_sidekiq_queue_spec.rb | 2 +
..._legacy_artifacts_to_job_artifacts_spec.rb | 4 +-
...bject_storage_upload_sidekiq_queue_spec.rb | 2 +
...ate_storage_migrator_sidekiq_queue_spec.rb | 2 +
...ne_for_merge_request_sidekiq_queue_spec.rb | 2 +
.../migrations/move_limits_from_plans_spec.rb | 37 +
..._empty_extern_uid_auth0_identities_spec.rb | 2 +
...ove_empty_github_service_templates_spec.rb | 55 +
.../remove_redundant_pipeline_stages_spec.rb | 2 +
...reschedule_builds_stages_migration_spec.rb | 2 +
...mmits_count_for_merge_request_diff_spec.rb | 2 +
...dule_digest_personal_access_tokens_spec.rb | 4 +-
...time_for_pages_domain_certificates_spec.rb | 4 +-
.../schedule_runners_token_encryption_spec.rb | 2 +
...nfidential_note_events_on_webhooks_spec.rb | 4 +-
.../schedule_stages_index_migration_spec.rb | 2 +
.../schedule_sync_issuables_state_id_spec.rb | 4 +-
.../schedule_to_archive_legacy_traces_spec.rb | 4 +-
.../migrations/truncate_user_fullname_spec.rb | 2 +
.../cycle_analytics/project_stage_spec.rb | 10 +-
spec/models/application_setting_spec.rb | 79 +
spec/models/aws/role_spec.rb | 52 +
spec/models/ci/build_spec.rb | 143 +-
spec/models/ci/build_trace_chunk_spec.rb | 54 +-
spec/models/ci/pipeline_spec.rb | 319 +-
.../applications/cert_manager_spec.rb | 2 +-
.../clusters/applications/crossplane_spec.rb | 57 +
.../applications/elastic_stack_spec.rb | 179 +
.../clusters/applications/ingress_spec.rb | 38 +-
spec/models/clusters/cluster_spec.rb | 160 +-
.../clusters/clusters_hierarchy_spec.rb | 40 +-
spec/models/clusters/providers/aws_spec.rb | 62 +-
spec/models/clusters/providers/gcp_spec.rb | 16 +-
spec/models/commit_status_spec.rb | 4 +-
.../concerns/deployment_platform_spec.rb | 20 +-
spec/models/concerns/from_union_spec.rb | 6 +-
spec/models/concerns/noteable_spec.rb | 44 -
spec/models/concerns/redactable_spec.rb | 38 -
spec/models/concerns/subscribable_spec.rb | 56 +
spec/models/container_repository_spec.rb | 32 +
spec/models/deployment_merge_request_spec.rb | 14 +
spec/models/deployment_spec.rb | 80 +
spec/models/environment_spec.rb | 189 +-
spec/models/environment_status_spec.rb | 2 +-
.../project_error_tracking_setting_spec.rb | 22 +
spec/models/evidence_spec.rb | 2 +-
spec/models/grafana_integration_spec.rb | 31 +
spec/models/group_group_link_spec.rb | 36 +
spec/models/group_spec.rb | 122 +
spec/models/hooks/system_hook_spec.rb | 2 +-
spec/models/issue_spec.rb | 13 +
spec/models/lfs_object_spec.rb | 12 +
spec/models/merge_request_diff_spec.rb | 8 +
spec/models/merge_request_spec.rb | 262 +-
spec/models/milestone_spec.rb | 11 +
spec/models/namespace_spec.rb | 38 +
spec/models/personal_snippet_spec.rb | 19 +
spec/models/project_import_state_spec.rb | 2 +-
.../chat_message/pipeline_message_spec.rb | 39 +
.../chat_message/push_message_spec.rb | 6 +-
.../project_services/data_fields_spec.rb | 18 +
.../project_services/irker_service_spec.rb | 2 +-
.../prometheus_service_spec.rb | 31 +
spec/models/project_snippet_spec.rb | 21 +
spec/models/project_spec.rb | 82 +-
spec/models/release_spec.rb | 20 +-
spec/models/releases/source_spec.rb | 2 +-
spec/models/remote_mirror_spec.rb | 2 +-
spec/models/service_spec.rb | 20 +
spec/models/shard_spec.rb | 3 +-
spec/models/snippet_spec.rb | 37 -
spec/models/spam_log_spec.rb | 2 +-
spec/models/todo_spec.rb | 47 +
spec/models/user_spec.rb | 24 +-
spec/models/wiki_page_spec.rb | 11 -
spec/models/zoom_meeting_spec.rb | 154 +
.../application_setting/term_policy_spec.rb | 2 +
spec/policies/base_policy_spec.rb | 43 +-
spec/policies/ci/build_policy_spec.rb | 2 +
spec/policies/ci/pipeline_policy_spec.rb | 2 +
.../ci/pipeline_schedule_policy_spec.rb | 2 +
spec/policies/ci/trigger_policy_spec.rb | 2 +
spec/policies/clusters/cluster_policy_spec.rb | 2 +
spec/policies/deploy_key_policy_spec.rb | 2 +
spec/policies/deploy_token_policy_spec.rb | 2 +
spec/policies/environment_policy_spec.rb | 2 +
spec/policies/global_policy_spec.rb | 2 +
spec/policies/group_policy_spec.rb | 2 +
spec/policies/issuable_policy_spec.rb | 2 +
spec/policies/issue_policy_spec.rb | 2 +
spec/policies/merge_request_policy_spec.rb | 2 +
spec/policies/namespace_policy_spec.rb | 2 +
spec/policies/note_policy_spec.rb | 2 +
spec/policies/personal_snippet_policy_spec.rb | 32 +-
spec/policies/project_policy_spec.rb | 27 +
spec/policies/project_snippet_policy_spec.rb | 2 +
spec/policies/protected_branch_policy_spec.rb | 2 +
.../resource_label_event_policy_spec.rb | 2 +
spec/policies/user_policy_spec.rb | 2 +
spec/presenters/ci/bridge_presenter_spec.rb | 2 +
spec/presenters/ci/build_presenter_spec.rb | 4 +-
.../ci/build_runner_presenter_spec.rb | 2 +
.../ci/group_variable_presenter_spec.rb | 2 +
spec/presenters/ci/pipeline_presenter_spec.rb | 2 +
spec/presenters/ci/trigger_presenter_spec.rb | 2 +
spec/presenters/ci/variable_presenter_spec.rb | 2 +
.../clusters/cluster_presenter_spec.rb | 2 +
.../commit_status_presenter_spec.rb | 2 +
.../metric_presenter_spec.rb | 2 +
.../group_clusterable_presenter_spec.rb | 6 +
.../presenters/group_member_presenter_spec.rb | 2 +
.../instance_clusterable_presenter_spec.rb | 37 +
.../merge_request_presenter_spec.rb | 2 +
.../project_clusterable_presenter_spec.rb | 6 +
.../project_member_presenter_spec.rb | 2 +
spec/presenters/project_presenter_spec.rb | 62 +-
.../settings/deploy_keys_presenter_spec.rb | 2 +
spec/presenters/release_presenter_spec.rb | 101 +
spec/requests/api/access_requests_spec.rb | 2 +
spec/requests/api/applications_spec.rb | 2 +
spec/requests/api/avatar_spec.rb | 2 +
spec/requests/api/award_emoji_spec.rb | 2 +
spec/requests/api/badges_spec.rb | 2 +
spec/requests/api/boards_spec.rb | 2 +
spec/requests/api/branches_spec.rb | 23 +-
spec/requests/api/broadcast_messages_spec.rb | 2 +
spec/requests/api/commit_statuses_spec.rb | 6 +-
spec/requests/api/commits_spec.rb | 45 +-
spec/requests/api/deploy_keys_spec.rb | 2 +
spec/requests/api/deployments_spec.rb | 91 +-
spec/requests/api/discussions_spec.rb | 2 +
spec/requests/api/doorkeeper_access_spec.rb | 2 +
spec/requests/api/environments_spec.rb | 2 +
spec/requests/api/events_spec.rb | 2 +
spec/requests/api/features_spec.rb | 17 +-
spec/requests/api/files_spec.rb | 2 +
.../graphql/current_user/todos_query_spec.rb | 48 +
.../api/graphql/current_user_query_spec.rb | 33 +
.../api/graphql/gitlab_schema_spec.rb | 2 +
.../merge_requests/set_assignees_spec.rb | 134 +
.../merge_requests/set_labels_spec.rb | 108 +
.../merge_requests/set_locked_spec.rb | 79 +
.../merge_requests/set_milestone_spec.rb | 66 +
.../merge_requests/set_subscription_spec.rb | 63 +
.../mutations/merge_requests/set_wip_spec.rb | 2 +
.../graphql/mutations/todos/mark_done_spec.rb | 97 +
.../api/graphql/project/issues_spec.rb | 141 +-
.../api/graphql/project/merge_request_spec.rb | 2 +
.../api/graphql/project_query_spec.rb | 2 +
spec/requests/api/group_boards_spec.rb | 2 +
spec/requests/api/group_clusters_spec.rb | 21 +-
.../api/group_container_repositories_spec.rb | 10 +-
spec/requests/api/group_export_spec.rb | 94 +
spec/requests/api/group_milestones_spec.rb | 2 +
spec/requests/api/group_variables_spec.rb | 2 +
spec/requests/api/groups_spec.rb | 2 +
spec/requests/api/helpers_spec.rb | 2 +
spec/requests/api/import_github_spec.rb | 2 +
spec/requests/api/internal/base_spec.rb | 12 +-
spec/requests/api/jobs_spec.rb | 10 +-
spec/requests/api/keys_spec.rb | 2 +
spec/requests/api/labels_spec.rb | 2 +
spec/requests/api/lint_spec.rb | 2 +
spec/requests/api/markdown_spec.rb | 2 +
spec/requests/api/members_spec.rb | 16 +-
spec/requests/api/merge_request_diffs_spec.rb | 2 +
spec/requests/api/merge_requests_spec.rb | 75 +-
spec/requests/api/namespaces_spec.rb | 2 +
spec/requests/api/notes_spec.rb | 2 +
.../api/notification_settings_spec.rb | 2 +
spec/requests/api/oauth_tokens_spec.rb | 2 +
.../api/pages/internal_access_spec.rb | 2 +
.../requests/api/pages/private_access_spec.rb | 2 +
spec/requests/api/pages/public_access_spec.rb | 2 +
spec/requests/api/pages_domains_spec.rb | 96 +-
spec/requests/api/pipeline_schedules_spec.rb | 2 +
spec/requests/api/pipelines_spec.rb | 2 +-
spec/requests/api/project_clusters_spec.rb | 21 +-
.../project_container_repositories_spec.rb | 9 +
spec/requests/api/project_events_spec.rb | 2 +
spec/requests/api/project_export_spec.rb | 4 +-
spec/requests/api/project_hooks_spec.rb | 2 +
spec/requests/api/project_import_spec.rb | 4 +-
spec/requests/api/project_milestones_spec.rb | 2 +
spec/requests/api/project_snapshots_spec.rb | 2 +
spec/requests/api/project_snippets_spec.rb | 2 +
spec/requests/api/project_templates_spec.rb | 2 +
spec/requests/api/projects_spec.rb | 119 +-
spec/requests/api/protected_branches_spec.rb | 2 +
spec/requests/api/protected_tags_spec.rb | 2 +
spec/requests/api/releases_spec.rb | 2 +
spec/requests/api/repositories_spec.rb | 2 +
spec/requests/api/runner_spec.rb | 6 +-
spec/requests/api/runners_spec.rb | 2 +
spec/requests/api/search_spec.rb | 3 +
spec/requests/api/services_spec.rb | 6 +-
spec/requests/api/settings_spec.rb | 82 +-
spec/requests/api/sidekiq_metrics_spec.rb | 6 +
spec/requests/api/snippets_spec.rb | 2 +
spec/requests/api/system_hooks_spec.rb | 2 +
spec/requests/api/tags_spec.rb | 2 +
spec/requests/api/templates_spec.rb | 2 +
spec/requests/api/todos_spec.rb | 2 +
spec/requests/api/triggers_spec.rb | 2 +
spec/requests/api/users_spec.rb | 34 +-
spec/requests/api/variables_spec.rb | 2 +
spec/requests/api/version_spec.rb | 2 +
spec/requests/api/wikis_spec.rb | 2 +
spec/requests/git_http_spec.rb | 36 +-
.../groups/milestones_controller_spec.rb | 2 +
.../registry/repositories_controller_spec.rb | 36 +
spec/requests/health_controller_spec.rb | 227 +
spec/requests/jwt_controller_spec.rb | 2 +
spec/requests/lfs_locks_api_spec.rb | 2 +
spec/requests/oauth_tokens_spec.rb | 2 +
spec/requests/openid_connect_spec.rb | 2 +
.../projects/cycle_analytics_events_spec.rb | 10 +-
spec/requests/rack_attack_global_spec.rb | 4 +-
spec/requests/request_profiler_spec.rb | 2 +
spec/routing/admin_routing_spec.rb | 2 +
spec/routing/environments_spec.rb | 2 +
spec/routing/group_routing_spec.rb | 2 +
spec/routing/import_routing_spec.rb | 2 +
spec/routing/notifications_routing_spec.rb | 2 +
spec/routing/openid_connect_spec.rb | 2 +
spec/routing/project_routing_spec.rb | 8 +
spec/routing/routing_spec.rb | 29 +
.../avoid_break_from_strong_memoize_spec.rb | 2 +
.../cop/avoid_return_from_blocks_spec.rb | 2 +
spec/rubocop/cop/destroy_all_spec.rb | 2 +
.../cop/gitlab/finder_with_find_by_spec.rb | 2 +
spec/rubocop/cop/gitlab/httparty_spec.rb | 2 +
.../module_with_instance_variables_spec.rb | 2 +
.../cop/gitlab/predicate_memoization_spec.rb | 2 +
.../group_public_or_visible_to_user_spec.rb | 2 +
.../cop/include_sidekiq_worker_spec.rb | 2 +
...ine_break_around_conditional_block_spec.rb | 2 +
.../add_concurrent_foreign_key_spec.rb | 2 +
.../migration/add_concurrent_index_spec.rb | 2 +
.../cop/migration/add_reference_spec.rb | 2 +
.../cop/migration/add_timestamps_spec.rb | 2 +
spec/rubocop/cop/migration/datetime_spec.rb | 2 +
spec/rubocop/cop/migration/hash_index_spec.rb | 2 +
.../cop/migration/remove_column_spec.rb | 2 +
.../migration/remove_concurrent_index_spec.rb | 2 +
.../cop/migration/remove_index_spec.rb | 2 +
...reversible_add_column_with_default_spec.rb | 2 +
.../migration/safer_boolean_column_spec.rb | 2 +
spec/rubocop/cop/migration/timestamps_spec.rb | 2 +
.../update_column_in_batches_spec.rb | 2 +
.../cop/migration/update_large_table_spec.rb | 2 +
spec/rubocop/cop/project_path_helper_spec.rb | 2 +
.../rubocop/cop/rspec/any_instance_of_spec.rb | 61 +
spec/rubocop/cop/rspec/env_assignment_spec.rb | 2 +
.../factories_in_migration_specs_spec.rb | 2 +
.../rubocop/cop/sidekiq_options_queue_spec.rb | 2 +
spec/serializers/blob_entity_spec.rb | 12 +-
.../serializers/diff_file_base_entity_spec.rb | 15 +-
spec/serializers/diff_file_entity_spec.rb | 7 +-
.../issuable_sidebar_extras_entity_spec.rb | 20 +
.../job_artifact_report_entity_spec.rb | 2 +-
.../merge_request_diff_entity_spec.rb | 19 +-
.../merge_request_widget_entity_spec.rb | 22 +
.../pipeline_details_entity_spec.rb | 2 +-
spec/serializers/pipeline_serializer_spec.rb | 2 +-
...rge_when_pipeline_succeeds_service_spec.rb | 5 +-
.../ci/cancel_user_pipelines_service_spec.rb | 2 +-
.../ci/create_pipeline_service/cache_spec.rb | 168 +
.../ci/create_pipeline_service/rules_spec.rb | 272 +-
.../ci/create_pipeline_service_spec.rb | 114 +-
.../ci/find_exposed_artifacts_service_spec.rb | 147 +
.../ci/process_pipeline_service_spec.rb | 22 +-
spec/services/ci/register_job_service_spec.rb | 51 +
.../applications/create_service_spec.rb | 28 +
.../aws/fetch_credentials_service_spec.rb | 68 +
.../aws/finalize_creation_service_spec.rb | 124 +
.../clusters/aws/provision_service_spec.rb | 131 +
.../clusters/aws/proxy_service_spec.rb | 210 +
.../verify_provision_status_service_spec.rb | 76 +
.../services/clusters/destroy_service_spec.rb | 56 +
...create_or_update_namespace_service_spec.rb | 2 +
..._or_update_service_account_service_spec.rb | 45 +
spec/services/clusters/update_service_spec.rb | 127 +
.../assigns_merge_params_spec.rb | 2 +
spec/services/create_branch_service_spec.rb | 15 +
.../deployments/after_create_service_spec.rb | 34 +
.../link_merge_requests_service_spec.rb | 121 +
.../deployments/update_service_spec.rb | 52 +-
.../issue_details_service_spec.rb | 48 +
.../issue_latest_event_service_spec.rb | 48 +
.../list_issues_service_spec.rb | 91 +-
.../list_projects_service_spec.rb | 2 +-
.../services/git/branch_hooks_service_spec.rb | 2 +-
spec/services/git/branch_push_service_spec.rb | 20 +-
spec/services/groups/destroy_service_spec.rb | 18 +-
.../groups/group_links/create_service_spec.rb | 119 +
.../group_links/destroy_service_spec.rb | 63 +
.../import_export/export_service_spec.rb | 55 +
spec/services/groups/transfer_service_spec.rb | 30 +-
spec/services/groups/update_service_spec.rb | 61 +-
.../import_export_clean_up_service_spec.rb | 2 +-
spec/services/issues/close_service_spec.rb | 6 +-
spec/services/issues/update_service_spec.rb | 40 +-
.../services/issues/zoom_link_service_spec.rb | 172 +-
spec/services/members/destroy_service_spec.rb | 2 +-
.../add_todo_when_build_fails_service_spec.rb | 4 +-
.../merge_requests/build_service_spec.rb | 34 +-
.../merge_requests/close_service_spec.rb | 2 +-
.../create_from_issue_service_spec.rb | 24 +-
.../merge_requests/create_service_spec.rb | 6 +-
.../merge_requests/ff_merge_service_spec.rb | 57 +-
.../merge_requests/merge_service_spec.rb | 106 +-
.../merge_to_ref_service_spec.rb | 8 +-
.../push_options_handler_service_spec.rb | 10 +-
.../merge_requests/rebase_service_spec.rb | 2 +-
.../merge_requests/refresh_service_spec.rb | 80 +-
.../merge_requests/reopen_service_spec.rb | 2 +-
...ed_discussion_notification_service_spec.rb | 2 +-
.../merge_requests/update_service_spec.rb | 20 +-
.../grafana_metric_embed_service_spec.rb | 177 +
.../project_dashboard_service_spec.rb | 3 +-
.../system_dashboard_service_spec.rb | 3 +-
.../statistics_refresher_service_spec.rb | 2 +-
spec/services/notification_service_spec.rb | 25 +-
.../projects/after_rename_service_spec.rb | 2 +-
.../delete_tags_service_spec.rb | 47 +-
.../services/projects/destroy_service_spec.rb | 18 +-
spec/services/projects/fork_service_spec.rb | 2 +-
.../base_attachment_service_spec.rb | 56 +
.../migrate_attachments_service_spec.rb | 32 +-
.../migrate_repository_service_spec.rb | 2 +-
.../hashed_storage/migration_service_spec.rb | 22 +-
.../rollback_attachments_service_spec.rb | 2 +-
.../rollback_repository_service_spec.rb | 2 +-
.../hashed_storage/rollback_service_spec.rb | 17 +-
.../import_export/export_service_spec.rb | 2 +-
.../lfs_pointers/lfs_link_service_spec.rb | 30 +-
spec/services/projects/update_service_spec.rb | 4 +-
spec/services/system_note_service_spec.rb | 252 +-
.../system_notes/issuables_service_spec.rb | 2 +-
.../merge_requests_service_spec.rb | 243 +
spec/services/users/signup_service_spec.rb | 64 +
spec/services/zoom_notes_service_spec.rb | 81 -
spec/sidekiq/cron/job_gem_dependency_spec.rb | 2 +
spec/spec_helper.rb | 5 +
spec/support/capybara.rb | 6 +-
...uth_callbacks_controller_shared_context.rb | 8 +-
.../test_generation.rb | 2 +-
spec/support/database_cleaner.rb | 33 +
spec/support/generate-seed-repo-rb | 7 +-
.../helpers/access_matchers_helpers.rb | 95 +
.../helpers/cycle_analytics_helpers.rb | 2 +-
.../helpers/filtered_search_helpers.rb | 4 +
spec/support/helpers/grafana_api_helpers.rb | 41 +
spec/support/helpers/graphql_helpers.rb | 21 +-
spec/support/helpers/kubernetes_helpers.rb | 218 +-
spec/support/helpers/login_helpers.rb | 2 +-
spec/support/helpers/smime_helper.rb | 2 +
spec/support/helpers/stub_experiments.rb | 14 +-
spec/support/helpers/stub_gitlab_calls.rb | 9 +-
spec/support/helpers/test_env.rb | 41 +-
spec/support/import_export/common_util.rb | 7 +
.../matchers/access_matchers_for_request.rb | 53 +
.../matchers/access_matchers_generic.rb | 66 +
spec/support/matchers/db_schema_matchers.rb | 32 +
.../prepare-gitlab-git-test-for-commit | 1 +
.../ci/auto_merge_merge_requests_examples.rb | 40 +
.../container_repositories_shared_examples.rb | 8 +
.../cycle_analytics_event_shared_examples.rb | 3 +-
.../cycle_analytics_stage_shared_examples.rb | 137 +-
...rchive_download_buttons_shared_examples.rb | 7 +-
spec/support/shared_examples/file_finder.rb | 10 +-
.../graphql/connection_paged_nodes.rb | 28 +
.../graphql/sort_enum_shared_examples.rb | 7 +
.../entry/key_validations_shared_examples.rb | 81 +
.../config/inheritable_shared_examples.rb | 105 +
...equests_rendering_a_single_diff_version.rb | 17 +
.../cluster_application_helm_cert_examples.rb | 4 -
.../issuable_shared_examples.rb | 0
.../concerns/redactable_shared_examples.rb | 39 +
.../models/with_uploads_shared_examples.rb | 2 +-
.../zoom_quick_actions_shared_examples.rb | 43 +-
.../merge_quick_action_shared_examples.rb | 2 +-
.../requests/api/discussions.rb | 23 +
.../shared_examples/requests/api/notes.rb | 2 +-
.../requests/rack_attack_shared_examples.rb | 4 +-
.../serializers/diff_file_entity_examples.rb | 33 +-
.../error_tracking_service_shared_examples.rb | 89 +
.../updating_mentions_shared_examples.rb | 26 +-
spec/support/sidekiq.rb | 21 +-
spec/support/unpack-gitlab-git-test | 6 +-
spec/tasks/gitlab/shell_rake_spec.rb | 2 +-
spec/tasks/gitlab/task_helpers_spec.rb | 27 +-
.../background_move_worker_spec.rb | 8 +-
.../integrations.html.haml_spec.rb | 34 +
.../devise/sessions/new.html.haml_spec.rb | 71 +
spec/views/layouts/_head.html.haml_spec.rb | 2 +-
.../preferences/show.html.haml_spec.rb | 72 +
spec/views/profiles/show.html.haml_spec.rb | 1 +
.../clusters/gcp/_form.html.haml_spec.rb | 38 +
.../_confirm_rollback_modal_spec.html_spec.rb | 2 +-
.../merge_requests/_commits.html.haml_spec.rb | 2 +-
.../pages_domains/show.html.haml_spec.rb | 34 -
spec/views/projects/show.html.haml_spec.rb | 41 +
.../tree/_tree_header.html.haml_spec.rb | 2 +
.../projects/tree/show.html.haml_spec.rb | 48 +-
spec/workers/cluster_provision_worker_spec.rb | 13 +-
spec/workers/every_sidekiq_worker_spec.rb | 37 +-
.../expire_build_artifacts_worker_spec.rb | 51 -
spec/workers/group_export_worker_spec.rb | 29 +
.../hashed_storage/migrator_worker_spec.rb | 2 +-
.../hashed_storage/rollbacker_worker_spec.rb | 2 +-
spec/workers/merge_worker_spec.rb | 1 +
spec/workers/new_note_worker_spec.rb | 19 +-
...ges_domain_ssl_renewal_cron_worker_spec.rb | 2 +-
spec/workers/pipeline_schedule_worker_spec.rb | 2 +-
spec/workers/process_commit_worker_spec.rb | 3 +-
spec/workers/project_cache_worker_spec.rb | 2 +-
.../remove_expired_group_links_worker_spec.rb | 57 +-
.../single_repository_worker_spec.rb | 8 +-
spec/workers/stuck_ci_jobs_worker_spec.rb | 21 +-
spec/workers/stuck_merge_jobs_worker_spec.rb | 2 +-
.../wait_for_cluster_creation_worker_spec.rb | 15 +-
vendor/aws/cloudformation/eks_cluster.yaml | 340 +
vendor/crossplane/values.yaml | 0
vendor/elastic_stack/values.yaml | 47 +
vendor/gitignore/C++.gitignore | 0
vendor/gitignore/Java.gitignore | 0
vendor/ingress/modsecurity.conf | 274 +
.../serverless_framework.tar.gz | Bin 0 -> 92193 bytes
yarn.lock | 1654 +-
2910 files changed, 92827 insertions(+), 25855 deletions(-)
delete mode 100644 .gitlab/ci/notifications.gitlab-ci.yml
create mode 100644 .gitlab/ci/releases.gitlab-ci.yml
create mode 100644 Guardfile
create mode 100644 app/assets/images/cluster_app_logos/crossplane.png
create mode 100644 app/assets/images/cluster_app_logos/elastic_stack.png
create mode 100644 app/assets/javascripts/boards/stores/getters.js
create mode 100644 app/assets/javascripts/boards/toggle_labels.js
create mode 100644 app/assets/javascripts/clusters/components/crossplane_provider_stack.vue
create mode 100644 app/assets/javascripts/contributors/components/contributors.vue
create mode 100644 app/assets/javascripts/contributors/index.js
create mode 100644 app/assets/javascripts/contributors/services/contributors_service.js
create mode 100644 app/assets/javascripts/contributors/stores/actions.js
create mode 100644 app/assets/javascripts/contributors/stores/getters.js
create mode 100644 app/assets/javascripts/contributors/stores/index.js
create mode 100644 app/assets/javascripts/contributors/stores/mutation_types.js
create mode 100644 app/assets/javascripts/contributors/stores/mutations.js
create mode 100644 app/assets/javascripts/contributors/stores/state.js
create mode 100644 app/assets/javascripts/contributors/utils.js
delete mode 100644 app/assets/javascripts/create_cluster/eks_cluster/components/region_dropdown.vue
rename app/assets/javascripts/{projects => create_cluster}/gke_cluster_namespace/index.js (100%)
create mode 100644 app/assets/javascripts/create_cluster/init_create_cluster.js
delete mode 100644 app/assets/javascripts/cycle_analytics/components/stage_card_list_item.vue
create mode 100644 app/assets/javascripts/error_tracking/components/error_details.vue
create mode 100644 app/assets/javascripts/error_tracking/components/stacktrace.vue
create mode 100644 app/assets/javascripts/error_tracking/components/stacktrace_entry.vue
create mode 100644 app/assets/javascripts/error_tracking/details.js
rename app/assets/javascripts/error_tracking/{index.js => list.js} (100%)
create mode 100644 app/assets/javascripts/error_tracking/store/details/actions.js
create mode 100644 app/assets/javascripts/error_tracking/store/details/getters.js
create mode 100644 app/assets/javascripts/error_tracking/store/details/mutation_types.js
create mode 100644 app/assets/javascripts/error_tracking/store/details/mutations.js
create mode 100644 app/assets/javascripts/error_tracking/store/details/state.js
rename app/assets/javascripts/error_tracking/store/{ => list}/actions.js (95%)
create mode 100644 app/assets/javascripts/error_tracking/store/list/getters.js
rename app/assets/javascripts/error_tracking/store/{ => list}/mutation_types.js (100%)
rename app/assets/javascripts/error_tracking/store/{ => list}/mutations.js (100%)
create mode 100644 app/assets/javascripts/error_tracking/store/list/state.js
create mode 100644 app/assets/javascripts/grafana_integration/components/grafana_integration.vue
create mode 100644 app/assets/javascripts/grafana_integration/index.js
create mode 100644 app/assets/javascripts/grafana_integration/store/actions.js
create mode 100644 app/assets/javascripts/grafana_integration/store/index.js
create mode 100644 app/assets/javascripts/grafana_integration/store/mutation_types.js
create mode 100644 app/assets/javascripts/grafana_integration/store/mutations.js
create mode 100644 app/assets/javascripts/grafana_integration/store/state.js
create mode 100644 app/assets/javascripts/ide/stores/modules/clientside/actions.js
create mode 100644 app/assets/javascripts/ide/stores/modules/clientside/index.js
create mode 100644 app/assets/javascripts/issuables_list/components/issuable.vue
create mode 100644 app/assets/javascripts/issuables_list/components/issuables_list_app.vue
create mode 100644 app/assets/javascripts/issuables_list/constants.js
create mode 100644 app/assets/javascripts/issuables_list/eventhub.js
create mode 100644 app/assets/javascripts/issuables_list/index.js
delete mode 100644 app/assets/javascripts/lib/utils/tick_formats.js
create mode 100644 app/assets/javascripts/monitoring/components/charts/anomaly.vue
create mode 100644 app/assets/javascripts/monitoring/components/charts/heatmap.vue
create mode 100644 app/assets/javascripts/monitoring/components/shared/prometheus_header.vue
create mode 100644 app/assets/javascripts/notes/components/diff_discussion_header.vue
create mode 100644 app/assets/javascripts/notes/mixins/description_version_history.js
create mode 100644 app/assets/javascripts/pages/groups/new/fetch_group_path_availability.js
create mode 100644 app/assets/javascripts/pages/groups/new/group_path_validator.js
delete mode 100644 app/assets/javascripts/pages/projects/clusters/new/index.js
create mode 100644 app/assets/javascripts/pages/projects/error_tracking/details/index.js
delete mode 100644 app/assets/javascripts/pages/projects/error_tracking/index.js
create mode 100644 app/assets/javascripts/pages/projects/error_tracking/index/index.js
delete mode 100644 app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js
delete mode 100644 app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js
delete mode 100644 app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js
create mode 100644 app/assets/javascripts/pages/projects/pipelines/test_report/index.js
create mode 100644 app/assets/javascripts/performance_bar/components/add_request.vue
create mode 100644 app/assets/javascripts/pipelines/components/test_reports/test_reports.vue
create mode 100644 app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue
create mode 100644 app/assets/javascripts/pipelines/components/test_reports/test_summary.vue
create mode 100644 app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue
create mode 100644 app/assets/javascripts/pipelines/stores/test_reports/actions.js
create mode 100644 app/assets/javascripts/pipelines/stores/test_reports/getters.js
create mode 100644 app/assets/javascripts/pipelines/stores/test_reports/index.js
create mode 100644 app/assets/javascripts/pipelines/stores/test_reports/mutation_types.js
create mode 100644 app/assets/javascripts/pipelines/stores/test_reports/mutations.js
create mode 100644 app/assets/javascripts/pipelines/stores/test_reports/state.js
create mode 100644 app/assets/javascripts/pipelines/stores/test_reports/utils.js
delete mode 100644 app/assets/javascripts/privacy_policy_update_callout.js
create mode 100644 app/assets/javascripts/releases/list/components/release_block_footer.vue
create mode 100644 app/assets/javascripts/repository/components/directory_download_links.vue
create mode 100644 app/assets/javascripts/repository/components/preview/index.vue
create mode 100644 app/assets/javascripts/repository/components/tree_action_link.vue
create mode 100644 app/assets/javascripts/repository/components/tree_content.vue
create mode 100644 app/assets/javascripts/repository/queries/commit.fragment.graphql
create mode 100644 app/assets/javascripts/repository/queries/getReadme.query.graphql
create mode 100644 app/assets/javascripts/repository/utils/commit.js
create mode 100644 app/assets/javascripts/repository/utils/dom.js
create mode 100644 app/assets/javascripts/repository/utils/readme.js
rename app/assets/javascripts/{raven => sentry}/index.js (76%)
rename app/assets/javascripts/{raven/raven_config.js => sentry/sentry_config.js} (63%)
create mode 100644 app/assets/javascripts/sourcegraph/index.js
create mode 100644 app/assets/javascripts/sourcegraph/load.js
create mode 100644 app/assets/javascripts/vue_shared/components/slot_switch.vue
create mode 100644 app/assets/javascripts/vue_shared/components/split_button.vue
create mode 100644 app/assets/stylesheets/mailer.scss
create mode 100644 app/assets/stylesheets/mailer_client_specific.scss
create mode 100644 app/assets/stylesheets/pages/error_details.scss
delete mode 100644 app/assets/stylesheets/pages/stat_graph.scss
create mode 100644 app/controllers/concerns/redirects_for_missing_path_on_tree.rb
create mode 100644 app/controllers/concerns/sourcegraph_gon.rb
create mode 100644 app/controllers/groups/group_links_controller.rb
create mode 100644 app/controllers/projects/usage_ping_controller.rb
create mode 100644 app/finders/abuse_reports_finder.rb
create mode 100644 app/finders/git_refs_finder.rb
create mode 100644 app/finders/prometheus_metrics_finder.rb
create mode 100644 app/graphql/mutations/merge_requests/set_assignees.rb
create mode 100644 app/graphql/mutations/merge_requests/set_labels.rb
create mode 100644 app/graphql/mutations/merge_requests/set_locked.rb
create mode 100644 app/graphql/mutations/merge_requests/set_milestone.rb
create mode 100644 app/graphql/mutations/merge_requests/set_subscription.rb
create mode 100644 app/graphql/mutations/todos/base.rb
create mode 100644 app/graphql/mutations/todos/mark_done.rb
create mode 100644 app/graphql/resolvers/commit_pipelines_resolver.rb
delete mode 100644 app/graphql/types/extended_issue_type.rb
create mode 100644 app/graphql/types/mutation_operation_mode_enum.rb
create mode 100644 app/helpers/sourcegraph_helper.rb
create mode 100644 app/models/clusters/applications/crossplane.rb
create mode 100644 app/models/clusters/applications/elastic_stack.rb
create mode 100644 app/models/deployment_merge_request.rb
create mode 100644 app/models/group_group_link.rb
create mode 100644 app/models/zoom_meeting.rb
create mode 100644 app/presenters/release_presenter.rb
create mode 100644 app/serializers/error_tracking/detailed_error_entity.rb
create mode 100644 app/serializers/error_tracking/detailed_error_serializer.rb
create mode 100644 app/serializers/error_tracking/error_event_entity.rb
create mode 100644 app/serializers/error_tracking/error_event_serializer.rb
create mode 100644 app/services/ci/find_exposed_artifacts_service.rb
create mode 100644 app/services/ci/generate_exposed_artifacts_report_service.rb
create mode 100644 app/services/clusters/aws/fetch_credentials_service.rb
create mode 100644 app/services/clusters/aws/finalize_creation_service.rb
create mode 100644 app/services/clusters/aws/provision_service.rb
create mode 100644 app/services/clusters/aws/proxy_service.rb
create mode 100644 app/services/clusters/aws/verify_provision_status_service.rb
create mode 100644 app/services/clusters/destroy_service.rb
create mode 100644 app/services/concerns/git/logger.rb
create mode 100644 app/services/deployments/link_merge_requests_service.rb
create mode 100644 app/services/error_tracking/base_service.rb
create mode 100644 app/services/error_tracking/issue_details_service.rb
create mode 100644 app/services/error_tracking/issue_latest_event_service.rb
create mode 100644 app/services/groups/group_links/create_service.rb
create mode 100644 app/services/groups/group_links/destroy_service.rb
create mode 100644 app/services/groups/import_export/export_service.rb
delete mode 100644 app/services/merge_requests/working_copy_base_service.rb
create mode 100644 app/services/metrics/dashboard/grafana_metric_embed_service.rb
create mode 100644 app/services/system_notes/merge_requests_service.rb
create mode 100644 app/services/users/signup_service.rb
delete mode 100644 app/services/zoom_notes_service.rb
create mode 100644 app/validators/same_project_association_validator.rb
create mode 100644 app/validators/zoom_url_validator.rb
create mode 100644 app/views/admin/application_settings/_eks.html.haml
create mode 100644 app/views/admin/application_settings/_sourcegraph.html.haml
create mode 100644 app/views/ci/group_variables/_content.html.haml
create mode 100644 app/views/ci/group_variables/_header.html.haml
create mode 100644 app/views/ci/group_variables/_index.html.haml
create mode 100644 app/views/ci/group_variables/_variable_header.html.haml
create mode 100644 app/views/ci/variables/_url_query_variable_row.html.haml
create mode 100644 app/views/clusters/clusters/aws/_new.html.haml
delete mode 100644 app/views/clusters/clusters/eks/_index.html.haml
create mode 100644 app/views/clusters/clusters/gcp/_new.html.haml
create mode 100644 app/views/profiles/preferences/_sourcegraph.html.haml
create mode 100644 app/views/projects/environments/empty_logs.html.haml
rename app/views/projects/environments/{empty.html.haml => empty_metrics.html.haml} (64%)
create mode 100644 app/views/projects/error_tracking/details.html.haml
create mode 100644 app/views/projects/pages_domains/_dns.html.haml
create mode 100644 app/views/projects/pages_domains/_lets_encrypt_callout.html.haml
create mode 100644 app/views/projects/settings/operations/_grafana_integration.html.haml
create mode 100644 app/views/shared/milestones/_description.html.haml
create mode 100644 app/views/shared/milestones/_header.html.haml
create mode 100644 app/workers/clusters/cleanup/app_worker.rb
create mode 100644 app/workers/clusters/cleanup/project_namespace_worker.rb
create mode 100644 app/workers/clusters/cleanup/service_account_worker.rb
create mode 100644 app/workers/group_export_worker.rb
create mode 100644 changelogs/unreleased/18126-change-tag-url-for-tag-push-events-in-chat-msg-integration.yaml
create mode 100644 config/initializers/database_config.rb
create mode 100644 config/initializers/validate_puma.rb
create mode 100644 db/fixtures/development/02_users.rb
delete mode 100644 db/fixtures/development/05_users.rb
create mode 100644 db/migrate/20180902070406_create_group_group_links.rb
create mode 100644 db/migrate/20190703171157_add_sourcing_epic_dates.rb
create mode 100644 db/migrate/20190703171555_add_sourcing_epic_dates_fks.rb
create mode 100644 db/migrate/20190827222124_add_sourcegraph_configuration_to_application_settings.rb
create mode 100644 db/migrate/20190910211526_create_packages_conan_file_metadata.rb
create mode 100644 db/migrate/20190918104731_add_cleanup_status_to_cluster.rb
create mode 100644 db/migrate/20190918121135_add_cleanup_status_reason_to_cluster.rb
create mode 100644 db/migrate/20190930153535_create_zoom_meetings.rb
create mode 100644 db/migrate/20191002123516_create_clusters_applications_elastic_stack.rb
create mode 100644 db/migrate/20191003161031_add_mark_for_deletion_to_projects.rb
create mode 100644 db/migrate/20191003161032_add_mark_for_deletion_indexes_to_projects.rb
create mode 100644 db/migrate/20191003195218_add_pendo_enabled_to_application_settings.rb
create mode 100644 db/migrate/20191003195620_add_pendo_url_to_application_settings.rb
create mode 100644 db/migrate/20191004080818_add_productivity_analytics_start_date.rb
create mode 100644 db/migrate/20191004081520_fill_productivity_analytics_start_date.rb
create mode 100644 db/migrate/20191009100244_add_geo_design_repository_counters.rb
create mode 100644 db/migrate/20191009110124_add_has_exposed_artifacts_to_ci_builds_metadata.rb
create mode 100644 db/migrate/20191009110757_add_index_to_ci_builds_metadata_has_exposed_artifacts.rb
create mode 100644 db/migrate/20191010174846_add_snowplow_iglu_registry_url_to_application_settings.rb
create mode 100644 db/migrate/20191011084019_add_project_deletion_adjourned_period_to_application_settings.rb
create mode 100644 db/migrate/20191013100213_add_squash_commit_sha_to_merge_requests.rb
create mode 100644 db/migrate/20191014025629_rename_design_management_version_user_to_author.rb
create mode 100644 db/migrate/20191014030730_add_author_index_to_design_management_versions.rb
create mode 100644 db/migrate/20191016133352_create_ci_subscriptions_projects.rb
create mode 100644 db/migrate/20191017001326_create_users_security_dashboard_projects.rb
create mode 100644 db/migrate/20191017094449_add_remove_source_branch_after_merge_to_projects.rb
create mode 100644 db/migrate/20191017134513_add_deployment_merge_requests.rb
create mode 100644 db/migrate/20191017191341_create_clusters_applications_crossplane.rb
create mode 100644 db/migrate/20191023132005_add_merge_requests_index_on_target_project_and_branch.rb
create mode 100644 db/migrate/20191023152913_add_default_and_free_plans.rb
create mode 100644 db/migrate/20191024134020_add_index_to_zoom_meetings.rb
create mode 100644 db/migrate/20191026124116_set_application_settings_default_project_and_snippet_visibility.rb
create mode 100644 db/migrate/20191028162543_add_setup_for_company_to_user_preferences.rb
create mode 100644 db/migrate/20191028184740_rename_snowplow_site_id_to_snowplow_app_id.rb
create mode 100644 db/migrate/20191029125305_create_packages_conan_metadata.rb
create mode 100644 db/migrate/20191029191901_add_enabled_to_grafana_integrations.rb
create mode 100644 db/migrate/20191030135044_create_plan_limits.rb
create mode 100644 db/migrate/20191030152934_move_limits_from_plans.rb
create mode 100644 db/migrate/20191101092917_replace_index_on_metrics_merged_at.rb
create mode 100644 db/migrate/20191103202505_add_eks_credentials_to_application_settings.rb
create mode 100644 db/migrate/20191104205020_add_license_details_to_application_settings.rb
create mode 100644 db/migrate/20191105094558_add_report_type_to_vulnerabilities.rb
create mode 100644 db/migrate/20191105193652_add_index_on_deployments_updated_at.rb
create mode 100644 db/migrate/20191107173446_add_sourcegraph_admin_and_user_preferences.rb
create mode 100644 db/migrate/20191107220314_add_index_to_projects_on_marked_for_deletion.rb
create mode 100644 db/migrate/20191111115229_add_group_id_to_import_export_uploads.rb
create mode 100644 db/migrate/20191111115431_add_group_fk_to_import_export_uploads.rb
create mode 100644 db/migrate/20191111121500_default_ci_config_path.rb
create mode 100644 db/migrate/20191112115247_add_cached_markdown_version_to_vulnerabilities.rb
create mode 100644 db/migrate/20191112214305_add_indexes_for_projects_api_default_params.rb
create mode 100644 db/migrate/20191112221821_add_indexes_for_projects_api_default_params_authenticated.rb
create mode 100644 db/migrate/20191112232338_ensure_no_empty_milestone_titles.rb
create mode 100644 db/migrate/20191114173508_add_resolved_attributes_to_vulnerabilities.rb
create mode 100644 db/migrate/20191114173602_add_foreign_key_on_resolved_by_id_to_vulnerabilities.rb
create mode 100644 db/migrate/20191115091425_create_vulnerability_issue_links.rb
create mode 100644 db/post_migrate/20190926180443_schedule_epic_issues_after_epics_move.rb
create mode 100644 db/post_migrate/20191008143850_fix_any_approver_rule_for_projects.rb
create mode 100644 db/post_migrate/20191014030134_cleanup_design_management_version_user_to_author_rename.rb
create mode 100644 db/post_migrate/20191017045817_schedule_fix_gitlab_com_pages_access_level.rb
create mode 100644 db/post_migrate/20191017180026_drop_ci_build_trace_sections_id.rb
create mode 100644 db/post_migrate/20191021101942_remove_empty_github_service_templates.rb
create mode 100644 db/post_migrate/20191022113635_nullify_feature_flag_plaintext_tokens.rb
create mode 100644 db/post_migrate/20191029095537_cleanup_application_settings_snowplow_site_id_rename.rb
create mode 100644 db/post_migrate/20191030193050_remove_pendo_from_application_settings.rb
create mode 100644 db/post_migrate/20191031112603_remove_limits_from_plans.rb
create mode 100644 db/post_migrate/20191105094625_set_report_type_for_vulnerabilities.rb
create mode 100644 db/post_migrate/20191105140942_add_indices_to_abuse_reports.rb
create mode 100644 db/post_migrate/20191112115317_change_vulnerabilities_title_html_to_nullable.rb
create mode 100644 db/post_migrate/20191114173624_set_resolved_state_on_vulnerabilities.rb
create mode 100644 doc/administration/git_annex.md
rename doc/{workflow/lfs/images => administration/lfs/img}/git-annex-branches.png (100%)
rename doc/{workflow => administration}/lfs/img/lfs-icon.png (100%)
create mode 100644 doc/administration/lfs/lfs_administration.md
create mode 100644 doc/administration/lfs/manage_large_binaries_with_git_lfs.md
create mode 100644 doc/administration/lfs/migrate_from_git_annex_to_git_lfs.md
create mode 100644 doc/administration/timezone.md
create mode 100644 doc/api/feature_flag_specs.md
create mode 100644 doc/api/feature_flags.md
create mode 100644 doc/api/graphql/reference/gitlab_schema.graphql
create mode 100644 doc/api/graphql/reference/gitlab_schema.json
create mode 100644 doc/api/visual_review_discussions.md
create mode 100644 doc/api/vulnerability_findings.md
create mode 100644 doc/ci/environments/environments_dashboard.md
create mode 100644 doc/ci/environments/img/environments_dashboard_v12_5.png
create mode 100644 doc/ci/img/pipelines_junit_test_report_ui_v12_5.png
create mode 100644 doc/ci/variables/img/inherited_group_variables_v12_5.png
create mode 100644 doc/development/creating_enums.md
create mode 100644 doc/development/documentation/site_architecture/release_process.md
create mode 100644 doc/development/testing_guide/end_to_end/feature_flags.md
create mode 100644 doc/development/testing_guide/end_to_end/flows.md
create mode 100644 doc/gitlab-basics/feature_branch_workflow.md
create mode 100644 doc/integration/img/sourcegraph_admin_v12_5.png
create mode 100644 doc/integration/img/sourcegraph_demo_v12_5.png
create mode 100644 doc/integration/img/sourcegraph_popover_v12_5.png
create mode 100644 doc/integration/img/sourcegraph_user_preferences_v12_5.png
create mode 100644 doc/integration/sourcegraph.md
create mode 100644 doc/topics/gitlab_flow.md
rename doc/{workflow => topics}/img/gitlab_flow.png (100%)
rename doc/{workflow/img/ci_mr.png => topics/img/gitlab_flow_ci_mr.png} (100%)
rename doc/{workflow/img/close_issue_mr.png => topics/img/gitlab_flow_close_issue_mr.png} (100%)
rename doc/{workflow/img/environment_branches.png => topics/img/gitlab_flow_environment_branches.png} (100%)
rename doc/{workflow/img/four_stages.png => topics/img/gitlab_flow_four_stages.png} (100%)
rename doc/{workflow/img/git_pull.png => topics/img/gitlab_flow_git_pull.png} (100%)
rename doc/{workflow/img/gitdashflow.png => topics/img/gitlab_flow_gitdashflow.png} (100%)
rename doc/{workflow/img/github_flow.png => topics/img/gitlab_flow_github_flow.png} (100%)
rename doc/{workflow/img/good_commit.png => topics/img/gitlab_flow_good_commit.png} (100%)
rename doc/{workflow/img/merge_commits.png => topics/img/gitlab_flow_merge_commits.png} (100%)
rename doc/{workflow/img/merge_request.png => topics/img/gitlab_flow_merge_request.png} (100%)
rename doc/{workflow/img/messy_flow.png => topics/img/gitlab_flow_messy_flow.png} (100%)
rename doc/{workflow/img/mr_inline_comments.png => topics/img/gitlab_flow_mr_inline_comments.png} (100%)
rename doc/{workflow/img/production_branch.png => topics/img/gitlab_flow_production_branch.png} (100%)
rename doc/{workflow/img/rebase.png => topics/img/gitlab_flow_rebase.png} (100%)
rename doc/{workflow/img/release_branches.png => topics/img/gitlab_flow_release_branches.png} (100%)
rename doc/{workflow/img/remove_checkbox.png => topics/img/gitlab_flow_remove_checkbox.png} (100%)
create mode 100644 doc/user/admin_area/activating_deactivating_users.md
create mode 100644 doc/user/admin_area/blocking_unblocking_users.md
create mode 100644 doc/user/admin_area/settings/img/two_factor_grace_period.png
create mode 100644 doc/user/admin_area/settings/sign_in_restrictions.md
create mode 100644 doc/user/application_security/dependency_list/img/dependency_list_v12_4.png
delete mode 100644 doc/user/application_security/security_dashboard/img/group_security_dashboard_v12_3.png
create mode 100644 doc/user/application_security/security_dashboard/img/group_security_dashboard_v12_4.png
create mode 100644 doc/user/clusters/crossplane.md
create mode 100644 doc/user/clusters/img/advanced-settings-cluster-management-project-v12_5.png
mode change 100755 => 100644 doc/user/group/epics/img/epic_view_roadmap_v12.3.png
mode change 100755 => 100644 doc/user/group/epics/img/epic_view_v12.3.png
delete mode 100755 doc/user/group/epics/img/epics_list_view_v12.3.png
create mode 100644 doc/user/group/epics/img/epics_list_view_v12.5.png
rename doc/{workflow => user}/img/todos_add_todo_sidebar.png (100%)
rename doc/{workflow => user}/img/todos_icon.png (100%)
rename doc/{workflow => user}/img/todos_index.png (100%)
rename doc/{workflow => user}/img/todos_mark_done_sidebar.png (100%)
rename doc/{workflow/img/todo_list_item.png => user/img/todos_todo_list_item.png} (100%)
create mode 100644 doc/user/incident_management/img/incident_management_settings.png
create mode 100644 doc/user/incident_management/index.md
delete mode 100644 doc/user/operations_dashboard/img/index_operations_dashboard_top_bar_icon.png
rename doc/{workflow => user/profile}/img/notification_global_settings.png (100%)
rename doc/{workflow => user/profile}/img/notification_group_settings.png (100%)
rename doc/{workflow => user/profile}/img/notification_project_settings.png (100%)
create mode 100644 doc/user/profile/notifications.md
create mode 100644 doc/user/project/clusters/add_remove_clusters.md
delete mode 100644 doc/user/project/clusters/eks_and_gitlab/img/create_dns.png
delete mode 100644 doc/user/project/clusters/eks_and_gitlab/img/create_project.png
delete mode 100644 doc/user/project/clusters/eks_and_gitlab/img/deploy_apps.png
rename doc/user/project/clusters/{eks_and_gitlab => }/img/add_cluster.png (100%)
rename doc/user/project/clusters/{eks_and_gitlab => }/img/environment.png (100%)
delete mode 100644 doc/user/project/clusters/img/kubernetes_pod_logs_v12_4.png
create mode 100644 doc/user/project/clusters/img/kubernetes_pod_logs_v12_5.png
rename doc/user/project/clusters/{eks_and_gitlab => }/img/pipeline.png (100%)
rename doc/user/project/clusters/{eks_and_gitlab => }/img/rbac.png (100%)
create mode 100644 doc/user/project/clusters/img/sidebar_menu_pod_logs_v12_5.png
mode change 100755 => 100644 doc/user/project/img/code_owners_approval_new_protected_branch_v12_4.png
mode change 100755 => 100644 doc/user/project/img/code_owners_approval_protected_branch_v12_4.png
rename doc/{workflow/time_tracking => user/project}/img/time_tracking_example_v12_2.png (100%)
rename doc/{workflow/time_tracking => user/project}/img/time_tracking_sidebar_v8_16.png (100%)
create mode 100644 doc/user/project/integrations/img/embed_metrics_issue_template.png
create mode 100644 doc/user/project/integrations/img/grafana_panel_v12_5.png
create mode 100644 doc/user/project/integrations/img/grafana_sharing_dialog_v12_5.png
create mode 100644 doc/user/project/integrations/img/heatmap_panel_type.png
create mode 100644 doc/user/project/integrations/img/http_proxy_access_v12_5.png
create mode 100644 doc/user/project/integrations/img/prometheus_dashboard_anomaly_panel_type.png
create mode 100644 doc/user/project/integrations/img/rendered_grafana_embed_v12_5.png
create mode 100644 doc/user/project/integrations/img/select_query_variables_v12_5.png
create mode 100644 doc/user/project/issues/associate_zoom_meeting.md
rename doc/{workflow/issue_weight/issue.png => user/project/issues/img/issue_weight.png} (100%)
delete mode 100644 doc/user/project/issues/img/select_all_designs_v12_4.png
create mode 100644 doc/user/project/issues/issue_weight.md
create mode 100644 doc/user/project/merge_requests/creating_merge_requests.md
delete mode 100644 doc/user/project/merge_requests/img/approvals_premium_project_edit_v12_3.png
create mode 100644 doc/user/project/merge_requests/img/approvals_premium_project_edit_v12_5.png
mode change 100755 => 100644 doc/user/project/merge_requests/img/mr_approvals_by_code_owners_v12_4.png
create mode 100644 doc/user/project/merge_requests/reviewing_and_managing_merge_requests.md
create mode 100644 doc/user/project/operations/img/error_details_v12_5.png
create mode 100644 doc/user/project/pages/getting_started/fork_sample_project.md
create mode 100644 doc/user/project/pages/getting_started/new_or_existing_website.md
create mode 100644 doc/user/project/pages/getting_started/pages_bundled_template.md
create mode 100644 doc/user/project/pages/img/new_project_for_pages_v12_5.png
create mode 100644 doc/user/project/pages/img/pages_workflow_v12_5.png
create mode 100644 doc/user/project/pages/pages_access_control.md
create mode 100644 doc/user/project/releases/img/edit_release_page_v12_5.png
create mode 100644 doc/user/project/releases/img/milestone_list_with_releases_v12_5.png
create mode 100644 doc/user/project/releases/img/milestone_with_releases_v12_5.png
rename doc/{workflow/releases/new_tag.png => user/project/releases/img/new_tag_12_5.png} (100%)
create mode 100644 doc/user/project/releases/img/release_edit_button_v12_5.png
create mode 100644 doc/user/project/releases/img/release_with_milestone_v12_5.png
rename doc/{workflow/releases/tags.png => user/project/releases/img/tags_12_5.png} (100%)
create mode 100644 doc/user/project/repository/file_finder.md
create mode 100644 doc/user/project/repository/forking_workflow.md
rename doc/{workflow => user/project/repository}/img/file_finder_find_button.png (100%)
rename doc/{workflow => user/project/repository}/img/file_finder_find_file.png (100%)
rename doc/{workflow/forking/branch_select.png => user/project/repository/img/forking_workflow_branch_select.png} (100%)
rename doc/{workflow => user/project/repository}/img/forking_workflow_choose_namespace.png (100%)
rename doc/{workflow => user/project/repository}/img/forking_workflow_fork_button.png (100%)
rename doc/{workflow/forking/merge_request.png => user/project/repository/img/forking_workflow_merge_request.png} (100%)
rename doc/{workflow => user/project/repository}/img/forking_workflow_path_taken_error.png (100%)
rename doc/{workflow/img/copy_ssh_public_key_button.png => user/project/repository/img/repository_mirroring_copy_ssh_public_key_button.png} (100%)
rename doc/{workflow => user/project/repository}/img/repository_mirroring_force_update.png (100%)
rename doc/{workflow => user/project/repository}/img/repository_mirroring_pull_settings_lower.png (100%)
rename doc/{workflow => user/project/repository}/img/repository_mirroring_pull_settings_upper.png (100%)
rename doc/{workflow => user/project/repository}/img/repository_mirroring_push_settings.png (100%)
create mode 100644 doc/user/project/repository/repository_mirroring.md
create mode 100644 doc/user/project/time_tracking.md
create mode 100644 doc/user/search/img/issue_search_filter_v12_5.png
create mode 100644 doc/user/shortcuts.md
create mode 100644 doc/user/todos.md
create mode 100644 lib/api/group_export.rb
create mode 100644 lib/banzai/filter/inline_grafana_metrics_filter.rb
create mode 100644 lib/gitlab/analytics/cycle_analytics/stage_events/metrics_based_stage_event.rb
delete mode 100644 lib/gitlab/analytics/cycle_analytics/stage_events/simple_stage_event.rb
create mode 100644 lib/gitlab/ci/build/context/base.rb
create mode 100644 lib/gitlab/ci/build/context/build.rb
create mode 100644 lib/gitlab/ci/build/context/global.rb
create mode 100644 lib/gitlab/ci/config/entry/boolean.rb
create mode 100644 lib/gitlab/ci/config/entry/files.rb
create mode 100644 lib/gitlab/ci/config/entry/need.rb
create mode 100644 lib/gitlab/ci/config/entry/needs.rb
create mode 100644 lib/gitlab/ci/config/entry/prefix.rb
create mode 100644 lib/gitlab/ci/config/entry/workflow.rb
create mode 100644 lib/gitlab/ci/pipeline/chain/config/content.rb
create mode 100644 lib/gitlab/ci/pipeline/chain/config/process.rb
create mode 100644 lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules.rb
create mode 100644 lib/gitlab/ci/pipeline/chain/seed.rb
delete mode 100644 lib/gitlab/ci/pipeline/chain/validate/config.rb
create mode 100644 lib/gitlab/ci/pipeline/seed/build/cache.rb
create mode 100644 lib/gitlab/config/entry/inheritable.rb
create mode 100644 lib/gitlab/error_tracking/detailed_error.rb
create mode 100644 lib/gitlab/error_tracking/error_event.rb
create mode 100644 lib/gitlab/exception_log_formatter.rb
create mode 100644 lib/gitlab/grape_logging/loggers/exception_logger.rb
create mode 100644 lib/gitlab/graphql/connections/filterable_array_connection.rb
create mode 100644 lib/gitlab/graphql/connections/keyset/conditions/base_condition.rb
create mode 100644 lib/gitlab/graphql/connections/keyset/conditions/not_null_condition.rb
create mode 100644 lib/gitlab/graphql/connections/keyset/conditions/null_condition.rb
create mode 100644 lib/gitlab/graphql/connections/keyset/connection.rb
create mode 100644 lib/gitlab/graphql/connections/keyset/legacy_keyset_connection.rb
create mode 100644 lib/gitlab/graphql/connections/keyset/order_info.rb
create mode 100644 lib/gitlab/graphql/connections/keyset/query_builder.rb
delete mode 100644 lib/gitlab/graphql/connections/keyset_connection.rb
create mode 100644 lib/gitlab/graphql/filterable_array.rb
delete mode 100644 lib/gitlab/graphql/loaders/pipeline_for_sha_loader.rb
create mode 100644 lib/gitlab/health_checks/master_check.rb
create mode 100644 lib/gitlab/import_export/group_import_export.yml
create mode 100644 lib/gitlab/import_export/group_tree_saver.rb
create mode 100644 lib/gitlab/import_export/relation_tree_saver.rb
create mode 100644 lib/gitlab/kubernetes/config_maps/aws_node_auth.rb
create mode 100644 lib/gitlab/metrics/dashboard/stages/grafana_formatter.rb
create mode 100644 lib/gitlab/pagination/base.rb
create mode 100644 lib/gitlab/pagination/offset_pagination.rb
create mode 100644 lib/gitlab/prometheus/internal.rb
create mode 100644 lib/gitlab/slash_commands/issue_comment.rb
create mode 100644 lib/gitlab/slash_commands/presenters/issue_comment.rb
create mode 100644 lib/gitlab/slash_commands/presenters/note_base.rb
create mode 100644 lib/gitlab/sourcegraph.rb
create mode 100644 qa/qa/flow/login.rb
create mode 100644 qa/qa/page/admin/settings/component/outbound_requests.rb
create mode 100644 qa/qa/page/dashboard/welcome.rb
create mode 100644 qa/qa/service/docker_run/jenkins.rb
create mode 100644 qa/qa/service/docker_run/maven.rb
create mode 100644 qa/qa/specs/features/browser_ui/1_manage/project/dashboard_images_spec.rb
create mode 100644 qa/qa/specs/loop_runner.rb
create mode 100644 qa/qa/vendor/jenkins/page/base.rb
create mode 100644 qa/qa/vendor/jenkins/page/configure.rb
create mode 100644 qa/qa/vendor/jenkins/page/configure_job.rb
create mode 100644 qa/qa/vendor/jenkins/page/login.rb
create mode 100644 qa/qa/vendor/jenkins/page/new_credentials.rb
create mode 100644 qa/qa/vendor/jenkins/page/new_job.rb
create mode 100644 rubocop/cop/rspec/any_instance_of.rb
delete mode 100755 scripts/notify-slack
create mode 100644 scripts/sync-stable-branch.sh
create mode 100644 spec/controllers/concerns/redirects_for_missing_path_on_tree_spec.rb
create mode 100644 spec/controllers/concerns/renders_commits_spec.rb
create mode 100644 spec/controllers/concerns/sourcegraph_gon_spec.rb
create mode 100644 spec/controllers/groups/group_links_controller_spec.rb
delete mode 100644 spec/controllers/health_controller_spec.rb
create mode 100644 spec/controllers/projects/usage_ping_controller_spec.rb
create mode 100644 spec/factories/error_tracking/detailed_error.rb
create mode 100644 spec/factories/error_tracking/error_event.rb
create mode 100644 spec/factories/group_group_links.rb
create mode 100644 spec/factories/zoom_meetings.rb
create mode 100644 spec/features/admin/clusters/eks_spec.rb
create mode 100644 spec/features/groups/clusters/eks_spec.rb
create mode 100644 spec/features/issues/filtered_search/dropdown_release_spec.rb
create mode 100644 spec/features/populate_new_pipeline_vars_with_params_spec.rb
create mode 100644 spec/features/project_group_variables_spec.rb
delete mode 100644 spec/features/raven_js_spec.rb
create mode 100644 spec/features/sentry_js_spec.rb
create mode 100644 spec/features/users/anonymous_sessions_spec.rb
create mode 100644 spec/finders/abuse_reports_finder_spec.rb
create mode 100644 spec/finders/prometheus_metrics_finder_spec.rb
create mode 100644 spec/fixtures/api/schemas/error_tracking/error_detailed.json
create mode 100644 spec/fixtures/api/schemas/error_tracking/error_stack_trace.json
create mode 100644 spec/fixtures/api/schemas/error_tracking/issue_detailed.json
create mode 100644 spec/fixtures/api/schemas/error_tracking/issue_stack_trace.json
create mode 100644 spec/fixtures/grafana/dashboard_response.json
create mode 100644 spec/fixtures/grafana/datasource_response.json
create mode 100644 spec/fixtures/grafana/expected_grafana_embed.json
create mode 100644 spec/fixtures/grafana/proxy_response.json
create mode 100644 spec/fixtures/grafana/simplified_dashboard_response.json
create mode 100644 spec/fixtures/group_export.tar.gz
rename spec/fixtures/lib/gitlab/import_export/{ => complex}/project.json (99%)
rename spec/fixtures/lib/gitlab/import_export/{project.group.json => group/project.json} (100%)
rename spec/fixtures/lib/gitlab/import_export/{project.light.json => light/project.json} (100%)
rename spec/fixtures/lib/gitlab/import_export/{project.milestone-iid.json => milestone-iid/project.json} (100%)
create mode 100644 spec/frontend/boards/components/issue_time_estimate_spec.js
create mode 100644 spec/frontend/boards/issue_card_spec.js
create mode 100644 spec/frontend/boards/stores/getters_spec.js
create mode 100644 spec/frontend/clusters/services/crossplane_provider_stack_spec.js
create mode 100644 spec/frontend/contributors/component/__snapshots__/contributors_spec.js.snap
create mode 100644 spec/frontend/contributors/component/contributors_spec.js
create mode 100644 spec/frontend/contributors/store/actions_spec.js
create mode 100644 spec/frontend/contributors/store/getters_spec.js
create mode 100644 spec/frontend/contributors/store/mutations_spec.js
create mode 100644 spec/frontend/contributors/utils_spec.js
create mode 100644 spec/frontend/create_cluster/eks_cluster/components/create_eks_cluster_spec.js
delete mode 100644 spec/frontend/create_cluster/eks_cluster/components/region_dropdown_spec.js
create mode 100644 spec/frontend/create_cluster/eks_cluster/components/service_credentials_form_spec.js
create mode 100644 spec/frontend/create_cluster/eks_cluster/services/aws_services_facade_spec.js
rename spec/frontend/{projects => create_cluster}/gke_cluster_namespace/gke_cluster_namespace_spec.js (95%)
create mode 100644 spec/frontend/create_cluster/init_create_cluster_spec.js
create mode 100644 spec/frontend/error_tracking/components/error_details_spec.js
create mode 100644 spec/frontend/error_tracking/components/stacktrace_entry_spec.js
create mode 100644 spec/frontend/error_tracking/components/stacktrace_spec.js
create mode 100644 spec/frontend/error_tracking/store/details/actions_spec.js
create mode 100644 spec/frontend/error_tracking/store/details/getters_spec.js
create mode 100644 spec/frontend/error_tracking/store/list/getters_spec.js
rename spec/frontend/error_tracking/store/{ => list}/mutation_spec.js (83%)
create mode 100644 spec/frontend/grafana_integration/components/__snapshots__/grafana_integration_spec.js.snap
create mode 100644 spec/frontend/grafana_integration/components/grafana_integration_spec.js
create mode 100644 spec/frontend/grafana_integration/store/mutations_spec.js
create mode 100644 spec/frontend/ide/components/jobs/__snapshots__/stage_spec.js.snap
create mode 100644 spec/frontend/ide/components/jobs/stage_spec.js
create mode 100644 spec/frontend/ide/stores/modules/clientside/actions_spec.js
create mode 100644 spec/frontend/issuables_list/components/__snapshots__/issuables_list_app_spec.js.snap
create mode 100644 spec/frontend/issuables_list/components/issuable_spec.js
create mode 100644 spec/frontend/issuables_list/components/issuables_list_app_spec.js
create mode 100644 spec/frontend/issuables_list/issuable_list_test_data.js
create mode 100644 spec/frontend/issue_show/helpers.js
create mode 100644 spec/frontend/lib/utils/chart_utils_spec.js
rename spec/{javascripts => frontend}/monitoring/charts/time_series_spec.js (70%)
create mode 100644 spec/frontend/monitoring/components/charts/anomaly_spec.js
create mode 100644 spec/frontend/monitoring/mock_data.js
create mode 100644 spec/frontend/monitoring/panel_type_spec.js
rename spec/{javascripts => frontend}/monitoring/store/actions_spec.js (65%)
rename spec/{javascripts => frontend}/monitoring/store/mutations_spec.js (63%)
rename spec/{javascripts => frontend}/monitoring/store/utils_spec.js (100%)
create mode 100644 spec/frontend/notes/components/comment_form_spec.js
create mode 100644 spec/frontend/notes/components/diff_discussion_header_spec.js
create mode 100644 spec/frontend/notes/mock_data.js
create mode 100644 spec/frontend/performance_bar/components/add_request_spec.js
create mode 100644 spec/frontend/pipelines/graph/action_component_spec.js
rename spec/{javascripts => frontend}/pipelines/pipeline_triggerer_spec.js (90%)
rename spec/{javascripts => frontend}/pipelines/pipelines_table_row_spec.js (53%)
create mode 100644 spec/frontend/pipelines/test_reports/mock_data.js
create mode 100644 spec/frontend/pipelines/test_reports/stores/actions_spec.js
create mode 100644 spec/frontend/pipelines/test_reports/stores/getters_spec.js
create mode 100644 spec/frontend/pipelines/test_reports/stores/mutations_spec.js
create mode 100644 spec/frontend/pipelines/test_reports/test_reports_spec.js
create mode 100644 spec/frontend/pipelines/test_reports/test_suite_table_spec.js
create mode 100644 spec/frontend/pipelines/test_reports/test_summary_spec.js
create mode 100644 spec/frontend/pipelines/test_reports/test_summary_table_spec.js
delete mode 100644 spec/frontend/releases/list/components/__snapshots__/release_block_spec.js.snap
create mode 100644 spec/frontend/releases/list/components/release_block_footer_spec.js
create mode 100644 spec/frontend/repository/components/__snapshots__/directory_download_links_spec.js.snap
create mode 100644 spec/frontend/repository/components/directory_download_links_spec.js
create mode 100644 spec/frontend/repository/components/preview/__snapshots__/index_spec.js.snap
create mode 100644 spec/frontend/repository/components/preview/index_spec.js
create mode 100644 spec/frontend/repository/components/tree_content_spec.js
create mode 100644 spec/frontend/repository/pages/index_spec.js
create mode 100644 spec/frontend/repository/pages/tree_spec.js
create mode 100644 spec/frontend/repository/utils/commit_spec.js
create mode 100644 spec/frontend/repository/utils/dom_spec.js
create mode 100644 spec/frontend/repository/utils/readme_spec.js
rename spec/{javascripts/raven => frontend/sentry}/index_spec.js (62%)
create mode 100644 spec/frontend/sentry/sentry_config_spec.js
create mode 100644 spec/frontend/vue_shared/components/__snapshots__/split_button_spec.js.snap
rename spec/{javascripts => frontend}/vue_shared/components/commit_spec.js (58%)
create mode 100644 spec/frontend/vue_shared/components/content_viewer/viewers/image_viewer_spec.js
create mode 100644 spec/frontend/vue_shared/components/slot_switch_spec.js
create mode 100644 spec/frontend/vue_shared/components/split_button_spec.js
rename spec/{javascripts => frontend}/vue_shared/components/table_pagination_spec.js (65%)
create mode 100644 spec/frontend/vue_shared/components/user_avatar/user_avatar_image_spec.js
create mode 100644 spec/frontend/vue_shared/components/user_popover/user_popover_spec.js
create mode 100644 spec/graphql/mutations/merge_requests/set_assignees_spec.rb
create mode 100644 spec/graphql/mutations/merge_requests/set_labels_spec.rb
create mode 100644 spec/graphql/mutations/merge_requests/set_locked_spec.rb
create mode 100644 spec/graphql/mutations/merge_requests/set_milestone_spec.rb
create mode 100644 spec/graphql/mutations/merge_requests/set_subscription_spec.rb
create mode 100644 spec/graphql/mutations/todos/mark_done_spec.rb
create mode 100644 spec/graphql/resolvers/commit_pipelines_resolver_spec.rb
create mode 100644 spec/graphql/types/base_enum_spec.rb
delete mode 100644 spec/graphql/types/extended_issue_type_spec.rb
create mode 100644 spec/graphql/types/issue_sort_enum_spec.rb
create mode 100644 spec/helpers/sourcegraph_helper_spec.rb
create mode 100644 spec/initializers/database_config_spec.rb
delete mode 100644 spec/javascripts/boards/components/issue_time_estimate_spec.js
delete mode 100644 spec/javascripts/boards/issue_card_spec.js
create mode 100644 spec/javascripts/diffs/mock_data/diff_file_unreadable.js
delete mode 100644 spec/javascripts/graphs/stat_graph_contributors_graph_spec.js
delete mode 100644 spec/javascripts/graphs/stat_graph_contributors_spec.js
delete mode 100644 spec/javascripts/graphs/stat_graph_contributors_util_spec.js
delete mode 100644 spec/javascripts/ide/components/jobs/stage_spec.js
delete mode 100644 spec/javascripts/lib/utils/tick_formats_spec.js
create mode 100644 spec/javascripts/monitoring/charts/heatmap_spec.js
delete mode 100644 spec/javascripts/monitoring/panel_type_spec.js
create mode 100644 spec/javascripts/monitoring/shared/prometheus_header_spec.js
delete mode 100644 spec/javascripts/notes/components/comment_form_spec.js
delete mode 100644 spec/javascripts/pipelines/graph/action_component_spec.js
delete mode 100644 spec/javascripts/raven/raven_config_spec.js
delete mode 100644 spec/javascripts/vue_shared/components/user_avatar/user_avatar_image_spec.js
delete mode 100644 spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js
create mode 100644 spec/lib/banzai/filter/inline_grafana_metrics_filter_spec.rb
create mode 100644 spec/lib/gitlab/ci/build/context/build_spec.rb
create mode 100644 spec/lib/gitlab/ci/build/context/global_spec.rb
create mode 100644 spec/lib/gitlab/ci/config/entry/files_spec.rb
create mode 100644 spec/lib/gitlab/ci/config/entry/need_spec.rb
create mode 100644 spec/lib/gitlab/ci/config/entry/needs_spec.rb
create mode 100644 spec/lib/gitlab/ci/config/entry/prefix_spec.rb
create mode 100644 spec/lib/gitlab/ci/config/entry/workflow_spec.rb
create mode 100644 spec/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules_spec.rb
create mode 100644 spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb
delete mode 100644 spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb
create mode 100644 spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb
create mode 100644 spec/lib/gitlab/devise_failure_spec.rb
create mode 100644 spec/lib/gitlab/grape_logging/loggers/exception_logger_spec.rb
create mode 100644 spec/lib/gitlab/graphql/connections/filterable_array_connection_spec.rb
create mode 100644 spec/lib/gitlab/graphql/connections/keyset/conditions/not_null_condition_spec.rb
create mode 100644 spec/lib/gitlab/graphql/connections/keyset/conditions/null_condition_spec.rb
create mode 100644 spec/lib/gitlab/graphql/connections/keyset/connection_spec.rb
create mode 100644 spec/lib/gitlab/graphql/connections/keyset/legacy_keyset_connection_spec.rb
create mode 100644 spec/lib/gitlab/graphql/connections/keyset/order_info_spec.rb
create mode 100644 spec/lib/gitlab/graphql/connections/keyset/query_builder_spec.rb
delete mode 100644 spec/lib/gitlab/graphql/connections/keyset_connection_spec.rb
delete mode 100644 spec/lib/gitlab/graphql/loaders/pipeline_for_sha_loader_spec.rb
create mode 100644 spec/lib/gitlab/health_checks/master_check_spec.rb
create mode 100644 spec/lib/gitlab/import_export/group_tree_saver_spec.rb
create mode 100644 spec/lib/gitlab/import_export/relation_tree_saver_spec.rb
create mode 100644 spec/lib/gitlab/instrumentation_helper_spec.rb
create mode 100644 spec/lib/gitlab/kubernetes/config_maps/aws_node_auth_spec.rb
create mode 100644 spec/lib/gitlab/metrics/dashboard/stages/grafana_formatter_spec.rb
create mode 100644 spec/lib/gitlab/pagination/offset_pagination_spec.rb
create mode 100644 spec/lib/gitlab/prometheus/internal_spec.rb
create mode 100644 spec/lib/gitlab/slash_commands/issue_comment_spec.rb
create mode 100644 spec/lib/gitlab/slash_commands/presenters/issue_comment_spec.rb
create mode 100644 spec/lib/gitlab/sourcegraph_spec.rb
create mode 100644 spec/migrations/add_default_and_free_plans_spec.rb
create mode 100644 spec/migrations/fill_productivity_analytics_start_date_spec.rb
create mode 100644 spec/migrations/move_limits_from_plans_spec.rb
create mode 100644 spec/migrations/remove_empty_github_service_templates_spec.rb
create mode 100644 spec/models/clusters/applications/crossplane_spec.rb
create mode 100644 spec/models/clusters/applications/elastic_stack_spec.rb
create mode 100644 spec/models/deployment_merge_request_spec.rb
create mode 100644 spec/models/group_group_link_spec.rb
create mode 100644 spec/models/personal_snippet_spec.rb
create mode 100644 spec/models/zoom_meeting_spec.rb
create mode 100644 spec/presenters/instance_clusterable_presenter_spec.rb
create mode 100644 spec/presenters/release_presenter_spec.rb
create mode 100644 spec/requests/api/graphql/current_user/todos_query_spec.rb
create mode 100644 spec/requests/api/graphql/current_user_query_spec.rb
create mode 100644 spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb
create mode 100644 spec/requests/api/graphql/mutations/merge_requests/set_labels_spec.rb
create mode 100644 spec/requests/api/graphql/mutations/merge_requests/set_locked_spec.rb
create mode 100644 spec/requests/api/graphql/mutations/merge_requests/set_milestone_spec.rb
create mode 100644 spec/requests/api/graphql/mutations/merge_requests/set_subscription_spec.rb
create mode 100644 spec/requests/api/graphql/mutations/todos/mark_done_spec.rb
create mode 100644 spec/requests/api/group_export_spec.rb
create mode 100644 spec/requests/groups/registry/repositories_controller_spec.rb
create mode 100644 spec/requests/health_controller_spec.rb
create mode 100644 spec/rubocop/cop/rspec/any_instance_of_spec.rb
create mode 100644 spec/serializers/issuable_sidebar_extras_entity_spec.rb
create mode 100644 spec/services/ci/create_pipeline_service/cache_spec.rb
create mode 100644 spec/services/ci/find_exposed_artifacts_service_spec.rb
create mode 100644 spec/services/clusters/aws/fetch_credentials_service_spec.rb
create mode 100644 spec/services/clusters/aws/finalize_creation_service_spec.rb
create mode 100644 spec/services/clusters/aws/provision_service_spec.rb
create mode 100644 spec/services/clusters/aws/proxy_service_spec.rb
create mode 100644 spec/services/clusters/aws/verify_provision_status_service_spec.rb
create mode 100644 spec/services/clusters/destroy_service_spec.rb
create mode 100644 spec/services/deployments/link_merge_requests_service_spec.rb
create mode 100644 spec/services/error_tracking/issue_details_service_spec.rb
create mode 100644 spec/services/error_tracking/issue_latest_event_service_spec.rb
create mode 100644 spec/services/groups/group_links/create_service_spec.rb
create mode 100644 spec/services/groups/group_links/destroy_service_spec.rb
create mode 100644 spec/services/groups/import_export/export_service_spec.rb
create mode 100644 spec/services/metrics/dashboard/grafana_metric_embed_service_spec.rb
create mode 100644 spec/services/projects/hashed_storage/base_attachment_service_spec.rb
create mode 100644 spec/services/system_notes/merge_requests_service_spec.rb
create mode 100644 spec/services/users/signup_service_spec.rb
delete mode 100644 spec/services/zoom_notes_service_spec.rb
create mode 100644 spec/support/helpers/access_matchers_helpers.rb
create mode 100644 spec/support/helpers/grafana_api_helpers.rb
create mode 100644 spec/support/matchers/access_matchers_for_request.rb
create mode 100644 spec/support/matchers/access_matchers_generic.rb
create mode 100644 spec/support/matchers/db_schema_matchers.rb
create mode 100644 spec/support/shared_examples/ci/auto_merge_merge_requests_examples.rb
create mode 100644 spec/support/shared_examples/graphql/connection_paged_nodes.rb
create mode 100644 spec/support/shared_examples/graphql/sort_enum_shared_examples.rb
create mode 100644 spec/support/shared_examples/lib/gitlab/ci/config/entry/key_validations_shared_examples.rb
create mode 100644 spec/support/shared_examples/lib/gitlab/config/inheritable_shared_examples.rb
create mode 100644 spec/support/shared_examples/merge_requests_rendering_a_single_diff_version.rb
rename spec/support/shared_examples/models/{concern => concerns}/issuable_shared_examples.rb (100%)
create mode 100644 spec/support/shared_examples/models/concerns/redactable_shared_examples.rb
create mode 100644 spec/support/shared_examples/services/error_tracking_service_shared_examples.rb
create mode 100644 spec/views/admin/application_settings/integrations.html.haml_spec.rb
create mode 100644 spec/views/devise/sessions/new.html.haml_spec.rb
create mode 100644 spec/views/profiles/preferences/show.html.haml_spec.rb
create mode 100644 spec/views/projects/clusters/clusters/gcp/_form.html.haml_spec.rb
create mode 100644 spec/views/projects/show.html.haml_spec.rb
create mode 100644 spec/workers/group_export_worker_spec.rb
create mode 100644 vendor/aws/cloudformation/eks_cluster.yaml
create mode 100644 vendor/crossplane/values.yaml
create mode 100644 vendor/elastic_stack/values.yaml
mode change 100755 => 100644 vendor/gitignore/C++.gitignore
mode change 100755 => 100644 vendor/gitignore/Java.gitignore
create mode 100644 vendor/ingress/modsecurity.conf
create mode 100644 vendor/project_templates/serverless_framework.tar.gz
diff --git a/.gitignore b/.gitignore
index 65befc2096..b8cbfe9966 100644
--- a/.gitignore
+++ b/.gitignore
@@ -59,6 +59,7 @@ eslint-report.html
/public/uploads.*
/public/uploads/
/shared/artifacts/
+/spec/examples.txt
/rails_best_practices_output.html
/tags
/vendor/bundle/*
@@ -82,3 +83,4 @@ jsdoc/
**/tmp/rubocop_cache/**
.overcommit.yml
.projections.json
+/qa/.rakeTasks
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 630c82bcc5..36108d04e9 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,6 +1,7 @@
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.3-golang-1.11-git-2.22-chrome-73.0-node-12.x-yarn-1.16-postgresql-9.6-graphicsmagick-1.3.33"
stages:
+ - sync
- prepare
- quick-test
- test
@@ -8,7 +9,6 @@ stages:
- review
- qa
- post-test
- - notification
- pages
variables:
@@ -33,7 +33,6 @@ include:
- local: .gitlab/ci/frontend.gitlab-ci.yml
- local: .gitlab/ci/global.gitlab-ci.yml
- local: .gitlab/ci/memory.gitlab-ci.yml
- - local: .gitlab/ci/notifications.gitlab-ci.yml
- local: .gitlab/ci/pages.gitlab-ci.yml
- local: .gitlab/ci/qa.gitlab-ci.yml
- local: .gitlab/ci/reports.gitlab-ci.yml
@@ -42,3 +41,4 @@ include:
- local: .gitlab/ci/setup.gitlab-ci.yml
- local: .gitlab/ci/test-metadata.gitlab-ci.yml
- local: .gitlab/ci/yaml.gitlab-ci.yml
+ - local: .gitlab/ci/releases.gitlab-ci.yml
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS
index a02740373d..c828332653 100644
--- a/.gitlab/CODEOWNERS
+++ b/.gitlab/CODEOWNERS
@@ -3,11 +3,12 @@
*.rake @gitlab-org/maintainers/rails-backend
# Technical writing team are the default reviewers for everything in `doc/`
-/doc/ @axil @marcia @eread @mikelewis
+/doc/ @gl-docsteam
# Frontend maintainers should see everything in `app/assets/`
-app/assets/ @ClemMakesApps @fatihacet @filipa @mikegreiling @timzallmann @kushalpandya @pslaughter @wortschi @ntepluhina
-*.scss @annabeldunstone @ClemMakesApps @fatihacet @filipa @mikegreiling @timzallmann @kushalpandya @pslaughter @wortschi @ntepluhina
+app/assets/ @gitlab-org/maintainers/frontend
+*.scss @annabeldunstone @gitlab-org/maintainers/frontend
+/scripts/frontend/ @gitlab-org/maintainers/frontend
# Database maintainers should review changes in `db/`
db/ @gitlab-org/maintainers/database
@@ -32,4 +33,5 @@ lib/gitlab/github_import/ @gitlab-org/maintainers/database
/.gitlab/ci/ @gl-quality/eng-prod
Dangerfile @gl-quality/eng-prod
/danger/ @gl-quality/eng-prod
+/lib/gitlab/danger/ @gl-quality/eng-prod
/scripts/ @gl-quality/eng-prod
diff --git a/.gitlab/ci/cng.gitlab-ci.yml b/.gitlab/ci/cng.gitlab-ci.yml
index 35859a1ab3..bd11042eb1 100644
--- a/.gitlab/ci/cng.gitlab-ci.yml
+++ b/.gitlab/ci/cng.gitlab-ci.yml
@@ -1,4 +1,5 @@
cloud-native-image:
+ extends: .only:variables-canonical-dot-com
image: ruby:2.6-alpine
dependencies: []
stage: post-test
@@ -12,5 +13,3 @@ cloud-native-image:
only:
refs:
- tags
- variables:
- - $CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE == "gitlab-org"
diff --git a/.gitlab/ci/docs.gitlab-ci.yml b/.gitlab/ci/docs.gitlab-ci.yml
index 14eeebb9db..07375fca61 100644
--- a/.gitlab/ci/docs.gitlab-ci.yml
+++ b/.gitlab/ci/docs.gitlab-ci.yml
@@ -2,12 +2,11 @@
extends:
- .default-tags
- .default-retry
- - .only-docs-changes
+ - .only:variables-canonical-dot-com
+ - .only:changes-docs
only:
refs:
- merge_requests
- variables:
- - $CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE == "gitlab-org"
image: ruby:2.6-alpine
stage: review
dependencies: []
@@ -50,7 +49,7 @@ docs lint:
- .default-tags
- .default-retry
- .default-only
- - .only-docs-changes
+ - .only:changes-docs
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-docs-lint"
stage: test
dependencies: []
@@ -68,7 +67,7 @@ docs lint:
# Check the internal anchor links
- bundle exec nanoc check internal_anchors
-graphql-docs-verify:
+graphql-reference-verify:
extends:
- .only-ee
- .default-tags
@@ -76,10 +75,10 @@ graphql-docs-verify:
- .default-cache
- .default-only
- .default-before_script
- - .only-graphql-changes
- variables:
- SETUP_DB: "false"
+ - .only:changes-code-backstage-qa
+ - .use-pg9
stage: test
needs: ["setup-test-env"]
script:
- bundle exec rake gitlab:graphql:check_docs
+ - bundle exec rake gitlab:graphql:check_schema
diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml
index 2f457bc0ee..0b72461a9f 100644
--- a/.gitlab/ci/frontend.gitlab-ci.yml
+++ b/.gitlab/ci/frontend.gitlab-ci.yml
@@ -12,7 +12,7 @@
- .default-only
- .default-before_script
- .assets-compile-cache
- - .only-code-qa-changes
+ - .only:changes-code-backstage-qa
image: registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.3-git-2.22-chrome-73.0-node-12.x-yarn-1.16-graphicsmagick-1.3.33-docker-18.06.1
stage: test
dependencies: ["setup-test-env"]
@@ -73,7 +73,7 @@ gitlab:assets:compile pull-cache:
- .default-only
- .default-before_script
- .assets-compile-cache
- - .only-code-qa-changes
+ - .only:changes-code-backstage-qa
- .use-pg9
stage: prepare
script:
@@ -128,7 +128,7 @@ compile-assets pull-cache foss:
- .default-cache
- .default-only
- .default-before_script
- - .only-code-changes
+ - .only:changes-code-backstage
- .use-pg9
stage: test
needs: ["setup-test-env", "compile-assets pull-cache"]
@@ -205,7 +205,7 @@ jest-foss:
- .default-retry
- .default-cache
- .default-only
- - .only-code-changes
+ - .only:changes-code-backstage
stage: test
dependencies: []
cache:
@@ -238,7 +238,7 @@ webpack-dev-server:
- .default-retry
- .default-cache
- .default-only
- - .only-code-changes
+ - .only:changes-code-backstage
stage: test
needs: ["setup-test-env", "compile-assets pull-cache"]
dependencies: ["setup-test-env", "compile-assets pull-cache"]
diff --git a/.gitlab/ci/global.gitlab-ci.yml b/.gitlab/ci/global.gitlab-ci.yml
index fc9b00b5d3..d746d8fe03 100644
--- a/.gitlab/ci/global.gitlab-ci.yml
+++ b/.gitlab/ci/global.gitlab-ci.yml
@@ -40,87 +40,160 @@
- merge_requests
- tags
-.only-code-changes:
- only:
- changes:
- - ".gitlab/ci/**/*"
- - ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}"
- - ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo,scss-lint}.yml"
- - ".csscomb.json"
- - "Dangerfile"
- - "Dockerfile.assets"
- - "*_VERSION"
- - "Gemfile{,.lock}"
- - "Rakefile"
- - "{babel.config,jest.config}.js"
- - "config.ru"
- - "{package.json,yarn.lock}"
- - "{app,bin,config,danger,db,ee,fixtures,haml_lint,lib,locale,public,rubocop,scripts,spec,symbol,vendor}/**/*"
- - "doc/README.md" # Some RSpec test rely on this file
-
-.only-qa-changes:
- only:
- changes:
- - ".dockerignore"
- - "qa/**/*"
-
-.only-docs-changes:
- only:
- changes:
- - ".gitlab/route-map.yml"
- - "doc/**/*"
- - ".markdownlint.json"
-
-.only-graphql-changes:
- only:
- changes:
- - "{,ee/}app/graphql/**/*"
- - "{,ee/}lib/gitlab/graphql/**/*"
-
-.only-code-qa-changes:
- only:
- changes:
- - ".gitlab/ci/**/*"
- - ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}"
- - ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo,scss-lint}.yml"
- - ".csscomb.json"
- - "Dangerfile"
- - "Dockerfile.assets"
- - "*_VERSION"
- - "Gemfile{,.lock}"
- - "Rakefile"
- - "{babel.config,jest.config}.js"
- - "config.ru"
- - "{package.json,yarn.lock}"
- - "{app,bin,config,danger,db,ee,fixtures,haml_lint,lib,locale,public,rubocop,scripts,spec,symbol,vendor}/**/*"
- - "doc/README.md" # Some RSpec test rely on this file
- - ".dockerignore"
- - "qa/**/*"
-
-.only-review:
+.only:variables-canonical-dot-com:
only:
variables:
- - $CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE == "gitlab-org"
- kubernetes: active
- except:
- refs:
- - master
- - /^\d+-\d+-auto-deploy-\d+$/
- - /^[\d-]+-stable(-ee)?$/
+ - $CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE =~ /^gitlab-org($|\/)/ # Matches the gitlab-org group or its subgroups
-.only-review-schedules:
+.only:variables_refs-canonical-dot-com-schedules:
+ extends: .only:variables-canonical-dot-com
only:
refs:
- schedules
- variables:
- - $CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE == "gitlab-org"
+
+.except:refs-deploy:
+ except:
+ refs:
+ - /^\d+-\d+-auto-deploy-\d+$/
+
+.except:refs-master-tags-stable-deploy:
+ except:
+ refs:
+ - master
+ - tags
+ - /^[\d-]+-stable(-ee)?$/
+ - /^\d+-\d+-auto-deploy-\d+$/
+
+.only:kubernetes:
+ only:
kubernetes: active
-.only-canonical-schedules:
+.only-review:
+ extends:
+ - .only:variables-canonical-dot-com
+ - .only:kubernetes
+ - .except:refs-master-tags-stable-deploy
+
+.only-review-schedules:
+ extends:
+ - .only:variables_refs-canonical-dot-com-schedules
+ - .only:kubernetes
+ - .except:refs-deploy
+
+.code-patterns: &code-patterns
+ - ".gitlab/ci/**/*"
+ - ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}"
+ - ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo,scss-lint}.yml"
+ - ".csscomb.json"
+ - "Dockerfile.assets"
+ - "*_VERSION"
+ - "Gemfile{,.lock}"
+ - "Rakefile"
+ - "{babel.config,jest.config}.js"
+ - "config.ru"
+ - "{package.json,yarn.lock}"
+ - "{,ee/}{app,bin,config,db,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*"
+ - "doc/api/graphql/**/*"
+
+.backstage-patterns: &backstage-patterns
+ - "Dangerfile"
+ - "danger/**/*"
+ - "{,ee/}fixtures/**/*"
+ - "{,ee/}rubocop/**/*"
+ - "{,ee/}spec/**/*"
+ - "doc/README.md" # Some RSpec test rely on this file
+
+.qa-patterns: &qa-patterns
+ - ".dockerignore"
+ - "qa/**/*"
+
+.docs-patterns: &docs-patterns
+ - ".gitlab/route-map.yml"
+ - "doc/**/*"
+ - ".markdownlint.json"
+
+.only:changes-code:
only:
- refs:
- - schedules@gitlab-org/gitlab
- - schedules@gitlab-org/gitlab-foss
+ changes: *code-patterns
+
+.only:changes-qa:
+ only:
+ changes: *qa-patterns
+
+.only:changes-docs:
+ only:
+ changes: *docs-patterns
+
+.only:changes-code-backstage:
+ only:
+ changes:
+ - ".gitlab/ci/**/*"
+ - ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}"
+ - ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo,scss-lint}.yml"
+ - ".csscomb.json"
+ - "Dockerfile.assets"
+ - "*_VERSION"
+ - "Gemfile{,.lock}"
+ - "Rakefile"
+ - "{babel.config,jest.config}.js"
+ - "config.ru"
+ - "{package.json,yarn.lock}"
+ - "{,ee/}{app,bin,config,db,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*"
+ - "doc/api/graphql/**/*"
+ # Backstage changes
+ - "Dangerfile"
+ - "danger/**/*"
+ - "{,ee/}fixtures/**/*"
+ - "{,ee/}rubocop/**/*"
+ - "{,ee/}spec/**/*"
+ - "doc/README.md" # Some RSpec test rely on this file
+
+.only:changes-code-qa:
+ only:
+ changes:
+ - ".gitlab/ci/**/*"
+ - ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}"
+ - ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo,scss-lint}.yml"
+ - ".csscomb.json"
+ - "Dockerfile.assets"
+ - "*_VERSION"
+ - "Gemfile{,.lock}"
+ - "Rakefile"
+ - "{babel.config,jest.config}.js"
+ - "config.ru"
+ - "{package.json,yarn.lock}"
+ - "{,ee/}{app,bin,config,db,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*"
+ - "doc/api/graphql/**/*"
+ # QA changes
+ - ".dockerignore"
+ - "qa/**/*"
+
+.only:changes-code-backstage-qa:
+ only:
+ changes:
+ - ".gitlab/ci/**/*"
+ - ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}"
+ - ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo,scss-lint}.yml"
+ - ".csscomb.json"
+ - "Dockerfile.assets"
+ - "*_VERSION"
+ - "Gemfile{,.lock}"
+ - "Rakefile"
+ - "{babel.config,jest.config}.js"
+ - "config.ru"
+ - "{package.json,yarn.lock}"
+ - "{,ee/}{app,bin,config,db,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*"
+ - "doc/api/graphql/**/*"
+ # Backstage changes
+ - "Dangerfile"
+ - "danger/**/*"
+ - "{,ee/}fixtures/**/*"
+ - "{,ee/}rubocop/**/*"
+ - "{,ee/}spec/**/*"
+ - "doc/README.md" # Some RSpec test rely on this file
+ # QA changes
+ - ".dockerignore"
+ - "qa/**/*"
.use-pg9:
services:
diff --git a/.gitlab/ci/memory.gitlab-ci.yml b/.gitlab/ci/memory.gitlab-ci.yml
index 93bf87b24b..ba14024df3 100644
--- a/.gitlab/ci/memory.gitlab-ci.yml
+++ b/.gitlab/ci/memory.gitlab-ci.yml
@@ -5,7 +5,7 @@
- .default-cache
- .default-only
- .default-before_script
- - .only-code-changes
+ - .only:changes-code
memory-static:
extends: .only-code-memory-job-base
diff --git a/.gitlab/ci/notifications.gitlab-ci.yml b/.gitlab/ci/notifications.gitlab-ci.yml
deleted file mode 100644
index 8e00ba022d..0000000000
--- a/.gitlab/ci/notifications.gitlab-ci.yml
+++ /dev/null
@@ -1,29 +0,0 @@
-.notify:
- image: alpine
- stage: notification
- dependencies: []
- cache: {}
- before_script:
- - apk update && apk add git curl bash
-
-schedule:package-and-qa:notify-success:
- extends:
- - .only-canonical-schedules
- - .notify
- variables:
- COMMIT_NOTES_URL: "https://$CI_SERVER_HOST/$CI_PROJECT_PATH/commit/$CI_COMMIT_SHA#notes-list"
- script:
- - 'scripts/notify-slack qa-master ":tada: Scheduled QA against master passed! :tada: See $CI_PIPELINE_URL. For downstream pipelines, see $COMMIT_NOTES_URL" ci_passing'
- needs: ["schedule:package-and-qa"]
- when: on_success
-
-schedule:package-and-qa:notify-failure:
- extends:
- - .only-canonical-schedules
- - .notify
- variables:
- COMMIT_NOTES_URL: "https://$CI_SERVER_HOST/$CI_PROJECT_PATH/commit/$CI_COMMIT_SHA#notes-list"
- script:
- - 'scripts/notify-slack qa-master ":skull_and_crossbones: Scheduled QA against master failed! :skull_and_crossbones: See $CI_PIPELINE_URL. For downstream pipelines, see $COMMIT_NOTES_URL" ci_failing'
- needs: ["schedule:package-and-qa"]
- when: on_failure
diff --git a/.gitlab/ci/pages.gitlab-ci.yml b/.gitlab/ci/pages.gitlab-ci.yml
index a30772d566..6a2d3702bd 100644
--- a/.gitlab/ci/pages.gitlab-ci.yml
+++ b/.gitlab/ci/pages.gitlab-ci.yml
@@ -4,12 +4,11 @@ pages:
- .default-retry
- .default-cache
- .default-only
- - .only-code-qa-changes
+ - .only:variables-canonical-dot-com
+ - .only:changes-code-backstage-qa
only:
refs:
- master
- variables:
- - $CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE == "gitlab-org"
stage: pages
dependencies: ["coverage", "karma", "gitlab:assets:compile pull-cache"]
script:
diff --git a/.gitlab/ci/qa.gitlab-ci.yml b/.gitlab/ci/qa.gitlab-ci.yml
index 1194948a76..3cb5a40a8b 100644
--- a/.gitlab/ci/qa.gitlab-ci.yml
+++ b/.gitlab/ci/qa.gitlab-ci.yml
@@ -3,7 +3,7 @@
- .default-tags
- .default-retry
- .default-only
- - .only-code-qa-changes
+ - .only:changes-code-qa
stage: test
dependencies: []
cache:
@@ -31,7 +31,6 @@ qa:selectors-foss:
- .only-ee-as-if-foss
.package-and-qa-base:
- extends: .default-only
image: ruby:2.6-alpine
stage: qa
dependencies: []
@@ -40,35 +39,31 @@ qa:selectors-foss:
- source scripts/utils.sh
- install_gitlab_gem
- ./scripts/trigger-build omnibus
- only:
- variables:
- - $CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE =~ /^gitlab-org($|\/)/ # Matches the gitlab-org group or its subgroups
package-and-qa-manual:
extends:
- .package-and-qa-base
- - .only-code-changes
- except:
- refs:
- - master
- - /^\d+-\d+-auto-deploy-\d+$/
+ - .default-only
+ - .only:variables-canonical-dot-com
+ - .except:refs-deploy
+ - .only:changes-code
when: manual
needs: ["build-qa-image", "gitlab:assets:compile pull-cache"]
package-and-qa:
extends:
- .package-and-qa-base
- - .only-qa-changes
- except:
- refs:
- - master
- - /^\d+-\d+-auto-deploy-\d+$/
+ - .default-only
+ - .only:variables-canonical-dot-com
+ - .except:refs-master-tags-stable-deploy
+ - .only:changes-qa
needs: ["build-qa-image", "gitlab:assets:compile pull-cache"]
allow_failure: true
schedule:package-and-qa:
extends:
- .package-and-qa-base
- - .only-code-qa-changes
- - .only-canonical-schedules
+ - .default-only
+ - .only:variables_refs-canonical-dot-com-schedules
needs: ["build-qa-image", "gitlab:assets:compile pull-cache"]
+ allow_failure: true
diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml
index bf478b6876..4ac187e167 100644
--- a/.gitlab/ci/rails.gitlab-ci.yml
+++ b/.gitlab/ci/rails.gitlab-ci.yml
@@ -22,7 +22,7 @@
- .default-cache
- .default-only
- .default-before_script
- - .only-code-changes
+ - .only:changes-code-backstage
.only-code-qa-rails-job-base:
extends:
@@ -31,7 +31,7 @@
- .default-cache
- .default-only
- .default-before_script
- - .only-code-qa-changes
+ - .only:changes-code-backstage-qa
setup-test-env:
extends:
@@ -92,6 +92,14 @@ setup-test-env:
- .use-pg10
- .only-master
+rspec migration pg9:
+ extends: .rspec-base-pg9
+ parallel: 4
+
+rspec migration pg9-foss:
+ extends: .rspec-base-pg9-foss
+ parallel: 4
+
rspec unit pg9:
extends: .rspec-base-pg9
parallel: 20
@@ -140,9 +148,13 @@ rspec system pg10:
- .only-ee
- .use-pg10-ee
+rspec-ee migration pg9:
+ extends: .rspec-ee-base-pg9
+ parallel: 2
+
rspec-ee unit pg9:
extends: .rspec-ee-base-pg9
- parallel: 7
+ parallel: 5
rspec-ee integration pg9:
extends: .rspec-ee-base-pg9
@@ -152,11 +164,17 @@ rspec-ee system pg9:
extends: .rspec-ee-base-pg9
parallel: 5
+rspec-ee migration pg10:
+ extends:
+ - .rspec-ee-base-pg10
+ - .only-master
+ parallel: 2
+
rspec-ee unit pg10:
extends:
- .rspec-ee-base-pg10
- .only-master
- parallel: 7
+ parallel: 5
rspec-ee integration pg10:
extends:
@@ -239,6 +257,7 @@ static-analysis:
dependencies: ["setup-test-env", "compile-assets pull-cache"]
variables:
SETUP_DB: "false"
+ parallel: 2
script:
- scripts/static-analysis
cache:
@@ -251,13 +270,8 @@ static-analysis:
downtime_check:
extends:
- .rake-exec
- - .only-code-changes
- except:
- refs:
- - master
- - tags
- variables:
- - $CI_COMMIT_REF_NAME =~ /^[\d-]+-stable(-ee)?$/
+ - .only:changes-code-backstage
+ - .except:refs-master-tags-stable-deploy
stage: test
needs: ["setup-test-env"]
dependencies: ["setup-test-env"]
diff --git a/.gitlab/ci/releases.gitlab-ci.yml b/.gitlab/ci/releases.gitlab-ci.yml
new file mode 100644
index 0000000000..1ddc4e90fc
--- /dev/null
+++ b/.gitlab/ci/releases.gitlab-ci.yml
@@ -0,0 +1,22 @@
+---
+
+# Syncs any changes pushed to a stable branch to the corresponding CE stable
+# branch. We run this prior to any tests so that random failures don't prevent a
+# sync.
+sync-stable-branch:
+ # We don't need/want any global before/after commands, so we overwrite these
+ # settings.
+ image: alpine:edge
+ stage: sync
+ # This job should only run on EE stable branches on the canonical GitLab.com
+ # repository.
+ only:
+ variables:
+ - $CI_SERVER_HOST == "gitlab.com"
+ refs:
+ - /^[\d-]+-stable-ee$/@gitlab-org/gitlab
+ before_script:
+ - apk add --no-cache --update curl bash
+ after_script: []
+ script:
+ - bash scripts/sync-stable-branch.sh
diff --git a/.gitlab/ci/reports.gitlab-ci.yml b/.gitlab/ci/reports.gitlab-ci.yml
index 16c3f0e4f8..fbb7826b6f 100644
--- a/.gitlab/ci/reports.gitlab-ci.yml
+++ b/.gitlab/ci/reports.gitlab-ci.yml
@@ -11,7 +11,7 @@ code_quality:
extends:
- .default-retry
- .default-only
- - .only-code-changes
+ - .only:changes-code-backstage
stage: test
image: docker:stable
allow_failure: true
@@ -50,7 +50,7 @@ sast:
extends:
- .default-retry
- .default-only
- - .only-code-changes
+ - .only:changes-code-backstage-qa
stage: test
image: docker:stable
variables:
@@ -132,7 +132,7 @@ dependency_scanning:
extends:
- .default-retry
- .default-only
- - .only-code-changes
+ - .only:changes-code-backstage-qa
stage: test
image: docker:stable
variables:
@@ -195,7 +195,7 @@ dast:
extends:
- .default-retry
- .default-only
- - .only-code-qa-changes
+ - .only:changes-code-qa
- .only-review
stage: qa
needs: ["review-deploy"]
diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml
index c78c6a8281..4ed9ac03d0 100644
--- a/.gitlab/ci/review.gitlab-ci.yml
+++ b/.gitlab/ci/review.gitlab-ci.yml
@@ -1,14 +1,8 @@
-.except-deploys:
- except:
- refs:
- - /^\d+-\d+-auto-deploy-\d+$/
-
.review-docker:
extends:
- .default-tags
- .default-retry
- .default-only
- - .except-deploys
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine
services:
- docker:19.03.0-dind
@@ -23,10 +17,9 @@
build-qa-image:
extends:
- .review-docker
- - .only-code-qa-changes
- only:
- variables:
- - $CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE == "gitlab-org"
+ - .only:variables-canonical-dot-com
+ - .except:refs-deploy
+ - .only:changes-code-qa
stage: prepare
script:
- '[[ ! -d "ee/" ]] || export GITLAB_EDITION="ee"'
@@ -35,14 +28,11 @@ build-qa-image:
- echo "${CI_JOB_TOKEN}" | docker login --username gitlab-ci-token --password-stdin ${CI_REGISTRY}
- time docker push ${QA_IMAGE}
-schedule:review-cleanup:
+.base-review-cleanup:
extends:
- .default-tags
- .default-retry
- .default-only
- - .only-code-qa-changes
- - .only-review-schedules
- - .except-deploys
stage: prepare
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base
allow_failure: true
@@ -55,11 +45,22 @@ schedule:review-cleanup:
script:
- ruby -rrubygems scripts/review_apps/automated_cleanup.rb
+schedule:review-cleanup:
+ extends:
+ - .base-review-cleanup
+ - .only-review-schedules
+
+manual:review-cleanup:
+ extends:
+ - .base-review-cleanup
+ - .only:changes-code-qa
+ when: manual
+
.review-build-cng-base:
extends:
+ - .default-tags
+ - .default-retry
- .default-only
- - .only-code-qa-changes
- - .except-deploys
image: ruby:2.6-alpine
stage: review-prepare
before_script:
@@ -74,6 +75,7 @@ review-build-cng:
extends:
- .review-build-cng-base
- .only-review
+ - .only:changes-code-qa
needs: ["gitlab:assets:compile pull-cache"]
schedule:review-build-cng:
@@ -82,26 +84,30 @@ schedule:review-build-cng:
- .only-review-schedules
needs: ["gitlab:assets:compile pull-cache"]
-.review-deploy-base:
+.review-workflow-base:
extends:
- .default-tags
- .default-retry
- .default-only
- - .only-code-qa-changes
- - .except-deploys
- stage: review
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base
dependencies: []
- allow_failure: true
variables:
HOST_SUFFIX: "${CI_ENVIRONMENT_SLUG}"
DOMAIN: "-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}"
- GITLAB_HELM_CHART_REF: "v2.3.7"
+ # v2.4.4 + two improvements:
+ # - Allow to pass an EE license when installing the chart: https://gitlab.com/gitlab-org/charts/gitlab/merge_requests/1008
+ # - Allow to customize the livenessProbe for `gitlab-shell`: https://gitlab.com/gitlab-org/charts/gitlab/merge_requests/1021
+ GITLAB_HELM_CHART_REF: "6c655ed77e60f1f7f533afb97bef8c9cb7dc61eb"
GITLAB_EDITION: "ce"
environment:
name: review/${CI_COMMIT_REF_NAME}
url: https://gitlab-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}
on_stop: review-stop
+
+.review-deploy-base:
+ extends: .review-workflow-base
+ stage: review
+ allow_failure: true
before_script:
- '[[ ! -d "ee/" ]] || export GITLAB_EDITION="ee"'
- export GITLAB_SHELL_VERSION=$( Settings > Integrations. !18990
+- Fixed admin geo collapsed sidebar fly out not showing. !19012
+- Serialize short sha as nil if head commit is blank. !19014
+- Add max width on manifest file attachment input. !19028
+- Do not generate To-Dos additional when editing group mentions. !19037
+- Fix previewing quick actions for epics. !19042
+- Fix errors in GraphQL Todos API due to missing TargetTypeEnum values. !19052
+- Hashed Storage Migration: Handle failed attachment migrations with existing target path. !19061
+- Set shorter TTL for all unauthenticated requests. !19064
+- Fix Todo IDs in GraphQL API. !19068
+- Triggers the correct endpoint on licence approval. !19078
+- Fix search button height on 404 page. !19080
+- Fix Kubernetes help text link. !19121
+- Make `jobs/request` to be resillient. !19150
+- Disable pull mirror if repository is in read-only state. !19182
- Only enable protected paths for POST requests. !19184
+- Enforce default, global project and snippet visibilities. !19188
+- Make Bitbucket Cloud superseded pull requests as closed. !19193
+- Fix crash when docker fails deleting tags. !19208
+- Fix environment name in rollback dialog. !19209
+- Fixed a typo in the "Keyboard Shortcuts" pop-up. !19217 (Manuel Stein)
+- Fix unable to expand or collapse files in merge request by clicking caret. !19222 (Brian T)
+- Allow release block edit button to be visible. !19226
+- Fix double escaping in /tableflip quick action. !19271 (Brian T)
+- Add missing bottom padding in CI/CD settings. !19284 (George Tsiolis)
+- Prevents console warning on design upload. !19297
+- Resolve: Web IDE does not create POSIX Compliant Files. !19339
+- Use initial commit SHA instead of branch id to request IDE files and contents. !19348 (David Palubin)
+- Resolve: Web IDE Throws Error When Viewing Diff for Renamed Files. !19348
+- Fix project service API 500 error. !19367
+- Fix cluster feature highlight popover image. !19372
+- Fix template selector filename bug. !19376
+- Fixes mobile styling issues on security modals. !19391
+- Only move repos for legacy project storage. !19410
+- Show correct total number of commit diff's changes. !19424
+- Increase the timeout for GitLab-managed cert-manager installation to 90 seconds (was 30 seconds). !19447
+- Fix uninitialized constant SystemDashboardService. !19453
+- Properly handle exceptions in StuckCiJobsWorker. !19465
+- Fix user popover not being displayed when the user has a status message. !19519
+- Update omniauth_openid_connect to v0.3.3. !19525
+- Fix project clone dropdown button width. !19551 (George Tsiolis)
+- Do not escape HTML tags in Ansi2json as they are escaped in the frontend. !19610
+- [Geo] Fix: undefined Gitlab::BackgroundMigration::PruneOrphanedGeoEvents. !19638
+- Revert btn-xs styling in projects scss. !19640
+- Fix canary badge and favicon inconsistency. !19645
+- Use fingerprint when comparing security reports in MR widget. !19654
+- Update GCP credit URLs. !19683
+- Update squash_commit_sha only on successful merge. !19688
+- Fix import of snippets having `award_emoji` (Project Export/Import). !19690
+- Allow admins to administer personal snippets. !19693 (Oren Kanner)
+- Re-add missing file sizes in 2-Up diff file viewer. !19710
+- Fix checking task item when previous tasks contain only spaces. !19724
- Fix Bitbucket Cloud importer pull request state. !19734
+- Fix merge train is not refreshed when the system aborts/drops a merge request. !19763
+- Resolve Hide Delete selected in designs when viewing an old version. !19889
+- Use new trial registration URL in billing. !19978
+- Helm v2.16.1. !19981
+- Ensure milestone titles are never empty. !19985
+- Remove unused image/screenshot. !20030 (Lee Tickett)
+- Remove local qualifier from geo sync indicators. !20034 (Lee Tickett)
+- Fixed the scale of embedded videos to fit the page. !20056
+- Fix broken monitor cluster health dashboard. !20120
+- Fix expanding collapsed threads when reference link clicked. !20148
+- Fix sub group export to export direct children. !20172
+- Remove update hook from date filter to prevent js from getting stuck. !20215
+- Prevent Dropzone.js initialisation error by checking target element existence. !20256 (Fabio Huser)
+- Fix style reset in job log when empty ANSI sequence is encoutered. !20367
+- Add productivity analytics merge date filtering limit. !32052
+- Fix productivity analytics listing with multiple labels. !33182
+- Fix closed board list loading issue.
+- Apply correctly the limit of 10 designs per upload.
+- Only allow confirmed users to run pipelines.
+- Fix scroll to bottom with new job log.
+- Fixed protected branches flash styling.
+
+### Deprecated (2 changes)
+
+- Ignore deprecated column and remove references to it. !18911
+- Move some project routes under - scope. !19954
+
+### Changed (56 changes, 6 of them are from the community)
+
+- Upgrade design/copy for issue weights locked feature. !17352
+- Reduce new MR page redundancy by moving the source/target branch selector to the top. !17559
+- Replace raven-js with @sentry/browser. !17715
+- Ask if the user is setting up GitLab for a company during signup. !17999
+- When a user views a file's blame or blob and switches to a branch where the current file does not exist, they will now be redirected to the root of the repository. !18169 (Jesse Hall @jessehall3)
+- Propagate custom environment variables to SAST analyzers. !18193
+- Fix any approver project rule records. !18265
+- Minor UX improvements to Environments Dashboard page. !18280
+- Reduce the allocated IP for Cluster and Services. !18341
+- Update flash messages color sitewide. !18369
+- Add modsecurity template for ingress-controller. !18485
+- Hide projects without access to admin user when admin mode is disabled. !18530 (Diego Louzán)
+- Update Runners Settings Text + Link to Docs. !18534
+- Store Zoom URLs in a table rather than in the issue description. !18620
+- Improve admin dashboard features. !18666
+- Drop `id` column from `ci_build_trace_sections` table. !18741
+- Truncate recommended branch name to a sane length. !18821
+- Add support for YAML anchors in CI scripts. !18849
+- Save dashboard changes by the user into the vuex store. !18862
+- Update expired trial status copy. !18962
+- Can directly add approvers to approval rule. !18965
+- Rename Vulnerabilities API to Vulnerability Findings API. !19029
+- Improve clarity of text for merge train position. !19031
+- Updated Auto-DevOps to kubectl v1.13.12 and helm v2.15.1. !19054 (Leo Antunes)
+- Refactor maximum user counts in license. !19071 (briankabiro)
+- Change return type of getDateInPast to Date. !19081
+- Show approval required status in license compliance. !19114
+- Handle new Container Scanning report format. !19123
+- Allow container scanning to run offline by specifying the Clair DB image to use. !19161
+- Add maven cli opts flag to maven security analyzer (part of dependency scanning). !19174
+- Added report_type attribute to Vulnerabilities. !19179
+- Migrate enabled flag on grafana_integrations table. !19234
+- Improve handling of gpg-agent processes. !19311
+- Update help text of "Tag name" field on Edit Release page. !19321
+- Add user filtering to abuse reports page. !19365
+- Move add license button to project buttons. !19370
+- Update to Mermaid v8.4.2 to support more graph types. !19444
+- Move release meta-data into footer on Releases page. !19451
+- Expose subscribed field in issue lists queried with GraphQL. !19458 (briankabiro)
+- [Geo] Fix: rake gitlab:geo:check on the primary is cluttered. !19460
+- Hide trial banner for namespaces with expired trials. !19510
+- Hide repeated trial offers on self-hosted instances. !19511
+- Add loading icon to error tracking settings page. !19539
+- Upgrade to Gitaly v1.71.0. !19611
+- Make role required when editing profile. !19636
+- Made `name` optional parameter of Release entity. !19705
+- Vulnerabilities history chart - use sparklines. !19745
+- Add event tracking to container registry. !19772
+- Update SaaS trial header to include the tier Gold. !19970
+- Update start a trial option in top right drop down to include Gold. !19971
+- Improve merge request description placeholder. !20032 (Jacopo Beschi @jacopo-beschi)
+- Add backtrace to production_json.log. !20122
+- Change the default concurrency factor of merge train to 20. !20201
+- Upgrade to Gitaly v1.72.0.
+- Require explicit null parameters to remove pages domain certificate and allow to use Let's Encrypt certificates through API.
+- Replace wording trace with log.
+
+### Performance (13 changes)
+
+- Record latencies for Sidekiq failures. !18909
+- Fix N+1 for group container repositories view. !18979
+- Do not render links in commit message on blame page. !19128
+- Puma only: database connection pool now always >= number of worker threads. !19286
+- Run check_mergeability only if merge status requires it. !19364
+- Execute limited request for diff commits instead of preloading. !19485
+- Improve performance of admin/abuse_reports page. !19630
+- Remove N+1 DB calls from branches API. !19661
+- Improve performance of linking LFS objects during import. !19709
+- Optimize MergeRequest#mergeable_discussions_state? method. !19988
+- Add index for unauthenticated requests to projects API default endpoint. !19989
+- Add index for authenticated requests to projects API default endpoint. !19993
+- Increase PumaWorkerKiller memory limit in development environment. !20039
+
+### Added (83 changes, 8 of them are from the community)
+
+- Adds Application Settings and ui settings in the integration admin area for Pendo. !15086
+- Add endpoint for a group's vulnerable projects. !15317
+- Added new chart component to display an anomaly boundary. !16530
+- Add links to associated releases on the Milestones page. !16558
+- Merge Details Page and Edit Page for Page Domains. !16687
+- Share groups with groups. !17117
+- Add links to associated release(s) to the milestone detail page. !17278
+- New group path uniqueness check. !17394
+- Unify html email layout for member html emails. !17699 (Diego Louzán)
+- The Security Dashboard displays DAST vulnerabilities for all the scanned sites, not just the first. !17779
+- Create table for elastic stack. !18015
+- Allow to define a default CI configuration path for new projects. !18073 (Mathieu Parent)
+- Issues queried in GraphQL now sortable by due date. !18094
+- Add cleanup status to clusters. !18144
+- Added Tests tab to pipeline detail that contains a UI for browsing test reports produced by JUnit. !18255
+- Users can verify SAML configuration and view SamlResponse XML. !18362
+- Support Enable/Disable operations in Feature Flag API. !18368
+- Expose arbitrary job artifacts in Merge Request widget. !18385
+- Add project option for deleting source branch. !18408 (Zsolt Kovari)
+- Adds ability to set management project for cluster via API. !18429
+- Close issues on Prometheus alert recovery. !18431
+- Add ApplicationSetting for snowplow_iglu_registry_url. !18449
+- Allow Grafana charts to be embedded in Gitlab Flavored Markdown. !18486
+- Mark todo done by GraphQL API. !18581
+- Create a users_security_dashboard_projects table to store the projects a user has added to their personal security dashboard. !18708
+- New API endpoint for creating anonymous merge request discussions from Visual Review Tools. !18710
+- Enable the color chip in AsciiDoc documents. !18723
+- Add prevent_ldap_sign_in option so LDAP can be used exclusively for sync. !18749
+- Show inherited group variables in project view. !18759
+- Add "release" filter to issue search page. !18761
+- Search list of Sentry errors by title in Gitlab. !18772
+- Add migrations and changes for soft-delete for projects. !18791
+- Support for Crossplane as a managed app. !18797 (Mahendra Bagul)
+- Bump Auto-Deploy image to v0.3.0. !18809
+- Set X-GitLab-NotificationReason header if notification reason is explicit subscription. !18812
+- Add issues, MRs, participants, and labels tabs in group milestone page. !18818
+- Add ability to reorder projects on operations dashboard. !18855
+- Make `Job`, `Bridge` and `Default` inheritable. !18867
+- Show epic events on group activity page. !18869
+- Detail view of Sentry error in GitLab. !18878
+- Expose mergeable state of a merge request. !18888 (briankabiro)
+- Add ability to select a Cluster management project. !18928
+- Add a Slack slash command to add a comment to an issue. !18946
+- Added installation commands for npm and yarn packages to package detail page. !18999
+- Show start and end dates in Epics list page. !19006
+- Populate new pipeline CI vars from params. !19023
+- Add warnings about pages access control settings. !19067
+- Graphql mutation for (un)subscribing to an epic. !19083
+- API for stack trace & detail view of Sentry error in GitLab. !19137
+- Add grafana integration active status checkbox. !19255
+- GraphQL: Add Merge Request milestone mutation. !19257
+- Add MergeRequestSetAssignees GraphQL mutation. !19272
+- Add edit button to metrics dashboard. !19279
+- Add "release" filter to merge request search page. !19315
+- Add dead jobs to Sidekiq metrics API. !19350 (Marco Peterseil)
+- Add pipeline information to dependency list header. !19352
+- Build CI cache key from commit SHAs that changed given files. !19392
+- Adding support for searching tags using '^' and '$'. !19435 (Cauhx Milloy)
+- Sentry error stacktrace. !19492
+- Add an `error_code` attribute to the API response when a cherry-pick or revert fails. !19518
+- Add documentation for sign-in application setting. !19561 (Horatiu Eugen Vlad)
+- Create AWS EKS cluster. !19578
+- Add modsecurity logging sidecar to ingress controller. !19600
+- Add start a trial option in the top-right user dropdown. !19632
+- Manage and display labels from epic in the GraphQL API. !19642
+- Allow order_by updated_at in Deployments API. !19658
+- Add can_edit and project_blob_path to metrics_dashboard endpoint. !19663
+- Add usage ping data for project services. !19687
+- Graphql query for issues can now be sorted by relative_position. !19713
+- Add API endpoint to trigger Group Structure Export. !19779
+- Show Tree UI containing child Epics and Issues within an Epic. !19812
+- Enable environments dashboard by default. !19838
+- Update the DB schema to allow linking between Vulnerabilities and Issues. !19852
+- Add Group Audit Events API. !19868
+- Adds a copy button next to package metadata on the details page. !19881
+- GraphQL: Create MR mutations needed for the sidebar. !19913
+- Add id_before, id_after filter param to projects API. !19949
+- Add modsecurity feature flag to usage ping. !20194
+- Specify management project for a Kubernetes cluster. !20216
+- Upgrade pages to 1.12.0. !20217
+- Support template_project_id parameter in project creation API. !20258
+- Add heatmap chart support. !32424
+- Add template for Serverless Framework/JS. !33805
+
+### Other (59 changes, 26 of them are from the community)
+
+- Add EKS cluster count to usage data. !17059
+- Track the starting and stopping of the current signup flow and the experimental signup flow. !17521
+- Attribute Sidekiq workers according to their workloads. !18066
+- Add ApplicationSetting entries for EKS integration. !18307
+- Geo: Add resigns-related fields to Geo Node Status table. !18379
+- Allow adding requests to performance bar manually. !18464
+- Removes `export_designs` feature flag. !18507 (nate geslin)
+- Update AWS SDK to 2.11.374. !18601
+- Remove required dependecy of Postgresql for Gitaly. !18659
+- Add deployment_merge_requests table. !18755
+- Bump Gitaly to 1.70.0 and remove cache invalidation feature flag. !18766
+- Update gRPC to v1.24.0. !18837
+- Update GitLab Runner Helm Chart to 0.10.0. !18879
+- Adds a Sidekiq queue duration metric. !19005
+- Create explicit Default and Free plans. !19033
+- Improve instance mirroring help text. !19047
+- Add Codesandbox metrics to usage ping. !19075
+- Add internal_socket_dir to gitaly config in setup helper. !19170
+- Use Rails 5.2 Redis caching store. !19202
+- Update GitLab Runner Helm Chart to 0.10.1. !19232
+- Rename snowplow_site_id to snowplow_app_id in application_settings table. !19252
+- Removed IIFEs from network.js file. !19254 (nuwe1)
+- Remove IIFEs from project_select.js. !19288 (minghuan lei)
+- Remove IIFEs from merge_request.js. !19294 (minghuan lei)
+- Make snippet list easier to scan. !19490
+- Removed IIFEs from image_file.js. !19548 (nuwe1)
+- Fix api docs for deleting project cluster. !19558
+- Change blob edit view button styling. !19566
+- Include exception and backtrace in API logs. !19671
+- Add index on marked_for_deletion_at in projects table. !19788
+- Visual design for edit buttons in blob view. !19932
+- Refactor disabled sidebar notifications to Vue. !20007 (minghuan lei)
+- Remove IIFEs from branch_graph.js. !20008 (minghuan lei)
+- Remove IIFEs from new_branch_form.js. !20009 (minghuan lei)
+- Remove duplication from slugifyWithUnderscore function. !20016 (Arun Kumar Mohan)
+- Update registry.gitlab.com/gitlab-org/security-products/codequality to 12-5-stable. !20046 (Takuya Noguchi)
+- Add mb-2 class to global alerts. !20081 (2knal)
+- Remove var from syntax_highlight_spec.js. !20086 (Lee Tickett)
+- Remove var from merge_request_tabs_spec.js. !20087 (Lee Tickett)
+- Remove var from bootstrap_jquery_spec.js. !20089 (Lee Tickett)
+- Remove var from project_select.js. !20091 (Lee Tickett)
+- Remove var from new_commit_form.js. !20095 (Lee Tickett)
+- Remove var from issue.js. !20098 (Lee Tickett)
+- Remove var from new_branch_form.js. !20099 (Lee Tickett)
+- Remove var from tree.js. !20103 (Lee Tickett)
+- Remove var from line_highlighter.js. !20108 (Lee Tickett)
+- Remove var from preview_markdown.js. !20115 (Lee Tickett)
+- remove all references of BoardService in boards_selector.vue. !20147 (nuwe1)
+- Remove all references to BoardsService in index.vue. !20152 (nuwe1)
+- Remove var from labels_select.js. !20153 (Lee Tickett)
+- Remove all reference to BoardService in board_form.vue. !20158 (nuwe1)
+- Remove calendar icon from personal access tokens. !20183
+- Move margin-top from flash container to flash. !20211
+- Bump Auto DevOps deploy image to v0.7.0. !20250
+- Make 'Sidekiq::Testing.fake!' mode as default. !31662 (@blackst0ne)
+- Replace task-done icon with list-task icon to better align with other toolbar list icons.
+- Dependency Scanning template that doesn't rely on Docker-in-Docker.
+- Adding dropdown arrow icon and updated text alignment.
+- Change selects from default browser style to custom style.
## 12.4.2
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 737e2ba509..22d6771a47 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-1.67.1
+1.72.1
diff --git a/GITLAB_ELASTICSEARCH_INDEXER_VERSION b/GITLAB_ELASTICSEARCH_INDEXER_VERSION
index 88c5fb891d..bc80560fad 100644
--- a/GITLAB_ELASTICSEARCH_INDEXER_VERSION
+++ b/GITLAB_ELASTICSEARCH_INDEXER_VERSION
@@ -1 +1 @@
-1.4.0
+1.5.0
diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION
index 1cac385c6c..0eed1a29ef 100644
--- a/GITLAB_PAGES_VERSION
+++ b/GITLAB_PAGES_VERSION
@@ -1 +1 @@
-1.11.0
+1.12.0
diff --git a/Gemfile b/Gemfile
index 920f778c05..d27bc27608 100644
--- a/Gemfile
+++ b/Gemfile
@@ -8,12 +8,12 @@ gem 'bootsnap', '~> 1.4'
gem 'nakayoshi_fork', '~> 0.0.4'
# Responders respond_to and respond_with
-gem 'responders', '~> 2.0'
+gem 'responders', '~> 3.0'
gem 'sprockets', '~> 3.7.0'
# Default values for AR models
-gem 'default_value_for', '~> 3.2.0'
+gem 'default_value_for', '~> 3.3.0'
# Supported DBs
gem 'pg', '~> 1.1'
@@ -42,7 +42,7 @@ gem 'omniauth-shibboleth', '~> 1.3.0'
gem 'omniauth-twitter', '~> 1.4'
gem 'omniauth_crowd', '~> 2.2.0'
gem 'omniauth-authentiq', '~> 0.3.3'
-gem 'omniauth_openid_connect', '~> 0.3.1'
+gem 'omniauth_openid_connect', '~> 0.3.3'
gem "omniauth-ultraauth", '~> 0.0.2'
gem 'omniauth-salesforce', '~> 1.0.5'
gem 'rack-oauth2', '~> 1.9.3'
@@ -64,7 +64,7 @@ gem 'u2f', '~> 0.2.1'
# GitLab Pages
gem 'validates_hostname', '~> 1.0.6'
-gem 'rubyzip', '~> 1.2.2', require: 'zip'
+gem 'rubyzip', '~> 1.3.0', require: 'zip'
# GitLab Pages letsencrypt support
gem 'acme-client', '~> 2.0.2'
@@ -72,7 +72,7 @@ gem 'acme-client', '~> 2.0.2'
gem 'browser', '~> 2.5'
# GPG
-gem 'gpgme', '~> 2.0.18'
+gem 'gpgme', '~> 2.0.19'
# LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes
@@ -136,7 +136,7 @@ gem 'faraday_middleware-aws-signers-v4'
# Markdown and HTML processing
gem 'html-pipeline', '~> 2.8'
-gem 'deckar01-task_list', '2.2.0'
+gem 'deckar01-task_list', '2.2.1'
gem 'gitlab-markup', '~> 1.7.0'
gem 'github-markup', '~> 1.7.0', require: 'github/markup'
gem 'commonmarker', '~> 0.17'
@@ -151,7 +151,7 @@ gem 'asciidoctor-plantuml', '0.0.9'
gem 'rouge', '~> 3.11.0'
gem 'truncato', '~> 0.7.11'
gem 'bootstrap_form', '~> 4.2.0'
-gem 'nokogiri', '~> 1.10.4'
+gem 'nokogiri', '~> 1.10.5'
gem 'escape_utils', '~> 1.1'
# Calendar rendering
@@ -159,6 +159,7 @@ gem 'icalendar'
# Diffs
gem 'diffy', '~> 3.1.0'
+gem 'diff_match_patch', '~> 0.1.0'
# Application server
gem 'rack', '~> 2.0.7'
@@ -175,7 +176,7 @@ group :puma do
end
# State machine
-gem 'state_machines-activerecord', '~> 0.5.1'
+gem 'state_machines-activerecord', '~> 0.6.0'
# Issue tags
gem 'acts-as-taggable-on', '~> 6.0'
@@ -259,9 +260,6 @@ gem 'loofah', '~> 2.2'
# Working with license
gem 'licensee', '~> 8.9'
-# Protect against bruteforcing
-gem 'rack-attack', '~> 4.4.1'
-
# Ace editor
gem 'ace-rails-ap', '~> 4.1.0'
@@ -293,10 +291,13 @@ gem 'base32', '~> 0.3.0'
gem "gitlab-license", "~> 1.0"
+# Protect against bruteforcing
+gem 'rack-attack', '~> 6.2.0'
+
# Sentry integration
gem 'sentry-raven', '~> 2.9'
-gem 'premailer-rails', '~> 1.9.7'
+gem 'premailer-rails', '~> 1.10.3'
# LabKit: Tracing and Correlation
gem 'gitlab-labkit', '~> 0.5'
@@ -331,7 +332,6 @@ group :metrics do
end
group :development do
- gem 'foreman', '~> 0.84.0'
gem 'brakeman', '~> 4.2', require: false
gem 'danger', '~> 6.0', require: false
@@ -388,7 +388,6 @@ group :development, :test do
gem 'benchmark-ips', '~> 2.3.0', require: false
- gem 'license_finder', '~> 5.4', require: false
gem 'knapsack', '~> 1.17'
gem 'stackprof', '~> 0.2.10', require: false
@@ -398,6 +397,11 @@ group :development, :test do
gem 'timecop', '~> 0.8.0'
end
+# Gems required in omnibus-gitlab pipeline
+group :development, :test, :omnibus do
+ gem 'license_finder', '~> 5.4', require: false
+end
+
group :test do
gem 'shoulda-matchers', '~> 4.0.1', require: false
gem 'email_spec', '~> 2.2.0'
@@ -407,6 +411,7 @@ group :test do
gem 'concurrent-ruby', '~> 1.1'
gem 'test-prof', '~> 0.10.0'
gem 'rspec_junit_formatter'
+ gem 'guard-rspec'
end
gem 'octokit', '~> 4.9'
@@ -446,18 +451,18 @@ group :ed25519 do
end
# Gitaly GRPC protocol definitions
-gem 'gitaly', '~> 1.65.0'
+gem 'gitaly', '~> 1.70.0'
-gem 'grpc', '~> 1.19.0'
+gem 'grpc', '~> 1.24.0'
-gem 'google-protobuf', '~> 3.7.1'
+gem 'google-protobuf', '~> 3.8.0'
gem 'toml-rb', '~> 1.0.0', require: false
# Feature toggles
-gem 'flipper', '~> 0.13.0'
-gem 'flipper-active_record', '~> 0.13.0'
-gem 'flipper-active_support_cache_store', '~> 0.13.0'
+gem 'flipper', '~> 0.17.1'
+gem 'flipper-active_record', '~> 0.17.1'
+gem 'flipper-active_support_cache_store', '~> 0.17.1'
gem 'unleash', '~> 0.1.5'
# Structured logging
@@ -469,3 +474,5 @@ gem 'gitlab-net-dns', '~> 0.9.1'
# Countries list
gem 'countries', '~> 3.0'
+
+gem 'retriable', '~> 3.1.2'
diff --git a/Gemfile.lock b/Gemfile.lock
index 18160932c5..15465cd6b0 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -50,8 +50,8 @@ GEM
i18n (>= 0.7, < 2)
minitest (~> 5.1)
tzinfo (~> 1.1)
- acts-as-taggable-on (6.0.0)
- activerecord (~> 5.0)
+ acts-as-taggable-on (6.5.0)
+ activerecord (>= 5.0, < 6.1)
adamantium (0.2.0)
ice_nine (~> 0.11.0)
memoizable (~> 0.4.0)
@@ -80,14 +80,16 @@ GEM
encryptor (~> 3.0.0)
attr_required (1.0.1)
awesome_print (1.8.0)
- aws-sdk (2.9.32)
- aws-sdk-resources (= 2.9.32)
- aws-sdk-core (2.9.32)
+ aws-eventstream (1.0.3)
+ aws-sdk (2.11.374)
+ aws-sdk-resources (= 2.11.374)
+ aws-sdk-core (2.11.374)
aws-sigv4 (~> 1.0)
jmespath (~> 1.0)
- aws-sdk-resources (2.9.32)
- aws-sdk-core (= 2.9.32)
- aws-sigv4 (1.0.0)
+ aws-sdk-resources (2.11.374)
+ aws-sdk-core (= 2.11.374)
+ aws-sigv4 (1.1.0)
+ aws-eventstream (~> 1.0, >= 1.0.2)
axiom-types (0.1.1)
descendants_tracker (~> 0.0.4)
ice_nine (~> 0.11.0)
@@ -171,9 +173,9 @@ GEM
unicode_utils (~> 1.4)
crack (0.4.3)
safe_yaml (~> 1.0.0)
- crass (1.0.4)
+ crass (1.0.5)
creole (0.5.0)
- css_parser (1.5.0)
+ css_parser (1.7.0)
addressable
daemons (1.2.6)
danger (6.0.9)
@@ -192,12 +194,12 @@ GEM
database_cleaner (1.7.0)
debug_inspector (0.0.3)
debugger-ruby_core_source (1.3.8)
- deckar01-task_list (2.2.0)
+ deckar01-task_list (2.2.1)
html-pipeline
declarative (0.0.10)
declarative-option (0.1.0)
- default_value_for (3.2.0)
- activerecord (>= 3.2.0, < 6.0)
+ default_value_for (3.3.0)
+ activerecord (>= 3.2.0, < 6.1)
derailed_benchmarks (1.3.5)
benchmark-ips (~> 2)
get_process_mem (~> 0)
@@ -222,6 +224,7 @@ GEM
railties
rotp (~> 2.0)
diff-lcs (1.3)
+ diff_match_patch (0.1.0)
diffy (3.1.0)
discordrb-webhooks-blackst0ne (3.3.0)
rest-client (~> 2.0)
@@ -285,13 +288,13 @@ GEM
fast_gettext (1.6.0)
ffaker (2.10.0)
ffi (1.11.1)
- flipper (0.13.0)
- flipper-active_record (0.13.0)
- activerecord (>= 3.2, < 6)
- flipper (~> 0.13.0)
- flipper-active_support_cache_store (0.13.0)
- activesupport (>= 3.2, < 6)
- flipper (~> 0.13.0)
+ flipper (0.17.1)
+ flipper-active_record (0.17.1)
+ activerecord (>= 4.2, < 7)
+ flipper (~> 0.17.1)
+ flipper-active_support_cache_store (0.17.1)
+ activesupport (>= 4.2, < 7)
+ flipper (~> 0.17.1)
flowdock (0.7.1)
httparty (~> 0.7)
multi_json
@@ -332,10 +335,8 @@ GEM
fog-xml (0.1.3)
fog-core
nokogiri (>= 1.5.11, < 2.0.0)
- font-awesome-rails (4.7.0.4)
- railties (>= 3.2, < 6.0)
- foreman (0.84.0)
- thor (~> 0.19.1)
+ font-awesome-rails (4.7.0.5)
+ railties (>= 3.2, < 6.1)
formatador (0.2.5)
fugit (1.2.1)
et-orbi (~> 1.1, >= 1.1.8)
@@ -358,12 +359,12 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
git (1.5.0)
- gitaly (1.65.0)
+ gitaly (1.70.0)
grpc (~> 1.0)
github-markup (1.7.0)
- gitlab-labkit (0.5.2)
- actionpack (~> 5)
- activesupport (~> 5)
+ gitlab-labkit (0.7.0)
+ actionpack (>= 5.0.0, < 6.1.0)
+ activesupport (>= 5.0.0, < 6.1.0)
grpc (~> 1.19)
jaeger-client (~> 0.10)
opentracing (~> 0.4)
@@ -400,7 +401,7 @@ GEM
mime-types (~> 3.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
- google-protobuf (3.7.1)
+ google-protobuf (3.8.0)
googleapis-common-protos-types (1.0.4)
google-protobuf (~> 3.0)
googleauth (0.6.6)
@@ -410,7 +411,7 @@ GEM
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (~> 0.7)
- gpgme (2.0.18)
+ gpgme (2.0.19)
mini_portile2 (~> 2.3)
grape (1.1.0)
activesupport
@@ -440,11 +441,25 @@ GEM
graphql (~> 1.6)
html-pipeline (~> 2.8)
sass (~> 3.4)
- grpc (1.19.0)
- google-protobuf (~> 3.1)
- googleapis-common-protos-types (~> 1.0.0)
+ grpc (1.24.0)
+ google-protobuf (~> 3.8)
+ googleapis-common-protos-types (~> 1.0)
gssapi (1.2.0)
ffi (>= 1.0.1)
+ guard (2.15.1)
+ formatador (>= 0.2.4)
+ listen (>= 2.7, < 4.0)
+ lumberjack (>= 1.0.12, < 2.0)
+ nenv (~> 0.1)
+ notiffany (~> 0.0)
+ pry (>= 0.9.12)
+ shellany (~> 0.0)
+ thor (>= 0.18.1)
+ guard-compat (1.2.1)
+ guard-rspec (4.7.3)
+ guard (~> 2.1)
+ guard-compat (~> 1.1)
+ rspec (>= 2.99.0, < 4.0)
haml (5.0.4)
temple (>= 0.8.0)
tilt
@@ -508,7 +523,7 @@ GEM
atlassian-jwt
multipart-post
oauth (~> 0.5, >= 0.5.0)
- jmespath (1.3.1)
+ jmespath (1.4.0)
js_regex (3.1.1)
character_set (~> 1.1)
regexp_parser (~> 1.1)
@@ -560,15 +575,20 @@ GEM
xml-simple
licensee (8.9.2)
rugged (~> 0.24)
+ listen (3.1.5)
+ rb-fsevent (~> 0.9, >= 0.9.4)
+ rb-inotify (~> 0.9, >= 0.9.7)
+ ruby_dep (~> 1.2)
locale (2.1.2)
lograge (0.10.0)
actionpack (>= 4)
activesupport (>= 4)
railties (>= 4)
request_store (~> 1.0)
- loofah (2.3.0)
+ loofah (2.3.1)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
+ lumberjack (1.0.13)
mail (2.7.1)
mini_mime (>= 0.1.1)
mail_room (0.9.1)
@@ -584,7 +604,7 @@ GEM
mime-types-data (3.2019.0331)
mimemagic (0.3.2)
mini_magick (4.9.5)
- mini_mime (1.0.1)
+ mini_mime (1.0.2)
mini_portile2 (2.4.0)
minitest (5.11.3)
msgpack (1.3.1)
@@ -597,16 +617,20 @@ GEM
mustermann (~> 1.0.0)
nakayoshi_fork (0.0.4)
nap (1.1.0)
+ nenv (0.3.0)
net-ldap (0.16.0)
net-ntp (2.1.3)
net-ssh (5.2.0)
netrc (0.11.0)
nio4r (2.3.1)
no_proxy_fix (0.1.2)
- nokogiri (1.10.4)
+ nokogiri (1.10.5)
mini_portile2 (~> 2.4.0)
nokogumbo (1.5.0)
nokogiri
+ notiffany (0.1.3)
+ nenv (~> 0.1)
+ shellany (~> 0.0)
numerizer (0.1.1)
oauth (0.5.4)
oauth2 (1.4.1)
@@ -675,12 +699,12 @@ GEM
activesupport
nokogiri (>= 1.4.4)
omniauth (~> 1.0)
- omniauth_openid_connect (0.3.1)
+ omniauth_openid_connect (0.3.3)
addressable (~> 2.5)
- omniauth (~> 1.3)
+ omniauth (~> 1.9)
openid_connect (~> 1.1)
open4 (1.3.4)
- openid_connect (1.1.6)
+ openid_connect (1.1.8)
activemodel
attr_required (>= 1.0.0)
json-jwt (>= 1.5.0)
@@ -703,12 +727,12 @@ GEM
pg (1.1.4)
po_to_json (1.0.1)
json (>= 1.6.0)
- premailer (1.10.4)
+ premailer (1.11.1)
addressable
- css_parser (>= 1.4.10)
+ css_parser (>= 1.6.0)
htmlentities (>= 4.0.0)
- premailer-rails (1.9.7)
- actionmailer (>= 3, < 6)
+ premailer-rails (1.10.3)
+ actionmailer (>= 3)
premailer (~> 1.7, >= 1.7.9)
proc_to_ast (0.1.0)
coderay
@@ -724,7 +748,7 @@ GEM
pry (~> 0.10)
pry-rails (0.3.6)
pry (>= 0.10.4)
- public_suffix (3.1.0)
+ public_suffix (3.1.1)
puma (3.12.0)
puma_worker_killer (0.1.0)
get_process_mem (~> 0.2)
@@ -734,8 +758,8 @@ GEM
rack (2.0.7)
rack-accept (0.4.5)
rack (>= 0.4)
- rack-attack (4.4.1)
- rack
+ rack-attack (6.2.0)
+ rack (>= 1.0, < 3)
rack-cors (1.0.2)
rack-oauth2 (1.9.3)
activesupport
@@ -763,10 +787,10 @@ GEM
bundler (>= 1.3.0)
railties (= 5.2.3)
sprockets-rails (>= 2.0.0)
- rails-controller-testing (1.0.2)
- actionpack (~> 5.x, >= 5.0.1)
- actionview (~> 5.x, >= 5.0.1)
- activesupport (~> 5.x)
+ rails-controller-testing (1.0.4)
+ actionpack (>= 5.0.1.x)
+ actionview (>= 5.0.1.x)
+ activesupport (>= 5.0.1.x)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
@@ -798,25 +822,25 @@ GEM
recaptcha (4.13.1)
json
recursive-open-struct (1.1.0)
- redis (4.1.2)
- redis-actionpack (5.0.2)
- actionpack (>= 4.0, < 6)
+ redis (4.1.3)
+ redis-actionpack (5.1.0)
+ actionpack (>= 4.0, < 7)
redis-rack (>= 1, < 3)
redis-store (>= 1.1.0, < 2)
- redis-activesupport (5.0.7)
- activesupport (>= 3, < 6)
+ redis-activesupport (5.2.0)
+ activesupport (>= 3, < 7)
redis-store (>= 1.3, < 2)
redis-namespace (1.6.0)
redis (>= 3.0.4)
- redis-rack (2.0.5)
+ redis-rack (2.0.6)
rack (>= 1.5, < 3)
redis-store (>= 1.2, < 2)
redis-rails (5.0.2)
redis-actionpack (>= 5.0, < 6)
redis-activesupport (>= 5.0, < 6)
redis-store (>= 1.2, < 2)
- redis-store (1.6.0)
- redis (>= 2.2, < 5)
+ redis-store (1.8.1)
+ redis (>= 4, < 5)
regexp_parser (1.5.1)
regexp_property_values (0.3.4)
representable (3.0.4)
@@ -824,9 +848,9 @@ GEM
declarative-option (< 0.2.0)
uber (< 0.2.0)
request_store (1.3.1)
- responders (2.4.1)
- actionpack (>= 4.2.0, < 6.0)
- railties (>= 4.2.0, < 6.0)
+ responders (3.0.0)
+ actionpack (>= 5.0)
+ railties (>= 5.0)
rest-client (2.0.2)
http-cookie (>= 1.0.2, < 2.0)
mime-types (>= 1.16, < 4.0)
@@ -897,11 +921,12 @@ GEM
ruby-progressbar (1.10.1)
ruby-saml (1.7.2)
nokogiri (>= 1.5.10)
+ ruby_dep (1.5.0)
ruby_parser (3.13.1)
sexp_processor (~> 4.9)
rubyntlm (0.6.2)
rubypants (0.2.0)
- rubyzip (1.2.2)
+ rubyzip (1.3.0)
rugged (0.28.3.1)
safe_yaml (1.0.4)
sanitize (4.6.6)
@@ -938,6 +963,7 @@ GEM
faraday (>= 0.7.6, < 1.0)
settingslogic (2.0.9)
sexp_processor (4.12.0)
+ shellany (0.0.1)
shoulda-matchers (4.0.1)
activesupport (>= 4.2.0)
sidekiq (5.2.7)
@@ -978,11 +1004,11 @@ GEM
sshkey (2.0.0)
stackprof (0.2.10)
state_machines (0.5.0)
- state_machines-activemodel (0.5.1)
- activemodel (>= 4.1, < 6.0)
+ state_machines-activemodel (0.7.1)
+ activemodel (>= 4.1)
state_machines (>= 0.5.0)
- state_machines-activerecord (0.5.1)
- activerecord (>= 4.1, < 6.0)
+ state_machines-activerecord (0.6.0)
+ activerecord (>= 4.1)
state_machines-activemodel (>= 0.5.0)
swd (1.1.2)
activesupport (>= 3)
@@ -1127,12 +1153,13 @@ DEPENDENCIES
creole (~> 0.5.0)
danger (~> 6.0)
database_cleaner (~> 1.7.0)
- deckar01-task_list (= 2.2.0)
- default_value_for (~> 3.2.0)
+ deckar01-task_list (= 2.2.1)
+ default_value_for (~> 3.3.0)
derailed_benchmarks
device_detector
devise (~> 4.6)
devise-two-factor (~> 3.0.0)
+ diff_match_patch (~> 0.1.0)
diffy (~> 3.1.0)
discordrb-webhooks-blackst0ne (~> 3.3)
doorkeeper (~> 4.3)
@@ -1149,9 +1176,9 @@ DEPENDENCIES
faraday_middleware-aws-signers-v4
fast_blank
ffaker (~> 2.10)
- flipper (~> 0.13.0)
- flipper-active_record (~> 0.13.0)
- flipper-active_support_cache_store (~> 0.13.0)
+ flipper (~> 0.17.1)
+ flipper-active_record (~> 0.17.1)
+ flipper-active_support_cache_store (~> 0.17.1)
flowdock (~> 0.7)
fog-aliyun (~> 0.3)
fog-aws (~> 3.5)
@@ -1161,14 +1188,13 @@ DEPENDENCIES
fog-openstack (~> 1.0)
fog-rackspace (~> 0.1.1)
font-awesome-rails (~> 4.7)
- foreman (~> 0.84.0)
fugit (~> 1.2.1)
fuubar (~> 2.2.0)
gemojione (~> 3.3)
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
- gitaly (~> 1.65.0)
+ gitaly (~> 1.70.0)
github-markup (~> 1.7.0)
gitlab-labkit (~> 0.5)
gitlab-license (~> 1.0)
@@ -1181,8 +1207,8 @@ DEPENDENCIES
gitlab_omniauth-ldap (~> 2.1.1)
gon (~> 6.2)
google-api-client (~> 0.23)
- google-protobuf (~> 3.7.1)
- gpgme (~> 2.0.18)
+ google-protobuf (~> 3.8.0)
+ gpgme (~> 2.0.19)
grape (~> 1.1.0)
grape-entity (~> 0.7.1)
grape-path-helpers (~> 1.1)
@@ -1190,8 +1216,9 @@ DEPENDENCIES
graphiql-rails (~> 1.4.10)
graphql (~> 1.9.11)
graphql-docs (~> 1.6.0)
- grpc (~> 1.19.0)
+ grpc (~> 1.24.0)
gssapi
+ guard-rspec
haml_lint (~> 0.31.0)
hamlit (~> 2.8.8)
hangouts-chat (~> 0.0.5)
@@ -1226,7 +1253,7 @@ DEPENDENCIES
net-ldap
net-ntp
net-ssh (~> 5.2)
- nokogiri (~> 1.10.4)
+ nokogiri (~> 1.10.5)
oauth2 (~> 1.4)
octokit (~> 4.9)
omniauth (~> 1.8)
@@ -1246,17 +1273,17 @@ DEPENDENCIES
omniauth-twitter (~> 1.4)
omniauth-ultraauth (~> 0.0.2)
omniauth_crowd (~> 2.2.0)
- omniauth_openid_connect (~> 0.3.1)
+ omniauth_openid_connect (~> 0.3.3)
org-ruby (~> 0.9.12)
pg (~> 1.1)
- premailer-rails (~> 1.9.7)
+ premailer-rails (~> 1.10.3)
prometheus-client-mmap (~> 0.9.10)
pry-byebug (~> 3.5.1)
pry-rails (~> 0.3.4)
puma (~> 3.12)
puma_worker_killer
rack (~> 2.0.7)
- rack-attack (~> 4.4.1)
+ rack-attack (~> 6.2.0)
rack-cors (~> 1.0.0)
rack-oauth2 (~> 1.9.3)
rack-proxy (~> 0.6.0)
@@ -1275,7 +1302,8 @@ DEPENDENCIES
redis-namespace (~> 1.6.0)
redis-rails (~> 5.0.2)
request_store (~> 1.3)
- responders (~> 2.0)
+ responders (~> 3.0)
+ retriable (~> 3.1.2)
rouge (~> 3.11.0)
rqrcode-rails3 (~> 0.1.7)
rspec-parameterized
@@ -1291,7 +1319,7 @@ DEPENDENCIES
ruby-prof (~> 1.0.0)
ruby-progressbar
ruby_parser (~> 3.8)
- rubyzip (~> 1.2.2)
+ rubyzip (~> 1.3.0)
rugged (~> 0.28)
sanitize (~> 4.6)
sassc-rails (~> 2.1.0)
@@ -1312,7 +1340,7 @@ DEPENDENCIES
sprockets (~> 3.7.0)
sshkey (~> 2.0)
stackprof (~> 0.2.10)
- state_machines-activerecord (~> 0.5.1)
+ state_machines-activerecord (~> 0.6.0)
sys-filesystem (~> 1.1.6)
test-prof (~> 0.10.0)
thin (~> 1.7.0)
diff --git a/Guardfile b/Guardfile
new file mode 100644
index 0000000000..8a43f414ca
--- /dev/null
+++ b/Guardfile
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+# More info at https://github.com/guard/guard#readme
+
+cmd = ENV['SPRING'] ? 'spring rspec' : 'bundle exec rspec'
+
+guard :rspec, cmd: cmd do
+ require "guard/rspec/dsl"
+ dsl = Guard::RSpec::Dsl.new(self)
+
+ directories %w(app ee lib spec)
+
+ # RSpec files
+ rspec = dsl.rspec
+ watch(rspec.spec_helper) { rspec.spec_dir }
+ watch(rspec.spec_support) { rspec.spec_dir }
+ watch(rspec.spec_files)
+
+ # Ruby files
+ ruby = dsl.ruby
+ dsl.watch_spec_files_for(ruby.lib_files)
+
+ # Rails files
+ rails = dsl.rails(view_extensions: %w(erb haml slim))
+ dsl.watch_spec_files_for(rails.app_files)
+ dsl.watch_spec_files_for(rails.views)
+
+ watch(rails.controllers) do |m|
+ [
+ rspec.spec.call("routing/#{m[1]}_routing"),
+ rspec.spec.call("controllers/#{m[1]}_controller")
+ ]
+ end
+
+ # Rails config changes
+ watch(rails.spec_helper) { rspec.spec_dir }
+ watch(rails.routes) { "#{rspec.spec_dir}/routing" }
+ watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
+
+ # Capybara features specs
+ watch(rails.view_dirs) { |m| rspec.spec.call("features/#{m[1]}") }
+ watch(rails.layouts) { |m| rspec.spec.call("features/#{m[1]}") }
+end
diff --git a/PROCESS.md b/PROCESS.md
index 6bff60bff0..45f28b33a6 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -79,7 +79,7 @@ star, smile, etc.). Some good tips about code reviews can be found in our
Overview and details of feature flag processes in development of GitLab itself is described in [feature flags process documentation](https://docs.gitlab.com/ee/development/feature_flags/process.html).
-Guides on how to include feature flags in your backend/frontend code while developing GitLab are described in [developing with feature flags documentation](https://docs.gitlab.com/ee/development/feature_flags/developing.html).
+Guides on how to include feature flags in your backend/frontend code while developing GitLab are described in [developing with feature flags documentation](https://docs.gitlab.com/ee/development/feature_flags/development.html).
Getting access and how to expose the feature to users is detailed in [controlling feature flags documentation](https://docs.gitlab.com/ee/development/feature_flags/controls.html).
diff --git a/VERSION b/VERSION
index fc2e27826a..b6125ec500 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-12.4.6
+12.5.4
diff --git a/app/assets/images/cluster_app_logos/crossplane.png b/app/assets/images/cluster_app_logos/crossplane.png
new file mode 100644
index 0000000000000000000000000000000000000000..32d8175108c251cdf6330fa7a2f5d3afc1fc9c24
GIT binary patch
literal 1850
zcmZ{lSu`8^7RLY4(A0Elp6BrxD(0Dl5LC3$P&!;QEmd)?67o#09b{FZ~0>G3@;-Jri?Xhx1A#wK>R*Epsq|qWn=R0n0
z*OLN4(D#~vpMfZ;lvhezOcV9N$Q!rg?VSb}@RMDv-+kRTE$7{A`V}puD_Z%vp
z74Q&dz&VrSk)T)rN3!sBH%<~lo&&0ZCvw8P%PHUYT}hIPLnxvKw+J>Aj**a+iCTp6
zDySQATW@|96RngB!<0N!O&2onO*ChQMeor<8TBz-iE^v83O3qx*es&T#^G~`v#~F>
z*L23~$KH^awgCsYEC93BnKMf+>ufoi!ka@4w2y}|$k}wPZ8Me?#WSr_Y|6S9GMvGi
z#N{TLeSsTLi3-EKw<||>7^UaatdNqWBz+MX=RJDadxsuRR+x6{70ag1yfzn!PU0o!
zew4F3{#*@`x+eemT_F7neb1P6Gbq(Ei&H^2yC1!2!IIua?08fW6@rg7}U%!}T?FD9+8hwO+_a>QHCBz3TEfqFP
z`Jc4QP4=<2wr)knjf7Q}#haY3&Oh#un}_MDWwMqVXA)2Q6J*hZhocD(c-s7w)Yl5t
zSRXw7mHJ_Dtj_1Q)OXih)cWM<%LI~at@A{Zd%?(%l^Y}S>AjTCmtEI{(u)uYKa*S2
z&nd*|4^>a6vS@j;P*lsO1ZrWmMkaliwysOP@EU#7Dafx9A1^A8Al~oGAn#K<_N<@F
zek-k%EzO|CfYpHDDmZI?ULeNU81ZLsW`uj6^dez<>Sg<&tR@A$%$b#quxmJn$k>;(U!REZU#joLOSL>
z$D4US!pG%6I~LKhHgHB6K@>C{|6rLXiX(SRCKP|HR#(_XyLcgi35+odKd2vpVtpS4!+QflRd_>|Inqh5Z3e|z^IKdsHJEFs8cqtgQ|3el(e
zm@(HOdSLBU?yHE-=PlTLl~W)vzEZsslk^*7b56>;%S&GGmk3~m7%Ms`a-!h5U#`}>
zbRES8>GMHAf^Zkt(t32<>A_-)RI!QxR#=Hb)c>|I9c5j{EOoZc_~_nBs~gOc=Iiwo
z#P`J#4xZwXUpMeuMl~8u(6HJcb787px3r7umPAaWvP;Ki4Vr}mR4-fYK3QNwvJL;`
zd|Fc1VSVEEn9d*MI8%;l$$Z%NZEMdqV<5GDc?I1)8LA!V4%MP|-64_4(FO!JkL0w9
z$)wH~3t`d6H_xVyMrRvlE2ju3HG8S?I!VW!+f2rFJrt%k_I_8FZb)A@oFO5OImE^Lg5
z6)ir<0|Jd>GM0^h-ci&$NZW!40HAp3lWDB~dR&h3Ew6!{0)Rj%z@`Qt)TmJZ6YqGe
z)ZoV_1kodZFC&7V%&tDUi(UxpLk#m&z;Jp#7NTDKmheEd)i@)K-djUrQNmAFS>b|M
z9D&E9GCp~?S10UuxOvF*%vYJ<+S9hE^?ZWSjTh}IRMpe%MT&!rSA59skIj;qi4Nw;
zNCEASGcn^IrxOjhc%_-=kJ|ivY+}`sGm;veMmHX%1&^B#rbJdjNWQB202ibBma-p&
z$r|nfYJxRy37Ilu?B}8qZRZi~?-Lzh<`)^j2GE1+nriF9wRMegaJZSFu^HS*8*X3*
khxjL@Q?o=5E8phVh4buoeS!{jd#+20qWgPiU0rr
literal 0
HcmV?d00001
diff --git a/app/assets/images/cluster_app_logos/elastic_stack.png b/app/assets/images/cluster_app_logos/elastic_stack.png
new file mode 100644
index 0000000000000000000000000000000000000000..69fbc6aacd072103d2946e166581cd25bc8e1d89
GIT binary patch
literal 2919
zcmeAS@N?(olHy`uVBq!ia0y~yV15C@9LzwGs#=w63=E770X`wFK<)>>83TnF_HPEE
z@-sm6@2Ch6p|K$%)FR=wAR7o5gfQ?p0GCR*MqKVk)`-jos&@)V6#~XVLrIWdFoT49
z@r)f;p8xzWnXu%(%n2snEq~e4ZpPiQJi|BjpY;RAnoY77HJ<3czVKh*z?KyU{(pGz
z|L4!=zqBv17)J;$nAq35omnmn=z0kd!=eLgw#ZnJvD)OgC>brKPdmxx*G0cjk=c
zzkilfr}91Ev$n3OVcfKd@#00Z4sBXD9i8IJ@<6824T^
zwdGTyHtHK}yPLOOr0T1$f2Qvnd7q5!)4BHEQtS6x{pdhKQ1xT4h2b~mY*BjfVA&RN
zm${;W$+mla=N5Z}nN(N*xwv|zy)K_ci1BUtpNqrKKe%8u*O;gKIxp|9#o@l^SFkc3
zHco$RQ~$;5=QoMD#yQ-_YNB6+e!6>XPebDD>p#|ASpE2SZ$Gouhr-9doz{1~U&haD
z^(pZeQ2gO-tL3vV?brRH^}vhu&%46)B1royhi!Idp~H1C^@Y!K{J1n6XDNTF@%8g&_S#_LD^|75Z7qk!
z(XK$J^H;x!1WYoCeKAw(y{t#X)RuY6PiihO$=q~dbI7ybZ#Ss9PHT}1P+FLtx#)ss
z=q!ydJZrOAr>xlO_d-%izO-Fi^~=WDOkP_v*Id{3qT>wK|c2eb*Ps
zb)8_CkdA6Rh6~XH4;c0YU5E%0cuWyA7bEZ*E@1c^!(5EOYvDt8FNV2PiR{L-D=uB@
zMahZ)hpt&aJl`~ZDJprEu32`lJCOhEjaEgw2WO+&`~;CwGQ_tS&LtR<%SNVwRxpKx9xw`2SJfcpEj^O
znYhrsYH^6c_r!(I(@P!RuAg#2o!_N%LH?=>#$OIp2P$d26RqmoCm!(MB1dy$$q&Ea
z7m>R*3odOumg{sr_~T>^&Oa+8oz7oA+pEE8k|$8LE^2>I(1G*jEfripGga!zg@J>xxJCw`Nd4_f0D6{
z`^}wS?6l55T)&9>!}{KIZrl3lV*mMH+^C-|-hM&*LC>*?jIvgLq?fl{(0(|j&)fZZ
zudwqA(L$%+{~2X}=f9UaP|cH`(*HaX%l2>g<6RhjYU$hkyevG<
z_v0@e4GccJV)f@oH|C`?^M&qyU-SNc=tbAJ_y4OsnDFP-_WC^ruU1Q%mSxI(KK-1T
z@7?p4M*6qHu3ycr|2Mgz`Nu{7=x~>{ri<^r>segC`}HmQ
z{x5@s_+MR0cZC$cyxMJ?l=0|@Khrmp^3q-ETK+F?+X@sO*_L~I8e5L}R*f$9#cD14
z?kWp@T%Q|%OQ {
+ .then(({ data, headers }) => {
callback(data);
-
- return data;
+ return { data, headers };
});
},
@@ -239,7 +241,8 @@ const Api = {
.get(url, {
params: Object.assign({}, defaults, options),
})
- .then(({ data }) => callback(data));
+ .then(({ data }) => callback(data))
+ .catch(() => flash(__('Something went wrong while fetching projects')));
},
commitMultiple(id, data) {
@@ -348,6 +351,20 @@ const Api = {
});
},
+ userProjects(userId, query, options, callback) {
+ const url = Api.buildUrl(Api.userProjectsPath).replace(':id', userId);
+ const defaults = {
+ search: query,
+ per_page: 20,
+ };
+ return axios
+ .get(url, {
+ params: Object.assign({}, defaults, options),
+ })
+ .then(({ data }) => callback(data))
+ .catch(() => flash(__('Something went wrong while fetching projects')));
+ },
+
branches(id, query = '', options = {}) {
const url = Api.buildUrl(this.createBranchPath).replace(':id', encodeURIComponent(id));
diff --git a/app/assets/javascripts/behaviors/preview_markdown.js b/app/assets/javascripts/behaviors/preview_markdown.js
index a07942d87c..ca91400eac 100644
--- a/app/assets/javascripts/behaviors/preview_markdown.js
+++ b/app/assets/javascripts/behaviors/preview_markdown.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, no-var */
+/* eslint-disable func-names */
import $ from 'jquery';
import axios from '~/lib/utils/axios_utils';
@@ -12,11 +12,8 @@ import { __ } from '~/locale';
// more than `x` users are referenced.
//
-var lastTextareaPreviewed;
-var lastTextareaHeight = null;
-var markdownPreview;
-var previewButtonSelector;
-var writeButtonSelector;
+let lastTextareaHeight;
+let lastTextareaPreviewed;
function MarkdownPreview() {}
@@ -27,14 +24,13 @@ MarkdownPreview.prototype.emptyMessage = __('Nothing to preview.');
MarkdownPreview.prototype.ajaxCache = {};
MarkdownPreview.prototype.showPreview = function($form) {
- var mdText;
- var preview = $form.find('.js-md-preview');
- var url = preview.data('url');
+ const preview = $form.find('.js-md-preview');
+ const url = preview.data('url');
if (preview.hasClass('md-preview-loading')) {
return;
}
- mdText = $form.find('textarea.markdown-area').val();
+ const mdText = $form.find('textarea.markdown-area').val();
if (mdText === undefined) {
return;
@@ -46,7 +42,7 @@ MarkdownPreview.prototype.showPreview = function($form) {
} else {
preview.addClass('md-preview-loading').text(__('Loading...'));
this.fetchMarkdownPreview(mdText, url, response => {
- var body;
+ let body;
if (response.body.length > 0) {
({ body } = response);
} else {
@@ -91,8 +87,7 @@ MarkdownPreview.prototype.hideReferencedUsers = function($form) {
};
MarkdownPreview.prototype.renderReferencedUsers = function(users, $form) {
- var referencedUsers;
- referencedUsers = $form.find('.referenced-users');
+ const referencedUsers = $form.find('.referenced-users');
if (referencedUsers.length) {
if (users.length >= this.referenceThreshold) {
referencedUsers.show();
@@ -108,8 +103,7 @@ MarkdownPreview.prototype.hideReferencedCommands = function($form) {
};
MarkdownPreview.prototype.renderReferencedCommands = function(commands, $form) {
- var referencedCommands;
- referencedCommands = $form.find('.referenced-commands');
+ const referencedCommands = $form.find('.referenced-commands');
if (commands.length > 0) {
referencedCommands.html(commands);
referencedCommands.show();
@@ -119,15 +113,15 @@ MarkdownPreview.prototype.renderReferencedCommands = function(commands, $form) {
}
};
-markdownPreview = new MarkdownPreview();
+const markdownPreview = new MarkdownPreview();
-previewButtonSelector = '.js-md-preview-button';
-writeButtonSelector = '.js-md-write-button';
+const previewButtonSelector = '.js-md-preview-button';
+const writeButtonSelector = '.js-md-write-button';
lastTextareaPreviewed = null;
const markdownToolbar = $('.md-header-toolbar');
$.fn.setupMarkdownPreview = function() {
- var $form = $(this);
+ const $form = $(this);
$form.find('textarea.markdown-area').on('input', () => {
markdownPreview.hideReferencedUsers($form);
});
@@ -188,7 +182,7 @@ $(document).on('markdown-preview:hide', (e, $form) => {
});
$(document).on('markdown-preview:toggle', (e, keyboardEvent) => {
- var $target;
+ let $target;
$target = $(keyboardEvent.target);
if ($target.is('textarea.markdown-area')) {
$(document).triggerHandler('markdown-preview:show', [$target.closest('form')]);
@@ -201,16 +195,14 @@ $(document).on('markdown-preview:toggle', (e, keyboardEvent) => {
});
$(document).on('click', previewButtonSelector, function(e) {
- var $form;
e.preventDefault();
- $form = $(this).closest('form');
+ const $form = $(this).closest('form');
$(document).triggerHandler('markdown-preview:show', [$form]);
});
$(document).on('click', writeButtonSelector, function(e) {
- var $form;
e.preventDefault();
- $form = $(this).closest('form');
+ const $form = $(this).closest('form');
$(document).triggerHandler('markdown-preview:hide', [$form]);
});
diff --git a/app/assets/javascripts/blob/file_template_mediator.js b/app/assets/javascripts/blob/file_template_mediator.js
index b371f6be26..aedd8004ea 100644
--- a/app/assets/javascripts/blob/file_template_mediator.js
+++ b/app/assets/javascripts/blob/file_template_mediator.js
@@ -118,8 +118,6 @@ export default class FileTemplateMediator {
}
});
- this.setFilename(item.name);
-
if (this.editor.getValue() !== '') {
this.setTypeSelectorToggleText(item.name);
}
@@ -133,14 +131,16 @@ export default class FileTemplateMediator {
selectTemplateFile(selector, query, data) {
const self = this;
+ const { name } = selector.config;
selector.renderLoading();
this.fetchFileTemplate(selector.config.type, query, data)
.then(file => {
this.setEditorContent(file);
+ this.setFilename(name);
selector.renderLoaded();
- this.typeSelector.setToggleText(selector.config.name);
+ this.typeSelector.setToggleText(name);
toast(__(`${query} template applied`), {
action: {
text: __('Undo'),
diff --git a/app/assets/javascripts/boards/components/board_form.vue b/app/assets/javascripts/boards/components/board_form.vue
index 3456056075..c0df8b7209 100644
--- a/app/assets/javascripts/boards/components/board_form.vue
+++ b/app/assets/javascripts/boards/components/board_form.vue
@@ -133,7 +133,7 @@ export default {
if (this.board.name.length === 0) return;
this.isLoading = true;
if (this.isDeleteForm) {
- gl.boardService
+ boardsStore
.deleteBoard(this.currentBoard)
.then(() => {
visitUrl(boardsStore.rootPath);
@@ -143,7 +143,7 @@ export default {
this.isLoading = false;
});
} else {
- gl.boardService
+ boardsStore
.createBoard(this.board)
.then(resp => resp.data)
.then(data => {
diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue
index 1273fcc6a9..b8439bc874 100644
--- a/app/assets/javascripts/boards/components/board_list.vue
+++ b/app/assets/javascripts/boards/components/board_list.vue
@@ -84,7 +84,8 @@ export default {
this.$nextTick(() => {
if (
this.scrollHeight() <= this.listHeight() &&
- this.list.issuesSize > this.list.issues.length
+ this.list.issuesSize > this.list.issues.length &&
+ this.list.isExpanded
) {
this.list.page += 1;
this.list.getIssues(false).catch(() => {
diff --git a/app/assets/javascripts/boards/components/boards_selector.vue b/app/assets/javascripts/boards/components/boards_selector.vue
index 334c162954..32491dfbcb 100644
--- a/app/assets/javascripts/boards/components/boards_selector.vue
+++ b/app/assets/javascripts/boards/components/boards_selector.vue
@@ -168,7 +168,7 @@ export default {
}
const recentBoardsPromise = new Promise((resolve, reject) =>
- gl.boardService
+ boardsStore
.recentBoards()
.then(resolve)
.catch(err => {
@@ -184,7 +184,7 @@ export default {
}),
);
- Promise.all([gl.boardService.allBoards(), recentBoardsPromise])
+ Promise.all([boardsStore.allBoards(), recentBoardsPromise])
.then(([allBoards, recentBoards]) => [allBoards.data, recentBoards.data])
.then(([allBoardsJson, recentBoardsJson]) => {
this.loading = false;
diff --git a/app/assets/javascripts/boards/components/issue_card_inner.vue b/app/assets/javascripts/boards/components/issue_card_inner.vue
index 40d75d53f7..d37e49bab4 100644
--- a/app/assets/javascripts/boards/components/issue_card_inner.vue
+++ b/app/assets/javascripts/boards/components/issue_card_inner.vue
@@ -1,5 +1,6 @@
@@ -217,7 +265,7 @@ export default {
{{
s__(`ClusterIntegration|Choose which applications to install on your Kubernetes cluster.
- Helm Tiller is required to install any of the following applications.`)
+ Helm Tiller is required to install any of the following applications.`)
}}
{{ __('More information') }}
@@ -242,9 +290,9 @@ export default {
{{
s__(`ClusterIntegration|Helm streamlines installing
- and managing Kubernetes applications.
- Tiller runs inside of your Kubernetes Cluster,
- and manages releases of your charts.`)
+ and managing Kubernetes applications.
+ Tiller runs inside of your Kubernetes Cluster,
+ and manages releases of your charts.`)
}}
@@ -252,7 +300,7 @@ export default {
{{
s__(`ClusterIntegration|You must first install Helm Tiller before
- installing the applications below`)
+ installing the applications below`)
}}
{{
s__(`ClusterIntegration|Ingress gives you a way to route
- requests to services based on the request host or path,
- centralizing a number of services into a single entrypoint.`)
+ requests to services based on the request host or path,
+ centralizing a number of services into a single entrypoint.`)
}}
@@ -308,8 +356,8 @@ export default {
{{
s__(`ClusterIntegration|Point a wildcard DNS to this
- generated endpoint in order to access
- your application after it has been deployed.`)
+ generated endpoint in order to access
+ your application after it has been deployed.`)
}}
{{ __('More information') }}
@@ -320,8 +368,8 @@ export default {
{{
s__(`ClusterIntegration|The endpoint is in
- the process of being assigned. Please check your Kubernetes
- cluster or Quotas on Google Kubernetes Engine if it takes a long time.`)
+ the process of being assigned. Please check your Kubernetes
+ cluster or Quotas on Google Kubernetes Engine if it takes a long time.`)
}}
{{ __('More information') }}
@@ -368,7 +416,7 @@ export default {
{{
s__(`ClusterIntegration|Issuers represent a certificate authority.
- You must provide an email address for your Issuer. `)
+ You must provide an email address for your Issuer. `)
}}
{{
s__(`ClusterIntegration|GitLab Runner connects to the
- repository and executes CI/CD jobs,
- pushing results back and deploying
- applications to production.`)
+ repository and executes CI/CD jobs,
+ pushing results back and deploying
+ applications to production.`)
}}
+
+
+
+
+
+
{{
s__(`ClusterIntegration|JupyterHub, a multi-user Hub, spawns,
- manages, and proxies multiple instances of the single-user
- Jupyter notebook server. JupyterHub can be used to serve
- notebooks to a class of students, a corporate data science group,
- or a scientific research group.`)
+ manages, and proxies multiple instances of the single-user
+ Jupyter notebook server. JupyterHub can be used to serve
+ notebooks to a class of students, a corporate data science group,
+ or a scientific research group.`)
}}
@@ -481,7 +557,7 @@ export default {
{{
s__(`ClusterIntegration|Replace this with your own hostname if you want.
- If you do so, point hostname to Ingress IP Address from above.`)
+ If you do so, point hostname to Ingress IP Address from above.`)
}}
{{ __('More information') }}
@@ -527,9 +603,9 @@ export default {
{{
s__(`ClusterIntegration|Knative extends Kubernetes to provide
- a set of middleware components that are essential to build modern,
- source-centric, and container-based applications that can run
- anywhere: on premises, in the cloud, or even in a third-party data center.`)
+ a set of middleware components that are essential to build modern,
+ source-centric, and container-based applications that can run
+ anywhere: on premises, in the cloud, or even in a third-party data center.`)
}}
@@ -542,6 +618,75 @@ export default {
/>
+
+
+
+ {{
+ s__(
+ `ClusterIntegration|The elastic stack collects logs from all pods in your cluster`,
+ )
+ }}
+
+
+
+
+
+
+
diff --git a/app/assets/javascripts/clusters/components/crossplane_provider_stack.vue b/app/assets/javascripts/clusters/components/crossplane_provider_stack.vue
new file mode 100644
index 0000000000..966918ae63
--- /dev/null
+++ b/app/assets/javascripts/clusters/components/crossplane_provider_stack.vue
@@ -0,0 +1,93 @@
+
+
+
+
+
+ {{ s__('ClusterIntegration|Enabled stack') }}
+
+
+
+ {{ stack.name }}
+
+
+
{{ validationError }}
+
+ {{ s__(`You must select a stack for configuring your cloud provider. Learn more about`) }}
+ {{ __('Crossplane') }}
+
+
+
diff --git a/app/assets/javascripts/clusters/components/uninstall_application_confirmation_modal.vue b/app/assets/javascripts/clusters/components/uninstall_application_confirmation_modal.vue
index f1925c243f..125bcaacc1 100644
--- a/app/assets/javascripts/clusters/components/uninstall_application_confirmation_modal.vue
+++ b/app/assets/javascripts/clusters/components/uninstall_application_confirmation_modal.vue
@@ -2,7 +2,16 @@
import { GlModal } from '@gitlab/ui';
import { sprintf, s__ } from '~/locale';
import trackUninstallButtonClickMixin from 'ee_else_ce/clusters/mixins/track_uninstall_button_click';
-import { HELM, INGRESS, CERT_MANAGER, PROMETHEUS, RUNNER, KNATIVE, JUPYTER } from '../constants';
+import {
+ HELM,
+ INGRESS,
+ CERT_MANAGER,
+ PROMETHEUS,
+ RUNNER,
+ KNATIVE,
+ JUPYTER,
+ ELASTIC_STACK,
+} from '../constants';
const CUSTOM_APP_WARNING_TEXT = {
[HELM]: sprintf(
@@ -28,6 +37,7 @@ const CUSTOM_APP_WARNING_TEXT = {
[JUPYTER]: s__(
'ClusterIntegration|All data not committed to GitLab will be deleted and cannot be restored.',
),
+ [ELASTIC_STACK]: s__('ClusterIntegration|All data will be deleted and cannot be restored.'),
};
export default {
diff --git a/app/assets/javascripts/clusters/constants.js b/app/assets/javascripts/clusters/constants.js
index c6e4b7951c..9f98f170fb 100644
--- a/app/assets/javascripts/clusters/constants.js
+++ b/app/assets/javascripts/clusters/constants.js
@@ -50,8 +50,19 @@ export const JUPYTER = 'jupyter';
export const KNATIVE = 'knative';
export const RUNNER = 'runner';
export const CERT_MANAGER = 'cert_manager';
+export const CROSSPLANE = 'crossplane';
export const PROMETHEUS = 'prometheus';
+export const ELASTIC_STACK = 'elastic_stack';
-export const APPLICATIONS = [HELM, INGRESS, JUPYTER, KNATIVE, RUNNER, CERT_MANAGER, PROMETHEUS];
+export const APPLICATIONS = [
+ HELM,
+ INGRESS,
+ JUPYTER,
+ KNATIVE,
+ RUNNER,
+ CERT_MANAGER,
+ PROMETHEUS,
+ ELASTIC_STACK,
+];
export const INGRESS_DOMAIN_SUFFIX = '.nip.io';
diff --git a/app/assets/javascripts/clusters/services/clusters_service.js b/app/assets/javascripts/clusters/services/clusters_service.js
index fa12802b3d..333fb293a1 100644
--- a/app/assets/javascripts/clusters/services/clusters_service.js
+++ b/app/assets/javascripts/clusters/services/clusters_service.js
@@ -7,10 +7,12 @@ export default class ClusterService {
helm: this.options.installHelmEndpoint,
ingress: this.options.installIngressEndpoint,
cert_manager: this.options.installCertManagerEndpoint,
+ crossplane: this.options.installCrossplaneEndpoint,
runner: this.options.installRunnerEndpoint,
prometheus: this.options.installPrometheusEndpoint,
jupyter: this.options.installJupyterEndpoint,
knative: this.options.installKnativeEndpoint,
+ elastic_stack: this.options.installElasticStackEndpoint,
};
this.appUpdateEndpointMap = {
knative: this.options.updateKnativeEndpoint,
diff --git a/app/assets/javascripts/clusters/stores/clusters_store.js b/app/assets/javascripts/clusters/stores/clusters_store.js
index 6464461ea0..35dbf95155 100644
--- a/app/assets/javascripts/clusters/stores/clusters_store.js
+++ b/app/assets/javascripts/clusters/stores/clusters_store.js
@@ -5,6 +5,8 @@ import {
JUPYTER,
KNATIVE,
CERT_MANAGER,
+ ELASTIC_STACK,
+ CROSSPLANE,
RUNNER,
APPLICATION_INSTALLED_STATUSES,
APPLICATION_STATUS,
@@ -25,6 +27,7 @@ const applicationInitialState = {
uninstallable: false,
uninstallFailed: false,
uninstallSuccessful: false,
+ validationError: null,
};
export default class ClusterStore {
@@ -57,6 +60,11 @@ export default class ClusterStore {
title: s__('ClusterIntegration|Cert-Manager'),
email: null,
},
+ crossplane: {
+ ...applicationInitialState,
+ title: s__('ClusterIntegration|Crossplane'),
+ stack: null,
+ },
runner: {
...applicationInitialState,
title: s__('ClusterIntegration|GitLab Runner'),
@@ -85,6 +93,11 @@ export default class ClusterStore {
updateSuccessful: false,
updateFailed: false,
},
+ elastic_stack: {
+ ...applicationInitialState,
+ title: s__('ClusterIntegration|Elastic Stack'),
+ kibana_hostname: null,
+ },
},
environments: [],
fetchingEnvironments: false,
@@ -197,13 +210,15 @@ export default class ClusterStore {
} else if (appId === CERT_MANAGER) {
this.state.applications.cert_manager.email =
this.state.applications.cert_manager.email || serverAppEntry.email;
+ } else if (appId === CROSSPLANE) {
+ this.state.applications.crossplane.stack =
+ this.state.applications.crossplane.stack || serverAppEntry.stack;
} else if (appId === JUPYTER) {
- this.state.applications.jupyter.hostname =
- this.state.applications.jupyter.hostname ||
- serverAppEntry.hostname ||
- (this.state.applications.ingress.externalIp
- ? `jupyter.${this.state.applications.ingress.externalIp}.nip.io`
- : '');
+ this.state.applications.jupyter.hostname = this.updateHostnameIfUnset(
+ this.state.applications.jupyter.hostname,
+ serverAppEntry.hostname,
+ 'jupyter',
+ );
} else if (appId === KNATIVE) {
if (!this.state.applications.knative.isEditingHostName) {
this.state.applications.knative.hostname =
@@ -216,10 +231,26 @@ export default class ClusterStore {
} else if (appId === RUNNER) {
this.state.applications.runner.version = version;
this.state.applications.runner.updateAvailable = updateAvailable;
+ } else if (appId === ELASTIC_STACK) {
+ this.state.applications.elastic_stack.kibana_hostname = this.updateHostnameIfUnset(
+ this.state.applications.elastic_stack.kibana_hostname,
+ serverAppEntry.kibana_hostname,
+ 'kibana',
+ );
}
});
}
+ updateHostnameIfUnset(current, updated, fallback) {
+ return (
+ current ||
+ updated ||
+ (this.state.applications.ingress.externalIp
+ ? `${fallback}.${this.state.applications.ingress.externalIp}.nip.io`
+ : '')
+ );
+ }
+
toggleFetchEnvironments(isFetching) {
this.state.fetchingEnvironments = isFetching;
}
diff --git a/app/assets/javascripts/commit/image_file.js b/app/assets/javascripts/commit/image_file.js
index 6c04e0beb4..60c2059a87 100644
--- a/app/assets/javascripts/commit/image_file.js
+++ b/app/assets/javascripts/commit/image_file.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, no-var, no-else-return, consistent-return, one-var, no-return-assign, no-unused-expressions, no-sequences */
+/* eslint-disable func-names, no-var, no-else-return, consistent-return, one-var, no-return-assign */
import $ from 'jquery';
@@ -9,40 +9,29 @@ const viewModes = ['two-up', 'swipe'];
export default class ImageFile {
constructor(file) {
this.file = file;
- this.requestImageInfo(
- $('.two-up.view .frame.deleted img', this.file),
- (function(_this) {
- return function() {
- return _this.requestImageInfo($('.two-up.view .frame.added img', _this.file), () => {
- _this.initViewModes();
+ this.requestImageInfo($('.two-up.view .frame.deleted img', this.file), () =>
+ this.requestImageInfo($('.two-up.view .frame.added img', this.file), () => {
+ this.initViewModes();
- // Load two-up view after images are loaded
- // so that we can display the correct width and height information
- const $images = $('.two-up.view img', _this.file);
+ // Load two-up view after images are loaded
+ // so that we can display the correct width and height information
+ const $images = $('.two-up.view img', this.file);
- $images.waitForImages(() => {
- _this.initView('two-up');
- });
- });
- };
- })(this),
+ $images.waitForImages(() => {
+ this.initView('two-up');
+ });
+ }),
);
}
initViewModes() {
const viewMode = viewModes[0];
$('.view-modes', this.file).removeClass('hide');
- $('.view-modes-menu', this.file).on(
- 'click',
- 'li',
- (function(_this) {
- return function(event) {
- if (!$(event.currentTarget).hasClass('active')) {
- return _this.activateViewMode(event.currentTarget.className);
- }
- };
- })(this),
- );
+ $('.view-modes-menu', this.file).on('click', 'li', event => {
+ if (!$(event.currentTarget).hasClass('active')) {
+ return this.activateViewMode(event.currentTarget.className);
+ }
+ });
return this.activateViewMode(viewMode);
}
@@ -51,15 +40,10 @@ export default class ImageFile {
.removeClass('active')
.filter(`.${viewMode}`)
.addClass('active');
- return $(`.view:visible:not(.${viewMode})`, this.file).fadeOut(
- 200,
- (function(_this) {
- return function() {
- $(`.view.${viewMode}`, _this.file).fadeIn(200);
- return _this.initView(viewMode);
- };
- })(this),
- );
+ return $(`.view:visible:not(.${viewMode})`, this.file).fadeOut(200, () => {
+ $(`.view.${viewMode}`, this.file).fadeIn(200);
+ return this.initView(viewMode);
+ });
}
initView(viewMode) {
@@ -103,22 +87,18 @@ export default class ImageFile {
.on('touchmove', dragMove);
}
- prepareFrames(view) {
+ static prepareFrames(view) {
var maxHeight, maxWidth;
maxWidth = 0;
maxHeight = 0;
$('.frame', view)
- .each(
- (function() {
- return function(index, frame) {
- var height, width;
- width = $(frame).width();
- height = $(frame).height();
- maxWidth = width > maxWidth ? width : maxWidth;
- return (maxHeight = height > maxHeight ? height : maxHeight);
- };
- })(this),
- )
+ .each((index, frame) => {
+ var height, width;
+ width = $(frame).width();
+ height = $(frame).height();
+ maxWidth = width > maxWidth ? width : maxWidth;
+ return (maxHeight = height > maxHeight ? height : maxHeight);
+ })
.css({
width: maxWidth,
height: maxHeight,
@@ -128,104 +108,95 @@ export default class ImageFile {
views = {
'two-up': function() {
- return $('.two-up.view .wrap', this.file).each(
- (function(_this) {
- return function(index, wrap) {
- $('img', wrap).each(function() {
- var currentWidth;
- currentWidth = $(this).width();
- if (currentWidth > availWidth / 2) {
- return $(this).width(availWidth / 2);
- }
- });
- return _this.requestImageInfo($('img', wrap), (width, height) => {
- $('.image-info .meta-width', wrap).text(`${width}px`);
- $('.image-info .meta-height', wrap).text(`${height}px`);
- return $('.image-info', wrap).removeClass('hide');
- });
- };
- })(this),
- );
+ return $('.two-up.view .wrap', this.file).each((index, wrap) => {
+ $('img', wrap).each(function() {
+ var currentWidth;
+ currentWidth = $(this).width();
+ if (currentWidth > availWidth / 2) {
+ return $(this).width(availWidth / 2);
+ }
+ });
+ return this.requestImageInfo($('img', wrap), (width, height) => {
+ $('.image-info .meta-width', wrap).text(`${width}px`);
+ $('.image-info .meta-height', wrap).text(`${height}px`);
+ return $('.image-info', wrap).removeClass('hide');
+ });
+ });
},
swipe() {
var maxHeight, maxWidth;
maxWidth = 0;
maxHeight = 0;
- return $('.swipe.view', this.file).each(
- (function(_this) {
- return function(index, view) {
- var $swipeWrap, $swipeBar, $swipeFrame, wrapPadding, ref;
- (ref = _this.prepareFrames(view)), ([maxWidth, maxHeight] = ref);
- $swipeFrame = $('.swipe-frame', view);
- $swipeWrap = $('.swipe-wrap', view);
- $swipeBar = $('.swipe-bar', view);
+ return $('.swipe.view', this.file).each((index, view) => {
+ var $swipeWrap, $swipeBar, $swipeFrame, wrapPadding;
+ const ref = ImageFile.prepareFrames(view);
+ [maxWidth, maxHeight] = ref;
+ $swipeFrame = $('.swipe-frame', view);
+ $swipeWrap = $('.swipe-wrap', view);
+ $swipeBar = $('.swipe-bar', view);
- $swipeFrame.css({
- width: maxWidth + 16,
- height: maxHeight + 28,
- });
- $swipeWrap.css({
- width: maxWidth + 1,
- height: maxHeight + 2,
- });
- // Set swipeBar left position to match image frame
- $swipeBar.css({
- left: 1,
- });
+ $swipeFrame.css({
+ width: maxWidth + 16,
+ height: maxHeight + 28,
+ });
+ $swipeWrap.css({
+ width: maxWidth + 1,
+ height: maxHeight + 2,
+ });
+ // Set swipeBar left position to match image frame
+ $swipeBar.css({
+ left: 1,
+ });
- wrapPadding = parseInt($swipeWrap.css('right').replace('px', ''), 10);
+ wrapPadding = parseInt($swipeWrap.css('right').replace('px', ''), 10);
- _this.initDraggable($swipeBar, wrapPadding, (e, left) => {
- if (left > 0 && left < $swipeFrame.width() - wrapPadding * 2) {
- $swipeWrap.width(maxWidth + 1 - left);
- $swipeBar.css('left', left);
- }
- });
- };
- })(this),
- );
+ this.initDraggable($swipeBar, wrapPadding, (e, left) => {
+ if (left > 0 && left < $swipeFrame.width() - wrapPadding * 2) {
+ $swipeWrap.width(maxWidth + 1 - left);
+ $swipeBar.css('left', left);
+ }
+ });
+ });
},
'onion-skin': function() {
var dragTrackWidth, maxHeight, maxWidth;
maxWidth = 0;
maxHeight = 0;
dragTrackWidth = $('.drag-track', this.file).width() - $('.dragger', this.file).width();
- return $('.onion-skin.view', this.file).each(
- (function(_this) {
- return function(index, view) {
- var $frame, $track, $dragger, $frameAdded, framePadding, ref;
- (ref = _this.prepareFrames(view)), ([maxWidth, maxHeight] = ref);
- $frame = $('.onion-skin-frame', view);
- $frameAdded = $('.frame.added', view);
- $track = $('.drag-track', view);
- $dragger = $('.dragger', $track);
+ return $('.onion-skin.view', this.file).each((index, view) => {
+ var $frame, $track, $dragger, $frameAdded, framePadding;
- $frame.css({
- width: maxWidth + 16,
- height: maxHeight + 28,
- });
- $('.swipe-wrap', view).css({
- width: maxWidth + 1,
- height: maxHeight + 2,
- });
- $dragger.css({
- left: dragTrackWidth,
- });
+ const ref = ImageFile.prepareFrames(view);
+ [maxWidth, maxHeight] = ref;
+ $frame = $('.onion-skin-frame', view);
+ $frameAdded = $('.frame.added', view);
+ $track = $('.drag-track', view);
+ $dragger = $('.dragger', $track);
- $frameAdded.css('opacity', 1);
- framePadding = parseInt($frameAdded.css('right').replace('px', ''), 10);
+ $frame.css({
+ width: maxWidth + 16,
+ height: maxHeight + 28,
+ });
+ $('.swipe-wrap', view).css({
+ width: maxWidth + 1,
+ height: maxHeight + 2,
+ });
+ $dragger.css({
+ left: dragTrackWidth,
+ });
- _this.initDraggable($dragger, framePadding, (e, left) => {
- var opacity = left / dragTrackWidth;
+ $frameAdded.css('opacity', 1);
+ framePadding = parseInt($frameAdded.css('right').replace('px', ''), 10);
- if (opacity >= 0 && opacity <= 1) {
- $dragger.css('left', left);
- $frameAdded.css('opacity', opacity);
- }
- });
- };
- })(this),
- );
+ this.initDraggable($dragger, framePadding, (e, left) => {
+ var opacity = left / dragTrackWidth;
+
+ if (opacity >= 0 && opacity <= 1) {
+ $dragger.css('left', left);
+ $frameAdded.css('opacity', opacity);
+ }
+ });
+ });
},
};
@@ -235,14 +206,7 @@ export default class ImageFile {
if (domImg.complete) {
return callback.call(this, domImg.naturalWidth, domImg.naturalHeight);
} else {
- return img.on(
- 'load',
- (function(_this) {
- return function() {
- return callback.call(_this, domImg.naturalWidth, domImg.naturalHeight);
- };
- })(this),
- );
+ return img.on('load', () => callback.call(this, domImg.naturalWidth, domImg.naturalHeight));
}
}
}
diff --git a/app/assets/javascripts/compare_autocomplete.js b/app/assets/javascripts/compare_autocomplete.js
index 81ba15577f..a23707209d 100644
--- a/app/assets/javascripts/compare_autocomplete.js
+++ b/app/assets/javascripts/compare_autocomplete.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, one-var, no-var, no-else-return */
+/* eslint-disable func-names, no-else-return */
import $ from 'jquery';
import { __ } from './locale';
@@ -8,9 +8,8 @@ import { capitalizeFirstCharacter } from './lib/utils/text_utility';
export default function initCompareAutocomplete(limitTo = null, clickHandler = () => {}) {
$('.js-compare-dropdown').each(function() {
- var $dropdown, selected;
- $dropdown = $(this);
- selected = $dropdown.data('selected');
+ const $dropdown = $(this);
+ const selected = $dropdown.data('selected');
const $dropdownContainer = $dropdown.closest('.dropdown');
const $fieldInput = $(`input[name="${$dropdown.data('fieldName')}"]`, $dropdownContainer);
const $filterInput = $('input[type="search"]', $dropdownContainer);
@@ -44,17 +43,16 @@ export default function initCompareAutocomplete(limitTo = null, clickHandler = (
fieldName: $dropdown.data('fieldName'),
filterInput: 'input[type="search"]',
renderRow(ref) {
- var link;
+ const link = $(' ')
+ .attr('href', '#')
+ .addClass(ref === selected ? 'is-active' : '')
+ .text(ref)
+ .attr('data-ref', ref);
if (ref.header != null) {
return $('
')
.addClass('dropdown-header')
.text(ref.header);
} else {
- link = $(' ')
- .attr('href', '#')
- .addClass(ref === selected ? 'is-active' : '')
- .text(ref)
- .attr('data-ref', ref);
return $('
').append(link);
}
},
diff --git a/app/assets/javascripts/confidential_merge_request/components/project_form_group.vue b/app/assets/javascripts/confidential_merge_request/components/project_form_group.vue
index 197a070606..4fa18b1955 100644
--- a/app/assets/javascripts/confidential_merge_request/components/project_form_group.vue
+++ b/app/assets/javascripts/confidential_merge_request/components/project_form_group.vue
@@ -41,7 +41,7 @@ export default {
noForkText() {
return sprintf(
__(
- "To protect this issue's confidentiality, %{link_start}fork the project%{link_end} and set the forks visiblity to private.",
+ "To protect this issue's confidentiality, %{link_start}fork the project%{link_end} and set the forks visibility to private.",
),
{ link_start: ` `, link_end: ' ' },
false,
diff --git a/app/assets/javascripts/contributors/components/contributors.vue b/app/assets/javascripts/contributors/components/contributors.vue
new file mode 100644
index 0000000000..7dd6b051cb
--- /dev/null
+++ b/app/assets/javascripts/contributors/components/contributors.vue
@@ -0,0 +1,227 @@
+
+
+
+
+
+
+
+
+
+
{{ __('Commits to') }} {{ branch }}
+
{{ __('Excluding merge commits. Limited to 6,000 commits.') }}
+
+
+
+
+
+
+
{{ contributor.name }}
+
{{ n__('%d commit', '%d commits', contributor.commits) }} ({{ contributor.email }})
+
+
+
+
+
+
diff --git a/app/assets/javascripts/contributors/index.js b/app/assets/javascripts/contributors/index.js
new file mode 100644
index 0000000000..b606358973
--- /dev/null
+++ b/app/assets/javascripts/contributors/index.js
@@ -0,0 +1,23 @@
+import Vue from 'vue';
+import ContributorsGraphs from './components/contributors.vue';
+import store from './stores';
+
+export default () => {
+ const el = document.querySelector('.js-contributors-graph');
+
+ if (!el) return null;
+
+ return new Vue({
+ el,
+ store,
+
+ render(createElement) {
+ return createElement(ContributorsGraphs, {
+ props: {
+ endpoint: el.dataset.projectGraphPath,
+ branch: el.dataset.projectBranch,
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/contributors/services/contributors_service.js b/app/assets/javascripts/contributors/services/contributors_service.js
new file mode 100644
index 0000000000..5a8bbb6651
--- /dev/null
+++ b/app/assets/javascripts/contributors/services/contributors_service.js
@@ -0,0 +1,7 @@
+import axios from '~/lib/utils/axios_utils';
+
+export default {
+ fetchChartData(endpoint) {
+ return axios.get(endpoint);
+ },
+};
diff --git a/app/assets/javascripts/contributors/stores/actions.js b/app/assets/javascripts/contributors/stores/actions.js
new file mode 100644
index 0000000000..4138ff24f1
--- /dev/null
+++ b/app/assets/javascripts/contributors/stores/actions.js
@@ -0,0 +1,20 @@
+import flash from '~/flash';
+import { __ } from '~/locale';
+import service from '../services/contributors_service';
+import * as types from './mutation_types';
+
+export const fetchChartData = ({ commit }, endpoint) => {
+ commit(types.SET_LOADING_STATE, true);
+
+ return service
+ .fetchChartData(endpoint)
+ .then(res => res.data)
+ .then(data => {
+ commit(types.SET_CHART_DATA, data);
+ commit(types.SET_LOADING_STATE, false);
+ })
+ .catch(() => flash(__('An error occurred while loading chart data')));
+};
+
+// prevent babel-plugin-rewire from generating an invalid default during karma tests
+export default () => {};
diff --git a/app/assets/javascripts/contributors/stores/getters.js b/app/assets/javascripts/contributors/stores/getters.js
new file mode 100644
index 0000000000..9e02e3ed9e
--- /dev/null
+++ b/app/assets/javascripts/contributors/stores/getters.js
@@ -0,0 +1,33 @@
+export const showChart = state => Boolean(!state.loading && state.chartData);
+
+export const parsedData = state => {
+ const byAuthor = {};
+ const total = {};
+
+ state.chartData.forEach(({ date, author_name, author_email }) => {
+ total[date] = total[date] ? total[date] + 1 : 1;
+
+ const authorData = byAuthor[author_name];
+
+ if (!authorData) {
+ byAuthor[author_name] = {
+ email: author_email.toLowerCase(),
+ commits: 1,
+ dates: {
+ [date]: 1,
+ },
+ };
+ } else {
+ authorData.commits += 1;
+ authorData.dates[date] = authorData.dates[date] ? authorData.dates[date] + 1 : 1;
+ }
+ });
+
+ return {
+ total,
+ byAuthor,
+ };
+};
+
+// prevent babel-plugin-rewire from generating an invalid default during karma tests
+export default () => {};
diff --git a/app/assets/javascripts/contributors/stores/index.js b/app/assets/javascripts/contributors/stores/index.js
new file mode 100644
index 0000000000..bc739851aa
--- /dev/null
+++ b/app/assets/javascripts/contributors/stores/index.js
@@ -0,0 +1,18 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import state from './state';
+import mutations from './mutations';
+import * as getters from './getters';
+import * as actions from './actions';
+
+Vue.use(Vuex);
+
+export const createStore = () =>
+ new Vuex.Store({
+ actions,
+ mutations,
+ getters,
+ state: state(),
+ });
+
+export default createStore();
diff --git a/app/assets/javascripts/contributors/stores/mutation_types.js b/app/assets/javascripts/contributors/stores/mutation_types.js
new file mode 100644
index 0000000000..62e0a51d5f
--- /dev/null
+++ b/app/assets/javascripts/contributors/stores/mutation_types.js
@@ -0,0 +1,3 @@
+export const SET_CHART_DATA = 'SET_CHART_DATA';
+export const SET_LOADING_STATE = 'SET_LOADING_STATE';
+export const SET_ACTIVE_BRANCH = 'SET_ACTIVE_BRANCH';
diff --git a/app/assets/javascripts/contributors/stores/mutations.js b/app/assets/javascripts/contributors/stores/mutations.js
new file mode 100644
index 0000000000..f1f460d072
--- /dev/null
+++ b/app/assets/javascripts/contributors/stores/mutations.js
@@ -0,0 +1,17 @@
+import * as types from './mutation_types';
+
+export default {
+ [types.SET_LOADING_STATE](state, value) {
+ state.loading = value;
+ },
+ [types.SET_CHART_DATA](state, chartData) {
+ Object.assign(state, {
+ chartData,
+ });
+ },
+ [types.SET_ACTIVE_BRANCH](state, branch) {
+ Object.assign(state, {
+ branch,
+ });
+ },
+};
diff --git a/app/assets/javascripts/contributors/stores/state.js b/app/assets/javascripts/contributors/stores/state.js
new file mode 100644
index 0000000000..1dc1a3c7b7
--- /dev/null
+++ b/app/assets/javascripts/contributors/stores/state.js
@@ -0,0 +1,5 @@
+export default () => ({
+ loading: false,
+ chartData: null,
+ branch: 'master',
+});
diff --git a/app/assets/javascripts/contributors/utils.js b/app/assets/javascripts/contributors/utils.js
new file mode 100644
index 0000000000..7d8932ce49
--- /dev/null
+++ b/app/assets/javascripts/contributors/utils.js
@@ -0,0 +1,30 @@
+import { getMonthNames } from '~/lib/utils/datetime_utility';
+
+/**
+ * Converts provided string to date and returns formatted value as a year for date in January and month name for the rest
+ * @param {String}
+ * @returns {String} - formatted value
+ *
+ * xAxisLabelFormatter('01-12-2019') will return '2019'
+ * xAxisLabelFormatter('02-12-2019') will return 'Feb'
+ * xAxisLabelFormatter('07-12-2019') will return 'Jul'
+ */
+export const xAxisLabelFormatter = val => {
+ const date = new Date(val);
+ const month = date.getUTCMonth();
+ const year = date.getUTCFullYear();
+ return month === 0 ? `${year}` : getMonthNames(true)[month];
+};
+
+/**
+ * Formats provided date to YYYY-MM-DD format
+ * @param {Date}
+ * @returns {String} - formatted value
+ */
+export const dateFormatter = date => {
+ const year = date.getUTCFullYear();
+ const month = date.getUTCMonth();
+ const day = date.getUTCDate();
+
+ return `${year}-${`0${month + 1}`.slice(-2)}-${`0${day}`.slice(-2)}`;
+};
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/components/cluster_form_dropdown.vue b/app/assets/javascripts/create_cluster/eks_cluster/components/cluster_form_dropdown.vue
index 3c6da43c4c..e6893c14cd 100644
--- a/app/assets/javascripts/create_cluster/eks_cluster/components/cluster_form_dropdown.vue
+++ b/app/assets/javascripts/create_cluster/eks_cluster/components/cluster_form_dropdown.vue
@@ -2,14 +2,19 @@
import DropdownSearchInput from '~/vue_shared/components/dropdown/dropdown_search_input.vue';
import DropdownHiddenInput from '~/vue_shared/components/dropdown/dropdown_hidden_input.vue';
import DropdownButton from '~/vue_shared/components/dropdown/dropdown_button.vue';
+import { GlIcon } from '@gitlab/ui';
-const findItem = (items, valueProp, value) => items.find(item => item[valueProp] === value);
+const toArray = value => [].concat(value);
+const itemsProp = (items, prop) => items.map(item => item[prop]);
+const defaultSearchFn = (searchQuery, labelProp) => item =>
+ item[labelProp].toLowerCase().indexOf(searchQuery) > -1;
export default {
components: {
DropdownButton,
DropdownSearchInput,
DropdownHiddenInput,
+ GlIcon,
},
props: {
fieldName: {
@@ -28,7 +33,7 @@ export default {
default: '',
},
value: {
- type: [Object, String],
+ type: [Object, Array, String],
required: false,
default: () => null,
},
@@ -72,6 +77,11 @@ export default {
required: false,
default: false,
},
+ multiple: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
errorMessage: {
type: String,
required: false,
@@ -90,12 +100,11 @@ export default {
searchFn: {
type: Function,
required: false,
- default: searchQuery => item => item.name.toLowerCase().indexOf(searchQuery) > -1,
+ default: defaultSearchFn,
},
},
data() {
return {
- selectedItem: findItem(this.items, this.value),
searchQuery: '',
};
},
@@ -109,36 +118,52 @@ export default {
return this.disabledText;
}
- if (!this.selectedItem) {
+ if (!this.selectedItems.length) {
return this.placeholder;
}
- return this.selectedItemLabel;
+ return this.selectedItemsLabels;
},
results() {
- if (!this.items) {
- return [];
- }
+ return this.getItemsOrEmptyList().filter(this.searchFn(this.searchQuery, this.labelProperty));
+ },
+ selectedItems() {
+ const valueProp = this.valueProperty;
+ const valueList = toArray(this.value);
+ const items = this.getItemsOrEmptyList();
- return this.items.filter(this.searchFn(this.searchQuery));
+ return items.filter(item => valueList.some(value => item[valueProp] === value));
},
- selectedItemLabel() {
- return this.selectedItem && this.selectedItem[this.labelProperty];
+ selectedItemsLabels() {
+ return itemsProp(this.selectedItems, this.labelProperty).join(', ');
},
- selectedItemValue() {
- return (this.selectedItem && this.selectedItem[this.valueProperty]) || '';
- },
- },
- watch: {
- value(value) {
- this.selectedItem = findItem(this.items, this.valueProperty, value);
+ selectedItemsValues() {
+ return itemsProp(this.selectedItems, this.valueProperty).join(', ');
},
},
methods: {
- select(item) {
- this.selectedItem = item;
+ getItemsOrEmptyList() {
+ return this.items || [];
+ },
+ selectSingle(item) {
this.$emit('input', item[this.valueProperty]);
},
+ selectMultiple(item) {
+ const value = toArray(this.value);
+ const itemValue = item[this.valueProperty];
+ const itemValueIndex = value.indexOf(itemValue);
+
+ if (itemValueIndex > -1) {
+ value.splice(itemValueIndex, 1);
+ } else {
+ value.push(itemValue);
+ }
+
+ this.$emit('input', value);
+ },
+ isSelected(item) {
+ return this.selectedItems.includes(item);
+ },
},
};
@@ -146,7 +171,7 @@ export default {
-
+
-
+
-
-
- {{ item.name }}
-
+
+
+ {{ item.name }}
+
+
+ {{ item.name }}
@@ -182,8 +220,7 @@ export default {
'text-muted': !hasErrors,
},
]"
+ >{{ errorMessage }}
- {{ errorMessage }}
-
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/components/create_eks_cluster.vue b/app/assets/javascripts/create_cluster/eks_cluster/components/create_eks_cluster.vue
index 22ee368b8e..3f7c2204b9 100644
--- a/app/assets/javascripts/create_cluster/eks_cluster/components/create_eks_cluster.vue
+++ b/app/assets/javascripts/create_cluster/eks_cluster/components/create_eks_cluster.vue
@@ -1,4 +1,5 @@
+
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue b/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue
index 1188cf0885..57d5f4f541 100644
--- a/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue
+++ b/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue
@@ -4,8 +4,8 @@ import { sprintf, s__ } from '~/locale';
import _ from 'underscore';
import { GlFormInput, GlFormCheckbox } from '@gitlab/ui';
import ClusterFormDropdown from './cluster_form_dropdown.vue';
-import RegionDropdown from './region_dropdown.vue';
import { KUBERNETES_VERSIONS } from '../constants';
+import LoadingButton from '~/vue_shared/components/loading_button.vue';
const { mapState: mapRolesState, mapActions: mapRolesActions } = createNamespacedHelpers('roles');
const { mapState: mapRegionsState, mapActions: mapRegionsActions } = createNamespacedHelpers(
@@ -22,13 +22,17 @@ const {
mapState: mapSecurityGroupsState,
mapActions: mapSecurityGroupsActions,
} = createNamespacedHelpers('securityGroups');
+const {
+ mapState: mapInstanceTypesState,
+ mapActions: mapInstanceTypesActions,
+} = createNamespacedHelpers('instanceTypes');
export default {
components: {
ClusterFormDropdown,
- RegionDropdown,
GlFormInput,
GlFormCheckbox,
+ LoadingButton,
},
props: {
gitlabManagedClusterHelpPath: {
@@ -39,6 +43,10 @@ export default {
type: String,
required: true,
},
+ externalLinkIcon: {
+ type: String,
+ required: true,
+ },
},
computed: {
...mapState([
@@ -51,7 +59,10 @@ export default {
'selectedSubnet',
'selectedRole',
'selectedSecurityGroup',
+ 'selectedInstanceType',
+ 'nodeCount',
'gitlabManagedCluster',
+ 'isCreatingCluster',
]),
...mapRolesState({
roles: 'items',
@@ -83,6 +94,11 @@ export default {
isLoadingSecurityGroups: 'isLoadingItems',
loadingSecurityGroupsError: 'loadingItemsError',
}),
+ ...mapInstanceTypesState({
+ instanceTypes: 'items',
+ isLoadingInstanceTypes: 'isLoadingItems',
+ loadingInstanceTypesError: 'loadingItemsError',
+ }),
kubernetesVersions() {
return KUBERNETES_VERSIONS;
},
@@ -98,6 +114,27 @@ export default {
securityGroupDropdownDisabled() {
return !this.selectedVpc;
},
+ createClusterButtonDisabled() {
+ return (
+ !this.clusterName ||
+ !this.environmentScope ||
+ !this.kubernetesVersion ||
+ !this.selectedRegion ||
+ !this.selectedKeyPair ||
+ !this.selectedVpc ||
+ !this.selectedSubnet ||
+ !this.selectedRole ||
+ !this.selectedSecurityGroup ||
+ !this.selectedInstanceType ||
+ !this.nodeCount ||
+ this.isCreatingCluster
+ );
+ },
+ createClusterButtonLabel() {
+ return this.isCreatingCluster
+ ? s__('ClusterIntegration|Creating Kubernetes cluster')
+ : s__('ClusterIntegration|Create Kubernetes cluster');
+ },
kubernetesIntegrationHelpText() {
const escapedUrl = _.escape(this.kubernetesIntegrationHelpPath);
@@ -115,11 +152,26 @@ export default {
roleDropdownHelpText() {
return sprintf(
s__(
- 'ClusterIntegration|Select the IAM Role to allow Amazon EKS and the Kubernetes control plane to manage AWS resources on your behalf. To use a new role name, first create one on %{startLink}Amazon Web Services%{endLink}.',
+ 'ClusterIntegration|Select the IAM Role to allow Amazon EKS and the Kubernetes control plane to manage AWS resources on your behalf. To use a new role name, first create one on %{startLink}Amazon Web Services %{externalLinkIcon} %{endLink}.',
),
{
startLink:
- '
',
+ ' ',
+ externalLinkIcon: this.externalLinkIcon,
+ endLink: ' ',
+ },
+ false,
+ );
+ },
+ regionsDropdownHelpText() {
+ return sprintf(
+ s__(
+ 'ClusterIntegration|Learn more about %{startLink}Regions %{externalLinkIcon}%{endLink}.',
+ ),
+ {
+ startLink:
+ '
',
+ externalLinkIcon: this.externalLinkIcon,
endLink: ' ',
},
false,
@@ -128,11 +180,12 @@ export default {
keyPairDropdownHelpText() {
return sprintf(
s__(
- 'ClusterIntegration|Select the key pair name that will be used to create EC2 nodes. To use a new key pair name, first create one on %{startLink}Amazon Web Services%{endLink}.',
+ 'ClusterIntegration|Select the key pair name that will be used to create EC2 nodes. To use a new key pair name, first create one on %{startLink}Amazon Web Services %{externalLinkIcon} %{endLink}.',
),
{
startLink:
'
',
+ externalLinkIcon: this.externalLinkIcon,
endLink: ' ',
},
false,
@@ -141,11 +194,12 @@ export default {
vpcDropdownHelpText() {
return sprintf(
s__(
- 'ClusterIntegration|Select a VPC to use for your EKS Cluster resources. To use a new VPC, first create one on %{startLink}Amazon Web Services%{endLink}.',
+ 'ClusterIntegration|Select a VPC to use for your EKS Cluster resources. To use a new VPC, first create one on %{startLink}Amazon Web Services %{externalLinkIcon} %{endLink}.',
),
{
startLink:
- '
',
+ ' ',
+ externalLinkIcon: this.externalLinkIcon,
endLink: ' ',
},
false,
@@ -154,11 +208,12 @@ export default {
subnetDropdownHelpText() {
return sprintf(
s__(
- 'ClusterIntegration|Choose the %{startLink}subnets%{endLink} in your VPC where your worker nodes will run.',
+ 'ClusterIntegration|Choose the %{startLink}subnets %{externalLinkIcon} %{endLink} in your VPC where your worker nodes will run.',
),
{
startLink:
'
',
+ externalLinkIcon: this.externalLinkIcon,
endLink: ' ',
},
false,
@@ -167,11 +222,26 @@ export default {
securityGroupDropdownHelpText() {
return sprintf(
s__(
- 'ClusterIntegration|Choose the %{startLink}security groups%{endLink} to apply to the EKS-managed Elastic Network Interfaces that are created in your worker node subnets.',
+ 'ClusterIntegration|Choose the %{startLink}security group %{externalLinkIcon} %{endLink} to apply to the EKS-managed Elastic Network Interfaces that are created in your worker node subnets.',
),
{
startLink:
'
',
+ externalLinkIcon: this.externalLinkIcon,
+ endLink: ' ',
+ },
+ false,
+ );
+ },
+ instanceTypesDropdownHelpText() {
+ return sprintf(
+ s__(
+ 'ClusterIntegration|Choose the worker node %{startLink}instance type %{externalLinkIcon} %{endLink}.',
+ ),
+ {
+ startLink:
+ '
',
+ externalLinkIcon: this.externalLinkIcon,
endLink: ' ',
},
false,
@@ -195,9 +265,12 @@ export default {
mounted() {
this.fetchRegions();
this.fetchRoles();
+ this.fetchInstanceTypes();
},
methods: {
...mapActions([
+ 'createCluster',
+ 'signOut',
'setClusterName',
'setEnvironmentScope',
'setKubernetesVersion',
@@ -207,6 +280,8 @@ export default {
'setRole',
'setKeyPair',
'setSecurityGroup',
+ 'setInstanceType',
+ 'setNodeCount',
'setGitlabManagedCluster',
]),
...mapRegionsActions({ fetchRegions: 'fetchItems' }),
@@ -215,15 +290,22 @@ export default {
...mapRolesActions({ fetchRoles: 'fetchItems' }),
...mapKeyPairsActions({ fetchKeyPairs: 'fetchItems' }),
...mapSecurityGroupsActions({ fetchSecurityGroups: 'fetchItems' }),
+ ...mapInstanceTypesActions({ fetchInstanceTypes: 'fetchItems' }),
setRegionAndFetchVpcsAndKeyPairs(region) {
this.setRegion({ region });
+ this.setVpc({ vpc: null });
+ this.setKeyPair({ keyPair: null });
+ this.setSubnet({ subnet: null });
+ this.setSecurityGroup({ securityGroup: null });
this.fetchVpcs({ region });
this.fetchKeyPairs({ region });
},
setVpcAndFetchSubnets(vpc) {
this.setVpc({ vpc });
- this.fetchSubnets({ vpc });
- this.fetchSecurityGroups({ vpc });
+ this.setSubnet({ subnet: null });
+ this.setSecurityGroup({ securityGroup: null });
+ this.fetchSubnets({ vpc, region: this.selectedRegion });
+ this.fetchSecurityGroups({ vpc, region: this.selectedRegion });
},
},
};
@@ -233,7 +315,12 @@ export default {
{{ s__('ClusterIntegration|Enter the details for your Amazon EKS Kubernetes cluster') }}
-
+
+
+
+ {{ s__('ClusterIntegration|Select a different AWS role') }}
+
+
-
+
+
-
+
{{ data.label }}
@@ -94,13 +128,11 @@ export default {
{{ errors.item.title.trim() }}
-
{{ errors.item.culprit }}
diff --git a/app/assets/javascripts/error_tracking/components/stacktrace.vue b/app/assets/javascripts/error_tracking/components/stacktrace.vue
new file mode 100644
index 0000000000..6b71967624
--- /dev/null
+++ b/app/assets/javascripts/error_tracking/components/stacktrace.vue
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
diff --git a/app/assets/javascripts/error_tracking/components/stacktrace_entry.vue b/app/assets/javascripts/error_tracking/components/stacktrace_entry.vue
new file mode 100644
index 0000000000..ad542c579a
--- /dev/null
+++ b/app/assets/javascripts/error_tracking/components/stacktrace_entry.vue
@@ -0,0 +1,110 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ lineNum(line) }}
+
+
+
+
+
+
+
+
diff --git a/app/assets/javascripts/error_tracking/details.js b/app/assets/javascripts/error_tracking/details.js
new file mode 100644
index 0000000000..b9b51a6539
--- /dev/null
+++ b/app/assets/javascripts/error_tracking/details.js
@@ -0,0 +1,25 @@
+import Vue from 'vue';
+import store from './store';
+import ErrorDetails from './components/error_details.vue';
+
+export default () => {
+ // eslint-disable-next-line no-new
+ new Vue({
+ el: '#js-error_details',
+ components: {
+ ErrorDetails,
+ },
+ store,
+ render(createElement) {
+ const domEl = document.querySelector(this.$options.el);
+ const { issueDetailsPath, issueStackTracePath } = domEl.dataset;
+
+ return createElement('error-details', {
+ props: {
+ issueDetailsPath,
+ issueStackTracePath,
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/error_tracking/index.js b/app/assets/javascripts/error_tracking/list.js
similarity index 100%
rename from app/assets/javascripts/error_tracking/index.js
rename to app/assets/javascripts/error_tracking/list.js
diff --git a/app/assets/javascripts/error_tracking/services/index.js b/app/assets/javascripts/error_tracking/services/index.js
index ab89521dc4..68988296cc 100644
--- a/app/assets/javascripts/error_tracking/services/index.js
+++ b/app/assets/javascripts/error_tracking/services/index.js
@@ -1,7 +1,7 @@
import axios from '~/lib/utils/axios_utils';
export default {
- getErrorList({ endpoint }) {
+ getSentryData({ endpoint }) {
return axios.get(endpoint);
},
};
diff --git a/app/assets/javascripts/error_tracking/store/details/actions.js b/app/assets/javascripts/error_tracking/store/details/actions.js
new file mode 100644
index 0000000000..0390bca717
--- /dev/null
+++ b/app/assets/javascripts/error_tracking/store/details/actions.js
@@ -0,0 +1,63 @@
+import service from '../../services';
+import * as types from './mutation_types';
+import createFlash from '~/flash';
+import Poll from '~/lib/utils/poll';
+import { __ } from '~/locale';
+
+let stackTracePoll;
+let detailPoll;
+
+const stopPolling = poll => {
+ if (poll) poll.stop();
+};
+
+export function startPollingDetails({ commit }, endpoint) {
+ detailPoll = new Poll({
+ resource: service,
+ method: 'getSentryData',
+ data: { endpoint },
+ successCallback: ({ data }) => {
+ if (!data) {
+ detailPoll.restart();
+ return;
+ }
+
+ commit(types.SET_ERROR, data.error);
+ commit(types.SET_LOADING, false);
+
+ stopPolling(detailPoll);
+ },
+ errorCallback: () => {
+ commit(types.SET_LOADING, false);
+ createFlash(__('Failed to load error details from Sentry.'));
+ },
+ });
+
+ detailPoll.makeRequest();
+}
+
+export function startPollingStacktrace({ commit }, endpoint) {
+ stackTracePoll = new Poll({
+ resource: service,
+ method: 'getSentryData',
+ data: { endpoint },
+ successCallback: ({ data }) => {
+ if (!data) {
+ stackTracePoll.restart();
+ return;
+ }
+ commit(types.SET_STACKTRACE_DATA, data.error);
+ commit(types.SET_LOADING_STACKTRACE, false);
+
+ stopPolling(stackTracePoll);
+ },
+ errorCallback: () => {
+ commit(types.SET_LOADING_STACKTRACE, false);
+ createFlash(__('Failed to load stacktrace.'));
+ },
+ });
+
+ stackTracePoll.makeRequest();
+}
+
+export default () => {};
diff --git a/app/assets/javascripts/error_tracking/store/details/getters.js b/app/assets/javascripts/error_tracking/store/details/getters.js
new file mode 100644
index 0000000000..7d13439d72
--- /dev/null
+++ b/app/assets/javascripts/error_tracking/store/details/getters.js
@@ -0,0 +1,3 @@
+export const stacktrace = state => state.stacktraceData.stack_trace_entries.reverse();
+
+export default () => {};
diff --git a/app/assets/javascripts/error_tracking/store/details/mutation_types.js b/app/assets/javascripts/error_tracking/store/details/mutation_types.js
new file mode 100644
index 0000000000..a2592253a2
--- /dev/null
+++ b/app/assets/javascripts/error_tracking/store/details/mutation_types.js
@@ -0,0 +1,4 @@
+export const SET_ERROR = 'SET_ERRORS';
+export const SET_LOADING = 'SET_LOADING';
+export const SET_LOADING_STACKTRACE = 'SET_LOADING_STACKTRACE';
+export const SET_STACKTRACE_DATA = 'SET_STACKTRACE_DATA';
diff --git a/app/assets/javascripts/error_tracking/store/details/mutations.js b/app/assets/javascripts/error_tracking/store/details/mutations.js
new file mode 100644
index 0000000000..6f4720444e
--- /dev/null
+++ b/app/assets/javascripts/error_tracking/store/details/mutations.js
@@ -0,0 +1,16 @@
+import * as types from './mutation_types';
+
+export default {
+ [types.SET_ERROR](state, data) {
+ state.error = data;
+ },
+ [types.SET_LOADING](state, loading) {
+ state.loading = loading;
+ },
+ [types.SET_LOADING_STACKTRACE](state, data) {
+ state.loadingStacktrace = data;
+ },
+ [types.SET_STACKTRACE_DATA](state, data) {
+ state.stacktraceData = data;
+ },
+};
diff --git a/app/assets/javascripts/error_tracking/store/details/state.js b/app/assets/javascripts/error_tracking/store/details/state.js
new file mode 100644
index 0000000000..95fb0ba055
--- /dev/null
+++ b/app/assets/javascripts/error_tracking/store/details/state.js
@@ -0,0 +1,6 @@
+export default () => ({
+ error: {},
+ stacktraceData: {},
+ loading: true,
+ loadingStacktrace: true,
+});
diff --git a/app/assets/javascripts/error_tracking/store/index.js b/app/assets/javascripts/error_tracking/store/index.js
index 3136682fb6..941c752e96 100644
--- a/app/assets/javascripts/error_tracking/store/index.js
+++ b/app/assets/javascripts/error_tracking/store/index.js
@@ -1,19 +1,36 @@
import Vue from 'vue';
import Vuex from 'vuex';
-import * as actions from './actions';
-import mutations from './mutations';
+
+import * as listActions from './list/actions';
+import listMutations from './list/mutations';
+import listState from './list/state';
+import * as listGetters from './list/getters';
+
+import * as detailsActions from './details/actions';
+import detailsMutations from './details/mutations';
+import detailsState from './details/state';
+import * as detailsGetters from './details/getters';
Vue.use(Vuex);
export const createStore = () =>
new Vuex.Store({
- state: {
- errors: [],
- externalUrl: '',
- loading: true,
+ modules: {
+ list: {
+ namespaced: true,
+ state: listState(),
+ actions: listActions,
+ mutations: listMutations,
+ getters: listGetters,
+ },
+ details: {
+ namespaced: true,
+ state: detailsState(),
+ actions: detailsActions,
+ mutations: detailsMutations,
+ getters: detailsGetters,
+ },
},
- actions,
- mutations,
});
export default createStore();
diff --git a/app/assets/javascripts/error_tracking/store/actions.js b/app/assets/javascripts/error_tracking/store/list/actions.js
similarity index 95%
rename from app/assets/javascripts/error_tracking/store/actions.js
rename to app/assets/javascripts/error_tracking/store/list/actions.js
index 1e754a4f54..18c6e5e969 100644
--- a/app/assets/javascripts/error_tracking/store/actions.js
+++ b/app/assets/javascripts/error_tracking/store/list/actions.js
@@ -1,4 +1,4 @@
-import Service from '../services';
+import Service from '../../services';
import * as types from './mutation_types';
import createFlash from '~/flash';
import Poll from '~/lib/utils/poll';
@@ -9,7 +9,7 @@ let eTagPoll;
export function startPolling({ commit, dispatch }, endpoint) {
eTagPoll = new Poll({
resource: Service,
- method: 'getErrorList',
+ method: 'getSentryData',
data: { endpoint },
successCallback: ({ data }) => {
if (!data) {
diff --git a/app/assets/javascripts/error_tracking/store/list/getters.js b/app/assets/javascripts/error_tracking/store/list/getters.js
new file mode 100644
index 0000000000..1a2ec62f79
--- /dev/null
+++ b/app/assets/javascripts/error_tracking/store/list/getters.js
@@ -0,0 +1,4 @@
+export const filterErrorsByTitle = state => errorQuery =>
+ state.errors.filter(error => error.title.match(new RegExp(`${errorQuery}`, 'i')));
+
+export default () => {};
diff --git a/app/assets/javascripts/error_tracking/store/mutation_types.js b/app/assets/javascripts/error_tracking/store/list/mutation_types.js
similarity index 100%
rename from app/assets/javascripts/error_tracking/store/mutation_types.js
rename to app/assets/javascripts/error_tracking/store/list/mutation_types.js
diff --git a/app/assets/javascripts/error_tracking/store/mutations.js b/app/assets/javascripts/error_tracking/store/list/mutations.js
similarity index 100%
rename from app/assets/javascripts/error_tracking/store/mutations.js
rename to app/assets/javascripts/error_tracking/store/list/mutations.js
diff --git a/app/assets/javascripts/error_tracking/store/list/state.js b/app/assets/javascripts/error_tracking/store/list/state.js
new file mode 100644
index 0000000000..d371350ef0
--- /dev/null
+++ b/app/assets/javascripts/error_tracking/store/list/state.js
@@ -0,0 +1,5 @@
+export default () => ({
+ errors: [],
+ externalUrl: '',
+ loading: true,
+});
diff --git a/app/assets/javascripts/error_tracking_settings/components/app.vue b/app/assets/javascripts/error_tracking_settings/components/app.vue
index 50eb3e63b7..786abc8ce4 100644
--- a/app/assets/javascripts/error_tracking_settings/components/app.vue
+++ b/app/assets/javascripts/error_tracking_settings/components/app.vue
@@ -43,16 +43,7 @@ export default {
'isProjectInvalid',
'projectSelectionLabel',
]),
- ...mapState([
- 'apiHost',
- 'connectError',
- 'connectSuccessful',
- 'enabled',
- 'projects',
- 'selectedProject',
- 'settingsLoading',
- 'token',
- ]),
+ ...mapState(['enabled', 'projects', 'selectedProject', 'settingsLoading', 'token']),
},
created() {
this.setInitialState({
@@ -65,15 +56,7 @@ export default {
});
},
methods: {
- ...mapActions([
- 'fetchProjects',
- 'setInitialState',
- 'updateApiHost',
- 'updateEnabled',
- 'updateSelectedProject',
- 'updateSettings',
- 'updateToken',
- ]),
+ ...mapActions(['setInitialState', 'updateEnabled', 'updateSelectedProject', 'updateSettings']),
handleSubmit() {
this.updateSettings();
},
@@ -95,15 +78,7 @@ export default {
s__('ErrorTracking|Active')
}}
-
+
@@ -60,15 +49,17 @@ export default {
id="error-tracking-token"
:value="token"
:state="tokenInputState"
- @input="$emit('update-token', $event)"
+ :disabled="isLoadingProjects"
+ @input="updateToken"
/>
-
{{ __('Connect') }}
+
{
+ commit(types.SET_PROJECTS_LOADING, true);
commit(types.RESET_CONNECT);
};
export const receiveProjectsSuccess = ({ commit }, projects) => {
commit(types.UPDATE_CONNECT_SUCCESS);
commit(types.RECEIVE_PROJECTS, projects);
+ commit(types.SET_PROJECTS_LOADING, false);
};
export const receiveProjectsError = ({ commit }) => {
commit(types.UPDATE_CONNECT_ERROR);
commit(types.CLEAR_PROJECTS);
+ commit(types.SET_PROJECTS_LOADING, false);
};
export const fetchProjects = ({ dispatch, state }) => {
diff --git a/app/assets/javascripts/error_tracking_settings/store/mutation_types.js b/app/assets/javascripts/error_tracking_settings/store/mutation_types.js
index b4f8a23794..bf3df383dd 100644
--- a/app/assets/javascripts/error_tracking_settings/store/mutation_types.js
+++ b/app/assets/javascripts/error_tracking_settings/store/mutation_types.js
@@ -9,3 +9,4 @@ export const UPDATE_ENABLED = 'UPDATE_ENABLED';
export const UPDATE_SELECTED_PROJECT = 'UPDATE_SELECTED_PROJECT';
export const UPDATE_SETTINGS_LOADING = 'UPDATE_SETTINGS_LOADING';
export const UPDATE_TOKEN = 'UPDATE_TOKEN';
+export const SET_PROJECTS_LOADING = 'SET_PROJECTS_LOADING';
diff --git a/app/assets/javascripts/error_tracking_settings/store/mutations.js b/app/assets/javascripts/error_tracking_settings/store/mutations.js
index 4089d1ee94..133f25264b 100644
--- a/app/assets/javascripts/error_tracking_settings/store/mutations.js
+++ b/app/assets/javascripts/error_tracking_settings/store/mutations.js
@@ -58,4 +58,7 @@ export default {
state.connectSuccessful = false;
state.connectError = true;
},
+ [types.SET_PROJECTS_LOADING](state, loading) {
+ state.isLoadingProjects = loading;
+ },
};
diff --git a/app/assets/javascripts/error_tracking_settings/store/state.js b/app/assets/javascripts/error_tracking_settings/store/state.js
index 98219d33f4..ab616f11e8 100644
--- a/app/assets/javascripts/error_tracking_settings/store/state.js
+++ b/app/assets/javascripts/error_tracking_settings/store/state.js
@@ -3,6 +3,7 @@ export default () => ({
enabled: false,
token: '',
projects: [],
+ isLoadingProjects: false,
selectedProject: null,
settingsLoading: false,
connectSuccessful: false,
diff --git a/app/assets/javascripts/filtered_search/available_dropdown_mappings.js b/app/assets/javascripts/filtered_search/available_dropdown_mappings.js
index f280f3cd26..5fa07045d5 100644
--- a/app/assets/javascripts/filtered_search/available_dropdown_mappings.js
+++ b/app/assets/javascripts/filtered_search/available_dropdown_mappings.js
@@ -13,6 +13,7 @@ export default class AvailableDropdownMappings {
runnerTagsEndpoint,
labelsEndpoint,
milestonesEndpoint,
+ releasesEndpoint,
groupsOnly,
includeAncestorGroups,
includeDescendantGroups,
@@ -21,6 +22,7 @@ export default class AvailableDropdownMappings {
this.runnerTagsEndpoint = runnerTagsEndpoint;
this.labelsEndpoint = labelsEndpoint;
this.milestonesEndpoint = milestonesEndpoint;
+ this.releasesEndpoint = releasesEndpoint;
this.groupsOnly = groupsOnly;
this.includeAncestorGroups = includeAncestorGroups;
this.includeDescendantGroups = includeDescendantGroups;
@@ -70,6 +72,19 @@ export default class AvailableDropdownMappings {
},
element: this.container.querySelector('#js-dropdown-milestone'),
},
+ release: {
+ reference: null,
+ gl: DropdownNonUser,
+ extraArguments: {
+ endpoint: this.getReleasesEndpoint(),
+ symbol: '',
+
+ // The DropdownNonUser class is hardcoded to look for and display a
+ // "title" property, so we need to add this property to each release object
+ preprocessing: releases => releases.map(r => ({ ...r, title: r.tag })),
+ },
+ element: this.container.querySelector('#js-dropdown-release'),
+ },
label: {
reference: null,
gl: DropdownNonUser,
@@ -130,6 +145,10 @@ export default class AvailableDropdownMappings {
return `${this.milestonesEndpoint}.json`;
}
+ getReleasesEndpoint() {
+ return `${this.releasesEndpoint}.json`;
+ }
+
getLabelsEndpoint() {
let endpoint = `${this.labelsEndpoint}.json?`;
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
index 835d3bf8a5..5ff95f45be 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
@@ -11,6 +11,7 @@ export default class FilteredSearchDropdownManager {
runnerTagsEndpoint = '',
labelsEndpoint = '',
milestonesEndpoint = '',
+ releasesEndpoint = '',
tokenizer,
page,
isGroup,
@@ -18,10 +19,13 @@ export default class FilteredSearchDropdownManager {
isGroupDecendent,
filteredSearchTokenKeys,
}) {
+ const removeTrailingSlash = url => url.replace(/\/$/, '');
+
this.container = FilteredSearchContainer.container;
- this.runnerTagsEndpoint = runnerTagsEndpoint.replace(/\/$/, '');
- this.labelsEndpoint = labelsEndpoint.replace(/\/$/, '');
- this.milestonesEndpoint = milestonesEndpoint.replace(/\/$/, '');
+ this.runnerTagsEndpoint = removeTrailingSlash(runnerTagsEndpoint);
+ this.labelsEndpoint = removeTrailingSlash(labelsEndpoint);
+ this.milestonesEndpoint = removeTrailingSlash(milestonesEndpoint);
+ this.releasesEndpoint = removeTrailingSlash(releasesEndpoint);
this.tokenizer = tokenizer;
this.filteredSearchTokenKeys = filteredSearchTokenKeys || FilteredSearchTokenKeys;
this.filteredSearchInput = this.container.querySelector('.filtered-search');
@@ -54,6 +58,7 @@ export default class FilteredSearchDropdownManager {
this.runnerTagsEndpoint,
this.labelsEndpoint,
this.milestonesEndpoint,
+ this.releasesEndpoint,
this.groupsOnly,
this.includeAncestorGroups,
this.includeDescendantGroups,
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index fd335362e5..5c2d32f4e8 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -89,6 +89,7 @@ export default class FilteredSearchManager {
this.filteredSearchInput.getAttribute('data-runner-tags-endpoint') || '',
labelsEndpoint: this.filteredSearchInput.getAttribute('data-labels-endpoint') || '',
milestonesEndpoint: this.filteredSearchInput.getAttribute('data-milestones-endpoint') || '',
+ releasesEndpoint: this.filteredSearchInput.getAttribute('data-releases-endpoint') || '',
tokenizer: this.tokenizer,
page: this.page,
isGroup: this.isGroup,
diff --git a/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js b/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js
index 6c3d9e3342..414bcf186a 100644
--- a/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js
+++ b/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js
@@ -1,7 +1,9 @@
import FilteredSearchTokenKeys from './filtered_search_token_keys';
import { __ } from '~/locale';
-export const tokenKeys = [
+export const tokenKeys = [];
+
+tokenKeys.push(
{
key: 'author',
type: 'string',
@@ -26,15 +28,27 @@ export const tokenKeys = [
icon: 'clock',
tag: '%milestone',
},
- {
- key: 'label',
- type: 'array',
- param: 'name[]',
- symbol: '~',
- icon: 'labels',
- tag: '~label',
- },
-];
+);
+
+if (gon && gon.features && gon.features.releaseSearchFilter) {
+ tokenKeys.push({
+ key: 'release',
+ type: 'string',
+ param: 'tag',
+ symbol: '',
+ icon: 'rocket',
+ tag: __('tag name'),
+ });
+}
+
+tokenKeys.push({
+ key: 'label',
+ type: 'array',
+ param: 'name[]',
+ symbol: '~',
+ icon: 'labels',
+ tag: '~label',
+});
if (gon.current_user_id) {
// Appending tokenkeys only logged-in
@@ -88,6 +102,16 @@ export const conditions = [
tokenKey: 'milestone',
value: __('Started'),
},
+ {
+ url: 'release_tag=None',
+ tokenKey: 'release',
+ value: __('None'),
+ },
+ {
+ url: 'release_tag=Any',
+ tokenKey: 'release',
+ value: __('Any'),
+ },
{
url: 'label_name[]=None',
tokenKey: 'label',
diff --git a/app/assets/javascripts/flash.js b/app/assets/javascripts/flash.js
index fc9c5827ed..2c3320b5e7 100644
--- a/app/assets/javascripts/flash.js
+++ b/app/assets/javascripts/flash.js
@@ -37,7 +37,7 @@ const createAction = config => `
`;
const createFlashEl = (message, type) => `
-
+
${_.escape(message)}
diff --git a/app/assets/javascripts/frequent_items/store/mutations.js b/app/assets/javascripts/frequent_items/store/mutations.js
index 41b660a243..92ac3a2c94 100644
--- a/app/assets/javascripts/frequent_items/store/mutations.js
+++ b/app/assets/javascripts/frequent_items/store/mutations.js
@@ -47,7 +47,8 @@ export default {
hasSearchQuery: true,
});
},
- [types.RECEIVE_SEARCHED_ITEMS_SUCCESS](state, rawItems) {
+ [types.RECEIVE_SEARCHED_ITEMS_SUCCESS](state, results) {
+ const rawItems = results.data;
Object.assign(state, {
items: rawItems.map(rawItem => ({
id: rawItem.id,
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index 4e1b4f2652..045f77af7e 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -617,7 +617,7 @@ GitLabDropdown = (function() {
GitLabDropdown.prototype.hidden = function(e) {
var $input;
this.resetRows();
- this.removeArrayKeyEvent();
+ this.removeArrowKeyEvent();
$input = this.dropdown.find('.dropdown-input-field');
if (this.options.filterable) {
$input.blur();
@@ -900,7 +900,7 @@ GitLabDropdown = (function() {
);
};
- GitLabDropdown.prototype.removeArrayKeyEvent = function() {
+ GitLabDropdown.prototype.removeArrowKeyEvent = function() {
return $('body').off('keydown');
};
diff --git a/app/assets/javascripts/grafana_integration/components/grafana_integration.vue b/app/assets/javascripts/grafana_integration/components/grafana_integration.vue
new file mode 100644
index 0000000000..bd504d95ee
--- /dev/null
+++ b/app/assets/javascripts/grafana_integration/components/grafana_integration.vue
@@ -0,0 +1,103 @@
+
+
+
+
+
diff --git a/app/assets/javascripts/grafana_integration/index.js b/app/assets/javascripts/grafana_integration/index.js
new file mode 100644
index 0000000000..a93edab438
--- /dev/null
+++ b/app/assets/javascripts/grafana_integration/index.js
@@ -0,0 +1,14 @@
+import Vue from 'vue';
+import store from './store';
+import GrafanaIntegration from './components/grafana_integration.vue';
+
+export default () => {
+ const el = document.querySelector('.js-grafana-integration');
+ return new Vue({
+ el,
+ store: store(el.dataset),
+ render(createElement) {
+ return createElement(GrafanaIntegration);
+ },
+ });
+};
diff --git a/app/assets/javascripts/grafana_integration/store/actions.js b/app/assets/javascripts/grafana_integration/store/actions.js
new file mode 100644
index 0000000000..d83f1e0831
--- /dev/null
+++ b/app/assets/javascripts/grafana_integration/store/actions.js
@@ -0,0 +1,42 @@
+import axios from '~/lib/utils/axios_utils';
+import { __ } from '~/locale';
+import createFlash from '~/flash';
+import { refreshCurrentPage } from '~/lib/utils/url_utility';
+import * as mutationTypes from './mutation_types';
+
+export const setGrafanaUrl = ({ commit }, url) => commit(mutationTypes.SET_GRAFANA_URL, url);
+
+export const setGrafanaToken = ({ commit }, token) =>
+ commit(mutationTypes.SET_GRAFANA_TOKEN, token);
+
+export const setGrafanaEnabled = ({ commit }, enabled) =>
+ commit(mutationTypes.SET_GRAFANA_ENABLED, enabled);
+
+export const updateGrafanaIntegration = ({ state, dispatch }) =>
+ axios
+ .patch(state.operationsSettingsEndpoint, {
+ project: {
+ grafana_integration_attributes: {
+ grafana_url: state.grafanaUrl,
+ token: state.grafanaToken,
+ enabled: state.grafanaEnabled,
+ },
+ },
+ })
+ .then(() => dispatch('receiveGrafanaIntegrationUpdateSuccess'))
+ .catch(error => dispatch('receiveGrafanaIntegrationUpdateError', error));
+
+export const receiveGrafanaIntegrationUpdateSuccess = () => {
+ /**
+ * The operations_controller currently handles successful requests
+ * by creating a flash banner messsage to notify the user.
+ */
+ refreshCurrentPage();
+};
+
+export const receiveGrafanaIntegrationUpdateError = (_, error) => {
+ const { response } = error;
+ const message = response.data && response.data.message ? response.data.message : '';
+
+ createFlash(`${__('There was an error saving your changes.')} ${message}`, 'alert');
+};
diff --git a/app/assets/javascripts/grafana_integration/store/index.js b/app/assets/javascripts/grafana_integration/store/index.js
new file mode 100644
index 0000000000..e96bb1e8aa
--- /dev/null
+++ b/app/assets/javascripts/grafana_integration/store/index.js
@@ -0,0 +1,16 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import createState from './state';
+import * as actions from './actions';
+import mutations from './mutations';
+
+Vue.use(Vuex);
+
+export const createStore = initialState =>
+ new Vuex.Store({
+ state: createState(initialState),
+ actions,
+ mutations,
+ });
+
+export default createStore;
diff --git a/app/assets/javascripts/grafana_integration/store/mutation_types.js b/app/assets/javascripts/grafana_integration/store/mutation_types.js
new file mode 100644
index 0000000000..314c3a4039
--- /dev/null
+++ b/app/assets/javascripts/grafana_integration/store/mutation_types.js
@@ -0,0 +1,3 @@
+export const SET_GRAFANA_URL = 'SET_GRAFANA_URL';
+export const SET_GRAFANA_TOKEN = 'SET_GRAFANA_TOKEN';
+export const SET_GRAFANA_ENABLED = 'SET_GRAFANA_ENABLED';
diff --git a/app/assets/javascripts/grafana_integration/store/mutations.js b/app/assets/javascripts/grafana_integration/store/mutations.js
new file mode 100644
index 0000000000..0992030d40
--- /dev/null
+++ b/app/assets/javascripts/grafana_integration/store/mutations.js
@@ -0,0 +1,13 @@
+import * as types from './mutation_types';
+
+export default {
+ [types.SET_GRAFANA_URL](state, url) {
+ state.grafanaUrl = url;
+ },
+ [types.SET_GRAFANA_TOKEN](state, token) {
+ state.grafanaToken = token;
+ },
+ [types.SET_GRAFANA_ENABLED](state, enabled) {
+ state.grafanaEnabled = enabled;
+ },
+};
diff --git a/app/assets/javascripts/grafana_integration/store/state.js b/app/assets/javascripts/grafana_integration/store/state.js
new file mode 100644
index 0000000000..a912eb5832
--- /dev/null
+++ b/app/assets/javascripts/grafana_integration/store/state.js
@@ -0,0 +1,8 @@
+import { parseBoolean } from '~/lib/utils/common_utils';
+
+export default (initialState = {}) => ({
+ operationsSettingsEndpoint: initialState.operationsSettingsEndpoint,
+ grafanaToken: initialState.grafanaIntegrationToken || '',
+ grafanaUrl: initialState.grafanaIntegrationUrl || '',
+ grafanaEnabled: parseBoolean(initialState.grafanaIntegrationEnabled) || false,
+});
diff --git a/app/assets/javascripts/group.js b/app/assets/javascripts/group.js
index 460174caf4..eda0f5d1d2 100644
--- a/app/assets/javascripts/group.js
+++ b/app/assets/javascripts/group.js
@@ -1,15 +1,23 @@
import $ from 'jquery';
import { slugify } from './lib/utils/text_utility';
+import fetchGroupPathAvailability from '~/pages/groups/new/fetch_group_path_availability';
+import flash from '~/flash';
+import { __ } from '~/locale';
export default class Group {
constructor() {
this.groupPath = $('#group_path');
this.groupName = $('#group_name');
+ this.parentId = $('#group_parent_id');
this.updateHandler = this.update.bind(this);
this.resetHandler = this.reset.bind(this);
+ this.updateGroupPathSlugHandler = this.updateGroupPathSlug.bind(this);
if (this.groupName.val() === '') {
this.groupName.on('keyup', this.updateHandler);
this.groupPath.on('keydown', this.resetHandler);
+ if (!this.parentId.val()) {
+ this.groupName.on('blur', this.updateGroupPathSlugHandler);
+ }
}
}
@@ -21,5 +29,21 @@ export default class Group {
reset() {
this.groupName.off('keyup', this.updateHandler);
this.groupPath.off('keydown', this.resetHandler);
+ this.groupName.off('blur', this.checkPathHandler);
+ }
+
+ updateGroupPathSlug() {
+ const slug = this.groupPath.val() || slugify(this.groupName.val());
+ if (!slug) return;
+
+ fetchGroupPathAvailability(slug)
+ .then(({ data }) => data)
+ .then(data => {
+ if (data.exists && data.suggests.length > 0) {
+ const suggestedSlug = data.suggests[0];
+ this.groupPath.val(suggestedSlug);
+ }
+ })
+ .catch(() => flash(__('An error occurred while checking group path')));
}
}
diff --git a/app/assets/javascripts/helpers/monitor_helper.js b/app/assets/javascripts/helpers/monitor_helper.js
index 2c2a04d5b5..d172aa8a44 100644
--- a/app/assets/javascripts/helpers/monitor_helper.js
+++ b/app/assets/javascripts/helpers/monitor_helper.js
@@ -1,17 +1,31 @@
-/* eslint-disable import/prefer-default-export */
-
+/**
+ * @param {Array} queryResults - Array of Result objects
+ * @param {Object} defaultConfig - Default chart config values (e.g. lineStyle, name)
+ * @returns {Array} The formatted values
+ */
+// eslint-disable-next-line import/prefer-default-export
export const makeDataSeries = (queryResults, defaultConfig) =>
- queryResults.reduce((acc, result) => {
- const data = result.values.filter(([, value]) => !Number.isNaN(value));
- if (!data.length) {
- return acc;
- }
- const relevantMetric = defaultConfig.name.toLowerCase().replace(' ', '_');
- const name = result.metric[relevantMetric];
- const series = { data };
- if (name) {
- series.name = `${defaultConfig.name}: ${name}`;
- }
+ queryResults
+ .map(result => {
+ const data = result.values.filter(([, value]) => !Number.isNaN(value));
+ if (!data.length) {
+ return null;
+ }
+ const relevantMetric = defaultConfig.name.toLowerCase().replace(' ', '_');
+ const name = result.metric[relevantMetric];
+ const series = { data };
+ if (name) {
+ series.name = `${defaultConfig.name}: ${name}`;
+ } else {
+ series.name = defaultConfig.name;
+ Object.keys(result.metric).forEach(templateVar => {
+ const value = result.metric[templateVar];
+ const regex = new RegExp(`{{\\s*${templateVar}\\s*}}`, 'g');
- return acc.concat({ ...defaultConfig, ...series });
- }, []);
+ series.name = series.name.replace(regex, value);
+ });
+ }
+
+ return { ...defaultConfig, ...series };
+ })
+ .filter(series => series !== null);
diff --git a/app/assets/javascripts/ide/components/jobs/stage.vue b/app/assets/javascripts/ide/components/jobs/stage.vue
index 9ad9d4455b..52ca61c06b 100644
--- a/app/assets/javascripts/ide/components/jobs/stage.vue
+++ b/app/assets/javascripts/ide/components/jobs/stage.vue
@@ -58,6 +58,7 @@ export default {
-
+
diff --git a/app/assets/javascripts/ide/components/preview/clientside.vue b/app/assets/javascripts/ide/components/preview/clientside.vue
index 6999746f11..beb179d041 100644
--- a/app/assets/javascripts/ide/components/preview/clientside.vue
+++ b/app/assets/javascripts/ide/components/preview/clientside.vue
@@ -92,6 +92,7 @@ export default {
},
methods: {
...mapActions(['getFileData', 'getRawFileData']),
+ ...mapActions('clientside', ['pingUsage']),
loadFileContent(path) {
return this.getFileData({ path, makeFileActive: false }).then(() =>
this.getRawFileData({ path }),
@@ -100,6 +101,8 @@ export default {
initPreview() {
if (!this.mainEntry) return null;
+ this.pingUsage();
+
return this.loadFileContent(this.mainEntry)
.then(() => this.$nextTick())
.then(() => {
diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue
index 3bf8308cce..08b3e8a34d 100644
--- a/app/assets/javascripts/ide/components/repo_editor.vue
+++ b/app/assets/javascripts/ide/components/repo_editor.vue
@@ -301,6 +301,7 @@ export default {
v-if="showContentViewer"
:content="file.content || file.raw"
:path="file.rawPath || file.path"
+ :file-path="file.path"
:file-size="file.size"
:project-path="file.projectId"
:type="fileType"
diff --git a/app/assets/javascripts/ide/services/index.js b/app/assets/javascripts/ide/services/index.js
index ba33b6826d..f6ad2f9c7d 100644
--- a/app/assets/javascripts/ide/services/index.js
+++ b/app/assets/javascripts/ide/services/index.js
@@ -1,4 +1,6 @@
import axios from '~/lib/utils/axios_utils';
+import { joinPaths } from '~/lib/utils/url_utility';
+import { escapeFileUrl } from '../stores/utils';
import Api from '~/api';
export default {
@@ -23,18 +25,25 @@ export default {
.then(({ data }) => data);
},
getBaseRawFileData(file, sha) {
- if (file.tempFile) {
- return Promise.resolve(file.baseRaw);
- }
+ if (file.tempFile || file.baseRaw) return Promise.resolve(file.baseRaw);
- if (file.baseRaw) {
- return Promise.resolve(file.baseRaw);
- }
+ // if files are renamed, their base path has changed
+ const filePath =
+ file.mrChange && file.mrChange.renamed_file ? file.mrChange.old_path : file.path;
return axios
- .get(file.rawPath.replace(`/raw/${file.branchId}/${file.path}`, `/raw/${sha}/${file.path}`), {
- transformResponse: [f => f],
- })
+ .get(
+ joinPaths(
+ gon.relative_url_root || '/',
+ file.projectId,
+ 'raw',
+ sha,
+ escapeFileUrl(filePath),
+ ),
+ {
+ transformResponse: [f => f],
+ },
+ )
.then(({ data }) => data);
},
getProjectData(namespace, project) {
@@ -58,8 +67,8 @@ export default {
commit(projectId, payload) {
return Api.commitMultiple(projectId, payload);
},
- getFiles(projectUrl, branchId) {
- const url = `${projectUrl}/files/${branchId}`;
+ getFiles(projectUrl, ref) {
+ const url = `${projectUrl}/files/${ref}`;
return axios.get(url, { params: { format: 'json' } });
},
lastCommitPipelines({ getters }) {
diff --git a/app/assets/javascripts/ide/stores/actions/file.js b/app/assets/javascripts/ide/stores/actions/file.js
index 59445afc7a..9af0b50d1a 100644
--- a/app/assets/javascripts/ide/stores/actions/file.js
+++ b/app/assets/javascripts/ide/stores/actions/file.js
@@ -1,11 +1,10 @@
import { joinPaths } from '~/lib/utils/url_utility';
-import { normalizeHeaders } from '~/lib/utils/common_utils';
import { __ } from '~/locale';
import eventHub from '../../eventhub';
import service from '../../services';
import * as types from '../mutation_types';
import router from '../../ide_router';
-import { setPageTitle, replaceFileUrl } from '../utils';
+import { escapeFileUrl, addFinalNewlineIfNeeded, setPageTitleForFile } from '../utils';
import { viewerTypes, stageKeys } from '../../constants';
export const closeFile = ({ commit, state, dispatch }, file) => {
@@ -58,7 +57,7 @@ export const setFileActive = ({ commit, state, getters, dispatch }, path) => {
};
export const getFileData = (
- { state, commit, dispatch },
+ { state, commit, dispatch, getters },
{ path, makeFileActive = true, openFile = makeFileActive },
) => {
const file = state.entries[path];
@@ -67,15 +66,18 @@ export const getFileData = (
commit(types.TOGGLE_LOADING, { entry: file });
- const url = file.prevPath ? replaceFileUrl(file.url, file.path, file.prevPath) : file.url;
+ const url = joinPaths(
+ gon.relative_url_root || '/',
+ state.currentProjectId,
+ file.type,
+ getters.lastCommit && getters.lastCommit.id,
+ escapeFileUrl(file.prevPath || file.path),
+ );
return service
- .getFileData(joinPaths(gon.relative_url_root || '', url.replace('/-/', '/')))
- .then(({ data, headers }) => {
- const normalizedHeaders = normalizeHeaders(headers);
- let title = normalizedHeaders['PAGE-TITLE'];
- title = file.prevPath ? title.replace(file.prevPath, file.path) : title;
- setPageTitle(decodeURI(title));
+ .getFileData(url)
+ .then(({ data }) => {
+ setPageTitleForFile(state, file);
if (data) commit(types.SET_FILE_DATA, { data, file });
if (openFile) commit(types.TOGGLE_FILE_OPEN, path);
@@ -140,7 +142,10 @@ export const getRawFileData = ({ state, commit, dispatch, getters }, { path }) =
export const changeFileContent = ({ commit, dispatch, state }, { path, content }) => {
const file = state.entries[path];
- commit(types.UPDATE_FILE_CONTENT, { path, content });
+ commit(types.UPDATE_FILE_CONTENT, {
+ path,
+ content: addFinalNewlineIfNeeded(content),
+ });
const indexOfChangedFile = state.changedFiles.findIndex(f => f.path === path);
diff --git a/app/assets/javascripts/ide/stores/actions/merge_request.js b/app/assets/javascripts/ide/stores/actions/merge_request.js
index 1273e37585..6790c0fbda 100644
--- a/app/assets/javascripts/ide/stores/actions/merge_request.js
+++ b/app/assets/javascripts/ide/stores/actions/merge_request.js
@@ -152,15 +152,17 @@ export const openMergeRequest = (
.then(mr => {
dispatch('setCurrentBranchId', mr.source_branch);
- dispatch('getBranchData', {
+ // getFiles needs to be called after getting the branch data
+ // since files are fetched using the last commit sha of the branch
+ return dispatch('getBranchData', {
projectId,
branchId: mr.source_branch,
- });
-
- return dispatch('getFiles', {
- projectId,
- branchId: mr.source_branch,
- });
+ }).then(() =>
+ dispatch('getFiles', {
+ projectId,
+ branchId: mr.source_branch,
+ }),
+ );
})
.then(() =>
dispatch('getMergeRequestVersions', {
diff --git a/app/assets/javascripts/ide/stores/actions/tree.js b/app/assets/javascripts/ide/stores/actions/tree.js
index 75511574d3..72cd099c5a 100644
--- a/app/assets/javascripts/ide/stores/actions/tree.js
+++ b/app/assets/javascripts/ide/stores/actions/tree.js
@@ -46,7 +46,7 @@ export const setDirectoryData = ({ state, commit }, { projectId, branchId, treeL
});
};
-export const getFiles = ({ state, commit, dispatch }, { projectId, branchId } = {}) =>
+export const getFiles = ({ state, commit, dispatch, getters }, { projectId, branchId } = {}) =>
new Promise((resolve, reject) => {
if (
!state.trees[`${projectId}/${branchId}`] ||
@@ -54,10 +54,11 @@ export const getFiles = ({ state, commit, dispatch }, { projectId, branchId } =
state.trees[`${projectId}/${branchId}`].tree.length === 0)
) {
const selectedProject = state.projects[projectId];
- commit(types.CREATE_TREE, { treePath: `${projectId}/${branchId}` });
+ const selectedBranch = getters.findBranch(projectId, branchId);
+ commit(types.CREATE_TREE, { treePath: `${projectId}/${branchId}` });
service
- .getFiles(selectedProject.web_url, branchId)
+ .getFiles(selectedProject.web_url, selectedBranch.commit.id)
.then(({ data }) => {
const { entries, treeList } = decorateFiles({
data,
diff --git a/app/assets/javascripts/ide/stores/getters.js b/app/assets/javascripts/ide/stores/getters.js
index 85fd45358b..a176fd0aca 100644
--- a/app/assets/javascripts/ide/stores/getters.js
+++ b/app/assets/javascripts/ide/stores/getters.js
@@ -34,7 +34,9 @@ export const currentMergeRequest = state => {
return null;
};
-export const currentProject = state => state.projects[state.currentProjectId];
+export const findProject = state => projectId => state.projects[projectId];
+
+export const currentProject = (state, getters) => getters.findProject(state.currentProjectId);
export const emptyRepo = state =>
state.projects[state.currentProjectId] && state.projects[state.currentProjectId].empty_repo;
@@ -94,8 +96,14 @@ export const lastCommit = (state, getters) => {
return branch ? branch.commit : null;
};
+export const findBranch = (state, getters) => (projectId, branchId) => {
+ const project = getters.findProject(projectId);
+
+ return project && project.branches[branchId];
+};
+
export const currentBranch = (state, getters) =>
- getters.currentProject && getters.currentProject.branches[state.currentBranchId];
+ getters.findBranch(state.currentProjectId, state.currentBranchId);
export const branchName = (_state, getters) => getters.currentBranch && getters.currentBranch.name;
diff --git a/app/assets/javascripts/ide/stores/index.js b/app/assets/javascripts/ide/stores/index.js
index f1f544b52b..85550578e9 100644
--- a/app/assets/javascripts/ide/stores/index.js
+++ b/app/assets/javascripts/ide/stores/index.js
@@ -10,6 +10,7 @@ import mergeRequests from './modules/merge_requests';
import branches from './modules/branches';
import fileTemplates from './modules/file_templates';
import paneModule from './modules/pane';
+import clientsideModule from './modules/clientside';
Vue.use(Vuex);
@@ -26,6 +27,7 @@ export const createStore = () =>
branches,
fileTemplates: fileTemplates(),
rightPane: paneModule(),
+ clientside: clientsideModule(),
},
});
diff --git a/app/assets/javascripts/ide/stores/modules/clientside/actions.js b/app/assets/javascripts/ide/stores/modules/clientside/actions.js
new file mode 100644
index 0000000000..eb3bcdff2a
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/clientside/actions.js
@@ -0,0 +1,12 @@
+import axios from '~/lib/utils/axios_utils';
+
+export const pingUsage = ({ rootGetters }) => {
+ const { web_url: projectUrl } = rootGetters.currentProject;
+
+ const url = `${projectUrl}/usage_ping/web_ide_clientside_preview`;
+
+ return axios.post(url);
+};
+
+// prevent babel-plugin-rewire from generating an invalid default during karma tests
+export default () => {};
diff --git a/app/assets/javascripts/ide/stores/modules/clientside/index.js b/app/assets/javascripts/ide/stores/modules/clientside/index.js
new file mode 100644
index 0000000000..b28f7b935a
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/clientside/index.js
@@ -0,0 +1,6 @@
+import * as actions from './actions';
+
+export default () => ({
+ namespaced: true,
+ actions,
+});
diff --git a/app/assets/javascripts/ide/stores/utils.js b/app/assets/javascripts/ide/stores/utils.js
index a8d8ff31af..be7ee80656 100644
--- a/app/assets/javascripts/ide/stores/utils.js
+++ b/app/assets/javascripts/ide/stores/utils.js
@@ -113,6 +113,11 @@ export const setPageTitle = title => {
document.title = title;
};
+export const setPageTitleForFile = (state, file) => {
+ const title = [file.path, state.currentBranchId, state.currentProjectId, 'GitLab'].join(' · ');
+ setPageTitle(title);
+};
+
export const commitActionForFile = file => {
if (file.prevPath) {
return commitActionTypes.move;
@@ -269,3 +274,7 @@ export const pathsAreEqual = (a, b) => {
return cleanA === cleanB;
};
+
+// if the contents of a file dont end with a newline, this function adds a newline
+export const addFinalNewlineIfNeeded = content =>
+ content.charAt(content.length - 1) !== '\n' ? `${content}\n` : content;
diff --git a/app/assets/javascripts/issuable_bulk_update_sidebar.js b/app/assets/javascripts/issuable_bulk_update_sidebar.js
index 74150ce3a8..bd6e843354 100644
--- a/app/assets/javascripts/issuable_bulk_update_sidebar.js
+++ b/app/assets/javascripts/issuable_bulk_update_sidebar.js
@@ -1,11 +1,13 @@
/* eslint-disable class-methods-use-this, no-new */
import $ from 'jquery';
+import { property } from 'underscore';
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
import MilestoneSelect from './milestone_select';
import issueStatusSelect from './issue_status_select';
import subscriptionSelect from './subscription_select';
import LabelsSelect from './labels_select';
+import issueableEventHub from './issuables_list/eventhub';
const HIDDEN_CLASS = 'hidden';
const DISABLED_CONTENT_CLASS = 'disabled-content';
@@ -14,6 +16,8 @@ const SIDEBAR_COLLAPSED_CLASS = 'right-sidebar-collapsed issuable-bulk-update-si
export default class IssuableBulkUpdateSidebar {
constructor() {
+ this.vueIssuablesListFeature = property(['gon', 'features', 'vueIssuablesList'])(window);
+
this.initDomElements();
this.bindEvents();
this.initDropdowns();
@@ -41,6 +45,17 @@ export default class IssuableBulkUpdateSidebar {
this.$issuesList.on('change', () => this.updateFormState());
this.$bulkEditSubmitBtn.on('click', () => this.prepForSubmit());
this.$checkAllContainer.on('click', () => this.updateFormState());
+
+ if (this.vueIssuablesListFeature) {
+ issueableEventHub.$on('issuables:updateBulkEdit', () => {
+ // Danger! Strong coupling ahead!
+ // The bulk update sidebar and its dropdowns look for .selected-issuable checkboxes, and get data on which issue
+ // is selected by inspecting the DOM. Ideally, we would pass the selected issuable IDs and their properties
+ // explicitly, but this component is used in too many places right now to refactor straight away.
+
+ this.updateFormState();
+ });
+ }
}
initDropdowns() {
@@ -73,6 +88,8 @@ export default class IssuableBulkUpdateSidebar {
toggleBulkEdit(e, enable) {
e.preventDefault();
+ issueableEventHub.$emit('issuables:toggleBulkEdit', enable);
+
this.toggleSidebarDisplay(enable);
this.toggleBulkEditButtonDisabled(enable);
this.toggleOtherFiltersDisabled(enable);
@@ -106,7 +123,7 @@ export default class IssuableBulkUpdateSidebar {
}
toggleCheckboxDisplay(show) {
- this.$checkAllContainer.toggleClass(HIDDEN_CLASS, !show);
+ this.$checkAllContainer.toggleClass(HIDDEN_CLASS, !show || this.vueIssuablesListFeature);
this.$issueChecks.toggleClass(HIDDEN_CLASS, !show);
}
diff --git a/app/assets/javascripts/issuables_list/components/issuable.vue b/app/assets/javascripts/issuables_list/components/issuable.vue
new file mode 100644
index 0000000000..eb924609a8
--- /dev/null
+++ b/app/assets/javascripts/issuables_list/components/issuable.vue
@@ -0,0 +1,335 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ issuable.title }}
+
+ {{
+ issuable.task_status
+ }}
+
+
+
+ {{ referencePath }}
+
+
+ ·
+
+
+
+
+
+ {{ issuable.milestone.title }}
+
+
+
+
+ {{ dueDateWords }}
+
+
+
+
+ {{ label.name }}
+
+
+
+
+
+ {{ issuable.weight }}
+
+
+
+
+
+
+
+ {{ __('CLOSED') }}
+
+
+
+
+
+
+
+ {{ meta.value }}
+
+
+
+
+
+ {{ userNotesCount }}
+
+
+
+ {{ updatedDateAgo }}
+
+
+
+
+
diff --git a/app/assets/javascripts/issuables_list/components/issuables_list_app.vue b/app/assets/javascripts/issuables_list/components/issuables_list_app.vue
new file mode 100644
index 0000000000..6b6a8bd406
--- /dev/null
+++ b/app/assets/javascripts/issuables_list/components/issuables_list_app.vue
@@ -0,0 +1,277 @@
+
+
+
+
+
+
+
+ {{ __('Select all') }}
+
+
+
+
+
+
+
+
diff --git a/app/assets/javascripts/issuables_list/constants.js b/app/assets/javascripts/issuables_list/constants.js
new file mode 100644
index 0000000000..71b9c52c70
--- /dev/null
+++ b/app/assets/javascripts/issuables_list/constants.js
@@ -0,0 +1,33 @@
+// Maps sort order as it appears in the URL query to API `order_by` and `sort` params.
+const PRIORITY = 'priority';
+const ASC = 'asc';
+const DESC = 'desc';
+const CREATED_AT = 'created_at';
+const UPDATED_AT = 'updated_at';
+const DUE_DATE = 'due_date';
+const MILESTONE_DUE = 'milestone_due';
+const POPULARITY = 'popularity';
+const WEIGHT = 'weight';
+const LABEL_PRIORITY = 'label_priority';
+export const RELATIVE_POSITION = 'relative_position';
+export const LOADING_LIST_ITEMS_LENGTH = 8;
+export const PAGE_SIZE = 20;
+export const PAGE_SIZE_MANUAL = 100;
+
+export const sortOrderMap = {
+ priority: { order_by: PRIORITY, sort: ASC }, // asc and desc are flipped for some reason
+ created_date: { order_by: CREATED_AT, sort: DESC },
+ created_asc: { order_by: CREATED_AT, sort: ASC },
+ updated_desc: { order_by: UPDATED_AT, sort: DESC },
+ updated_asc: { order_by: UPDATED_AT, sort: ASC },
+ milestone_due_desc: { order_by: MILESTONE_DUE, sort: DESC },
+ milestone: { order_by: MILESTONE_DUE, sort: ASC },
+ due_date_desc: { order_by: DUE_DATE, sort: DESC },
+ due_date: { order_by: DUE_DATE, sort: ASC },
+ popularity: { order_by: POPULARITY, sort: DESC },
+ popularity_asc: { order_by: POPULARITY, sort: ASC },
+ label_priority: { order_by: LABEL_PRIORITY, sort: ASC }, // asc and desc are flipped
+ relative_position: { order_by: RELATIVE_POSITION, sort: ASC },
+ weight_desc: { order_by: WEIGHT, sort: DESC },
+ weight: { order_by: WEIGHT, sort: ASC },
+};
diff --git a/app/assets/javascripts/issuables_list/eventhub.js b/app/assets/javascripts/issuables_list/eventhub.js
new file mode 100644
index 0000000000..d1601a7d8f
--- /dev/null
+++ b/app/assets/javascripts/issuables_list/eventhub.js
@@ -0,0 +1,5 @@
+import Vue from 'vue';
+
+const issueablesEventBus = new Vue();
+
+export default issueablesEventBus;
diff --git a/app/assets/javascripts/issuables_list/index.js b/app/assets/javascripts/issuables_list/index.js
new file mode 100644
index 0000000000..9fc7fa837f
--- /dev/null
+++ b/app/assets/javascripts/issuables_list/index.js
@@ -0,0 +1,24 @@
+import Vue from 'vue';
+import IssuablesListApp from './components/issuables_list_app.vue';
+
+export default function initIssuablesList() {
+ if (!gon.features || !gon.features.vueIssuablesList) {
+ return;
+ }
+
+ document.querySelectorAll('.js-issuables-list').forEach(el => {
+ const { canBulkEdit, ...data } = el.dataset;
+
+ const props = {
+ ...data,
+ canBulkEdit: Boolean(canBulkEdit),
+ };
+
+ return new Vue({
+ el,
+ render(createElement) {
+ return createElement(IssuablesListApp, { props });
+ },
+ });
+ });
+}
diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js
index a9e086fade..9136a47d54 100644
--- a/app/assets/javascripts/issue.js
+++ b/app/assets/javascripts/issue.js
@@ -1,4 +1,4 @@
-/* eslint-disable no-var, one-var, consistent-return */
+/* eslint-disable consistent-return */
import $ from 'jquery';
import axios from './lib/utils/axios_utils';
@@ -91,18 +91,17 @@ export default class Issue {
'click',
'.js-issuable-actions a.btn-close, .js-issuable-actions a.btn-reopen',
e => {
- var $button, shouldSubmit, url;
e.preventDefault();
e.stopImmediatePropagation();
- $button = $(e.currentTarget);
- shouldSubmit = $button.hasClass('btn-comment');
+ const $button = $(e.currentTarget);
+ const shouldSubmit = $button.hasClass('btn-comment');
if (shouldSubmit) {
Issue.submitNoteForm($button.closest('form'));
}
this.disableCloseReopenButton($button);
- url = $button.attr('href');
+ const url = $button.attr('href');
return axios
.put(url)
.then(({ data }) => {
@@ -139,16 +138,14 @@ export default class Issue {
}
static submitNoteForm(form) {
- var noteText;
- noteText = form.find('textarea.js-note-text').val();
+ const noteText = form.find('textarea.js-note-text').val();
if (noteText && noteText.trim().length > 0) {
return form.submit();
}
}
static initRelatedBranches() {
- var $container;
- $container = $('#related-branches');
+ const $container = $('#related-branches');
return axios
.get($container.data('url'))
.then(({ data }) => {
diff --git a/app/assets/javascripts/jobs/components/log/log.vue b/app/assets/javascripts/jobs/components/log/log.vue
index ef126166e8..eb0de53f36 100644
--- a/app/assets/javascripts/jobs/components/log/log.vue
+++ b/app/assets/javascripts/jobs/components/log/log.vue
@@ -9,13 +9,42 @@ export default {
LogLine,
},
computed: {
- ...mapState(['traceEndpoint', 'trace', 'isTraceComplete']),
+ ...mapState([
+ 'traceEndpoint',
+ 'trace',
+ 'isTraceComplete',
+ 'isScrolledToBottomBeforeReceivingTrace',
+ ]),
+ },
+ updated() {
+ this.$nextTick(() => {
+ this.handleScrollDown();
+ });
+ },
+ mounted() {
+ this.$nextTick(() => {
+ this.handleScrollDown();
+ });
},
methods: {
- ...mapActions(['toggleCollapsibleLine']),
+ ...mapActions(['toggleCollapsibleLine', 'scrollBottom']),
handleOnClickCollapsibleLine(section) {
this.toggleCollapsibleLine(section);
},
+ /**
+ * The job log is sent in HTML, which means we need to use `v-html` to render it
+ * Using the updated hook with $nextTick is not enough to wait for the DOM to be updated
+ * in this case because it runs before `v-html` has finished running, since there's no
+ * Vue binding.
+ * In order to scroll the page down after `v-html` has finished, we need to use setTimeout
+ */
+ handleScrollDown() {
+ if (this.isScrolledToBottomBeforeReceivingTrace) {
+ setTimeout(() => {
+ this.scrollBottom();
+ }, 0);
+ }
+ },
},
};
diff --git a/app/assets/javascripts/jobs/store/utils.js b/app/assets/javascripts/jobs/store/utils.js
index 58e49f54d9..179d0bc4e0 100644
--- a/app/assets/javascripts/jobs/store/utils.js
+++ b/app/assets/javascripts/jobs/store/utils.js
@@ -17,7 +17,7 @@ export const parseLine = (line = {}, lineNumber) => ({
* @param Number lineNumber
*/
export const parseHeaderLine = (line = {}, lineNumber) => ({
- isClosed: true,
+ isClosed: false,
isHeader: true,
line: parseLine(line, lineNumber),
lines: [],
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index 72de3b5d72..6abf723be9 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -1,4 +1,4 @@
-/* eslint-disable no-useless-return, func-names, no-var, no-underscore-dangle, one-var, no-new, consistent-return, no-shadow, no-param-reassign, vars-on-top, no-lonely-if, no-else-return, dot-notation, no-empty */
+/* eslint-disable no-useless-return, func-names, no-underscore-dangle, no-new, consistent-return, no-shadow, no-param-reassign, no-lonely-if, no-else-return, dot-notation, no-empty */
/* global Issuable */
/* global ListLabel */
@@ -15,63 +15,39 @@ import { isScopedLabel } from '~/lib/utils/common_utils';
export default class LabelsSelect {
constructor(els, options = {}) {
- var _this, $els;
- _this = this;
+ const _this = this;
- $els = $(els);
+ let $els = $(els);
if (!els) {
$els = $('.js-label-select');
}
$els.each((i, dropdown) => {
- var $block,
- $dropdown,
- $form,
- $loading,
- $selectbox,
- $sidebarCollapsedValue,
- $value,
- $dropdownMenu,
- abilityName,
- defaultLabel,
- issueUpdateURL,
- labelUrl,
- namespacePath,
- projectPath,
- saveLabelData,
- selectedLabel,
- showAny,
- showNo,
- $sidebarLabelTooltip,
- initialSelected,
- fieldName,
- showMenuAbove,
- $dropdownContainer;
- $dropdown = $(dropdown);
- $dropdownContainer = $dropdown.closest('.labels-filter');
- namespacePath = $dropdown.data('namespacePath');
- projectPath = $dropdown.data('projectPath');
- issueUpdateURL = $dropdown.data('issueUpdate');
- selectedLabel = $dropdown.data('selected');
+ const $dropdown = $(dropdown);
+ const $dropdownContainer = $dropdown.closest('.labels-filter');
+ const namespacePath = $dropdown.data('namespacePath');
+ const projectPath = $dropdown.data('projectPath');
+ const issueUpdateURL = $dropdown.data('issueUpdate');
+ let selectedLabel = $dropdown.data('selected');
if (selectedLabel != null && !$dropdown.hasClass('js-multiselect')) {
selectedLabel = selectedLabel.split(',');
}
- showNo = $dropdown.data('showNo');
- showAny = $dropdown.data('showAny');
- showMenuAbove = $dropdown.data('showMenuAbove');
- defaultLabel = $dropdown.data('defaultLabel') || __('Label');
- abilityName = $dropdown.data('abilityName');
- $selectbox = $dropdown.closest('.selectbox');
- $block = $selectbox.closest('.block');
- $form = $dropdown.closest('form, .js-issuable-update');
- $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span');
- $sidebarLabelTooltip = $block.find('.js-sidebar-labels-tooltip');
- $value = $block.find('.value');
- $dropdownMenu = $dropdown.parent().find('.dropdown-menu');
- $loading = $block.find('.block-loading').fadeOut();
- fieldName = $dropdown.data('fieldName');
- initialSelected = $selectbox
+ const showNo = $dropdown.data('showNo');
+ const showAny = $dropdown.data('showAny');
+ const showMenuAbove = $dropdown.data('showMenuAbove');
+ const defaultLabel = $dropdown.data('defaultLabel') || __('Label');
+ const abilityName = $dropdown.data('abilityName');
+ const $selectbox = $dropdown.closest('.selectbox');
+ const $block = $selectbox.closest('.block');
+ const $form = $dropdown.closest('form, .js-issuable-update');
+ const $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span');
+ const $sidebarLabelTooltip = $block.find('.js-sidebar-labels-tooltip');
+ const $value = $block.find('.value');
+ const $dropdownMenu = $dropdown.parent().find('.dropdown-menu');
+ const $loading = $block.find('.block-loading').fadeOut();
+ const fieldName = $dropdown.data('fieldName');
+ let initialSelected = $selectbox
.find(`input[name="${$dropdown.data('fieldName')}"]`)
.map(function() {
return this.value;
@@ -90,9 +66,8 @@ export default class LabelsSelect {
);
}
- saveLabelData = function() {
- var data, selected;
- selected = $dropdown
+ const saveLabelData = function() {
+ const selected = $dropdown
.closest('.selectbox')
.find(`input[name='${fieldName}']`)
.map(function() {
@@ -103,7 +78,7 @@ export default class LabelsSelect {
if (_.isEqual(initialSelected, selected)) return;
initialSelected = selected;
- data = {};
+ const data = {};
data[abilityName] = {};
data[abilityName].label_ids = selected;
if (!selected.length) {
@@ -114,12 +89,13 @@ export default class LabelsSelect {
axios
.put(issueUpdateURL, data)
.then(({ data }) => {
- var labelCount, template, labelTooltipTitle, labelTitles;
+ let labelTooltipTitle;
+ let template;
$loading.fadeOut();
$dropdown.trigger('loaded.gl.dropdown');
$selectbox.hide();
data.issueUpdateURL = issueUpdateURL;
- labelCount = 0;
+ let labelCount = 0;
if (data.labels.length && issueUpdateURL) {
template = LabelsSelect.getLabelTemplate({
labels: _.sortBy(data.labels, 'title'),
@@ -174,7 +150,7 @@ export default class LabelsSelect {
$sidebarCollapsedValue.text(labelCount);
if (data.labels.length) {
- labelTitles = data.labels.map(label => label.title);
+ let labelTitles = data.labels.map(label => label.title);
if (labelTitles.length > 5) {
labelTitles = labelTitles.slice(0, 5);
@@ -199,13 +175,13 @@ export default class LabelsSelect {
$dropdown.glDropdown({
showMenuAbove,
data(term, callback) {
- labelUrl = $dropdown.attr('data-labels');
+ const labelUrl = $dropdown.attr('data-labels');
axios
.get(labelUrl)
.then(res => {
let { data } = res;
if ($dropdown.hasClass('js-extra-options')) {
- var extraData = [];
+ const extraData = [];
if (showNo) {
extraData.unshift({
id: 0,
@@ -232,22 +208,14 @@ export default class LabelsSelect {
.catch(() => flash(__('Error fetching labels.')));
},
renderRow(label) {
- var linkEl,
- listItemEl,
- colorEl,
- indeterminate,
- removesAll,
- selectedClass,
- i,
- marked,
- dropdownValue;
+ let colorEl;
- selectedClass = [];
- removesAll = label.id <= 0 || label.id == null;
+ const selectedClass = [];
+ const removesAll = label.id <= 0 || label.id == null;
if ($dropdown.hasClass('js-filter-bulk-update')) {
- indeterminate = $dropdown.data('indeterminate') || [];
- marked = $dropdown.data('marked') || [];
+ const indeterminate = $dropdown.data('indeterminate') || [];
+ const marked = $dropdown.data('marked') || [];
if (indeterminate.indexOf(label.id) !== -1) {
selectedClass.push('is-indeterminate');
@@ -255,7 +223,7 @@ export default class LabelsSelect {
if (marked.indexOf(label.id) !== -1) {
// Remove is-indeterminate class if the item will be marked as active
- i = selectedClass.indexOf('is-indeterminate');
+ const i = selectedClass.indexOf('is-indeterminate');
if (i !== -1) {
selectedClass.splice(i, 1);
}
@@ -263,7 +231,7 @@ export default class LabelsSelect {
}
} else {
if (this.id(label)) {
- dropdownValue = this.id(label)
+ const dropdownValue = this.id(label)
.toString()
.replace(/'/g, "\\'");
@@ -287,7 +255,7 @@ export default class LabelsSelect {
colorEl = '';
}
- linkEl = document.createElement('a');
+ const linkEl = document.createElement('a');
linkEl.href = '#';
// We need to identify which items are actually labels
@@ -300,7 +268,7 @@ export default class LabelsSelect {
linkEl.className = selectedClass.join(' ');
linkEl.innerHTML = `${colorEl} ${_.escape(label.title)}`;
- listItemEl = document.createElement('li');
+ const listItemEl = document.createElement('li');
listItemEl.appendChild(linkEl);
return listItemEl;
@@ -312,12 +280,12 @@ export default class LabelsSelect {
filterable: true,
selected: $dropdown.data('selected') || [],
toggleLabel(selected, el) {
- var $dropdownParent = $dropdown.parent();
- var $dropdownInputField = $dropdownParent.find('.dropdown-input-field');
- var isSelected = el !== null ? el.hasClass('is-active') : false;
+ const $dropdownParent = $dropdown.parent();
+ const $dropdownInputField = $dropdownParent.find('.dropdown-input-field');
+ const isSelected = el !== null ? el.hasClass('is-active') : false;
- var title = selected ? selected.title : null;
- var selectedLabels = this.selected;
+ const title = selected ? selected.title : null;
+ const selectedLabels = this.selected;
if ($dropdownInputField.length && $dropdownInputField.val().length) {
$dropdownParent.find('.dropdown-input-clear').trigger('click');
@@ -329,7 +297,7 @@ export default class LabelsSelect {
} else if (isSelected) {
this.selected.push(title);
} else if (!isSelected && title) {
- var index = this.selected.indexOf(title);
+ const index = this.selected.indexOf(title);
this.selected.splice(index, 1);
}
@@ -359,10 +327,9 @@ export default class LabelsSelect {
}
},
hidden() {
- var isIssueIndex, isMRIndex, page;
- page = $('body').attr('data-page');
- isIssueIndex = page === 'projects:issues:index';
- isMRIndex = page === 'projects:merge_requests:index';
+ const page = $('body').attr('data-page');
+ const isIssueIndex = page === 'projects:issues:index';
+ const isMRIndex = page === 'projects:merge_requests:index';
$selectbox.hide();
// display:block overrides the hide-collapse rule
$value.removeAttr('style');
@@ -393,14 +360,13 @@ export default class LabelsSelect {
const { $el, e, isMarking } = clickEvent;
const label = clickEvent.selectedObj;
- var isIssueIndex, isMRIndex, page, boardsModel;
- var fadeOutLoader = () => {
+ const fadeOutLoader = () => {
$loading.fadeOut();
};
- page = $('body').attr('data-page');
- isIssueIndex = page === 'projects:issues:index';
- isMRIndex = page === 'projects:merge_requests:index';
+ const page = $('body').attr('data-page');
+ const isIssueIndex = page === 'projects:issues:index';
+ const isMRIndex = page === 'projects:merge_requests:index';
if ($dropdown.parent().find('.is-active:not(.dropdown-clear-active)').length) {
$dropdown
@@ -419,6 +385,7 @@ export default class LabelsSelect {
return;
}
+ let boardsModel;
if ($dropdown.closest('.add-issues-modal').length) {
boardsModel = ModalStore.store.filter;
}
@@ -450,7 +417,7 @@ export default class LabelsSelect {
}),
);
} else {
- var { labels } = boardsStore.detail.issue;
+ let { labels } = boardsStore.detail.issue;
labels = labels.filter(selectedLabel => selectedLabel.id !== label.id);
boardsStore.detail.issue.labels = labels;
}
@@ -578,16 +545,14 @@ export default class LabelsSelect {
}
// eslint-disable-next-line class-methods-use-this
setDropdownData($dropdown, isMarking, value) {
- var i, markedIds, unmarkedIds, indeterminateIds;
-
- markedIds = $dropdown.data('marked') || [];
- unmarkedIds = $dropdown.data('unmarked') || [];
- indeterminateIds = $dropdown.data('indeterminate') || [];
+ const markedIds = $dropdown.data('marked') || [];
+ const unmarkedIds = $dropdown.data('unmarked') || [];
+ const indeterminateIds = $dropdown.data('indeterminate') || [];
if (isMarking) {
markedIds.push(value);
- i = indeterminateIds.indexOf(value);
+ let i = indeterminateIds.indexOf(value);
if (i > -1) {
indeterminateIds.splice(i, 1);
}
@@ -598,7 +563,7 @@ export default class LabelsSelect {
}
} else {
// If marked item (not common) is unmarked
- i = markedIds.indexOf(value);
+ const i = markedIds.indexOf(value);
if (i > -1) {
markedIds.splice(i, 1);
}
diff --git a/app/assets/javascripts/lib/graphql.js b/app/assets/javascripts/lib/graphql.js
index c05db4a5c7..2c5278d16a 100644
--- a/app/assets/javascripts/lib/graphql.js
+++ b/app/assets/javascripts/lib/graphql.js
@@ -26,7 +26,11 @@ export default (resolvers = {}, config = {}) => {
createUploadLink(httpOptions),
new BatchHttpLink(httpOptions),
),
- cache: new InMemoryCache(config.cacheConfig),
+ cache: new InMemoryCache({
+ ...config.cacheConfig,
+ freezeResults: config.assumeImmutableResults,
+ }),
resolvers,
+ assumeImmutableResults: config.assumeImmutableResults,
});
};
diff --git a/app/assets/javascripts/lib/utils/chart_utils.js b/app/assets/javascripts/lib/utils/chart_utils.js
index 0f78756aac..4a1e6c5d68 100644
--- a/app/assets/javascripts/lib/utils/chart_utils.js
+++ b/app/assets/javascripts/lib/utils/chart_utils.js
@@ -81,3 +81,20 @@ export const lineChartOptions = ({ width, numberOfPoints, shouldAdjustFontSize }
},
},
});
+
+/**
+ * Takes a dataset and returns an array containing the y-values of it's first and last entry.
+ * (e.g., [['xValue1', 'yValue1'], ['xValue2', 'yValue2'], ['xValue3', 'yValue3']] will yield ['yValue1', 'yValue3'])
+ *
+ * @param {Array} data
+ * @returns {[*, *]}
+ */
+export const firstAndLastY = data => {
+ const [firstEntry] = data;
+ const [lastEntry] = data.slice(-1);
+
+ const firstY = firstEntry[1];
+ const lastY = lastEntry[1];
+
+ return [firstY, lastY];
+};
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
index 37b0215f6f..28143859e4 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -78,11 +78,11 @@ export const getDayName = date =>
* @param {date} datetime
* @returns {String}
*/
-export const formatDate = datetime => {
+export const formatDate = (datetime, format = 'mmm d, yyyy h:MMtt Z') => {
if (_.isString(datetime) && datetime.match(/\d+-\d+\d+ /)) {
throw new Error(__('Invalid date'));
}
- return dateFormat(datetime, 'mmm d, yyyy h:MMtt Z');
+ return dateFormat(datetime, format);
};
/**
@@ -541,7 +541,7 @@ export const stringifyTime = (timeObject, fullNameFormat = false) => {
* The result cannot become negative.
*
* @param endDate date string that the time difference is calculated for
- * @return {number} number of milliseconds remaining until the given date
+ * @return {Number} number of milliseconds remaining until the given date
*/
export const calculateRemainingMilliseconds = endDate => {
const remainingMilliseconds = new Date(endDate).getTime() - Date.now();
@@ -552,15 +552,53 @@ export const calculateRemainingMilliseconds = endDate => {
* Subtracts a given number of days from a given date and returns the new date.
*
* @param {Date} date the date that we will substract days from
- * @param {number} daysInPast number of days that are subtracted from a given date
- * @returns {String} Date string in ISO format
+ * @param {Number} daysInPast number of days that are subtracted from a given date
+ * @returns {Date} Date in past as Date object
*/
-export const getDateInPast = (date, daysInPast) => {
- const dateClone = newDate(date);
- return new Date(
- dateClone.setTime(dateClone.getTime() - daysInPast * 24 * 60 * 60 * 1000),
- ).toISOString();
+export const getDateInPast = (date, daysInPast) =>
+ new Date(newDate(date).setDate(date.getDate() - daysInPast));
+
+/*
+ * Appending T00:00:00 makes JS assume local time and prevents it from shifting the date
+ * to match the user's time zone. We want to display the date in server time for now, to
+ * be consistent with the "edit issue -> due date" UI.
+ */
+
+export const newDateAsLocaleTime = date => {
+ const suffix = 'T00:00:00';
+ return new Date(`${date}${suffix}`);
};
export const beginOfDayTime = 'T00:00:00Z';
export const endOfDayTime = 'T23:59:59Z';
+
+/**
+ * @param {Date} d1
+ * @param {Date} d2
+ * @param {Function} formatter
+ * @return {Any[]} an array of formatted dates between 2 given dates (including start&end date)
+ */
+export const getDatesInRange = (d1, d2, formatter = x => x) => {
+ if (!(d1 instanceof Date) || !(d2 instanceof Date)) {
+ return [];
+ }
+ let startDate = d1.getTime();
+ const endDate = d2.getTime();
+ const oneDay = 24 * 3600 * 1000;
+ const range = [d1];
+
+ while (startDate < endDate) {
+ startDate += oneDay;
+ range.push(new Date(startDate));
+ }
+
+ return range.map(formatter);
+};
+
+/**
+ * Converts the supplied number of seconds to milliseconds.
+ *
+ * @param {Number} seconds
+ * @return {Number} number of milliseconds
+ */
+export const secondsToMilliseconds = seconds => seconds * 1000;
diff --git a/app/assets/javascripts/lib/utils/notify.js b/app/assets/javascripts/lib/utils/notify.js
index cd509a1319..8db08099b3 100644
--- a/app/assets/javascripts/lib/utils/notify.js
+++ b/app/assets/javascripts/lib/utils/notify.js
@@ -1,8 +1,7 @@
-/* eslint-disable no-var, consistent-return, no-return-assign */
+/* eslint-disable consistent-return, no-return-assign */
function notificationGranted(message, opts, onclick) {
- var notification;
- notification = new Notification(message, opts);
+ const notification = new Notification(message, opts);
setTimeout(
() =>
// Hide the notification after X amount of seconds
@@ -21,8 +20,7 @@ function notifyPermissions() {
}
function notifyMe(message, body, icon, onclick) {
- var opts;
- opts = {
+ const opts = {
body,
icon,
};
diff --git a/app/assets/javascripts/lib/utils/number_utils.js b/app/assets/javascripts/lib/utils/number_utils.js
index 0f2cc57b1f..bc87232f40 100644
--- a/app/assets/javascripts/lib/utils/number_utils.js
+++ b/app/assets/javascripts/lib/utils/number_utils.js
@@ -117,3 +117,36 @@ export const median = arr => {
const sorted = arr.sort((a, b) => a - b);
return arr.length % 2 !== 0 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2;
};
+
+/**
+ * Computes the change from one value to the other as a percentage.
+ * @param {Number} firstY
+ * @param {Number} lastY
+ * @returns {Number}
+ */
+export const changeInPercent = (firstY, lastY) => {
+ if (firstY === lastY) {
+ return 0;
+ }
+
+ return Math.round(((lastY - firstY) / Math.abs(firstY)) * 100);
+};
+
+/**
+ * Computes and formats the change from one value to the other as a percentage.
+ * Prepends the computed percentage with either "+" or "-" to indicate an in- or decrease and
+ * returns a given string if the result is not finite (for example, if the first value is "0").
+ * @param firstY
+ * @param lastY
+ * @param nonFiniteResult
+ * @returns {String}
+ */
+export const formattedChangeInPercent = (firstY, lastY, { nonFiniteResult = '-' } = {}) => {
+ const change = changeInPercent(firstY, lastY);
+
+ if (!Number.isFinite(change)) {
+ return nonFiniteResult;
+ }
+
+ return `${change >= 0 ? '+' : ''}${change}%`;
+};
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js
index d13fbeb5fc..0c194d67bc 100644
--- a/app/assets/javascripts/lib/utils/text_utility.js
+++ b/app/assets/javascripts/lib/utils/text_utility.js
@@ -36,25 +36,26 @@ export const humanize = string =>
export const dasherize = str => str.replace(/[_\s]+/g, '-');
/**
- * Replaces whitespaces with hyphens, convert to lower case and remove non-allowed special characters
- * @param {String} str
+ * Replaces whitespace and non-sluggish characters with a given separator
+ * @param {String} str - The string to slugify
+ * @param {String=} separator - The separator used to separate words (defaults to "-")
* @returns {String}
*/
-export const slugify = str => {
+export const slugify = (str, separator = '-') => {
const slug = str
.trim()
.toLowerCase()
- .replace(/[^a-zA-Z0-9_.-]+/g, '-');
+ .replace(/[^a-zA-Z0-9_.-]+/g, separator);
- return slug === '-' ? '' : slug;
+ return slug === separator ? '' : slug;
};
/**
- * Replaces whitespaces with underscore and converts to lower case
+ * Replaces whitespace and non-sluggish characters with underscores
* @param {String} str
* @returns {String}
*/
-export const slugifyWithUnderscore = str => str.toLowerCase().replace(/\s+/g, '_');
+export const slugifyWithUnderscore = str => slugify(str, '_');
/**
* Truncates given text
@@ -138,6 +139,14 @@ export const stripHtml = (string, replace = '') => {
*/
export const convertToCamelCase = string => string.replace(/(_\w)/g, s => s[1].toUpperCase());
+/**
+ * Converts camelCase string to snake_case
+ *
+ * @param {*} string
+ */
+export const convertToSnakeCase = string =>
+ slugifyWithUnderscore(string.match(/([a-zA-Z][^A-Z]*)/g).join(' '));
+
/**
* Converts a sentence to lower case from the second word onwards
* e.g. Hello World => Hello world
diff --git a/app/assets/javascripts/lib/utils/tick_formats.js b/app/assets/javascripts/lib/utils/tick_formats.js
deleted file mode 100644
index af3ca71440..0000000000
--- a/app/assets/javascripts/lib/utils/tick_formats.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import { createDateTimeFormat } from '../../locale';
-
-let dateTimeFormats;
-
-export const initDateFormats = () => {
- const dayFormat = createDateTimeFormat({ month: 'short', day: 'numeric' });
- const monthFormat = createDateTimeFormat({ month: 'long' });
- const yearFormat = createDateTimeFormat({ year: 'numeric' });
-
- dateTimeFormats = {
- dayFormat,
- monthFormat,
- yearFormat,
- };
-};
-
-initDateFormats();
-
-/**
- Formats a localized date in way that it can be used for d3.js axis.tickFormat().
-
- That is, it displays
- - 4-digit for first of January
- - full month name for first of every month
- - day and abbreviated month otherwise
-
- see also https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Axes.md#tickFormat
- */
-export const dateTickFormat = date => {
- if (date.getDate() !== 1) {
- return dateTimeFormats.dayFormat.format(date);
- }
-
- if (date.getMonth() > 0) {
- return dateTimeFormats.monthFormat.format(date);
- }
-
- return dateTimeFormats.yearFormat.format(date);
-};
diff --git a/app/assets/javascripts/line_highlighter.js b/app/assets/javascripts/line_highlighter.js
index b6b96fe7bd..dd868bb9f4 100644
--- a/app/assets/javascripts/line_highlighter.js
+++ b/app/assets/javascripts/line_highlighter.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, no-var, no-underscore-dangle, no-param-reassign, consistent-return, one-var, no-else-return */
+/* eslint-disable func-names, no-underscore-dangle, no-param-reassign, consistent-return, no-else-return */
import $ from 'jquery';
@@ -82,13 +82,13 @@ LineHighlighter.prototype.highlightHash = function(newHash) {
};
LineHighlighter.prototype.clickHandler = function(event) {
- var current, lineNumber, range;
+ let range;
event.preventDefault();
this.clearHighlight();
- lineNumber = $(event.target)
+ const lineNumber = $(event.target)
.closest('a')
.data('lineNumber');
- current = this.hashToRange(this._hash);
+ const current = this.hashToRange(this._hash);
if (!(current[0] && event.shiftKey)) {
// If there's no current selection, or there is but Shift wasn't held,
// treat this like a single-line selection.
@@ -121,12 +121,11 @@ LineHighlighter.prototype.clearHighlight = function() {
//
// Returns an Array
LineHighlighter.prototype.hashToRange = function(hash) {
- var first, last, matches;
// ?L(\d+)(?:-(\d+))?$/)
- matches = hash.match(/^#?L(\d+)(?:-(\d+))?$/);
+ const matches = hash.match(/^#?L(\d+)(?:-(\d+))?$/);
if (matches && matches.length) {
- first = parseInt(matches[1], 10);
- last = matches[2] ? parseInt(matches[2], 10) : null;
+ const first = parseInt(matches[1], 10);
+ const last = matches[2] ? parseInt(matches[2], 10) : null;
return [first, last];
} else {
return [null, null];
@@ -160,7 +159,7 @@ LineHighlighter.prototype.highlightRange = function(range) {
// Set the URL hash string
LineHighlighter.prototype.setHash = function(firstLineNumber, lastLineNumber) {
- var hash;
+ let hash;
if (lastLineNumber) {
hash = `#L${firstLineNumber}-${lastLineNumber}`;
} else {
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index c19a845eb6..465c9a362b 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -37,7 +37,6 @@ import GlFieldErrors from './gl_field_errors';
import initUserPopovers from './user_popovers';
import { initUserTracking } from './tracking';
import { __ } from './locale';
-import initPrivacyPolicyUpdateCallout from './privacy_policy_update_callout';
import 'ee_else_ce/main_ee';
@@ -97,7 +96,6 @@ function deferredInitialisation() {
initUsagePingConsent();
initUserPopovers();
initUserTracking();
- initPrivacyPolicyUpdateCallout();
if (document.querySelector('.search')) initSearchAutocomplete();
@@ -162,24 +160,6 @@ function deferredInitialisation() {
});
loadAwardsHandler();
-
- /**
- * Toggle Canary Badge
- *
- * For GitLab.com only, when the user is using canary
- * we render a Next badge and hide the option to switch
- * to canay
- */
- if (Cookies.get('gitlab_canary') && Cookies.get('gitlab_canary') === 'true') {
- const canaryBadge = document.querySelector('.js-canary-badge');
- const canaryLink = document.querySelector('.js-canary-link');
- if (canaryBadge) {
- canaryBadge.classList.remove('hidden');
- }
- if (canaryLink) {
- canaryLink.classList.add('hidden');
- }
- }
}
document.addEventListener('DOMContentLoaded', () => {
diff --git a/app/assets/javascripts/manual_ordering.js b/app/assets/javascripts/manual_ordering.js
index 29a0e5a904..f93dbcd4c4 100644
--- a/app/assets/javascripts/manual_ordering.js
+++ b/app/assets/javascripts/manual_ordering.js
@@ -18,7 +18,7 @@ const updateIssue = (url, issueList, { move_before_id, move_after_id }) =>
createFlash(s__("ManualOrdering|Couldn't save the order of the issues"));
});
-const initManualOrdering = () => {
+const initManualOrdering = (draggableSelector = 'li.issue') => {
const issueList = document.querySelector('.manual-ordering');
if (!issueList || !(gon.current_user_id > 0)) {
@@ -34,14 +34,14 @@ const initManualOrdering = () => {
group: {
name: 'issues',
},
- draggable: 'li.issue',
+ draggable: draggableSelector,
onStart: () => {
sortableStart();
},
onUpdate: event => {
const el = event.item;
- const url = el.getAttribute('url');
+ const url = el.getAttribute('url') || el.dataset.url;
const prev = el.previousElementSibling;
const next = el.nextElementSibling;
diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js
index 7223b5c0d4..3a7ade5ad9 100644
--- a/app/assets/javascripts/merge_request.js
+++ b/app/assets/javascripts/merge_request.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, no-var, no-underscore-dangle, one-var, consistent-return */
+/* eslint-disable func-names, no-underscore-dangle, consistent-return */
import $ from 'jquery';
import { __ } from '~/locale';
@@ -17,14 +17,7 @@ function MergeRequest(opts) {
this.opts = opts != null ? opts : {};
this.submitNoteForm = this.submitNoteForm.bind(this);
this.$el = $('.merge-request');
- this.$('.show-all-commits').on(
- 'click',
- (function(_this) {
- return function() {
- return _this.showAllCommits();
- };
- })(this),
- );
+ this.$('.show-all-commits').on('click', () => this.showAllCommits());
this.initTabs();
this.initMRBtnListeners();
@@ -71,12 +64,10 @@ MergeRequest.prototype.showAllCommits = function() {
};
MergeRequest.prototype.initMRBtnListeners = function() {
- var _this;
- _this = this;
+ const _this = this;
return $('a.btn-close, a.btn-reopen').on('click', function(e) {
- var $this, shouldSubmit;
- $this = $(this);
- shouldSubmit = $this.hasClass('btn-comment');
+ const $this = $(this);
+ const shouldSubmit = $this.hasClass('btn-comment');
if (shouldSubmit && $this.data('submitted')) {
return;
}
@@ -95,8 +86,7 @@ MergeRequest.prototype.initMRBtnListeners = function() {
};
MergeRequest.prototype.submitNoteForm = function(form, $button) {
- var noteText;
- noteText = form.find('textarea.js-note-text').val();
+ const noteText = form.find('textarea.js-note-text').val();
if (noteText.trim().length > 0) {
form.submit();
$button.data('submitted', true);
@@ -106,7 +96,7 @@ MergeRequest.prototype.submitNoteForm = function(form, $button) {
MergeRequest.prototype.initCommitMessageListeners = function() {
$(document).on('click', 'a.js-with-description-link', e => {
- var textarea = $('textarea.js-commit-message');
+ const textarea = $('textarea.js-commit-message');
e.preventDefault();
textarea.val(textarea.data('messageWithDescription'));
@@ -115,7 +105,7 @@ MergeRequest.prototype.initCommitMessageListeners = function() {
});
$(document).on('click', 'a.js-without-description-link', e => {
- var textarea = $('textarea.js-commit-message');
+ const textarea = $('textarea.js-commit-message');
e.preventDefault();
textarea.val(textarea.data('messageWithoutDescription'));
diff --git a/app/assets/javascripts/monitoring/components/charts/anomaly.vue b/app/assets/javascripts/monitoring/components/charts/anomaly.vue
new file mode 100644
index 0000000000..8eeac737a1
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/charts/anomaly.vue
@@ -0,0 +1,227 @@
+
+
+
+
+
+
+
+
+ {{ content.name }}
+
+
+ {{ yValueFormatted(seriesIndex, content.dataIndex) }}
+
+
+
+
+
diff --git a/app/assets/javascripts/monitoring/components/charts/heatmap.vue b/app/assets/javascripts/monitoring/components/charts/heatmap.vue
new file mode 100644
index 0000000000..b8158247e4
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/charts/heatmap.vue
@@ -0,0 +1,73 @@
+
+
+
+
diff --git a/app/assets/javascripts/monitoring/components/charts/time_series.vue b/app/assets/javascripts/monitoring/components/charts/time_series.vue
index 78fe575717..6a88c8a5ee 100644
--- a/app/assets/javascripts/monitoring/components/charts/time_series.vue
+++ b/app/assets/javascripts/monitoring/components/charts/time_series.vue
@@ -1,17 +1,23 @@
-
+
diff --git a/app/assets/javascripts/monitoring/components/embed.vue b/app/assets/javascripts/monitoring/components/embed.vue
index 7857aaa6ec..f75839c7c6 100644
--- a/app/assets/javascripts/monitoring/components/embed.vue
+++ b/app/assets/javascripts/monitoring/components/embed.vue
@@ -35,9 +35,9 @@ export default {
};
},
computed: {
- ...mapState('monitoringDashboard', ['groups', 'metricsWithData']),
+ ...mapState('monitoringDashboard', ['dashboard', 'metricsWithData']),
charts() {
- const groupWithMetrics = this.groups.find(group =>
+ const groupWithMetrics = this.dashboard.panel_groups.find(group =>
group.metrics.find(chart => this.chartHasData(chart)),
) || { metrics: [] };
@@ -78,9 +78,6 @@ export default {
}, sidebarAnimationDuration);
},
setInitialState() {
- this.setFeatureFlags({
- prometheusEndpointEnabled: true,
- });
this.setEndpoints({
dashboardEndpoint: removeParams(['start', 'end'], this.dashboardUrl),
});
diff --git a/app/assets/javascripts/monitoring/components/graph_group.vue b/app/assets/javascripts/monitoring/components/graph_group.vue
index ee3a2bae79..3cb6ccb64b 100644
--- a/app/assets/javascripts/monitoring/components/graph_group.vue
+++ b/app/assets/javascripts/monitoring/components/graph_group.vue
@@ -45,7 +45,7 @@ export default {
diff --git a/app/assets/javascripts/monitoring/components/panel_type.vue b/app/assets/javascripts/monitoring/components/panel_type.vue
index 1a14d06f4c..cafb4b0b47 100644
--- a/app/assets/javascripts/monitoring/components/panel_type.vue
+++ b/app/assets/javascripts/monitoring/components/panel_type.vue
@@ -11,7 +11,9 @@ import {
} from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
import MonitorTimeSeriesChart from './charts/time_series.vue';
+import MonitorAnomalyChart from './charts/anomaly.vue';
import MonitorSingleStatChart from './charts/single_stat.vue';
+import MonitorHeatmapChart from './charts/heatmap.vue';
import MonitorEmptyChart from './charts/empty_chart.vue';
import TrackEventDirective from '~/vue_shared/directives/track_event';
import { downloadCSVOptions, generateLinkToChartOptions } from '../utils';
@@ -19,7 +21,7 @@ import { downloadCSVOptions, generateLinkToChartOptions } from '../utils';
export default {
components: {
MonitorSingleStatChart,
- MonitorTimeSeriesChart,
+ MonitorHeatmapChart,
MonitorEmptyChart,
Icon,
GlDropdown,
@@ -40,10 +42,6 @@ export default {
type: Object,
required: true,
},
- dashboardWidth: {
- type: Number,
- required: true,
- },
index: {
type: String,
required: false,
@@ -71,6 +69,12 @@ export default {
const data = new Blob([this.csvText], { type: 'text/plain' });
return window.URL.createObjectURL(data);
},
+ monitorChartComponent() {
+ if (this.isPanelType('anomaly-chart')) {
+ return MonitorAnomalyChart;
+ }
+ return MonitorTimeSeriesChart;
+ },
},
methods: {
getGraphAlerts(queries) {
@@ -97,14 +101,19 @@ export default {
v-if="isPanelType('single-stat') && graphDataHasMetrics"
:graph-data="graphData"
/>
-
+
-
+
diff --git a/app/assets/javascripts/monitoring/components/shared/prometheus_header.vue b/app/assets/javascripts/monitoring/components/shared/prometheus_header.vue
new file mode 100644
index 0000000000..153c8f389d
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/shared/prometheus_header.vue
@@ -0,0 +1,15 @@
+
+
+
+
diff --git a/app/assets/javascripts/monitoring/constants.js b/app/assets/javascripts/monitoring/constants.js
index 2836fe4fc2..1a1fcdd0e6 100644
--- a/app/assets/javascripts/monitoring/constants.js
+++ b/app/assets/javascripts/monitoring/constants.js
@@ -14,13 +14,28 @@ export const graphTypes = {
};
export const symbolSizes = {
+ anomaly: 8,
default: 14,
};
+export const areaOpacityValues = {
+ default: 0.2,
+};
+
+export const colorValues = {
+ primaryColor: '#1f78d1', // $blue-500 (see variables.scss)
+ anomalySymbol: '#db3b21',
+ anomalyAreaColor: '#1f78d1',
+};
+
export const lineTypes = {
default: 'solid',
};
+export const lineWidths = {
+ default: 2,
+};
+
export const timeWindows = {
thirtyMinutes: __('30 minutes'),
threeHours: __('3 hours'),
diff --git a/app/assets/javascripts/monitoring/monitoring_bundle.js b/app/assets/javascripts/monitoring/monitoring_bundle.js
index 6aa1fb5e9c..a14145d480 100644
--- a/app/assets/javascripts/monitoring/monitoring_bundle.js
+++ b/app/assets/javascripts/monitoring/monitoring_bundle.js
@@ -11,13 +11,6 @@ export default (props = {}) => {
const el = document.getElementById('prometheus-graphs');
if (el && el.dataset) {
- if (gon.features) {
- store.dispatch('monitoringDashboard/setFeatureFlags', {
- prometheusEndpointEnabled: gon.features.environmentMetricsUsePrometheusEndpoint,
- additionalPanelTypesEnabled: gon.features.environmentMetricsAdditionalPanelTypes,
- });
- }
-
const [currentDashboard] = getParameterValues('dashboard');
// eslint-disable-next-line no-new
diff --git a/app/assets/javascripts/monitoring/stores/actions.js b/app/assets/javascripts/monitoring/stores/actions.js
index 2cf34ddb45..6a8e3cc82f 100644
--- a/app/assets/javascripts/monitoring/stores/actions.js
+++ b/app/assets/javascripts/monitoring/stores/actions.js
@@ -7,7 +7,7 @@ import { s__, __ } from '../../locale';
const MAX_REQUESTS = 3;
-function backOffRequest(makeRequestCallback) {
+export function backOffRequest(makeRequestCallback) {
let requestCounter = 0;
return backOff((next, stop) => {
makeRequestCallback()
@@ -35,14 +35,6 @@ export const setEndpoints = ({ commit }, endpoints) => {
commit(types.SET_ENDPOINTS, endpoints);
};
-export const setFeatureFlags = (
- { commit },
- { prometheusEndpointEnabled, additionalPanelTypesEnabled },
-) => {
- commit(types.SET_DASHBOARD_ENABLED, prometheusEndpointEnabled);
- commit(types.SET_ADDITIONAL_PANEL_TYPES_ENABLED, additionalPanelTypesEnabled);
-};
-
export const setShowErrorBanner = ({ commit }, enabled) => {
commit(types.SET_SHOW_ERROR_BANNER, enabled);
};
@@ -79,29 +71,7 @@ export const fetchData = ({ dispatch }, params) => {
dispatch('fetchEnvironmentsData');
};
-export const fetchMetricsData = ({ state, dispatch }, params) => {
- if (state.useDashboardEndpoint) {
- return dispatch('fetchDashboard', params);
- }
-
- dispatch('requestMetricsData');
-
- return backOffRequest(() => axios.get(state.metricsEndpoint, { params }))
- .then(resp => resp.data)
- .then(response => {
- if (!response || !response.data || !response.success) {
- dispatch('receiveMetricsDataFailure', null);
- createFlash(s__('Metrics|Unexpected metrics data response from prometheus endpoint'));
- }
- dispatch('receiveMetricsDataSuccess', response.data);
- })
- .catch(error => {
- dispatch('receiveMetricsDataFailure', error);
- if (state.setShowErrorBanner) {
- createFlash(s__('Metrics|There was an error while retrieving metrics'));
- }
- });
-};
+export const fetchMetricsData = ({ dispatch }, params) => dispatch('fetchDashboard', params);
export const fetchDashboard = ({ state, dispatch }, params) => {
dispatch('requestMetricsDashboard');
@@ -111,11 +81,13 @@ export const fetchDashboard = ({ state, dispatch }, params) => {
params.dashboard = state.currentDashboard;
}
- return axios
- .get(state.dashboardEndpoint, { params })
+ return backOffRequest(() => axios.get(state.dashboardEndpoint, { params }))
.then(resp => resp.data)
.then(response => {
- dispatch('receiveMetricsDashboardSuccess', { response, params });
+ dispatch('receiveMetricsDashboardSuccess', {
+ response,
+ params,
+ });
})
.catch(error => {
dispatch('receiveMetricsDashboardFailure', error);
@@ -166,7 +138,7 @@ export const fetchPrometheusMetrics = ({ state, commit, dispatch }, params) => {
commit(types.REQUEST_METRICS_DATA);
const promises = [];
- state.groups.forEach(group => {
+ state.dashboard.panel_groups.forEach(group => {
group.panels.forEach(panel => {
panel.metrics.forEach(metric => {
promises.push(dispatch('fetchPrometheusMetric', { metric, params }));
@@ -221,5 +193,15 @@ export const fetchEnvironmentsData = ({ state, dispatch }) => {
});
};
+/**
+ * Set a new array of metrics to a panel group
+ * @param {*} data An object containing
+ * - `key` with a unique panel key
+ * - `metrics` with the metrics array
+ */
+export const setPanelGroupMetrics = ({ commit }, data) => {
+ commit(types.SET_PANEL_GROUP_METRICS, data);
+};
+
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/monitoring/stores/mutation_types.js b/app/assets/javascripts/monitoring/stores/mutation_types.js
index 9c546427c6..fa15a2ba80 100644
--- a/app/assets/javascripts/monitoring/stores/mutation_types.js
+++ b/app/assets/javascripts/monitoring/stores/mutation_types.js
@@ -9,10 +9,9 @@ export const RECEIVE_ENVIRONMENTS_DATA_SUCCESS = 'RECEIVE_ENVIRONMENTS_DATA_SUCC
export const RECEIVE_ENVIRONMENTS_DATA_FAILURE = 'RECEIVE_ENVIRONMENTS_DATA_FAILURE';
export const SET_QUERY_RESULT = 'SET_QUERY_RESULT';
export const SET_TIME_WINDOW = 'SET_TIME_WINDOW';
-export const SET_DASHBOARD_ENABLED = 'SET_DASHBOARD_ENABLED';
-export const SET_ADDITIONAL_PANEL_TYPES_ENABLED = 'SET_ADDITIONAL_PANEL_TYPES_ENABLED';
export const SET_ALL_DASHBOARDS = 'SET_ALL_DASHBOARDS';
export const SET_ENDPOINTS = 'SET_ENDPOINTS';
export const SET_GETTING_STARTED_EMPTY_STATE = 'SET_GETTING_STARTED_EMPTY_STATE';
export const SET_NO_DATA_EMPTY_STATE = 'SET_NO_DATA_EMPTY_STATE';
export const SET_SHOW_ERROR_BANNER = 'SET_SHOW_ERROR_BANNER';
+export const SET_PANEL_GROUP_METRICS = 'SET_PANEL_GROUP_METRICS';
diff --git a/app/assets/javascripts/monitoring/stores/mutations.js b/app/assets/javascripts/monitoring/stores/mutations.js
index 320b33d3d6..696af5aed7 100644
--- a/app/assets/javascripts/monitoring/stores/mutations.js
+++ b/app/assets/javascripts/monitoring/stores/mutations.js
@@ -1,6 +1,7 @@
import Vue from 'vue';
+import { slugify } from '~/lib/utils/text_utility';
import * as types from './mutation_types';
-import { normalizeMetrics, sortMetrics, normalizeMetric, normalizeQueryResult } from './utils';
+import { normalizeMetrics, normalizeMetric, normalizeQueryResult } from './utils';
const normalizePanel = panel => panel.metrics.map(normalizeMetric);
@@ -10,10 +11,12 @@ export default {
state.showEmptyState = true;
},
[types.RECEIVE_METRICS_DATA_SUCCESS](state, groupData) {
- state.groups = groupData.map(group => {
+ state.dashboard.panel_groups = groupData.map((group, i) => {
+ const key = `${slugify(group.group || 'default')}-${i}`;
let { metrics = [], panels = [] } = group;
// each panel has metric information that needs to be normalized
+
panels = panels.map(panel => ({
...panel,
metrics: normalizePanel(panel),
@@ -22,24 +25,21 @@ export default {
// for backwards compatibility, and to limit Vue template changes:
// for each group alias panels to metrics
// for each panel alias metrics to queries
- if (state.useDashboardEndpoint) {
- metrics = panels.map(panel => ({
- ...panel,
- queries: panel.metrics,
- }));
- }
+ metrics = panels.map(panel => ({
+ ...panel,
+ queries: panel.metrics,
+ }));
return {
...group,
panels,
- metrics: normalizeMetrics(sortMetrics(metrics)),
+ key,
+ metrics: normalizeMetrics(metrics),
};
});
- if (!state.groups.length) {
+ if (!state.dashboard.panel_groups.length) {
state.emptyState = 'noData';
- } else {
- state.showEmptyState = false;
}
},
[types.RECEIVE_METRICS_DATA_FAILURE](state, error) {
@@ -65,7 +65,7 @@ export default {
state.showEmptyState = false;
- state.groups.forEach(group => {
+ state.dashboard.panel_groups.forEach(group => {
group.metrics.forEach(metric => {
metric.queries.forEach(query => {
if (query.metric_id === metricId) {
@@ -86,9 +86,6 @@ export default {
state.currentDashboard = endpoints.currentDashboard;
state.projectPath = endpoints.projectPath;
},
- [types.SET_DASHBOARD_ENABLED](state, enabled) {
- state.useDashboardEndpoint = enabled;
- },
[types.SET_GETTING_STARTED_EMPTY_STATE](state) {
state.emptyState = 'gettingStarted';
},
@@ -97,12 +94,13 @@ export default {
state.emptyState = 'noData';
},
[types.SET_ALL_DASHBOARDS](state, dashboards) {
- state.allDashboards = dashboards;
- },
- [types.SET_ADDITIONAL_PANEL_TYPES_ENABLED](state, enabled) {
- state.additionalPanelTypesEnabled = enabled;
+ state.allDashboards = dashboards || [];
},
[types.SET_SHOW_ERROR_BANNER](state, enabled) {
state.showErrorBanner = enabled;
},
+ [types.SET_PANEL_GROUP_METRICS](state, payload) {
+ const panelGroup = state.dashboard.panel_groups.find(pg => payload.key === pg.key);
+ panelGroup.metrics = payload.metrics;
+ },
};
diff --git a/app/assets/javascripts/monitoring/stores/state.js b/app/assets/javascripts/monitoring/stores/state.js
index e894e988f6..87e9431117 100644
--- a/app/assets/javascripts/monitoring/stores/state.js
+++ b/app/assets/javascripts/monitoring/stores/state.js
@@ -7,12 +7,12 @@ export default () => ({
environmentsEndpoint: null,
deploymentsEndpoint: null,
dashboardEndpoint: invalidUrl,
- useDashboardEndpoint: false,
- additionalPanelTypesEnabled: false,
emptyState: 'gettingStarted',
showEmptyState: true,
showErrorBanner: true,
- groups: [],
+ dashboard: {
+ panel_groups: [],
+ },
deploymentData: [],
environments: [],
metricsWithData: [],
diff --git a/app/assets/javascripts/monitoring/stores/utils.js b/app/assets/javascripts/monitoring/stores/utils.js
index a19829f0c6..8a396b15a3 100644
--- a/app/assets/javascripts/monitoring/stores/utils.js
+++ b/app/assets/javascripts/monitoring/stores/utils.js
@@ -82,12 +82,6 @@ export const normalizeMetric = (metric = {}) =>
'id',
);
-export const sortMetrics = metrics =>
- _.chain(metrics)
- .sortBy('title')
- .sortBy('weight')
- .value();
-
export const normalizeQueryResult = timeSeries => {
let normalizedResult = {};
diff --git a/app/assets/javascripts/monitoring/utils.js b/app/assets/javascripts/monitoring/utils.js
index 00f188c1d5..2ae1647011 100644
--- a/app/assets/javascripts/monitoring/utils.js
+++ b/app/assets/javascripts/monitoring/utils.js
@@ -1,7 +1,6 @@
import dateformat from 'dateformat';
import { secondsIn, dateTimePickerRegex, dateFormats } from './constants';
-
-const secondsToMilliseconds = seconds => seconds * 1000;
+import { secondsToMilliseconds } from '~/lib/utils/datetime_utility';
export const getTimeDiff = timeWindow => {
const end = Math.floor(Date.now() / 1000); // convert milliseconds to seconds
@@ -131,4 +130,20 @@ export const downloadCSVOptions = title => {
return { category, action, label: 'Chart title', property: title };
};
+/**
+ * This function validates the graph data contains exactly 3 queries plus
+ * value validations from graphDataValidatorForValues.
+ * @param {Object} isValues
+ * @param {Object} graphData the graph data response from a prometheus request
+ * @returns {boolean} true if the data is valid
+ */
+export const graphDataValidatorForAnomalyValues = graphData => {
+ const anomalySeriesCount = 3; // metric, upper, lower
+ return (
+ graphData.queries &&
+ graphData.queries.length === anomalySeriesCount &&
+ graphDataValidatorForValues(false, graphData)
+ );
+};
+
export default {};
diff --git a/app/assets/javascripts/network/branch_graph.js b/app/assets/javascripts/network/branch_graph.js
index a0ba2193d9..c301c30440 100644
--- a/app/assets/javascripts/network/branch_graph.js
+++ b/app/assets/javascripts/network/branch_graph.js
@@ -1,12 +1,12 @@
-/* eslint-disable func-names, no-var, one-var, no-loop-func, consistent-return, camelcase */
+/* eslint-disable func-names, consistent-return, camelcase */
import $ from 'jquery';
import { __ } from '../locale';
import axios from '../lib/utils/axios_utils';
import Raphael from './raphael';
-export default (function() {
- function BranchGraph(element1, options1) {
+export default class BranchGraph {
+ constructor(element1, options1) {
this.element = element1;
this.options = options1;
this.scrollTop = this.scrollTop.bind(this);
@@ -28,7 +28,7 @@ export default (function() {
this.load();
}
- BranchGraph.prototype.load = function() {
+ load() {
axios
.get(this.options.url)
.then(({ data }) => {
@@ -37,21 +37,23 @@ export default (function() {
this.buildGraph();
})
.catch(() => __('Error fetching network graph.'));
- };
+ }
- BranchGraph.prototype.prepareData = function(days, commits) {
- var c, ch, cw, j, len, ref;
+ prepareData(days, commits) {
+ let c = 0;
+ let j = 0;
+ let len = 0;
this.days = days;
this.commits = commits;
this.collectParents();
this.graphHeight = $(this.element).height();
this.graphWidth = $(this.element).width();
- ch = Math.max(this.graphHeight, this.offsetY + this.unitTime * this.mtime + 150);
- cw = Math.max(this.graphWidth, this.offsetX + this.unitSpace * this.mspace + 300);
+ const ch = Math.max(this.graphHeight, this.offsetY + this.unitTime * this.mtime + 150);
+ const cw = Math.max(this.graphWidth, this.offsetX + this.unitSpace * this.mspace + 300);
this.r = Raphael(this.element.get(0), cw, ch);
this.top = this.r.set();
this.barHeight = Math.max(this.graphHeight, this.unitTime * this.days.length + 320);
- ref = this.commits;
+ const ref = this.commits;
for (j = 0, len = ref.length; j < len; j += 1) {
c = ref[j];
if (c.id in this.parents) {
@@ -61,37 +63,34 @@ export default (function() {
this.markCommit(c);
}
return this.collectColors();
- };
+ }
- BranchGraph.prototype.collectParents = function() {
- var c, j, len, p, ref, results;
- ref = this.commits;
- results = [];
+ collectParents() {
+ let j = 0;
+ let l = 0;
+ let len = 0;
+ let len1 = 0;
+ const ref = this.commits;
+ const results = [];
for (j = 0, len = ref.length; j < len; j += 1) {
- c = ref[j];
+ const c = ref[j];
this.mtime = Math.max(this.mtime, c.time);
this.mspace = Math.max(this.mspace, c.space);
- results.push(
- function() {
- var l, len1, ref1, results1;
- ref1 = c.parents;
- results1 = [];
- for (l = 0, len1 = ref1.length; l < len1; l += 1) {
- p = ref1[l];
- this.parents[p[0]] = true;
- results1.push((this.mspace = Math.max(this.mspace, p[1])));
- }
- return results1;
- }.call(this),
- );
+ const ref1 = c.parents;
+ const results1 = [];
+ for (l = 0, len1 = ref1.length; l < len1; l += 1) {
+ const p = ref1[l];
+ this.parents[p[0]] = true;
+ results1.push((this.mspace = Math.max(this.mspace, p[1])));
+ }
+ results.push(results1);
}
return results;
- };
+ }
- BranchGraph.prototype.collectColors = function() {
- var k, results;
- k = 0;
- results = [];
+ collectColors() {
+ let k = 0;
+ const results = [];
while (k < this.mspace) {
this.colors.push(Raphael.getColor(0.8));
// Skipping a few colors in the spectrum to get more contrast between colors
@@ -100,23 +99,24 @@ export default (function() {
results.push((k += 1));
}
return results;
- };
+ }
- BranchGraph.prototype.buildGraph = function() {
- var cuday, cumonth, day, len, mm, ref;
+ buildGraph() {
+ let mm = 0;
+ let len = 0;
+ let cuday = 0;
+ let cumonth = '';
const { r } = this;
- cuday = 0;
- cumonth = '';
r.rect(0, 0, 40, this.barHeight).attr({
fill: '#222',
});
r.rect(40, 0, 30, this.barHeight).attr({
fill: '#444',
});
- ref = this.days;
+ const ref = this.days;
for (mm = 0, len = ref.length; mm < len; mm += 1) {
- day = ref[mm];
+ const day = ref[mm];
if (cuday !== day[0] || cumonth !== day[1]) {
// Dates
r.text(55, this.offsetY + this.unitTime * mm, day[0]).attr({
@@ -138,29 +138,28 @@ export default (function() {
}
this.renderPartialGraph();
return this.bindEvents();
- };
+ }
- BranchGraph.prototype.renderPartialGraph = function() {
- var commit, end, i, isGraphEdge, start, x, y;
- start = Math.floor((this.element.scrollTop() - this.offsetY) / this.unitTime) - 10;
+ renderPartialGraph() {
+ const isGraphEdge = true;
+ let i = 0;
+ let start = Math.floor((this.element.scrollTop() - this.offsetY) / this.unitTime) - 10;
if (start < 0) {
- isGraphEdge = true;
start = 0;
}
- end = start + 40;
+ let end = start + 40;
if (this.commits.length < end) {
- isGraphEdge = true;
end = this.commits.length;
}
if (this.prev_start === -1 || Math.abs(this.prev_start - start) > 10 || isGraphEdge) {
i = start;
this.prev_start = start;
while (i < end) {
- commit = this.commits[i];
+ const commit = this.commits[i];
i += 1;
if (commit.hasDrawn !== true) {
- x = this.offsetX + this.unitSpace * (this.mspace - commit.space);
- y = this.offsetY + this.unitTime * commit.time;
+ const x = this.offsetX + this.unitSpace * (this.mspace - commit.space);
+ const y = this.offsetY + this.unitTime * commit.time;
this.drawDot(x, y, commit);
this.drawLines(x, y, commit);
this.appendLabel(x, y, commit);
@@ -170,70 +169,62 @@ export default (function() {
}
return this.top.toFront();
}
- };
+ }
- BranchGraph.prototype.bindEvents = function() {
+ bindEvents() {
const { element } = this;
- return $(element).scroll(
- (function(_this) {
- return function() {
- return _this.renderPartialGraph();
- };
- })(this),
- );
- };
+ return $(element).scroll(() => this.renderPartialGraph());
+ }
- BranchGraph.prototype.scrollDown = function() {
+ scrollDown() {
this.element.scrollTop(this.element.scrollTop() + 50);
return this.renderPartialGraph();
- };
+ }
- BranchGraph.prototype.scrollUp = function() {
+ scrollUp() {
this.element.scrollTop(this.element.scrollTop() - 50);
return this.renderPartialGraph();
- };
+ }
- BranchGraph.prototype.scrollLeft = function() {
+ scrollLeft() {
this.element.scrollLeft(this.element.scrollLeft() - 50);
return this.renderPartialGraph();
- };
+ }
- BranchGraph.prototype.scrollRight = function() {
+ scrollRight() {
this.element.scrollLeft(this.element.scrollLeft() + 50);
return this.renderPartialGraph();
- };
+ }
- BranchGraph.prototype.scrollBottom = function() {
+ scrollBottom() {
return this.element.scrollTop(this.element.find('svg').height());
- };
+ }
- BranchGraph.prototype.scrollTop = function() {
+ scrollTop() {
return this.element.scrollTop(0);
- };
-
- BranchGraph.prototype.appendLabel = function(x, y, commit) {
- var label, rect, shortrefs, text, textbox;
+ }
+ appendLabel(x, y, commit) {
if (!commit.refs) {
return;
}
const { r } = this;
- shortrefs = commit.refs;
+ let shortrefs = commit.refs;
// Truncate if longer than 15 chars
if (shortrefs.length > 17) {
shortrefs = `${shortrefs.substr(0, 15)}…`;
}
- text = r.text(x + 4, y, shortrefs).attr({
+ const text = r.text(x + 4, y, shortrefs).attr({
'text-anchor': 'start',
font: '10px Monaco, monospace',
fill: '#FFF',
title: commit.refs,
});
- textbox = text.getBBox();
+ const textbox = text.getBBox();
// Create rectangle based on the size of the textbox
- rect = r.rect(x, y - 7, textbox.width + 5, textbox.height + 5, 4).attr({
+ const rect = r.rect(x, y - 7, textbox.width + 5, textbox.height + 5, 4).attr({
fill: '#000',
'fill-opacity': 0.5,
stroke: 'none',
@@ -244,13 +235,13 @@ export default (function() {
'fill-opacity': 0.5,
stroke: 'none',
});
- label = r.set(rect, text);
+ const label = r.set(rect, text);
label.transform(['t', -rect.getBBox().width - 15, 0]);
// Set text to front
return text.toFront();
- };
+ }
- BranchGraph.prototype.appendAnchor = function(x, y, commit) {
+ appendAnchor(x, y, commit) {
const { r, top, options } = this;
const anchor = r
.circle(x, y, 10)
@@ -270,9 +261,9 @@ export default (function() {
},
);
return top.push(anchor);
- };
+ }
- BranchGraph.prototype.drawDot = function(x, y, commit) {
+ drawDot(x, y, commit) {
const { r } = this;
r.circle(x, y, 3).attr({
fill: this.colors[commit.space],
@@ -293,20 +284,24 @@ export default (function() {
'text-anchor': 'start',
font: '14px Monaco, monospace',
});
- };
+ }
- BranchGraph.prototype.drawLines = function(x, y, commit) {
- var arrow, color, i, len, offset, parent, parentCommit, parentX1, parentX2, parentY, route;
+ drawLines(x, y, commit) {
+ let i = 0;
+ let len = 0;
+ let arrow = '';
+ let offset = [];
+ let color = [];
const { r } = this;
const ref = commit.parents;
const results = [];
for (i = 0, len = ref.length; i < len; i += 1) {
- parent = ref[i];
- parentCommit = this.preparedCommits[parent[0]];
- parentY = this.offsetY + this.unitTime * parentCommit.time;
- parentX1 = this.offsetX + this.unitSpace * (this.mspace - parentCommit.space);
- parentX2 = this.offsetX + this.unitSpace * (this.mspace - parent[1]);
+ const parent = ref[i];
+ const parentCommit = this.preparedCommits[parent[0]];
+ const parentY = this.offsetY + this.unitTime * parentCommit.time;
+ const parentX1 = this.offsetX + this.unitSpace * (this.mspace - parentCommit.space);
+ const parentX2 = this.offsetX + this.unitSpace * (this.mspace - parent[1]);
// Set line color
if (parentCommit.space <= commit.space) {
color = this.colors[commit.space];
@@ -325,7 +320,7 @@ export default (function() {
arrow = 'l-5,0,2,4,3,-4,-4,2';
}
// Start point
- route = ['M', x + offset[0], y + offset[1]];
+ const route = ['M', x + offset[0], y + offset[1]];
// Add arrow if not first parent
if (i > 0) {
route.push(arrow);
@@ -344,9 +339,9 @@ export default (function() {
);
}
return results;
- };
+ }
- BranchGraph.prototype.markCommit = function(commit) {
+ markCommit(commit) {
if (commit.id === this.options.commit_id) {
const { r } = this;
const x = this.offsetX + this.unitSpace * (this.mspace - commit.space);
@@ -359,7 +354,5 @@ export default (function() {
// Displayed in the center
return this.element.scrollTop(y - this.graphHeight / 2);
}
- };
-
- return BranchGraph;
-})();
+ }
+}
diff --git a/app/assets/javascripts/new_branch_form.js b/app/assets/javascripts/new_branch_form.js
index 9f9db21d65..918c6e408a 100644
--- a/app/assets/javascripts/new_branch_form.js
+++ b/app/assets/javascripts/new_branch_form.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, no-var, one-var, consistent-return, no-return-assign, no-shadow, no-else-return, @gitlab/i18n/no-non-i18n-strings */
+/* eslint-disable func-names, consistent-return, no-return-assign, no-else-return, @gitlab/i18n/no-non-i18n-strings */
import $ from 'jquery';
import RefSelectDropdown from './ref_select_dropdown';
@@ -26,23 +26,22 @@ export default class NewBranchForm {
}
setupRestrictions() {
- var endsWith, invalid, single, startsWith;
- startsWith = {
+ const startsWith = {
pattern: /^(\/|\.)/g,
prefix: "can't start with",
conjunction: 'or',
};
- endsWith = {
+ const endsWith = {
pattern: /(\/|\.|\.lock)$/g,
prefix: "can't end in",
conjunction: 'or',
};
- invalid = {
+ const invalid = {
pattern: /(\s|~|\^|:|\?|\*|\[|\\|\.\.|@\{|\/{2,}){1}/g,
prefix: "can't contain",
conjunction: ', ',
};
- single = {
+ const single = {
pattern: /^@+$/g,
prefix: "can't be",
conjunction: 'or',
@@ -51,19 +50,17 @@ export default class NewBranchForm {
}
validate() {
- var errorMessage, errors, formatter, unique, validator;
const { indexOf } = [];
this.branchNameError.empty();
- unique = function(values, value) {
+ const unique = function(values, value) {
if (indexOf.call(values, value) === -1) {
values.push(value);
}
return values;
};
- formatter = function(values, restriction) {
- var formatted;
- formatted = values.map(value => {
+ const formatter = function(values, restriction) {
+ const formatted = values.map(value => {
switch (false) {
case !/\s/.test(value):
return 'spaces';
@@ -75,20 +72,17 @@ export default class NewBranchForm {
});
return `${restriction.prefix} ${formatted.join(restriction.conjunction)}`;
};
- validator = (function(_this) {
- return function(errors, restriction) {
- var matched;
- matched = _this.name.val().match(restriction.pattern);
- if (matched) {
- return errors.concat(formatter(matched.reduce(unique, []), restriction));
- } else {
- return errors;
- }
- };
- })(this);
- errors = this.restrictions.reduce(validator, []);
+ const validator = (errors, restriction) => {
+ const matched = this.name.val().match(restriction.pattern);
+ if (matched) {
+ return errors.concat(formatter(matched.reduce(unique, []), restriction));
+ } else {
+ return errors;
+ }
+ };
+ const errors = this.restrictions.reduce(validator, []);
if (errors.length > 0) {
- errorMessage = $('
').text(errors.join(', '));
+ const errorMessage = $('
').text(errors.join(', '));
return this.branchNameError.append(errorMessage);
}
}
diff --git a/app/assets/javascripts/new_commit_form.js b/app/assets/javascripts/new_commit_form.js
index b142f212eb..037be8467c 100644
--- a/app/assets/javascripts/new_commit_form.js
+++ b/app/assets/javascripts/new_commit_form.js
@@ -1,4 +1,4 @@
-/* eslint-disable no-var, no-return-assign */
+/* eslint-disable no-return-assign */
export default class NewCommitForm {
constructor(form) {
this.form = form;
@@ -11,8 +11,7 @@ export default class NewCommitForm {
this.renderDestination();
}
renderDestination() {
- var different;
- different = this.branchName.val() !== this.originalBranch.val();
+ const different = this.branchName.val() !== this.originalBranch.val();
if (different) {
this.createMergeRequestContainer.show();
if (!this.wasDifferent) {
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index 3715a91d59..defa278c08 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -1,8 +1,8 @@
-/* eslint-disable no-restricted-properties, func-names, no-var, camelcase,
+/* eslint-disable no-restricted-properties, no-var, camelcase,
no-unused-expressions, one-var, default-case,
-consistent-return, no-alert, no-return-assign,
-no-param-reassign, no-else-return, vars-on-top,
-no-shadow, no-useless-escape, class-methods-use-this */
+consistent-return, no-alert, no-param-reassign, no-else-return,
+vars-on-top, no-shadow, no-useless-escape,
+class-methods-use-this */
/* global ResolveService */
@@ -281,14 +281,7 @@ export default class Notes {
if (Notes.interval) {
clearInterval(Notes.interval);
}
- return (Notes.interval = setInterval(
- (function(_this) {
- return function() {
- return _this.refresh();
- };
- })(this),
- this.pollingInterval,
- ));
+ Notes.interval = setInterval(() => this.refresh(), this.pollingInterval);
}
refresh() {
@@ -847,57 +840,52 @@ export default class Notes {
var noteElId, $note;
$note = $(e.currentTarget).closest('.note');
noteElId = $note.attr('id');
- $(`.note[id="${noteElId}"]`).each(
- (function() {
- // A same note appears in the "Discussion" and in the "Changes" tab, we have
- // to remove all. Using $('.note[id='noteId']') ensure we get all the notes,
- // where $('#noteId') would return only one.
- return function(i, el) {
- var $note, $notes;
- $note = $(el);
- $notes = $note.closest('.discussion-notes');
- const discussionId = $('.notes', $notes).data('discussionId');
+ $(`.note[id="${noteElId}"]`).each((i, el) => {
+ // A same note appears in the "Discussion" and in the "Changes" tab, we have
+ // to remove all. Using $('.note[id='noteId']') ensure we get all the notes,
+ // where $('#noteId') would return only one.
+ const $note = $(el);
+ const $notes = $note.closest('.discussion-notes');
+ const discussionId = $('.notes', $notes).data('discussionId');
- if (typeof gl.diffNotesCompileComponents !== 'undefined') {
- if (gl.diffNoteApps[noteElId]) {
- gl.diffNoteApps[noteElId].$destroy();
- }
+ if (typeof gl.diffNotesCompileComponents !== 'undefined') {
+ if (gl.diffNoteApps[noteElId]) {
+ gl.diffNoteApps[noteElId].$destroy();
+ }
+ }
+
+ $note.remove();
+
+ // check if this is the last note for this line
+ if ($notes.find('.note').length === 0) {
+ const notesTr = $notes.closest('tr');
+
+ // "Discussions" tab
+ $notes.closest('.timeline-entry').remove();
+
+ $(`.js-diff-avatars-${discussionId}`).trigger('remove.vue');
+
+ // The notes tr can contain multiple lists of notes, like on the parallel diff
+ // notesTr does not exist for image diffs
+ if (notesTr.find('.discussion-notes').length > 1 || notesTr.length === 0) {
+ const $diffFile = $notes.closest('.diff-file');
+ if ($diffFile.length > 0) {
+ const removeBadgeEvent = new CustomEvent('removeBadge.imageDiff', {
+ detail: {
+ // badgeNumber's start with 1 and index starts with 0
+ badgeNumber: $notes.index() + 1,
+ },
+ });
+
+ $diffFile[0].dispatchEvent(removeBadgeEvent);
}
- $note.remove();
-
- // check if this is the last note for this line
- if ($notes.find('.note').length === 0) {
- var notesTr = $notes.closest('tr');
-
- // "Discussions" tab
- $notes.closest('.timeline-entry').remove();
-
- $(`.js-diff-avatars-${discussionId}`).trigger('remove.vue');
-
- // The notes tr can contain multiple lists of notes, like on the parallel diff
- // notesTr does not exist for image diffs
- if (notesTr.find('.discussion-notes').length > 1 || notesTr.length === 0) {
- const $diffFile = $notes.closest('.diff-file');
- if ($diffFile.length > 0) {
- const removeBadgeEvent = new CustomEvent('removeBadge.imageDiff', {
- detail: {
- // badgeNumber's start with 1 and index starts with 0
- badgeNumber: $notes.index() + 1,
- },
- });
-
- $diffFile[0].dispatchEvent(removeBadgeEvent);
- }
-
- $notes.remove();
- } else if (notesTr.length > 0) {
- notesTr.remove();
- }
- }
- };
- })(this),
- );
+ $notes.remove();
+ } else if (notesTr.length > 0) {
+ notesTr.remove();
+ }
+ }
+ });
Notes.checkMergeRequestStatus();
return this.updateNotesCount(-1);
diff --git a/app/assets/javascripts/notes/components/diff_discussion_header.vue b/app/assets/javascripts/notes/components/diff_discussion_header.vue
new file mode 100644
index 0000000000..4c9075912e
--- /dev/null
+++ b/app/assets/javascripts/notes/components/diff_discussion_header.vue
@@ -0,0 +1,133 @@
+
+
+
+
+
diff --git a/app/assets/javascripts/notes/components/note_header.vue b/app/assets/javascripts/notes/components/note_header.vue
index 3158e086f6..e4f09492d9 100644
--- a/app/assets/javascripts/notes/components/note_header.vue
+++ b/app/assets/javascripts/notes/components/note_header.vue
@@ -101,6 +101,7 @@ export default {
+
-import _ from 'underscore';
import { mapActions, mapGetters } from 'vuex';
import { GlTooltipDirective } from '@gitlab/ui';
-import { truncateSha } from '~/lib/utils/text_utility';
-import { s__, __, sprintf } from '~/locale';
+import { s__, __ } from '~/locale';
import { clearDraft, getDiscussionReplyKey } from '~/lib/utils/autosave';
import icon from '~/vue_shared/components/icon.vue';
import diffLineNoteFormMixin from 'ee_else_ce/notes/mixins/diff_line_note_form';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import Flash from '../../flash';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
-import noteHeader from './note_header.vue';
+import diffDiscussionHeader from './diff_discussion_header.vue';
import noteSignedOutWidget from './note_signed_out_widget.vue';
-import noteEditedText from './note_edited_text.vue';
import noteForm from './note_form.vue';
import diffWithNote from './diff_with_note.vue';
import noteable from '../mixins/noteable';
@@ -27,9 +24,8 @@ export default {
components: {
icon,
userAvatarLink,
- noteHeader,
+ diffDiscussionHeader,
noteSignedOutWidget,
- noteEditedText,
noteForm,
DraftNote: () => import('ee_component/batch_comments/components/draft_note.vue'),
TimelineEntryItem,
@@ -92,9 +88,6 @@ export default {
currentUser() {
return this.getUserData;
},
- author() {
- return this.firstNote.author;
- },
autosaveKey() {
return getDiscussionReplyKey(this.firstNote.noteable_type, this.discussion.id);
},
@@ -104,27 +97,6 @@ export default {
firstNote() {
return this.discussion.notes.slice(0, 1)[0];
},
- lastUpdatedBy() {
- const { notes } = this.discussion;
-
- if (notes.length > 1) {
- return notes[notes.length - 1].author;
- }
-
- return null;
- },
- lastUpdatedAt() {
- const { notes } = this.discussion;
-
- if (notes.length > 1) {
- return notes[notes.length - 1].created_at;
- }
-
- return null;
- },
- resolvedText() {
- return this.discussion.resolved_by_push ? __('Automatically resolved') : __('Resolved');
- },
shouldShowJumpToNextDiscussion() {
return this.showJumpToNextDiscussion(this.discussionsByDiffOrder ? 'diff' : 'discussion');
},
@@ -150,40 +122,6 @@ export default {
shouldHideDiscussionBody() {
return this.shouldRenderDiffs && !this.isExpanded;
},
- actionText() {
- const linkStart = ``;
- const linkEnd = ' ';
-
- let { commit_id: commitId } = this.discussion;
- if (commitId) {
- commitId = `${truncateSha(commitId)} `;
- }
-
- const {
- for_commit: isForCommit,
- diff_discussion: isDiffDiscussion,
- active: isActive,
- } = this.discussion;
-
- let text = s__('MergeRequests|started a thread');
- if (isForCommit) {
- text = s__('MergeRequests|started a thread on commit %{linkStart}%{commitId}%{linkEnd}');
- } else if (isDiffDiscussion && commitId) {
- text = isActive
- ? s__('MergeRequests|started a thread on commit %{linkStart}%{commitId}%{linkEnd}')
- : s__(
- 'MergeRequests|started a thread on an outdated change in commit %{linkStart}%{commitId}%{linkEnd}',
- );
- } else if (isDiffDiscussion) {
- text = isActive
- ? s__('MergeRequests|started a thread on %{linkStart}the diff%{linkEnd}')
- : s__(
- 'MergeRequests|started a thread on %{linkStart}an old version of the diff%{linkEnd}',
- );
- }
-
- return sprintf(text, { commitId, linkStart, linkEnd }, false);
- },
diffLine() {
if (this.line) {
return this.line;
@@ -208,16 +146,11 @@ export default {
methods: {
...mapActions([
'saveNote',
- 'toggleDiscussion',
'removePlaceholderNotes',
'toggleResolveNote',
'expandDiscussion',
'removeConvertedDiscussion',
]),
- truncateSha,
- toggleDiscussionHandler() {
- this.toggleDiscussion({ discussionId: this.discussion.id });
- },
showReplyForm() {
this.isReplying = true;
},
@@ -311,43 +244,7 @@ export default {
class="discussion js-discussion-container"
data-qa-selector="discussion_content"
>
-
+
{
@@ -131,6 +133,7 @@ export default {
},
beforeDestroy() {
this.stopPolling();
+ window.removeEventListener('hashchange', this.handleHashChanged);
},
methods: {
...mapActions([
@@ -138,7 +141,6 @@ export default {
'fetchDiscussions',
'poll',
'toggleAward',
- 'scrollToNoteIfNeeded',
'setNotesData',
'setNoteableData',
'setUserData',
@@ -151,6 +153,13 @@ export default {
'convertToDiscussion',
'stopPolling',
]),
+ handleHashChanged() {
+ const noteId = this.checkLocationHash();
+
+ if (noteId) {
+ this.setTargetNoteHash(getLocationHash());
+ }
+ },
fetchNotes() {
if (this.isFetching) return null;
@@ -194,6 +203,8 @@ export default {
this.expandDiscussion({ discussionId: discussion.id });
}
}
+
+ return noteId;
},
startReplying(discussionId) {
return this.convertToDiscussion(discussionId)
diff --git a/app/assets/javascripts/notes/mixins/description_version_history.js b/app/assets/javascripts/notes/mixins/description_version_history.js
new file mode 100644
index 0000000000..12d80f3faa
--- /dev/null
+++ b/app/assets/javascripts/notes/mixins/description_version_history.js
@@ -0,0 +1,12 @@
+// Placeholder for GitLab FOSS
+// Actual implementation: ee/app/assets/javascripts/notes/mixins/description_version_history.js
+export default {
+ computed: {
+ canSeeDescriptionVersion() {},
+ shouldShowDescriptionVersion() {},
+ descriptionVersionToggleIcon() {},
+ },
+ methods: {
+ toggleDescriptionVersion() {},
+ },
+};
diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js
index 004035ea1d..82c291379e 100644
--- a/app/assets/javascripts/notes/stores/actions.js
+++ b/app/assets/javascripts/notes/stores/actions.js
@@ -12,6 +12,7 @@ import service from '../services/notes_service';
import loadAwardsHandler from '../../awards_handler';
import sidebarTimeTrackingEventHub from '../../sidebar/event_hub';
import { isInViewport, scrollToElement, isInMRPage } from '../../lib/utils/common_utils';
+import { mergeUrlParams } from '../../lib/utils/url_utility';
import mrWidgetEventHub from '../../vue_merge_request_widget/event_hub';
import { __ } from '~/locale';
import Api from '~/api';
@@ -475,5 +476,20 @@ export const convertToDiscussion = ({ commit }, noteId) =>
export const removeConvertedDiscussion = ({ commit }, noteId) =>
commit(types.REMOVE_CONVERTED_DISCUSSION, noteId);
+export const fetchDescriptionVersion = (_, { endpoint, startingVersion }) => {
+ let requestUrl = endpoint;
+
+ if (startingVersion) {
+ requestUrl = mergeUrlParams({ start_version_id: startingVersion }, requestUrl);
+ }
+
+ return axios
+ .get(requestUrl)
+ .then(res => res.data)
+ .catch(() => {
+ Flash(__('Something went wrong while fetching description changes. Please try again.'));
+ });
+};
+
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/notes/stores/collapse_utils.js b/app/assets/javascripts/notes/stores/collapse_utils.js
index bee6d4f032..3cdcc7a05b 100644
--- a/app/assets/javascripts/notes/stores/collapse_utils.js
+++ b/app/assets/javascripts/notes/stores/collapse_utils.js
@@ -1,34 +1,9 @@
-import { n__, s__, sprintf } from '~/locale';
import { DESCRIPTION_TYPE } from '../constants';
-/**
- * Changes the description from a note, returns 'changed the description n number of times'
- */
-export const changeDescriptionNote = (note, descriptionChangedTimes, timeDifferenceMinutes) => {
- const descriptionNote = Object.assign({}, note);
-
- descriptionNote.note_html = sprintf(
- s__(`MergeRequest|
- %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}`),
- {
- paragraphStart: '',
- paragraphEnd: '
',
- descriptionChangedTimes,
- timeDifferenceMinutes: n__('within %d minute ', 'within %d minutes ', timeDifferenceMinutes),
- },
- false,
- );
-
- descriptionNote.times_updated = descriptionChangedTimes;
-
- return descriptionNote;
-};
-
/**
* Checks the time difference between two notes from their 'created_at' dates
* returns an integer
*/
-
export const getTimeDifferenceMinutes = (noteBeggining, noteEnd) => {
const descriptionNoteBegin = new Date(noteBeggining.created_at);
const descriptionNoteEnd = new Date(noteEnd.created_at);
@@ -57,7 +32,6 @@ export const isDescriptionSystemNote = note => note.system && note.note === DESC
export const collapseSystemNotes = notes => {
let lastDescriptionSystemNote = null;
let lastDescriptionSystemNoteIndex = -1;
- let descriptionChangedTimes = 1;
return notes.slice(0).reduce((acc, currentNote) => {
const note = currentNote.notes[0];
@@ -70,32 +44,24 @@ export const collapseSystemNotes = notes => {
} else if (lastDescriptionSystemNote) {
const timeDifferenceMinutes = getTimeDifferenceMinutes(lastDescriptionSystemNote, note);
- // are they less than 10 minutes apart?
- if (timeDifferenceMinutes > 10) {
- // reset counter
- descriptionChangedTimes = 1;
+ // are they less than 10 minutes apart from the same user?
+ if (timeDifferenceMinutes > 10 || note.author.id !== lastDescriptionSystemNote.author.id) {
// update the previous system note
lastDescriptionSystemNote = note;
lastDescriptionSystemNoteIndex = acc.length;
} else {
- // increase counter
- descriptionChangedTimes += 1;
+ // set the first version to fetch grouped system note versions
+ note.start_description_version_id = lastDescriptionSystemNote.description_version_id;
// delete the previous one
acc.splice(lastDescriptionSystemNoteIndex, 1);
- // replace the text of the current system note with the collapsed note.
- currentNote.notes.splice(
- 0,
- 1,
- changeDescriptionNote(note, descriptionChangedTimes, timeDifferenceMinutes),
- );
-
// update the previous system note index
lastDescriptionSystemNoteIndex = acc.length;
}
}
}
+
acc.push(currentNote);
return acc;
}, []);
diff --git a/app/assets/javascripts/pages/admin/abuse_reports/index.js b/app/assets/javascripts/pages/admin/abuse_reports/index.js
index d76b1f174f..d97e24d9e0 100644
--- a/app/assets/javascripts/pages/admin/abuse_reports/index.js
+++ b/app/assets/javascripts/pages/admin/abuse_reports/index.js
@@ -1,3 +1,8 @@
+/* eslint-disable no-new */
import AbuseReports from './abuse_reports';
+import UsersSelect from '~/users_select';
-document.addEventListener('DOMContentLoaded', () => new AbuseReports());
+document.addEventListener('DOMContentLoaded', () => {
+ new AbuseReports();
+ new UsersSelect();
+});
diff --git a/app/assets/javascripts/pages/admin/clusters/index.js b/app/assets/javascripts/pages/admin/clusters/index.js
index 43992938d0..4d04c37caa 100644
--- a/app/assets/javascripts/pages/admin/clusters/index.js
+++ b/app/assets/javascripts/pages/admin/clusters/index.js
@@ -1,21 +1,5 @@
-import PersistentUserCallout from '~/persistent_user_callout';
-import initGkeDropdowns from '~/create_cluster/gke_cluster';
-
-function initGcpSignupCallout() {
- const callout = document.querySelector('.gcp-signup-offer');
- PersistentUserCallout.factory(callout);
-}
+import initCreateCluster from '~/create_cluster/init_create_cluster';
document.addEventListener('DOMContentLoaded', () => {
- const { page } = document.body.dataset;
- const newClusterViews = [
- 'admin:clusters:new',
- 'admin:clusters:create_gcp',
- 'admin:clusters:create_user',
- ];
-
- if (newClusterViews.indexOf(page) > -1) {
- initGcpSignupCallout();
- initGkeDropdowns();
- }
+ initCreateCluster(document, gon);
});
diff --git a/app/assets/javascripts/pages/groups/index.js b/app/assets/javascripts/pages/groups/index.js
index a33d242908..4d04c37caa 100644
--- a/app/assets/javascripts/pages/groups/index.js
+++ b/app/assets/javascripts/pages/groups/index.js
@@ -1,21 +1,5 @@
-import PersistentUserCallout from '~/persistent_user_callout';
-import initGkeDropdowns from '~/create_cluster/gke_cluster';
-
-function initGcpSignupCallout() {
- const callout = document.querySelector('.gcp-signup-offer');
- PersistentUserCallout.factory(callout);
-}
+import initCreateCluster from '~/create_cluster/init_create_cluster';
document.addEventListener('DOMContentLoaded', () => {
- const { page } = document.body.dataset;
- const newClusterViews = [
- 'groups:clusters:new',
- 'groups:clusters:create_gcp',
- 'groups:clusters:create_user',
- ];
-
- if (newClusterViews.indexOf(page) > -1) {
- initGcpSignupCallout();
- initGkeDropdowns();
- }
+ initCreateCluster(document, gon);
});
diff --git a/app/assets/javascripts/pages/groups/issues/index.js b/app/assets/javascripts/pages/groups/issues/index.js
index dcdee77a8a..090e1a2bc6 100644
--- a/app/assets/javascripts/pages/groups/issues/index.js
+++ b/app/assets/javascripts/pages/groups/issues/index.js
@@ -1,3 +1,4 @@
+import initIssuablesList from '~/issuables_list';
import projectSelect from '~/project_select';
import initFilteredSearch from '~/pages/search/init_filtered_search';
import issuableInitBulkUpdateSidebar from '~/issuable_init_bulk_update_sidebar';
@@ -11,6 +12,8 @@ document.addEventListener('DOMContentLoaded', () => {
IssuableFilteredSearchTokenKeys.addExtraTokensForIssues();
issuableInitBulkUpdateSidebar.init(ISSUE_BULK_UPDATE_PREFIX);
+ initIssuablesList();
+
initFilteredSearch({
page: FILTERED_SEARCH.ISSUES,
isGroupDecendent: true,
diff --git a/app/assets/javascripts/pages/groups/new/fetch_group_path_availability.js b/app/assets/javascripts/pages/groups/new/fetch_group_path_availability.js
new file mode 100644
index 0000000000..1d68ccd724
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/new/fetch_group_path_availability.js
@@ -0,0 +1,7 @@
+import axios from '~/lib/utils/axios_utils';
+
+const rootUrl = gon.relative_url_root;
+
+export default function fetchGroupPathAvailability(groupPath) {
+ return axios.get(`${rootUrl}/users/${groupPath}/suggests`);
+}
diff --git a/app/assets/javascripts/pages/groups/new/group_path_validator.js b/app/assets/javascripts/pages/groups/new/group_path_validator.js
new file mode 100644
index 0000000000..2021ad117e
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/new/group_path_validator.js
@@ -0,0 +1,91 @@
+import InputValidator from '~/validators/input_validator';
+
+import _ from 'underscore';
+import fetchGroupPathAvailability from './fetch_group_path_availability';
+import flash from '~/flash';
+import { __ } from '~/locale';
+
+const debounceTimeoutDuration = 1000;
+const invalidInputClass = 'gl-field-error-outline';
+const successInputClass = 'gl-field-success-outline';
+const successMessageSelector = '.validation-success';
+const pendingMessageSelector = '.validation-pending';
+const unavailableMessageSelector = '.validation-error';
+const suggestionsMessageSelector = '.gl-path-suggestions';
+
+export default class GroupPathValidator extends InputValidator {
+ constructor(opts = {}) {
+ super();
+
+ const container = opts.container || '';
+ const validateElements = document.querySelectorAll(`${container} .js-validate-group-path`);
+
+ this.debounceValidateInput = _.debounce(inputDomElement => {
+ GroupPathValidator.validateGroupPathInput(inputDomElement);
+ }, debounceTimeoutDuration);
+
+ validateElements.forEach(element =>
+ element.addEventListener('input', this.eventHandler.bind(this)),
+ );
+ }
+
+ eventHandler(event) {
+ const inputDomElement = event.target;
+
+ GroupPathValidator.resetInputState(inputDomElement);
+ this.debounceValidateInput(inputDomElement);
+ }
+
+ static validateGroupPathInput(inputDomElement) {
+ const groupPath = inputDomElement.value;
+
+ if (inputDomElement.checkValidity() && groupPath.length > 0) {
+ GroupPathValidator.setMessageVisibility(inputDomElement, pendingMessageSelector);
+
+ fetchGroupPathAvailability(groupPath)
+ .then(({ data }) => data)
+ .then(data => {
+ GroupPathValidator.setInputState(inputDomElement, !data.exists);
+ GroupPathValidator.setMessageVisibility(inputDomElement, pendingMessageSelector, false);
+ GroupPathValidator.setMessageVisibility(
+ inputDomElement,
+ data.exists ? unavailableMessageSelector : successMessageSelector,
+ );
+
+ if (data.exists) {
+ GroupPathValidator.showSuggestions(inputDomElement, data.suggests);
+ }
+ })
+ .catch(() => flash(__('An error occurred while validating group path')));
+ }
+ }
+
+ static showSuggestions(inputDomElement, suggestions) {
+ const messageElement = inputDomElement.parentElement.parentElement.querySelector(
+ suggestionsMessageSelector,
+ );
+ const textSuggestions = suggestions && suggestions.length > 0 ? suggestions.join(', ') : 'none';
+ messageElement.textContent = textSuggestions;
+ }
+
+ static setMessageVisibility(inputDomElement, messageSelector, isVisible = true) {
+ const messageElement = inputDomElement.parentElement.parentElement.querySelector(
+ messageSelector,
+ );
+ messageElement.classList.toggle('hide', !isVisible);
+ }
+
+ static setInputState(inputDomElement, success = true) {
+ inputDomElement.classList.toggle(successInputClass, success);
+ inputDomElement.classList.toggle(invalidInputClass, !success);
+ }
+
+ static resetInputState(inputDomElement) {
+ GroupPathValidator.setMessageVisibility(inputDomElement, successMessageSelector, false);
+ GroupPathValidator.setMessageVisibility(inputDomElement, unavailableMessageSelector, false);
+
+ if (inputDomElement.checkValidity()) {
+ inputDomElement.classList.remove(successInputClass, invalidInputClass);
+ }
+ }
+}
diff --git a/app/assets/javascripts/pages/groups/new/index.js b/app/assets/javascripts/pages/groups/new/index.js
index 57b53eb9e5..0710fefe70 100644
--- a/app/assets/javascripts/pages/groups/new/index.js
+++ b/app/assets/javascripts/pages/groups/new/index.js
@@ -1,8 +1,14 @@
+import $ from 'jquery';
import BindInOut from '~/behaviors/bind_in_out';
import Group from '~/group';
import initAvatarPicker from '~/avatar_picker';
+import GroupPathValidator from './group_path_validator';
document.addEventListener('DOMContentLoaded', () => {
+ const parentId = $('#group_parent_id');
+ if (!parentId.val()) {
+ new GroupPathValidator(); // eslint-disable-line no-new
+ }
BindInOut.initAll();
new Group(); // eslint-disable-line no-new
initAvatarPicker();
diff --git a/app/assets/javascripts/pages/projects/blob/show/index.js b/app/assets/javascripts/pages/projects/blob/show/index.js
index 84e5bb3c46..aee67899ca 100644
--- a/app/assets/javascripts/pages/projects/blob/show/index.js
+++ b/app/assets/javascripts/pages/projects/blob/show/index.js
@@ -3,6 +3,7 @@ import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_sta
import BlobViewer from '~/blob/viewer/index';
import initBlob from '~/pages/projects/init_blob';
import GpgBadges from '~/gpg_badges';
+import '~/sourcegraph/load';
document.addEventListener('DOMContentLoaded', () => {
new BlobViewer(); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/pages/projects/clusters/new/index.js b/app/assets/javascripts/pages/projects/clusters/new/index.js
deleted file mode 100644
index 14d5ab2155..0000000000
--- a/app/assets/javascripts/pages/projects/clusters/new/index.js
+++ /dev/null
@@ -1,13 +0,0 @@
-document.addEventListener('DOMContentLoaded', () => {
- if (gon.features.createEksClusters) {
- import(/* webpackChunkName: 'eks_cluster' */ '~/create_cluster/eks_cluster')
- .then(({ default: initCreateEKSCluster }) => {
- const el = document.querySelector('.js-create-eks-cluster-form-container');
-
- if (el) {
- initCreateEKSCluster(el);
- }
- })
- .catch(() => {});
- }
-});
diff --git a/app/assets/javascripts/pages/projects/clusters/show/index.js b/app/assets/javascripts/pages/projects/clusters/show/index.js
index f091c01fc9..397f9faf6f 100644
--- a/app/assets/javascripts/pages/projects/clusters/show/index.js
+++ b/app/assets/javascripts/pages/projects/clusters/show/index.js
@@ -1,5 +1,5 @@
import ClustersBundle from '~/clusters/clusters_bundle';
-import initGkeNamespace from '~/projects/gke_cluster_namespace';
+import initGkeNamespace from '~/create_cluster/gke_cluster_namespace';
document.addEventListener('DOMContentLoaded', () => {
new ClustersBundle(); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/pages/projects/commit/show/index.js b/app/assets/javascripts/pages/projects/commit/show/index.js
index 5aa4734244..0eb6f23183 100644
--- a/app/assets/javascripts/pages/projects/commit/show/index.js
+++ b/app/assets/javascripts/pages/projects/commit/show/index.js
@@ -9,6 +9,7 @@ import initNotes from '~/init_notes';
import initChangesDropdown from '~/init_changes_dropdown';
import initDiffNotes from '~/diff_notes/diff_notes_bundle';
import { fetchCommitMergeRequests } from '~/commit_merge_requests';
+import '~/sourcegraph/load';
document.addEventListener('DOMContentLoaded', () => {
const hasPerfBar = document.querySelector('.with-performance-bar');
diff --git a/app/assets/javascripts/pages/projects/error_tracking/details/index.js b/app/assets/javascripts/pages/projects/error_tracking/details/index.js
new file mode 100644
index 0000000000..25d1c744e1
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/error_tracking/details/index.js
@@ -0,0 +1,5 @@
+import ErrorTrackingDetails from '~/error_tracking/details';
+
+document.addEventListener('DOMContentLoaded', () => {
+ ErrorTrackingDetails();
+});
diff --git a/app/assets/javascripts/pages/projects/error_tracking/index.js b/app/assets/javascripts/pages/projects/error_tracking/index.js
deleted file mode 100644
index 5a8fe137e9..0000000000
--- a/app/assets/javascripts/pages/projects/error_tracking/index.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import ErrorTracking from '~/error_tracking';
-
-document.addEventListener('DOMContentLoaded', () => {
- ErrorTracking();
-});
diff --git a/app/assets/javascripts/pages/projects/error_tracking/index/index.js b/app/assets/javascripts/pages/projects/error_tracking/index/index.js
new file mode 100644
index 0000000000..ead81cd5d2
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/error_tracking/index/index.js
@@ -0,0 +1,5 @@
+import ErrorTrackingList from '~/error_tracking/list';
+
+document.addEventListener('DOMContentLoaded', () => {
+ ErrorTrackingList();
+});
diff --git a/app/assets/javascripts/pages/projects/graphs/show/index.js b/app/assets/javascripts/pages/projects/graphs/show/index.js
index f79c386b59..09d9c78c44 100644
--- a/app/assets/javascripts/pages/projects/graphs/show/index.js
+++ b/app/assets/javascripts/pages/projects/graphs/show/index.js
@@ -1,25 +1,3 @@
-import $ from 'jquery';
-import flash from '~/flash';
-import { __ } from '~/locale';
-import axios from '~/lib/utils/axios_utils';
-import ContributorsStatGraph from './stat_graph_contributors';
+import initContributorsGraphs from '~/contributors';
-document.addEventListener('DOMContentLoaded', () => {
- const url = document.querySelector('.js-graphs-show').dataset.projectGraphPath;
-
- axios
- .get(url)
- .then(({ data }) => {
- const graph = new ContributorsStatGraph();
- graph.init(data);
-
- $('#brush_change').change(() => {
- graph.change_date_header();
- graph.redraw_authors();
- });
-
- $('.stat-graph').fadeIn();
- $('.loading-graph').hide();
- })
- .catch(() => flash(__('Error fetching contributors data.')));
-});
+document.addEventListener('DOMContentLoaded', initContributorsGraphs);
diff --git a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js
deleted file mode 100644
index 5b873e6b90..0000000000
--- a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js
+++ /dev/null
@@ -1,140 +0,0 @@
-/* eslint-disable func-names, no-var, one-var, camelcase, no-param-reassign, no-return-assign */
-
-import $ from 'jquery';
-import _ from 'underscore';
-import { n__, s__, createDateTimeFormat, sprintf } from '~/locale';
-import {
- ContributorsGraph,
- ContributorsAuthorGraph,
- ContributorsMasterGraph,
-} from './stat_graph_contributors_graph';
-import ContributorsStatGraphUtil from './stat_graph_contributors_util';
-
-export default (function() {
- function ContributorsStatGraph() {
- this.dateFormat = createDateTimeFormat({ year: 'numeric', month: 'long', day: 'numeric' });
- }
-
- ContributorsStatGraph.prototype.init = function(log) {
- var author_commits, total_commits;
- this.parsed_log = ContributorsStatGraphUtil.parse_log(log);
- this.set_current_field('commits');
- total_commits = ContributorsStatGraphUtil.get_total_data(this.parsed_log, this.field);
- author_commits = ContributorsStatGraphUtil.get_author_data(this.parsed_log, this.field);
- this.add_master_graph(total_commits);
- this.add_authors_graph(author_commits);
- return this.change_date_header();
- };
-
- ContributorsStatGraph.prototype.add_master_graph = function(total_data) {
- this.master_graph = new ContributorsMasterGraph(total_data);
- return this.master_graph.draw();
- };
-
- ContributorsStatGraph.prototype.add_authors_graph = function(author_data) {
- var limited_author_data;
- this.authors = [];
- limited_author_data = author_data.slice(0, 100);
- return _.each(
- limited_author_data,
- (function(_this) {
- return function(d) {
- var author_graph, author_header;
- author_header = _this.create_author_header(d);
- $('.contributors-list').append(author_header);
-
- author_graph = new ContributorsAuthorGraph(d.dates);
- _this.authors[d.author_name] = author_graph;
- return author_graph.draw();
- };
- })(this),
- );
- };
-
- ContributorsStatGraph.prototype.format_author_commit_info = function(author) {
- var commits;
- commits = $(' ', {
- class: 'graph-author-commits-count',
- });
- commits.text(n__('%d commit', '%d commits', author.commits));
- return $(' ').append(commits);
- };
-
- ContributorsStatGraph.prototype.create_author_header = function(author) {
- var author_commit_info, author_commit_info_span, author_email, author_name, list_item;
- list_item = $(' ', {
- class: 'person',
- style: 'display: block;',
- });
- author_name = $(`${author.author_name} `);
- author_email = $(`${author.author_email}
`);
- author_commit_info_span = $(' ', {
- class: 'commits',
- });
- author_commit_info = this.format_author_commit_info(author);
- author_commit_info_span.html(author_commit_info);
- list_item.append(author_name);
- list_item.append(author_email);
- list_item.append(author_commit_info_span);
- return list_item;
- };
-
- ContributorsStatGraph.prototype.redraw_master = function() {
- var total_data;
- total_data = ContributorsStatGraphUtil.get_total_data(this.parsed_log, this.field);
- this.master_graph.set_data(total_data);
- return this.master_graph.redraw();
- };
-
- ContributorsStatGraph.prototype.redraw_authors = function() {
- $('ol').html('');
-
- const { x_domain } = ContributorsGraph.prototype;
- const author_commits = ContributorsStatGraphUtil.get_author_data(
- this.parsed_log,
- this.field,
- x_domain,
- );
-
- return _.each(
- author_commits,
- (function(_this) {
- return function(d) {
- _this.redraw_author_commit_info(d);
- if (_this.authors[d.author_name] != null) {
- $(_this.authors[d.author_name].list_item).appendTo('ol');
- _this.authors[d.author_name].set_data(d.dates);
- return _this.authors[d.author_name].redraw();
- }
- return '';
- };
- })(this),
- );
- };
-
- ContributorsStatGraph.prototype.set_current_field = function(field) {
- return (this.field = field);
- };
-
- ContributorsStatGraph.prototype.change_date_header = function() {
- const { x_domain } = ContributorsGraph.prototype;
- const formattedDateRange = sprintf(s__('ContributorsPage|%{startDate} – %{endDate}'), {
- startDate: this.dateFormat.format(new Date(x_domain[0])),
- endDate: this.dateFormat.format(new Date(x_domain[1])),
- });
- return $('#date_header').text(formattedDateRange);
- };
-
- ContributorsStatGraph.prototype.redraw_author_commit_info = function(author) {
- var author_commit_info, author_list_item, $author;
- $author = this.authors[author.author_name];
- if ($author != null) {
- author_list_item = $(this.authors[author.author_name].list_item);
- author_commit_info = this.format_author_commit_info(author);
- return author_list_item.find('span').html(author_commit_info);
- }
- return '';
- };
-
- return ContributorsStatGraph;
-})();
diff --git a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js
deleted file mode 100644
index 86794800f8..0000000000
--- a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js
+++ /dev/null
@@ -1,379 +0,0 @@
-/* eslint-disable func-names, no-restricted-syntax, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, no-return-assign, no-else-return, no-shadow */
-
-import $ from 'jquery';
-import _ from 'underscore';
-import { extent, max } from 'd3-array';
-import { select, event as d3Event } from 'd3-selection';
-import { scaleTime, scaleLinear } from 'd3-scale';
-import { axisLeft, axisBottom } from 'd3-axis';
-import { area } from 'd3-shape';
-import { brushX } from 'd3-brush';
-import { timeParse } from 'd3-time-format';
-import { dateTickFormat } from '~/lib/utils/tick_formats';
-
-const d3 = {
- extent,
- max,
- select,
- scaleTime,
- scaleLinear,
- axisLeft,
- axisBottom,
- area,
- brushX,
- timeParse,
-};
-
-const hasProp = {}.hasOwnProperty;
-const extend = function(child, parent) {
- for (const key in parent) {
- if (hasProp.call(parent, key)) child[key] = parent[key];
- }
- function ctor() {
- this.constructor = child;
- }
- ctor.prototype = parent.prototype;
- child.prototype = new ctor();
- child.__super__ = parent.prototype;
- return child;
-};
-
-export const ContributorsGraph = (function() {
- function ContributorsGraph() {}
-
- ContributorsGraph.prototype.MARGIN = {
- top: 20,
- right: 10,
- bottom: 30,
- left: 40,
- };
-
- ContributorsGraph.prototype.x_domain = null;
-
- ContributorsGraph.prototype.y_domain = null;
-
- ContributorsGraph.prototype.dates = [];
-
- ContributorsGraph.prototype.determine_width = function(baseWidth, $parentElement) {
- const parentPaddingWidth =
- parseFloat($parentElement.css('padding-left')) +
- parseFloat($parentElement.css('padding-right'));
- const marginWidth = this.MARGIN.left + this.MARGIN.right;
- return baseWidth - parentPaddingWidth - marginWidth;
- };
-
- ContributorsGraph.set_x_domain = function(data) {
- return (ContributorsGraph.prototype.x_domain = data);
- };
-
- ContributorsGraph.set_y_domain = function(data) {
- return (ContributorsGraph.prototype.y_domain = [
- 0,
- d3.max(data, d => (d.commits = d.commits || d.additions || d.deletions)),
- ]);
- };
-
- ContributorsGraph.init_x_domain = function(data) {
- return (ContributorsGraph.prototype.x_domain = d3.extent(data, d => d.date));
- };
-
- ContributorsGraph.init_y_domain = function(data) {
- return (ContributorsGraph.prototype.y_domain = [
- 0,
- d3.max(data, d => (d.commits = d.commits || d.additions || d.deletions)),
- ]);
- };
-
- ContributorsGraph.init_domain = function(data) {
- ContributorsGraph.init_x_domain(data);
- return ContributorsGraph.init_y_domain(data);
- };
-
- ContributorsGraph.set_dates = function(data) {
- return (ContributorsGraph.prototype.dates = data);
- };
-
- ContributorsGraph.prototype.set_x_domain = function() {
- return this.x.domain(this.x_domain);
- };
-
- ContributorsGraph.prototype.set_y_domain = function() {
- return this.y.domain(this.y_domain);
- };
-
- ContributorsGraph.prototype.set_domain = function() {
- this.set_x_domain();
- return this.set_y_domain();
- };
-
- ContributorsGraph.prototype.create_scale = function(width, height) {
- this.x = d3
- .scaleTime()
- .range([0, width])
- .clamp(true);
- return (this.y = d3
- .scaleLinear()
- .range([height, 0])
- .nice());
- };
-
- ContributorsGraph.prototype.draw_x_axis = function() {
- return this.svg
- .append('g')
- .attr('class', 'x axis')
- .attr('transform', `translate(0, ${this.height})`)
- .call(this.x_axis);
- };
-
- ContributorsGraph.prototype.draw_y_axis = function() {
- return this.svg
- .append('g')
- .attr('class', 'y axis')
- .call(this.y_axis);
- };
-
- ContributorsGraph.prototype.set_data = function(data) {
- return (this.data = data);
- };
-
- return ContributorsGraph;
-})();
-
-export const ContributorsMasterGraph = (function(superClass) {
- extend(ContributorsMasterGraph, superClass);
-
- function ContributorsMasterGraph(data1) {
- const $parentElement = $('#contributors-master');
-
- this.data = data1;
- this.update_content = this.update_content.bind(this);
- this.width = this.determine_width($('.js-graphs-show').width(), $parentElement);
- this.height = 200;
- this.x = null;
- this.y = null;
- this.x_axis = null;
- this.y_axis = null;
- this.area = null;
- this.svg = null;
- this.brush = null;
- this.x_max_domain = null;
- }
-
- ContributorsMasterGraph.prototype.process_dates = function(data) {
- const dates = this.get_dates(data);
- this.parse_dates(data);
- return ContributorsGraph.set_dates(dates);
- };
-
- ContributorsMasterGraph.prototype.get_dates = function(data) {
- return _.pluck(data, 'date');
- };
-
- ContributorsMasterGraph.prototype.parse_dates = function(data) {
- const parseDate = d3.timeParse('%Y-%m-%d');
- return data.forEach(d => (d.date = parseDate(d.date)));
- };
-
- ContributorsMasterGraph.prototype.create_scale = function() {
- return ContributorsMasterGraph.__super__.create_scale.call(this, this.width, this.height);
- };
-
- ContributorsMasterGraph.prototype.create_axes = function() {
- this.x_axis = d3
- .axisBottom()
- .scale(this.x)
- .tickFormat(dateTickFormat);
- return (this.y_axis = d3
- .axisLeft()
- .scale(this.y)
- .ticks(5));
- };
-
- ContributorsMasterGraph.prototype.create_svg = function() {
- this.svg = d3
- .select('#contributors-master')
- .append('svg')
- .attr('width', this.width + this.MARGIN.left + this.MARGIN.right)
- .attr('height', this.height + this.MARGIN.top + this.MARGIN.bottom)
- .attr('class', 'tint-box')
- .append('g')
- .attr('transform', `translate(${this.MARGIN.left},${this.MARGIN.top})`);
- return this.svg;
- };
-
- ContributorsMasterGraph.prototype.create_area = function(x, y) {
- return (this.area = d3
- .area()
- .x(d => x(d.date))
- .y0(this.height)
- .y1(d => {
- d.commits = d.commits || d.additions || d.deletions;
- return y(d.commits);
- }));
- };
-
- ContributorsMasterGraph.prototype.create_brush = function() {
- return (this.brush = d3
- .brushX(this.x)
- .extent([[this.x.range()[0], 0], [this.x.range()[1], this.height]])
- .on('end', this.update_content));
- };
-
- ContributorsMasterGraph.prototype.draw_path = function(data) {
- return this.svg
- .append('path')
- .datum(data)
- .attr('class', 'area')
- .attr('d', this.area);
- };
-
- ContributorsMasterGraph.prototype.add_brush = function() {
- return this.svg
- .append('g')
- .attr('class', 'selection')
- .call(this.brush)
- .selectAll('rect')
- .attr('height', this.height);
- };
-
- ContributorsMasterGraph.prototype.update_content = function() {
- // d3Event.selection replaces the function brush.empty() calls
- if (d3Event.selection != null) {
- ContributorsGraph.set_x_domain(d3Event.selection.map(this.x.invert));
- } else {
- ContributorsGraph.set_x_domain(this.x_max_domain);
- }
- return $('#brush_change').trigger('change');
- };
-
- ContributorsMasterGraph.prototype.draw = function() {
- this.process_dates(this.data);
- this.create_scale();
- this.create_axes();
- ContributorsGraph.init_domain(this.data);
- this.x_max_domain = this.x_domain;
- this.set_domain();
- this.create_area(this.x, this.y);
- this.create_svg();
- this.create_brush();
- this.draw_path(this.data);
- this.draw_x_axis();
- this.draw_y_axis();
- return this.add_brush();
- };
-
- ContributorsMasterGraph.prototype.redraw = function() {
- this.process_dates(this.data);
- ContributorsGraph.set_y_domain(this.data);
- this.set_y_domain();
- this.svg.select('path').datum(this.data);
- this.svg.select('path').attr('d', this.area);
- return this.svg.select('.y.axis').call(this.y_axis);
- };
-
- return ContributorsMasterGraph;
-})(ContributorsGraph);
-
-export const ContributorsAuthorGraph = (function(superClass) {
- extend(ContributorsAuthorGraph, superClass);
-
- function ContributorsAuthorGraph(data1) {
- const $parentElements = $('.person');
-
- this.data = data1;
- // Don't split graph size in half for mobile devices.
- if ($(window).width() < 790) {
- this.width = this.determine_width($('.js-graphs-show').width(), $parentElements);
- } else {
- this.width = this.determine_width($('.js-graphs-show').width() / 2, $parentElements);
- }
- this.height = 200;
- this.x = null;
- this.y = null;
- this.x_axis = null;
- this.y_axis = null;
- this.area = null;
- this.svg = null;
- this.list_item = null;
- }
-
- ContributorsAuthorGraph.prototype.create_scale = function() {
- return ContributorsAuthorGraph.__super__.create_scale.call(this, this.width, this.height);
- };
-
- ContributorsAuthorGraph.prototype.create_axes = function() {
- this.x_axis = d3
- .axisBottom()
- .scale(this.x)
- .ticks(8)
- .tickFormat(dateTickFormat);
- return (this.y_axis = d3
- .axisLeft()
- .scale(this.y)
- .ticks(5));
- };
-
- ContributorsAuthorGraph.prototype.create_area = function(x, y) {
- return (this.area = d3
- .area()
- .x(d => {
- const parseDate = d3.timeParse('%Y-%m-%d');
- return x(parseDate(d));
- })
- .y0(this.height)
- .y1(
- (function(_this) {
- return function(d) {
- if (_this.data[d] != null) {
- return y(_this.data[d]);
- } else {
- return y(0);
- }
- };
- })(this),
- ));
- };
-
- ContributorsAuthorGraph.prototype.create_svg = function() {
- const persons = document.querySelectorAll('.person');
- this.list_item = persons[persons.length - 1];
- this.svg = d3
- .select(this.list_item)
- .append('svg')
- .attr('width', this.width + this.MARGIN.left + this.MARGIN.right)
- .attr('height', this.height + this.MARGIN.top + this.MARGIN.bottom)
- .attr('class', 'spark')
- .append('g')
- .attr('transform', `translate(${this.MARGIN.left},${this.MARGIN.top})`);
- return this.svg;
- };
-
- ContributorsAuthorGraph.prototype.draw_path = function(data) {
- return this.svg
- .append('path')
- .datum(data)
- .attr('class', 'area-contributor')
- .attr('d', this.area);
- };
-
- ContributorsAuthorGraph.prototype.draw = function() {
- this.create_scale();
- this.create_axes();
- this.set_domain();
- this.create_area(this.x, this.y);
- this.create_svg();
- this.draw_path(this.dates);
- this.draw_x_axis();
- return this.draw_y_axis();
- };
-
- ContributorsAuthorGraph.prototype.redraw = function() {
- this.set_domain();
- this.svg.select('path').datum(this.dates);
- this.svg.select('path').attr('d', this.area);
- this.svg.select('.x.axis').call(this.x_axis);
- return this.svg.select('.y.axis').call(this.y_axis);
- };
-
- return ContributorsAuthorGraph;
-})(ContributorsGraph);
diff --git a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js
deleted file mode 100644
index a89a13fe37..0000000000
--- a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js
+++ /dev/null
@@ -1,143 +0,0 @@
-/* eslint-disable func-names, no-var, one-var, camelcase, no-param-reassign, no-return-assign, consistent-return, no-cond-assign, no-else-return */
-import _ from 'underscore';
-
-export default {
- parse_log(log) {
- var by_author, by_email, data, entry, i, len, total, normalized_email;
- total = {};
- by_author = {};
- by_email = {};
- for (i = 0, len = log.length; i < len; i += 1) {
- entry = log[i];
- if (total[entry.date] == null) {
- this.add_date(entry.date, total);
- }
- normalized_email = entry.author_email.toLowerCase();
- data = by_author[entry.author_name] || by_email[normalized_email];
- if (data == null) {
- data = this.add_author(entry, by_author, by_email);
- }
- if (!data[entry.date]) {
- this.add_date(entry.date, data);
- }
- this.store_data(entry, total[entry.date], data[entry.date]);
- }
- total = _.toArray(total);
- by_author = _.toArray(by_author);
- return {
- total,
- by_author,
- };
- },
- add_date(date, collection) {
- collection[date] = {};
- return (collection[date].date = date);
- },
- add_author(author, by_author, by_email) {
- var data, normalized_email;
- data = {};
- data.author_name = author.author_name;
- data.author_email = author.author_email;
- normalized_email = author.author_email.toLowerCase();
- by_author[author.author_name] = data;
- by_email[normalized_email] = data;
- return data;
- },
- store_data(entry, total, by_author) {
- this.store_commits(total, by_author);
- this.store_additions(entry, total, by_author);
- return this.store_deletions(entry, total, by_author);
- },
- store_commits(total, by_author) {
- this.add(total, 'commits', 1);
- return this.add(by_author, 'commits', 1);
- },
- add(collection, field, value) {
- if (collection[field] == null) {
- collection[field] = 0;
- }
- return (collection[field] += value);
- },
- store_additions(entry, total, by_author) {
- if (entry.additions == null) {
- entry.additions = 0;
- }
- this.add(total, 'additions', entry.additions);
- return this.add(by_author, 'additions', entry.additions);
- },
- store_deletions(entry, total, by_author) {
- if (entry.deletions == null) {
- entry.deletions = 0;
- }
- this.add(total, 'deletions', entry.deletions);
- return this.add(by_author, 'deletions', entry.deletions);
- },
- get_total_data(parsed_log, field) {
- var log, total_data;
- log = parsed_log.total;
- total_data = this.pick_field(log, field);
- return _.sortBy(total_data, d => d.date);
- },
- pick_field(log, field) {
- var total_data;
- total_data = [];
- _.each(log, d => total_data.push(_.pick(d, [field, 'date'])));
- return total_data;
- },
- get_author_data(parsed_log, field, date_range) {
- var author_data, log;
- if (date_range == null) {
- date_range = null;
- }
- log = parsed_log.by_author;
- author_data = [];
- _.each(
- log,
- (function(_this) {
- return function(log_entry) {
- var parsed_log_entry;
- parsed_log_entry = _this.parse_log_entry(log_entry, field, date_range);
- if (!_.isEmpty(parsed_log_entry.dates)) {
- return author_data.push(parsed_log_entry);
- }
- };
- })(this),
- );
- return _.sortBy(author_data, d => d[field]).reverse();
- },
- parse_log_entry(log_entry, field, date_range) {
- var parsed_entry;
- parsed_entry = {};
-
- parsed_entry.author_name = log_entry.author_name;
- parsed_entry.author_email = log_entry.author_email;
- parsed_entry.dates = {};
-
- parsed_entry.commits = 0;
- parsed_entry.additions = 0;
- parsed_entry.deletions = 0;
-
- _.each(
- _.omit(log_entry, 'author_name', 'author_email'),
- (function(_this) {
- return function(value) {
- if (_this.in_range(value.date, date_range)) {
- parsed_entry.dates[value.date] = value[field];
- parsed_entry.commits += value.commits;
- parsed_entry.additions += value.additions;
- return (parsed_entry.deletions += value.deletions);
- }
- };
- })(this),
- );
- return parsed_entry;
- },
- in_range(date, date_range) {
- var ref;
- if (date_range === null || (date_range[0] <= (ref = new Date(date)) && ref <= date_range[1])) {
- return true;
- } else {
- return false;
- }
- },
-};
diff --git a/app/assets/javascripts/pages/projects/index.js b/app/assets/javascripts/pages/projects/index.js
index 196798a907..190d0806c2 100644
--- a/app/assets/javascripts/pages/projects/index.js
+++ b/app/assets/javascripts/pages/projects/index.js
@@ -1,24 +1,9 @@
-import initGkeDropdowns from '~/create_cluster/gke_cluster';
-import initGkeNamespace from '~/projects/gke_cluster_namespace';
-import PersistentUserCallout from '../../persistent_user_callout';
import Project from './project';
import ShortcutsNavigation from '../../behaviors/shortcuts/shortcuts_navigation';
+import initCreateCluster from '~/create_cluster/init_create_cluster';
document.addEventListener('DOMContentLoaded', () => {
- const { page } = document.body.dataset;
- const newClusterViews = [
- 'projects:clusters:new',
- 'projects:clusters:create_gcp',
- 'projects:clusters:create_user',
- ];
-
- if (newClusterViews.indexOf(page) > -1) {
- const callout = document.querySelector('.gcp-signup-offer');
- PersistentUserCallout.factory(callout);
-
- initGkeDropdowns();
- initGkeNamespace();
- }
+ initCreateCluster(document, gon);
new Project(); // eslint-disable-line no-new
new ShortcutsNavigation(); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js
index fa1de1f13c..16034313af 100644
--- a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js
+++ b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js
@@ -5,6 +5,7 @@ import { handleLocationHash } from '~/lib/utils/common_utils';
import howToMerge from '~/how_to_merge';
import initPipelines from '~/commit/pipelines/pipelines_bundle';
import initVueIssuableSidebarApp from '~/issuable_sidebar/sidebar_bundle';
+import initSourcegraph from '~/sourcegraph';
import initWidget from '../../../vue_merge_request_widget';
export default function() {
@@ -19,4 +20,5 @@ export default function() {
handleLocationHash();
howToMerge();
initWidget();
+ initSourcegraph();
}
diff --git a/app/assets/javascripts/pages/projects/network/network.js b/app/assets/javascripts/pages/projects/network/network.js
index 43417fa970..5f2014f163 100644
--- a/app/assets/javascripts/pages/projects/network/network.js
+++ b/app/assets/javascripts/pages/projects/network/network.js
@@ -1,22 +1,19 @@
-/* eslint-disable func-names, no-var */
-
import $ from 'jquery';
import BranchGraph from '../../../network/branch_graph';
-export default (function() {
- function Network(opts) {
- var vph;
- $('#filter_ref').click(function() {
- return $(this)
- .closest('form')
- .submit();
- });
- this.branch_graph = new BranchGraph($('.network-graph'), opts);
- vph = $(window).height() - 250;
- $('.network-graph').css({
- height: `${vph}px`,
- });
+const vph = $(window).height() - 250;
+
+export default class Network {
+ constructor(opts) {
+ this.opts = opts;
+ this.filter_ref = $('#filter_ref');
+ this.network_graph = $('.network-graph');
+ this.filter_ref.click(() => this.submit());
+ this.branch_graph = new BranchGraph(this.network_graph, this.opts);
+ this.network_graph.css({ height: `${vph}px` });
}
- return Network;
-})();
+ submit() {
+ return this.filter_ref.closest('form').submit();
+ }
+}
diff --git a/app/assets/javascripts/pages/projects/pages_domains/form.js b/app/assets/javascripts/pages/projects/pages_domains/form.js
index cef8e92610..ae5368179b 100644
--- a/app/assets/javascripts/pages/projects/pages_domains/form.js
+++ b/app/assets/javascripts/pages/projects/pages_domains/form.js
@@ -1,17 +1,23 @@
import setupToggleButtons from '~/toggle_buttons';
+function updateVisibility(selector, isVisible) {
+ Array.from(document.querySelectorAll(selector)).forEach(el => {
+ if (isVisible) {
+ el.classList.remove('d-none');
+ } else {
+ el.classList.add('d-none');
+ }
+ });
+}
+
export default () => {
const toggleContainer = document.querySelector('.js-auto-ssl-toggle-container');
if (toggleContainer) {
const onToggleButtonClicked = isAutoSslEnabled => {
- Array.from(document.querySelectorAll('.js-shown-unless-auto-ssl')).forEach(el => {
- if (isAutoSslEnabled) {
- el.classList.add('d-none');
- } else {
- el.classList.remove('d-none');
- }
- });
+ updateVisibility('.js-shown-unless-auto-ssl', !isAutoSslEnabled);
+
+ updateVisibility('.js-shown-if-auto-ssl', isAutoSslEnabled);
Array.from(document.querySelectorAll('.js-enabled-unless-auto-ssl')).forEach(el => {
if (isAutoSslEnabled) {
diff --git a/app/assets/javascripts/pages/projects/pipelines/test_report/index.js b/app/assets/javascripts/pages/projects/pipelines/test_report/index.js
new file mode 100644
index 0000000000..7e69983c2e
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/pipelines/test_report/index.js
@@ -0,0 +1,2 @@
+// /test_report is an alias for show
+import '../show/index';
diff --git a/app/assets/javascripts/pages/projects/project.js b/app/assets/javascripts/pages/projects/project.js
index 435e870580..01acfca158 100644
--- a/app/assets/javascripts/pages/projects/project.js
+++ b/app/assets/javascripts/pages/projects/project.js
@@ -40,11 +40,6 @@ export default class Project {
$label.text(activeText);
});
- $('#modal-geo-info').data({
- cloneUrlSecondary: $this.attr('href'),
- cloneUrlPrimary: $this.data('primaryUrl') || '',
- });
-
if (mobileCloneField) {
mobileCloneField.dataset.clipboardText = url;
} else {
diff --git a/app/assets/javascripts/pages/projects/settings/operations/show/index.js b/app/assets/javascripts/pages/projects/settings/operations/show/index.js
index 98e1970597..a32c188909 100644
--- a/app/assets/javascripts/pages/projects/settings/operations/show/index.js
+++ b/app/assets/javascripts/pages/projects/settings/operations/show/index.js
@@ -1,9 +1,11 @@
import mountErrorTrackingForm from '~/error_tracking_settings';
import mountOperationSettings from '~/operation_settings';
+import mountGrafanaIntegration from '~/grafana_integration';
import initSettingsPanels from '~/settings_panels';
document.addEventListener('DOMContentLoaded', () => {
mountErrorTrackingForm();
mountOperationSettings();
+ mountGrafanaIntegration();
initSettingsPanels();
});
diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
index 89cac42aba..4802cc2ad2 100644
--- a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
+++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
@@ -1,7 +1,6 @@
+
+
+
+
+
diff --git a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
index 3b07eba02b..8ce653bf1f 100644
--- a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
+++ b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
@@ -1,12 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ s__('TestReports|There are no tests to show.') }}
+
+
+
+
diff --git a/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue b/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue
new file mode 100644
index 0000000000..28b2c70632
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ testCase.classname }}
+
+
+
+
+
{{ testCase.name }}
+
+
+
+
+
+
+
+
{{testCase.system_output}}
+
+
+
+
+
+
+ {{ testCase.formattedTime }}
+
+
+
+
+
+
+
{{ s__('TestReports|There are no test cases to display.') }}
+
+
+
diff --git a/app/assets/javascripts/pipelines/components/test_reports/test_summary.vue b/app/assets/javascripts/pipelines/components/test_reports/test_summary.vue
new file mode 100644
index 0000000000..dce8b020d6
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/test_reports/test_summary.vue
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+
+
+
{{ heading }}
+
+
+
+
+
+ {{
+ sprintf(s__('TestReports|%{count} jobs'), { count: report.total_count })
+ }}
+
+
+
+ {{
+ sprintf(s__('TestReports|%{count} failures'), { count: report.failed_count })
+ }}
+
+
+
+ {{
+ sprintf(s__('TestReports|%{count} errors'), { count: report.error_count })
+ }}
+
+
+
+ {{
+ sprintf(s__('TestReports|%{rate}%{sign} success rate'), {
+ rate: successPercentage,
+ sign: '%',
+ })
+ }}
+
+
+
+ {{ formattedDuration }}
+
+
+
+
+
+
diff --git a/app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue b/app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue
new file mode 100644
index 0000000000..96177512e3
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ testSuite.name }}
+
+
+
+
+
+
+ {{ testSuite.formattedTime }}
+
+
+
+
+
+
{{ testSuite.failed_count }}
+
+
+
+
+
{{ testSuite.error_count }}
+
+
+
+
+
{{ testSuite.skipped_count }}
+
+
+
+
+
{{ testSuite.success_count }}
+
+
+
+
+
{{ testSuite.total_count }}
+
+
+
+
+
+
{{ s__('TestReports|There are no test suites to show.') }}
+
+
+
diff --git a/app/assets/javascripts/pipelines/constants.js b/app/assets/javascripts/pipelines/constants.js
index d27829db50..c9655d18a0 100644
--- a/app/assets/javascripts/pipelines/constants.js
+++ b/app/assets/javascripts/pipelines/constants.js
@@ -1,3 +1,9 @@
export const CANCEL_REQUEST = 'CANCEL_REQUEST';
export const PIPELINES_TABLE = 'PIPELINES_TABLE';
export const LAYOUT_CHANGE_DELAY = 300;
+
+export const TestStatus = {
+ FAILED: 'failed',
+ SKIPPED: 'skipped',
+ SUCCESS: 'success',
+};
diff --git a/app/assets/javascripts/pipelines/pipeline_details_bundle.js b/app/assets/javascripts/pipelines/pipeline_details_bundle.js
index b6f8716d37..d8dbc3c245 100644
--- a/app/assets/javascripts/pipelines/pipeline_details_bundle.js
+++ b/app/assets/javascripts/pipelines/pipeline_details_bundle.js
@@ -7,6 +7,8 @@ import GraphBundleMixin from './mixins/graph_pipeline_bundle_mixin';
import PipelinesMediator from './pipeline_details_mediator';
import pipelineHeader from './components/header_component.vue';
import eventHub from './event_hub';
+import TestReports from './components/test_reports/test_reports.vue';
+import testReportsStore from './stores/test_reports';
Vue.use(Translate);
@@ -17,7 +19,7 @@ export default () => {
mediator.fetchPipeline();
- // eslint-disable-next-line
+ // eslint-disable-next-line no-new
new Vue({
el: '#js-pipeline-graph-vue',
components: {
@@ -47,7 +49,7 @@ export default () => {
},
});
- // eslint-disable-next-line
+ // eslint-disable-next-line no-new
new Vue({
el: '#js-pipeline-header-vue',
components: {
@@ -81,4 +83,23 @@ export default () => {
});
},
});
+
+ const testReportsEnabled =
+ window.gon && window.gon.features && window.gon.features.junitPipelineView;
+
+ if (testReportsEnabled) {
+ testReportsStore.dispatch('setEndpoint', dataset.testReportEndpoint);
+ testReportsStore.dispatch('fetchReports');
+
+ // eslint-disable-next-line no-new
+ new Vue({
+ el: '#js-pipeline-tests-detail',
+ components: {
+ TestReports,
+ },
+ render(createElement) {
+ return createElement('test-reports');
+ },
+ });
+ }
};
diff --git a/app/assets/javascripts/pipelines/stores/test_reports/actions.js b/app/assets/javascripts/pipelines/stores/test_reports/actions.js
new file mode 100644
index 0000000000..71d875c1a8
--- /dev/null
+++ b/app/assets/javascripts/pipelines/stores/test_reports/actions.js
@@ -0,0 +1,30 @@
+import axios from '~/lib/utils/axios_utils';
+import * as types from './mutation_types';
+import createFlash from '~/flash';
+import { s__ } from '~/locale';
+
+export const setEndpoint = ({ commit }, data) => commit(types.SET_ENDPOINT, data);
+
+export const fetchReports = ({ state, commit, dispatch }) => {
+ dispatch('toggleLoading');
+
+ return axios
+ .get(state.endpoint)
+ .then(response => {
+ const { data } = response;
+ commit(types.SET_REPORTS, data);
+ })
+ .catch(() => {
+ createFlash(s__('TestReports|There was an error fetching the test reports.'));
+ })
+ .finally(() => {
+ dispatch('toggleLoading');
+ });
+};
+
+export const setSelectedSuite = ({ commit }, data) => commit(types.SET_SELECTED_SUITE, data);
+export const removeSelectedSuite = ({ commit }) => commit(types.SET_SELECTED_SUITE, {});
+export const toggleLoading = ({ commit }) => commit(types.TOGGLE_LOADING);
+
+// prevent babel-plugin-rewire from generating an invalid default during karma tests
+export default () => {};
diff --git a/app/assets/javascripts/pipelines/stores/test_reports/getters.js b/app/assets/javascripts/pipelines/stores/test_reports/getters.js
new file mode 100644
index 0000000000..788c1d3298
--- /dev/null
+++ b/app/assets/javascripts/pipelines/stores/test_reports/getters.js
@@ -0,0 +1,23 @@
+import { addIconStatus, formattedTime, sortTestCases } from './utils';
+
+export const getTestSuites = state => {
+ const { test_suites: testSuites = [] } = state.testReports;
+
+ return testSuites.map(suite => ({
+ ...suite,
+ formattedTime: formattedTime(suite.total_time),
+ }));
+};
+
+export const getSuiteTests = state => {
+ const { selectedSuite } = state;
+
+ if (selectedSuite.test_cases) {
+ return selectedSuite.test_cases.sort(sortTestCases).map(addIconStatus);
+ }
+
+ return [];
+};
+
+// prevent babel-plugin-rewire from generating an invalid default during karma tests
+export default () => {};
diff --git a/app/assets/javascripts/pipelines/stores/test_reports/index.js b/app/assets/javascripts/pipelines/stores/test_reports/index.js
new file mode 100644
index 0000000000..318dff5bcb
--- /dev/null
+++ b/app/assets/javascripts/pipelines/stores/test_reports/index.js
@@ -0,0 +1,15 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import state from './state';
+import * as actions from './actions';
+import * as getters from './getters';
+import mutations from './mutations';
+
+Vue.use(Vuex);
+
+export default new Vuex.Store({
+ actions,
+ getters,
+ mutations,
+ state,
+});
diff --git a/app/assets/javascripts/pipelines/stores/test_reports/mutation_types.js b/app/assets/javascripts/pipelines/stores/test_reports/mutation_types.js
new file mode 100644
index 0000000000..832e45cf7a
--- /dev/null
+++ b/app/assets/javascripts/pipelines/stores/test_reports/mutation_types.js
@@ -0,0 +1,4 @@
+export const SET_ENDPOINT = 'SET_ENDPOINT';
+export const SET_REPORTS = 'SET_REPORTS';
+export const SET_SELECTED_SUITE = 'SET_SELECTED_SUITE';
+export const TOGGLE_LOADING = 'TOGGLE_LOADING';
diff --git a/app/assets/javascripts/pipelines/stores/test_reports/mutations.js b/app/assets/javascripts/pipelines/stores/test_reports/mutations.js
new file mode 100644
index 0000000000..349e6ec046
--- /dev/null
+++ b/app/assets/javascripts/pipelines/stores/test_reports/mutations.js
@@ -0,0 +1,19 @@
+import * as types from './mutation_types';
+
+export default {
+ [types.SET_ENDPOINT](state, endpoint) {
+ Object.assign(state, { endpoint });
+ },
+
+ [types.SET_REPORTS](state, testReports) {
+ Object.assign(state, { testReports });
+ },
+
+ [types.SET_SELECTED_SUITE](state, selectedSuite) {
+ Object.assign(state, { selectedSuite });
+ },
+
+ [types.TOGGLE_LOADING](state) {
+ Object.assign(state, { isLoading: !state.isLoading });
+ },
+};
diff --git a/app/assets/javascripts/pipelines/stores/test_reports/state.js b/app/assets/javascripts/pipelines/stores/test_reports/state.js
new file mode 100644
index 0000000000..80a0c2a46a
--- /dev/null
+++ b/app/assets/javascripts/pipelines/stores/test_reports/state.js
@@ -0,0 +1,6 @@
+export default () => ({
+ endpoint: '',
+ testReports: {},
+ selectedSuite: {},
+ isLoading: false,
+});
diff --git a/app/assets/javascripts/pipelines/stores/test_reports/utils.js b/app/assets/javascripts/pipelines/stores/test_reports/utils.js
new file mode 100644
index 0000000000..95466587d6
--- /dev/null
+++ b/app/assets/javascripts/pipelines/stores/test_reports/utils.js
@@ -0,0 +1,36 @@
+import { TestStatus } from '~/pipelines/constants';
+import { formatTime, secondsToMilliseconds } from '~/lib/utils/datetime_utility';
+
+function iconForTestStatus(status) {
+ switch (status) {
+ case 'success':
+ return 'status_success_borderless';
+ case 'failed':
+ return 'status_failed_borderless';
+ default:
+ return 'status_skipped_borderless';
+ }
+}
+
+export const formattedTime = timeInSeconds => formatTime(secondsToMilliseconds(timeInSeconds));
+
+export const addIconStatus = testCase => ({
+ ...testCase,
+ icon: iconForTestStatus(testCase.status),
+ formattedTime: formattedTime(testCase.execution_time),
+});
+
+export const sortTestCases = (a, b) => {
+ if (a.status === b.status) {
+ return 0;
+ }
+
+ switch (b.status) {
+ case TestStatus.SUCCESS:
+ return -1;
+ case TestStatus.FAILED:
+ return 1;
+ default:
+ return 0;
+ }
+};
diff --git a/app/assets/javascripts/privacy_policy_update_callout.js b/app/assets/javascripts/privacy_policy_update_callout.js
deleted file mode 100644
index 97f41deb30..0000000000
--- a/app/assets/javascripts/privacy_policy_update_callout.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import PersistentUserCallout from '~/persistent_user_callout';
-
-function initPrivacyPolicyUpdateCallout() {
- const callout = document.querySelector('.js-privacy-policy-update');
- PersistentUserCallout.factory(callout);
-}
-
-export default initPrivacyPolicyUpdateCallout;
diff --git a/app/assets/javascripts/profile/gl_crop.js b/app/assets/javascripts/profile/gl_crop.js
index 44bc2d9f5f..880e1a8897 100644
--- a/app/assets/javascripts/profile/gl_crop.js
+++ b/app/assets/javascripts/profile/gl_crop.js
@@ -1,4 +1,4 @@
-/* eslint-disable no-useless-escape, no-var, no-underscore-dangle, func-names, no-return-assign, one-var, consistent-return, class-methods-use-this */
+/* eslint-disable no-useless-escape, no-underscore-dangle, func-names, no-return-assign, consistent-return, class-methods-use-this */
import $ from 'jquery';
import 'cropper';
@@ -59,8 +59,7 @@ import _ from 'underscore';
}
bindEvents() {
- var _this;
- _this = this;
+ const _this = this;
this.fileInput.on('change', function(e) {
_this.onFileInputChange(e, this);
this.value = null;
@@ -70,8 +69,7 @@ import _ from 'underscore';
this.modalCrop.on('hidden.bs.modal', this.onModalHide);
this.uploadImageBtn.on('click', this.onUploadImageBtnClick);
this.cropActionsBtn.on('click', function() {
- var btn;
- btn = this;
+ const btn = this;
return _this.onActionBtnClick(btn);
});
return (this.croppedImageBlob = null);
@@ -82,8 +80,7 @@ import _ from 'underscore';
}
onModalShow() {
- var _this;
- _this = this;
+ const _this = this;
return this.modalCropImg.cropper({
viewMode: 1,
center: false,
@@ -128,8 +125,7 @@ import _ from 'underscore';
}
onActionBtnClick(btn) {
- var data;
- data = $(btn).data();
+ const data = $(btn).data();
if (this.modalCropImg.data('cropper') && data.method) {
return this.modalCropImg.cropper(data.method, data.option);
}
@@ -140,9 +136,8 @@ import _ from 'underscore';
}
readFile(input) {
- var _this, reader;
- _this = this;
- reader = new FileReader();
+ const _this = this;
+ const reader = new FileReader();
reader.onload = () => {
_this.modalCropImg.attr('src', reader.result);
return _this.modalCrop.modal('show');
@@ -151,9 +146,10 @@ import _ from 'underscore';
}
dataURLtoBlob(dataURL) {
- var array, binary, i, len;
- binary = atob(dataURL.split(',')[1]);
- array = [];
+ let i = 0;
+ let len = 0;
+ const binary = atob(dataURL.split(',')[1]);
+ const array = [];
for (i = 0, len = binary.length; i < len; i += 1) {
array.push(binary.charCodeAt(i));
@@ -164,9 +160,8 @@ import _ from 'underscore';
}
setPreview() {
- var filename;
+ const filename = this.fileInput.val().replace(FILENAMEREGEX, '');
this.previewImage.attr('src', this.dataURL);
- filename = this.fileInput.val().replace(FILENAMEREGEX, '');
return this.filename.text(filename);
}
diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/project_find_file.js
index 2c375b39c1..031c54d233 100644
--- a/app/assets/javascripts/project_find_file.js
+++ b/app/assets/javascripts/project_find_file.js
@@ -1,16 +1,20 @@
-/* eslint-disable func-names, no-var, consistent-return, one-var, no-cond-assign, no-return-assign */
+/* eslint-disable func-names, consistent-return, no-return-assign */
import $ from 'jquery';
import fuzzaldrinPlus from 'fuzzaldrin-plus';
import axios from '~/lib/utils/axios_utils';
import flash from '~/flash';
import { __ } from '~/locale';
+import sanitize from 'sanitize-html';
// highlight text(awefwbwgtc -> a wefwb wgtc )
const highlighter = function(element, text, matches) {
- var j, lastIndex, len, matchIndex, matchedChars, unmatched;
- lastIndex = 0;
- matchedChars = [];
+ let j = 0;
+ let len = 0;
+ let lastIndex = 0;
+ let matchedChars = [];
+ let matchIndex = matches[j];
+ let unmatched = text.substring(lastIndex, matchIndex);
for (j = 0, len = matches.length; j < len; j += 1) {
matchIndex = matches[j];
unmatched = text.substring(lastIndex, matchIndex);
@@ -54,10 +58,10 @@ export default class ProjectFindFile {
'keyup',
(function(_this) {
return function(event) {
- var oldValue, ref, target, value;
- target = $(event.target);
- value = target.val();
- oldValue = (ref = target.data('oldValue')) != null ? ref : '';
+ const target = $(event.target);
+ const value = target.val();
+ const ref = target.data('oldValue');
+ const oldValue = ref != null ? ref : '';
if (value !== oldValue) {
target.data('oldValue', value);
_this.findFile();
@@ -73,9 +77,8 @@ export default class ProjectFindFile {
}
findFile() {
- var result, searchText;
- searchText = this.inputElement.val();
- result =
+ const searchText = sanitize(this.inputElement.val());
+ const result =
searchText.length > 0 ? fuzzaldrinPlus.filter(this.filePaths, searchText) : this.filePaths;
return this.renderList(result, searchText);
// find file
@@ -100,20 +103,21 @@ export default class ProjectFindFile {
// render result
renderList(filePaths, searchText) {
- var blobItemUrl, filePath, html, i, len, matches, results;
+ let i = 0;
+ let len = 0;
+ let matches = [];
+ const results = [];
this.element.find('.tree-table > tbody').empty();
- results = [];
-
for (i = 0, len = filePaths.length; i < len; i += 1) {
- filePath = filePaths[i];
+ const filePath = filePaths[i];
if (i === 20) {
break;
}
if (searchText) {
matches = fuzzaldrinPlus.match(filePath, searchText);
}
- blobItemUrl = `${this.options.blobUrlTemplate}/${encodeURIComponent(filePath)}`;
- html = ProjectFindFile.makeHtml(filePath, matches, blobItemUrl);
+ const blobItemUrl = `${this.options.blobUrlTemplate}/${encodeURIComponent(filePath)}`;
+ const html = ProjectFindFile.makeHtml(filePath, matches, blobItemUrl);
results.push(this.element.find('.tree-table > tbody').append(html));
}
@@ -124,8 +128,7 @@ export default class ProjectFindFile {
// make tbody row html
static makeHtml(filePath, matches, blobItemUrl) {
- var $tr;
- $tr = $(
+ const $tr = $(
" ",
);
if (matches) {
@@ -140,9 +143,9 @@ export default class ProjectFindFile {
}
selectRow(type) {
- var next, rows, selectedRow;
- rows = this.element.find('.files-slider tr.tree-item');
- selectedRow = this.element.find('.files-slider tr.tree-item.selected');
+ const rows = this.element.find('.files-slider tr.tree-item');
+ let selectedRow = this.element.find('.files-slider tr.tree-item.selected');
+ let next = selectedRow.prev();
if (rows && rows.length > 0) {
if (selectedRow && selectedRow.length > 0) {
if (type === 'UP') {
@@ -174,7 +177,7 @@ export default class ProjectFindFile {
}
goToBlob() {
- var $link = this.element.find('.tree-item.selected .tree-item-file-name a');
+ const $link = this.element.find('.tree-item.selected .tree-item-file-name a');
if ($link.length) {
$link.get(0).click();
diff --git a/app/assets/javascripts/project_select.js b/app/assets/javascripts/project_select.js
index 0fbb7e5ca4..66ce1ab565 100644
--- a/app/assets/javascripts/project_select.js
+++ b/app/assets/javascripts/project_select.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, no-var, one-var, no-else-return */
+/* eslint-disable func-names, no-else-return */
import $ from 'jquery';
import Api from './api';
@@ -7,9 +7,11 @@ import { s__ } from './locale';
const projectSelect = () => {
$('.ajax-project-select').each(function(i, select) {
- var placeholder;
+ let placeholder;
const simpleFilter = $(select).data('simpleFilter') || false;
+ const isInstantiated = $(select).data('select2');
this.groupId = $(select).data('groupId');
+ this.userId = $(select).data('userId');
this.includeGroups = $(select).data('includeGroups');
this.allProjects = $(select).data('allProjects') || false;
this.orderBy = $(select).data('orderBy') || 'id';
@@ -28,55 +30,62 @@ const projectSelect = () => {
$(select).select2({
placeholder,
minimumInputLength: 0,
- query: (function(_this) {
- return function(query) {
- var finalCallback, projectsCallback;
- finalCallback = function(projects) {
- var data;
- data = {
- results: projects,
- };
- return query.callback(data);
+ query: query => {
+ let projectsCallback;
+ const finalCallback = function(projects) {
+ const data = {
+ results: projects,
};
- if (_this.includeGroups) {
- projectsCallback = function(projects) {
- var groupsCallback;
- groupsCallback = function(groups) {
- var data;
- data = groups.concat(projects);
- return finalCallback(data);
- };
- return Api.groups(query.term, {}, groupsCallback);
- };
- } else {
- projectsCallback = finalCallback;
- }
- if (_this.groupId) {
- return Api.groupProjects(
- _this.groupId,
- query.term,
- {
- with_issues_enabled: _this.withIssuesEnabled,
- with_merge_requests_enabled: _this.withMergeRequestsEnabled,
- with_shared: _this.withShared,
- include_subgroups: _this.includeProjectsInSubgroups,
- },
- projectsCallback,
- );
- } else {
- return Api.projects(
- query.term,
- {
- order_by: _this.orderBy,
- with_issues_enabled: _this.withIssuesEnabled,
- with_merge_requests_enabled: _this.withMergeRequestsEnabled,
- membership: !_this.allProjects,
- },
- projectsCallback,
- );
- }
+ return query.callback(data);
};
- })(this),
+ if (this.includeGroups) {
+ projectsCallback = function(projects) {
+ const groupsCallback = function(groups) {
+ const data = groups.concat(projects);
+ return finalCallback(data);
+ };
+ return Api.groups(query.term, {}, groupsCallback);
+ };
+ } else {
+ projectsCallback = finalCallback;
+ }
+ if (this.groupId) {
+ return Api.groupProjects(
+ this.groupId,
+ query.term,
+ {
+ with_issues_enabled: this.withIssuesEnabled,
+ with_merge_requests_enabled: this.withMergeRequestsEnabled,
+ with_shared: this.withShared,
+ include_subgroups: this.includeProjectsInSubgroups,
+ },
+ projectsCallback,
+ );
+ } else if (this.userId) {
+ return Api.userProjects(
+ this.userId,
+ query.term,
+ {
+ with_issues_enabled: this.withIssuesEnabled,
+ with_merge_requests_enabled: this.withMergeRequestsEnabled,
+ with_shared: this.withShared,
+ include_subgroups: this.includeProjectsInSubgroups,
+ },
+ projectsCallback,
+ );
+ } else {
+ return Api.projects(
+ query.term,
+ {
+ order_by: this.orderBy,
+ with_issues_enabled: this.withIssuesEnabled,
+ with_merge_requests_enabled: this.withMergeRequestsEnabled,
+ membership: !this.allProjects,
+ },
+ projectsCallback,
+ );
+ }
+ },
id(project) {
if (simpleFilter) return project.id;
return JSON.stringify({
@@ -96,7 +105,7 @@ const projectSelect = () => {
dropdownCssClass: 'ajax-project-dropdown',
});
- if (simpleFilter) return select;
+ if (isInstantiated || simpleFilter) return select;
return new ProjectSelectComboButton(select);
});
};
diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js
index 9066844f68..2429da9c06 100644
--- a/app/assets/javascripts/projects/project_new.js
+++ b/app/assets/javascripts/projects/project_new.js
@@ -182,6 +182,10 @@ const bindEvents = () => {
text: s__('ProjectTemplates|Netlify/Hexo'),
icon: '.template-option .icon-netlify',
},
+ serverless_framework: {
+ text: s__('ProjectTemplates|Serverless Framework/JS'),
+ icon: '.template-option .icon-serverless_framework',
+ },
};
const selectedTemplate = templates[value];
diff --git a/app/assets/javascripts/registry/components/collapsible_container.vue b/app/assets/javascripts/registry/components/collapsible_container.vue
index 95f8270b5d..5a6f937056 100644
--- a/app/assets/javascripts/registry/components/collapsible_container.vue
+++ b/app/assets/javascripts/registry/components/collapsible_container.vue
@@ -8,12 +8,13 @@ import {
GlModalDirective,
GlEmptyState,
} from '@gitlab/ui';
-import createFlash from '../../flash';
-import ClipboardButton from '../../vue_shared/components/clipboard_button.vue';
-import Icon from '../../vue_shared/components/icon.vue';
+import createFlash from '~/flash';
+import Tracking from '~/tracking';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import Icon from '~/vue_shared/components/icon.vue';
import TableRegistry from './table_registry.vue';
-import { errorMessages, errorMessagesTypes } from '../constants';
-import { __ } from '../../locale';
+import { DELETE_REPO_ERROR_MESSAGE } from '../constants';
+import { __ } from '~/locale';
export default {
name: 'CollapsibeContainerRegisty',
@@ -30,6 +31,7 @@ export default {
GlTooltip: GlTooltipDirective,
GlModal: GlModalDirective,
},
+ mixins: [Tracking.mixin({})],
props: {
repo: {
type: Object,
@@ -40,6 +42,10 @@ export default {
return {
isOpen: false,
modalId: `confirm-repo-deletion-modal-${this.repo.id}`,
+ tracking: {
+ category: document.body.dataset.page,
+ label: 'registry_repository_delete',
+ },
};
},
computed: {
@@ -61,15 +67,13 @@ export default {
}
},
handleDeleteRepository() {
+ this.track('confirm_delete', {});
return this.deleteItem(this.repo)
.then(() => {
createFlash(__('This container registry has been scheduled for deletion.'), 'notice');
this.fetchRepos();
})
- .catch(() => this.showError(errorMessagesTypes.DELETE_REPO));
- },
- showError(message) {
- createFlash(errorMessages[message]);
+ .catch(() => createFlash(DELETE_REPO_ERROR_MESSAGE));
},
},
};
@@ -97,10 +101,9 @@ export default {
v-gl-modal="modalId"
:title="s__('ContainerRegistry|Remove repository')"
:aria-label="s__('ContainerRegistry|Remove repository')"
- data-track-event="click_button"
- data-track-label="registry_repository_delete"
class="js-remove-repo btn-inverted"
variant="danger"
+ @click="track('click_button', {})"
>
@@ -124,7 +127,13 @@ export default {
class="mx-auto my-0"
/>
-
+
{{ s__('ContainerRegistry|Remove repository') }}
-
+
{{ modalAction }}
{{ modalAction }}
diff --git a/app/assets/javascripts/registry/constants.js b/app/assets/javascripts/registry/constants.js
index 712b0fade3..db798fb88a 100644
--- a/app/assets/javascripts/registry/constants.js
+++ b/app/assets/javascripts/registry/constants.js
@@ -1,15 +1,8 @@
import { __ } from '../locale';
-export const errorMessagesTypes = {
- FETCH_REGISTRY: 'FETCH_REGISTRY',
- FETCH_REPOS: 'FETCH_REPOS',
- DELETE_REPO: 'DELETE_REPO',
- DELETE_REGISTRY: 'DELETE_REGISTRY',
-};
-
-export const errorMessages = {
- [errorMessagesTypes.FETCH_REGISTRY]: __('Something went wrong while fetching the registry list.'),
- [errorMessagesTypes.FETCH_REPOS]: __('Something went wrong while fetching the projects.'),
- [errorMessagesTypes.DELETE_REPO]: __('Something went wrong on our end.'),
- [errorMessagesTypes.DELETE_REGISTRY]: __('Something went wrong on our end.'),
-};
+export const FETCH_REGISTRY_ERROR_MESSAGE = __(
+ 'Something went wrong while fetching the registry list.',
+);
+export const FETCH_REPOS_ERROR_MESSAGE = __('Something went wrong while fetching the projects.');
+export const DELETE_REPO_ERROR_MESSAGE = __('Something went wrong on our end.');
+export const DELETE_REGISTRY_ERROR_MESSAGE = __('Something went wrong on our end.');
diff --git a/app/assets/javascripts/registry/stores/actions.js b/app/assets/javascripts/registry/stores/actions.js
index 2121f518a7..6afba61848 100644
--- a/app/assets/javascripts/registry/stores/actions.js
+++ b/app/assets/javascripts/registry/stores/actions.js
@@ -1,7 +1,7 @@
import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
import * as types from './mutation_types';
-import { errorMessages, errorMessagesTypes } from '../constants';
+import { FETCH_REPOS_ERROR_MESSAGE, FETCH_REGISTRY_ERROR_MESSAGE } from '../constants';
export const fetchRepos = ({ commit, state }) => {
commit(types.TOGGLE_MAIN_LOADING);
@@ -14,7 +14,7 @@ export const fetchRepos = ({ commit, state }) => {
})
.catch(() => {
commit(types.TOGGLE_MAIN_LOADING);
- createFlash(errorMessages[errorMessagesTypes.FETCH_REPOS]);
+ createFlash(FETCH_REPOS_ERROR_MESSAGE);
});
};
@@ -30,7 +30,7 @@ export const fetchList = ({ commit }, { repo, page }) => {
})
.catch(() => {
commit(types.TOGGLE_REGISTRY_LIST_LOADING, repo);
- createFlash(errorMessages[errorMessagesTypes.FETCH_REGISTRY]);
+ createFlash(FETCH_REGISTRY_ERROR_MESSAGE);
});
};
diff --git a/app/assets/javascripts/registry/stores/mutations.js b/app/assets/javascripts/registry/stores/mutations.js
index ea5925247d..419de84888 100644
--- a/app/assets/javascripts/registry/stores/mutations.js
+++ b/app/assets/javascripts/registry/stores/mutations.js
@@ -1,33 +1,31 @@
import * as types from './mutation_types';
-import { parseIntPagination, normalizeHeaders } from '../../lib/utils/common_utils';
+import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils';
export default {
[types.SET_MAIN_ENDPOINT](state, endpoint) {
- Object.assign(state, { endpoint });
+ state.endpoint = endpoint;
},
[types.SET_IS_DELETE_DISABLED](state, isDeleteDisabled) {
- Object.assign(state, { isDeleteDisabled });
+ state.isDeleteDisabled = isDeleteDisabled;
},
[types.SET_REPOS_LIST](state, list) {
- Object.assign(state, {
- repos: list.map(el => ({
- canDelete: Boolean(el.destroy_path),
- destroyPath: el.destroy_path,
- id: el.id,
- isLoading: false,
- list: [],
- location: el.location,
- name: el.path,
- tagsPath: el.tags_path,
- projectId: el.project_id,
- })),
- });
+ state.repos = list.map(el => ({
+ canDelete: Boolean(el.destroy_path),
+ destroyPath: el.destroy_path,
+ id: el.id,
+ isLoading: false,
+ list: [],
+ location: el.location,
+ name: el.path,
+ tagsPath: el.tags_path,
+ projectId: el.project_id,
+ }));
},
[types.TOGGLE_MAIN_LOADING](state) {
- Object.assign(state, { isLoading: !state.isLoading });
+ state.isLoading = !state.isLoading;
},
[types.SET_REGISTRY_LIST](state, { repo, resp, headers }) {
diff --git a/app/assets/javascripts/releases/detail/components/app.vue b/app/assets/javascripts/releases/detail/components/app.vue
index 54a441de88..073cfcd769 100644
--- a/app/assets/javascripts/releases/detail/components/app.vue
+++ b/app/assets/javascripts/releases/detail/components/app.vue
@@ -1,6 +1,7 @@
+
+
+
+
+
+
+ {{ commit.short_id }}
+
+ {{ commit.short_id }}
+
+
+
+
+
+
+
+ {{ tagName }}
+
+ {{ tagName }}
+
+
+
+
+
{{ __('Created') }}
+
+
+ {{ releasedAtTimeAgo }}
+
+
+
+
+ {{ __('by') }}
+
+
+
+
+
diff --git a/app/assets/javascripts/reports/components/issue_status_icon.vue b/app/assets/javascripts/reports/components/issue_status_icon.vue
index 386653b944..62a9338b86 100644
--- a/app/assets/javascripts/reports/components/issue_status_icon.vue
+++ b/app/assets/javascripts/reports/components/issue_status_icon.vue
@@ -50,6 +50,6 @@ export default {
}"
class="report-block-list-icon"
>
-
+
diff --git a/app/assets/javascripts/reports/components/report_item.vue b/app/assets/javascripts/reports/components/report_item.vue
index f3f7d2648a..3c8a9e6ebe 100644
--- a/app/assets/javascripts/reports/components/report_item.vue
+++ b/app/assets/javascripts/reports/components/report_item.vue
@@ -46,6 +46,7 @@ export default {
+import { GlLink } from '@gitlab/ui';
+
+export default {
+ components: {
+ GlLink,
+ },
+ props: {
+ currentPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ links: {
+ type: Array,
+ required: true,
+ },
+ },
+ computed: {
+ normalizedLinks() {
+ return this.links.map(link => ({
+ text: link.text,
+ path: `${link.path}?path=${this.currentPath}`,
+ }));
+ },
+ },
+};
+
+
+
+
+
diff --git a/app/assets/javascripts/repository/components/last_commit.vue b/app/assets/javascripts/repository/components/last_commit.vue
index 19a2db2db2..70678b0db3 100644
--- a/app/assets/javascripts/repository/components/last_commit.vue
+++ b/app/assets/javascripts/repository/components/last_commit.vue
@@ -1,5 +1,6 @@
-
+
+
+
+
@@ -102,7 +114,7 @@ export default {
class="text-expander"
@click="toggleShowDescription"
>
-
+
{{ commit.author.name }}
+
+ {{ commit.authorName }}
+
{{ s__('LastCommit|authored') }}
{{ commit.description }}
@@ -125,19 +140,20 @@ export default {
-
-
-
+
+
+
+
+
{{ showCommitId }}
@@ -153,3 +169,9 @@ export default {
+
+
diff --git a/app/assets/javascripts/repository/components/preview/index.vue b/app/assets/javascripts/repository/components/preview/index.vue
new file mode 100644
index 0000000000..7f97483835
--- /dev/null
+++ b/app/assets/javascripts/repository/components/preview/index.vue
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+ {{ blob.name }}
+
+
+
+
+
diff --git a/app/assets/javascripts/repository/components/table/index.vue b/app/assets/javascripts/repository/components/table/index.vue
index 610c7e8d99..8f2e9264bc 100644
--- a/app/assets/javascripts/repository/components/table/index.vue
+++ b/app/assets/javascripts/repository/components/table/index.vue
@@ -1,19 +1,15 @@
+
+
+ {{ text }}
+
diff --git a/app/assets/javascripts/repository/components/tree_content.vue b/app/assets/javascripts/repository/components/tree_content.vue
new file mode 100644
index 0000000000..949e653fc8
--- /dev/null
+++ b/app/assets/javascripts/repository/components/tree_content.vue
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
diff --git a/app/assets/javascripts/repository/graphql.js b/app/assets/javascripts/repository/graphql.js
index 6cb253c816..6936c08d85 100644
--- a/app/assets/javascripts/repository/graphql.js
+++ b/app/assets/javascripts/repository/graphql.js
@@ -1,6 +1,7 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
+import axios from '~/lib/utils/axios_utils';
import createDefaultClient from '~/lib/graphql';
import introspectionQueryResultData from './fragmentTypes.json';
import { fetchLogsTree } from './log_tree';
@@ -27,6 +28,11 @@ const defaultClient = createDefaultClient(
});
});
},
+ readme(_, { url }) {
+ return axios
+ .get(url, { params: { viewer: 'rich', format: 'json' } })
+ .then(({ data }) => ({ ...data, __typename: 'ReadmeFile' }));
+ },
},
},
{
diff --git a/app/assets/javascripts/repository/index.js b/app/assets/javascripts/repository/index.js
index f972796004..d826f20981 100644
--- a/app/assets/javascripts/repository/index.js
+++ b/app/assets/javascripts/repository/index.js
@@ -3,13 +3,18 @@ import createRouter from './router';
import App from './components/app.vue';
import Breadcrumbs from './components/breadcrumbs.vue';
import LastCommit from './components/last_commit.vue';
+import TreeActionLink from './components/tree_action_link.vue';
+import DirectoryDownloadLinks from './components/directory_download_links.vue';
import apolloProvider from './graphql';
import { setTitle } from './utils/title';
import { parseBoolean } from '../lib/utils/common_utils';
+import { webIDEUrl } from '../lib/utils/url_utility';
+import { __ } from '../locale';
export default function setupVueRepositoryList() {
const el = document.getElementById('js-tree-list');
- const { projectPath, projectShortPath, ref, fullName } = el.dataset;
+ const { dataset } = el;
+ const { projectPath, projectShortPath, ref, fullName } = dataset;
const router = createRouter(projectPath, ref);
apolloProvider.clients.defaultClient.cache.writeData({
@@ -22,19 +27,7 @@ export default function setupVueRepositoryList() {
});
router.afterEach(({ params: { pathMatch } }) => {
- const isRoot = pathMatch === undefined || pathMatch === '/';
-
setTitle(pathMatch, ref, fullName);
-
- if (!isRoot) {
- document
- .querySelectorAll('.js-keep-hidden-on-navigation')
- .forEach(elem => elem.classList.add('hidden'));
- }
-
- document
- .querySelectorAll('.js-hide-on-navigation')
- .forEach(elem => elem.classList.toggle('hidden', !isRoot));
});
const breadcrumbEl = document.getElementById('js-repo-breadcrumb');
@@ -88,7 +81,68 @@ export default function setupVueRepositoryList() {
},
});
- return new Vue({
+ const treeHistoryLinkEl = document.getElementById('js-tree-history-link');
+ const { historyLink } = treeHistoryLinkEl.dataset;
+
+ // eslint-disable-next-line no-new
+ new Vue({
+ el: treeHistoryLinkEl,
+ router,
+ render(h) {
+ return h(TreeActionLink, {
+ props: {
+ path: historyLink + (this.$route.params.pathMatch || '/'),
+ text: __('History'),
+ },
+ });
+ },
+ });
+
+ const webIdeLinkEl = document.getElementById('js-tree-web-ide-link');
+
+ if (webIdeLinkEl) {
+ // eslint-disable-next-line no-new
+ new Vue({
+ el: webIdeLinkEl,
+ router,
+ render(h) {
+ return h(TreeActionLink, {
+ props: {
+ path: webIDEUrl(`/${projectPath}/edit/${ref}/-${this.$route.params.pathMatch || '/'}`),
+ text: __('Web IDE'),
+ cssClass: 'qa-web-ide-button',
+ },
+ });
+ },
+ });
+ }
+
+ const directoryDownloadLinks = document.getElementById('js-directory-downloads');
+
+ if (directoryDownloadLinks) {
+ // eslint-disable-next-line no-new
+ new Vue({
+ el: directoryDownloadLinks,
+ router,
+ render(h) {
+ const currentPath = this.$route.params.pathMatch || '/';
+
+ if (currentPath !== '/') {
+ return h(DirectoryDownloadLinks, {
+ props: {
+ currentPath: currentPath.replace(/^\//, ''),
+ links: JSON.parse(directoryDownloadLinks.dataset.links),
+ },
+ });
+ }
+
+ return null;
+ },
+ });
+ }
+
+ // eslint-disable-next-line no-new
+ new Vue({
el,
router,
apolloProvider,
@@ -96,4 +150,6 @@ export default function setupVueRepositoryList() {
return h(App);
},
});
+
+ return { router, data: dataset };
}
diff --git a/app/assets/javascripts/repository/log_tree.js b/app/assets/javascripts/repository/log_tree.js
index 2c19aca239..5bf30e625a 100644
--- a/app/assets/javascripts/repository/log_tree.js
+++ b/app/assets/javascripts/repository/log_tree.js
@@ -1,4 +1,5 @@
import axios from '~/lib/utils/axios_utils';
+import { normalizeData } from 'ee_else_ce/repository/utils/commit';
import getCommits from './queries/getCommits.query.graphql';
import getProjectPath from './queries/getProjectPath.query.graphql';
import getRef from './queries/getRef.query.graphql';
@@ -6,18 +7,6 @@ import getRef from './queries/getRef.query.graphql';
let fetchpromise;
let resolvers = [];
-export function normalizeData(data) {
- return data.map(d => ({
- sha: d.commit.id,
- message: d.commit.message,
- committedDate: d.commit.committed_date,
- commitPath: d.commit_path,
- fileName: d.file_name,
- type: d.type,
- __typename: 'LogTreeCommit',
- }));
-}
-
export function resolveCommit(commits, { resolve, entry }) {
const commit = commits.find(c => c.fileName === entry.name && c.type === entry.type);
@@ -37,9 +26,12 @@ export function fetchLogsTree(client, path, offset, resolver = null) {
const { ref } = client.readQuery({ query: getRef });
fetchpromise = axios
- .get(`${gon.gitlab_url}/${projectPath}/refs/${ref}/logs_tree${path ? `/${path}` : ''}`, {
- params: { format: 'json', offset },
- })
+ .get(
+ `${gon.relative_url_root}/${projectPath}/refs/${ref}/logs_tree/${path.replace(/^\//, '')}`,
+ {
+ params: { format: 'json', offset },
+ },
+ )
.then(({ data, headers }) => {
const headerLogsOffset = headers['more-logs-offset'];
const { commits } = client.readQuery({ query: getCommits });
diff --git a/app/assets/javascripts/repository/pages/index.vue b/app/assets/javascripts/repository/pages/index.vue
index 2d92e9174c..29786bf4ec 100644
--- a/app/assets/javascripts/repository/pages/index.vue
+++ b/app/assets/javascripts/repository/pages/index.vue
@@ -1,18 +1,25 @@
-
+
diff --git a/app/assets/javascripts/repository/pages/tree.vue b/app/assets/javascripts/repository/pages/tree.vue
index 3b898d1aa9..dd4d437f4d 100644
--- a/app/assets/javascripts/repository/pages/tree.vue
+++ b/app/assets/javascripts/repository/pages/tree.vue
@@ -1,9 +1,10 @@
-
+
diff --git a/app/assets/javascripts/repository/queries/commit.fragment.graphql b/app/assets/javascripts/repository/queries/commit.fragment.graphql
new file mode 100644
index 0000000000..9bb13c475c
--- /dev/null
+++ b/app/assets/javascripts/repository/queries/commit.fragment.graphql
@@ -0,0 +1,8 @@
+fragment TreeEntryCommit on LogTreeCommit {
+ sha
+ message
+ committedDate
+ commitPath
+ fileName
+ type
+}
diff --git a/app/assets/javascripts/repository/queries/getCommit.query.graphql b/app/assets/javascripts/repository/queries/getCommit.query.graphql
index e2a2d831e4..e4aeaaff8f 100644
--- a/app/assets/javascripts/repository/queries/getCommit.query.graphql
+++ b/app/assets/javascripts/repository/queries/getCommit.query.graphql
@@ -1,10 +1,7 @@
+#import "ee_else_ce/repository/queries/commit.fragment.graphql"
+
query getCommit($fileName: String!, $type: String!, $path: String!) {
commit(path: $path, fileName: $fileName, type: $type) @client {
- sha
- message
- committedDate
- commitPath
- fileName
- type
+ ...TreeEntryCommit
}
}
diff --git a/app/assets/javascripts/repository/queries/getCommits.query.graphql b/app/assets/javascripts/repository/queries/getCommits.query.graphql
index df9e67cc44..0976b8f32d 100644
--- a/app/assets/javascripts/repository/queries/getCommits.query.graphql
+++ b/app/assets/javascripts/repository/queries/getCommits.query.graphql
@@ -1,10 +1,7 @@
+#import "ee_else_ce/repository/queries/commit.fragment.graphql"
+
query getCommits {
commits @client {
- sha
- message
- committedDate
- commitPath
- fileName
- type
+ ...TreeEntryCommit
}
}
diff --git a/app/assets/javascripts/repository/queries/getFiles.query.graphql b/app/assets/javascripts/repository/queries/getFiles.query.graphql
index c4814f8e63..2aaf5066b4 100644
--- a/app/assets/javascripts/repository/queries/getFiles.query.graphql
+++ b/app/assets/javascripts/repository/queries/getFiles.query.graphql
@@ -2,6 +2,7 @@
fragment TreeEntry on Entry {
id
+ sha
name
flatPath
type
diff --git a/app/assets/javascripts/repository/queries/getReadme.query.graphql b/app/assets/javascripts/repository/queries/getReadme.query.graphql
new file mode 100644
index 0000000000..cf05633013
--- /dev/null
+++ b/app/assets/javascripts/repository/queries/getReadme.query.graphql
@@ -0,0 +1,5 @@
+query getReadme($url: String!) {
+ readme(url: $url) @client {
+ html
+ }
+}
diff --git a/app/assets/javascripts/repository/queries/pathLastCommit.query.graphql b/app/assets/javascripts/repository/queries/pathLastCommit.query.graphql
index 71c1bf1274..9be025afe3 100644
--- a/app/assets/javascripts/repository/queries/pathLastCommit.query.graphql
+++ b/app/assets/javascripts/repository/queries/pathLastCommit.query.graphql
@@ -5,22 +5,27 @@ query pathLastCommit($projectPath: ID!, $path: String, $ref: String!) {
lastCommit {
sha
title
- message
+ description
webUrl
authoredDate
+ authorName
author {
name
avatarUrl
webUrl
}
signatureHtml
- latestPipeline {
- detailedStatus {
- detailsPath
- icon
- tooltip
- text
- group
+ pipelines(ref: $ref, first: 1) {
+ edges {
+ node {
+ detailedStatus {
+ detailsPath
+ icon
+ tooltip
+ text
+ group
+ }
+ }
}
}
}
diff --git a/app/assets/javascripts/repository/router.js b/app/assets/javascripts/repository/router.js
index 9322c81ab9..ebf0a7091e 100644
--- a/app/assets/javascripts/repository/router.js
+++ b/app/assets/javascripts/repository/router.js
@@ -16,7 +16,7 @@ export default function createRouter(base, baseRef) {
name: 'treePath',
component: TreePage,
props: route => ({
- path: route.params.pathMatch && route.params.pathMatch.replace(/^\//, ''),
+ path: route.params.pathMatch && (route.params.pathMatch.replace(/^\//, '') || '/'),
}),
},
{
diff --git a/app/assets/javascripts/repository/utils/commit.js b/app/assets/javascripts/repository/utils/commit.js
new file mode 100644
index 0000000000..6c204b57b3
--- /dev/null
+++ b/app/assets/javascripts/repository/utils/commit.js
@@ -0,0 +1,13 @@
+// eslint-disable-next-line import/prefer-default-export
+export function normalizeData(data, extra = () => {}) {
+ return data.map(d => ({
+ sha: d.commit.id,
+ message: d.commit.message,
+ committedDate: d.commit.committed_date,
+ commitPath: d.commit_path,
+ fileName: d.file_name,
+ type: d.type,
+ __typename: 'LogTreeCommit',
+ ...extra(d),
+ }));
+}
diff --git a/app/assets/javascripts/repository/utils/dom.js b/app/assets/javascripts/repository/utils/dom.js
new file mode 100644
index 0000000000..963e6fc0bc
--- /dev/null
+++ b/app/assets/javascripts/repository/utils/dom.js
@@ -0,0 +1,4 @@
+// eslint-disable-next-line import/prefer-default-export
+export const updateElementsVisibility = (selector, isVisible) => {
+ document.querySelectorAll(selector).forEach(elem => elem.classList.toggle('hidden', !isVisible));
+};
diff --git a/app/assets/javascripts/repository/utils/readme.js b/app/assets/javascripts/repository/utils/readme.js
new file mode 100644
index 0000000000..e43b2bdc33
--- /dev/null
+++ b/app/assets/javascripts/repository/utils/readme.js
@@ -0,0 +1,21 @@
+const MARKDOWN_EXTENSIONS = ['mdown', 'mkd', 'mkdn', 'md', 'markdown'];
+const ASCIIDOC_EXTENSIONS = ['adoc', 'ad', 'asciidoc'];
+const OTHER_EXTENSIONS = ['textile', 'rdoc', 'org', 'creole', 'wiki', 'mediawiki', 'rst'];
+const EXTENSIONS = [...MARKDOWN_EXTENSIONS, ...ASCIIDOC_EXTENSIONS, ...OTHER_EXTENSIONS];
+const PLAIN_FILENAMES = ['readme', 'index'];
+const FILE_REGEXP = new RegExp(
+ `^(${PLAIN_FILENAMES.join('|')})(.(${EXTENSIONS.join('|')}))?$`,
+ 'i',
+);
+const PLAIN_FILE_REGEXP = new RegExp(`^(${PLAIN_FILENAMES.join('|')})`, 'i');
+const EXTENSIONS_REGEXP = new RegExp(`.(${EXTENSIONS.join('|')})$`, 'i');
+
+// eslint-disable-next-line import/prefer-default-export
+export const readmeFile = blobs => {
+ const readMeFiles = blobs.filter(f => f.name.search(FILE_REGEXP) !== -1);
+
+ const previewableReadme = readMeFiles.find(f => f.name.search(EXTENSIONS_REGEXP) !== -1);
+ const plainReadme = readMeFiles.find(f => f.name.search(PLAIN_FILE_REGEXP) !== -1);
+
+ return previewableReadme || plainReadme;
+};
diff --git a/app/assets/javascripts/repository/utils/title.js b/app/assets/javascripts/repository/utils/title.js
index 87d54c0120..ff16fbdd42 100644
--- a/app/assets/javascripts/repository/utils/title.js
+++ b/app/assets/javascripts/repository/utils/title.js
@@ -1,10 +1,14 @@
+const DEFAULT_TITLE = '· GitLab';
// eslint-disable-next-line import/prefer-default-export
export const setTitle = (pathMatch, ref, project) => {
- if (!pathMatch) return;
+ if (!pathMatch) {
+ document.title = `${project} ${DEFAULT_TITLE}`;
+ return;
+ }
const path = pathMatch.replace(/^\//, '');
const isEmpty = path === '';
/* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
- document.title = `${isEmpty ? 'Files' : path} · ${ref} · ${project}`;
+ document.title = `${isEmpty ? 'Files' : path} · ${ref} · ${project} ${DEFAULT_TITLE}`;
};
diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js
index 87454ee056..fa5649679d 100644
--- a/app/assets/javascripts/right_sidebar.js
+++ b/app/assets/javascripts/right_sidebar.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, no-var, consistent-return, one-var, no-else-return, no-param-reassign */
+/* eslint-disable func-names, consistent-return, no-else-return, no-param-reassign */
import $ from 'jquery';
import _ from 'underscore';
@@ -44,12 +44,11 @@ Sidebar.prototype.addEventListeners = function() {
};
Sidebar.prototype.sidebarToggleClicked = function(e, triggered) {
- var $allGutterToggleIcons, $this, isExpanded, tooltipLabel;
+ const $this = $(this);
+ const isExpanded = $this.find('i').hasClass('fa-angle-double-right');
+ const tooltipLabel = isExpanded ? __('Expand sidebar') : __('Collapse sidebar');
+ const $allGutterToggleIcons = $('.js-sidebar-toggle i');
e.preventDefault();
- $this = $(this);
- isExpanded = $this.find('i').hasClass('fa-angle-double-right');
- tooltipLabel = isExpanded ? __('Expand sidebar') : __('Collapse sidebar');
- $allGutterToggleIcons = $('.js-sidebar-toggle i');
if (isExpanded) {
$allGutterToggleIcons.removeClass('fa-angle-double-right').addClass('fa-angle-double-left');
@@ -77,15 +76,9 @@ Sidebar.prototype.sidebarToggleClicked = function(e, triggered) {
};
Sidebar.prototype.toggleTodo = function(e) {
- var $this, ajaxType, url;
- $this = $(e.currentTarget);
- ajaxType = $this.data('deletePath') ? 'delete' : 'post';
-
- if ($this.data('deletePath')) {
- url = String($this.data('deletePath'));
- } else {
- url = String($this.data('createPath'));
- }
+ const $this = $(e.currentTarget);
+ const ajaxType = $this.data('deletePath') ? 'delete' : 'post';
+ const url = String($this.data('deletePath') || $this.data('createPath'));
$this.tooltip('hide');
@@ -141,13 +134,12 @@ Sidebar.prototype.todoUpdateDone = function(data) {
};
Sidebar.prototype.sidebarDropdownLoading = function() {
- var $loading, $sidebarCollapsedIcon, i, img;
- $sidebarCollapsedIcon = $(this)
+ const $sidebarCollapsedIcon = $(this)
.closest('.block')
.find('.sidebar-collapsed-icon');
- img = $sidebarCollapsedIcon.find('img');
- i = $sidebarCollapsedIcon.find('i');
- $loading = $('
');
+ const img = $sidebarCollapsedIcon.find('img');
+ const i = $sidebarCollapsedIcon.find('i');
+ const $loading = $('
');
if (img.length) {
img.before($loading);
return img.hide();
@@ -158,13 +150,12 @@ Sidebar.prototype.sidebarDropdownLoading = function() {
};
Sidebar.prototype.sidebarDropdownLoaded = function() {
- var $sidebarCollapsedIcon, i, img;
- $sidebarCollapsedIcon = $(this)
+ const $sidebarCollapsedIcon = $(this)
.closest('.block')
.find('.sidebar-collapsed-icon');
- img = $sidebarCollapsedIcon.find('img');
+ const img = $sidebarCollapsedIcon.find('img');
$sidebarCollapsedIcon.find('i.fa-spin').remove();
- i = $sidebarCollapsedIcon.find('i');
+ const i = $sidebarCollapsedIcon.find('i');
if (img.length) {
return img.show();
} else {
@@ -173,19 +164,17 @@ Sidebar.prototype.sidebarDropdownLoaded = function() {
};
Sidebar.prototype.sidebarCollapseClicked = function(e) {
- var $block, sidebar;
if ($(e.currentTarget).hasClass('dont-change-state')) {
return;
}
- sidebar = e.data;
+ const sidebar = e.data;
e.preventDefault();
- $block = $(this).closest('.block');
+ const $block = $(this).closest('.block');
return sidebar.openDropdown($block);
};
Sidebar.prototype.openDropdown = function(blockOrName) {
- var $block;
- $block = _.isString(blockOrName) ? this.getBlock(blockOrName) : blockOrName;
+ const $block = _.isString(blockOrName) ? this.getBlock(blockOrName) : blockOrName;
if (!this.isOpen()) {
this.setCollapseAfterUpdate($block);
this.toggleSidebar('open');
@@ -204,10 +193,9 @@ Sidebar.prototype.setCollapseAfterUpdate = function($block) {
};
Sidebar.prototype.onSidebarDropdownHidden = function(e) {
- var $block, sidebar;
- sidebar = e.data;
+ const sidebar = e.data;
e.preventDefault();
- $block = $(e.target).closest('.block');
+ const $block = $(e.target).closest('.block');
return sidebar.sidebarDropdownHidden($block);
};
diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js
index f6722ff7bc..8d888a574d 100644
--- a/app/assets/javascripts/search_autocomplete.js
+++ b/app/assets/javascripts/search_autocomplete.js
@@ -1,4 +1,4 @@
-/* eslint-disable no-return-assign, one-var, no-var, consistent-return, class-methods-use-this, no-lonely-if, vars-on-top */
+/* eslint-disable no-return-assign, consistent-return, class-methods-use-this */
import $ from 'jquery';
import { escape, throttle } from 'underscore';
@@ -29,14 +29,14 @@ const KEYCODE = {
};
function setSearchOptions() {
- var $projectOptionsDataEl = $('.js-search-project-options');
- var $groupOptionsDataEl = $('.js-search-group-options');
- var $dashboardOptionsDataEl = $('.js-search-dashboard-options');
+ const $projectOptionsDataEl = $('.js-search-project-options');
+ const $groupOptionsDataEl = $('.js-search-group-options');
+ const $dashboardOptionsDataEl = $('.js-search-dashboard-options');
if ($projectOptionsDataEl.length) {
gl.projectOptions = gl.projectOptions || {};
- var projectPath = $projectOptionsDataEl.data('projectPath');
+ const projectPath = $projectOptionsDataEl.data('projectPath');
gl.projectOptions[projectPath] = {
name: $projectOptionsDataEl.data('name'),
@@ -49,7 +49,7 @@ function setSearchOptions() {
if ($groupOptionsDataEl.length) {
gl.groupOptions = gl.groupOptions || {};
- var groupPath = $groupOptionsDataEl.data('groupPath');
+ const groupPath = $groupOptionsDataEl.data('groupPath');
gl.groupOptions[groupPath] = {
name: $groupOptionsDataEl.data('name'),
@@ -95,10 +95,9 @@ export class SearchAutocomplete {
this.createAutocomplete();
}
- this.searchInput.addClass('disabled');
- this.saveTextLength();
this.bindEvents();
this.dropdownToggle.dropdown();
+ this.searchInput.addClass('js-autocomplete-disabled');
}
// Finds an element inside wrapper element
@@ -107,7 +106,7 @@ export class SearchAutocomplete {
this.onClearInputClick = this.onClearInputClick.bind(this);
this.onSearchInputFocus = this.onSearchInputFocus.bind(this);
this.onSearchInputKeyUp = this.onSearchInputKeyUp.bind(this);
- this.onSearchInputKeyDown = this.onSearchInputKeyDown.bind(this);
+ this.onSearchInputChange = this.onSearchInputChange.bind(this);
this.setScrollFade = this.setScrollFade.bind(this);
}
getElement(selector) {
@@ -118,10 +117,6 @@ export class SearchAutocomplete {
return (this.originalState = this.serializeState());
}
- saveTextLength() {
- return (this.lastTextLength = this.searchInput.val().length);
- }
-
createAutocomplete() {
return this.searchInput.glDropdown({
filterInputBlur: false,
@@ -318,12 +313,16 @@ export class SearchAutocomplete {
}
bindEvents() {
- this.searchInput.on('keydown', this.onSearchInputKeyDown);
+ this.searchInput.on('input', this.onSearchInputChange);
this.searchInput.on('keyup', this.onSearchInputKeyUp);
this.searchInput.on('focus', this.onSearchInputFocus);
this.searchInput.on('blur', this.onSearchInputBlur);
this.clearInput.on('click', this.onClearInputClick);
this.dropdownContent.on('scroll', throttle(this.setScrollFade, 250));
+
+ this.searchInput.on('click', e => {
+ e.stopPropagation();
+ });
}
enableAutocomplete() {
@@ -338,47 +337,23 @@ export class SearchAutocomplete {
if (!this.dropdown.hasClass('show')) {
this.loadingSuggestions = false;
this.dropdownToggle.dropdown('toggle');
- return this.searchInput.removeClass('disabled');
+ return this.searchInput.removeClass('js-autocomplete-disabled');
}
}
- // Saves last length of the entered text
- onSearchInputKeyDown() {
- return this.saveTextLength();
+ onSearchInputChange() {
+ this.enableAutocomplete();
}
onSearchInputKeyUp(e) {
switch (e.keyCode) {
- case KEYCODE.BACKSPACE:
- // When removing the last character and no badge is present
- if (this.lastTextLength === 1) {
- this.disableAutocomplete();
- }
- // When removing any character from existin value
- if (this.lastTextLength > 1) {
- this.enableAutocomplete();
- }
- break;
case KEYCODE.ESCAPE:
this.restoreOriginalState();
break;
case KEYCODE.ENTER:
this.disableAutocomplete();
break;
- case KEYCODE.UP:
- case KEYCODE.DOWN:
- return;
default:
- // Handle the case when deleting the input value other than backspace
- // e.g. Pressing ctrl + backspace or ctrl + x
- if (this.searchInput.val() === '') {
- this.disableAutocomplete();
- } else {
- // We should display the menu only when input is not empty
- if (e.keyCode !== KEYCODE.ENTER) {
- this.enableAutocomplete();
- }
- }
}
this.wrap.toggleClass('has-value', Boolean(e.target.value));
}
@@ -412,36 +387,33 @@ export class SearchAutocomplete {
}
restoreOriginalState() {
- var i, input, inputs, len;
- inputs = Object.keys(this.originalState);
- for (i = 0, len = inputs.length; i < len; i += 1) {
- input = inputs[i];
+ const inputs = Object.keys(this.originalState);
+ for (let i = 0, len = inputs.length; i < len; i += 1) {
+ const input = inputs[i];
this.getElement(`#${input}`).val(this.originalState[input]);
}
}
resetSearchState() {
- var i, input, inputs, len, results;
- inputs = Object.keys(this.originalState);
- results = [];
- for (i = 0, len = inputs.length; i < len; i += 1) {
- input = inputs[i];
+ const inputs = Object.keys(this.originalState);
+ const results = [];
+ for (let i = 0, len = inputs.length; i < len; i += 1) {
+ const input = inputs[i];
results.push(this.getElement(`#${input}`).val(''));
}
return results;
}
disableAutocomplete() {
- if (!this.searchInput.hasClass('disabled') && this.dropdown.hasClass('show')) {
- this.searchInput.addClass('disabled');
- this.dropdown.removeClass('show').trigger('hidden.bs.dropdown');
+ if (!this.searchInput.hasClass('js-autocomplete-disabled') && this.dropdown.hasClass('show')) {
+ this.searchInput.addClass('js-autocomplete-disabled');
+ this.dropdown.dropdown('toggle');
this.restoreMenu();
}
}
restoreMenu() {
- var html;
- html = `
`;
+ const html = `
`;
return this.dropdownContent.html(html);
}
diff --git a/app/assets/javascripts/raven/index.js b/app/assets/javascripts/sentry/index.js
similarity index 76%
rename from app/assets/javascripts/raven/index.js
rename to app/assets/javascripts/sentry/index.js
index 4dd0175e52..06e4e0aa50 100644
--- a/app/assets/javascripts/raven/index.js
+++ b/app/assets/javascripts/sentry/index.js
@@ -1,8 +1,8 @@
-import RavenConfig from './raven_config';
+import SentryConfig from './sentry_config';
const index = function index() {
- RavenConfig.init({
- sentryDsn: gon.sentry_dsn,
+ SentryConfig.init({
+ dsn: gon.sentry_dsn,
currentUserId: gon.current_user_id,
whitelistUrls:
process.env.NODE_ENV === 'production'
@@ -15,7 +15,7 @@ const index = function index() {
},
});
- return RavenConfig;
+ return SentryConfig;
};
index();
diff --git a/app/assets/javascripts/raven/raven_config.js b/app/assets/javascripts/sentry/sentry_config.js
similarity index 63%
rename from app/assets/javascripts/raven/raven_config.js
rename to app/assets/javascripts/sentry/sentry_config.js
index 7259e0df10..bc3b2f16a6 100644
--- a/app/assets/javascripts/raven/raven_config.js
+++ b/app/assets/javascripts/sentry/sentry_config.js
@@ -1,4 +1,4 @@
-import Raven from 'raven-js';
+import * as Sentry from '@sentry/browser';
import $ from 'jquery';
import { __ } from '~/locale';
@@ -26,7 +26,7 @@ const IGNORE_ERRORS = [
'conduitPage',
];
-const IGNORE_URLS = [
+const BLACKLIST_URLS = [
// Facebook flakiness
/graph\.facebook\.com/i,
// Facebook blocked
@@ -43,62 +43,62 @@ const IGNORE_URLS = [
/metrics\.itunes\.apple\.com\.edgesuite\.net\//i,
];
-const SAMPLE_RATE = 95;
+const SAMPLE_RATE = 0.95;
-const RavenConfig = {
+const SentryConfig = {
IGNORE_ERRORS,
- IGNORE_URLS,
+ BLACKLIST_URLS,
SAMPLE_RATE,
init(options = {}) {
this.options = options;
this.configure();
- this.bindRavenErrors();
+ this.bindSentryErrors();
if (this.options.currentUserId) this.setUser();
},
configure() {
- Raven.config(this.options.sentryDsn, {
- release: this.options.release,
- tags: this.options.tags,
- whitelistUrls: this.options.whitelistUrls,
- environment: this.options.environment,
- ignoreErrors: this.IGNORE_ERRORS,
- ignoreUrls: this.IGNORE_URLS,
- shouldSendCallback: this.shouldSendSample.bind(this),
- }).install();
+ const { dsn, release, tags, whitelistUrls, environment } = this.options;
+ Sentry.init({
+ dsn,
+ release,
+ tags,
+ whitelistUrls,
+ environment,
+ ignoreErrors: this.IGNORE_ERRORS, // TODO: Remove in favor of https://gitlab.com/gitlab-org/gitlab/issues/35144
+ blacklistUrls: this.BLACKLIST_URLS,
+ sampleRate: SAMPLE_RATE,
+ });
},
setUser() {
- Raven.setUserContext({
+ Sentry.setUser({
id: this.options.currentUserId,
});
},
- bindRavenErrors() {
- $(document).on('ajaxError.raven', this.handleRavenErrors);
+ bindSentryErrors() {
+ $(document).on('ajaxError.sentry', this.handleSentryErrors);
},
- handleRavenErrors(event, req, config, err) {
+ handleSentryErrors(event, req, config, err) {
const error = err || req.statusText;
- const responseText = req.responseText || __('Unknown response text');
+ const { responseText = __('Unknown response text') } = req;
+ const { type, url, data } = config;
+ const { status } = req;
- Raven.captureMessage(error, {
+ Sentry.captureMessage(error, {
extra: {
- type: config.type,
- url: config.url,
- data: config.data,
- status: req.status,
+ type,
+ url,
+ data,
+ status,
response: responseText,
error,
event,
},
});
},
-
- shouldSendSample() {
- return Math.random() * 100 <= this.SAMPLE_RATE;
- },
};
-export default RavenConfig;
+export default SentryConfig;
diff --git a/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions.vue b/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions.vue
index 95a2c8cce6..91fe5fc50a 100644
--- a/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions.vue
+++ b/app/assets/javascripts/sidebar/components/subscriptions/sidebar_subscriptions.vue
@@ -33,6 +33,8 @@ export default {
diff --git a/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue b/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue
index ea5edb3ce3..0e489b2859 100644
--- a/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue
+++ b/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue
@@ -26,6 +26,16 @@ export default {
required: false,
default: false,
},
+ projectEmailsDisabled: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ subscribeDisabledDescription: {
+ type: String,
+ required: false,
+ default: '',
+ },
subscribed: {
type: Boolean,
required: false,
@@ -42,11 +52,23 @@ export default {
return this.subscribed === null;
},
notificationIcon() {
+ if (this.projectEmailsDisabled) {
+ return ICON_OFF;
+ }
return this.subscribed ? ICON_ON : ICON_OFF;
},
notificationTooltip() {
+ if (this.projectEmailsDisabled) {
+ return this.subscribeDisabledDescription;
+ }
return this.subscribed ? LABEL_ON : LABEL_OFF;
},
+ notificationText() {
+ if (this.projectEmailsDisabled) {
+ return this.subscribeDisabledDescription;
+ }
+ return __('Notifications');
+ },
},
methods: {
/**
@@ -81,6 +103,7 @@ export default {
-
+
{
- var next, path;
+ let next, path;
if ($('input:focus').length > 0 && (e.which === 38 || e.which === 40)) {
return false;
}
diff --git a/app/assets/javascripts/user_popovers.js b/app/assets/javascripts/user_popovers.js
index c0b7587be1..7d6a725b30 100644
--- a/app/assets/javascripts/user_popovers.js
+++ b/app/assets/javascripts/user_popovers.js
@@ -73,9 +73,14 @@ const handleUserPopoverMouseOver = event => {
location: userData.location,
bio: userData.bio,
organization: userData.organization,
+ status: userData.status,
loaded: true,
});
+ if (userData.status) {
+ return Promise.resolve();
+ }
+
return UsersCache.retrieveStatusById(userId);
})
.then(status => {
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue
index 339e154aff..57be97855e 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue
@@ -65,9 +65,13 @@ export default {
simplePoll(this.checkRebaseStatus);
})
.catch(error => {
- this.rebasingError = error.merge_error;
this.isMakingRequest = false;
- Flash(__('Something went wrong. Please try again.'));
+
+ if (error.response && error.response.data && error.response.data.merge_error) {
+ this.rebasingError = error.response.data.merge_error;
+ } else {
+ Flash(__('Something went wrong. Please try again.'));
+ }
});
},
checkRebaseStatus(continuePolling, stopPolling) {
diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/content_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/content_viewer.vue
index 1e6f4c376c..66155ddcdd 100644
--- a/app/assets/javascripts/vue_shared/components/content_viewer/content_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/content_viewer/content_viewer.vue
@@ -18,6 +18,11 @@ export default {
required: false,
default: 0,
},
+ filePath: {
+ type: String,
+ required: false,
+ default: '',
+ },
projectPath: {
type: String,
required: false,
@@ -52,6 +57,7 @@ export default {
@@ -22,12 +32,14 @@ export default {
-
+
diff --git a/app/assets/javascripts/vue_shared/components/issue/issue_assignees.vue b/app/assets/javascripts/vue_shared/components/issue/issue_assignees.vue
index 715cf97f0a..1524b313f9 100644
--- a/app/assets/javascripts/vue_shared/components/issue/issue_assignees.vue
+++ b/app/assets/javascripts/vue_shared/components/issue/issue_assignees.vue
@@ -1,7 +1,6 @@
@@ -89,14 +96,18 @@ export default {
@@ -106,6 +117,12 @@ export default {
{{ __('Toggle commit list') }}
+
diff --git a/app/assets/javascripts/vue_shared/components/project_selector/project_selector.vue b/app/assets/javascripts/vue_shared/components/project_selector/project_selector.vue
index 478e44d104..f984a0a620 100644
--- a/app/assets/javascripts/vue_shared/components/project_selector/project_selector.vue
+++ b/app/assets/javascripts/vue_shared/components/project_selector/project_selector.vue
@@ -1,6 +1,6 @@
+
+
+
+
+
+
+
+
diff --git a/app/assets/javascripts/vue_shared/components/split_button.vue b/app/assets/javascripts/vue_shared/components/split_button.vue
new file mode 100644
index 0000000000..f7dc00a345
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/split_button.vue
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+ {{ item.title }}
+ {{ item.description }}
+
+
+
+
+
+
diff --git a/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue b/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue
index 8bcad7ac76..43935cf31d 100644
--- a/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue
+++ b/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue
@@ -32,7 +32,7 @@ export default {
-
+
@@ -90,7 +90,7 @@ export default {
name="location"
class="category-icon flex-shrink-0"
/>
-
{{ user.location }}
+
{{ user.location }}
{
+ this.enter(
+ $(e.target)
+ .closest('.md-area')
+ .find('.zen-backdrop'),
+ );
+ });
+ $(document).on('zen_mode:leave', () => {
+ this.exit();
+ });
$(document).on('keydown', e => {
// Esc
if (e.keyCode === 27) {
diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss
index c9b00e5ff2..885e9ac666 100644
--- a/app/assets/stylesheets/bootstrap_migration.scss
+++ b/app/assets/stylesheets/bootstrap_migration.scss
@@ -282,8 +282,7 @@ pre code {
white-space: pre-wrap;
}
-.alert,
-.flash-notice {
+.alert {
border-radius: 0;
}
@@ -310,12 +309,10 @@ pre code {
.alert-success,
.alert-info,
.alert-warning,
-.alert-danger,
-.flash-notice {
+.alert-danger {
color: $white-light;
h4,
- a:not(.btn),
.alert-link {
color: $white-light;
}
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index 56a88ca44d..249e9a24b1 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -2,7 +2,7 @@
@import 'framework/variables_overrides';
@import 'framework/mixins';
-@import '@gitlab/ui/scss/gitlab_ui';
+@import '@gitlab/ui/src/scss/gitlab_ui';
@import 'bootstrap_migration';
@import 'framework/layout';
diff --git a/app/assets/stylesheets/framework/ci_variable_list.scss b/app/assets/stylesheets/framework/ci_variable_list.scss
index 28d7492b99..cae7b9b5e4 100644
--- a/app/assets/stylesheets/framework/ci_variable_list.scss
+++ b/app/assets/stylesheets/framework/ci_variable_list.scss
@@ -99,3 +99,13 @@
color: $gl-text-color-disabled;
}
}
+
+.group-variable-list {
+ color: $gray-700;
+
+ .table-section:not(:first-child) {
+ @include media-breakpoint-down(sm) {
+ border-top: hidden;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 4b89a2f2b0..31ea59df4c 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -562,4 +562,20 @@ img.emoji {
}
.gl-font-size-small { font-size: $gl-font-size-small; }
+.gl-font-size-large { font-size: $gl-font-size-large; }
+
.gl-line-height-24 { line-height: $gl-line-height-24; }
+
+.gl-font-size-12 { font-size: $gl-font-size-12; }
+.gl-font-size-14 { font-size: $gl-font-size-14; }
+.gl-font-size-16 { font-size: $gl-font-size-16; }
+.gl-font-size-20 { font-size: $gl-font-size-20; }
+.gl-font-size-28 { font-size: $gl-font-size-28; }
+.gl-font-size-42 { font-size: $gl-font-size-42; }
+
+.border-section {
+ @include gl-py-6;
+ @include gl-m-0;
+
+ border-top: 1px solid $border-color;
+}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index ce74aa6ed0..d53a4c1286 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -506,7 +506,8 @@
.dropdown-menu-selectable {
li {
a,
- button {
+ button,
+ .dropdown-item {
padding: 8px 40px;
position: relative;
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 487fbf0fcf..4938215b2e 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -376,6 +376,10 @@ span.idiff {
float: none;
}
+ .file-actions .ide-edit-button {
+ z-index: 2;
+ }
+
@include media-breakpoint-down(xs) {
display: block;
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index 5984efd1cf..2d82606456 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -249,7 +249,7 @@
}
.filtered-search-input-dropdown-menu {
- max-height: $dropdown-max-height;
+ max-height: $dropdown-max-height-lg;
max-width: 280px;
overflow: auto;
@@ -357,12 +357,18 @@
}
}
- .filter-dropdown-container > div {
- margin: 0;
+ .filter-dropdown-container {
+ > div {
+ margin: 0;
- > .btn {
- margin: 0 0 10px;
- width: 100%;
+ > .btn {
+ margin: 0 0 10px;
+ width: 100%;
+ }
+ }
+
+ .board-labels-toggle-wrapper {
+ margin-bottom: $gl-input-padding;
}
}
diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss
index 8fc2fd5f53..d604d97d27 100644
--- a/app/assets/stylesheets/framework/flash.scss
+++ b/app/assets/stylesheets/framework/flash.scss
@@ -12,17 +12,22 @@ $notification-box-shadow-color: rgba(0, 0, 0, 0.25);
position: -webkit-sticky;
top: $flash-container-top;
z-index: 251;
+ }
- .flash-content {
- box-shadow: 0 2px 4px 0 $notification-box-shadow-color;
- }
+ &.flash-container-page {
+ margin-bottom: 0;
+ }
+
+ &:empty {
+ margin: 0;
}
.close-icon-wrapper {
- padding: ($gl-btn-padding + $gl-padding-4) $gl-padding $gl-btn-padding;
+ padding: ($gl-padding + $gl-padding-4) $gl-padding $gl-padding;
position: absolute;
right: 0;
top: 0;
+ bottom: 0;
cursor: pointer;
.close-icon {
@@ -31,13 +36,12 @@ $notification-box-shadow-color: rgba(0, 0, 0, 0.25);
}
}
- .flash-notice,
.flash-alert,
+ .flash-notice,
.flash-success,
.flash-warning {
- border-radius: $border-radius-default;
- color: $white-light;
- padding-right: $gl-padding * 2;
+ padding: $gl-padding $gl-padding-32 $gl-padding ($gl-padding + $gl-padding-4);
+ margin-top: 10px;
.container-fluid,
.container-fluid.container-limited {
@@ -45,75 +49,31 @@ $notification-box-shadow-color: rgba(0, 0, 0, 0.25);
}
}
+ .flash-alert {
+ background-color: $red-100;
+ color: $red-700;
+ }
+
.flash-notice {
- @extend .alert;
- background-color: $blue-500;
- margin: 0;
+ background-color: $blue-100;
+ color: $blue-700;
+ }
- &.flash-notice-persistent {
- background-color: $blue-100;
- color: $gl-text-color;
-
- a {
- color: $blue-600;
-
- &:hover {
- color: $blue-800;
- text-decoration: none;
- }
- }
- }
+ .flash-success {
+ background-color: $theme-green-100;
+ color: $green-700;
}
.flash-warning {
- @extend .alert;
background-color: $orange-100;
- color: $orange-900;
+ color: $orange-800;
cursor: default;
- margin: 0;
}
.flash-text,
.flash-action {
display: inline-block;
}
-
- .flash-alert {
- @extend .alert;
- background-color: $red-500;
- margin: 0;
-
- .flash-action {
- margin-left: 5px;
- text-decoration: none;
- font-weight: $gl-font-weight-normal;
- border-bottom: 1px solid;
-
- &:hover {
- border-color: transparent;
- }
- }
- }
-
- .flash-success {
- @extend .alert;
- background-color: $green-500;
- margin: 0;
- }
-
- &.flash-container-page {
- margin-bottom: 0;
-
- .flash-notice,
- .flash-alert,
- .flash-success {
- border-radius: 0;
- }
- }
-
- &:empty {
- margin: 0;
- }
}
@include media-breakpoint-down(sm) {
diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss
index 7205324e86..8038a367fb 100644
--- a/app/assets/stylesheets/framework/layout.scss
+++ b/app/assets/stylesheets/framework/layout.scss
@@ -33,7 +33,8 @@ body {
&.limit-container-width {
.flash-container.sticky {
max-width: $limited-layout-width;
- margin: 0 auto;
+ margin-right: auto;
+ margin-left: auto;
}
}
}
diff --git a/app/assets/stylesheets/framework/memory_graph.scss b/app/assets/stylesheets/framework/memory_graph.scss
index 81cdf6b59e..c84010c6f1 100644
--- a/app/assets/stylesheets/framework/memory_graph.scss
+++ b/app/assets/stylesheets/framework/memory_graph.scss
@@ -1,11 +1,7 @@
.memory-graph-container {
svg {
background: $white-light;
- cursor: pointer;
-
- &:hover {
- box-shadow: 0 0 4px $gray-darkest inset;
- }
+ border: 1px solid $gray-200;
}
path {
diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss
index 9c92455913..757264add9 100644
--- a/app/assets/stylesheets/framework/modal.scss
+++ b/app/assets/stylesheets/framework/modal.scss
@@ -49,7 +49,7 @@
line-height: $line-height-base;
position: relative;
min-height: $modal-body-height;
- padding: #{2 * $grid-size} #{6 * $grid-size} #{2 * $grid-size} #{2 * $grid-size};
+ padding: #{2 * $grid-size};
text-align: left;
white-space: normal;
@@ -70,9 +70,9 @@
margin: 0;
}
- .btn + .btn:not(.dropdown-toggle-split),
.btn + .btn-group,
- .btn-group + .btn {
+ .btn-group + .btn,
+ .btn-group + .btn-group {
margin-left: $grid-size;
}
@@ -83,13 +83,6 @@
@include media-breakpoint-down(xs) {
flex-direction: column;
- .btn + .btn:not(.dropdown-toggle-split),
- .btn + .btn-group,
- .btn-group + .btn {
- margin-left: 0;
- margin-top: $grid-size;
- }
-
.btn-group .btn + .btn {
margin-left: -1px;
margin-top: 0;
diff --git a/app/assets/stylesheets/framework/responsive_tables.scss b/app/assets/stylesheets/framework/responsive_tables.scss
index fd6f80e26c..1878fac1c6 100644
--- a/app/assets/stylesheets/framework/responsive_tables.scss
+++ b/app/assets/stylesheets/framework/responsive_tables.scss
@@ -20,6 +20,17 @@
@extend .gl-responsive-table-row-layout;
margin-top: 10px;
border: 1px solid $border-color;
+ color: $gray-700;
+
+ &.gl-responsive-table-row-clickable {
+ &:hover {
+ background-color: $gray-light;
+
+ .underline {
+ text-decoration: underline;
+ }
+ }
+ }
@include media-breakpoint-up(md) {
margin: 0;
diff --git a/app/assets/stylesheets/framework/snippets.scss b/app/assets/stylesheets/framework/snippets.scss
index f57b1d9f35..404f60f17e 100644
--- a/app/assets/stylesheets/framework/snippets.scss
+++ b/app/assets/stylesheets/framework/snippets.scss
@@ -4,7 +4,12 @@
}
.snippet-filename {
- padding: 0 2px;
+ color: $gl-text-color-secondary;
+ font-weight: normal;
+ }
+
+ .snippet-info {
+ color: $gl-text-color-secondary;
}
}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index dfc39d8e03..0f77c451fa 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -20,6 +20,27 @@ $spacing-scale: (
5: #{4 * $grid-size}
);
+/*
+ * Why another sizing scale???
+ * Great question, friend!
+ * This size scale is a "backport" of the equivalent set of "named" sizes
+ * (e.g. `xl` versus `70`) that came from the following design document as of 2019-10-23:
+ *
+ * https://gitlab-org.gitlab.io/gitlab-design/hosted/design-gitlab-specs/forms-spec-previews/
+ *
+ * (See `input-` items at the bottom)
+ *
+ * The presumption here is that these sizes will be standardized in GitLab UI and thus will be
+ * broadly useful here in the GitLab product when not using the GitLab UI components.
+ */
+$size-scale: (
+ 'xs': #{10 * $grid-size},
+ 's': #{20 * $grid-size},
+ 'm': #{30 * $grid-size},
+ 'l': #{40 * $grid-size},
+ 'xl': #{70 * $grid-size}
+);
+
/*
* Color schema
*/
@@ -304,6 +325,12 @@ $gl-grayish-blue: #7f8fa4;
$gl-gray-dark: #313236;
$gl-gray-light: #5c5c5c;
$gl-header-color: #4c4e54;
+$gl-font-size-12: 12px;
+$gl-font-size-14: 14px;
+$gl-font-size-16: 16px;
+$gl-font-size-20: 20px;
+$gl-font-size-28: 28px;
+$gl-font-size-42: 42px;
$type-scale: (
1: 12px,
diff --git a/app/assets/stylesheets/framework/vue_transitions.scss b/app/assets/stylesheets/framework/vue_transitions.scss
index e3bdc0b019..a082cd25ab 100644
--- a/app/assets/stylesheets/framework/vue_transitions.scss
+++ b/app/assets/stylesheets/framework/vue_transitions.scss
@@ -11,3 +11,27 @@
.fade-leave-to {
opacity: 0;
}
+
+.slide-enter-from-element {
+ &.slide-enter,
+ &.slide-leave-to {
+ transform: translateX(-150%);
+ }
+}
+
+.slide-enter-to-element {
+ &.slide-enter,
+ &.slide-leave-to {
+ transform: translateX(150%);
+ }
+}
+
+.slide-enter-active,
+.slide-leave-active {
+ transition: transform 300ms ease-out;
+}
+
+.slide-enter-to,
+.slide-leave {
+ transform: translateX(0);
+}
diff --git a/app/assets/stylesheets/mailer.scss b/app/assets/stylesheets/mailer.scss
new file mode 100644
index 0000000000..f7d93870a2
--- /dev/null
+++ b/app/assets/stylesheets/mailer.scss
@@ -0,0 +1,117 @@
+@import 'framework/variables';
+
+// Do not use 3-letter hex codes, bgcolor vs css background-color is problematic in emails
+// See https://stackoverflow.com/questions/28551981/why-are-3-digit-hex-color-code-values-interpreted-differently-in-internet-explor
+//
+// stylelint-disable color-hex-length
+
+$mailer-font: 'Helvetica Neue', Helvetica, Arial, sans-serif;
+$mailer-text-color: #333333;
+$mailer-bg-color: #fafafa;
+$mailer-link-color: #3777b0;
+$mailer-link-muted-color: #333333;
+$mailer-line-cell-bg-color: #6b4fbb;
+$mailer-wrapper-cell-bg-color: #ffffff;
+$mailer-wrapper-cell-border-color: #ededed;
+$mailer-header-footer-text-color: #5c5c5c;
+
+body {
+ margin: 0 !important;
+ background-color: $mailer-bg-color;
+ padding: 0;
+ text-align: center;
+ min-width: 640px;
+ width: 100%;
+ height: 100%;
+ font-family: $mailer-font;
+}
+
+table#body {
+ background-color: $mailer-bg-color;
+ margin: 0;
+ padding: 0;
+ text-align: center;
+ min-width: 640px;
+ width: 100%;
+}
+
+a {
+ color: $mailer-link-color;
+ text-decoration: none;
+
+ &.muted {
+ color: $mailer-link-muted-color;
+ }
+}
+
+.highlight {
+ font-weight: 500;
+}
+
+tr td {
+ font-family: $mailer-font;
+}
+
+tr.line td {
+ font-family: $mailer-font;
+ background-color: $mailer-line-cell-bg-color;
+ height: 4px;
+ font-size: 4px;
+ line-height: 4px;
+}
+
+tr.header td,
+tr.footer td,
+td.footer-message {
+ font-family: $mailer-font;
+ padding: 25px 0;
+ font-size: 13px;
+ line-height: 1.6;
+ color: $mailer-header-footer-text-color;
+}
+
+table.wrapper {
+ width: 640px;
+ margin: 0 auto;
+ border-collapse: separate;
+ border-spacing: 0;
+
+ td.wrapper-cell {
+ font-family: $mailer-font;
+ background-color: $mailer-wrapper-cell-bg-color;
+ text-align: left;
+ padding: 18px 25px;
+ border: 1px solid $mailer-wrapper-cell-border-color;
+ border-radius: 3px;
+ overflow: hidden;
+ }
+}
+
+table.content {
+ width: 100%;
+ border-collapse: separate;
+ border-spacing: 0;
+
+ td.text-content {
+ font-family: $mailer-font;
+ color: $mailer-text-color;
+ font-size: 15px;
+ font-weight: 400;
+ line-height: 1.4;
+ padding: 15px 5px;
+ text-align: center;
+ }
+}
+
+tr.footer td {
+ img {
+ display: block;
+ margin: 0 auto 1em;
+ }
+
+ .mng-notif-link,
+ .help-link {
+ color: $mailer-link-color;
+ text-decoration: none;
+ }
+}
diff --git a/app/assets/stylesheets/mailer_client_specific.scss b/app/assets/stylesheets/mailer_client_specific.scss
new file mode 100644
index 0000000000..41bedecf90
--- /dev/null
+++ b/app/assets/stylesheets/mailer_client_specific.scss
@@ -0,0 +1,65 @@
+/* CLIENT-SPECIFIC STYLES */
+
+// These are client-specific rules, ignore some linting rules
+//
+// stylelint-disable property-no-vendor-prefix, property-no-unknown, length-zero-no-unit
+// scss-lint:disable PropertySpelling, ZeroUnit
+
+body,
+table,
+td,
+a {
+ -webkit-text-size-adjust: 100%;
+ -ms-text-size-adjust: 100%;
+}
+
+table,
+td {
+ mso-table-lspace: 0pt;
+ mso-table-rspace: 0pt;
+}
+
+img {
+ -ms-interpolation-mode: bicubic;
+}
+
+.hidden {
+ display: none !important;
+ visibility: hidden !important;
+}
+
+/* iOS BLUE LINKS */
+a[x-apple-data-detectors] {
+ color: inherit !important;
+ text-decoration: none !important;
+ font-size: inherit !important;
+ font-family: inherit !important;
+ font-weight: inherit !important;
+ line-height: inherit !important;
+}
+
+/* ANDROID MARGIN HACK */
+div[style*='margin: 16px 0'] {
+ margin: 0 !important;
+}
+
+@media only screen and (max-width: 639px) {
+ body,
+ #body {
+ min-width: 320px !important;
+ }
+
+ table.wrapper {
+ width: 100% !important;
+ min-width: 320px !important;
+ }
+
+ table.wrapper td.wrapper-cell {
+ border-left: 0 !important;
+ border-right: 0 !important;
+ border-radius: 0 !important;
+ padding-left: 10px !important;
+ padding-right: 10px !important;
+ }
+}
+
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index 2a7a53d8bd..d26979bc17 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -497,7 +497,7 @@
.add-issues-footer-to-list {
padding-left: $gl-vert-padding;
padding-right: $gl-vert-padding;
- line-height: 34px;
+ line-height: $input-height;
}
.issue-card-selected {
@@ -545,3 +545,11 @@
.board-issue-path.js-show-tooltip {
cursor: help;
}
+
+.board-labels-toggle-wrapper {
+ /**
+ * Make the wrapper the same height as a button so it aligns properly when the
+ * filtered-search-box input element increases in size on Linux smaller breakpoints
+ */
+ height: $input-height;
+}
diff --git a/app/assets/stylesheets/pages/error_details.scss b/app/assets/stylesheets/pages/error_details.scss
new file mode 100644
index 0000000000..0515db914e
--- /dev/null
+++ b/app/assets/stylesheets/pages/error_details.scss
@@ -0,0 +1,18 @@
+.error-details {
+ li {
+ @include gl-line-height-32;
+ }
+}
+
+.stacktrace {
+ .file-title {
+ svg {
+ vertical-align: middle;
+ top: -1px;
+ }
+ }
+
+ .line_content.old::before {
+ content: none !important;
+ }
+}
diff --git a/app/assets/stylesheets/pages/experimental_separate_sign_up.scss b/app/assets/stylesheets/pages/experimental_separate_sign_up.scss
index 8b1ec1ced3..5a80ea7960 100644
--- a/app/assets/stylesheets/pages/experimental_separate_sign_up.scss
+++ b/app/assets/stylesheets/pages/experimental_separate_sign_up.scss
@@ -23,6 +23,7 @@
.signup-heading h2 {
font-weight: $gl-font-weight-bold;
+ padding: 0 $gl-padding;
@include media-breakpoint-down(md) {
font-size: $gl-font-size-large;
diff --git a/app/assets/stylesheets/pages/graph.scss b/app/assets/stylesheets/pages/graph.scss
index 3febf4cf82..a8de8303a1 100644
--- a/app/assets/stylesheets/pages/graph.scss
+++ b/app/assets/stylesheets/pages/graph.scss
@@ -17,21 +17,6 @@
}
}
-.graphs {
- .graph-author-email {
- float: right;
- color: $gl-gray-500;
- }
-
- .graph-additions {
- color: $green-600;
- }
-
- .graph-deletions {
- color: $red-500;
- }
-}
-
.svg-graph-container {
width: 100%;
diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss
index 00d84df165..b399662997 100644
--- a/app/assets/stylesheets/pages/milestone.scss
+++ b/app/assets/stylesheets/pages/milestone.scss
@@ -30,7 +30,8 @@ $status-box-line-height: 26px;
margin-bottom: $gl-padding-4;
}
- .milestone-progress {
+ .milestone-progress,
+ .milestone-release-links {
a {
color: $blue-600;
}
@@ -238,10 +239,6 @@ $status-box-line-height: 26px;
}
}
-.milestone-range {
- color: $gl-text-color-tertiary;
-}
-
@include media-breakpoint-down(xs) {
.milestone-banner-text,
.milestone-banner-link {
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 21a9f14303..1da9f69163 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -310,6 +310,17 @@ $note-form-margin-left: 72px;
.note-body {
overflow: hidden;
+ .description-version {
+ pre {
+ max-height: $dropdown-max-height-lg;
+ white-space: pre-wrap;
+
+ &.loading-state {
+ height: 94px;
+ }
+ }
+ }
+
.system-note-commit-list-toggler {
color: $blue-600;
padding: 10px 0 0;
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 1b2af93273..364fe3da71 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -871,7 +871,7 @@ button.mini-pipeline-graph-dropdown-toggle {
height: $ci-action-dropdown-svg-size;
fill: $gl-text-color-secondary;
position: relative;
- top: 1px;
+ top: auto;
vertical-align: initial;
}
}
@@ -1082,3 +1082,13 @@ button.mini-pipeline-graph-dropdown-toggle {
.legend-success {
color: $green-500;
}
+
+.test-reports-table {
+ .build-trace {
+ @include build-trace();
+ }
+}
+
+.progress-bar.bg-primary {
+ background-color: $blue-500 !important;
+}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index b2c1d0b6dc..17a446fca5 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -252,6 +252,7 @@
.fa-caret-down {
margin-left: 3px;
+ line-height: 0;
&.dropdown-btn-icon {
margin-left: 0;
diff --git a/app/assets/stylesheets/pages/reports.scss b/app/assets/stylesheets/pages/reports.scss
index 0fbf7033aa..390ebd4868 100644
--- a/app/assets/stylesheets/pages/reports.scss
+++ b/app/assets/stylesheets/pages/reports.scss
@@ -131,7 +131,6 @@
.modal-security-report-dast {
.modal-dialog {
- width: $modal-lg;
max-width: $modal-lg;
}
diff --git a/app/assets/stylesheets/pages/stat_graph.scss b/app/assets/stylesheets/pages/stat_graph.scss
deleted file mode 100644
index 31ccdacbc0..0000000000
--- a/app/assets/stylesheets/pages/stat_graph.scss
+++ /dev/null
@@ -1,62 +0,0 @@
-.tint-box {
- background: $stat-graph-common-bg;
- position: relative;
- margin-bottom: 10px;
-}
-
-.area {
- fill: $green-500;
- fill-opacity: 0.5;
-}
-
-.axis {
- font-size: 10px;
-}
-
-#contributors-master {
- @include media-breakpoint-up(md) {
- @include make-col-ready();
- @include make-col(12);
- }
-}
-
-#contributors {
- flex: 1;
-
- .contributors-list {
- margin: 0 0 10px;
- list-style: none;
- padding: 0;
- }
-
- .person {
- @include media-breakpoint-up(md) {
- @include make-col-ready();
- @include make-col(6);
- }
-
- margin-top: 10px;
-
- @include media-breakpoint-down(xs) {
- width: 100%;
- }
-
- .spark {
- display: block;
- background: $stat-graph-common-bg;
- width: 100%;
- }
-
- .area-contributor {
- fill: $orange-500;
- }
- }
-}
-
-.selection rect {
- fill-opacity: 0.1;
- stroke-width: 1px;
- stroke-opacity: 0.4;
- shape-rendering: crispedges;
- stroke-dasharray: 3 3;
-}
diff --git a/app/assets/stylesheets/performance_bar.scss b/app/assets/stylesheets/performance_bar.scss
index ad7d87f0bf..87e650c765 100644
--- a/app/assets/stylesheets/performance_bar.scss
+++ b/app/assets/stylesheets/performance_bar.scss
@@ -18,6 +18,11 @@
width: 200px;
}
+ input {
+ color: $gl-gray-400;
+ width: $input-short-width - 60px;
+ }
+
&.disabled {
display: none;
}
@@ -25,7 +30,8 @@
&.production {
background-color: $perf-bar-production;
- select {
+ select,
+ input {
background: $perf-bar-production;
}
}
@@ -33,7 +39,8 @@
&.staging {
background-color: $perf-bar-staging;
- select {
+ select,
+ input {
background: $perf-bar-staging;
}
}
@@ -41,7 +48,8 @@
&.development {
background-color: $perf-bar-development;
- select {
+ select,
+ input {
background: $perf-bar-development;
}
}
diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss
index d2906ce078..3b3a2778b2 100644
--- a/app/assets/stylesheets/utilities.scss
+++ b/app/assets/stylesheets/utilities.scss
@@ -16,8 +16,18 @@
}
}
+@each $index, $size in $size-scale {
+ #{'.mw-#{$index}'} {
+ max-width: $size;
+ }
+}
+
.border-width-1px { border-width: 1px; }
.border-style-dashed { border-style: dashed; }
.border-style-solid { border-style: solid; }
.border-color-blue-300 { border-color: $blue-300; }
.border-color-default { border-color: $border-color; }
+.box-shadow-default { box-shadow: 0 2px 4px 0 $black-transparent; }
+
+.gl-w-64 { width: px-to-rem($grid-size * 8); }
+.gl-h-64 { height: px-to-rem($grid-size * 8); }
diff --git a/app/controllers/admin/abuse_reports_controller.rb b/app/controllers/admin/abuse_reports_controller.rb
index d5537023b2..31d825c235 100644
--- a/app/controllers/admin/abuse_reports_controller.rb
+++ b/app/controllers/admin/abuse_reports_controller.rb
@@ -1,12 +1,9 @@
# frozen_string_literal: true
class Admin::AbuseReportsController < Admin::ApplicationController
- # rubocop: disable CodeReuse/ActiveRecord
def index
- @abuse_reports = AbuseReport.order(id: :desc).page(params[:page])
- @abuse_reports.includes(:reporter, :user)
+ @abuse_reports = AbuseReportsFinder.new(params).execute
end
- # rubocop: enable CodeReuse/ActiveRecord
def destroy
abuse_report = AbuseReport.find(params[:id])
diff --git a/app/controllers/admin/applications_controller.rb b/app/controllers/admin/applications_controller.rb
index 22e629ccf5..907b295870 100644
--- a/app/controllers/admin/applications_controller.rb
+++ b/app/controllers/admin/applications_controller.rb
@@ -44,7 +44,7 @@ class Admin::ApplicationsController < Admin::ApplicationController
def destroy
@application.destroy
- redirect_to admin_applications_url, status: 302, notice: _('Application was successfully destroyed.')
+ redirect_to admin_applications_url, status: :found, notice: _('Application was successfully destroyed.')
end
private
diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb
index 85a37fcd43..5455cefdc8 100644
--- a/app/controllers/admin/groups_controller.rb
+++ b/app/controllers/admin/groups_controller.rb
@@ -69,7 +69,7 @@ class Admin::GroupsController < Admin::ApplicationController
Groups::DestroyService.new(@group, current_user).async_execute
redirect_to admin_groups_path,
- status: 302,
+ status: :found,
alert: _('Group %{group_name} was scheduled for deletion.') % { group_name: @group.name }
end
diff --git a/app/controllers/admin/identities_controller.rb b/app/controllers/admin/identities_controller.rb
index f518f7a657..8f2e34a629 100644
--- a/app/controllers/admin/identities_controller.rb
+++ b/app/controllers/admin/identities_controller.rb
@@ -38,9 +38,9 @@ class Admin::IdentitiesController < Admin::ApplicationController
def destroy
if @identity.destroy
RepairLdapBlockedUserService.new(@user).execute
- redirect_to admin_user_identities_path(@user), status: 302, notice: _('User identity was successfully removed.')
+ redirect_to admin_user_identities_path(@user), status: :found, notice: _('User identity was successfully removed.')
else
- redirect_to admin_user_identities_path(@user), status: 302, alert: _('Failed to remove user identity.')
+ redirect_to admin_user_identities_path(@user), status: :found, alert: _('Failed to remove user identity.')
end
end
diff --git a/app/controllers/admin/keys_controller.rb b/app/controllers/admin/keys_controller.rb
index 340eecd763..58ea19d121 100644
--- a/app/controllers/admin/keys_controller.rb
+++ b/app/controllers/admin/keys_controller.rb
@@ -17,9 +17,9 @@ class Admin::KeysController < Admin::ApplicationController
respond_to do |format|
if key.destroy
- format.html { redirect_to keys_admin_user_path(user), status: 302, notice: _('User key was successfully removed.') }
+ format.html { redirect_to keys_admin_user_path(user), status: :found, notice: _('User key was successfully removed.') }
else
- format.html { redirect_to keys_admin_user_path(user), status: 302, alert: _('Failed to remove user key.') }
+ format.html { redirect_to keys_admin_user_path(user), status: :found, alert: _('Failed to remove user key.') }
end
end
end
diff --git a/app/controllers/admin/labels_controller.rb b/app/controllers/admin/labels_controller.rb
index 90c1694fd2..6cb206c168 100644
--- a/app/controllers/admin/labels_controller.rb
+++ b/app/controllers/admin/labels_controller.rb
@@ -43,7 +43,7 @@ class Admin::LabelsController < Admin::ApplicationController
respond_to do |format|
format.html do
- redirect_to admin_labels_path, status: 302, notice: _('Label was removed')
+ redirect_to admin_labels_path, status: :found, notice: _('Label was removed')
end
format.js
end
diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb
index 0e8c69eb7d..cdedc34e63 100644
--- a/app/controllers/admin/projects_controller.rb
+++ b/app/controllers/admin/projects_controller.rb
@@ -41,7 +41,7 @@ class Admin::ProjectsController < Admin::ApplicationController
redirect_to admin_projects_path, status: :found
rescue Projects::DestroyService::DestroyError => ex
- redirect_to admin_projects_path, status: 302, alert: ex.message
+ redirect_to admin_projects_path, status: :found, alert: ex.message
end
# rubocop: disable CodeReuse/ActiveRecord
diff --git a/app/controllers/admin/spam_logs_controller.rb b/app/controllers/admin/spam_logs_controller.rb
index 45cf0d3207..a41d8a2265 100644
--- a/app/controllers/admin/spam_logs_controller.rb
+++ b/app/controllers/admin/spam_logs_controller.rb
@@ -13,7 +13,7 @@ class Admin::SpamLogsController < Admin::ApplicationController
if params[:remove_user]
spam_log.remove_user(deleted_by: current_user)
redirect_to admin_spam_logs_path,
- status: 302,
+ status: :found,
notice: _('User %{username} was successfully removed.') % { username: spam_log.user.username }
else
spam_log.destroy
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index 4c1ac8f206..9fbfc59f63 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -169,7 +169,7 @@ class Admin::UsersController < Admin::ApplicationController
user.delete_async(deleted_by: current_user, params: params.permit(:hard_delete))
respond_to do |format|
- format.html { redirect_to admin_users_path, status: 302, notice: _("The user is being deleted.") }
+ format.html { redirect_to admin_users_path, status: :found, notice: _("The user is being deleted.") }
format.json { head :ok }
end
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 27e88ae569..25c1d80b11 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -12,6 +12,7 @@ class ApplicationController < ActionController::Base
include EnforcesTwoFactorAuthentication
include WithPerformanceBar
include SessionlessAuthentication
+ include SessionsHelper
include ConfirmEmailWarning
include Gitlab::Tracking::ControllerConcern
include Gitlab::Experimentation::ControllerConcern
@@ -29,13 +30,13 @@ class ApplicationController < ActionController::Base
before_action :active_user_check, unless: :devise_controller?
before_action :set_usage_stats_consent_flag
before_action :check_impersonation_availability
- before_action :require_role
+ before_action :required_signup_info
around_action :set_locale
around_action :set_session_storage
after_action :set_page_title_header, if: :json_request?
- after_action :limit_unauthenticated_session_times
+ after_action :limit_session_time, if: -> { !current_user }
protect_from_forgery with: :exception, prepend: true
@@ -57,7 +58,7 @@ class ApplicationController < ActionController::Base
rescue_from Encoding::CompatibilityError do |exception|
log_exception(exception)
- render "errors/encoding", layout: "errors", status: 500
+ render "errors/encoding", layout: "errors", status: :internal_server_error
end
rescue_from ActiveRecord::RecordNotFound do |exception|
@@ -103,24 +104,6 @@ class ApplicationController < ActionController::Base
end
end
- # By default, all sessions are given the same expiration time configured in
- # the session store (e.g. 1 week). However, unauthenticated users can
- # generate a lot of sessions, primarily for CSRF verification. It makes
- # sense to reduce the TTL for unauthenticated to something much lower than
- # the default (e.g. 1 hour) to limit Redis memory. In addition, Rails
- # creates a new session after login, so the short TTL doesn't even need to
- # be extended.
- def limit_unauthenticated_session_times
- return if current_user
-
- # Rack sets this header, but not all tests may have it: https://github.com/rack/rack/blob/fdcd03a3c5a1c51d1f96fc97f9dfa1a9deac0c77/lib/rack/session/abstract/id.rb#L251-L259
- return unless request.env['rack.session.options']
-
- # This works because Rack uses these options every time a request is handled:
- # https://github.com/rack/rack/blob/fdcd03a3c5a1c51d1f96fc97f9dfa1a9deac0c77/lib/rack/session/abstract/id.rb#L342
- request.env['rack.session.options'][:expire_after] = Settings.gitlab['unauthenticated_session_expire_delay']
- end
-
def render(*args)
super.tap do
# Set a header for custom error pages to prevent them from being intercepted by gitlab-workhorse
@@ -214,25 +197,29 @@ class ApplicationController < ActionController::Base
end
def git_not_found!
- render "errors/git_not_found.html", layout: "errors", status: 404
+ render "errors/git_not_found.html", layout: "errors", status: :not_found
end
def render_403
respond_to do |format|
format.any { head :forbidden }
- format.html { render "errors/access_denied", layout: "errors", status: 403 }
+ format.html { render "errors/access_denied", layout: "errors", status: :forbidden }
end
end
def render_404
respond_to do |format|
- format.html { render "errors/not_found", layout: "errors", status: 404 }
+ format.html { render "errors/not_found", layout: "errors", status: :not_found }
# Prevent the Rails CSRF protector from thinking a missing .js file is a JavaScript file
format.js { render json: '', status: :not_found, content_type: 'application/json' }
format.any { head :not_found }
end
end
+ def respond_201
+ head :created
+ end
+
def respond_422
head :unprocessable_entity
end
@@ -551,10 +538,13 @@ class ApplicationController < ActionController::Base
@current_user_mode ||= Gitlab::Auth::CurrentUserMode.new(current_user)
end
- # A user requires a role when they are part of the experimental signup flow (executed by the Growth team). Users
- # are redirected to the welcome page when their role is required and the experiment is enabled for the current user.
- def require_role
- return unless current_user && current_user.role_required? && experiment_enabled?(:signup_flow)
+ # A user requires a role and have the setup_for_company attribute set when they are part of the experimental signup
+ # flow (executed by the Growth team). Users are redirected to the welcome page when their role is required and the
+ # experiment is enabled for the current user.
+ def required_signup_info
+ return unless current_user
+ return unless current_user.role_required?
+ return unless experiment_enabled?(:signup_flow)
store_location_for :user, request.fullpath
diff --git a/app/controllers/boards/issues_controller.rb b/app/controllers/boards/issues_controller.rb
index 9894dd7d18..1298b33471 100644
--- a/app/controllers/boards/issues_controller.rb
+++ b/app/controllers/boards/issues_controller.rb
@@ -13,7 +13,7 @@ module Boards
requires_cross_project_access if: -> { board&.group_board? }
- before_action :whitelist_query_limiting, only: [:index, :update, :bulk_move]
+ before_action :whitelist_query_limiting, only: [:bulk_move]
before_action :authorize_read_issue, only: [:index]
before_action :authorize_create_issue, only: [:create]
before_action :authorize_update_issue, only: [:update]
@@ -130,8 +130,7 @@ module Boards
end
def whitelist_query_limiting
- # Also see https://gitlab.com/gitlab-org/gitlab-foss/issues/42439
- Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42428')
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab/issues/35174')
end
def validate_id_list
diff --git a/app/controllers/clusters/applications_controller.rb b/app/controllers/clusters/applications_controller.rb
index 16c2365f85..be68d0d0a1 100644
--- a/app/controllers/clusters/applications_controller.rb
+++ b/app/controllers/clusters/applications_controller.rb
@@ -47,7 +47,7 @@ class Clusters::ApplicationsController < Clusters::BaseController
end
def cluster_application_params
- params.permit(:application, :hostname, :email)
+ params.permit(:application, :hostname, :kibana_hostname, :email, :stack)
end
def cluster_application_destroy_params
diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb
index 993aba661f..9a539cf7c2 100644
--- a/app/controllers/clusters/clusters_controller.rb
+++ b/app/controllers/clusters/clusters_controller.rb
@@ -3,18 +3,22 @@
class Clusters::ClustersController < Clusters::BaseController
include RoutableActions
- before_action :cluster, except: [:index, :new, :create_gcp, :create_user]
+ before_action :cluster, only: [:cluster_status, :show, :update, :destroy]
before_action :generate_gcp_authorize_url, only: [:new]
before_action :validate_gcp_token, only: [:new]
before_action :gcp_cluster, only: [:new]
before_action :user_cluster, only: [:new]
- before_action :authorize_create_cluster!, only: [:new]
+ before_action :authorize_create_cluster!, only: [:new, :authorize_aws_role, :revoke_aws_role, :aws_proxy]
before_action :authorize_update_cluster!, only: [:update]
before_action :authorize_admin_cluster!, only: [:destroy]
before_action :update_applications_status, only: [:cluster_status]
before_action only: [:new, :create_gcp] do
push_frontend_feature_flag(:create_eks_clusters)
end
+ before_action only: [:show] do
+ push_frontend_feature_flag(:enable_cluster_application_elastic_stack)
+ push_frontend_feature_flag(:enable_cluster_application_crossplane)
+ end
helper_method :token_in_session
@@ -40,10 +44,13 @@ class Clusters::ClustersController < Clusters::BaseController
def new
return unless Feature.enabled?(:create_eks_clusters)
- @gke_selected = params[:provider] == 'gke'
- @eks_selected = params[:provider] == 'eks'
+ if params[:provider] == 'aws'
+ @aws_role = current_user.aws_role || Aws::Role.new
+ @aws_role.ensure_role_external_id!
- return redirect_to @authorize_url if @gke_selected && @authorize_url && !@valid_gcp_token
+ elsif params[:provider] == 'gcp'
+ redirect_to @authorize_url if @authorize_url && !@valid_gcp_token
+ end
end
# Overridding ActionController::Metal#status is NOT a good idea
@@ -86,13 +93,12 @@ class Clusters::ClustersController < Clusters::BaseController
end
def destroy
- if cluster.destroy
- flash[:notice] = _('Kubernetes cluster integration was successfully removed.')
- redirect_to clusterable.index_path, status: :found
- else
- flash[:notice] = _('Kubernetes cluster integration was not removed.')
- render :show
- end
+ response = Clusters::DestroyService
+ .new(current_user, destroy_params)
+ .execute(cluster)
+
+ flash[:notice] = response[:message]
+ redirect_to clusterable.index_path, status: :found
end
def create_gcp
@@ -112,6 +118,19 @@ class Clusters::ClustersController < Clusters::BaseController
end
end
+ def create_aws
+ @aws_cluster = ::Clusters::CreateService
+ .new(current_user, create_aws_cluster_params)
+ .execute
+ .present(current_user: current_user)
+
+ if @aws_cluster.persisted?
+ head :created, location: @aws_cluster.show_path
+ else
+ render status: :unprocessable_entity, json: @aws_cluster.errors
+ end
+ end
+
def create_user
@user_cluster = ::Clusters::CreateService
.new(current_user, create_user_cluster_params)
@@ -129,8 +148,37 @@ class Clusters::ClustersController < Clusters::BaseController
end
end
+ def authorize_aws_role
+ role = current_user.build_aws_role(create_role_params)
+
+ role.save ? respond_201 : respond_422
+ end
+
+ def revoke_aws_role
+ current_user.aws_role&.destroy
+
+ head :no_content
+ end
+
+ def aws_proxy
+ response = Clusters::Aws::ProxyService.new(
+ current_user.aws_role,
+ params: params
+ ).execute
+
+ render json: response.body, status: response.status
+ end
+
private
+ def destroy_params
+ # To be uncomented on https://gitlab.com/gitlab-org/gitlab/merge_requests/16954
+ # This MR got split into other since it was too big.
+ #
+ # params.permit(:cleanup)
+ {}
+ end
+
def update_params
if cluster.provided_by_user?
params.require(:cluster).permit(
@@ -139,6 +187,7 @@ class Clusters::ClustersController < Clusters::BaseController
:environment_scope,
:managed,
:base_domain,
+ :management_project_id,
platform_kubernetes_attributes: [
:api_url,
:token,
@@ -152,6 +201,7 @@ class Clusters::ClustersController < Clusters::BaseController
:environment_scope,
:managed,
:base_domain,
+ :management_project_id,
platform_kubernetes_attributes: [
:namespace
]
@@ -179,6 +229,28 @@ class Clusters::ClustersController < Clusters::BaseController
)
end
+ def create_aws_cluster_params
+ params.require(:cluster).permit(
+ :enabled,
+ :name,
+ :environment_scope,
+ :managed,
+ provider_aws_attributes: [
+ :key_name,
+ :role_arn,
+ :region,
+ :vpc_id,
+ :instance_type,
+ :num_nodes,
+ :security_group_id,
+ subnet_ids: []
+ ]).merge(
+ provider_type: :aws,
+ platform_type: :kubernetes,
+ clusterable: clusterable.subject
+ )
+ end
+
def create_user_cluster_params
params.require(:cluster).permit(
:enabled,
@@ -198,6 +270,10 @@ class Clusters::ClustersController < Clusters::BaseController
)
end
+ def create_role_params
+ params.require(:cluster).permit(:role_arn, :role_external_id)
+ end
+
def generate_gcp_authorize_url
params = Feature.enabled?(:create_eks_clusters) ? { provider: :gke } : {}
state = generate_session_key_redirect(clusterable.new_path(params).to_s)
diff --git a/app/controllers/concerns/confirm_email_warning.rb b/app/controllers/concerns/confirm_email_warning.rb
index 5a4b5897a4..86df001066 100644
--- a/app/controllers/concerns/confirm_email_warning.rb
+++ b/app/controllers/concerns/confirm_email_warning.rb
@@ -16,7 +16,7 @@ module ConfirmEmailWarning
email = current_user.unconfirmed_email || current_user.email
- flash.now[:warning] = _("Please check your email (%{email}) to verify that you own this address. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}.").html_safe % {
+ flash.now[:warning] = _("Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}.").html_safe % {
email: email,
resend_link: view_context.link_to(_('Resend it'), user_confirmation_path(user: { email: email }), method: :post),
update_link: view_context.link_to(_('Update it'), profile_path)
diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb
index c9a8de0b29..5aa00af891 100644
--- a/app/controllers/concerns/issuable_collections.rb
+++ b/app/controllers/concerns/issuable_collections.rb
@@ -148,7 +148,7 @@ module IssuableCollections
when 'Issue'
common_attributes + [:project, project: :namespace]
when 'MergeRequest'
- common_attributes + [:target_project, source_project: :route, head_pipeline: :project, target_project: :namespace, latest_merge_request_diff: :merge_request_diff_commits]
+ common_attributes + [:target_project, :latest_merge_request_diff, source_project: :route, head_pipeline: :project, target_project: :namespace]
end
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
diff --git a/app/controllers/concerns/lfs_request.rb b/app/controllers/concerns/lfs_request.rb
index 417bb169f3..61072eec53 100644
--- a/app/controllers/concerns/lfs_request.rb
+++ b/app/controllers/concerns/lfs_request.rb
@@ -56,7 +56,7 @@ module LfsRequest
documentation_url: help_url
},
content_type: CONTENT_TYPE,
- status: 403
+ status: :forbidden
)
end
@@ -67,7 +67,7 @@ module LfsRequest
documentation_url: help_url
},
content_type: CONTENT_TYPE,
- status: 404
+ status: :not_found
)
end
diff --git a/app/controllers/concerns/metrics_dashboard.rb b/app/controllers/concerns/metrics_dashboard.rb
index 62efdacb71..dc392147cb 100644
--- a/app/controllers/concerns/metrics_dashboard.rb
+++ b/app/controllers/concerns/metrics_dashboard.rb
@@ -3,21 +3,26 @@
# Provides an action which fetches a metrics dashboard according
# to the parameters specified by the controller.
module MetricsDashboard
+ include RenderServiceResults
+ include ChecksCollaboration
+
extend ActiveSupport::Concern
def metrics_dashboard
result = dashboard_finder.find(
project_for_dashboard,
current_user,
- metrics_dashboard_params
+ metrics_dashboard_params.to_h.symbolize_keys
)
- if include_all_dashboards?
- result[:all_dashboards] = dashboard_finder.find_all_paths(project_for_dashboard)
+ if include_all_dashboards? && result
+ result[:all_dashboards] = all_dashboards
end
respond_to do |format|
- if result[:status] == :success
+ if result.nil?
+ format.json { continue_polling_response }
+ elsif result[:status] == :success
format.json { render dashboard_success_response(result) }
else
format.json { render dashboard_error_response(result) }
@@ -27,6 +32,30 @@ module MetricsDashboard
private
+ def all_dashboards
+ dashboards = dashboard_finder.find_all_paths(project_for_dashboard)
+ dashboards.map do |dashboard|
+ amend_dashboard(dashboard)
+ end
+ end
+
+ def amend_dashboard(dashboard)
+ project_dashboard = project_for_dashboard && !dashboard[:system_dashboard]
+
+ dashboard[:can_edit] = project_dashboard ? can_edit?(dashboard) : false
+ dashboard[:project_blob_path] = project_dashboard ? dashboard_project_blob_path(dashboard) : nil
+
+ dashboard
+ end
+
+ def dashboard_project_blob_path(dashboard)
+ project_blob_path(project_for_dashboard, File.join(project_for_dashboard.default_branch, dashboard.fetch(:path, "")))
+ end
+
+ def can_edit?(dashboard)
+ can_collaborate_with_project?(project_for_dashboard, ref: project_for_dashboard.default_branch)
+ end
+
# Override in class to provide arguments to the finder.
def metrics_dashboard_params
{}
@@ -56,7 +85,7 @@ module MetricsDashboard
def dashboard_error_response(result)
{
- status: result[:http_status],
+ status: result[:http_status] || :bad_request,
json: result.slice(:all_dashboards, :message, :status)
}
end
diff --git a/app/controllers/concerns/milestone_actions.rb b/app/controllers/concerns/milestone_actions.rb
index 672d31ec77..dbc575a148 100644
--- a/app/controllers/concerns/milestone_actions.rb
+++ b/app/controllers/concerns/milestone_actions.rb
@@ -53,12 +53,10 @@ module MilestoneActions
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def milestone_redirect_path
- if @project
- project_milestone_path(@project, @milestone)
- elsif @group
- group_milestone_path(@group, @milestone.safe_title, title: @milestone.title)
+ if @milestone.global_milestone?
+ url_for(action: :show, title: @milestone.title)
else
- dashboard_milestone_path(@milestone.safe_title, title: @milestone.title)
+ url_for(action: :show)
end
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
diff --git a/app/controllers/concerns/preview_markdown.rb b/app/controllers/concerns/preview_markdown.rb
index 2a9729b6ff..c7c9f2e9b7 100644
--- a/app/controllers/concerns/preview_markdown.rb
+++ b/app/controllers/concerns/preview_markdown.rb
@@ -5,19 +5,10 @@ module PreviewMarkdown
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def preview_markdown
- result = PreviewMarkdownService.new(@project, current_user, params).execute
-
- markdown_params =
- case controller_name
- when 'wikis' then { pipeline: :wiki, project_wiki: @project_wiki, page_slug: params[:id] }
- when 'snippets' then { skip_project_check: true }
- when 'groups' then { group: group }
- when 'projects' then projects_filter_params
- else {}
- end
+ result = PreviewMarkdownService.new(@project, current_user, markdown_service_params).execute
render json: {
- body: view_context.markdown(result[:text], markdown_params),
+ body: view_context.markdown(result[:text], markdown_context_params),
references: {
users: result[:users],
suggestions: SuggestionSerializer.new.represent_diff(result[:suggestions]),
@@ -26,11 +17,28 @@ module PreviewMarkdown
}
end
+ private
+
def projects_filter_params
{
issuable_state_filter_enabled: true,
suggestions_filter_enabled: params[:preview_suggestions].present?
}
end
+
+ def markdown_service_params
+ params
+ end
+
+ def markdown_context_params
+ case controller_name
+ when 'wikis' then { pipeline: :wiki, project_wiki: @project_wiki, page_slug: params[:id] }
+ when 'snippets' then { skip_project_check: true }
+ when 'groups' then { group: group }
+ when 'projects' then projects_filter_params
+ else {}
+ end.merge(requested_path: params[:path])
+ end
+
# rubocop:enable Gitlab/ModuleWithInstanceVariables
end
diff --git a/app/controllers/concerns/redirects_for_missing_path_on_tree.rb b/app/controllers/concerns/redirects_for_missing_path_on_tree.rb
new file mode 100644
index 0000000000..085afbf397
--- /dev/null
+++ b/app/controllers/concerns/redirects_for_missing_path_on_tree.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module RedirectsForMissingPathOnTree
+ def redirect_to_tree_root_for_missing_path(project, ref, path)
+ redirect_to project_tree_path(project, ref), notice: missing_path_on_ref(path, ref)
+ end
+
+ private
+
+ def missing_path_on_ref(path, ref)
+ _('"%{path}" did not exist on "%{ref}"') % { path: truncate_path(path), ref: ref }
+ end
+
+ def truncate_path(path)
+ path.reverse.truncate(60, separator: "/").reverse
+ end
+end
diff --git a/app/controllers/concerns/renders_commits.rb b/app/controllers/concerns/renders_commits.rb
index ed9b898a2a..826fae834f 100644
--- a/app/controllers/concerns/renders_commits.rb
+++ b/app/controllers/concerns/renders_commits.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
module RendersCommits
- def limited_commits(commits)
- if commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
+ def limited_commits(commits, commits_count)
+ if commits_count > MergeRequestDiff::COMMITS_SAFE_SIZE
[
commits.first(MergeRequestDiff::COMMITS_SAFE_SIZE),
- commits.size - MergeRequestDiff::COMMITS_SAFE_SIZE
+ commits_count - MergeRequestDiff::COMMITS_SAFE_SIZE
]
else
[commits, 0]
@@ -14,9 +14,10 @@ module RendersCommits
# This is used as a helper method in a controller.
# rubocop: disable Gitlab/ModuleWithInstanceVariables
- def set_commits_for_rendering(commits)
- @total_commit_count = commits.size
- limited, @hidden_commit_count = limited_commits(commits)
+ def set_commits_for_rendering(commits, commits_count: nil)
+ @total_commit_count = commits_count || commits.size
+ limited, @hidden_commit_count = limited_commits(commits, @total_commit_count)
+ commits.each(&:lazy_author) # preload authors
prepare_commits_for_rendering(limited)
end
# rubocop: enable Gitlab/ModuleWithInstanceVariables
diff --git a/app/controllers/concerns/routable_actions.rb b/app/controllers/concerns/routable_actions.rb
index 45f9888a04..1b2e6461de 100644
--- a/app/controllers/concerns/routable_actions.rb
+++ b/app/controllers/concerns/routable_actions.rb
@@ -47,7 +47,7 @@ module RoutableActions
canonical_path = routable.full_path
if canonical_path != requested_full_path
- if canonical_path.casecmp(requested_full_path) != 0
+ if !request.xhr? && request.format.html? && canonical_path.casecmp(requested_full_path) != 0
flash[:notice] = "#{routable.class.to_s.titleize} '#{requested_full_path}' was moved to '#{canonical_path}'. Please update any links and bookmarks that may still have the old path."
end
diff --git a/app/controllers/concerns/sourcegraph_gon.rb b/app/controllers/concerns/sourcegraph_gon.rb
new file mode 100644
index 0000000000..ab4abd734f
--- /dev/null
+++ b/app/controllers/concerns/sourcegraph_gon.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module SourcegraphGon
+ extend ActiveSupport::Concern
+
+ included do
+ before_action :push_sourcegraph_gon, unless: :json_request?
+ end
+
+ private
+
+ def push_sourcegraph_gon
+ return unless sourcegraph_enabled?
+
+ gon.push({
+ sourcegraph: { url: Gitlab::CurrentSettings.sourcegraph_url }
+ })
+ end
+
+ def sourcegraph_enabled?
+ Gitlab::CurrentSettings.sourcegraph_enabled && sourcegraph_enabled_for_project? && current_user&.sourcegraph_enabled
+ end
+
+ def sourcegraph_enabled_for_project?
+ return false unless project && Gitlab::Sourcegraph.feature_enabled?(project)
+ return project.public? if Gitlab::CurrentSettings.sourcegraph_public_only
+
+ true
+ end
+end
diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb
index 80c0a0d88a..ebee8e9094 100644
--- a/app/controllers/dashboard/todos_controller.rb
+++ b/app/controllers/dashboard/todos_controller.rb
@@ -22,7 +22,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
respond_to do |format|
format.html do
redirect_to dashboard_todos_path,
- status: 302,
+ status: :found,
notice: _('To-do item successfully marked as done.')
end
format.js { head :ok }
@@ -34,7 +34,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
updated_ids = TodoService.new.mark_todos_as_done(@todos, current_user)
respond_to do |format|
- format.html { redirect_to dashboard_todos_path, status: 302, notice: _('Everything on your to-do list is marked as done.') }
+ format.html { redirect_to dashboard_todos_path, status: :found, notice: _('Everything on your to-do list is marked as done.') }
format.js { head :ok }
format.json { render json: todos_counts.merge(updated_ids: updated_ids) }
end
diff --git a/app/controllers/groups/boards_controller.rb b/app/controllers/groups/boards_controller.rb
index 3c86f3108a..8c9bf17f01 100644
--- a/app/controllers/groups/boards_controller.rb
+++ b/app/controllers/groups/boards_controller.rb
@@ -6,7 +6,7 @@ class Groups::BoardsController < Groups::ApplicationController
before_action :assign_endpoint_vars
before_action do
- push_frontend_feature_flag(:multi_select_board)
+ push_frontend_feature_flag(:multi_select_board, default_enabled: true)
end
private
diff --git a/app/controllers/groups/group_links_controller.rb b/app/controllers/groups/group_links_controller.rb
new file mode 100644
index 0000000000..7965311c5f
--- /dev/null
+++ b/app/controllers/groups/group_links_controller.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+class Groups::GroupLinksController < Groups::ApplicationController
+ before_action :check_feature_flag!
+ before_action :authorize_admin_group!
+
+ def create
+ shared_with_group = Group.find(params[:shared_with_group_id]) if params[:shared_with_group_id].present?
+
+ if shared_with_group
+ result = Groups::GroupLinks::CreateService
+ .new(shared_with_group, current_user, group_link_create_params)
+ .execute(group)
+
+ return render_404 if result[:http_status] == 404
+
+ flash[:alert] = result[:message] if result[:status] == :error
+ else
+ flash[:alert] = _('Please select a group.')
+ end
+
+ redirect_to group_group_members_path(group)
+ end
+
+ private
+
+ def group_link_create_params
+ params.permit(:shared_group_access, :expires_at)
+ end
+
+ def check_feature_flag!
+ render_404 unless Feature.enabled?(:share_group_with_group)
+ end
+end
diff --git a/app/controllers/groups/labels_controller.rb b/app/controllers/groups/labels_controller.rb
index 26768c628c..1034ca6cd7 100644
--- a/app/controllers/groups/labels_controller.rb
+++ b/app/controllers/groups/labels_controller.rb
@@ -63,7 +63,7 @@ class Groups::LabelsController < Groups::ApplicationController
respond_to do |format|
format.html do
- redirect_to group_labels_path(@group), status: 302, notice: "#{@label.name} deleted permanently"
+ redirect_to group_labels_path(@group), status: :found, notice: "#{@label.name} deleted permanently"
end
format.js
end
diff --git a/app/controllers/groups/registry/repositories_controller.rb b/app/controllers/groups/registry/repositories_controller.rb
index e09a9e6eb2..cfddd8a3ba 100644
--- a/app/controllers/groups/registry/repositories_controller.rb
+++ b/app/controllers/groups/registry/repositories_controller.rb
@@ -16,7 +16,7 @@ module Groups
render json: ContainerRepositoriesSerializer
.new(current_user: current_user)
- .represent(@images)
+ .represent_read_only(@images)
end
end
end
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 35e364abba..755d97b091 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -6,6 +6,7 @@ class GroupsController < Groups::ApplicationController
include ParamsBackwardCompatibility
include PreviewMarkdown
include RecordUserLastActivity
+ extend ::Gitlab::Utils::Override
respond_to :html
@@ -24,6 +25,10 @@ class GroupsController < Groups::ApplicationController
before_action :user_actions, only: [:show]
+ before_action do
+ push_frontend_feature_flag(:vue_issuables_list, @group)
+ end
+
skip_cross_project_access_check :index, :new, :create, :edit, :update,
:destroy, :projects
# When loading show as an atom feed, we render events that could leak cross
@@ -111,7 +116,7 @@ class GroupsController < Groups::ApplicationController
def destroy
Groups::DestroyService.new(@group, current_user).async_execute
- redirect_to root_path, status: 302, alert: "Group '#{@group.name}' was scheduled for deletion."
+ redirect_to root_path, status: :found, alert: "Group '#{@group.name}' was scheduled for deletion."
end
# rubocop: disable CodeReuse/ActiveRecord
@@ -233,6 +238,11 @@ class GroupsController < Groups::ApplicationController
@group.self_and_descendants.public_or_visible_to_user(current_user)
end
end
+
+ override :markdown_service_params
+ def markdown_service_params
+ params.merge(group: group)
+ end
end
GroupsController.prepend_if_ee('EE::GroupsController')
diff --git a/app/controllers/health_controller.rb b/app/controllers/health_controller.rb
index efd5f0fc60..c6a0225089 100644
--- a/app/controllers/health_controller.rb
+++ b/app/controllers/health_controller.rb
@@ -5,6 +5,11 @@ class HealthController < ActionController::Base
include RequiresWhitelistedMonitoringClient
CHECKS = [
+ Gitlab::HealthChecks::MasterCheck
+ ].freeze
+
+ ALL_CHECKS = [
+ *CHECKS,
Gitlab::HealthChecks::DbCheck,
Gitlab::HealthChecks::Redis::RedisCheck,
Gitlab::HealthChecks::Redis::CacheCheck,
@@ -14,8 +19,9 @@ class HealthController < ActionController::Base
].freeze
def readiness
- # readiness check is a collection with all above application-level checks
- render_checks(*CHECKS)
+ # readiness check is a collection of application-level checks
+ # and optionally all service checks
+ render_checks(params[:all] ? ALL_CHECKS : CHECKS)
end
def liveness
@@ -25,7 +31,7 @@ class HealthController < ActionController::Base
private
- def render_checks(*checks)
+ def render_checks(checks = [])
result = Gitlab::HealthChecks::Probes::Collection
.new(*checks)
.execute
diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb
index a58235790a..97895d6461 100644
--- a/app/controllers/help_controller.rb
+++ b/app/controllers/help_controller.rb
@@ -36,7 +36,7 @@ class HelpController < ApplicationController
render 'show.html.haml'
else
# Force template to Haml
- render 'errors/not_found.html.haml', layout: 'errors', status: 404
+ render 'errors/not_found.html.haml', layout: 'errors', status: :not_found
end
end
diff --git a/app/controllers/ldap/omniauth_callbacks_controller.rb b/app/controllers/ldap/omniauth_callbacks_controller.rb
index 4d8875937e..71a88bf339 100644
--- a/app/controllers/ldap/omniauth_callbacks_controller.rb
+++ b/app/controllers/ldap/omniauth_callbacks_controller.rb
@@ -4,7 +4,7 @@ class Ldap::OmniauthCallbacksController < OmniauthCallbacksController
extend ::Gitlab::Utils::Override
def self.define_providers!
- return unless Gitlab::Auth::LDAP::Config.enabled?
+ return unless Gitlab::Auth::LDAP::Config.sign_in_enabled?
Gitlab::Auth::LDAP::Config.available_servers.each do |server|
alias_method server['provider_name'], :ldap
@@ -14,6 +14,8 @@ class Ldap::OmniauthCallbacksController < OmniauthCallbacksController
# We only find ourselves here
# if the authentication to LDAP was successful.
def ldap
+ return unless Gitlab::Auth::LDAP::Config.sign_in_enabled?
+
sign_in_user_flow(Gitlab::Auth::LDAP::User)
end
diff --git a/app/controllers/notification_settings_controller.rb b/app/controllers/notification_settings_controller.rb
index c97fec0a6e..e5d4a4bb07 100644
--- a/app/controllers/notification_settings_controller.rb
+++ b/app/controllers/notification_settings_controller.rb
@@ -16,12 +16,7 @@ class NotificationSettingsController < ApplicationController
@notification_setting = current_user.notification_settings.find(params[:id])
@saved = @notification_setting.update(notification_setting_params_for(@notification_setting.source))
- if params[:hide_label].present?
- btn_class = params[:project_id].present? ? 'btn-xs' : ''
- render_response("shared/notifications/_new_button", btn_class)
- else
- render_response
- end
+ render_response
end
private
@@ -42,7 +37,16 @@ class NotificationSettingsController < ApplicationController
can?(current_user, ability_name, resource)
end
- def render_response(response_template = "shared/notifications/_button", btn_class = nil)
+ def render_response
+ btn_class = nil
+
+ if params[:hide_label].present?
+ btn_class = 'btn-xs' if params[:project_id].present?
+ response_template = 'shared/notifications/_new_button'
+ else
+ response_template = 'shared/notifications/_button'
+ end
+
render json: {
html: view_to_html_string(response_template, notification_setting: @notification_setting, btn_class: btn_class),
saved: @saved
diff --git a/app/controllers/oauth/applications_controller.rb b/app/controllers/oauth/applications_controller.rb
index 12dc2d1af1..8dd51ce1d6 100644
--- a/app/controllers/oauth/applications_controller.rb
+++ b/app/controllers/oauth/applications_controller.rb
@@ -57,7 +57,7 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
end
rescue_from ActiveRecord::RecordNotFound do |exception|
- render "errors/not_found", layout: "errors", status: 404
+ render "errors/not_found", layout: "errors", status: :not_found
end
def create_application_params
diff --git a/app/controllers/oauth/authorized_applications_controller.rb b/app/controllers/oauth/authorized_applications_controller.rb
index a59ade559b..9cfa57c53a 100644
--- a/app/controllers/oauth/authorized_applications_controller.rb
+++ b/app/controllers/oauth/authorized_applications_controller.rb
@@ -13,7 +13,7 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
end
redirect_to applications_profile_url,
- status: 302,
+ status: :found,
notice: I18n.t(:notice, scope: [:doorkeeper, :flash, :authorized_applications, :destroy])
end
end
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index b992972dfb..eca58748cc 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -47,7 +47,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def omniauth_error
@provider = params[:provider]
@error = params[:error]
- render 'errors/omniauth_error', layout: "oauth_error", status: 422
+ render 'errors/omniauth_error', layout: "oauth_error", status: :unprocessable_entity
end
def cas3
diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb
index 42d4d78517..214640a529 100644
--- a/app/controllers/profiles/preferences_controller.rb
+++ b/app/controllers/profiles/preferences_controller.rb
@@ -47,7 +47,8 @@ class Profiles::PreferencesController < Profiles::ApplicationController
:preferred_language,
:time_display_relative,
:time_format_in_24h,
- :show_whitespace_in_diffs
+ :show_whitespace_in_diffs,
+ :sourcegraph_enabled
]
end
end
diff --git a/app/controllers/profiles/u2f_registrations_controller.rb b/app/controllers/profiles/u2f_registrations_controller.rb
index 866c4dee6e..84ce4a56e6 100644
--- a/app/controllers/profiles/u2f_registrations_controller.rb
+++ b/app/controllers/profiles/u2f_registrations_controller.rb
@@ -4,6 +4,6 @@ class Profiles::U2fRegistrationsController < Profiles::ApplicationController
def destroy
u2f_registration = current_user.u2f_registrations.find(params[:id])
u2f_registration.destroy
- redirect_to profile_two_factor_auth_path, status: 302, notice: _("Successfully deleted U2F device.")
+ redirect_to profile_two_factor_auth_path, status: :found, notice: _("Successfully deleted U2F device.")
end
end
diff --git a/app/controllers/projects/blame_controller.rb b/app/controllers/projects/blame_controller.rb
index 9076bdb9f0..92655d593d 100644
--- a/app/controllers/projects/blame_controller.rb
+++ b/app/controllers/projects/blame_controller.rb
@@ -3,6 +3,7 @@
# Controller for viewing a file's blame
class Projects::BlameController < Projects::ApplicationController
include ExtractsPath
+ include RedirectsForMissingPathOnTree
before_action :require_non_empty_project
before_action :assign_ref_vars
@@ -11,7 +12,9 @@ class Projects::BlameController < Projects::ApplicationController
def show
@blob = @repository.blob_at(@commit.id, @path)
- return render_404 unless @blob
+ unless @blob
+ return redirect_to_tree_root_for_missing_path(@project, @ref, @path)
+ end
environment_params = @repository.branch_exists?(@ref) ? { ref: @ref } : { commit: @commit }
@environment = EnvironmentsFinder.new(@project, current_user, environment_params).execute.last
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 7c3d43fb49..7c97f771a7 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -7,6 +7,9 @@ class Projects::BlobController < Projects::ApplicationController
include RendersBlob
include NotesHelper
include ActionView::Helpers::SanitizeHelper
+ include RedirectsForMissingPathOnTree
+ include SourcegraphGon
+
prepend_before_action :authenticate_user!, only: [:edit]
around_action :allow_gitaly_ref_name_caching, only: [:show]
@@ -119,7 +122,7 @@ class Projects::BlobController < Projects::ApplicationController
end
end
- return render_404
+ return redirect_to_tree_root_for_missing_path(@project, @ref, @path)
end
end
diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb
index 3b335fa4af..db05da0bb7 100644
--- a/app/controllers/projects/boards_controller.rb
+++ b/app/controllers/projects/boards_controller.rb
@@ -8,7 +8,7 @@ class Projects::BoardsController < Projects::ApplicationController
before_action :authorize_read_board!, only: [:index, :show]
before_action :assign_endpoint_vars
before_action do
- push_frontend_feature_flag(:multi_select_board)
+ push_frontend_feature_flag(:multi_select_board, default_enabled: true)
end
private
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 939a09d4fd..afb670b687 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -8,6 +8,7 @@ class Projects::CommitController < Projects::ApplicationController
include CreatesCommit
include DiffForPath
include DiffHelper
+ include SourcegraphGon
# Authorize
before_action :require_non_empty_project
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index c053ca19a9..4562296cea 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -13,8 +13,6 @@ class Projects::EnvironmentsController < Projects::ApplicationController
before_action :verify_api_request!, only: :terminal_websocket_authorize
before_action :expire_etag_cache, only: [:index]
before_action only: [:metrics, :additional_metrics, :metrics_dashboard] do
- push_frontend_feature_flag(:environment_metrics_use_prometheus_endpoint, default_enabled: true)
- push_frontend_feature_flag(:environment_metrics_additional_panel_types)
push_frontend_feature_flag(:prometheus_computed_alerts)
end
@@ -133,7 +131,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
if environment
redirect_to environment_metrics_path(environment)
else
- render :empty
+ render :empty_metrics
end
end
@@ -199,8 +197,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
def metrics_dashboard_params
params
- .permit(:embedded, :group, :title, :y_label)
- .to_h.symbolize_keys
+ .permit(:embedded, :group, :title, :y_label, :dashboard_path, :environment)
.merge(dashboard_path: params[:dashboard], environment: environment)
end
diff --git a/app/controllers/projects/error_tracking_controller.rb b/app/controllers/projects/error_tracking_controller.rb
index 88d0755f41..7143424473 100644
--- a/app/controllers/projects/error_tracking_controller.rb
+++ b/app/controllers/projects/error_tracking_controller.rb
@@ -2,6 +2,7 @@
class Projects::ErrorTrackingController < Projects::ApplicationController
before_action :authorize_read_sentry_issue!
+ before_action :set_issue_id, only: [:details, :stack_trace]
POLLING_INTERVAL = 10_000
@@ -15,6 +16,23 @@ class Projects::ErrorTrackingController < Projects::ApplicationController
end
end
+ def details
+ respond_to do |format|
+ format.html
+ format.json do
+ render_issue_detail_json
+ end
+ end
+ end
+
+ def stack_trace
+ respond_to do |format|
+ format.json do
+ render_issue_stack_trace_json
+ end
+ end
+ end
+
def list_projects
respond_to do |format|
format.json do
@@ -29,10 +47,7 @@ class Projects::ErrorTrackingController < Projects::ApplicationController
service = ErrorTracking::ListIssuesService.new(project, current_user)
result = service.execute
- unless result[:status] == :success
- return render json: { message: result[:message] },
- status: result[:http_status] || :bad_request
- end
+ return if handle_errors(result)
render json: {
errors: serialize_errors(result[:issues]),
@@ -40,6 +55,28 @@ class Projects::ErrorTrackingController < Projects::ApplicationController
}
end
+ def render_issue_detail_json
+ service = ErrorTracking::IssueDetailsService.new(project, current_user, issue_details_params)
+ result = service.execute
+
+ return if handle_errors(result)
+
+ render json: {
+ error: serialize_detailed_error(result[:issue])
+ }
+ end
+
+ def render_issue_stack_trace_json
+ service = ErrorTracking::IssueLatestEventService.new(project, current_user, issue_details_params)
+ result = service.execute
+
+ return if handle_errors(result)
+
+ render json: {
+ error: serialize_error_event(result[:latest_event])
+ }
+ end
+
def render_project_list_json
service = ErrorTracking::ListProjectsService.new(
project,
@@ -62,10 +99,25 @@ class Projects::ErrorTrackingController < Projects::ApplicationController
end
end
+ def handle_errors(result)
+ unless result[:status] == :success
+ render json: { message: result[:message] },
+ status: result[:http_status] || :bad_request
+ end
+ end
+
def list_projects_params
params.require(:error_tracking_setting).permit([:api_host, :token])
end
+ def issue_details_params
+ params.permit(:issue_id)
+ end
+
+ def set_issue_id
+ @issue_id = issue_details_params[:issue_id]
+ end
+
def set_polling_interval
Gitlab::PollingInterval.set_header(response, interval: POLLING_INTERVAL)
end
@@ -76,6 +128,18 @@ class Projects::ErrorTrackingController < Projects::ApplicationController
.represent(errors)
end
+ def serialize_detailed_error(error)
+ ErrorTracking::DetailedErrorSerializer
+ .new(project: project, user: current_user)
+ .represent(error)
+ end
+
+ def serialize_error_event(event)
+ ErrorTracking::ErrorEventSerializer
+ .new(project: project, user: current_user)
+ .represent(event)
+ end
+
def serialize_projects(projects)
ErrorTracking::ProjectSerializer
.new(project: project, user: current_user)
diff --git a/app/controllers/projects/grafana_api_controller.rb b/app/controllers/projects/grafana_api_controller.rb
index 4bdf4c12ca..380a18818a 100644
--- a/app/controllers/projects/grafana_api_controller.rb
+++ b/app/controllers/projects/grafana_api_controller.rb
@@ -2,6 +2,7 @@
class Projects::GrafanaApiController < Projects::ApplicationController
include RenderServiceResults
+ include MetricsDashboard
def proxy
result = ::Grafana::ProxyService.new(
@@ -19,6 +20,10 @@ class Projects::GrafanaApiController < Projects::ApplicationController
private
+ def metrics_dashboard_params
+ params.permit(:embedded, :grafana_url)
+ end
+
def query_params
params.permit(:query, :start, :end, :step)
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 96cb400950..009765702a 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -44,6 +44,7 @@ class Projects::IssuesController < Projects::ApplicationController
before_action do
push_frontend_feature_flag(:vue_issuable_sidebar, project.group)
+ push_frontend_feature_flag(:release_search_filter, project)
end
respond_to :html
diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb
index 386a1f00bd..b7aeab8f5f 100644
--- a/app/controllers/projects/labels_controller.rb
+++ b/app/controllers/projects/labels_controller.rb
@@ -76,7 +76,7 @@ class Projects::LabelsController < Projects::ApplicationController
@labels = find_labels
redirect_to project_labels_path(@project),
- status: 302,
+ status: :found,
notice: 'Label was removed'
end
diff --git a/app/controllers/projects/lfs_api_controller.rb b/app/controllers/projects/lfs_api_controller.rb
index a1983bc546..1273c55b83 100644
--- a/app/controllers/projects/lfs_api_controller.rb
+++ b/app/controllers/projects/lfs_api_controller.rb
@@ -109,7 +109,7 @@ class Projects::LfsApiController < Projects::GitHttpClientController
message: lfs_read_only_message
},
content_type: LfsRequest::CONTENT_TYPE,
- status: 403
+ status: :forbidden
)
end
end
diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb
index 808265634d..78dc196b08 100644
--- a/app/controllers/projects/merge_requests/creations_controller.rb
+++ b/app/controllers/projects/merge_requests/creations_controller.rb
@@ -109,7 +109,13 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
@target_project = @merge_request.target_project
@source_project = @merge_request.source_project
- @commits = set_commits_for_rendering(@merge_request.commits)
+
+ @commits =
+ set_commits_for_rendering(
+ @merge_request.recent_commits.with_latest_pipeline(@merge_request.source_branch),
+ commits_count: @merge_request.commits_count
+ )
+
@commit = @merge_request.diff_head_commit
# FIXME: We have to assign a presenter to another instance variable
diff --git a/app/controllers/projects/merge_requests/diffs_controller.rb b/app/controllers/projects/merge_requests/diffs_controller.rb
index 4a37dfe5c1..42f9c0522a 100644
--- a/app/controllers/projects/merge_requests/diffs_controller.rb
+++ b/app/controllers/projects/merge_requests/diffs_controller.rb
@@ -31,6 +31,7 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
options = {
merge_request: @merge_request,
+ diff_view: diff_view,
pagination_data: diffs.pagination_data
}
@@ -60,7 +61,9 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
render: ->(partial, locals) { view_to_html_string(partial, locals) }
}
- render json: DiffsSerializer.new(request).represent(@diffs, additional_attributes)
+ options = additional_attributes.merge(diff_view: diff_view)
+
+ render json: DiffsSerializer.new(request).represent(@diffs, options)
end
def define_diff_vars
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index ff199e05e9..766ec1e33f 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -9,11 +9,12 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
include ToggleAwardEmoji
include IssuableCollections
include RecordUserLastActivity
+ include SourcegraphGon
skip_before_action :merge_request, only: [:index, :bulk_update]
before_action :whitelist_query_limiting, only: [:assign_related_issues, :update]
before_action :authorize_update_issuable!, only: [:close, :edit, :update, :remove_wip, :sort]
- before_action :authorize_test_reports!, only: [:test_reports]
+ before_action :authorize_read_actual_head_pipeline!, only: [:test_reports, :exposed_artifacts]
before_action :set_issuables_index, only: [:index]
before_action :authenticate_user!, only: [:assign_related_issues]
before_action :check_user_can_push_to_source_branch!, only: [:rebase]
@@ -23,6 +24,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
before_action do
push_frontend_feature_flag(:vue_issuable_sidebar, @project.group)
+ push_frontend_feature_flag(:release_search_filter, @project)
end
around_action :allow_gitaly_ref_name_caching, only: [:index, :show, :discussions]
@@ -89,7 +91,10 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
# Get commits from repository
# or from cache if already merged
@commits =
- set_commits_for_rendering(@merge_request.commits.with_latest_pipeline)
+ set_commits_for_rendering(
+ @merge_request.recent_commits.with_latest_pipeline(@merge_request.source_branch),
+ commits_count: @merge_request.commits_count
+ )
render json: { html: view_to_html_string('projects/merge_requests/_commits') }
end
@@ -115,6 +120,14 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
reports_response(@merge_request.compare_test_reports)
end
+ def exposed_artifacts
+ if @merge_request.has_exposed_artifacts?
+ reports_response(@merge_request.find_exposed_artifacts)
+ else
+ head :no_content
+ end
+ end
+
def edit
define_edit_vars
end
@@ -218,6 +231,8 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
@merge_request.rebase_async(current_user.id)
head :ok
+ rescue MergeRequest::RebaseLockTimeout => e
+ render json: { merge_error: e.message }, status: :conflict
end
def discussions
@@ -241,7 +256,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
end
def merge_params_attributes
- [:should_remove_source_branch, :commit_message, :squash_commit_message, :squash, :auto_merge_strategy]
+ MergeRequest::KNOWN_MERGE_PARAMS
end
def auto_merge_requested?
@@ -281,7 +296,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
return :sha_mismatch if params[:sha] != @merge_request.diff_head_sha
- @merge_request.update(merge_error: nil, squash: merge_params.fetch(:squash, false))
+ @merge_request.update(merge_error: nil, squash: params.fetch(:squash, false))
if auto_merge_requested?
if merge_request.auto_merge_enabled?
@@ -353,12 +368,11 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
when :error
render json: { status_reason: report_comparison[:status_reason] }, status: :bad_request
else
- render json: { status_reason: 'Unknown error' }, status: :internal_server_error
+ raise "Failed to build comparison response as comparison yielded unknown status '#{report_comparison[:status]}'"
end
end
- def authorize_test_reports!
- # MergeRequest#actual_head_pipeline is the pipeline accessed in MergeRequest#compare_reports.
+ def authorize_read_actual_head_pipeline!
return render_404 unless can?(current_user, :read_build, merge_request.actual_head_pipeline)
end
end
diff --git a/app/controllers/projects/pages_controller.rb b/app/controllers/projects/pages_controller.rb
index 73e629ab7c..722fc30b3f 100644
--- a/app/controllers/projects/pages_controller.rb
+++ b/app/controllers/projects/pages_controller.rb
@@ -21,7 +21,7 @@ class Projects::PagesController < Projects::ApplicationController
respond_to do |format|
format.html do
redirect_to project_pages_path(@project),
- status: 302,
+ status: :found,
notice: 'Pages were removed'
end
end
diff --git a/app/controllers/projects/pages_domains_controller.rb b/app/controllers/projects/pages_domains_controller.rb
index c287e440db..b693642981 100644
--- a/app/controllers/projects/pages_domains_controller.rb
+++ b/app/controllers/projects/pages_domains_controller.rb
@@ -8,6 +8,7 @@ class Projects::PagesDomainsController < Projects::ApplicationController
before_action :domain, except: [:new, :create]
def show
+ redirect_to edit_project_pages_domain_path(@project, @domain)
end
def new
@@ -23,7 +24,7 @@ class Projects::PagesDomainsController < Projects::ApplicationController
flash[:alert] = 'Failed to verify domain ownership'
end
- redirect_to project_pages_domain_path(@project, @domain)
+ redirect_to edit_project_pages_domain_path(@project, @domain)
end
def edit
@@ -33,7 +34,7 @@ class Projects::PagesDomainsController < Projects::ApplicationController
@domain = @project.pages_domains.create(create_params)
if @domain.valid?
- redirect_to project_pages_domain_path(@project, @domain)
+ redirect_to edit_project_pages_domain_path(@project, @domain)
else
render 'new'
end
@@ -42,7 +43,7 @@ class Projects::PagesDomainsController < Projects::ApplicationController
def update
if @domain.update(update_params)
redirect_to project_pages_path(@project),
- status: 302,
+ status: :found,
notice: 'Domain was updated'
else
render 'edit'
@@ -55,13 +56,21 @@ class Projects::PagesDomainsController < Projects::ApplicationController
respond_to do |format|
format.html do
redirect_to project_pages_path(@project),
- status: 302,
+ status: :found,
notice: 'Domain was removed'
end
format.js
end
end
+ def clean_certificate
+ unless @domain.update(user_provided_certificate: nil, user_provided_key: nil)
+ flash[:alert] = @domain.errors.full_messages.join(', ')
+ end
+
+ redirect_to edit_project_pages_domain_path(@project, @domain)
+ end
+
private
def create_params
@@ -69,7 +78,7 @@ class Projects::PagesDomainsController < Projects::ApplicationController
end
def update_params
- params.require(:pages_domain).permit(:user_provided_key, :user_provided_certificate, :auto_ssl_enabled)
+ params.fetch(:pages_domain, {}).permit(:user_provided_key, :user_provided_certificate, :auto_ssl_enabled)
end
def domain
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index 106ef1b72c..4d35353d5f 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -12,6 +12,7 @@ class Projects::PipelinesController < Projects::ApplicationController
before_action :authorize_update_pipeline!, only: [:retry, :cancel]
before_action do
push_frontend_feature_flag(:hide_dismissed_vulnerabilities)
+ push_frontend_feature_flag(:junit_pipeline_view)
end
around_action :allow_gitaly_ref_name_caching, only: [:index, :show]
@@ -156,14 +157,21 @@ class Projects::PipelinesController < Projects::ApplicationController
def test_report
return unless Feature.enabled?(:junit_pipeline_view, project)
- if pipeline_test_report == :error
- render json: { status: :error_parsing_report }
- return
- end
+ respond_to do |format|
+ format.html do
+ render 'show'
+ end
- render json: TestReportSerializer
- .new(current_user: @current_user)
- .represent(pipeline_test_report)
+ format.json do
+ if pipeline_test_report == :error
+ render json: { status: :error_parsing_report }
+ else
+ render json: TestReportSerializer
+ .new(current_user: @current_user)
+ .represent(pipeline_test_report)
+ end
+ end
+ end
end
private
diff --git a/app/controllers/projects/releases_controller.rb b/app/controllers/projects/releases_controller.rb
index 717df9f09e..72c82aec31 100644
--- a/app/controllers/projects/releases_controller.rb
+++ b/app/controllers/projects/releases_controller.rb
@@ -2,12 +2,48 @@
class Projects::ReleasesController < Projects::ApplicationController
# Authorize
- before_action :require_non_empty_project
+ before_action :require_non_empty_project, except: [:index]
+ before_action :release, only: %i[edit update]
before_action :authorize_read_release!
before_action do
- push_frontend_feature_flag(:release_edit_page, project)
+ push_frontend_feature_flag(:release_edit_page, project, default_enabled: true)
+ push_frontend_feature_flag(:release_issue_summary, project)
end
+ before_action :authorize_update_release!, only: %i[edit update]
def index
+ respond_to do |format|
+ format.html do
+ require_non_empty_project
+ end
+ format.json { render json: releases }
+ end
+ end
+
+ protected
+
+ def releases
+ ReleasesFinder.new(@project, current_user).execute
+ end
+
+ def edit
+ respond_to do |format|
+ format.html { render 'edit' }
+ end
+ end
+
+ private
+
+ def authorize_update_release!
+ access_denied! unless Feature.enabled?(:release_edit_page, project, default_enabled: true)
+ access_denied! unless can?(current_user, :update_release, release)
+ end
+
+ def release
+ @release ||= project.releases.find_by_tag!(sanitized_tag_name)
+ end
+
+ def sanitized_tag_name
+ CGI.unescape(params[:tag])
end
end
diff --git a/app/controllers/projects/settings/operations_controller.rb b/app/controllers/projects/settings/operations_controller.rb
index 5bf3618b38..1571cb8cd3 100644
--- a/app/controllers/projects/settings/operations_controller.rb
+++ b/app/controllers/projects/settings/operations_controller.rb
@@ -70,7 +70,7 @@ module Projects
project: [:slug, :name, :organization_slug, :organization_name]
],
- grafana_integration_attributes: [:token, :grafana_url]
+ grafana_integration_attributes: [:token, :grafana_url, :enabled]
}
end
end
diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb
index 7d9387b1d9..c89bfd110c 100644
--- a/app/controllers/projects/tags_controller.rb
+++ b/app/controllers/projects/tags_controller.rb
@@ -84,7 +84,7 @@ class Projects::TagsController < Projects::ApplicationController
format.html do
redirect_to project_tags_path(@project),
- alert: @error, status: 303
+ alert: @error, status: :see_other
end
format.js do
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
index 7509cc29a7..eec89afe35 100644
--- a/app/controllers/projects/tree_controller.rb
+++ b/app/controllers/projects/tree_controller.rb
@@ -5,6 +5,7 @@ class Projects::TreeController < Projects::ApplicationController
include ExtractsPath
include CreatesCommit
include ActionView::Helpers::SanitizeHelper
+ include RedirectsForMissingPathOnTree
around_action :allow_gitaly_ref_name_caching, only: [:show]
@@ -19,12 +20,9 @@ class Projects::TreeController < Projects::ApplicationController
if tree.entries.empty?
if @repository.blob_at(@commit.id, @path)
- return redirect_to(
- project_blob_path(@project,
- File.join(@ref, @path))
- )
+ return redirect_to project_blob_path(@project, File.join(@ref, @path))
elsif @path.present?
- return render_404
+ return redirect_to_tree_root_for_missing_path(@project, @ref, @path)
end
end
diff --git a/app/controllers/projects/usage_ping_controller.rb b/app/controllers/projects/usage_ping_controller.rb
new file mode 100644
index 0000000000..ebdf28bd59
--- /dev/null
+++ b/app/controllers/projects/usage_ping_controller.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class Projects::UsagePingController < Projects::ApplicationController
+ before_action :authenticate_user!
+
+ def web_ide_clientside_preview
+ return render_404 unless Gitlab::CurrentSettings.web_ide_clientside_preview_enabled?
+
+ Gitlab::UsageDataCounters::WebIdeCounter.increment_previews_count
+
+ head(200)
+ end
+end
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index b187fdb272..fb06299676 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -110,7 +110,7 @@ class Projects::WikisController < Projects::ApplicationController
WikiPages::DestroyService.new(@project, current_user).execute(@page)
redirect_to project_wiki_path(@project, :home),
- status: 302,
+ status: :found,
notice: _("Page was successfully deleted")
rescue Gitlab::Git::Wiki::OperationError => e
@error = e
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index abd19df9a3..e5dea031bb 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -154,7 +154,7 @@ class ProjectsController < Projects::ApplicationController
redirect_to dashboard_projects_path, status: :found
rescue Projects::DestroyService::DestroyError => ex
- redirect_to edit_project_path(@project), status: 302, alert: ex.message
+ redirect_to edit_project_path(@project), status: :found, alert: ex.message
end
def new_issuable_address
@@ -371,6 +371,7 @@ class ProjectsController < Projects::ApplicationController
:path,
:printing_merge_request_link_enabled,
:public_builds,
+ :remove_source_branch_after_merge,
:request_access_enabled,
:runners_token,
:tag_list,
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index 4a746fc915..5fc7f5c84f 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -8,7 +8,7 @@ class RegistrationsController < Devise::RegistrationsController
layout :choose_layout
- skip_before_action :require_role, only: [:welcome, :update_role]
+ skip_before_action :required_signup_info, only: [:welcome, :update_registration]
prepend_before_action :check_captcha, only: :create
before_action :whitelist_query_limiting, only: [:destroy]
before_action :ensure_terms_accepted,
@@ -16,6 +16,7 @@ class RegistrationsController < Devise::RegistrationsController
def new
if experiment_enabled?(:signup_flow)
+ track_experiment_event(:signup_flow, 'start') # We want this event to be tracked when the user is _in_ the experimental group
@resource = build_resource
else
redirect_to new_user_session_path(anchor: 'register-pane')
@@ -23,6 +24,8 @@ class RegistrationsController < Devise::RegistrationsController
end
def create
+ track_experiment_event(:signup_flow, 'end') unless experiment_enabled?(:signup_flow) # We want this event to be tracked when the user is _in_ the control group
+
accept_pending_invitations
super do |new_user|
@@ -42,29 +45,30 @@ class RegistrationsController < Devise::RegistrationsController
if destroy_confirmation_valid?
current_user.delete_async(deleted_by: current_user)
session.try(:destroy)
- redirect_to new_user_session_path, status: 303, notice: s_('Profiles|Account scheduled for removal.')
+ redirect_to new_user_session_path, status: :see_other, notice: s_('Profiles|Account scheduled for removal.')
else
- redirect_to profile_account_path, status: 303, alert: destroy_confirmation_failure_message
+ redirect_to profile_account_path, status: :see_other, alert: destroy_confirmation_failure_message
end
end
def welcome
return redirect_to new_user_registration_path unless current_user
- return redirect_to stored_location_or_dashboard_or_almost_there_path(current_user) if current_user.role.present?
+ return redirect_to stored_location_or_dashboard_or_almost_there_path(current_user) if current_user.role.present? && !current_user.setup_for_company.nil?
- current_user.name = nil
+ current_user.name = nil if current_user.name == current_user.username
render layout: 'devise_experimental_separate_sign_up_flow'
end
- def update_role
- user_params = params.require(:user).permit(:name, :role)
- result = ::Users::UpdateService.new(current_user, user_params.merge(user: current_user)).execute
+ def update_registration
+ user_params = params.require(:user).permit(:name, :role, :setup_for_company)
+ result = ::Users::SignupService.new(current_user, user_params).execute
if result[:status] == :success
+ track_experiment_event(:signup_flow, 'end') # We want this event to be tracked when the user is _in_ the experimental group
set_flash_message! :notice, :signed_up
redirect_to stored_location_or_dashboard_or_almost_there_path(current_user)
else
- redirect_to users_sign_up_welcome_path, alert: result[:message]
+ render :welcome, layout: 'devise_experimental_separate_sign_up_flow'
end
end
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 1c506065b5..0007d5826b 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -24,6 +24,7 @@ class SessionsController < Devise::SessionsController
before_action :store_unauthenticated_sessions, only: [:new]
before_action :save_failed_login, if: :action_new_and_failed_login?
before_action :load_recaptcha
+ before_action :frontend_tracking_data, only: [:new]
after_action :log_failed_login, if: :action_new_and_failed_login?
@@ -269,7 +270,13 @@ class SessionsController < Devise::SessionsController
end
def ldap_servers
- @ldap_servers ||= Gitlab::Auth::LDAP::Config.available_servers
+ @ldap_servers ||= begin
+ if Gitlab::Auth::LDAP::Config.sign_in_enabled?
+ Gitlab::Auth::LDAP::Config.available_servers
+ else
+ []
+ end
+ end
end
def unverified_anonymous_user?
@@ -293,6 +300,11 @@ class SessionsController < Devise::SessionsController
"standard"
end
end
+
+ def frontend_tracking_data
+ # We want tracking data pushed to the frontend when the user is _in_ the control group
+ frontend_experimentation_tracking_data(:signup_flow, 'start') unless experiment_enabled?(:signup_flow)
+ end
end
SessionsController.prepend_if_ee('EE::SessionsController')
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index c3c227b08c..06374736dc 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -16,7 +16,7 @@ class UsersController < ApplicationController
skip_before_action :authenticate_user!
prepend_before_action(only: [:show]) { authenticate_sessionless_user!(:rss) }
- before_action :user, except: [:exists]
+ before_action :user, except: [:exists, :suggests]
before_action :authorize_read_user_profile!,
only: [:calendar, :calendar_activities, :groups, :projects, :contributed_projects, :starred_projects, :snippets]
@@ -114,6 +114,14 @@ class UsersController < ApplicationController
render json: { exists: !!Namespace.find_by_path_or_name(params[:username]) }
end
+ def suggests
+ namespace_path = params[:username]
+ exists = !!Namespace.find_by_path_or_name(namespace_path)
+ suggestions = exists ? [Namespace.clean_path(namespace_path)] : []
+
+ render json: { exists: exists, suggests: suggestions }
+ end
+
private
def user
diff --git a/app/finders/abuse_reports_finder.rb b/app/finders/abuse_reports_finder.rb
new file mode 100644
index 0000000000..04043f3642
--- /dev/null
+++ b/app/finders/abuse_reports_finder.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class AbuseReportsFinder
+ attr_reader :params
+
+ def initialize(params = {})
+ @params = params
+ end
+
+ def execute
+ reports = AbuseReport.all
+ reports = reports.by_user(params[:user_id]) if params[:user_id].present?
+
+ reports.with_order_id_desc
+ .with_users
+ .page(params[:page])
+ end
+end
diff --git a/app/finders/admin/projects_finder.rb b/app/finders/admin/projects_finder.rb
index e2b9b0b44c..53dbf65c43 100644
--- a/app/finders/admin/projects_finder.rb
+++ b/app/finders/admin/projects_finder.rb
@@ -12,7 +12,7 @@ class Admin::ProjectsFinder
def execute
items = Project.without_deleted.with_statistics.with_route
items = by_namespace_id(items)
- items = by_visibilty_level(items)
+ items = by_visibility_level(items)
items = by_with_push(items)
items = by_abandoned(items)
items = by_last_repository_check_failed(items)
@@ -31,7 +31,7 @@ class Admin::ProjectsFinder
end
# rubocop: disable CodeReuse/ActiveRecord
- def by_visibilty_level(items)
+ def by_visibility_level(items)
params[:visibility_level].present? ? items.where(visibility_level: params[:visibility_level]) : items
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/finders/branches_finder.rb b/app/finders/branches_finder.rb
index 291a24c140..8001c70a9b 100644
--- a/app/finders/branches_finder.rb
+++ b/app/finders/branches_finder.rb
@@ -1,9 +1,8 @@
# frozen_string_literal: true
-class BranchesFinder
+class BranchesFinder < GitRefsFinder
def initialize(repository, params = {})
- @repository = repository
- @params = params
+ super(repository, params)
end
def execute
@@ -15,56 +14,10 @@ class BranchesFinder
private
- attr_reader :repository, :params
-
def names
@params[:names].presence
end
- def search
- @params[:search].presence
- end
-
- def sort
- @params[:sort].presence || 'name'
- end
-
- def by_search(branches)
- return branches unless search
-
- case search
- when ->(v) { v.starts_with?('^') }
- filter_branches_with_prefix(branches, search.slice(1..-1).upcase)
- when ->(v) { v.ends_with?('$') }
- filter_branches_with_suffix(branches, search.chop.upcase)
- else
- matches = filter_branches_by_name(branches, search.upcase)
- set_exact_match_as_first_result(matches, search)
- end
- end
-
- def filter_branches_with_prefix(branches, prefix)
- branches.select { |branch| branch.name.upcase.starts_with?(prefix) }
- end
-
- def filter_branches_with_suffix(branches, suffix)
- branches.select { |branch| branch.name.upcase.ends_with?(suffix) }
- end
-
- def filter_branches_by_name(branches, term)
- branches.select { |branch| branch.name.upcase.include?(term) }
- end
-
- def set_exact_match_as_first_result(matches, term)
- exact_match_index = find_exact_match_index(matches, term)
- matches.insert(0, matches.delete_at(exact_match_index)) if exact_match_index
- matches
- end
-
- def find_exact_match_index(matches, term)
- matches.index { |branch| branch.name.casecmp(term) == 0 }
- end
-
def by_names(branches)
return branches unless names
diff --git a/app/finders/container_repositories_finder.rb b/app/finders/container_repositories_finder.rb
index eb91d7f825..34921df840 100644
--- a/app/finders/container_repositories_finder.rb
+++ b/app/finders/container_repositories_finder.rb
@@ -1,34 +1,38 @@
# frozen_string_literal: true
class ContainerRepositoriesFinder
- # id: group or project id
- # container_type: :group or :project
- def initialize(id:, container_type:)
- @id = id
- @type = container_type.to_sym
+ VALID_SUBJECTS = [Group, Project].freeze
+
+ def initialize(user:, subject:)
+ @user = user
+ @subject = subject
end
def execute
- if project_type?
- project.container_repositories
- else
- group.container_repositories
- end
+ raise ArgumentError, "invalid subject_type" unless valid_subject_type?
+ return unless authorized?
+
+ return project_repositories if @subject.is_a?(Project)
+ return group_repositories if @subject.is_a?(Group)
end
private
- attr_reader :id, :type
-
- def project_type?
- type == :project
+ def valid_subject_type?
+ VALID_SUBJECTS.include?(@subject.class)
end
- def project
- Project.find(id)
+ def project_repositories
+ return unless @subject.container_registry_enabled
+
+ @subject.container_repositories
end
- def group
- Group.find(id)
+ def group_repositories
+ ContainerRepository.for_group_and_its_subgroups(@subject)
+ end
+
+ def authorized?
+ Ability.allowed?(@user, :read_container_image, @subject)
end
end
diff --git a/app/finders/git_refs_finder.rb b/app/finders/git_refs_finder.rb
new file mode 100644
index 0000000000..2289b34e56
--- /dev/null
+++ b/app/finders/git_refs_finder.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+class GitRefsFinder
+ def initialize(repository, params = {})
+ @repository = repository
+ @params = params
+ end
+
+ protected
+
+ attr_reader :repository, :params
+
+ def search
+ @params[:search].presence
+ end
+
+ def sort
+ @params[:sort].presence || 'name'
+ end
+
+ def by_search(refs)
+ return refs unless search
+
+ case search
+ when ->(v) { v.starts_with?('^') }
+ filter_refs_with_prefix(refs, search.slice(1..-1))
+ when ->(v) { v.ends_with?('$') }
+ filter_refs_with_suffix(refs, search.chop)
+ else
+ matches = filter_refs_by_name(refs, search)
+ set_exact_match_as_first_result(matches, search)
+ end
+ end
+
+ def filter_refs_with_prefix(refs, prefix)
+ refs.select { |ref| ref.name.upcase.starts_with?(prefix.upcase) }
+ end
+
+ def filter_refs_with_suffix(refs, suffix)
+ refs.select { |ref| ref.name.upcase.ends_with?(suffix.upcase) }
+ end
+
+ def filter_refs_by_name(refs, term)
+ refs.select { |ref| ref.name.upcase.include?(term.upcase) }
+ end
+
+ def set_exact_match_as_first_result(matches, term)
+ exact_match_index = find_exact_match_index(matches, term)
+ matches.insert(0, matches.delete_at(exact_match_index)) if exact_match_index
+ matches
+ end
+
+ def find_exact_match_index(matches, term)
+ matches.index { |ref| ref.name.casecmp(term) == 0 }
+ end
+end
diff --git a/app/finders/group_descendants_finder.rb b/app/finders/group_descendants_finder.rb
index 4e489a9c93..1f6829a97d 100644
--- a/app/finders/group_descendants_finder.rb
+++ b/app/finders/group_descendants_finder.rb
@@ -80,7 +80,7 @@ class GroupDescendantsFinder
if current_user
authorized_groups = GroupsFinder.new(current_user,
all_available: false)
- .execute.as('authorized')
+ .execute.arel.as('authorized')
authorized_to_user = groups_table.project(1).from(authorized_groups)
.where(authorized_groups[:id].eq(groups_table[:id]))
.exists
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 477093ddad..dfddd32d7d 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -385,6 +385,9 @@ class IssuableFinder
end
def count_key(value)
+ # value may be an array if the finder used in `count_by_state` added an
+ # additional `group by`. Anyway we are sure that state will be always the
+ # last item because it's added as the last one to the query.
value = Array(value).last
klass.available_states.key(value)
end
@@ -483,6 +486,7 @@ class IssuableFinder
# rubocop: disable CodeReuse/ActiveRecord
def by_search(items)
return items unless search
+ return items if items.is_a?(ActiveRecord::NullRelation)
if use_cte_for_search?
cte = Gitlab::SQL::RecursiveCTE.new(klass.table_name)
diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb
index df06e68c94..42a15234e5 100644
--- a/app/finders/projects_finder.rb
+++ b/app/finders/projects_finder.rb
@@ -110,7 +110,10 @@ class ProjectsFinder < UnionFinder
# rubocop: disable CodeReuse/ActiveRecord
def by_ids(items)
- project_ids_relation ? items.where(id: project_ids_relation) : items
+ items = items.where(id: project_ids_relation) if project_ids_relation
+ items = items.where('id > ?', params[:id_after]) if params[:id_after]
+ items = items.where('id < ?', params[:id_before]) if params[:id_before]
+ items
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/finders/prometheus_metrics_finder.rb b/app/finders/prometheus_metrics_finder.rb
new file mode 100644
index 0000000000..84a071abbd
--- /dev/null
+++ b/app/finders/prometheus_metrics_finder.rb
@@ -0,0 +1,129 @@
+# frozen_string_literal: true
+
+class PrometheusMetricsFinder
+ ACCEPTED_PARAMS = [
+ :project,
+ :group,
+ :title,
+ :y_label,
+ :identifier,
+ :id,
+ :common,
+ :ordered
+ ].freeze
+
+ # Cautiously preferring a memoized class method over a constant
+ # so that the DB connection is accessed after the class is loaded.
+ def self.indexes
+ @indexes ||= PrometheusMetric
+ .connection
+ .indexes(:prometheus_metrics)
+ .map { |index| index.columns.map(&:to_sym) }
+ end
+
+ def initialize(params = {})
+ @params = params.slice(*ACCEPTED_PARAMS)
+ end
+
+ # @return [PrometheusMetric, PrometheusMetric::ActiveRecord_Relation]
+ def execute
+ validate_params!
+
+ metrics = by_project(::PrometheusMetric.all)
+ metrics = by_group(metrics)
+ metrics = by_title(metrics)
+ metrics = by_y_label(metrics)
+ metrics = by_common(metrics)
+ metrics = by_ordered(metrics)
+ metrics = by_identifier(metrics)
+ metrics = by_id(metrics)
+
+ metrics
+ end
+
+ private
+
+ attr_reader :params
+
+ def by_project(metrics)
+ return metrics unless params[:project]
+
+ metrics.for_project(params[:project])
+ end
+
+ def by_group(metrics)
+ return metrics unless params[:group]
+
+ metrics.for_group(params[:group])
+ end
+
+ def by_title(metrics)
+ return metrics unless params[:title]
+
+ metrics.for_title(params[:title])
+ end
+
+ def by_y_label(metrics)
+ return metrics unless params[:y_label]
+
+ metrics.for_y_label(params[:y_label])
+ end
+
+ def by_common(metrics)
+ return metrics unless params[:common]
+
+ metrics.common
+ end
+
+ def by_ordered(metrics)
+ return metrics unless params[:ordered]
+
+ metrics.ordered
+ end
+
+ def by_identifier(metrics)
+ return metrics unless params[:identifier]
+
+ metrics.for_identifier(params[:identifier])
+ end
+
+ def by_id(metrics)
+ return metrics unless params[:id]
+
+ metrics.id_in(params[:id])
+ end
+
+ def validate_params!
+ validate_params_present!
+ validate_id_params!
+ validate_indexes!
+ end
+
+ # Ensure all provided params are supported
+ def validate_params_present!
+ raise ArgumentError, "Please provide one or more of: #{ACCEPTED_PARAMS}" if params.blank?
+ end
+
+ # Protect against the caller "finding" the wrong metric
+ def validate_id_params!
+ raise ArgumentError, 'Only one of :identifier, :id is permitted' if params[:identifier] && params[:id]
+ raise ArgumentError, ':identifier must be scoped to a :project or :common' if params[:identifier] && !(params[:project] || params[:common])
+ end
+
+ # Protect against unaccounted-for, complex/slow queries.
+ # This is not a hard and fast rule, but is meant to encourage
+ # mindful inclusion of new queries.
+ def validate_indexes!
+ indexable_params = params.except(:ordered, :id, :project).keys
+ indexable_params << :project_id if params[:project]
+ indexable_params.sort!
+
+ raise ArgumentError, "An index should exist for params: #{indexable_params}" unless appropriate_index?(indexable_params)
+ end
+
+ def appropriate_index?(indexable_params)
+ return true if indexable_params.blank?
+
+ self.class.indexes.any? { |index| (index - indexable_params).empty? }
+ end
+end
diff --git a/app/finders/releases_finder.rb b/app/finders/releases_finder.rb
index 59e84198fd..72bf968c8e 100644
--- a/app/finders/releases_finder.rb
+++ b/app/finders/releases_finder.rb
@@ -6,9 +6,11 @@ class ReleasesFinder
@current_user = current_user
end
- def execute
+ def execute(preload: true)
return Release.none unless Ability.allowed?(@current_user, :read_release, @project)
- @project.releases.sorted
+ releases = @project.releases
+ releases = releases.preloaded if preload
+ releases.sorted
end
end
diff --git a/app/finders/tags_finder.rb b/app/finders/tags_finder.rb
index 2ffd46245e..fd58f478b4 100644
--- a/app/finders/tags_finder.rb
+++ b/app/finders/tags_finder.rb
@@ -1,31 +1,13 @@
# frozen_string_literal: true
-class TagsFinder
+class TagsFinder < GitRefsFinder
def initialize(repository, params)
- @repository = repository
- @params = params
+ super(repository, params)
end
def execute
- tags = @repository.tags_sorted_by(sort)
- filter_by_name(tags)
- end
-
- private
-
- def sort
- @params[:sort].presence
- end
-
- def search
- @params[:search].presence
- end
-
- def filter_by_name(tags)
- if search
- tags.select { |tag| tag.name.include?(search) }
- else
- tags
- end
+ tags = repository.tags_sorted_by(sort)
+ tags = by_search(tags)
+ tags
end
end
diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb
index 2b46e51290..e56009be33 100644
--- a/app/finders/todos_finder.rb
+++ b/app/finders/todos_finder.rb
@@ -23,10 +23,16 @@ class TodosFinder
NONE = '0'
- TODO_TYPES = Set.new(%w(Issue MergeRequest Epic)).freeze
+ TODO_TYPES = Set.new(%w(Issue MergeRequest)).freeze
attr_accessor :current_user, :params
+ class << self
+ def todo_types
+ TODO_TYPES
+ end
+ end
+
def initialize(current_user, params = {})
@current_user = current_user
@params = params
@@ -111,12 +117,6 @@ class TodosFinder
params[:group_id].present?
end
- def project
- strong_memoize(:project) do
- Project.find_without_deleted(params[:project_id]) if project?
- end
- end
-
def group
strong_memoize(:group) do
Group.find(params[:group_id])
@@ -124,7 +124,7 @@ class TodosFinder
end
def type?
- type.present? && TODO_TYPES.include?(type)
+ type.present? && self.class.todo_types.include?(type)
end
def type
@@ -175,7 +175,7 @@ class TodosFinder
def by_project(items)
if project?
- items.for_project(project)
+ items.for_undeleted_projects.for_project(params[:project_id])
else
items
end
@@ -188,11 +188,9 @@ class TodosFinder
end
def by_state(items)
- if params[:state].to_s == 'done'
- items.done
- else
- items.pending
- end
+ return items.pending if params[:state].blank?
+
+ items.with_states(params[:state])
end
def by_type(items)
@@ -203,3 +201,5 @@ class TodosFinder
end
end
end
+
+TodosFinder.prepend_if_ee('EE::TodosFinder')
diff --git a/app/graphql/gitlab_schema.rb b/app/graphql/gitlab_schema.rb
index 1899278ff3..a5ddf31657 100644
--- a/app/graphql/gitlab_schema.rb
+++ b/app/graphql/gitlab_schema.rb
@@ -46,7 +46,7 @@ class GitlabSchema < GraphQL::Schema
super(query_str, **kwargs)
end
- def id_from_object(object)
+ def id_from_object(object, _type = nil, _ctx = nil)
unless object.respond_to?(:to_global_id)
# This is an error in our schema and needs to be solved. So raise a
# more meaningful error message
@@ -57,7 +57,7 @@ class GitlabSchema < GraphQL::Schema
object.to_global_id
end
- def object_from_id(global_id)
+ def object_from_id(global_id, _ctx = nil)
gid = GlobalID.parse(global_id)
unless gid
diff --git a/app/graphql/mutations/merge_requests/set_assignees.rb b/app/graphql/mutations/merge_requests/set_assignees.rb
new file mode 100644
index 0000000000..8f0025f0a5
--- /dev/null
+++ b/app/graphql/mutations/merge_requests/set_assignees.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+module Mutations
+ module MergeRequests
+ class SetAssignees < Base
+ graphql_name 'MergeRequestSetAssignees'
+
+ argument :assignee_usernames,
+ [GraphQL::STRING_TYPE],
+ required: true,
+ description: <<~DESC
+ The usernames to assign to the merge request. Replaces existing assignees by default.
+ DESC
+
+ argument :operation_mode,
+ Types::MutationOperationModeEnum,
+ required: false,
+ description: <<~DESC
+ The operation to perform. Defaults to REPLACE.
+ DESC
+
+ def resolve(project_path:, iid:, assignee_usernames:, operation_mode: Types::MutationOperationModeEnum.enum[:replace])
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab/issues/36098')
+
+ merge_request = authorized_find!(project_path: project_path, iid: iid)
+ project = merge_request.project
+
+ assignee_ids = []
+ assignee_ids += merge_request.assignees.map(&:id) if Types::MutationOperationModeEnum.enum.values_at(:remove, :append).include?(operation_mode)
+ user_ids = UsersFinder.new(current_user, username: assignee_usernames).execute.map(&:id)
+
+ if operation_mode == Types::MutationOperationModeEnum.enum[:remove]
+ assignee_ids -= user_ids
+ else
+ assignee_ids |= user_ids
+ end
+
+ ::MergeRequests::UpdateService.new(project, current_user, assignee_ids: assignee_ids)
+ .execute(merge_request)
+
+ {
+ merge_request: merge_request,
+ errors: merge_request.errors.full_messages
+ }
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/merge_requests/set_labels.rb b/app/graphql/mutations/merge_requests/set_labels.rb
new file mode 100644
index 0000000000..71f7a353bc
--- /dev/null
+++ b/app/graphql/mutations/merge_requests/set_labels.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+module Mutations
+ module MergeRequests
+ class SetLabels < Base
+ graphql_name 'MergeRequestSetLabels'
+
+ argument :label_ids,
+ [GraphQL::ID_TYPE],
+ required: true,
+ description: <<~DESC
+ The Label IDs to set. Replaces existing labels by default.
+ DESC
+
+ argument :operation_mode,
+ Types::MutationOperationModeEnum,
+ required: false,
+ description: <<~DESC
+ Changes the operation mode. Defaults to REPLACE.
+ DESC
+
+ def resolve(project_path:, iid:, label_ids:, operation_mode: Types::MutationOperationModeEnum.enum[:replace])
+ merge_request = authorized_find!(project_path: project_path, iid: iid)
+ project = merge_request.project
+
+ label_ids = label_ids
+ .select(&method(:label_descendant?))
+ .map { |gid| GlobalID.parse(gid).model_id } # MergeRequests::UpdateService expects integers
+
+ attribute_name = case operation_mode
+ when Types::MutationOperationModeEnum.enum[:append]
+ :add_label_ids
+ when Types::MutationOperationModeEnum.enum[:remove]
+ :remove_label_ids
+ else
+ :label_ids
+ end
+
+ ::MergeRequests::UpdateService.new(project, current_user, attribute_name => label_ids)
+ .execute(merge_request)
+
+ {
+ merge_request: merge_request,
+ errors: merge_request.errors.full_messages
+ }
+ end
+
+ def label_descendant?(gid)
+ GlobalID.parse(gid)&.model_class&.ancestors&.include?(Label)
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/merge_requests/set_locked.rb b/app/graphql/mutations/merge_requests/set_locked.rb
new file mode 100644
index 0000000000..09aaa0b39a
--- /dev/null
+++ b/app/graphql/mutations/merge_requests/set_locked.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Mutations
+ module MergeRequests
+ class SetLocked < Base
+ graphql_name 'MergeRequestSetLocked'
+
+ argument :locked,
+ GraphQL::BOOLEAN_TYPE,
+ required: true,
+ description: <<~DESC
+ Whether or not to lock the merge request.
+ DESC
+
+ def resolve(project_path:, iid:, locked:)
+ merge_request = authorized_find!(project_path: project_path, iid: iid)
+ project = merge_request.project
+
+ ::MergeRequests::UpdateService.new(project, current_user, discussion_locked: locked)
+ .execute(merge_request)
+
+ {
+ merge_request: merge_request,
+ errors: merge_request.errors.full_messages
+ }
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/merge_requests/set_milestone.rb b/app/graphql/mutations/merge_requests/set_milestone.rb
new file mode 100644
index 0000000000..707d667795
--- /dev/null
+++ b/app/graphql/mutations/merge_requests/set_milestone.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Mutations
+ module MergeRequests
+ class SetMilestone < Base
+ graphql_name 'MergeRequestSetMilestone'
+
+ argument :milestone_id,
+ GraphQL::ID_TYPE,
+ required: false,
+ loads: Types::MilestoneType,
+ description: <<~DESC
+ The milestone to assign to the merge request.
+ DESC
+
+ def resolve(project_path:, iid:, milestone: nil)
+ merge_request = authorized_find!(project_path: project_path, iid: iid)
+ project = merge_request.project
+
+ ::MergeRequests::UpdateService.new(project, current_user, milestone: milestone)
+ .execute(merge_request)
+
+ {
+ merge_request: merge_request,
+ errors: merge_request.errors.full_messages
+ }
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/merge_requests/set_subscription.rb b/app/graphql/mutations/merge_requests/set_subscription.rb
new file mode 100644
index 0000000000..8675015277
--- /dev/null
+++ b/app/graphql/mutations/merge_requests/set_subscription.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Mutations
+ module MergeRequests
+ class SetSubscription < Base
+ graphql_name 'MergeRequestSetSubscription'
+
+ argument :subscribed_state,
+ GraphQL::BOOLEAN_TYPE,
+ required: true,
+ description: 'The desired state of the subscription'
+
+ def resolve(project_path:, iid:, subscribed_state:)
+ merge_request = authorized_find!(project_path: project_path, iid: iid)
+ project = merge_request.project
+
+ merge_request.set_subscription(current_user, subscribed_state, project)
+
+ {
+ merge_request: merge_request,
+ errors: merge_request.errors.full_messages
+ }
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/todos/base.rb b/app/graphql/mutations/todos/base.rb
new file mode 100644
index 0000000000..b6c7b320be
--- /dev/null
+++ b/app/graphql/mutations/todos/base.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Todos
+ class Base < ::Mutations::BaseMutation
+ private
+
+ def find_object(id:)
+ GitlabSchema.object_from_id(id)
+ end
+
+ def to_global_id(id)
+ ::URI::GID.build(app: GlobalID.app, model_name: Todo.name, model_id: id, params: nil).to_s
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/todos/mark_done.rb b/app/graphql/mutations/todos/mark_done.rb
new file mode 100644
index 0000000000..5483708b5c
--- /dev/null
+++ b/app/graphql/mutations/todos/mark_done.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Todos
+ class MarkDone < ::Mutations::Todos::Base
+ graphql_name 'TodoMarkDone'
+
+ authorize :update_todo
+
+ argument :id,
+ GraphQL::ID_TYPE,
+ required: true,
+ description: 'The global id of the todo to mark as done'
+
+ field :todo, Types::TodoType,
+ null: false,
+ description: 'The requested todo'
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def resolve(id:)
+ todo = authorized_find!(id: id)
+ mark_done(Todo.where(id: todo.id)) unless todo.done?
+
+ {
+ todo: todo.reset,
+ errors: errors_on_object(todo)
+ }
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ private
+
+ def mark_done(todo)
+ TodoService.new.mark_todos_as_done(todo, current_user)
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/base_resolver.rb b/app/graphql/resolvers/base_resolver.rb
index 5b7eb57841..85d6b37793 100644
--- a/app/graphql/resolvers/base_resolver.rb
+++ b/app/graphql/resolvers/base_resolver.rb
@@ -10,6 +10,14 @@ module Resolvers
end
end
+ def self.last
+ @last ||= Class.new(self) do
+ def resolve(**args)
+ super.last
+ end
+ end
+ end
+
def self.resolver_complexity(args, child_complexity:)
complexity = 1
complexity += 1 if args[:sort]
diff --git a/app/graphql/resolvers/commit_pipelines_resolver.rb b/app/graphql/resolvers/commit_pipelines_resolver.rb
new file mode 100644
index 0000000000..92a8352359
--- /dev/null
+++ b/app/graphql/resolvers/commit_pipelines_resolver.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Resolvers
+ class CommitPipelinesResolver < BaseResolver
+ include ::ResolvesPipelines
+
+ alias_method :commit, :object
+
+ def resolve(**args)
+ resolve_pipelines(commit.project, args.merge!({ sha: commit.sha }))
+ end
+ end
+end
diff --git a/app/graphql/types/base_enum.rb b/app/graphql/types/base_enum.rb
index cf43fea45e..94f6c47e87 100644
--- a/app/graphql/types/base_enum.rb
+++ b/app/graphql/types/base_enum.rb
@@ -2,5 +2,18 @@
module Types
class BaseEnum < GraphQL::Schema::Enum
+ class << self
+ def value(*args, **kwargs, &block)
+ enum[args[0].downcase] = kwargs[:value] || args[0]
+
+ super(*args, **kwargs, &block)
+ end
+
+ # Returns an indifferent access hash with the key being the downcased name of the attribute
+ # and the value being the Ruby value (either the explicit `value` passed or the same as the value attr).
+ def enum
+ @enum_values ||= {}.with_indifferent_access
+ end
+ end
end
end
diff --git a/app/graphql/types/commit_type.rb b/app/graphql/types/commit_type.rb
index fe71791f41..87f84ec576 100644
--- a/app/graphql/types/commit_type.rb
+++ b/app/graphql/types/commit_type.rb
@@ -8,25 +8,39 @@ module Types
present_using CommitPresenter
- field :id, type: GraphQL::ID_TYPE, null: false # rubocop:disable Graphql/Descriptions
- field :sha, type: GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
- field :title, type: GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions
- field :description, type: GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions
- field :message, type: GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions
- field :authored_date, type: Types::TimeType, null: true # rubocop:disable Graphql/Descriptions
- field :web_url, type: GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
- field :signature_html, type: GraphQL::STRING_TYPE,
- null: true, calls_gitaly: true, description: 'Rendered html for the commit signature'
+ field :id, type: GraphQL::ID_TYPE, null: false,
+ description: 'ID (global ID) of the commit'
+ field :sha, type: GraphQL::STRING_TYPE, null: false,
+ description: 'SHA1 ID of the commit'
+ field :title, type: GraphQL::STRING_TYPE, null: true,
+ description: 'Title of the commit message'
+ field :description, type: GraphQL::STRING_TYPE, null: true,
+ description: 'Description of the commit message'
+ field :message, type: GraphQL::STRING_TYPE, null: true,
+ description: 'Raw commit message'
+ field :authored_date, type: Types::TimeType, null: true,
+ description: 'Timestamp of when the commit was authored'
+ field :web_url, type: GraphQL::STRING_TYPE, null: false,
+ description: 'Web URL of the commit'
+ field :signature_html, type: GraphQL::STRING_TYPE, null: true, calls_gitaly: true,
+ description: 'Rendered HTML of the commit signature'
+ field :author_name, type: GraphQL::STRING_TYPE, null: true,
+ description: 'Commit authors name'
# models/commit lazy loads the author by email
- field :author, type: Types::UserType, null: true # rubocop:disable Graphql/Descriptions
+ field :author, type: Types::UserType, null: true,
+ description: 'Author of the commit'
+
+ field :pipelines, Types::Ci::PipelineType.connection_type,
+ null: true,
+ description: 'Pipelines of the commit ordered latest first',
+ resolver: Resolvers::CommitPipelinesResolver
field :latest_pipeline,
type: Types::Ci::PipelineType,
null: true,
- description: "Latest pipeline for this commit",
- resolve: -> (obj, ctx, args) do
- Gitlab::Graphql::Loaders::PipelineForShaLoader.new(obj.project, obj.sha).find_last
- end
+ description: "Latest pipeline of the commit",
+ deprecation_reason: 'use pipelines',
+ resolver: Resolvers::CommitPipelinesResolver.last
end
end
diff --git a/app/graphql/types/extended_issue_type.rb b/app/graphql/types/extended_issue_type.rb
deleted file mode 100644
index e007c1109a..0000000000
--- a/app/graphql/types/extended_issue_type.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-# frozen_string_literal: true
-
-module Types
- class ExtendedIssueType < IssueType
- graphql_name 'ExtendedIssue'
-
- authorize :read_issue
- expose_permissions Types::PermissionTypes::Issue
- present_using IssuePresenter
-
- field :subscribed, GraphQL::BOOLEAN_TYPE, method: :subscribed?, null: false, complexity: 5,
- description: 'Boolean flag for whether the currently logged in user is subscribed to this issue'
- end
-end
diff --git a/app/graphql/types/group_type.rb b/app/graphql/types/group_type.rb
index 1e52c0cb14..386ae6ed4a 100644
--- a/app/graphql/types/group_type.rb
+++ b/app/graphql/types/group_type.rb
@@ -8,14 +8,17 @@ module Types
expose_permissions Types::PermissionTypes::Group
- field :web_url, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
+ field :web_url, GraphQL::STRING_TYPE, null: false,
+ description: 'Web URL of the group'
- field :avatar_url, GraphQL::STRING_TYPE, null: true, resolve: -> (group, args, ctx) do # rubocop:disable Graphql/Descriptions
- group.avatar_url(only_path: false)
- end
+ field :avatar_url, GraphQL::STRING_TYPE, null: true,
+ description: 'Avatar URL of the group',
+ resolve: -> (group, args, ctx) do
+ group.avatar_url(only_path: false)
+ end
- field :parent, GroupType, # rubocop:disable Graphql/Descriptions
- null: true,
+ field :parent, GroupType, null: true,
+ description: 'Parent group',
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, obj.parent_id).find }
end
end
diff --git a/app/graphql/types/issue_sort_enum.rb b/app/graphql/types/issue_sort_enum.rb
index ad919b5548..48ff581928 100644
--- a/app/graphql/types/issue_sort_enum.rb
+++ b/app/graphql/types/issue_sort_enum.rb
@@ -5,6 +5,10 @@ module Types
class IssueSortEnum < IssuableSortEnum
graphql_name 'IssueSort'
description 'Values for sorting issues'
+
+ value 'DUE_DATE_ASC', 'Due date by ascending order', value: 'due_date_asc'
+ value 'DUE_DATE_DESC', 'Due date by descending order', value: 'due_date_desc'
+ value 'RELATIVE_POSITION_ASC', 'Relative position by ascending order', value: 'relative_position_asc'
end
# rubocop: enable Graphql/AuthorizeTypes
end
diff --git a/app/graphql/types/issue_type.rb b/app/graphql/types/issue_type.rb
index 4965601fe6..4cbb849da3 100644
--- a/app/graphql/types/issue_type.rb
+++ b/app/graphql/types/issue_type.rb
@@ -12,53 +12,79 @@ module Types
present_using IssuePresenter
- field :iid, GraphQL::ID_TYPE, null: false # rubocop:disable Graphql/Descriptions
- field :title, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
+ field :iid, GraphQL::ID_TYPE, null: false,
+ description: "Internal ID of the issue"
+ field :title, GraphQL::STRING_TYPE, null: false,
+ description: 'Title of the issue'
markdown_field :title_html, null: true
- field :description, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions
+ field :description, GraphQL::STRING_TYPE, null: true,
+ description: 'Description of the issue'
markdown_field :description_html, null: true
- field :state, IssueStateEnum, null: false # rubocop:disable Graphql/Descriptions
+ field :state, IssueStateEnum, null: false,
+ description: 'State of the issue'
- field :reference, GraphQL::STRING_TYPE, null: false, method: :to_reference do # rubocop:disable Graphql/Descriptions
- argument :full, GraphQL::BOOLEAN_TYPE, required: false, default_value: false # rubocop:disable Graphql/Descriptions
+ field :reference, GraphQL::STRING_TYPE, null: false,
+ description: 'Internal reference of the issue. Returned in shortened format by default',
+ method: :to_reference do
+ argument :full, GraphQL::BOOLEAN_TYPE, required: false, default_value: false,
+ description: 'Boolean option specifying whether the reference should be returned in full'
end
- field :author, Types::UserType, # rubocop:disable Graphql/Descriptions
- null: false,
+ field :author, Types::UserType, null: false,
+ description: 'User that created the issue',
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, obj.author_id).find }
# Remove complexity when BatchLoader is used
- field :assignees, Types::UserType.connection_type, null: true, complexity: 5 # rubocop:disable Graphql/Descriptions
+ field :assignees, Types::UserType.connection_type, null: true, complexity: 5,
+ description: 'Assignees of the issue'
# Remove complexity when BatchLoader is used
- field :labels, Types::LabelType.connection_type, null: true, complexity: 5 # rubocop:disable Graphql/Descriptions
- field :milestone, Types::MilestoneType, # rubocop:disable Graphql/Descriptions
- null: true,
+ field :labels, Types::LabelType.connection_type, null: true, complexity: 5,
+ description: 'Labels of the issue'
+ field :milestone, Types::MilestoneType, null: true,
+ description: 'Milestone of the issue',
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Milestone, obj.milestone_id).find }
- field :due_date, Types::TimeType, null: true # rubocop:disable Graphql/Descriptions
- field :confidential, GraphQL::BOOLEAN_TYPE, null: false # rubocop:disable Graphql/Descriptions
- field :discussion_locked, GraphQL::BOOLEAN_TYPE, # rubocop:disable Graphql/Descriptions
- null: false,
+ field :due_date, Types::TimeType, null: true,
+ description: 'Due date of the issue'
+ field :confidential, GraphQL::BOOLEAN_TYPE, null: false,
+ description: 'Indicates the issue is confidential'
+ field :discussion_locked, GraphQL::BOOLEAN_TYPE, null: false,
+ description: 'Indicates discussion is locked on the issue',
resolve: -> (obj, _args, _ctx) { !!obj.discussion_locked }
- field :upvotes, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions
- field :downvotes, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions
- field :user_notes_count, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions
- field :web_path, GraphQL::STRING_TYPE, null: false, method: :issue_path # rubocop:disable Graphql/Descriptions
- field :web_url, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
- field :relative_position, GraphQL::INT_TYPE, null: true # rubocop:disable Graphql/Descriptions
+ field :upvotes, GraphQL::INT_TYPE, null: false,
+ description: 'Number of upvotes the issue has received'
+ field :downvotes, GraphQL::INT_TYPE, null: false,
+ description: 'Number of downvotes the issue has received'
+ field :user_notes_count, GraphQL::INT_TYPE, null: false,
+ description: 'Number of user notes of the issue'
+ field :web_path, GraphQL::STRING_TYPE, null: false, method: :issue_path,
+ description: 'Web path of the issue'
+ field :web_url, GraphQL::STRING_TYPE, null: false,
+ description: 'Web URL of the issue'
+ field :relative_position, GraphQL::INT_TYPE, null: true,
+ description: 'Relative position of the issue (used for positioning in epic tree and issue boards)'
- field :participants, Types::UserType.connection_type, null: true, complexity: 5, description: 'List of participants for the issue'
- field :time_estimate, GraphQL::INT_TYPE, null: false, description: 'The time estimate on the issue'
- field :total_time_spent, GraphQL::INT_TYPE, null: false, description: 'Total time reported as spent on the issue'
+ field :participants, Types::UserType.connection_type, null: true, complexity: 5,
+ description: 'List of participants in the issue'
+ field :subscribed, GraphQL::BOOLEAN_TYPE, method: :subscribed?, null: false, complexity: 5,
+ description: 'Boolean flag for whether the currently logged in user is subscribed to this issue'
+ field :time_estimate, GraphQL::INT_TYPE, null: false,
+ description: 'Time estimate of the issue'
+ field :total_time_spent, GraphQL::INT_TYPE, null: false,
+ description: 'Total time reported as spent on the issue'
- field :closed_at, Types::TimeType, null: true # rubocop:disable Graphql/Descriptions
+ field :closed_at, Types::TimeType, null: true,
+ description: 'Timestamp of when the issue was closed'
- field :created_at, Types::TimeType, null: false # rubocop:disable Graphql/Descriptions
- field :updated_at, Types::TimeType, null: false # rubocop:disable Graphql/Descriptions
+ field :created_at, Types::TimeType, null: false,
+ description: 'Timestamp of when the issue was created'
+ field :updated_at, Types::TimeType, null: false,
+ description: 'Timestamp of when the issue was last updated'
- field :task_completion_status, Types::TaskCompletionStatus, null: false # rubocop:disable Graphql/Descriptions
+ field :task_completion_status, Types::TaskCompletionStatus, null: false,
+ description: 'Task completion status of the issue'
end
end
diff --git a/app/graphql/types/label_type.rb b/app/graphql/types/label_type.rb
index 384a27df56..d0bcf2068b 100644
--- a/app/graphql/types/label_type.rb
+++ b/app/graphql/types/label_type.rb
@@ -6,10 +6,16 @@ module Types
authorize :read_label
- field :description, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions
+ field :id, GraphQL::ID_TYPE, null: false,
+ description: 'Label ID'
+ field :description, GraphQL::STRING_TYPE, null: true,
+ description: 'Description of the label (markdown rendered as HTML for caching)'
markdown_field :description_html, null: true
- field :title, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
- field :color, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
- field :text_color, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
+ field :title, GraphQL::STRING_TYPE, null: false,
+ description: 'Content of the label'
+ field :color, GraphQL::STRING_TYPE, null: false,
+ description: 'Background color of the label'
+ field :text_color, GraphQL::STRING_TYPE, null: false,
+ description: 'Text color of the label'
end
end
diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb
index 71a65dc671..278a95fe3c 100644
--- a/app/graphql/types/merge_request_type.rb
+++ b/app/graphql/types/merge_request_type.rb
@@ -12,70 +12,116 @@ module Types
present_using MergeRequestPresenter
- field :id, GraphQL::ID_TYPE, null: false # rubocop:disable Graphql/Descriptions
- field :iid, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
- field :title, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
+ field :id, GraphQL::ID_TYPE, null: false,
+ description: 'ID of the merge request'
+ field :iid, GraphQL::STRING_TYPE, null: false,
+ description: 'Internal ID of the merge request'
+ field :title, GraphQL::STRING_TYPE, null: false,
+ description: 'Title of the merge request'
markdown_field :title_html, null: true
- field :description, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions
+ field :description, GraphQL::STRING_TYPE, null: true,
+ description: 'Description of the merge request (markdown rendered as HTML for caching)'
markdown_field :description_html, null: true
- field :state, MergeRequestStateEnum, null: false # rubocop:disable Graphql/Descriptions
- field :created_at, Types::TimeType, null: false # rubocop:disable Graphql/Descriptions
- field :updated_at, Types::TimeType, null: false # rubocop:disable Graphql/Descriptions
- field :source_project, Types::ProjectType, null: true # rubocop:disable Graphql/Descriptions
- field :target_project, Types::ProjectType, null: false # rubocop:disable Graphql/Descriptions
- field :diff_refs, Types::DiffRefsType, null: true # rubocop:disable Graphql/Descriptions
- # Alias for target_project
- field :project, Types::ProjectType, null: false # rubocop:disable Graphql/Descriptions
- field :project_id, GraphQL::INT_TYPE, null: false, method: :target_project_id # rubocop:disable Graphql/Descriptions
- field :source_project_id, GraphQL::INT_TYPE, null: true # rubocop:disable Graphql/Descriptions
- field :target_project_id, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions
- field :source_branch, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
- field :target_branch, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
- field :work_in_progress, GraphQL::BOOLEAN_TYPE, method: :work_in_progress?, null: false # rubocop:disable Graphql/Descriptions
- field :merge_when_pipeline_succeeds, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions
- field :diff_head_sha, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions
- field :merge_commit_sha, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions
- field :user_notes_count, GraphQL::INT_TYPE, null: true # rubocop:disable Graphql/Descriptions
- field :should_remove_source_branch, GraphQL::BOOLEAN_TYPE, method: :should_remove_source_branch?, null: true # rubocop:disable Graphql/Descriptions
- field :force_remove_source_branch, GraphQL::BOOLEAN_TYPE, method: :force_remove_source_branch?, null: true # rubocop:disable Graphql/Descriptions
- field :merge_status, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions
- field :in_progress_merge_commit_sha, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions
- field :merge_error, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions
- field :allow_collaboration, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions
- field :should_be_rebased, GraphQL::BOOLEAN_TYPE, method: :should_be_rebased?, null: false # rubocop:disable Graphql/Descriptions
- field :rebase_commit_sha, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions
- field :rebase_in_progress, GraphQL::BOOLEAN_TYPE, method: :rebase_in_progress?, null: false, calls_gitaly: true # rubocop:disable Graphql/Descriptions
- # rubocop:disable Graphql/Descriptions
- field :merge_commit_message, GraphQL::STRING_TYPE, method: :default_merge_commit_message, null: true, deprecation_reason: "Renamed to defaultMergeCommitMessage"
- # rubocop:enable Graphql/Descriptions
- field :default_merge_commit_message, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions
- field :merge_ongoing, GraphQL::BOOLEAN_TYPE, method: :merge_ongoing?, null: false # rubocop:disable Graphql/Descriptions
- field :source_branch_exists, GraphQL::BOOLEAN_TYPE, method: :source_branch_exists?, null: false # rubocop:disable Graphql/Descriptions
- field :mergeable_discussions_state, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions
- field :web_url, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions
- field :upvotes, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions
- field :downvotes, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions
+ field :state, MergeRequestStateEnum, null: false,
+ description: 'State of the merge request'
+ field :created_at, Types::TimeType, null: false,
+ description: 'Timestamp of when the merge request was created'
+ field :updated_at, Types::TimeType, null: false,
+ description: 'Timestamp of when the merge request was last updated'
+ field :source_project, Types::ProjectType, null: true,
+ description: 'Source project of the merge request'
+ field :target_project, Types::ProjectType, null: false,
+ description: 'Target project of the merge request'
+ field :diff_refs, Types::DiffRefsType, null: true,
+ description: 'References of the base SHA, the head SHA, and the start SHA for this merge request'
+ field :project, Types::ProjectType, null: false,
+ description: 'Alias for target_project'
+ field :project_id, GraphQL::INT_TYPE, null: false, method: :target_project_id,
+ description: 'ID of the merge request project'
+ field :source_project_id, GraphQL::INT_TYPE, null: true,
+ description: 'ID of the merge request source project'
+ field :target_project_id, GraphQL::INT_TYPE, null: false,
+ description: 'ID of the merge request target project'
+ field :source_branch, GraphQL::STRING_TYPE, null: false,
+ description: 'Source branch of the merge request'
+ field :target_branch, GraphQL::STRING_TYPE, null: false,
+ description: 'Target branch of the merge request'
+ field :work_in_progress, GraphQL::BOOLEAN_TYPE, method: :work_in_progress?, null: false,
+ description: 'Indicates if the merge request is a work in progress (WIP)'
+ field :merge_when_pipeline_succeeds, GraphQL::BOOLEAN_TYPE, null: true,
+ description: 'Indicates if the merge has been set to be merged when its pipeline succeeds (MWPS)'
+ field :diff_head_sha, GraphQL::STRING_TYPE, null: true,
+ description: 'Diff head SHA of the merge request'
+ field :merge_commit_sha, GraphQL::STRING_TYPE, null: true,
+ description: 'SHA of the merge request commit (set once merged)'
+ field :user_notes_count, GraphQL::INT_TYPE, null: true,
+ description: 'User notes count of the merge request'
+ field :should_remove_source_branch, GraphQL::BOOLEAN_TYPE, method: :should_remove_source_branch?, null: true,
+ description: 'Indicates if the source branch of the merge request will be deleted after merge'
+ field :force_remove_source_branch, GraphQL::BOOLEAN_TYPE, method: :force_remove_source_branch?, null: true,
+ description: 'Indicates if the project settings will lead to source branch deletion after merge'
+ field :merge_status, GraphQL::STRING_TYPE, null: true,
+ description: 'Status of the merge request'
+ field :in_progress_merge_commit_sha, GraphQL::STRING_TYPE, null: true,
+ description: 'Commit SHA of the merge request if merge is in progress'
+ field :merge_error, GraphQL::STRING_TYPE, null: true,
+ description: 'Error message due to a merge error'
+ field :allow_collaboration, GraphQL::BOOLEAN_TYPE, null: true,
+ description: 'Indicates if members of the target project can push to the fork'
+ field :should_be_rebased, GraphQL::BOOLEAN_TYPE, method: :should_be_rebased?, null: false,
+ description: 'Indicates if the merge request will be rebased'
+ field :rebase_commit_sha, GraphQL::STRING_TYPE, null: true,
+ description: 'Rebase commit SHA of the merge request'
+ field :rebase_in_progress, GraphQL::BOOLEAN_TYPE, method: :rebase_in_progress?, null: false, calls_gitaly: true,
+ description: 'Indicates if there is a rebase currently in progress for the merge request'
+ field :merge_commit_message, GraphQL::STRING_TYPE, method: :default_merge_commit_message, null: true, deprecation_reason: "Renamed to defaultMergeCommitMessage",
+ description: 'Deprecated - renamed to defaultMergeCommitMessage'
+ field :default_merge_commit_message, GraphQL::STRING_TYPE, null: true,
+ description: 'Default merge commit message of the merge request'
+ field :merge_ongoing, GraphQL::BOOLEAN_TYPE, method: :merge_ongoing?, null: false,
+ description: 'Indicates if a merge is currently occurring'
+ field :source_branch_exists, GraphQL::BOOLEAN_TYPE, method: :source_branch_exists?, null: false,
+ description: 'Indicates if the source branch of the merge request exists'
+ field :mergeable_discussions_state, GraphQL::BOOLEAN_TYPE, null: true,
+ description: 'Indicates if all discussions in the merge request have been resolved, allowing the merge request to be merged'
+ field :web_url, GraphQL::STRING_TYPE, null: true,
+ description: 'Web URL of the merge request'
+ field :upvotes, GraphQL::INT_TYPE, null: false,
+ description: 'Number of upvotes for the merge request'
+ field :downvotes, GraphQL::INT_TYPE, null: false,
+ description: 'Number of downvotes for the merge request'
- field :head_pipeline, Types::Ci::PipelineType, null: true, method: :actual_head_pipeline # rubocop:disable Graphql/Descriptions
- field :pipelines, Types::Ci::PipelineType.connection_type, # rubocop:disable Graphql/Descriptions
+ field :head_pipeline, Types::Ci::PipelineType, null: true, method: :actual_head_pipeline,
+ description: 'The pipeline running on the branch HEAD of the merge request'
+ field :pipelines, Types::Ci::PipelineType.connection_type,
+ description: 'Pipelines for the merge request',
resolver: Resolvers::MergeRequestPipelinesResolver
- field :milestone, Types::MilestoneType, description: 'The milestone this merge request is linked to',
- null: true,
+ field :milestone, Types::MilestoneType, null: true,
+ description: 'The milestone of the merge request',
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Milestone, obj.milestone_id).find }
- field :assignees, Types::UserType.connection_type, null: true, complexity: 5, description: 'The list of assignees for the merge request'
- field :participants, Types::UserType.connection_type, null: true, complexity: 5, description: 'The list of participants on the merge request'
+ field :assignees, Types::UserType.connection_type, null: true, complexity: 5,
+ description: 'Assignees of the merge request'
+ field :participants, Types::UserType.connection_type, null: true, complexity: 5,
+ description: 'Participants in the merge request'
field :subscribed, GraphQL::BOOLEAN_TYPE, method: :subscribed?, null: false, complexity: 5,
- description: 'Boolean flag for whether the currently logged in user is subscribed to this MR'
- field :labels, Types::LabelType.connection_type, null: true, complexity: 5, description: 'The list of labels on the merge request'
- field :discussion_locked, GraphQL::BOOLEAN_TYPE, description: 'Boolean flag determining if comments on the merge request are locked to members only',
+ description: 'Indicates if the currently logged in user is subscribed to this merge request'
+ field :labels, Types::LabelType.connection_type, null: true, complexity: 5,
+ description: 'Labels of the merge request'
+ field :discussion_locked, GraphQL::BOOLEAN_TYPE,
+ description: 'Indicates if comments on the merge request are locked to members only',
null: false,
resolve: -> (obj, _args, _ctx) { !!obj.discussion_locked }
- field :time_estimate, GraphQL::INT_TYPE, null: false, description: 'The time estimate for the merge request'
- field :total_time_spent, GraphQL::INT_TYPE, null: false, description: 'Total time reported as spent on the merge request'
- field :reference, GraphQL::STRING_TYPE, null: false, method: :to_reference, description: 'Internal merge request reference. Returned in shortened format by default' do
- argument :full, GraphQL::BOOLEAN_TYPE, required: false, default_value: false, description: 'Boolean option specifying whether the reference should be returned in full'
+ field :time_estimate, GraphQL::INT_TYPE, null: false,
+ description: 'Time estimate of the merge request'
+ field :total_time_spent, GraphQL::INT_TYPE, null: false,
+ description: 'Total time reported as spent on the merge request'
+ field :reference, GraphQL::STRING_TYPE, null: false, method: :to_reference,
+ description: 'Internal reference of the merge request. Returned in shortened format by default' do
+ argument :full, GraphQL::BOOLEAN_TYPE, required: false, default_value: false,
+ description: 'Boolean option specifying whether the reference should be returned in full'
end
- field :task_completion_status, Types::TaskCompletionStatus, null: false # rubocop:disable Graphql/Descriptions
+ field :task_completion_status, Types::TaskCompletionStatus, null: false,
+ description: Types::TaskCompletionStatus.description
end
end
diff --git a/app/graphql/types/metadata_type.rb b/app/graphql/types/metadata_type.rb
index bfcb929f5a..1998b036a5 100644
--- a/app/graphql/types/metadata_type.rb
+++ b/app/graphql/types/metadata_type.rb
@@ -6,7 +6,9 @@ module Types
authorize :read_instance_metadata
- field :version, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
- field :revision, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
+ field :version, GraphQL::STRING_TYPE, null: false,
+ description: 'Version'
+ field :revision, GraphQL::STRING_TYPE, null: false,
+ description: 'Revision'
end
end
diff --git a/app/graphql/types/milestone_type.rb b/app/graphql/types/milestone_type.rb
index 78d0a8220e..9c3afb2867 100644
--- a/app/graphql/types/milestone_type.rb
+++ b/app/graphql/types/milestone_type.rb
@@ -6,14 +6,23 @@ module Types
authorize :read_milestone
- field :description, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions
- field :title, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
- field :state, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
+ field :id, GraphQL::ID_TYPE, null: false,
+ description: 'ID of the milestone'
+ field :description, GraphQL::STRING_TYPE, null: true,
+ description: 'Description of the milestone'
+ field :title, GraphQL::STRING_TYPE, null: false,
+ description: 'Title of the milestone'
+ field :state, GraphQL::STRING_TYPE, null: false,
+ description: 'State of the milestone'
- field :due_date, Types::TimeType, null: true # rubocop:disable Graphql/Descriptions
- field :start_date, Types::TimeType, null: true # rubocop:disable Graphql/Descriptions
+ field :due_date, Types::TimeType, null: true,
+ description: 'Timestamp of the milestone due date'
+ field :start_date, Types::TimeType, null: true,
+ description: 'Timestamp of the milestone start date'
- field :created_at, Types::TimeType, null: false # rubocop:disable Graphql/Descriptions
- field :updated_at, Types::TimeType, null: false # rubocop:disable Graphql/Descriptions
+ field :created_at, Types::TimeType, null: false,
+ description: 'Timestamp of milestone creation'
+ field :updated_at, Types::TimeType, null: false,
+ description: 'Timestamp of last milestone update'
end
end
diff --git a/app/graphql/types/mutation_operation_mode_enum.rb b/app/graphql/types/mutation_operation_mode_enum.rb
new file mode 100644
index 0000000000..90a29d2b0e
--- /dev/null
+++ b/app/graphql/types/mutation_operation_mode_enum.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Types
+ class MutationOperationModeEnum < BaseEnum
+ graphql_name 'MutationOperationMode'
+ description 'Different toggles for changing mutator behavior.'
+
+ # Suggested param name for the enum: `operation_mode`
+
+ value 'REPLACE', 'Performs a replace operation'
+ value 'APPEND', 'Performs an append operation'
+ value 'REMOVE', 'Performs a removal operation'
+ end
+end
diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb
index 17f922a5e5..b3c7c162bb 100644
--- a/app/graphql/types/mutation_type.rb
+++ b/app/graphql/types/mutation_type.rb
@@ -9,12 +9,18 @@ module Types
mount_mutation Mutations::AwardEmojis::Add
mount_mutation Mutations::AwardEmojis::Remove
mount_mutation Mutations::AwardEmojis::Toggle
+ mount_mutation Mutations::MergeRequests::SetLabels
+ mount_mutation Mutations::MergeRequests::SetLocked
+ mount_mutation Mutations::MergeRequests::SetMilestone
+ mount_mutation Mutations::MergeRequests::SetSubscription
mount_mutation Mutations::MergeRequests::SetWip, calls_gitaly: true
+ mount_mutation Mutations::MergeRequests::SetAssignees
mount_mutation Mutations::Notes::Create::Note, calls_gitaly: true
mount_mutation Mutations::Notes::Create::DiffNote, calls_gitaly: true
mount_mutation Mutations::Notes::Create::ImageDiffNote, calls_gitaly: true
mount_mutation Mutations::Notes::Update
mount_mutation Mutations::Notes::Destroy
+ mount_mutation Mutations::Todos::MarkDone
end
end
diff --git a/app/graphql/types/namespace_type.rb b/app/graphql/types/namespace_type.rb
index cc1d06b19e..1714284a5c 100644
--- a/app/graphql/types/namespace_type.rb
+++ b/app/graphql/types/namespace_type.rb
@@ -6,27 +6,35 @@ module Types
authorize :read_namespace
- field :id, GraphQL::ID_TYPE, null: false # rubocop:disable Graphql/Descriptions
+ field :id, GraphQL::ID_TYPE, null: false,
+ description: 'ID of the namespace'
- field :name, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
- field :path, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
- field :full_name, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
- field :full_path, GraphQL::ID_TYPE, null: false # rubocop:disable Graphql/Descriptions
+ field :name, GraphQL::STRING_TYPE, null: false,
+ description: 'Name of the namespace'
+ field :path, GraphQL::STRING_TYPE, null: false,
+ description: 'Path of the namespace'
+ field :full_name, GraphQL::STRING_TYPE, null: false,
+ description: 'Full name of the namespace'
+ field :full_path, GraphQL::ID_TYPE, null: false,
+ description: 'Full path of the namespace'
- field :description, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions
+ field :description, GraphQL::STRING_TYPE, null: true,
+ description: 'Description of the namespace'
markdown_field :description_html, null: true
- field :visibility, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions
- field :lfs_enabled, GraphQL::BOOLEAN_TYPE, null: true, method: :lfs_enabled? # rubocop:disable Graphql/Descriptions
- field :request_access_enabled, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions
+ field :visibility, GraphQL::STRING_TYPE, null: true,
+ description: 'Visibility of the namespace'
+ field :lfs_enabled, GraphQL::BOOLEAN_TYPE, null: true, method: :lfs_enabled?,
+ description: 'Indicates if Large File Storage (LFS) is enabled for namespace'
+ field :request_access_enabled, GraphQL::BOOLEAN_TYPE, null: true,
+ description: 'Indicates if users can request access to namespace'
field :root_storage_statistics, Types::RootStorageStatisticsType,
null: true,
- description: 'The aggregated storage statistics. Only available for root namespaces',
+ description: 'Aggregated storage statistics of the namespace. Only available for root namespaces',
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchRootStorageStatisticsLoader.new(obj.id).find }
- field :projects, # rubocop:disable Graphql/Descriptions
- Types::ProjectType.connection_type,
- null: false,
+ field :projects, Types::ProjectType.connection_type, null: false,
+ description: 'Projects within this namespace',
resolver: ::Resolvers::NamespaceProjectsResolver
end
end
diff --git a/app/graphql/types/project_statistics_type.rb b/app/graphql/types/project_statistics_type.rb
index 5045471a75..c46410df6c 100644
--- a/app/graphql/types/project_statistics_type.rb
+++ b/app/graphql/types/project_statistics_type.rb
@@ -6,13 +6,20 @@ module Types
authorize :read_statistics
- field :commit_count, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions
+ field :commit_count, GraphQL::INT_TYPE, null: false,
+ description: 'Commit count of the project'
- field :storage_size, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions
- field :repository_size, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions
- field :lfs_objects_size, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions
- field :build_artifacts_size, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions
- field :packages_size, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions
- field :wiki_size, GraphQL::INT_TYPE, null: true # rubocop:disable Graphql/Descriptions
+ field :storage_size, GraphQL::INT_TYPE, null: false,
+ description: 'Storage size of the project'
+ field :repository_size, GraphQL::INT_TYPE, null: false,
+ description: 'Repository size of the project'
+ field :lfs_objects_size, GraphQL::INT_TYPE, null: false,
+ description: 'Large File Storage (LFS) object size of the project'
+ field :build_artifacts_size, GraphQL::INT_TYPE, null: false,
+ description: 'Build artifacts size of the project'
+ field :packages_size, GraphQL::INT_TYPE, null: false,
+ description: 'Packages size of the project'
+ field :wiki_size, GraphQL::INT_TYPE, null: true,
+ description: 'Wiki size of the project'
end
end
diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb
index 5663f833b7..7325502111 100644
--- a/app/graphql/types/project_type.rb
+++ b/app/graphql/types/project_type.rb
@@ -8,97 +8,142 @@ module Types
expose_permissions Types::PermissionTypes::Project
- field :id, GraphQL::ID_TYPE, null: false # rubocop:disable Graphql/Descriptions
+ field :id, GraphQL::ID_TYPE, null: false,
+ description: 'ID of the project'
- field :full_path, GraphQL::ID_TYPE, null: false # rubocop:disable Graphql/Descriptions
- field :path, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
+ field :full_path, GraphQL::ID_TYPE, null: false,
+ description: 'Full path of the project'
+ field :path, GraphQL::STRING_TYPE, null: false,
+ description: 'Path of the project'
- field :name_with_namespace, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
- field :name, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
+ field :name_with_namespace, GraphQL::STRING_TYPE, null: false,
+ description: 'Full name of the project with its namespace'
+ field :name, GraphQL::STRING_TYPE, null: false,
+ description: 'Name of the project (without namespace)'
- field :description, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions
+ field :description, GraphQL::STRING_TYPE, null: true,
+ description: 'Short description of the project'
markdown_field :description_html, null: true
- field :tag_list, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions
+ field :tag_list, GraphQL::STRING_TYPE, null: true,
+ description: 'List of project tags'
- field :ssh_url_to_repo, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions
- field :http_url_to_repo, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions
- field :web_url, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions
+ field :ssh_url_to_repo, GraphQL::STRING_TYPE, null: true,
+ description: 'URL to connect to the project via SSH'
+ field :http_url_to_repo, GraphQL::STRING_TYPE, null: true,
+ description: 'URL to connect to the project via HTTPS'
+ field :web_url, GraphQL::STRING_TYPE, null: true,
+ description: 'Web URL of the project'
- field :star_count, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions
- field :forks_count, GraphQL::INT_TYPE, null: false, calls_gitaly: true # 4 times # rubocop:disable Graphql/Descriptions
+ field :star_count, GraphQL::INT_TYPE, null: false,
+ description: 'Number of times the project has been starred'
+ field :forks_count, GraphQL::INT_TYPE, null: false, calls_gitaly: true, # 4 times
+ description: 'Number of times the project has been forked'
- field :created_at, Types::TimeType, null: true # rubocop:disable Graphql/Descriptions
- field :last_activity_at, Types::TimeType, null: true # rubocop:disable Graphql/Descriptions
+ field :created_at, Types::TimeType, null: true,
+ description: 'Timestamp of the project creation'
+ field :last_activity_at, Types::TimeType, null: true,
+ description: 'Timestamp of the project last activity'
- field :archived, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions
+ field :archived, GraphQL::BOOLEAN_TYPE, null: true,
+ description: 'Archived status of the project'
- field :visibility, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions
+ field :visibility, GraphQL::STRING_TYPE, null: true,
+ description: 'Visibility of the project'
- field :container_registry_enabled, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions
- field :shared_runners_enabled, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions
- field :lfs_enabled, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions
- field :merge_requests_ff_only_enabled, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions
+ field :container_registry_enabled, GraphQL::BOOLEAN_TYPE, null: true,
+ description: 'Indicates if the project stores Docker container images in a container registry'
+ field :shared_runners_enabled, GraphQL::BOOLEAN_TYPE, null: true,
+ description: 'Indicates if shared runners are enabled on the project'
+ field :lfs_enabled, GraphQL::BOOLEAN_TYPE, null: true,
+ description: 'Indicates if the project has Large File Storage (LFS) enabled'
+ field :merge_requests_ff_only_enabled, GraphQL::BOOLEAN_TYPE, null: true,
+ description: 'Indicates if no merge commits should be created and all merges should instead be fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded.'
- field :avatar_url, GraphQL::STRING_TYPE, null: true, calls_gitaly: true, resolve: -> (project, args, ctx) do # rubocop:disable Graphql/Descriptions
- project.avatar_url(only_path: false)
- end
+ field :avatar_url, GraphQL::STRING_TYPE, null: true, calls_gitaly: true,
+ description: 'URL to avatar image file of the project',
+ resolve: -> (project, args, ctx) do
+ project.avatar_url(only_path: false)
+ end
%i[issues merge_requests wiki snippets].each do |feature|
- field "#{feature}_enabled", GraphQL::BOOLEAN_TYPE, null: true, resolve: -> (project, args, ctx) do # rubocop:disable Graphql/Descriptions
- project.feature_available?(feature, ctx[:current_user])
- end
+ field "#{feature}_enabled", GraphQL::BOOLEAN_TYPE, null: true,
+ description: "(deprecated) Does this project have #{feature} enabled?. Use `#{feature}_access_level` instead",
+ resolve: -> (project, args, ctx) do
+ project.feature_available?(feature, ctx[:current_user])
+ end
end
- field :jobs_enabled, GraphQL::BOOLEAN_TYPE, null: true, resolve: -> (project, args, ctx) do # rubocop:disable Graphql/Descriptions
- project.feature_available?(:builds, ctx[:current_user])
- end
+ field :jobs_enabled, GraphQL::BOOLEAN_TYPE, null: true,
+ description: '(deprecated) Enable jobs for this project. Use `builds_access_level` instead',
+ resolve: -> (project, args, ctx) do
+ project.feature_available?(:builds, ctx[:current_user])
+ end
- field :public_jobs, GraphQL::BOOLEAN_TYPE, method: :public_builds, null: true # rubocop:disable Graphql/Descriptions
+ field :public_jobs, GraphQL::BOOLEAN_TYPE, method: :public_builds, null: true,
+ description: 'Indicates if there is public access to pipelines and job details of the project, including output logs and artifacts'
- field :open_issues_count, GraphQL::INT_TYPE, null: true, resolve: -> (project, args, ctx) do # rubocop:disable Graphql/Descriptions
- project.open_issues_count if project.feature_available?(:issues, ctx[:current_user])
- end
+ field :open_issues_count, GraphQL::INT_TYPE, null: true,
+ description: 'Number of open issues for the project',
+ resolve: -> (project, args, ctx) do
+ project.open_issues_count if project.feature_available?(:issues, ctx[:current_user])
+ end
- field :import_status, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions
+ field :import_status, GraphQL::STRING_TYPE, null: true,
+ description: 'Status of project import background job of the project'
- field :only_allow_merge_if_pipeline_succeeds, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions
- field :request_access_enabled, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions
- field :only_allow_merge_if_all_discussions_are_resolved, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions
- field :printing_merge_request_link_enabled, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions
+ field :only_allow_merge_if_pipeline_succeeds, GraphQL::BOOLEAN_TYPE, null: true,
+ description: 'Indicates if merge requests of the project can only be merged with successful jobs'
+ field :request_access_enabled, GraphQL::BOOLEAN_TYPE, null: true,
+ description: 'Indicates if users can request member access to the project'
+ field :only_allow_merge_if_all_discussions_are_resolved, GraphQL::BOOLEAN_TYPE, null: true,
+ description: 'Indicates if merge requests of the project can only be merged when all the discussions are resolved'
+ field :printing_merge_request_link_enabled, GraphQL::BOOLEAN_TYPE, null: true,
+ description: 'Indicates if a link to create or view a merge request should display after a push to Git repositories of the project from the command line'
+ field :remove_source_branch_after_merge, GraphQL::BOOLEAN_TYPE, null: true,
+ description: 'Indicates if `Delete source branch` option should be enabled by default for all new merge requests of the project'
- field :namespace, Types::NamespaceType, null: true # rubocop:disable Graphql/Descriptions
- field :group, Types::GroupType, null: true # rubocop:disable Graphql/Descriptions
+ field :namespace, Types::NamespaceType, null: true,
+ description: 'Namespace of the project'
+ field :group, Types::GroupType, null: true,
+ description: 'Group of the project'
- field :statistics, Types::ProjectStatisticsType, # rubocop:disable Graphql/Descriptions
+ field :statistics, Types::ProjectStatisticsType,
null: true,
+ description: 'Statistics of the project',
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchProjectStatisticsLoader.new(obj.id).find }
- field :repository, Types::RepositoryType, null: true # rubocop:disable Graphql/Descriptions
+ field :repository, Types::RepositoryType, null: true,
+ description: 'Git repository of the project'
- field :merge_requests, # rubocop:disable Graphql/Descriptions
+ field :merge_requests,
Types::MergeRequestType.connection_type,
null: true,
+ description: 'Merge requests of the project',
resolver: Resolvers::MergeRequestsResolver
- field :merge_request, # rubocop:disable Graphql/Descriptions
+ field :merge_request,
Types::MergeRequestType,
null: true,
+ description: 'A single merge request of the project',
resolver: Resolvers::MergeRequestsResolver.single
- field :issues, # rubocop:disable Graphql/Descriptions
+ field :issues,
Types::IssueType.connection_type,
null: true,
+ description: 'Issues of the project',
resolver: Resolvers::IssuesResolver
- field :issue, # rubocop:disable Graphql/Descriptions
- Types::ExtendedIssueType,
+ field :issue,
+ Types::IssueType,
null: true,
+ description: 'A single issue of the project',
resolver: Resolvers::IssuesResolver.single
- field :pipelines, # rubocop:disable Graphql/Descriptions
+ field :pipelines,
Types::Ci::PipelineType.connection_type,
null: true,
+ description: 'Build pipelines of the project',
resolver: Resolvers::ProjectPipelinesResolver
end
end
diff --git a/app/graphql/types/repository_type.rb b/app/graphql/types/repository_type.rb
index 9ecd336b41..f0c25e13a2 100644
--- a/app/graphql/types/repository_type.rb
+++ b/app/graphql/types/repository_type.rb
@@ -6,9 +6,13 @@ module Types
authorize :download_code
- field :root_ref, GraphQL::STRING_TYPE, null: true, calls_gitaly: true # rubocop:disable Graphql/Descriptions
- field :empty, GraphQL::BOOLEAN_TYPE, null: false, method: :empty?, calls_gitaly: true # rubocop:disable Graphql/Descriptions
- field :exists, GraphQL::BOOLEAN_TYPE, null: false, method: :exists? # rubocop:disable Graphql/Descriptions
- field :tree, Types::Tree::TreeType, null: true, resolver: Resolvers::TreeResolver, calls_gitaly: true # rubocop:disable Graphql/Descriptions
+ field :root_ref, GraphQL::STRING_TYPE, null: true, calls_gitaly: true,
+ description: 'Default branch of the repository'
+ field :empty, GraphQL::BOOLEAN_TYPE, null: false, method: :empty?, calls_gitaly: true,
+ description: 'Indicates repository has no visible content'
+ field :exists, GraphQL::BOOLEAN_TYPE, null: false, method: :exists?,
+ description: 'Indicates a corresponding Git repository exists on disk'
+ field :tree, Types::Tree::TreeType, null: true, resolver: Resolvers::TreeResolver, calls_gitaly: true,
+ description: 'Tree of the repository'
end
end
diff --git a/app/graphql/types/task_completion_status.rb b/app/graphql/types/task_completion_status.rb
index 0aa8fc60a7..73a8b4f302 100644
--- a/app/graphql/types/task_completion_status.rb
+++ b/app/graphql/types/task_completion_status.rb
@@ -8,8 +8,10 @@ module Types
graphql_name 'TaskCompletionStatus'
description 'Completion status of tasks'
- field :count, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions
- field :completed_count, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions
+ field :count, GraphQL::INT_TYPE, null: false,
+ description: 'Number of total tasks'
+ field :completed_count, GraphQL::INT_TYPE, null: false,
+ description: 'Number of completed tasks'
end
# rubocop: enable Graphql/AuthorizeTypes
end
diff --git a/app/graphql/types/todo_target_enum.rb b/app/graphql/types/todo_target_enum.rb
index 9a7391dcd9..8358a86b35 100644
--- a/app/graphql/types/todo_target_enum.rb
+++ b/app/graphql/types/todo_target_enum.rb
@@ -2,8 +2,10 @@
module Types
class TodoTargetEnum < BaseEnum
- value 'Issue'
- value 'MergeRequest'
- value 'Epic'
+ value 'COMMIT', value: 'Commit', description: 'A Commit'
+ value 'ISSUE', value: 'Issue', description: 'An Issue'
+ value 'MERGEREQUEST', value: 'MergeRequest', description: 'A MergeRequest'
end
end
+
+Types::TodoTargetEnum.prepend_if_ee('::EE::Types::TodoTargetEnum')
diff --git a/app/graphql/types/todo_type.rb b/app/graphql/types/todo_type.rb
index d36daaf7de..5ce5093c55 100644
--- a/app/graphql/types/todo_type.rb
+++ b/app/graphql/types/todo_type.rb
@@ -40,7 +40,8 @@ module Types
field :body, GraphQL::STRING_TYPE,
description: 'Body of the todo',
- null: false
+ null: false,
+ calls_gitaly: true # TODO This is only true when `target_type` is `Commit`. See https://gitlab.com/gitlab-org/gitlab/issues/34757#note_234752665
field :state, Types::TodoStateEnum,
description: 'State of the todo',
diff --git a/app/graphql/types/tree/entry_type.rb b/app/graphql/types/tree/entry_type.rb
index 10c2ad8815..87a3eced89 100644
--- a/app/graphql/types/tree/entry_type.rb
+++ b/app/graphql/types/tree/entry_type.rb
@@ -5,6 +5,7 @@ module Types
include Types::BaseInterface
field :id, GraphQL::ID_TYPE, null: false # rubocop:disable Graphql/Descriptions
+ field :sha, GraphQL::STRING_TYPE, null: false, description: "Last commit sha for entry", method: :id
field :name, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
field :type, Tree::TypeEnum, null: false # rubocop:disable Graphql/Descriptions
field :path, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
diff --git a/app/graphql/types/user_type.rb b/app/graphql/types/user_type.rb
index 1ba37927b4..b45c7893e7 100644
--- a/app/graphql/types/user_type.rb
+++ b/app/graphql/types/user_type.rb
@@ -8,12 +8,16 @@ module Types
present_using UserPresenter
- field :name, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
- field :username, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
- field :avatar_url, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
- field :web_url, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
+ field :name, GraphQL::STRING_TYPE, null: false,
+ description: 'Human-readable name of the user'
+ field :username, GraphQL::STRING_TYPE, null: false,
+ description: 'Username of the user. Unique within this instance of GitLab'
+ field :avatar_url, GraphQL::STRING_TYPE, null: false,
+ description: "URL of the user's avatar"
+ field :web_url, GraphQL::STRING_TYPE, null: false,
+ description: 'Web URL of the user'
field :todos, Types::TodoType.connection_type, null: false,
resolver: Resolvers::TodoResolver,
- description: 'Todos of this user'
+ description: 'Todos of the user'
end
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index ecaeb7060c..3ae804ff23 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -324,6 +324,15 @@ module ApplicationHelper
}
end
+ def asset_to_string(name)
+ app = Rails.application
+ if Rails.configuration.assets.compile
+ app.assets.find_asset(name).to_s
+ else
+ controller.view_context.render(file: Rails.root.join('public/assets', app.assets_manifest.assets[name]).to_s)
+ end
+ end
+
private
def appearance
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index df17b82412..a011209375 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -3,11 +3,11 @@
module ApplicationSettingsHelper
extend self
- delegate :allow_signup?,
- :gravatar_enabled?,
- :password_authentication_enabled_for_web?,
- :akismet_enabled?,
- to: :'Gitlab::CurrentSettings.current_application_settings'
+ delegate :allow_signup?,
+ :gravatar_enabled?,
+ :password_authentication_enabled_for_web?,
+ :akismet_enabled?,
+ to: :'Gitlab::CurrentSettings.current_application_settings'
def user_oauth_applications?
Gitlab::CurrentSettings.user_oauth_applications
@@ -176,6 +176,7 @@ module ApplicationSettingsHelper
:container_registry_token_expire_delay,
:default_artifacts_expire_in,
:default_branch_protection,
+ :default_ci_config_path,
:default_group_visibility,
:default_project_creation,
:default_project_visibility,
@@ -193,6 +194,10 @@ module ApplicationSettingsHelper
:dsa_key_restriction,
:ecdsa_key_restriction,
:ed25519_key_restriction,
+ :eks_integration_enabled,
+ :eks_account_id,
+ :eks_access_key_id,
+ :eks_secret_access_key,
:email_author_in_body,
:enabled_git_access_protocol,
:enforce_terms,
@@ -254,6 +259,9 @@ module ApplicationSettingsHelper
:shared_runners_text,
:sign_in_text,
:signup_enabled,
+ :sourcegraph_enabled,
+ :sourcegraph_url,
+ :sourcegraph_public_only,
:terminal_max_session_time,
:terms,
:throttle_authenticated_api_enabled,
@@ -289,7 +297,8 @@ module ApplicationSettingsHelper
:snowplow_collector_hostname,
:snowplow_cookie_domain,
:snowplow_enabled,
- :snowplow_site_id,
+ :snowplow_app_id,
+ :snowplow_iglu_registry_url,
:push_event_hooks_limit,
:push_event_activities_limit,
:custom_http_clone_url_root
@@ -312,6 +321,10 @@ module ApplicationSettingsHelper
Rails.env.test?
end
+ def integration_expanded?(substring)
+ @application_setting.errors.any? { |k| k.to_s.start_with?(substring) }
+ end
+
def instance_clusters_enabled?
can?(current_user, :read_cluster, Clusters::Instance.new)
end
diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb
index 9e6fcf6a26..a9c4cfe7dc 100644
--- a/app/helpers/auth_helper.rb
+++ b/app/helpers/auth_helper.rb
@@ -8,6 +8,10 @@ module AuthHelper
Gitlab::Auth::LDAP::Config.enabled?
end
+ def ldap_sign_in_enabled?
+ Gitlab::Auth::LDAP::Config.sign_in_enabled?
+ end
+
def omniauth_enabled?
Gitlab::Auth.omniauth_enabled?
end
@@ -56,6 +60,16 @@ module AuthHelper
auth_providers.select { |provider| form_based_provider?(provider) }
end
+ def any_form_based_providers_enabled?
+ form_based_providers.any? { |provider| form_enabled_for_sign_in?(provider) }
+ end
+
+ def form_enabled_for_sign_in?(provider)
+ return true unless provider.to_s.match?(LDAP_PROVIDER)
+
+ ldap_sign_in_enabled?
+ end
+
def crowd_enabled?
auth_providers.include? :crowd
end
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 5c24b0e170..912f0b6197 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -47,7 +47,7 @@ module BlobHelper
def edit_blob_button(project = @project, ref = @ref, path = @path, options = {})
return unless blob = readable_blob(options, path, project, ref)
- common_classes = "btn js-edit-blob #{options[:extra_class]}"
+ common_classes = "btn btn-primary js-edit-blob #{options[:extra_class]}"
edit_button_tag(blob,
common_classes,
@@ -62,7 +62,7 @@ module BlobHelper
return unless blob = readable_blob(options, path, project, ref)
edit_button_tag(blob,
- 'btn btn-default',
+ 'btn btn-inverted btn-primary ide-edit-button',
_('Web IDE'),
ide_edit_path(project, ref, path, options),
project,
@@ -108,7 +108,7 @@ module BlobHelper
path,
label: _("Delete"),
action: "delete",
- btn_class: "remove",
+ btn_class: "default",
modal_type: "remove"
)
end
@@ -141,11 +141,7 @@ module BlobHelper
if @build && @entry
raw_project_job_artifacts_url(@project, @build, path: @entry.path, **kwargs)
elsif @snippet
- if @snippet.project_id
- raw_project_snippet_url(@project, @snippet, **kwargs)
- else
- raw_snippet_url(@snippet, **kwargs)
- end
+ reliable_raw_snippet_url(@snippet)
elsif @blob
project_raw_url(@project, @id, **kwargs)
end
diff --git a/app/helpers/builds_helper.rb b/app/helpers/builds_helper.rb
index a5fe6bb8f0..2def348818 100644
--- a/app/helpers/builds_helper.rb
+++ b/app/helpers/builds_helper.rb
@@ -4,12 +4,12 @@ module BuildsHelper
def build_summary(build, skip: false)
if build.has_trace?
if skip
- link_to _("View job trace"), pipeline_job_url(build.pipeline, build)
+ link_to _("View job log"), pipeline_job_url(build.pipeline, build)
else
build.trace.html(last_lines: 10).html_safe
end
else
- _("No job trace")
+ _("No job log")
end
end
diff --git a/app/helpers/clusters_helper.rb b/app/helpers/clusters_helper.rb
index 7ca509873c..0037c49f13 100644
--- a/app/helpers/clusters_helper.rb
+++ b/app/helpers/clusters_helper.rb
@@ -6,6 +6,28 @@ module ClustersHelper
false
end
+ def create_new_cluster_label(provider: nil)
+ case provider
+ when 'aws'
+ s_('ClusterIntegration|Create new Cluster on EKS')
+ when 'gcp'
+ s_('ClusterIntegration|Create new Cluster on GKE')
+ else
+ s_('ClusterIntegration|Create new Cluster')
+ end
+ end
+
+ def new_cluster_partial(provider: nil)
+ case provider
+ when 'aws'
+ 'clusters/clusters/aws/new'
+ when 'gcp'
+ 'clusters/clusters/gcp/new'
+ else
+ 'clusters/clusters/cloud_providers/cloud_provider_selector'
+ end
+ end
+
def render_gcp_signup_offer
return if Gitlab::CurrentSettings.current_application_settings.hide_third_party_offers?
return unless show_gcp_signup_offer?
@@ -18,7 +40,7 @@ module ClustersHelper
def has_rbac_enabled?(cluster)
return cluster.platform_kubernetes_rbac? if cluster.platform_kubernetes
- !cluster.provider.legacy_abac?
+ cluster.provider.has_rbac_enabled?
end
end
diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb
index 518cb7c971..679622897a 100644
--- a/app/helpers/dashboard_helper.rb
+++ b/app/helpers/dashboard_helper.rb
@@ -27,16 +27,25 @@ module DashboardHelper
false
end
- def feature_entry(title, href: nil, enabled: true)
+ def feature_entry(title, href: nil, enabled: true, doc_href: nil)
enabled_text = enabled ? 'on' : 'off'
label = "#{title}: status #{enabled_text}"
link_or_title = href && enabled ? tag.a(title, href: href) : title
tag.p(aria: { label: label }) do
concat(link_or_title)
+
concat(tag.span(class: ['light', 'float-right']) do
- concat(boolean_to_icon(enabled))
+ boolean_to_icon(enabled)
end)
+
+ if doc_href.present?
+ link_to_doc = link_to(sprite_icon('question', size: 16), doc_href,
+ class: 'prepend-left-5', title: _('Documentation'),
+ target: '_blank', rel: 'noopener noreferrer')
+
+ concat(link_to_doc)
+ end
end
end
diff --git a/app/helpers/environments_helper.rb b/app/helpers/environments_helper.rb
index c642a64ad6..f57d0fa19d 100644
--- a/app/helpers/environments_helper.rb
+++ b/app/helpers/environments_helper.rb
@@ -34,6 +34,7 @@ module EnvironmentsHelper
"project-path" => project_path(project),
"tags-path" => project_tags_path(project),
"has-metrics" => "#{environment.has_metrics?}",
+ "prometheus-status" => "#{environment.prometheus_status}",
"external-dashboard-url" => project.metrics_setting_external_dashboard_url
}
end
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index 4f31cc67cc..404ea7b00d 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -66,7 +66,7 @@ module GitlabRoutingHelper
end
def preview_markdown_path(parent, *args)
- return group_preview_markdown_path(parent) if parent.is_a?(Group)
+ return group_preview_markdown_path(parent, *args) if parent.is_a?(Group)
if @snippet.is_a?(PersonalSnippet)
preview_markdown_snippets_path
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index df9d193327..3c72f41a4c 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -281,10 +281,7 @@ module IssuablesHelper
}
data[:hasClosingMergeRequest] = issuable.merge_requests_count(current_user) != 0 if issuable.is_a?(Issue)
-
- zoom_links = Gitlab::ZoomLinkExtractor.new(issuable.description).links
-
- data[:zoomMeetingUrl] = zoom_links.last if zoom_links.any?
+ data[:zoomMeetingUrl] = ZoomMeeting.canonical_meeting_url(issuable) if issuable.is_a?(Issue)
if parent.is_a?(Group)
data[:groupPath] = parent.path
diff --git a/app/helpers/markup_helper.rb b/app/helpers/markup_helper.rb
index e2524938e1..e1e756c2f4 100644
--- a/app/helpers/markup_helper.rb
+++ b/app/helpers/markup_helper.rb
@@ -51,12 +51,15 @@ module MarkupHelper
text = fragment.children[0].text
fragment.children[0].replace(link_to(text, url, html_options))
else
- # Traverse the fragment's first generation of children looking for pure
- # text, wrapping anything found in the requested link
+ # Traverse the fragment's first generation of children looking for
+ # either pure text or emojis, wrapping anything found in the
+ # requested link
fragment.children.each do |node|
- next unless node.text?
-
- node.replace(link_to(node.text, url, html_options))
+ if node.text?
+ node.replace(link_to(node.text, url, html_options))
+ elsif node.name == 'gl-emoji'
+ node.replace(link_to(node.to_html.html_safe, url, html_options))
+ end
end
end
diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb
index e769734f27..b12b39073e 100644
--- a/app/helpers/milestones_helper.rb
+++ b/app/helpers/milestones_helper.rb
@@ -4,6 +4,18 @@ module MilestonesHelper
include EntityDateHelper
include Gitlab::Utils::StrongMemoize
+ def milestone_status_string(milestone)
+ if milestone.closed?
+ _('Closed')
+ elsif milestone.expired?
+ _('Past due')
+ elsif milestone.upcoming?
+ _('Upcoming')
+ else
+ _('Open')
+ end
+ end
+
def milestones_filter_path(opts = {})
if @project
project_milestones_path(@project, opts)
@@ -170,6 +182,23 @@ module MilestonesHelper
content.join(' ').html_safe
end
+ def milestone_releases_tooltip_text(milestone)
+ count = milestone.releases.count
+
+ return _("Releases") if count.zero?
+
+ n_("%{releases} release", "%{releases} releases", count) % { releases: count }
+ end
+
+ def recent_releases_with_counts(milestone)
+ total_count = milestone.releases.size
+ return [[], 0, 0] if total_count == 0
+
+ recent_releases = milestone.releases.recent.to_a
+ more_count = total_count - recent_releases.size
+ [recent_releases, total_count, more_count]
+ end
+
def milestone_tooltip_due_date(milestone)
if milestone.due_date
"#{milestone.due_date.to_s(:medium)} (#{remaining_days_in_words(milestone.due_date, milestone.start_date)})"
@@ -196,33 +225,19 @@ module MilestonesHelper
end
end
- def milestone_merge_request_tab_path(milestone)
- if @project
- merge_requests_project_milestone_path(@project, milestone, format: :json)
- elsif @group
- merge_requests_group_milestone_path(@group, milestone.safe_title, title: milestone.title, format: :json)
+ def milestone_tab_path(milestone, tab)
+ if milestone.global_milestone?
+ url_for(action: tab, title: milestone.title, format: :json)
else
- merge_requests_dashboard_milestone_path(milestone, title: milestone.title, format: :json)
+ url_for(action: tab, format: :json)
end
end
- def milestone_participants_tab_path(milestone)
- if @project
- participants_project_milestone_path(@project, milestone, format: :json)
- elsif @group
- participants_group_milestone_path(@group, milestone.safe_title, title: milestone.title, format: :json)
+ def update_milestone_path(milestone, params = {})
+ if milestone.project_milestone?
+ project_milestone_path(milestone.project, milestone, milestone: params)
else
- participants_dashboard_milestone_path(milestone, title: milestone.title, format: :json)
- end
- end
-
- def milestone_labels_tab_path(milestone)
- if @project
- labels_project_milestone_path(@project, milestone, format: :json)
- elsif @group
- labels_group_milestone_path(@group, milestone.safe_title, title: milestone.title, format: :json)
- else
- labels_dashboard_milestone_path(milestone, title: milestone.title, format: :json)
+ group_milestone_route(milestone, params)
end
end
@@ -247,6 +262,14 @@ module MilestonesHelper
milestone_path(milestone.milestone, params)
end
+ def edit_milestone_path(milestone)
+ if milestone.group_milestone?
+ edit_group_milestone_path(milestone.group, milestone)
+ elsif milestone.project_milestone?
+ edit_project_milestone_path(milestone.project, milestone)
+ end
+ end
+
def can_admin_project_milestones?
strong_memoize(:can_admin_project_milestones) do
can?(current_user, :admin_milestone, @project)
diff --git a/app/helpers/projects/error_tracking_helper.rb b/app/helpers/projects/error_tracking_helper.rb
index fd1222a1df..c31e16e715 100644
--- a/app/helpers/projects/error_tracking_helper.rb
+++ b/app/helpers/projects/error_tracking_helper.rb
@@ -13,4 +13,13 @@ module Projects::ErrorTrackingHelper
'illustration-path' => image_path('illustrations/cluster_popover.svg')
}
end
+
+ def error_details_data(project, issue_id)
+ opts = [project, issue_id, { format: :json }]
+
+ {
+ 'issue-details-path' => details_project_error_tracking_index_path(*opts),
+ 'issue-stack-trace-path' => stack_trace_project_error_tracking_index_path(*opts)
+ }
+ end
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 0967b6b911..c68b6bdea0 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -369,6 +369,10 @@ module ProjectsHelper
@project.grafana_integration&.token
end
+ def grafana_integration_enabled?
+ @project.grafana_integration&.enabled?
+ end
+
private
def get_project_nav_tabs(project, current_user)
diff --git a/app/helpers/releases_helper.rb b/app/helpers/releases_helper.rb
index 68a19152d8..c4fe40a087 100644
--- a/app/helpers/releases_helper.rb
+++ b/app/helpers/releases_helper.rb
@@ -26,7 +26,8 @@ module ReleasesHelper
tag_name: @release.tag,
markdown_preview_path: preview_markdown_path(@project),
markdown_docs_path: help_page_path('user/markdown'),
- releases_page_path: project_releases_path(@project, anchor: @release.tag)
+ releases_page_path: project_releases_path(@project, anchor: @release.tag),
+ update_release_api_docs_path: help_page_path('api/releases/index.md', anchor: 'update-a-release')
}
end
end
diff --git a/app/helpers/repository_languages_helper.rb b/app/helpers/repository_languages_helper.rb
index cf7eee7fff..7834e86ada 100644
--- a/app/helpers/repository_languages_helper.rb
+++ b/app/helpers/repository_languages_helper.rb
@@ -4,7 +4,7 @@ module RepositoryLanguagesHelper
def repository_languages_bar(languages)
return if languages.none?
- content_tag :div, class: 'progress repository-languages-bar' do
+ content_tag :div, class: 'progress repository-languages-bar js-show-on-project-root' do
safe_join(languages.map { |lang| language_progress(lang) })
end
end
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 9a19758b4e..777fe82e4c 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
module SearchHelper
+ SEARCH_PERMITTED_PARAMS = [:search, :scope, :project_id, :group_id, :repository_ref, :snippets].freeze
+
def search_autocomplete_opts(term)
return unless current_user
@@ -96,8 +98,9 @@ module SearchHelper
result
end
- def search_blob_title(project, filename)
- filename
+ # Overriden in EE
+ def search_blob_title(project, path)
+ path
end
def search_service
@@ -199,7 +202,7 @@ module SearchHelper
search_params = params
.merge(search)
.merge({ scope: scope })
- .permit(:search, :scope, :project_id, :group_id, :repository_ref, :snippets)
+ .permit(SEARCH_PERMITTED_PARAMS)
if @scope == scope
li_class = 'active'
@@ -235,6 +238,7 @@ module SearchHelper
opts[:data]['project-id'] = @project.id
opts[:data]['labels-endpoint'] = project_labels_path(@project)
opts[:data]['milestones-endpoint'] = project_milestones_path(@project)
+ opts[:data]['releases-endpoint'] = project_releases_path(@project)
elsif @group.present?
opts[:data]['group-id'] = @group.id
opts[:data]['labels-endpoint'] = group_labels_path(@group)
@@ -272,7 +276,7 @@ module SearchHelper
sanitize(html, tags: %w(a p ol ul li pre code))
end
- def search_tabs?(tab)
+ def show_user_search_tab?
return false if Feature.disabled?(:users_search, default_enabled: true)
if @project
diff --git a/app/helpers/services_helper.rb b/app/helpers/services_helper.rb
index ea7c7af72d..19a27ba349 100644
--- a/app/helpers/services_helper.rb
+++ b/app/helpers/services_helper.rb
@@ -32,7 +32,7 @@ module ServicesHelper
end
def service_save_button(service)
- button_tag(class: 'btn btn-success', type: 'submit', disabled: service.deprecated?) do
+ button_tag(class: 'btn btn-success', type: 'submit', disabled: service.deprecated?, data: { qa_selector: 'save_changes_button' }) do
icon('spinner spin', class: 'hidden js-btn-spinner') +
content_tag(:span, 'Save changes', class: 'js-btn-label')
end
diff --git a/app/helpers/sessions_helper.rb b/app/helpers/sessions_helper.rb
index af98a611b8..ef737b25bc 100644
--- a/app/helpers/sessions_helper.rb
+++ b/app/helpers/sessions_helper.rb
@@ -4,4 +4,20 @@ module SessionsHelper
def unconfirmed_email?
flash[:alert] == t(:unconfirmed, scope: [:devise, :failure])
end
+
+ # By default, all sessions are given the same expiration time configured in
+ # the session store (e.g. 1 week). However, unauthenticated users can
+ # generate a lot of sessions, primarily for CSRF verification. It makes
+ # sense to reduce the TTL for unauthenticated to something much lower than
+ # the default (e.g. 1 hour) to limit Redis memory. In addition, Rails
+ # creates a new session after login, so the short TTL doesn't even need to
+ # be extended.
+ def limit_session_time
+ # Rack sets this header, but not all tests may have it: https://github.com/rack/rack/blob/fdcd03a3c5a1c51d1f96fc97f9dfa1a9deac0c77/lib/rack/session/abstract/id.rb#L251-L259
+ return unless request.env['rack.session.options']
+
+ # This works because Rack uses these options every time a request is handled:
+ # https://github.com/rack/rack/blob/fdcd03a3c5a1c51d1f96fc97f9dfa1a9deac0c77/lib/rack/session/abstract/id.rb#L342
+ request.env['rack.session.options'][:expire_after] = Settings.gitlab['unauthenticated_session_expire_delay']
+ end
end
diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb
index 6ccc1fb2ed..10e31fb888 100644
--- a/app/helpers/snippets_helper.rb
+++ b/app/helpers/snippets_helper.rb
@@ -11,22 +11,40 @@ module SnippetsHelper
end
end
- def reliable_snippet_path(snippet, opts = nil)
+ def reliable_snippet_path(snippet, opts = {})
+ reliable_snippet_url(snippet, opts.merge(only_path: true))
+ end
+
+ def reliable_raw_snippet_path(snippet, opts = {})
+ reliable_raw_snippet_url(snippet, opts.merge(only_path: true))
+ end
+
+ def reliable_snippet_url(snippet, opts = {})
if snippet.project_id?
- project_snippet_path(snippet.project, snippet, opts)
+ project_snippet_url(snippet.project, snippet, nil, opts)
else
- snippet_path(snippet, opts)
+ snippet_url(snippet, nil, opts)
end
end
- def download_snippet_path(snippet)
- if snippet.project_id
- raw_project_snippet_path(@project, snippet, inline: false)
+ def reliable_raw_snippet_url(snippet, opts = {})
+ if snippet.project_id?
+ raw_project_snippet_url(snippet.project, snippet, nil, opts)
else
- raw_snippet_path(snippet, inline: false)
+ raw_snippet_url(snippet, nil, opts)
end
end
+ def download_raw_snippet_button(snippet)
+ link_to(icon('download'),
+ reliable_raw_snippet_path(snippet, inline: false),
+ target: '_blank',
+ rel: 'noopener noreferrer',
+ class: "btn btn-sm has-tooltip",
+ title: 'Download',
+ data: { container: 'body' })
+ end
+
# Return the path of a snippets index for a user or for a project
#
# @returns String, path to snippet index
@@ -114,30 +132,45 @@ module SnippetsHelper
{ snippet_object: snippet, snippet_chunks: snippet_chunks }
end
- def snippet_embed
- ""
+ def snippet_embed_tag(snippet)
+ content_tag(:script, nil, src: reliable_snippet_url(snippet, format: :js, only_path: false))
end
- def embedded_snippet_raw_button
+ def snippet_badge(snippet)
+ return unless attrs = snippet_badge_attributes(snippet)
+
+ css_class, text = attrs
+ tag.span(class: ['badge', 'badge-gray']) do
+ concat(tag.i(class: ['fa', css_class]))
+ concat(' ')
+ concat(text)
+ end
+ end
+
+ def snippet_badge_attributes(snippet)
+ if snippet.private?
+ ['fa-lock', _('private')]
+ end
+ end
+
+ def embedded_raw_snippet_button
blob = @snippet.blob
return if blob.empty? || blob.binary? || blob.stored_externally?
- snippet_raw_url = if @snippet.is_a?(PersonalSnippet)
- raw_snippet_url(@snippet)
- else
- raw_project_snippet_url(@snippet.project, @snippet)
- end
-
- link_to external_snippet_icon('doc-code'), snippet_raw_url, class: 'btn', target: '_blank', rel: 'noopener noreferrer', title: 'Open raw'
+ link_to(external_snippet_icon('doc-code'),
+ reliable_raw_snippet_url(@snippet),
+ class: 'btn',
+ target: '_blank',
+ rel: 'noopener noreferrer',
+ title: 'Open raw')
end
def embedded_snippet_download_button
- download_url = if @snippet.is_a?(PersonalSnippet)
- raw_snippet_url(@snippet, inline: false)
- else
- raw_project_snippet_url(@snippet.project, @snippet, inline: false)
- end
-
- link_to external_snippet_icon('download'), download_url, class: 'btn', target: '_blank', title: 'Download', rel: 'noopener noreferrer'
+ link_to(external_snippet_icon('download'),
+ reliable_raw_snippet_url(@snippet, inline: false),
+ class: 'btn',
+ target: '_blank',
+ title: 'Download',
+ rel: 'noopener noreferrer')
end
end
diff --git a/app/helpers/sourcegraph_helper.rb b/app/helpers/sourcegraph_helper.rb
new file mode 100644
index 0000000000..cc5a5c77e9
--- /dev/null
+++ b/app/helpers/sourcegraph_helper.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module SourcegraphHelper
+ def sourcegraph_url_message
+ link_start = ''.html_safe % { url: Gitlab::CurrentSettings.sourcegraph_url }
+ link_end = "#{sprite_icon('external-link', size: 12, css_class: 'ml-1 vertical-align-center')} ".html_safe
+
+ message =
+ if Gitlab::CurrentSettings.sourcegraph_url_is_com?
+ s_('SourcegraphPreferences|Uses %{link_start}Sourcegraph.com%{link_end}.').html_safe
+ else
+ s_('SourcegraphPreferences|Uses a custom %{link_start}Sourcegraph instance%{link_end}.').html_safe
+ end
+
+ message % { link_start: link_start, link_end: link_end }
+ end
+
+ def sourcegraph_experimental_message
+ if Gitlab::Sourcegraph.feature_conditional?
+ s_("SourcegraphPreferences|This feature is experimental and currently limited to certain projects.")
+ elsif Gitlab::CurrentSettings.sourcegraph_public_only
+ s_("SourcegraphPreferences|This feature is experimental and limited to public projects.")
+ else
+ s_("SourcegraphPreferences|This feature is experimental.")
+ end
+ end
+end
diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb
index 53739cb63e..58edb327be 100644
--- a/app/helpers/tab_helper.rb
+++ b/app/helpers/tab_helper.rb
@@ -108,16 +108,6 @@ module TabHelper
current_controller?(c) && current_action?(a)
end
- def project_tab_class
- if controller.controller_path.start_with?('projects')
- return 'active'
- end
-
- if %w(services hooks deploy_keys protected_branches).include? controller.controller_name
- "active"
- end
- end
-
def branches_tab_class
if current_controller?(:protected_branches) ||
current_controller?(:branches) ||
@@ -125,14 +115,6 @@ module TabHelper
'active'
end
end
-
- def profile_tab_class
- if controller.controller_path.start_with?('profiles')
- return 'active'
- end
-
- 'active' if current_controller?('oauth/applications')
- end
end
TabHelper.prepend_if_ee('EE::TabHelper')
diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb
index afa057421e..fc25b78da9 100644
--- a/app/helpers/tree_helper.rb
+++ b/app/helpers/tree_helper.rb
@@ -186,6 +186,24 @@ module TreeHelper
attrs
end
+
+ def vue_file_list_data(project, ref)
+ {
+ project_path: project.full_path,
+ project_short_path: project.path,
+ ref: ref,
+ full_name: project.name_with_namespace
+ }
+ end
+
+ def directory_download_links(project, ref, archive_prefix)
+ Gitlab::Workhorse::ARCHIVE_FORMATS.map do |fmt|
+ {
+ text: fmt,
+ path: project_archive_path(project, id: tree_join(ref, archive_prefix), format: fmt)
+ }
+ end
+ end
end
TreeHelper.prepend_if_ee('::EE::TreeHelper')
diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb
index 4ff25d021f..ef0cb8b4bc 100644
--- a/app/helpers/users_helper.rb
+++ b/app/helpers/users_helper.rb
@@ -95,6 +95,14 @@ module UsersHelper
tabs
end
+ def trials_link_url
+ 'https://about.gitlab.com/free-trial/'
+ end
+
+ def trials_allowed?(user)
+ false
+ end
+
def get_current_user_menu_items
items = []
@@ -105,6 +113,7 @@ module UsersHelper
items << :help
items << :profile if can?(current_user, :read_user, current_user)
items << :settings if can?(current_user, :update_user, current_user)
+ items << :start_trial if trials_allowed?(current_user)
items
end
diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb
index 2bd803c017..a36de5dc54 100644
--- a/app/helpers/visibility_level_helper.rb
+++ b/app/helpers/visibility_level_helper.rb
@@ -201,9 +201,9 @@ module VisibilityLevelHelper
def visibility_level_errors_for_group(group, level_name)
group_name = link_to group.name, group_path(group)
- change_visiblity = link_to 'change the visibility', edit_group_path(group)
+ change_visibility = link_to 'change the visibility', edit_group_path(group)
{ reason: "the visibility of #{group_name} is #{group.visibility}",
- instruction: " To make this group #{level_name}, you must first #{change_visiblity} of the parent group." }
+ instruction: " To make this group #{level_name}, you must first #{change_visibility} of the parent group." }
end
end
diff --git a/app/mailers/emails/members.rb b/app/mailers/emails/members.rb
index ea8032324a..06d2219d6a 100644
--- a/app/mailers/emails/members.rb
+++ b/app/mailers/emails/members.rb
@@ -15,16 +15,18 @@ module Emails
user = User.find(recipient_id)
- mail(to: user.notification_email_for(notification_group),
- subject: subject("Request to join the #{member_source.human_name} #{member_source.model_name.singular}"))
+ member_email_with_layout(
+ to: user.notification_email_for(notification_group),
+ subject: subject("Request to join the #{member_source.human_name} #{member_source.model_name.singular}"))
end
def member_access_granted_email(member_source_type, member_id)
@member_source_type = member_source_type
@member_id = member_id
- mail(to: member.user.notification_email_for(notification_group),
- subject: subject("Access to the #{member_source.human_name} #{member_source.model_name.singular} was granted"))
+ member_email_with_layout(
+ to: member.user.notification_email_for(notification_group),
+ subject: subject("Access to the #{member_source.human_name} #{member_source.model_name.singular} was granted"))
end
def member_access_denied_email(member_source_type, source_id, user_id)
@@ -33,8 +35,9 @@ module Emails
user = User.find(user_id)
- mail(to: user.notification_email_for(notification_group),
- subject: subject("Access to the #{member_source.human_name} #{member_source.model_name.singular} was denied"))
+ member_email_with_layout(
+ to: user.notification_email_for(notification_group),
+ subject: subject("Access to the #{member_source.human_name} #{member_source.model_name.singular} was denied"))
end
def member_invited_email(member_source_type, member_id, token)
@@ -42,8 +45,9 @@ module Emails
@member_id = member_id
@token = token
- mail(to: member.invite_email,
- subject: subject("Invitation to join the #{member_source.human_name} #{member_source.model_name.singular}"))
+ member_email_with_layout(
+ to: member.invite_email,
+ subject: subject("Invitation to join the #{member_source.human_name} #{member_source.model_name.singular}"))
end
def member_invite_accepted_email(member_source_type, member_id)
@@ -51,8 +55,9 @@ module Emails
@member_id = member_id
return unless member.created_by
- mail(to: member.created_by.notification_email_for(notification_group),
- subject: subject('Invitation accepted'))
+ member_email_with_layout(
+ to: member.created_by.notification_email_for(notification_group),
+ subject: subject('Invitation accepted'))
end
def member_invite_declined_email(member_source_type, source_id, invite_email, created_by_id)
@@ -64,8 +69,9 @@ module Emails
user = User.find(created_by_id)
- mail(to: user.notification_email_for(notification_group),
- subject: subject('Invitation declined'))
+ member_email_with_layout(
+ to: user.notification_email_for(notification_group),
+ subject: subject('Invitation declined'))
end
def member
@@ -85,5 +91,12 @@ module Emails
def member_source_class
@member_source_type.classify.constantize
end
+
+ def member_email_with_layout(to:, subject:)
+ mail(to: to, subject: subject) do |format|
+ format.html { render layout: 'mailer' }
+ format.text { render layout: 'mailer' }
+ end
+ end
end
end
diff --git a/app/mailers/emails/pipelines.rb b/app/mailers/emails/pipelines.rb
index 34e12a5fa6..95bb52d8f9 100644
--- a/app/mailers/emails/pipelines.rb
+++ b/app/mailers/emails/pipelines.rb
@@ -18,12 +18,11 @@ module Emails
@merge_request = pipeline.all_merge_requests.first
add_headers
- # We use bcc here because we don't want to generate this emails for a
+ # We use bcc here because we don't want to generate these emails for a
# thousand times. This could be potentially expensive in a loop, and
# recipients would contain all project watchers so it could be a lot.
mail(bcc: recipients,
- subject: pipeline_subject(status),
- skip_premailer: true) do |format|
+ subject: pipeline_subject(status)) do |format|
format.html { render layout: 'mailer' }
format.text { render layout: 'mailer' }
end
diff --git a/app/mailers/emails/releases.rb b/app/mailers/emails/releases.rb
index 137858d31e..c9c77ab933 100644
--- a/app/mailers/emails/releases.rb
+++ b/app/mailers/emails/releases.rb
@@ -21,7 +21,13 @@ module Emails
private
def release_email_subject
- release_info = [@release.name, @release.tag].select(&:presence).join(' - ')
+ release_info =
+ if @release.name == @release.tag
+ @release.tag
+ else
+ [@release.name, @release.tag].select(&:presence).join(' - ')
+ end
+
"New release: #{release_info}"
end
end
diff --git a/app/mailers/previews/notify_preview.rb b/app/mailers/previews/notify_preview.rb
index 3d42423ba4..381a4f54d9 100644
--- a/app/mailers/previews/notify_preview.rb
+++ b/app/mailers/previews/notify_preview.rb
@@ -77,7 +77,7 @@ class NotifyPreview < ActionMailer::Preview
end
def import_issues_csv_email
- Notify.import_issues_csv_email(user, project, { success: 3, errors: [5, 6, 7], valid_file: true })
+ Notify.import_issues_csv_email(user.id, project.id, { success: 3, errors: [5, 6, 7], valid_file: true })
end
def closed_merge_request_email
@@ -109,11 +109,11 @@ class NotifyPreview < ActionMailer::Preview
end
def member_access_requested_email
- Notify.member_access_requested_email('group', user.id, user.id).message
+ Notify.member_access_requested_email(member.source_type, member.id, user.id).message
end
def member_invite_accepted_email
- Notify.member_invite_accepted_email('project', user.id).message
+ Notify.member_invite_accepted_email(member.source_type, member.id).message
end
def member_invite_declined_email
diff --git a/app/models/abuse_report.rb b/app/models/abuse_report.rb
index a3a1748142..7cfebf0473 100644
--- a/app/models/abuse_report.rb
+++ b/app/models/abuse_report.rb
@@ -2,6 +2,7 @@
class AbuseReport < ApplicationRecord
include CacheMarkdownField
+ include Sortable
cache_markdown_field :message, pipeline: :single_line
@@ -13,6 +14,9 @@ class AbuseReport < ApplicationRecord
validates :message, presence: true
validates :user_id, uniqueness: { message: 'has already been reported' }
+ scope :by_user, -> (user) { where(user_id: user) }
+ scope :with_users, -> { includes(:reporter, :user) }
+
# For CacheMarkdownField
alias_method :author, :reporter
diff --git a/app/models/analytics/cycle_analytics/project_stage.rb b/app/models/analytics/cycle_analytics/project_stage.rb
index 23f0db0829..b2c16444a2 100644
--- a/app/models/analytics/cycle_analytics/project_stage.rb
+++ b/app/models/analytics/cycle_analytics/project_stage.rb
@@ -10,6 +10,25 @@ module Analytics
alias_attribute :parent, :project
alias_attribute :parent_id, :project_id
+
+ delegate :group, to: :project
+
+ validate :validate_project_group_for_label_events, if: -> { start_event_label_based? || end_event_label_based? }
+
+ def self.relative_positioning_query_base(stage)
+ where(project_id: stage.project_id)
+ end
+
+ def self.relative_positioning_parent_column
+ :project_id
+ end
+
+ private
+
+ # Project should belong to a group when the stage has Label based events since only GroupLabels are allowed.
+ def validate_project_group_for_label_events
+ errors.add(:project, s_('CycleAnalyticsStage|should be under a group')) unless project.group
+ end
end
end
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index fb702b3898..72605af433 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -6,6 +6,12 @@ class ApplicationSetting < ApplicationRecord
include TokenAuthenticatable
include ChronicDurationAttribute
+ # Only remove this >= %12.6 and >= 2019-12-01
+ self.ignored_columns += %i[
+ pendo_enabled
+ pendo_url
+ ]
+
add_authentication_token_field :runners_registration_token, encrypted: -> { Feature.enabled?(:application_settings_tokens_optional_encryption, default_enabled: true) ? :optional : :required }
add_authentication_token_field :health_check_access_token
add_authentication_token_field :static_objects_external_storage_auth_token
@@ -18,12 +24,6 @@ class ApplicationSetting < ApplicationRecord
# fix a lot of tests using allow_any_instance_of
include ApplicationSettingImplementation
- attr_encrypted :asset_proxy_secret_key,
- mode: :per_attribute_iv,
- insecure_mode: true,
- key: Settings.attr_encrypted_db_key_base_truncated,
- algorithm: 'aes-256-cbc'
-
serialize :restricted_visibility_levels # rubocop:disable Cop/ActiveRecordSerialize
serialize :import_sources # rubocop:disable Cop/ActiveRecordSerialize
serialize :disabled_oauth_sign_in_sources, Array # rubocop:disable Cop/ActiveRecordSerialize
@@ -99,11 +99,20 @@ class ApplicationSetting < ApplicationRecord
presence: true,
if: :plantuml_enabled
+ validates :sourcegraph_url,
+ presence: true,
+ if: :sourcegraph_enabled
+
validates :snowplow_collector_hostname,
presence: true,
hostname: true,
if: :snowplow_enabled
+ validates :snowplow_iglu_registry_url,
+ addressable_url: true,
+ allow_blank: true,
+ if: :snowplow_enabled
+
validates :max_attachment_size,
presence: true,
numericality: { only_integer: true, greater_than: 0 }
@@ -270,12 +279,40 @@ class ApplicationSetting < ApplicationRecord
presence: true,
if: :lets_encrypt_terms_of_service_accepted?
+ validates :eks_integration_enabled,
+ inclusion: { in: [true, false] }
+
+ validates :eks_account_id,
+ format: { with: Gitlab::Regex.aws_account_id_regex,
+ message: Gitlab::Regex.aws_account_id_message },
+ if: :eks_integration_enabled?
+
+ validates :eks_access_key_id,
+ length: { in: 16..128 },
+ if: :eks_integration_enabled?
+
+ validates :eks_secret_access_key,
+ presence: true,
+ if: :eks_integration_enabled?
+
validates_with X509CertificateCredentialsValidator,
certificate: :external_auth_client_cert,
pkey: :external_auth_client_key,
pass: :external_auth_client_key_pass,
if: -> (setting) { setting.external_auth_client_cert.present? }
+ validates :default_ci_config_path,
+ format: { without: %r{(\.{2}|\A/)},
+ message: N_('cannot include leading slash or directory traversal.') },
+ length: { maximum: 255 },
+ allow_blank: true
+
+ attr_encrypted :asset_proxy_secret_key,
+ mode: :per_attribute_iv,
+ key: Settings.attr_encrypted_db_key_base_truncated,
+ algorithm: 'aes-256-cbc',
+ insecure_mode: true
+
private_class_method def self.encryption_options_base_truncated_aes_256_gcm
{
mode: :per_attribute_iv,
@@ -288,6 +325,7 @@ class ApplicationSetting < ApplicationRecord
attr_encrypted :external_auth_client_key, encryption_options_base_truncated_aes_256_gcm
attr_encrypted :external_auth_client_key_pass, encryption_options_base_truncated_aes_256_gcm
attr_encrypted :lets_encrypt_private_key, encryption_options_base_truncated_aes_256_gcm
+ attr_encrypted :eks_secret_access_key, encryption_options_base_truncated_aes_256_gcm
attr_encrypted :akismet_api_key, encryption_options_base_truncated_aes_256_gcm
attr_encrypted :elasticsearch_aws_secret_access_key, encryption_options_base_truncated_aes_256_gcm
attr_encrypted :recaptcha_private_key, encryption_options_base_truncated_aes_256_gcm
@@ -305,6 +343,10 @@ class ApplicationSetting < ApplicationRecord
end
after_commit :expire_performance_bar_allowed_user_ids_cache, if: -> { previous_changes.key?('performance_bar_allowed_group_id') }
+ def sourcegraph_url_is_com?
+ !!(sourcegraph_url =~ /\Ahttps:\/\/(www\.)?sourcegraph\.com/)
+ end
+
def self.create_from_defaults
transaction(requires_new: true) do
super
diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb
index 0c0ffb67c9..7bb89f0d1e 100644
--- a/app/models/application_setting_implementation.rb
+++ b/app/models/application_setting_implementation.rb
@@ -42,6 +42,7 @@ module ApplicationSettingImplementation
container_registry_token_expire_delay: 5,
default_artifacts_expire_in: '30 days',
default_branch_protection: Settings.gitlab['default_branch_protection'],
+ default_ci_config_path: nil,
default_group_visibility: Settings.gitlab.default_projects_features['visibility_level'],
default_project_creation: Settings.gitlab['default_project_creation'],
default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
@@ -54,6 +55,10 @@ module ApplicationSettingImplementation
dsa_key_restriction: 0,
ecdsa_key_restriction: 0,
ed25519_key_restriction: 0,
+ eks_integration_enabled: false,
+ eks_account_id: nil,
+ eks_access_key_id: nil,
+ eks_secret_access_key: nil,
first_day_of_week: 0,
gitaly_timeout_default: 55,
gitaly_timeout_fast: 10,
@@ -97,6 +102,9 @@ module ApplicationSettingImplementation
shared_runners_text: nil,
sign_in_text: nil,
signup_enabled: Settings.gitlab['signup_enabled'],
+ sourcegraph_enabled: false,
+ sourcegraph_url: nil,
+ sourcegraph_public_only: true,
terminal_max_session_time: 0,
throttle_authenticated_api_enabled: false,
throttle_authenticated_api_period_in_seconds: 3600,
@@ -128,8 +136,10 @@ module ApplicationSettingImplementation
snowplow_collector_hostname: nil,
snowplow_cookie_domain: nil,
snowplow_enabled: false,
- snowplow_site_id: nil,
- custom_http_clone_url_root: nil
+ snowplow_app_id: nil,
+ snowplow_iglu_registry_url: nil,
+ custom_http_clone_url_root: nil,
+ productivity_analytics_start_date: Time.now
}
end
diff --git a/app/models/award_emoji.rb b/app/models/award_emoji.rb
index 24fcb97db6..5a33a8f89d 100644
--- a/app/models/award_emoji.rb
+++ b/app/models/award_emoji.rb
@@ -6,11 +6,14 @@ class AwardEmoji < ApplicationRecord
include Participable
include GhostUser
+ include Importable
belongs_to :awardable, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
belongs_to :user
- validates :awardable, :user, presence: true
+ validates :user, presence: true
+ validates :awardable, presence: true, unless: :importing?
+
validates :name, presence: true, inclusion: { in: Gitlab::Emoji.emojis_names }
validates :name, uniqueness: { scope: [:user, :awardable_type, :awardable_id] }, unless: :ghost_user?
diff --git a/app/models/aws/role.rb b/app/models/aws/role.rb
index 836107435a..54132be749 100644
--- a/app/models/aws/role.rb
+++ b/app/models/aws/role.rb
@@ -13,5 +13,11 @@ module Aws
with: Gitlab::Regex.aws_arn_regex,
message: Gitlab::Regex.aws_arn_regex_message
}
+
+ before_validation :ensure_role_external_id!, on: :create
+
+ def ensure_role_external_id!
+ self.role_external_id ||= SecureRandom.hex(20)
+ end
end
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index c48ab28ce7..59a2c09bd2 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -35,10 +35,13 @@ module Ci
refspecs: -> (build) { build.merge_request_ref? }
}.freeze
+ DEFAULT_RETRIES = {
+ scheduler_failure: 2
+ }.freeze
+
has_one :deployment, as: :deployable, class_name: 'Deployment'
has_many :trace_sections, class_name: 'Ci::BuildTraceSection'
has_many :trace_chunks, class_name: 'Ci::BuildTraceChunk', foreign_key: :build_id
- has_many :needs, class_name: 'Ci::BuildNeed', foreign_key: :build_id, inverse_of: :build
has_many :job_artifacts, class_name: 'Ci::JobArtifact', foreign_key: :job_id, dependent: :destroy, inverse_of: :job # rubocop:disable Cop/ActiveRecordDependent
has_many :job_variables, class_name: 'Ci::JobVariable', foreign_key: :job_id
@@ -52,7 +55,6 @@ module Ci
accepts_nested_attributes_for :runner_session
accepts_nested_attributes_for :job_variables
- accepts_nested_attributes_for :needs
delegate :url, to: :runner_session, prefix: true, allow_nil: true
delegate :terminal_specification, to: :runner_session, allow_nil: true
@@ -118,6 +120,11 @@ module Ci
scope :eager_load_job_artifacts, -> { includes(:job_artifacts) }
+ scope :with_exposed_artifacts, -> do
+ joins(:metadata).merge(Ci::BuildMetadata.with_exposed_artifacts)
+ .includes(:metadata, :job_artifacts_metadata)
+ end
+
scope :with_artifacts_not_expired, ->() { with_artifacts_archive.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) }
scope :with_expired_artifacts, ->() { with_artifacts_archive.where('artifacts_expire_at < ?', Time.now) }
scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) }
@@ -367,18 +374,25 @@ module Ci
pipeline.builds.retried.where(name: self.name).count
end
- def retries_max
- normalized_retry.fetch(:max, 0)
- end
-
- def retry_when
- normalized_retry.fetch(:when, ['always'])
- end
-
def retry_failure?
- return false if retries_max.zero? || retries_count >= retries_max
+ max_allowed_retries = nil
+ max_allowed_retries ||= options_retry_max if retry_on_reason_or_always?
+ max_allowed_retries ||= DEFAULT_RETRIES.fetch(failure_reason.to_sym, 0)
- retry_when.include?('always') || retry_when.include?(failure_reason.to_s)
+ max_allowed_retries > 0 && retries_count < max_allowed_retries
+ end
+
+ def options_retry_max
+ options_retry[:max]
+ end
+
+ def options_retry_when
+ options_retry.fetch(:when, ['always'])
+ end
+
+ def retry_on_reason_or_always?
+ options_retry_when.include?(failure_reason.to_s) ||
+ options_retry_when.include?('always')
end
def latest?
@@ -595,6 +609,14 @@ module Ci
update_column(:trace, nil)
end
+ def artifacts_expose_as
+ options.dig(:artifacts, :expose_as)
+ end
+
+ def artifacts_paths
+ options.dig(:artifacts, :paths)
+ end
+
def needs_touch?
Time.now - updated_at > 15.minutes.to_i
end
@@ -818,6 +840,13 @@ module Ci
:creating
end
+ # Consider this object to have a structural integrity problems
+ def doom!
+ update_columns(
+ status: :failed,
+ failure_reason: :data_integrity_failure)
+ end
+
private
def successful_deployment_status
@@ -862,19 +891,13 @@ module Ci
# format, but builds created before GitLab 11.5 and saved in database still
# have the old integer only format. This method returns the retry option
# normalized as a hash in 11.5+ format.
- def normalized_retry
- strong_memoize(:normalized_retry) do
+ def options_retry
+ strong_memoize(:options_retry) do
value = options&.dig(:retry)
value = value.is_a?(Integer) ? { max: value } : value.to_h
value.with_indifferent_access
end
end
-
- def build_attributes_from_config
- return {} unless pipeline.config_processor
-
- pipeline.config_processor.build_attributes(name)
- end
end
end
diff --git a/app/models/ci/build_metadata.rb b/app/models/ci/build_metadata.rb
index 3097e40dd3..0df5ebfe84 100644
--- a/app/models/ci/build_metadata.rb
+++ b/app/models/ci/build_metadata.rb
@@ -27,6 +27,7 @@ module Ci
scope :scoped_build, -> { where('ci_builds_metadata.build_id = ci_builds.id') }
scope :with_interruptible, -> { where(interruptible: true) }
+ scope :with_exposed_artifacts, -> { where(has_exposed_artifacts: true) }
enum timeout_source: {
unknown_timeout_source: 1,
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 3bf19399ce..f730b949ee 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -405,7 +405,7 @@ module Ci
.where('stage=sg.stage').failed_but_allowed.to_sql
stages_with_statuses = CommitStatus.from(stages_query, :sg)
- .pluck('sg.stage', status_sql, "(#{warnings_sql})")
+ .pluck('sg.stage', Arel.sql(status_sql), Arel.sql("(#{warnings_sql})"))
stages_with_statuses.map do |stage|
Ci::LegacyStage.new(self, Hash[%i[name status warnings].zip(stage)])
@@ -551,23 +551,6 @@ module Ci
end
end
- def stage_seeds
- return [] unless config_processor
-
- strong_memoize(:stage_seeds) do
- seeds = config_processor.stages_attributes.inject([]) do |previous_stages, attributes|
- seed = Gitlab::Ci::Pipeline::Seed::Stage.new(self, attributes, previous_stages)
- previous_stages + [seed]
- end
-
- seeds.select(&:included?)
- end
- end
-
- def seeds_size
- stage_seeds.sum(&:size)
- end
-
def has_kubernetes_active?
project.deployment_platform&.active?
end
@@ -587,56 +570,14 @@ module Ci
end
end
- def set_config_source
- if ci_yaml_from_repo
- self.config_source = :repository_source
- elsif implied_ci_yaml_file
- self.config_source = :auto_devops_source
- end
- end
-
- ##
- # TODO, setting yaml_errors should be moved to the pipeline creation chain.
- #
- def config_processor
- return unless ci_yaml_file
- return @config_processor if defined?(@config_processor)
-
- @config_processor ||= begin
- ::Gitlab::Ci::YamlProcessor.new(ci_yaml_file, { project: project, sha: sha, user: user })
- rescue Gitlab::Ci::YamlProcessor::ValidationError => e
- self.yaml_errors = e.message
- nil
- rescue
- self.yaml_errors = 'Undefined error'
- nil
- end
- end
-
- def ci_yaml_file_path
+ # TODO: this logic is duplicate with Pipeline::Chain::Config::Content
+ # we should persist this is `ci_pipelines.config_path`
+ def config_path
return unless repository_source? || unknown_source?
project.ci_config_path.presence || '.gitlab-ci.yml'
end
- def ci_yaml_file
- return @ci_yaml_file if defined?(@ci_yaml_file)
-
- @ci_yaml_file =
- if auto_devops_source?
- implied_ci_yaml_file
- else
- ci_yaml_from_repo
- end
-
- if @ci_yaml_file
- @ci_yaml_file
- else
- self.yaml_errors = "Failed to load CI/CD config file for #{sha}"
- nil
- end
- end
-
def has_yaml_errors?
yaml_errors.present?
end
@@ -705,7 +646,7 @@ module Ci
def predefined_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables|
variables.append(key: 'CI_PIPELINE_IID', value: iid.to_s)
- variables.append(key: 'CI_CONFIG_PATH', value: ci_yaml_file_path)
+ variables.append(key: 'CI_CONFIG_PATH', value: config_path)
variables.append(key: 'CI_PIPELINE_SOURCE', value: source.to_s)
variables.append(key: 'CI_COMMIT_MESSAGE', value: git_commit_message.to_s)
variables.append(key: 'CI_COMMIT_TITLE', value: git_commit_full_title.to_s)
@@ -783,6 +724,10 @@ module Ci
end
end
+ def has_exposed_artifacts?
+ complete? && builds.latest.with_exposed_artifacts.exists?
+ end
+
def branch_updated?
strong_memoize(:branch_updated) do
push_details.branch_updated?
@@ -896,24 +841,6 @@ module Ci
private
- def ci_yaml_from_repo
- return unless project
- return unless sha
- return unless ci_yaml_file_path
-
- project.repository.gitlab_ci_yml_for(sha, ci_yaml_file_path)
- rescue GRPC::NotFound, GRPC::Internal
- nil
- end
-
- def implied_ci_yaml_file
- return unless project
-
- if project.auto_devops_enabled?
- Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps').content
- end
- end
-
def pipeline_data
Gitlab::DataBuilder::Pipeline.build(self)
end
diff --git a/app/models/clusters/applications/cert_manager.rb b/app/models/clusters/applications/cert_manager.rb
index 18cbf827a6..7ba04d1a2d 100644
--- a/app/models/clusters/applications/cert_manager.rb
+++ b/app/models/clusters/applications/cert_manager.rb
@@ -65,7 +65,7 @@ module Clusters
end
def retry_command(command)
- "for i in $(seq 1 30); do #{command} && s=0 && break || s=$?; sleep 1s; echo \"Retrying ($i)...\"; done; (exit $s)"
+ "for i in $(seq 1 90); do #{command} && s=0 && break || s=$?; sleep 1s; echo \"Retrying ($i)...\"; done; (exit $s)"
end
def post_delete_script
diff --git a/app/models/clusters/applications/crossplane.rb b/app/models/clusters/applications/crossplane.rb
new file mode 100644
index 0000000000..36246b2606
--- /dev/null
+++ b/app/models/clusters/applications/crossplane.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Applications
+ class Crossplane < ApplicationRecord
+ VERSION = '0.4.1'
+
+ self.table_name = 'clusters_applications_crossplane'
+
+ include ::Clusters::Concerns::ApplicationCore
+ include ::Clusters::Concerns::ApplicationStatus
+ include ::Clusters::Concerns::ApplicationVersion
+ include ::Clusters::Concerns::ApplicationData
+
+ default_value_for :version, VERSION
+
+ default_value_for :stack do |crossplane|
+ ''
+ end
+
+ validates :stack, presence: true
+
+ def chart
+ 'crossplane/crossplane'
+ end
+
+ def repository
+ 'https://charts.crossplane.io/alpha'
+ end
+
+ def install_command
+ Gitlab::Kubernetes::Helm::InstallCommand.new(
+ name: 'crossplane',
+ repository: repository,
+ version: VERSION,
+ rbac: cluster.platform_kubernetes_rbac?,
+ chart: chart,
+ files: files
+ )
+ end
+
+ def values
+ crossplane_values.to_yaml
+ end
+
+ private
+
+ def crossplane_values
+ {
+ "clusterStacks" => {
+ self.stack => {
+ "deploy" => true,
+ "version" => "alpha"
+ }
+ }
+ }
+ end
+ end
+ end
+end
diff --git a/app/models/clusters/applications/elastic_stack.rb b/app/models/clusters/applications/elastic_stack.rb
new file mode 100644
index 0000000000..8589f8c00c
--- /dev/null
+++ b/app/models/clusters/applications/elastic_stack.rb
@@ -0,0 +1,108 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Applications
+ class ElasticStack < ApplicationRecord
+ VERSION = '1.8.0'
+
+ ELASTICSEARCH_PORT = 9200
+
+ self.table_name = 'clusters_applications_elastic_stacks'
+
+ include ::Clusters::Concerns::ApplicationCore
+ include ::Clusters::Concerns::ApplicationStatus
+ include ::Clusters::Concerns::ApplicationVersion
+ include ::Clusters::Concerns::ApplicationData
+ include ::Gitlab::Utils::StrongMemoize
+
+ default_value_for :version, VERSION
+
+ def set_initial_status
+ return unless not_installable?
+ return unless cluster&.application_ingress_available?
+
+ ingress = cluster.application_ingress
+ self.status = status_states[:installable] if ingress.external_ip_or_hostname?
+ end
+
+ def chart
+ 'stable/elastic-stack'
+ end
+
+ def values
+ content_values.to_yaml
+ end
+
+ def install_command
+ Gitlab::Kubernetes::Helm::InstallCommand.new(
+ name: 'elastic-stack',
+ version: VERSION,
+ rbac: cluster.platform_kubernetes_rbac?,
+ chart: chart,
+ files: files
+ )
+ end
+
+ def uninstall_command
+ Gitlab::Kubernetes::Helm::DeleteCommand.new(
+ name: 'elastic-stack',
+ rbac: cluster.platform_kubernetes_rbac?,
+ files: files,
+ postdelete: post_delete_script
+ )
+ end
+
+ def elasticsearch_client
+ strong_memoize(:elasticsearch_client) do
+ next unless kube_client
+
+ proxy_url = kube_client.proxy_url('service', 'elastic-stack-elasticsearch-client', ::Clusters::Applications::ElasticStack::ELASTICSEARCH_PORT, Gitlab::Kubernetes::Helm::NAMESPACE)
+
+ Elasticsearch::Client.new(url: proxy_url) do |faraday|
+ # ensures headers containing auth data are appended to original client options
+ faraday.headers.merge!(kube_client.headers)
+ # ensure TLS certs are properly verified
+ faraday.ssl[:verify] = kube_client.ssl_options[:verify_ssl]
+ faraday.ssl[:cert_store] = kube_client.ssl_options[:cert_store]
+ end
+
+ rescue Kubeclient::HttpError => error
+ # If users have mistakenly set parameters or removed the depended clusters,
+ # `proxy_url` could raise an exception because gitlab can not communicate with the cluster.
+ # We check for a nil client in downstream use and behaviour is equivalent to an empty state
+ log_exception(error, :failed_to_create_elasticsearch_client)
+ end
+ end
+
+ private
+
+ def specification
+ {
+ "kibana" => {
+ "ingress" => {
+ "hosts" => [kibana_hostname],
+ "tls" => [{
+ "hosts" => [kibana_hostname],
+ "secretName" => "kibana-cert"
+ }]
+ }
+ }
+ }
+ end
+
+ def content_values
+ YAML.load_file(chart_values_file).deep_merge!(specification)
+ end
+
+ def post_delete_script
+ [
+ Gitlab::Kubernetes::KubectlCmd.delete("pvc", "--selector", "release=elastic-stack")
+ ].compact
+ end
+
+ def kube_client
+ cluster&.kubeclient&.core_client
+ end
+ end
+ end
+end
diff --git a/app/models/clusters/applications/ingress.rb b/app/models/clusters/applications/ingress.rb
index 885e4ff719..d140649af3 100644
--- a/app/models/clusters/applications/ingress.rb
+++ b/app/models/clusters/applications/ingress.rb
@@ -21,6 +21,7 @@ module Clusters
}
FETCH_IP_ADDRESS_DELAY = 30.seconds
+ MODSEC_SIDECAR_INITIAL_DELAY_SECONDS = 10
state_machine :status do
after_transition any => [:installed] do |application|
@@ -40,7 +41,7 @@ module Clusters
end
def allowed_to_uninstall?
- external_ip_or_hostname? && application_jupyter_nil_or_installable?
+ external_ip_or_hostname? && application_jupyter_nil_or_installable? && application_elastic_stack_nil_or_installable?
end
def install_command
@@ -78,12 +79,74 @@ module Clusters
"controller" => {
"config" => {
"enable-modsecurity" => "true",
- "enable-owasp-modsecurity-crs" => "true"
- }
+ "enable-owasp-modsecurity-crs" => "true",
+ "modsecurity.conf" => modsecurity_config_content
+ },
+ "extraContainers" => [
+ {
+ "name" => "modsecurity-log",
+ "image" => "busybox",
+ "args" => [
+ "/bin/sh",
+ "-c",
+ "tail -f /var/log/modsec/audit.log"
+ ],
+ "volumeMounts" => [
+ {
+ "name" => "modsecurity-log-volume",
+ "mountPath" => "/var/log/modsec",
+ "readOnly" => true
+ }
+ ],
+ "startupProbe" => {
+ "exec" => {
+ "command" => ["ls", "/var/log/modsec"]
+ },
+ "initialDelaySeconds" => MODSEC_SIDECAR_INITIAL_DELAY_SECONDS
+ }
+ }
+ ],
+ "extraVolumeMounts" => [
+ {
+ "name" => "modsecurity-template-volume",
+ "mountPath" => "/etc/nginx/modsecurity/modsecurity.conf",
+ "subPath" => "modsecurity.conf"
+ },
+ {
+ "name" => "modsecurity-log-volume",
+ "mountPath" => "/var/log/modsec"
+ }
+ ],
+ "extraVolumes" => [
+ {
+ "name" => "modsecurity-template-volume",
+ "configMap" => {
+ "name" => "ingress-nginx-ingress-controller",
+ "items" => [
+ {
+ "key" => "modsecurity.conf",
+ "path" => "modsecurity.conf"
+ }
+ ]
+ }
+ },
+ {
+ "name" => "modsecurity-log-volume",
+ "emptyDir" => {}
+ }
+ ]
}
}
end
+ def modsecurity_config_content
+ File.read(modsecurity_config_file_path)
+ end
+
+ def modsecurity_config_file_path
+ Rails.root.join('vendor', 'ingress', 'modsecurity.conf')
+ end
+
def content_values
YAML.load_file(chart_values_file).deep_merge!(specification)
end
@@ -91,6 +154,10 @@ module Clusters
def application_jupyter_nil_or_installable?
cluster.application_jupyter.nil? || cluster.application_jupyter&.installable?
end
+
+ def application_elastic_stack_nil_or_installable?
+ cluster.application_elastic_stack.nil? || cluster.application_elastic_stack&.installable?
+ end
end
end
end
diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb
index 954046c143..37ba8a7c97 100644
--- a/app/models/clusters/applications/runner.rb
+++ b/app/models/clusters/applications/runner.rb
@@ -3,7 +3,7 @@
module Clusters
module Applications
class Runner < ApplicationRecord
- VERSION = '0.9.0'
+ VERSION = '0.10.1'
self.table_name = 'clusters_applications_runners'
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index d6f5d7c3f9..f522f3f2fd 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -6,20 +6,21 @@ module Clusters
include Gitlab::Utils::StrongMemoize
include FromUnion
include ReactiveCaching
+ include AfterCommitQueue
self.table_name = 'clusters'
- PROJECT_ONLY_APPLICATIONS = {
- }.freeze
APPLICATIONS = {
Applications::Helm.application_name => Applications::Helm,
Applications::Ingress.application_name => Applications::Ingress,
Applications::CertManager.application_name => Applications::CertManager,
+ Applications::Crossplane.application_name => Applications::Crossplane,
Applications::Prometheus.application_name => Applications::Prometheus,
Applications::Runner.application_name => Applications::Runner,
Applications::Jupyter.application_name => Applications::Jupyter,
- Applications::Knative.application_name => Applications::Knative
- }.merge(PROJECT_ONLY_APPLICATIONS).freeze
+ Applications::Knative.application_name => Applications::Knative,
+ Applications::ElasticStack.application_name => Applications::ElasticStack
+ }.freeze
DEFAULT_ENVIRONMENT = '*'
KUBE_INGRESS_BASE_DOMAIN = 'KUBE_INGRESS_BASE_DOMAIN'
@@ -47,14 +48,17 @@ module Clusters
has_one_cluster_application :helm
has_one_cluster_application :ingress
has_one_cluster_application :cert_manager
+ has_one_cluster_application :crossplane
has_one_cluster_application :prometheus
has_one_cluster_application :runner
has_one_cluster_application :jupyter
has_one_cluster_application :knative
+ has_one_cluster_application :elastic_stack
has_many :kubernetes_namespaces
accepts_nested_attributes_for :provider_gcp, update_only: true
+ accepts_nested_attributes_for :provider_aws, update_only: true
accepts_nested_attributes_for :platform_kubernetes, update_only: true
validates :name, cluster_name: true
@@ -72,6 +76,7 @@ module Clusters
delegate :status, to: :provider, allow_nil: true
delegate :status_reason, to: :provider, allow_nil: true
delegate :on_creation?, to: :provider, allow_nil: true
+ delegate :knative_pre_installed?, to: :provider, allow_nil: true
delegate :active?, to: :platform_kubernetes, prefix: true, allow_nil: true
delegate :rbac?, to: :platform_kubernetes, prefix: true, allow_nil: true
@@ -115,6 +120,8 @@ module Clusters
scope :default_environment, -> { where(environment_scope: DEFAULT_ENVIRONMENT) }
+ scope :for_project_namespace, -> (namespace_id) { joins(:projects).where(projects: { namespace_id: namespace_id }) }
+
def self.ancestor_clusters_for_clusterable(clusterable, hierarchy_order: :asc)
return [] if clusterable.is_a?(Instance)
@@ -124,7 +131,55 @@ module Clusters
hierarchy_groups.flat_map(&:clusters) + Instance.new.clusters
end
+ state_machine :cleanup_status, initial: :cleanup_not_started do
+ state :cleanup_not_started, value: 1
+ state :cleanup_uninstalling_applications, value: 2
+ state :cleanup_removing_project_namespaces, value: 3
+ state :cleanup_removing_service_account, value: 4
+ state :cleanup_errored, value: 5
+
+ event :start_cleanup do |cluster|
+ transition [:cleanup_not_started, :cleanup_errored] => :cleanup_uninstalling_applications
+ end
+
+ event :continue_cleanup do
+ transition(
+ cleanup_uninstalling_applications: :cleanup_removing_project_namespaces,
+ cleanup_removing_project_namespaces: :cleanup_removing_service_account)
+ end
+
+ event :make_cleanup_errored do
+ transition any => :cleanup_errored
+ end
+
+ before_transition any => [:cleanup_errored] do |cluster, transition|
+ status_reason = transition.args.first
+ cluster.cleanup_status_reason = status_reason if status_reason
+ end
+
+ after_transition [:cleanup_not_started, :cleanup_errored] => :cleanup_uninstalling_applications do |cluster|
+ cluster.run_after_commit do
+ Clusters::Cleanup::AppWorker.perform_async(cluster.id)
+ end
+ end
+
+ after_transition cleanup_uninstalling_applications: :cleanup_removing_project_namespaces do |cluster|
+ cluster.run_after_commit do
+ Clusters::Cleanup::ProjectNamespaceWorker.perform_async(cluster.id)
+ end
+ end
+
+ after_transition cleanup_removing_project_namespaces: :cleanup_removing_service_account do |cluster|
+ cluster.run_after_commit do
+ Clusters::Cleanup::ServiceAccountWorker.perform_async(cluster.id)
+ end
+ end
+ end
+
def status_name
+ return cleanup_status_name if cleanup_errored?
+ return :cleanup_ongoing unless cleanup_not_started?
+
provider&.status_name || connection_status.presence || :created
end
@@ -207,10 +262,6 @@ module Clusters
end
end
- def knative_pre_installed?
- provider&.knative_pre_installed?
- end
-
private
def unique_management_project_environment_scope
diff --git a/app/models/clusters/clusters_hierarchy.rb b/app/models/clusters/clusters_hierarchy.rb
index a906eb2888..c9c18d8c96 100644
--- a/app/models/clusters/clusters_hierarchy.rb
+++ b/app/models/clusters/clusters_hierarchy.rb
@@ -20,7 +20,7 @@ module Clusters
.with
.recursive(cte.to_arel)
.from(cte_alias)
- .order(DEPTH_COLUMN => :asc)
+ .order(depth_order_clause)
end
private
@@ -40,7 +40,7 @@ module Clusters
end
if clusterable.is_a?(::Project) && include_management_project
- cte << management_clusters_query
+ cte << same_namespace_management_clusters_query
end
cte << base_query
@@ -49,13 +49,42 @@ module Clusters
cte
end
+ # Returns project-level clusters where the project is the management project
+ # for the cluster. The management project has to be in the same namespace /
+ # group as the cluster's project.
+ #
+ # Support for management project in sub-groups is planned in
+ # https://gitlab.com/gitlab-org/gitlab/issues/34650
+ #
+ # NB: group_parent_id is un-used but we still need to match the same number of
+ # columns as other queries in the CTE.
+ def same_namespace_management_clusters_query
+ clusterable.management_clusters
+ .project_type
+ .select([clusters_star, 'NULL AS group_parent_id', "0 AS #{DEPTH_COLUMN}"])
+ .for_project_namespace(clusterable.namespace_id)
+ end
+
# Management clusters should be first in the hierarchy so we use 0 for the
# depth column.
#
- # group_parent_id is un-used but we still need to match the same number of
- # columns as other queries in the CTE.
- def management_clusters_query
- clusterable.management_clusters.select([clusters_star, 'NULL AS group_parent_id', "0 AS #{DEPTH_COLUMN}"])
+ # Only applicable if the clusterable is a project (most especially when
+ # requesting project.deployment_platform).
+ def depth_order_clause
+ return { DEPTH_COLUMN => :asc } unless clusterable.is_a?(::Project) && include_management_project
+
+ order = <<~SQL
+ (CASE clusters.management_project_id
+ WHEN :project_id THEN 0
+ ELSE #{DEPTH_COLUMN}
+ END) ASC
+ SQL
+
+ values = {
+ project_id: clusterable.id
+ }
+
+ model.sanitize_sql_array([Arel.sql(order), values])
end
def group_clusters_base_query
diff --git a/app/models/clusters/concerns/application_core.rb b/app/models/clusters/concerns/application_core.rb
index 979cf0645f..21b9853480 100644
--- a/app/models/clusters/concerns/application_core.rb
+++ b/app/models/clusters/concerns/application_core.rb
@@ -60,6 +60,24 @@ module Clusters
# Override if your application needs any action after
# being uninstalled by Helm
end
+
+ def logger
+ @logger ||= Gitlab::Kubernetes::Logger.build
+ end
+
+ def log_exception(error, event)
+ logger.error({
+ exception: error.class.name,
+ status_code: error.error_code,
+ cluster_id: cluster&.id,
+ application_id: id,
+ class_name: self.class.name,
+ event: event,
+ message: error.message
+ })
+
+ Gitlab::Sentry.track_acceptable_exception(error, extra: { cluster_id: cluster&.id, application_id: id })
+ end
end
end
end
diff --git a/app/models/clusters/instance.rb b/app/models/clusters/instance.rb
index f21dbdf7f2..8c9d9ab9ab 100644
--- a/app/models/clusters/instance.rb
+++ b/app/models/clusters/instance.rb
@@ -9,5 +9,9 @@ module Clusters
def feature_available?(feature)
::Feature.enabled?(feature, default_enabled: true)
end
+
+ def flipper_id
+ self.class.to_s
+ end
end
end
diff --git a/app/models/clusters/providers/aws.rb b/app/models/clusters/providers/aws.rb
index ae4156896b..78eb75ddcc 100644
--- a/app/models/clusters/providers/aws.rb
+++ b/app/models/clusters/providers/aws.rb
@@ -3,12 +3,12 @@
module Clusters
module Providers
class Aws < ApplicationRecord
+ include Gitlab::Utils::StrongMemoize
include Clusters::Concerns::ProviderStatus
self.table_name = 'cluster_providers_aws'
belongs_to :cluster, inverse_of: :provider_aws, class_name: 'Clusters::Cluster'
- belongs_to :created_by_user, class_name: 'User'
default_value_for :region, 'us-east-1'
default_value_for :num_nodes, 3
@@ -42,6 +42,30 @@ module Clusters
session_token: nil
)
end
+
+ def api_client
+ strong_memoize(:api_client) do
+ ::Aws::CloudFormation::Client.new(credentials: credentials, region: region)
+ end
+ end
+
+ def credentials
+ strong_memoize(:credentials) do
+ ::Aws::Credentials.new(access_key_id, secret_access_key, session_token)
+ end
+ end
+
+ def has_rbac_enabled?
+ true
+ end
+
+ def knative_pre_installed?
+ false
+ end
+
+ def created_by_user
+ cluster.user
+ end
end
end
end
diff --git a/app/models/clusters/providers/gcp.rb b/app/models/clusters/providers/gcp.rb
index f871674676..2ca7d0249d 100644
--- a/app/models/clusters/providers/gcp.rb
+++ b/app/models/clusters/providers/gcp.rb
@@ -54,6 +54,10 @@ module Clusters
assign_attributes(operation_id: operation_id)
end
+ def has_rbac_enabled?
+ !legacy_abac
+ end
+
def knative_pre_installed?
cloud_run?
end
diff --git a/app/models/commit_status_enums.rb b/app/models/commit_status_enums.rb
index a540e29199..2ca6d15e64 100644
--- a/app/models/commit_status_enums.rb
+++ b/app/models/commit_status_enums.rb
@@ -15,7 +15,9 @@ module CommitStatusEnums
stale_schedule: 7,
job_execution_timeout: 8,
archived_failure: 9,
- unmet_prerequisites: 10
+ unmet_prerequisites: 10,
+ scheduler_failure: 11,
+ data_integrity_failure: 12
}
end
end
diff --git a/app/models/concerns/analytics/cycle_analytics/stage.rb b/app/models/concerns/analytics/cycle_analytics/stage.rb
index 54e9a13d1e..0e07806dd6 100644
--- a/app/models/concerns/analytics/cycle_analytics/stage.rb
+++ b/app/models/concerns/analytics/cycle_analytics/stage.rb
@@ -4,19 +4,28 @@ module Analytics
module CycleAnalytics
module Stage
extend ActiveSupport::Concern
+ include RelativePositioning
+ include Gitlab::Utils::StrongMemoize
included do
+ belongs_to :start_event_label, class_name: 'GroupLabel', optional: true
+ belongs_to :end_event_label, class_name: 'GroupLabel', optional: true
+
validates :name, presence: true
validates :name, exclusion: { in: Gitlab::Analytics::CycleAnalytics::DefaultStages.names }, if: :custom?
validates :start_event_identifier, presence: true
validates :end_event_identifier, presence: true
+ validates :start_event_label, presence: true, if: :start_event_label_based?
+ validates :end_event_label, presence: true, if: :end_event_label_based?
validate :validate_stage_event_pairs
+ validate :validate_labels
enum start_event_identifier: Gitlab::Analytics::CycleAnalytics::StageEvents.to_enum, _prefix: :start_event_identifier
enum end_event_identifier: Gitlab::Analytics::CycleAnalytics::StageEvents.to_enum, _prefix: :end_event_identifier
alias_attribute :custom_stage?, :custom
scope :default_stages, -> { where(custom: false) }
+ scope :ordered, -> { order(:relative_position, :id) }
end
def parent=(_)
@@ -28,19 +37,41 @@ module Analytics
end
def start_event
- Gitlab::Analytics::CycleAnalytics::StageEvents[start_event_identifier].new(params_for_start_event)
+ strong_memoize(:start_event) do
+ Gitlab::Analytics::CycleAnalytics::StageEvents[start_event_identifier].new(params_for_start_event)
+ end
end
def end_event
- Gitlab::Analytics::CycleAnalytics::StageEvents[end_event_identifier].new(params_for_end_event)
+ strong_memoize(:end_event) do
+ Gitlab::Analytics::CycleAnalytics::StageEvents[end_event_identifier].new(params_for_end_event)
+ end
+ end
+
+ def start_event_label_based?
+ start_event_identifier && start_event.label_based?
+ end
+
+ def end_event_label_based?
+ end_event_identifier && end_event.label_based?
+ end
+
+ def start_event_identifier=(identifier)
+ clear_memoization(:start_event)
+ super
+ end
+
+ def end_event_identifier=(identifier)
+ clear_memoization(:end_event)
+ super
end
def params_for_start_event
- {}
+ start_event_label.present? ? { label: start_event_label } : {}
end
def params_for_end_event
- {}
+ end_event_label.present? ? { label: end_event_label } : {}
end
def default_stage?
@@ -58,19 +89,44 @@ module Analytics
end_event_identifier.to_s.eql?(stage_params[:end_event_identifier].to_s)
end
+ def find_with_same_parent!(id)
+ parent.cycle_analytics_stages.find(id)
+ end
+
private
def validate_stage_event_pairs
return if start_event_identifier.nil? || end_event_identifier.nil?
unless pairing_rules.fetch(start_event.class, []).include?(end_event.class)
- errors.add(:end_event, :not_allowed_for_the_given_start_event)
+ errors.add(:end_event, s_('CycleAnalytics|not allowed for the given start event'))
end
end
def pairing_rules
Gitlab::Analytics::CycleAnalytics::StageEvents.pairing_rules
end
+
+ def validate_labels
+ validate_label_within_group(:start_event_label, start_event_label_id) if start_event_label_id_changed?
+ validate_label_within_group(:end_event_label, end_event_label_id) if end_event_label_id_changed?
+ end
+
+ def validate_label_within_group(association_name, label_id)
+ return unless label_id
+ return unless group
+
+ unless label_available_for_group?(label_id)
+ errors.add(association_name, s_('CycleAnalyticsStage|is not available for the selected group'))
+ end
+ end
+
+ def label_available_for_group?(label_id)
+ LabelsFinder.new(nil, { group_id: group.id, include_ancestor_groups: true, only_group_labels: true })
+ .execute(skip_authorization: true)
+ .by_ids(label_id)
+ .exists?
+ end
end
end
end
diff --git a/app/models/concerns/ci/metadatable.rb b/app/models/concerns/ci/metadatable.rb
index a0ca8a34c6..17d431bacf 100644
--- a/app/models/concerns/ci/metadatable.rb
+++ b/app/models/concerns/ci/metadatable.rb
@@ -16,6 +16,7 @@ module Ci
delegate :timeout, to: :metadata, prefix: true, allow_nil: true
delegate :interruptible, to: :metadata, prefix: false, allow_nil: true
+ delegate :has_exposed_artifacts?, to: :metadata, prefix: false, allow_nil: true
before_create :ensure_metadata
end
@@ -45,6 +46,9 @@ module Ci
def options=(value)
write_metadata_attribute(:options, :config_options, value)
+
+ # Store presence of exposed artifacts in build metadata to make it easier to query
+ ensure_metadata.has_exposed_artifacts = value&.dig(:artifacts, :expose_as).present?
end
def yaml_variables=(value)
diff --git a/app/models/concerns/ci/processable.rb b/app/models/concerns/ci/processable.rb
index 268fa8ec69..ed0087f34d 100644
--- a/app/models/concerns/ci/processable.rb
+++ b/app/models/concerns/ci/processable.rb
@@ -8,6 +8,14 @@ module Ci
#
#
module Processable
+ extend ActiveSupport::Concern
+
+ included do
+ has_many :needs, class_name: 'Ci::BuildNeed', foreign_key: :build_id, inverse_of: :build
+
+ accepts_nested_attributes_for :needs
+ end
+
def schedulable?
raise NotImplementedError
end
diff --git a/app/models/concerns/deployment_platform.rb b/app/models/concerns/deployment_platform.rb
index fe8e960982..3b893a56bd 100644
--- a/app/models/concerns/deployment_platform.rb
+++ b/app/models/concerns/deployment_platform.rb
@@ -12,7 +12,7 @@ module DeploymentPlatform
private
def cluster_management_project_enabled?
- Feature.enabled?(:cluster_management_project, default_enabled: true)
+ Feature.enabled?(:cluster_management_project, self, default_enabled: true)
end
def find_deployment_platform(environment)
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 796e6438a2..01cd1e0224 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -118,8 +118,8 @@ module Issuable
# rubocop:enable GitlabSecurity/SqlInjection
scope :left_joins_milestones, -> { joins("LEFT OUTER JOIN milestones ON #{table_name}.milestone_id = milestones.id") }
- scope :order_milestone_due_desc, -> { left_joins_milestones.reorder('milestones.due_date IS NULL, milestones.id IS NULL, milestones.due_date DESC') }
- scope :order_milestone_due_asc, -> { left_joins_milestones.reorder('milestones.due_date IS NULL, milestones.id IS NULL, milestones.due_date ASC') }
+ scope :order_milestone_due_desc, -> { left_joins_milestones.reorder(Arel.sql('milestones.due_date IS NULL, milestones.id IS NULL, milestones.due_date DESC')) }
+ scope :order_milestone_due_asc, -> { left_joins_milestones.reorder(Arel.sql('milestones.due_date IS NULL, milestones.id IS NULL, milestones.due_date ASC')) }
scope :without_label, -> { joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{name}' AND label_links.target_id = #{table_name}.id").where(label_links: { id: nil }) }
scope :any_label, -> { joins(:label_links).group(:id) }
diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb
index 42b370990a..b1a7d7ec81 100644
--- a/app/models/concerns/milestoneish.rb
+++ b/app/models/concerns/milestoneish.rb
@@ -101,6 +101,10 @@ module Milestoneish
false
end
+ def global_milestone?
+ false
+ end
+
def total_issue_time_spent
@total_issue_time_spent ||= issues.joins(:timelogs).sum(:time_spent)
end
diff --git a/app/models/concerns/noteable.rb b/app/models/concerns/noteable.rb
index 3065e0ba6c..19f2daa1b0 100644
--- a/app/models/concerns/noteable.rb
+++ b/app/models/concerns/noteable.rb
@@ -108,10 +108,6 @@ module Noteable
discussions_resolvable? && resolvable_discussions.none?(&:to_be_resolved?)
end
- def discussions_to_be_resolved?
- discussions_resolvable? && !discussions_resolved?
- end
-
def discussions_to_be_resolved
@discussions_to_be_resolved ||= resolvable_discussions.select(&:to_be_resolved?)
end
diff --git a/app/models/concerns/protected_ref.rb b/app/models/concerns/protected_ref.rb
index ebacc459cb..d9a7f0a96d 100644
--- a/app/models/concerns/protected_ref.rb
+++ b/app/models/concerns/protected_ref.rb
@@ -39,8 +39,8 @@ module ProtectedRef
end
end
- def developers_can?(action, ref)
- access_levels_for_ref(ref, action: action).any? do |access_level|
+ def developers_can?(action, ref, protected_refs: nil)
+ access_levels_for_ref(ref, action: action, protected_refs: protected_refs).any? do |access_level|
access_level.access_level == Gitlab::Access::DEVELOPER
end
end
diff --git a/app/models/concerns/storage/legacy_namespace.rb b/app/models/concerns/storage/legacy_namespace.rb
index 78544405c4..9c2b0372d5 100644
--- a/app/models/concerns/storage/legacy_namespace.rb
+++ b/app/models/concerns/storage/legacy_namespace.rb
@@ -55,20 +55,22 @@ module Storage
def move_repositories
# Move the namespace directory in all storages used by member projects
- repository_storages.each do |repository_storage|
+ repository_storages(legacy_only: true).each do |repository_storage|
# Ensure old directory exists before moving it
- gitlab_shell.add_namespace(repository_storage, full_path_before_last_save)
+ Gitlab::GitalyClient::NamespaceService.allow do
+ gitlab_shell.add_namespace(repository_storage, full_path_before_last_save)
- # Ensure new directory exists before moving it (if there's a parent)
- gitlab_shell.add_namespace(repository_storage, parent.full_path) if parent
+ # Ensure new directory exists before moving it (if there's a parent)
+ gitlab_shell.add_namespace(repository_storage, parent.full_path) if parent
- unless gitlab_shell.mv_namespace(repository_storage, full_path_before_last_save, full_path)
+ unless gitlab_shell.mv_namespace(repository_storage, full_path_before_last_save, full_path)
- Rails.logger.error "Exception moving path #{repository_storage} from #{full_path_before_last_save} to #{full_path}" # rubocop:disable Gitlab/RailsLogger
+ Rails.logger.error "Exception moving path #{repository_storage} from #{full_path_before_last_save} to #{full_path}" # rubocop:disable Gitlab/RailsLogger
- # if we cannot move namespace directory we should rollback
- # db changes in order to prevent out of sync between db and fs
- raise Gitlab::UpdatePathError.new('namespace directory cannot be moved')
+ # if we cannot move namespace directory we should rollback
+ # db changes in order to prevent out of sync between db and fs
+ raise Gitlab::UpdatePathError.new('namespace directory cannot be moved')
+ end
end
end
end
@@ -77,12 +79,14 @@ module Storage
@old_repository_storage_paths ||= repository_storages
end
- def repository_storages
+ def repository_storages(legacy_only: false)
# We need to get the storage paths for all the projects, even the ones that are
# pending delete. Unscoping also get rids of the default order, which causes
# problems with SELECT DISTINCT.
Project.unscoped do
- all_projects.select('distinct(repository_storage)').to_a.map(&:repository_storage)
+ namespace_projects = all_projects
+ namespace_projects = namespace_projects.without_storage_feature(:repository) if legacy_only
+ namespace_projects.pluck(Arel.sql('distinct(repository_storage)'))
end
end
@@ -93,13 +97,15 @@ module Storage
# We will remove it later async
new_path = "#{full_path}+#{id}+deleted"
- if gitlab_shell.mv_namespace(repository_storage, full_path, new_path)
- Gitlab::AppLogger.info %Q(Namespace directory "#{full_path}" moved to "#{new_path}")
+ Gitlab::GitalyClient::NamespaceService.allow do
+ if gitlab_shell.mv_namespace(repository_storage, full_path, new_path)
+ Gitlab::AppLogger.info %Q(Namespace directory "#{full_path}" moved to "#{new_path}")
- # Remove namespace directory async with delay so
- # GitLab has time to remove all projects first
- run_after_commit do
- GitlabShellWorker.perform_in(5.minutes, :rm_namespace, repository_storage, new_path)
+ # Remove namespace directory async with delay so
+ # GitLab has time to remove all projects first
+ run_after_commit do
+ GitlabShellWorker.perform_in(5.minutes, :rm_namespace, repository_storage, new_path)
+ end
end
end
end
diff --git a/app/models/concerns/subscribable.rb b/app/models/concerns/subscribable.rb
index 92a5c1112a..33e9e0e38f 100644
--- a/app/models/concerns/subscribable.rb
+++ b/app/models/concerns/subscribable.rb
@@ -59,6 +59,14 @@ module Subscribable
.update(subscribed: false)
end
+ def set_subscription(user, desired_state, project = nil)
+ if desired_state
+ subscribe(user, project)
+ else
+ unsubscribe(user, project)
+ end
+ end
+
private
def unsubscribe_from_other_levels(user, project)
diff --git a/app/models/concerns/worker_attributes.rb b/app/models/concerns/worker_attributes.rb
index af40e9e3b1..506215ca9e 100644
--- a/app/models/concerns/worker_attributes.rb
+++ b/app/models/concerns/worker_attributes.rb
@@ -3,6 +3,10 @@
module WorkerAttributes
extend ActiveSupport::Concern
+ # Resource boundaries that workers can declare through the
+ # `worker_resource_boundary` attribute
+ VALID_RESOURCE_BOUNDARIES = [:memory, :cpu, :unknown].freeze
+
class_methods do
def feature_category(value)
raise "Invalid category. Use `feature_category_not_owned!` to mark a worker as not owned" if value == :not_owned
@@ -24,6 +28,48 @@ module WorkerAttributes
get_worker_attribute(:feature_category) == :not_owned
end
+ # This should be set for jobs that need to be run immediately, or, if
+ # they are delayed, risk creating inconsistencies in the application
+ # that could being perceived by the user as incorrect behavior
+ # (ie, a bug)
+ # See doc/development/sidekiq_style_guide.md#Latency-Sensitive-Jobs
+ # for details
+ def latency_sensitive_worker!
+ worker_attributes[:latency_sensitive] = true
+ end
+
+ # Returns a truthy value if the worker is latency sensitive.
+ # See doc/development/sidekiq_style_guide.md#Latency-Sensitive-Jobs
+ # for details
+ def latency_sensitive_worker?
+ worker_attributes[:latency_sensitive]
+ end
+
+ # Set this attribute on a job when it will call to services outside of the
+ # application, such as 3rd party applications, other k8s clusters etc See
+ # doc/development/sidekiq_style_guide.md#Jobs-with-External-Dependencies for
+ # details
+ def worker_has_external_dependencies!
+ worker_attributes[:external_dependencies] = true
+ end
+
+ # Returns a truthy value if the worker has external dependencies.
+ # See doc/development/sidekiq_style_guide.md#Jobs-with-External-Dependencies
+ # for details
+ def worker_has_external_dependencies?
+ worker_attributes[:external_dependencies]
+ end
+
+ def worker_resource_boundary(boundary)
+ raise "Invalid boundary" unless VALID_RESOURCE_BOUNDARIES.include? boundary
+
+ worker_attributes[:resource_boundary] = boundary
+ end
+
+ def get_worker_resource_boundary
+ worker_attributes[:resource_boundary] || :unknown
+ end
+
protected
# Returns a worker attribute declared on this class or its parent class.
diff --git a/app/models/container_repository.rb b/app/models/container_repository.rb
index 27bb76835c..152aa7b321 100644
--- a/app/models/container_repository.rb
+++ b/app/models/container_repository.rb
@@ -11,7 +11,10 @@ class ContainerRepository < ApplicationRecord
delegate :client, to: :registry
scope :ordered, -> { order(:name) }
- scope :with_api_entity_associations, -> { preload(:project) }
+ scope :with_api_entity_associations, -> { preload(project: [:route, { namespace: :route }]) }
+ scope :for_group_and_its_subgroups, ->(group) do
+ where(project_id: Project.for_group_and_its_subgroups(group).with_container_registry.select(:id))
+ end
# rubocop: disable CodeReuse/ServiceClass
def registry
diff --git a/app/models/dashboard_group_milestone.rb b/app/models/dashboard_group_milestone.rb
index ec52f1ed37..cf6094682f 100644
--- a/app/models/dashboard_group_milestone.rb
+++ b/app/models/dashboard_group_milestone.rb
@@ -18,4 +18,8 @@ class DashboardGroupMilestone < GlobalMilestone
milestones = milestones.search_title(params[:search_title]) if params[:search_title].present?
Milestone.filter_by_state(milestones, params[:state]).map { |m| new(m) }
end
+
+ def dashboard_milestone?
+ true
+ end
end
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index 7ccd5e9836..4a38912db9 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -10,6 +10,10 @@ class Deployment < ApplicationRecord
belongs_to :cluster, class_name: 'Clusters::Cluster', optional: true
belongs_to :user
belongs_to :deployable, polymorphic: true, optional: true # rubocop:disable Cop/PolymorphicAssociations
+ has_many :deployment_merge_requests
+
+ has_many :merge_requests,
+ through: :deployment_merge_requests
has_internal_id :iid, scope: :project, init: ->(s) do
Deployment.where(project: s.project).maximum(:iid) if s&.project
@@ -75,6 +79,11 @@ class Deployment < ApplicationRecord
find(ids)
end
+ def self.distinct_on_environment
+ order('environment_id, deployments.id DESC')
+ .select('DISTINCT ON (environment_id) deployments.*')
+ end
+
def self.find_successful_deployment!(iid)
success.find_by!(iid: iid)
end
@@ -144,6 +153,18 @@ class Deployment < ApplicationRecord
project.deployments.joins(:environment)
.where(environments: { name: self.environment.name }, ref: self.ref)
.where.not(id: self.id)
+ .order(id: :desc)
+ .take
+ end
+
+ def previous_environment_deployment
+ project
+ .deployments
+ .success
+ .joins(:environment)
+ .where(environments: { name: environment.name })
+ .where.not(id: self.id)
+ .order(id: :desc)
.take
end
@@ -176,6 +197,18 @@ class Deployment < ApplicationRecord
deployable&.user || user
end
+ def link_merge_requests(relation)
+ select = relation.select(['merge_requests.id', id]).to_sql
+
+ # We don't use `Gitlab::Database.bulk_insert` here so that we don't need to
+ # first pluck lots of IDs into memory.
+ DeploymentMergeRequest.connection.execute(<<~SQL)
+ INSERT INTO #{DeploymentMergeRequest.table_name}
+ (merge_request_id, deployment_id)
+ #{select}
+ SQL
+ end
+
private
def ref_path
diff --git a/app/models/deployment_merge_request.rb b/app/models/deployment_merge_request.rb
new file mode 100644
index 0000000000..ff4d9f6620
--- /dev/null
+++ b/app/models/deployment_merge_request.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+class DeploymentMergeRequest < ApplicationRecord
+ belongs_to :deployment, optional: false
+ belongs_to :merge_request, optional: false
+end
diff --git a/app/models/description_version.rb b/app/models/description_version.rb
index abab7f9421..05362a2f90 100644
--- a/app/models/description_version.rb
+++ b/app/models/description_version.rb
@@ -10,6 +10,10 @@ class DescriptionVersion < ApplicationRecord
%i(issue merge_request).freeze
end
+ def issuable
+ issue || merge_request
+ end
+
private
def exactly_one_issuable
diff --git a/app/models/environment.rb b/app/models/environment.rb
index af0c219d9a..327b1e594d 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -4,12 +4,20 @@ class Environment < ApplicationRecord
include Gitlab::Utils::StrongMemoize
include ReactiveCaching
+ self.reactive_cache_refresh_interval = 1.minute
+ self.reactive_cache_lifetime = 55.seconds
+
belongs_to :project, required: true
has_many :deployments, -> { visible }, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :successful_deployments, -> { success }, class_name: 'Deployment'
has_one :last_deployment, -> { success.order('deployments.id DESC') }, class_name: 'Deployment'
+ has_one :last_deployable, through: :last_deployment, source: 'deployable', source_type: 'CommitStatus'
+ has_one :last_pipeline, through: :last_deployable, source: 'pipeline'
+ has_one :last_visible_deployment, -> { visible.distinct_on_environment }, inverse_of: :environment, class_name: 'Deployment'
+ has_one :last_visible_deployable, through: :last_visible_deployment, source: 'deployable', source_type: 'CommitStatus'
+ has_one :last_visible_pipeline, through: :last_visible_deployable, source: 'pipeline'
before_validation :nullify_external_url
before_validation :generate_slug, if: ->(env) { env.slug.blank? }
@@ -60,6 +68,10 @@ class Environment < ApplicationRecord
scope :for_project, -> (project) { where(project_id: project) }
scope :with_deployment, -> (sha) { where('EXISTS (?)', Deployment.select(1).where('deployments.environment_id = environments.id').where(sha: sha)) }
+ scope :unfoldered, -> { where(environment_type: nil) }
+ scope :with_rank, -> do
+ select('environments.*, rank() OVER (PARTITION BY project_id ORDER BY id DESC)')
+ end
state_machine :state, initial: :available do
event :start do
@@ -188,6 +200,10 @@ class Environment < ApplicationRecord
prometheus_adapter.query(:environment, self) if has_metrics?
end
+ def prometheus_status
+ deployment_platform&.cluster&.application_prometheus&.status_name
+ end
+
def additional_metrics(*args)
return unless has_metrics?
diff --git a/app/models/error_tracking/project_error_tracking_setting.rb b/app/models/error_tracking/project_error_tracking_setting.rb
index 0b4fef5eac..2aa058a243 100644
--- a/app/models/error_tracking/project_error_tracking_setting.rb
+++ b/app/models/error_tracking/project_error_tracking_setting.rb
@@ -7,6 +7,7 @@ module ErrorTracking
SENTRY_API_ERROR_TYPE_MISSING_KEYS = 'missing_keys_in_sentry_response'
SENTRY_API_ERROR_TYPE_NON_20X_RESPONSE = 'non_20x_response_from_sentry'
+ SENTRY_API_ERROR_INVALID_SIZE = 'invalid_size_of_sentry_response'
API_URL_PATH_REGEXP = %r{
\A
@@ -87,15 +88,37 @@ module ErrorTracking
{ projects: sentry_client.list_projects }
end
+ def issue_details(opts = {})
+ with_reactive_cache('issue_details', opts.stringify_keys) do |result|
+ result
+ end
+ end
+
+ def issue_latest_event(opts = {})
+ with_reactive_cache('issue_latest_event', opts.stringify_keys) do |result|
+ result
+ end
+ end
+
def calculate_reactive_cache(request, opts)
case request
when 'list_issues'
{ issues: sentry_client.list_issues(**opts.symbolize_keys) }
+ when 'issue_details'
+ {
+ issue: sentry_client.issue_details(**opts.symbolize_keys)
+ }
+ when 'issue_latest_event'
+ {
+ latest_event: sentry_client.issue_latest_event(**opts.symbolize_keys)
+ }
end
rescue Sentry::Client::Error => e
{ error: e.message, error_type: SENTRY_API_ERROR_TYPE_NON_20X_RESPONSE }
rescue Sentry::Client::MissingKeysError => e
{ error: e.message, error_type: SENTRY_API_ERROR_TYPE_MISSING_KEYS }
+ rescue Sentry::Client::ResponseInvalidSizeError => e
+ { error: e.message, error_type: SENTRY_API_ERROR_INVALID_SIZE }
end
# http://HOST/api/0/projects/ORG/PROJECT
diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb
index 7d766e1f25..65fd5c1b35 100644
--- a/app/models/global_milestone.rb
+++ b/app/models/global_milestone.rb
@@ -11,7 +11,7 @@ class GlobalMilestone
delegate :title, :state, :due_date, :start_date, :participants, :project,
:group, :expires_at, :closed?, :iid, :group_milestone?, :safe_title,
- :milestoneish_id, :resource_parent, to: :milestone
+ :milestoneish_id, :resource_parent, :releases, to: :milestone
def to_hash
{
@@ -100,4 +100,8 @@ class GlobalMilestone
def labels
@labels ||= GlobalLabel.build_collection(milestone.labels).sort_by!(&:title)
end
+
+ def global_milestone?
+ true
+ end
end
diff --git a/app/models/grafana_integration.rb b/app/models/grafana_integration.rb
index 51cc398394..ed4c279965 100644
--- a/app/models/grafana_integration.rb
+++ b/app/models/grafana_integration.rb
@@ -14,7 +14,13 @@ class GrafanaIntegration < ApplicationRecord
validates :token, :project, presence: true
+ validates :enabled, inclusion: { in: [true, false] }
+
+ scope :enabled, -> { where(enabled: true) }
+
def client
+ return unless enabled?
+
@client ||= ::Grafana::Client.new(api_url: grafana_url.chomp('/'), token: token)
end
end
diff --git a/app/models/group.rb b/app/models/group.rb
index 042201ffa1..8289d4f099 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -30,6 +30,10 @@ class Group < Namespace
has_many :members_and_requesters, as: :source, class_name: 'GroupMember'
has_many :milestones
+ has_many :shared_group_links, foreign_key: :shared_with_group_id, class_name: 'GroupGroupLink'
+ has_many :shared_with_group_links, foreign_key: :shared_group_id, class_name: 'GroupGroupLink'
+ has_many :shared_groups, through: :shared_group_links, source: :shared_group
+ has_many :shared_with_groups, through: :shared_with_group_links, source: :shared_with_group
has_many :project_group_links, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :shared_projects, through: :project_group_links, source: :project
@@ -51,6 +55,8 @@ class Group < Namespace
has_many :todos
+ has_one :import_export_upload
+
accepts_nested_attributes_for :variables, allow_destroy: true
validate :visibility_level_allowed_by_projects
@@ -120,7 +126,7 @@ class Group < Namespace
def visible_to_user_arel(user)
groups_table = self.arel_table
- authorized_groups = user.authorized_groups.as('authorized')
+ authorized_groups = user.authorized_groups.arel.as('authorized')
groups_table.project(1)
.from(authorized_groups)
@@ -259,8 +265,8 @@ class Group < Namespace
members_with_parents.maintainers.exists?(user_id: user)
end
- def has_container_repositories?
- container_repositories.exists?
+ def has_container_repository_including_subgroups?
+ ::ContainerRepository.for_group_and_its_subgroups(self).exists?
end
# @deprecated
@@ -376,11 +382,12 @@ class Group < Namespace
return GroupMember::OWNER if user.admin?
- members_with_parents
- .where(user_id: user)
- .reorder(access_level: :desc)
- .first&.
- access_level || GroupMember::NO_ACCESS
+ max_member_access = members_with_parents.where(user_id: user)
+ .reorder(access_level: :desc)
+ .first
+ &.access_level
+
+ max_member_access || max_member_access_for_user_from_shared_groups(user) || GroupMember::NO_ACCESS
end
def mattermost_team_params
@@ -444,6 +451,14 @@ class Group < Namespace
false
end
+ def export_file_exists?
+ export_file&.file
+ end
+
+ def export_file
+ import_export_upload&.export_file
+ end
+
private
def update_two_factor_requirement
@@ -474,6 +489,26 @@ class Group < Namespace
errors.add(:visibility_level, "#{visibility} is not allowed since there are sub-groups with higher visibility.")
end
+ def max_member_access_for_user_from_shared_groups(user)
+ return unless Feature.enabled?(:share_group_with_group)
+
+ group_group_link_table = GroupGroupLink.arel_table
+ group_member_table = GroupMember.arel_table
+
+ group_group_links_query = GroupGroupLink.where(shared_group_id: self_and_ancestors_ids)
+ cte = Gitlab::SQL::CTE.new(:group_group_links_cte, group_group_links_query)
+
+ link = GroupGroupLink
+ .with(cte.to_arel)
+ .from([group_member_table, cte.alias_to(group_group_link_table)])
+ .where(group_member_table[:user_id].eq(user.id))
+ .where(group_member_table[:source_id].eq(group_group_link_table[:shared_with_group_id]))
+ .reorder(Arel::Nodes::Descending.new(group_group_link_table[:group_access]))
+ .first
+
+ link&.group_access
+ end
+
def self.groups_including_descendants_by(group_ids)
Gitlab::ObjectHierarchy
.new(Group.where(id: group_ids))
diff --git a/app/models/group_group_link.rb b/app/models/group_group_link.rb
new file mode 100644
index 0000000000..4b279b7af5
--- /dev/null
+++ b/app/models/group_group_link.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+class GroupGroupLink < ApplicationRecord
+ include Expirable
+
+ belongs_to :shared_group, class_name: 'Group', foreign_key: :shared_group_id
+ belongs_to :shared_with_group, class_name: 'Group', foreign_key: :shared_with_group_id
+
+ validates :shared_group, presence: true
+ validates :shared_group_id, uniqueness: { scope: [:shared_with_group_id],
+ message: _('The group has already been shared with this group') }
+ validates :shared_with_group, presence: true
+ validates :group_access, inclusion: { in: Gitlab::Access.values },
+ presence: true
+
+ def self.access_options
+ Gitlab::Access.options
+ end
+
+ def self.default_access
+ Gitlab::Access::DEVELOPER
+ end
+end
diff --git a/app/models/import_export_upload.rb b/app/models/import_export_upload.rb
index 60f5491849..7d73fd281f 100644
--- a/app/models/import_export_upload.rb
+++ b/app/models/import_export_upload.rb
@@ -5,6 +5,7 @@ class ImportExportUpload < ApplicationRecord
include ObjectStorage::BackgroundMove
belongs_to :project
+ belongs_to :group
# These hold the project Import/Export archives (.tar.gz files)
mount_uploader :import_file, ImportExportUploader
diff --git a/app/models/issue.rb b/app/models/issue.rb
index b9b481ac29..948cadc34e 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -40,6 +40,7 @@ class Issue < ApplicationRecord
has_many :issue_assignees
has_many :assignees, class_name: "User", through: :issue_assignees
+ has_many :zoom_meetings
validates :project, presence: true
@@ -54,9 +55,9 @@ class Issue < ApplicationRecord
scope :due_between, ->(from_date, to_date) { where('issues.due_date >= ?', from_date).where('issues.due_date <= ?', to_date) }
scope :due_tomorrow, -> { where(due_date: Date.tomorrow) }
- scope :order_due_date_asc, -> { reorder('issues.due_date IS NULL, issues.due_date ASC') }
- scope :order_due_date_desc, -> { reorder('issues.due_date IS NULL, issues.due_date DESC') }
- scope :order_closest_future_date, -> { reorder('CASE WHEN issues.due_date >= CURRENT_DATE THEN 0 ELSE 1 END ASC, ABS(CURRENT_DATE - issues.due_date) ASC') }
+ scope :order_due_date_asc, -> { reorder(::Gitlab::Database.nulls_last_order('due_date', 'ASC')) }
+ scope :order_due_date_desc, -> { reorder(::Gitlab::Database.nulls_last_order('due_date', 'DESC')) }
+ scope :order_closest_future_date, -> { reorder(Arel.sql('CASE WHEN issues.due_date >= CURRENT_DATE THEN 0 ELSE 1 END ASC, ABS(CURRENT_DATE - issues.due_date) ASC')) }
scope :order_relative_position_asc, -> { reorder(::Gitlab::Database.nulls_last_order('relative_position', 'ASC')) }
scope :preload_associations, -> { preload(:labels, project: :namespace) }
@@ -65,6 +66,8 @@ class Issue < ApplicationRecord
scope :public_only, -> { where(confidential: false) }
scope :confidential_only, -> { where(confidential: true) }
+ scope :counts_by_state, -> { reorder(nil).group(:state).count }
+
after_commit :expire_etag_cache
after_save :ensure_metrics, unless: :imported?
@@ -137,8 +140,8 @@ class Issue < ApplicationRecord
def self.sort_by_attribute(method, excluded_labels: [])
case method.to_s
when 'closest_future_date', 'closest_future_date_asc' then order_closest_future_date
- when 'due_date', 'due_date_asc' then order_due_date_asc
- when 'due_date_desc' then order_due_date_desc
+ when 'due_date', 'due_date_asc' then order_due_date_asc.with_order_id_desc
+ when 'due_date_desc' then order_due_date_desc.with_order_id_desc
when 'relative_position', 'relative_position_asc' then order_relative_position_asc.with_order_id_desc
else
super
@@ -206,7 +209,16 @@ class Issue < ApplicationRecord
if self.confidential?
"#{iid}-confidential-issue"
else
- "#{iid}-#{title.parameterize}"
+ branch_name = "#{iid}-#{title.parameterize}"
+
+ if branch_name.length > 100
+ truncated_string = branch_name[0, 100]
+ # Delete everything dangling after the last hyphen so as not to risk
+ # existence of unintended words in the branch name due to mid-word split.
+ branch_name = truncated_string[0, truncated_string.rindex("-")]
+ end
+
+ branch_name
end
end
diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb
index 535c3cf2ba..48c971194c 100644
--- a/app/models/lfs_object.rb
+++ b/app/models/lfs_object.rb
@@ -18,6 +18,11 @@ class LfsObject < ApplicationRecord
after_save :update_file_store, if: :saved_change_to_file?
+ def self.not_linked_to_project(project)
+ where('NOT EXISTS (?)',
+ project.lfs_objects_projects.select(1).where('lfs_objects_projects.lfs_object_id = lfs_objects.id'))
+ end
+
def update_file_store
# The file.object_store is set during `uploader.store!`
# which happens after object is inserted/updated
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 32741046f3..7e1898e714 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -16,6 +16,9 @@ class MergeRequest < ApplicationRecord
include ReactiveCaching
include FromUnion
include DeprecatedAssignee
+ include ShaAttribute
+
+ sha_attribute :squash_commit_sha
self.reactive_cache_key = ->(model) { [model.project.id, model.iid] }
self.reactive_cache_refresh_interval = 10.minutes
@@ -65,6 +68,7 @@ class MergeRequest < ApplicationRecord
has_many :cached_closes_issues, through: :merge_requests_closing_issues, source: :issue
has_many :pipelines_for_merge_request, foreign_key: 'merge_request_id', class_name: 'Ci::Pipeline'
has_many :suggestions, through: :notes
+ has_many :unresolved_notes, -> { unresolved }, as: :noteable, class_name: 'Note'
has_many :merge_request_assignees
has_many :assignees, class_name: "User", through: :merge_request_assignees
@@ -202,11 +206,14 @@ class MergeRequest < ApplicationRecord
scope :by_commit_sha, ->(sha) do
where('EXISTS (?)', MergeRequestDiff.select(1).where('merge_requests.latest_merge_request_diff_id = merge_request_diffs.id').by_commit_sha(sha)).reorder(nil)
end
+ scope :by_merge_commit_sha, -> (sha) do
+ where(merge_commit_sha: sha)
+ end
scope :join_project, -> { joins(:target_project) }
scope :references_project, -> { references(:target_project) }
scope :with_api_entity_associations, -> {
- preload(:assignees, :author, :notes, :labels, :milestone, :timelogs,
- latest_merge_request_diff: [:merge_request_diff_commits],
+ preload(:assignees, :author, :unresolved_notes, :labels, :milestone,
+ :timelogs, :latest_merge_request_diff,
metrics: [:latest_closed_by, :merged_by],
target_project: [:route, { namespace: :route }],
source_project: [:route, { namespace: :route }])
@@ -217,17 +224,27 @@ class MergeRequest < ApplicationRecord
scope :by_target_branch, ->(branch_name) { where(target_branch: branch_name) }
scope :preload_source_project, -> { preload(:source_project) }
- scope :with_open_merge_when_pipeline_succeeds, -> do
- with_state(:opened).where(merge_when_pipeline_succeeds: true)
+ scope :with_auto_merge_enabled, -> do
+ with_state(:opened).where(auto_merge_enabled: true)
end
after_save :keep_around_commit
alias_attribute :project, :target_project
alias_attribute :project_id, :target_project_id
+
+ # Currently, `merge_when_pipeline_succeeds` column is used as a flag
+ # to check if _any_ auto merge strategy is activated on the merge request.
+ # Today, we have multiple strategies and MWPS is one of them.
+ # we'd eventually rename the column for avoiding confusions, but in the mean time
+ # please use `auto_merge_enabled` alias instead of `merge_when_pipeline_succeeds`.
alias_attribute :auto_merge_enabled, :merge_when_pipeline_succeeds
alias_method :issuing_parent, :target_project
+ RebaseLockTimeout = Class.new(StandardError)
+
+ REBASE_LOCK_MESSAGE = _("Failed to enqueue the rebase operation, possibly due to a long-lived transaction. Try again later.")
+
def self.reference_prefix
'!'
end
@@ -357,11 +374,12 @@ class MergeRequest < ApplicationRecord
"#{project.to_reference(from, full: full)}#{reference}"
end
- def commits
- return merge_request_diff.commits if persisted?
+ def commits(limit: nil)
+ return merge_request_diff.commits(limit: limit) if persisted?
commits_arr = if compare_commits
- compare_commits.reverse
+ reversed_commits = compare_commits.reverse
+ limit ? reversed_commits.take(limit) : reversed_commits
else
[]
end
@@ -369,6 +387,10 @@ class MergeRequest < ApplicationRecord
CommitCollection.new(source_project, commits_arr, source_branch)
end
+ def recent_commits
+ commits(limit: MergeRequestDiff::COMMITS_SAFE_SIZE)
+ end
+
def commits_count
if persisted?
merge_request_diff.commits_count
@@ -379,14 +401,17 @@ class MergeRequest < ApplicationRecord
end
end
- def commit_shas
- if persisted?
- merge_request_diff.commit_shas
- elsif compare_commits
- compare_commits.to_a.reverse.map(&:sha)
- else
- Array(diff_head_sha)
- end
+ def commit_shas(limit: nil)
+ return merge_request_diff.commit_shas(limit: limit) if persisted?
+
+ shas =
+ if compare_commits
+ compare_commits.to_a.reverse.map(&:sha)
+ else
+ Array(diff_head_sha)
+ end
+
+ limit ? shas.take(limit) : shas
end
# Returns true if there are commits that match at least one commit SHA.
@@ -417,9 +442,7 @@ class MergeRequest < ApplicationRecord
# Set off a rebase asynchronously, atomically updating the `rebase_jid` of
# the MR so that the status of the operation can be tracked.
def rebase_async(user_id)
- transaction do
- lock!
-
+ with_rebase_lock do
raise ActiveRecord::StaleObjectError if !open? || rebase_in_progress?
# Although there is a race between setting rebase_jid here and clearing it
@@ -782,6 +805,8 @@ class MergeRequest < ApplicationRecord
end
def check_mergeability
+ return if Feature.enabled?(:merge_requests_conditional_mergeability_check, default_enabled: true) && !recheck_merge_status?
+
MergeRequests::MergeabilityCheckService.new(self).execute(retry_lease: false)
end
# rubocop: enable CodeReuse/ServiceClass
@@ -896,7 +921,7 @@ class MergeRequest < ApplicationRecord
def commit_notes
# Fetch comments only from last 100 commits
- commit_ids = commit_shas.take(100)
+ commit_ids = commit_shas(limit: 100)
Note
.user
@@ -907,7 +932,7 @@ class MergeRequest < ApplicationRecord
def mergeable_discussions_state?
return true unless project.only_allow_merge_if_all_discussions_are_resolved?
- !discussions_to_be_resolved?
+ unresolved_notes.none?(&:to_be_resolved?)
end
def for_fork?
@@ -1087,7 +1112,7 @@ class MergeRequest < ApplicationRecord
return true unless project.only_allow_merge_if_pipeline_succeeds?
return false unless actual_head_pipeline
- actual_head_pipeline.success? || actual_head_pipeline.skipped?
+ actual_head_pipeline.success?
end
def environments_for(current_user)
@@ -1263,6 +1288,27 @@ class MergeRequest < ApplicationRecord
compare_reports(Ci::CompareTestReportsService)
end
+ def has_exposed_artifacts?
+ return false unless Feature.enabled?(:ci_expose_arbitrary_artifacts_in_mr, default_enabled: true)
+
+ actual_head_pipeline&.has_exposed_artifacts?
+ end
+
+ # TODO: this method and compare_test_reports use the same
+ # result type, which is handled by the controller's #reports_response.
+ # we should minimize mistakes by isolating the common parts.
+ # issue: https://gitlab.com/gitlab-org/gitlab/issues/34224
+ def find_exposed_artifacts
+ unless has_exposed_artifacts?
+ return { status: :error, status_reason: 'This merge request does not have exposed artifacts' }
+ end
+
+ compare_reports(Ci::GenerateExposedArtifactsReportService)
+ end
+
+ # TODO: consider renaming this as with exposed artifacts we generate reports,
+ # not always compare
+ # issue: https://gitlab.com/gitlab-org/gitlab/issues/34224
def compare_reports(service_class, current_user = nil)
with_reactive_cache(service_class.name, current_user&.id) do |data|
unless service_class.new(project, current_user)
@@ -1277,6 +1323,8 @@ class MergeRequest < ApplicationRecord
def calculate_reactive_cache(identifier, current_user_id = nil, *args)
service_class = identifier.constantize
+ # TODO: the type check should change to something that includes exposed artifacts service
+ # issue: https://gitlab.com/gitlab-org/gitlab/issues/34224
raise NameError, service_class unless service_class < Ci::CompareReportsBaseService
current_user = User.find_by(id: current_user_id)
@@ -1453,6 +1501,30 @@ class MergeRequest < ApplicationRecord
private
+ def with_rebase_lock
+ if Feature.enabled?(:merge_request_rebase_nowait_lock, default_enabled: true)
+ with_retried_nowait_lock { yield }
+ else
+ with_lock(true) { yield }
+ end
+ end
+
+ # If the merge request is idle in transaction or has a SELECT FOR
+ # UPDATE, we don't want to block indefinitely or this could cause a
+ # queue of SELECT FOR UPDATE calls. Instead, try to get the lock for
+ # 5 s before raising an error to the user.
+ def with_retried_nowait_lock
+ # Try at most 0.25 + (1.5 * .25) + (1.5^2 * .25) ... (1.5^5 * .25) = 5.2 s to get the lock
+ Retriable.retriable(on: ActiveRecord::LockWaitTimeout, tries: 6, base_interval: 0.25) do
+ with_lock('FOR UPDATE NOWAIT') do
+ yield
+ end
+ end
+ rescue ActiveRecord::LockWaitTimeout => e
+ Gitlab::Sentry.track_acceptable_exception(e)
+ raise RebaseLockTimeout, REBASE_LOCK_MESSAGE
+ end
+
def source_project_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables|
break variables unless source_project
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 735ad046f2..70ce4df567 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -213,12 +213,14 @@ class MergeRequestDiff < ApplicationRecord
end
end
- def commits
- @commits ||= load_commits
+ def commits(limit: nil)
+ strong_memoize(:"commits_#{limit || 'all'}") do
+ load_commits(limit: limit)
+ end
end
def last_commit_sha
- commit_shas.first
+ commit_shas(limit: 1).first
end
def first_commit
@@ -247,8 +249,8 @@ class MergeRequestDiff < ApplicationRecord
project.commit_by(oid: head_commit_sha)
end
- def commit_shas
- merge_request_diff_commits.map(&:sha)
+ def commit_shas(limit: nil)
+ merge_request_diff_commits.limit(limit).pluck(:sha)
end
def commits_by_shas(shas)
@@ -529,8 +531,9 @@ class MergeRequestDiff < ApplicationRecord
end
end
- def load_commits
- commits = merge_request_diff_commits.map { |commit| Commit.from_hash(commit.to_hash, project) }
+ def load_commits(limit: nil)
+ commits = merge_request_diff_commits.limit(limit)
+ .map { |commit| Commit.from_hash(commit.to_hash, project) }
CommitCollection
.new(merge_request.source_project, commits, merge_request.source_branch)
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index a9f4cdec90..d0be54eed0 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -60,6 +60,7 @@ class Milestone < ApplicationRecord
validates :group, presence: true, unless: :project
validates :project, presence: true, unless: :group
+ validates :title, presence: true
validate :uniqueness_of_title, if: :title_changed?
validate :milestone_type_check
@@ -330,6 +331,6 @@ class Milestone < ApplicationRecord
end
def issues_finder_params
- { project_id: project_id }
+ { project_id: project_id, group_id: group_id }.compact
end
end
diff --git a/app/models/notification_reason.rb b/app/models/notification_reason.rb
index 6856d39741..a796723941 100644
--- a/app/models/notification_reason.rb
+++ b/app/models/notification_reason.rb
@@ -6,12 +6,14 @@ class NotificationReason
OWN_ACTIVITY = 'own_activity'
ASSIGNED = 'assigned'
MENTIONED = 'mentioned'
+ SUBSCRIBED = 'subscribed'
# Priority list for selecting which reason to return in the notification
REASON_PRIORITY = [
OWN_ACTIVITY,
ASSIGNED,
- MENTIONED
+ MENTIONED,
+ SUBSCRIBED
].freeze
# returns the priority of a reason as an integer
diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb
index 7903a2182d..3869d86b66 100644
--- a/app/models/pages_domain.rb
+++ b/app/models/pages_domain.rb
@@ -24,6 +24,8 @@ class PagesDomain < ApplicationRecord
validate :validate_matching_key, if: ->(domain) { domain.certificate.present? || domain.key.present? }
validate :validate_intermediates, if: ->(domain) { domain.certificate.present? && domain.certificate_changed? }
+ default_value_for(:auto_ssl_enabled, allow_nil: false) { ::Gitlab::LetsEncrypt.enabled? }
+
attr_encrypted :key,
mode: :per_attribute_iv_and_salt,
insecure_mode: true,
diff --git a/app/models/project.rb b/app/models/project.rb
index b0d305e80d..7ae4e2a4cd 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -76,6 +76,10 @@ class Project < ApplicationRecord
delegate :no_import?, to: :import_state, allow_nil: true
+ # TODO: remove once GitLab 12.5 is released
+ # https://gitlab.com/gitlab-org/gitlab/issues/34638
+ self.ignored_columns += %i[merge_requests_require_code_owner_approval]
+
default_value_for :archived, false
default_value_for :resolve_outdated_diff_discussions, false
default_value_for :container_registry_enabled, gitlab_config_features.container_registry
@@ -87,6 +91,8 @@ class Project < ApplicationRecord
default_value_for :wiki_enabled, gitlab_config_features.wiki
default_value_for :snippets_enabled, gitlab_config_features.snippets
default_value_for :only_allow_merge_if_all_discussions_are_resolved, false
+ default_value_for :remove_source_branch_after_merge, true
+ default_value_for(:ci_config_path) { Gitlab::CurrentSettings.default_ci_config_path }
add_authentication_token_field :runners_token, encrypted: -> { Feature.enabled?(:projects_tokens_optional_encryption, default_enabled: true) ? :optional : :required }
@@ -281,6 +287,7 @@ class Project < ApplicationRecord
has_many :variables, class_name: 'Ci::Variable'
has_many :triggers, class_name: 'Ci::Trigger'
has_many :environments
+ has_many :environments_for_dashboard, -> { from(with_rank.unfoldered.available, :environments).where('rank <= 3') }, class_name: 'Environment'
has_many :deployments
has_many :pipeline_schedules, class_name: 'Ci::PipelineSchedule'
has_many :project_deploy_tokens
@@ -390,6 +397,7 @@ class Project < ApplicationRecord
scope :with_project_feature, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id') }
scope :with_statistics, -> { includes(:statistics) }
scope :with_shared_runners, -> { where(shared_runners_enabled: true) }
+ scope :with_container_registry, -> { where(container_registry_enabled: true) }
scope :inside_path, ->(path) do
# We need routes alias rs for JOIN so it does not conflict with
# includes(:route) which we use in ProjectsFinder.
@@ -456,13 +464,6 @@ class Project < ApplicationRecord
# Used by Projects::CleanupService to hold a map of rewritten object IDs
mount_uploader :bfg_object_map, AttachmentUploader
- # Returns a project, if it is not about to be removed.
- #
- # id - The ID of the project to retrieve.
- def self.find_without_deleted(id)
- without_deleted.find_by_id(id)
- end
-
def self.eager_load_namespace_and_owner
includes(namespace: :owner)
end
@@ -660,6 +661,11 @@ class Project < ApplicationRecord
end
end
+ def preload_protected_branches
+ preloader = ActiveRecord::Associations::Preloader.new
+ preloader.preload(self, protected_branches: [:push_access_levels, :merge_access_levels])
+ end
+
# returns all ancestor-groups upto but excluding the given namespace
# when no namespace is given, all ancestors upto the top are returned
def ancestors_upto(top = nil, hierarchy_order: nil)
@@ -1910,7 +1916,7 @@ class Project < ApplicationRecord
end
def default_environment
- production_first = "(CASE WHEN name = 'production' THEN 0 ELSE 1 END), id ASC"
+ production_first = Arel.sql("(CASE WHEN name = 'production' THEN 0 ELSE 1 END), id ASC")
environments
.with_state(:available)
@@ -1965,27 +1971,6 @@ class Project < ApplicationRecord
(auto_devops || build_auto_devops)&.predefined_variables
end
- def append_or_update_attribute(name, value)
- if Project.reflect_on_association(name).try(:macro) == :has_many
- # if this is 1-to-N relation, update the parent object
- value.each do |item|
- item.update!(
- Project.reflect_on_association(name).foreign_key => id)
- end
-
- # force to drop relation cache
- public_send(name).reset # rubocop:disable GitlabSecurity/PublicSend
-
- # succeeded
- true
- else
- # if this is another relation or attribute, update just object
- update_attribute(name, value)
- end
- rescue ActiveRecord::RecordInvalid => e
- raise e, "Failed to set #{name}: #{e.message}"
- end
-
# Tries to set repository as read_only, checking for existing Git transfers in progress beforehand
#
# @return [Boolean] true when set to read_only or false when an existing git transfer is in progress
diff --git a/app/models/project_ci_cd_setting.rb b/app/models/project_ci_cd_setting.rb
index a495d34c07..d089a004d3 100644
--- a/app/models/project_ci_cd_setting.rb
+++ b/app/models/project_ci_cd_setting.rb
@@ -1,6 +1,9 @@
# frozen_string_literal: true
class ProjectCiCdSetting < ApplicationRecord
+ # TODO: remove once GitLab 12.7 is released
+ # https://gitlab.com/gitlab-org/gitlab/issues/36651
+ self.ignored_columns += %i[merge_trains_enabled]
belongs_to :project, inverse_of: :ci_cd_settings
# The version of the schema that first introduced this model/table.
diff --git a/app/models/project_services/chat_message/pipeline_message.rb b/app/models/project_services/chat_message/pipeline_message.rb
index a3793d9937..46fe894cfc 100644
--- a/app/models/project_services/chat_message/pipeline_message.rb
+++ b/app/models/project_services/chat_message/pipeline_message.rb
@@ -75,11 +75,11 @@ module ChatMessage
def activity
{
- title: s_("ChatMessage|Pipeline %{pipeline_link} of %{ref_type} %{branch_link} by %{user_combined_name} %{humanized_status}") %
+ title: s_("ChatMessage|Pipeline %{pipeline_link} of %{ref_type} %{ref_link} by %{user_combined_name} %{humanized_status}") %
{
pipeline_link: pipeline_link,
ref_type: ref_type,
- branch_link: branch_link,
+ ref_link: ref_link,
user_combined_name: user_combined_name,
humanized_status: humanized_status
},
@@ -123,7 +123,7 @@ module ChatMessage
fields = [
{
title: ref_type == "tag" ? s_("ChatMessage|Tag") : s_("ChatMessage|Branch"),
- value: Slack::Notifier::LinkFormatter.format(ref_name_link),
+ value: Slack::Notifier::LinkFormatter.format(ref_link),
short: true
},
{
@@ -141,12 +141,12 @@ module ChatMessage
end
def message
- s_("ChatMessage|%{project_link}: Pipeline %{pipeline_link} of %{ref_type} %{branch_link} by %{user_combined_name} %{humanized_status} in %{duration}") %
+ s_("ChatMessage|%{project_link}: Pipeline %{pipeline_link} of %{ref_type} %{ref_link} by %{user_combined_name} %{humanized_status} in %{duration}") %
{
project_link: project_link,
pipeline_link: pipeline_link,
ref_type: ref_type,
- branch_link: branch_link,
+ ref_link: ref_link,
user_combined_name: user_combined_name,
humanized_status: humanized_status,
duration: pretty_duration(duration)
@@ -193,12 +193,16 @@ module ChatMessage
end
end
- def branch_url
- "#{project_url}/commits/#{ref}"
+ def ref_url
+ if ref_type == 'tag'
+ "#{project_url}/-/tags/#{ref}"
+ else
+ "#{project_url}/commits/#{ref}"
+ end
end
- def branch_link
- "[#{ref}](#{branch_url})"
+ def ref_link
+ "[#{ref}](#{ref_url})"
end
def project_url
@@ -266,14 +270,6 @@ module ChatMessage
"[#{commit.title}](#{commit_url})"
end
- def commits_page_url
- "#{project_url}/commits/#{ref}"
- end
-
- def ref_name_link
- "[#{ref}](#{commits_page_url})"
- end
-
def author_url
return unless user && committer
diff --git a/app/models/project_services/chat_message/push_message.rb b/app/models/project_services/chat_message/push_message.rb
index 8163fca33a..07622f570c 100644
--- a/app/models/project_services/chat_message/push_message.rb
+++ b/app/models/project_services/chat_message/push_message.rb
@@ -82,16 +82,20 @@ module ChatMessage
Gitlab::Git.blank_ref?(after)
end
- def branch_url
- "#{project_url}/commits/#{ref}"
+ def ref_url
+ if ref_type == 'tag'
+ "#{project_url}/-/tags/#{ref}"
+ else
+ "#{project_url}/commits/#{ref}"
+ end
end
def compare_url
"#{project_url}/compare/#{before}...#{after}"
end
- def branch_link
- "[#{ref}](#{branch_url})"
+ def ref_link
+ "[#{ref}](#{ref_url})"
end
def project_link
@@ -104,11 +108,11 @@ module ChatMessage
def compose_action_details
if new_branch?
- ['pushed new', branch_link, "to #{project_link}"]
+ ['pushed new', ref_link, "to #{project_link}"]
elsif removed_branch?
['removed', ref, "from #{project_link}"]
else
- ['pushed to', branch_link, "of #{project_link} (#{compare_link})"]
+ ['pushed to', ref_link, "of #{project_link} (#{compare_link})"]
end
end
diff --git a/app/models/project_services/data_fields.rb b/app/models/project_services/data_fields.rb
index cffb493d56..cf406a784c 100644
--- a/app/models/project_services/data_fields.rb
+++ b/app/models/project_services/data_fields.rb
@@ -50,7 +50,7 @@ module DataFields
end
def data_fields_present?
- data_fields.persisted?
+ data_fields.present?
rescue NotImplementedError
false
end
diff --git a/app/models/project_services/prometheus_service.rb b/app/models/project_services/prometheus_service.rb
index 6eff2ea2e3..a0273fe0e5 100644
--- a/app/models/project_services/prometheus_service.rb
+++ b/app/models/project_services/prometheus_service.rb
@@ -7,8 +7,15 @@ class PrometheusService < MonitoringService
prop_accessor :api_url
boolean_accessor :manual_configuration
+ # We need to allow the self-monitoring project to connect to the internal
+ # Prometheus instance.
+ # Since the internal Prometheus instance is usually a localhost URL, we need
+ # to allow localhost URLs when the following conditions are true:
+ # 1. project is the self-monitoring project.
+ # 2. api_url is the internal Prometheus URL.
with_options presence: true, if: :manual_configuration? do
- validates :api_url, public_url: true
+ validates :api_url, public_url: true, unless: proc { |object| object.allow_local_api_url? }
+ validates :api_url, url: true, if: proc { |object| object.allow_local_api_url? }
end
before_save :synchronize_service_state
@@ -82,12 +89,28 @@ class PrometheusService < MonitoringService
project.clusters.enabled.any? { |cluster| cluster.application_prometheus_available? }
end
+ def allow_local_api_url?
+ self_monitoring_project? && internal_prometheus_url?
+ end
+
private
+ def self_monitoring_project?
+ project && project.id == current_settings.instance_administration_project_id
+ end
+
+ def internal_prometheus_url?
+ api_url.present? && api_url == ::Gitlab::Prometheus::Internal.uri
+ end
+
def should_return_client?
api_url.present? && manual_configuration? && active? && valid?
end
+ def current_settings
+ Gitlab::CurrentSettings.current_application_settings
+ end
+
def synchronize_service_state
self.active = prometheus_available? || manual_configuration?
diff --git a/app/models/project_snippet.rb b/app/models/project_snippet.rb
index b3585c4cf4..e732c1bd86 100644
--- a/app/models/project_snippet.rb
+++ b/app/models/project_snippet.rb
@@ -2,13 +2,6 @@
class ProjectSnippet < Snippet
belongs_to :project
- belongs_to :author, class_name: "User"
validates :project, presence: true
-
- # Scopes
- scope :fresh, -> { order("created_at DESC") }
-
- participant :author
- participant :notes_with_associations
end
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index bb222ac762..f02ccd9e55 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -160,12 +160,6 @@ class ProjectWiki
update_project_activity
end
- def page_formatted_data(page)
- page_title, page_dir = page_title_and_dir(page.title)
-
- wiki.page_formatted_data(title: page_title, dir: page_dir, version: page.version)
- end
-
def page_title_and_dir(title)
return unless title
diff --git a/app/models/prometheus_metric.rb b/app/models/prometheus_metric.rb
index 08f4df7ea0..d0dc31476f 100644
--- a/app/models/prometheus_metric.rb
+++ b/app/models/prometheus_metric.rb
@@ -14,7 +14,13 @@ class PrometheusMetric < ApplicationRecord
validates :project, presence: true, unless: :common?
validates :project, absence: true, if: :common?
+ scope :for_project, -> (project) { where(project: project) }
+ scope :for_group, -> (group) { where(group: group) }
+ scope :for_title, -> (title) { where(title: title) }
+ scope :for_y_label, -> (y_label) { where(y_label: y_label) }
+ scope :for_identifier, -> (identifier) { where(identifier: identifier) }
scope :common, -> { where(common: true) }
+ scope :ordered, -> { reorder(created_at: :asc) }
def priority
group_details(group).fetch(:priority)
diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb
index 1857a59e01..735e2bdea8 100644
--- a/app/models/protected_branch.rb
+++ b/app/models/protected_branch.rb
@@ -38,7 +38,7 @@ class ProtectedBranch < ApplicationRecord
end
def self.protected_refs(project)
- project.protected_branches.select(:name)
+ project.protected_branches
end
def self.branch_requires_code_owner_approval?(project, branch_name)
diff --git a/app/models/release.rb b/app/models/release.rb
index 5a7bfe2d49..401e8359f4 100644
--- a/app/models/release.rb
+++ b/app/models/release.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
class Release < ApplicationRecord
+ include Presentable
include CacheMarkdownField
include Gitlab::Utils::StrongMemoize
@@ -26,13 +27,21 @@ class Release < ApplicationRecord
validates_associated :milestone_releases, message: -> (_, obj) { obj[:value].map(&:errors).map(&:full_messages).join(",") }
scope :sorted, -> { order(released_at: :desc) }
+ scope :preloaded, -> { includes(project: :namespace) }
scope :with_project_and_namespace, -> { includes(project: :namespace) }
+ scope :recent, -> { sorted.limit(MAX_NUMBER_TO_DISPLAY) }
delegate :repository, to: :project
after_commit :create_evidence!, on: :create
after_commit :notify_new_release, on: :create
+ MAX_NUMBER_TO_DISPLAY = 3
+
+ def to_param
+ CGI.escape(tag)
+ end
+
def commit
strong_memoize(:commit) do
repository.commit(actual_sha)
@@ -60,6 +69,10 @@ class Release < ApplicationRecord
released_at.present? && released_at > Time.zone.now
end
+ def name
+ self.read_attribute(:name) || tag
+ end
+
private
def actual_sha
diff --git a/app/models/releases/source.rb b/app/models/releases/source.rb
index 4d3d54457a..2f00d25d76 100644
--- a/app/models/releases/source.rb
+++ b/app/models/releases/source.rb
@@ -6,11 +6,9 @@ module Releases
attr_accessor :project, :tag_name, :format
- FORMATS = %w(zip tar.gz tar.bz2 tar).freeze
-
class << self
def all(project, tag_name)
- Releases::Source::FORMATS.map do |format|
+ Gitlab::Workhorse::ARCHIVE_FORMATS.map do |format|
Releases::Source.new(project: project,
tag_name: tag_name,
format: format)
diff --git a/app/models/service.rb b/app/models/service.rb
index 305cf7b78a..6d5b974dd3 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -40,6 +40,7 @@ class Service < ApplicationRecord
scope :external_wikis, -> { where(type: 'ExternalWikiService').active }
scope :active, -> { where(active: true) }
scope :without_defaults, -> { where(default: false) }
+ scope :by_type, -> (type) { where(type: type) }
scope :push_hooks, -> { where(push_events: true, active: true) }
scope :tag_push_hooks, -> { where(tag_push_events: true, active: true) }
diff --git a/app/models/todo.rb b/app/models/todo.rb
index 1927b54510..f217c942e8 100644
--- a/app/models/todo.rb
+++ b/app/models/todo.rb
@@ -55,7 +55,8 @@ class Todo < ApplicationRecord
scope :done, -> { with_state(:done) }
scope :for_action, -> (action) { where(action: action) }
scope :for_author, -> (author) { where(author: author) }
- scope :for_project, -> (project) { where(project: project) }
+ scope :for_project, -> (projects) { where(project: projects) }
+ scope :for_undeleted_projects, -> { joins(:project).merge(Project.without_deleted) }
scope :for_group, -> (group) { where(group: group) }
scope :for_type, -> (type) { where(target_type: type) }
scope :for_target, -> (id) { where(target_id: id) }
@@ -160,6 +161,10 @@ class Todo < ApplicationRecord
action == ASSIGNED
end
+ def done?
+ state == 'done'
+ end
+
def action_name
ACTION_NAMES[action]
end
diff --git a/app/models/user.rb b/app/models/user.rb
index eec8ad6edb..d0e758b005 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -56,9 +56,6 @@ class User < ApplicationRecord
BLOCKED_MESSAGE = "Your account has been blocked. Please contact your GitLab " \
"administrator if you think this is an error."
- # Removed in GitLab 12.3. Keep until after 2019-09-22.
- self.ignored_columns += %i[support_bot]
-
MINIMUM_INACTIVE_DAYS = 180
# Override Devise::Models::Trackable#update_tracked_fields!
@@ -243,6 +240,8 @@ class User < ApplicationRecord
delegate :time_display_relative, :time_display_relative=, to: :user_preference
delegate :time_format_in_24h, :time_format_in_24h=, to: :user_preference
delegate :show_whitespace_in_diffs, :show_whitespace_in_diffs=, to: :user_preference
+ delegate :sourcegraph_enabled, :sourcegraph_enabled=, to: :user_preference
+ delegate :setup_for_company, :setup_for_company=, to: :user_preference
accepts_nested_attributes_for :user_preference, update_only: true
@@ -1423,14 +1422,13 @@ class User < ApplicationRecord
# flow means we don't call that automatically (and can't conveniently do so).
#
# See:
- #
+ #
#
# rubocop: disable CodeReuse/ServiceClass
def increment_failed_attempts!
return if ::Gitlab::Database.read_only?
- self.failed_attempts ||= 0
- self.failed_attempts += 1
+ increment_failed_attempts
if attempts_exceeded?
lock_access! unless access_locked?
@@ -1458,7 +1456,7 @@ class User < ApplicationRecord
# Does the user have access to all private groups & projects?
# Overridden in EE to also check auditor?
def full_private_access?
- admin?
+ can?(:read_all_resources)
end
def update_two_factor_requirement
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
index 68241d2bd9..f9c562364c 100644
--- a/app/models/wiki_page.rb
+++ b/app/models/wiki_page.rb
@@ -112,11 +112,6 @@ class WikiPage
wiki.page_title_and_dir(slug)&.last.to_s
end
- # The processed/formatted content of this page.
- def formatted_content
- @attributes[:formatted_content] ||= @wiki.page_formatted_data(@page)
- end
-
# The markup format for the page.
def format
@attributes[:format] || :markdown
diff --git a/app/models/zoom_meeting.rb b/app/models/zoom_meeting.rb
new file mode 100644
index 0000000000..a7ecd1e6a2
--- /dev/null
+++ b/app/models/zoom_meeting.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+class ZoomMeeting < ApplicationRecord
+ belongs_to :project, optional: false
+ belongs_to :issue, optional: false
+
+ validates :url, presence: true, length: { maximum: 255 }, zoom_url: true
+ validates :issue, same_project_association: true
+
+ enum issue_status: {
+ added: 1,
+ removed: 2
+ }
+
+ scope :added_to_issue, -> { where(issue_status: :added) }
+ scope :removed_from_issue, -> { where(issue_status: :removed) }
+ scope :canonical, -> (issue) { where(issue: issue).added_to_issue }
+
+ def self.canonical_meeting(issue)
+ canonical(issue)&.take
+ end
+
+ def self.canonical_meeting_url(issue)
+ canonical_meeting(issue)&.url
+ end
+end
diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb
index 18c23cbd13..8f5c6957a2 100644
--- a/app/policies/base_policy.rb
+++ b/app/policies/base_policy.rb
@@ -21,10 +21,6 @@ class BasePolicy < DeclarativePolicy::Base
with_options scope: :user, score: 0
condition(:deactivated) { @user&.deactivated? }
- desc "User has access to all private groups & projects"
- with_options scope: :user, score: 0
- condition(:full_private_access) { @user&.full_private_access? }
-
with_options scope: :user, score: 0
condition(:external_user) { @user.nil? || @user.external? }
@@ -40,10 +36,12 @@ class BasePolicy < DeclarativePolicy::Base
::Gitlab::ExternalAuthorization.perform_check?
end
- rule { external_authorization_enabled & ~full_private_access }.policy do
+ rule { external_authorization_enabled & ~can?(:read_all_resources) }.policy do
prevent :read_cross_project
end
+ rule { admin }.enable :read_all_resources
+
rule { default }.enable :read_cross_project
end
diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb
index 13e5b4ae41..1cd400e4df 100644
--- a/app/policies/group_policy.rb
+++ b/app/policies/group_policy.rb
@@ -44,6 +44,7 @@ class GroupPolicy < BasePolicy
rule { public_group }.policy do
enable :read_group
+ enable :read_package
end
rule { logged_in_viewable }.enable :read_group
@@ -70,7 +71,10 @@ class GroupPolicy < BasePolicy
rule { has_access }.enable :read_namespace
- rule { developer }.enable :admin_milestone
+ rule { developer }.policy do
+ enable :admin_milestone
+ enable :read_package
+ end
rule { reporter }.policy do
enable :read_container_image
diff --git a/app/policies/personal_snippet_policy.rb b/app/policies/personal_snippet_policy.rb
index 40dd49b4af..91a8f3a713 100644
--- a/app/policies/personal_snippet_policy.rb
+++ b/app/policies/personal_snippet_policy.rb
@@ -10,7 +10,7 @@ class PersonalSnippetPolicy < BasePolicy
enable :create_note
end
- rule { is_author }.policy do
+ rule { is_author | admin }.policy do
enable :read_personal_snippet
enable :update_personal_snippet
enable :destroy_personal_snippet
@@ -30,5 +30,5 @@ class PersonalSnippetPolicy < BasePolicy
rule { can?(:create_note) }.enable :award_emoji
- rule { full_private_access }.enable :read_personal_snippet
+ rule { can?(:read_all_resources) }.enable :read_personal_snippet
end
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index ea2be37d7e..ff70c6e6ae 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -117,6 +117,10 @@ class ProjectPolicy < BasePolicy
!@subject.builds_enabled?
end
+ condition(:user_confirmed?) do
+ @user && @user.confirmed?
+ end
+
features = %w[
merge_requests
issues
@@ -249,10 +253,7 @@ class ProjectPolicy < BasePolicy
enable :update_commit_status
enable :create_build
enable :update_build
- enable :create_pipeline
- enable :update_pipeline
enable :read_pipeline_schedule
- enable :create_pipeline_schedule
enable :create_merge_request_from
enable :create_wiki
enable :push_code
@@ -267,6 +268,12 @@ class ProjectPolicy < BasePolicy
enable :update_release
end
+ rule { can?(:developer_access) & user_confirmed? }.policy do
+ enable :create_pipeline
+ enable :update_pipeline
+ enable :create_pipeline_schedule
+ end
+
rule { can?(:maintainer_access) }.policy do
enable :admin_board
enable :push_to_delete_protected_branch
@@ -418,7 +425,7 @@ class ProjectPolicy < BasePolicy
# These rules are included to allow maintainers of projects to push to certain
# to run pipelines for the branches they have access to.
- rule { can?(:public_access) & has_merge_requests_allowing_pushes }.policy do
+ rule { can?(:public_access) & has_merge_requests_allowing_pushes & user_confirmed? }.policy do
enable :create_build
enable :create_pipeline
end
diff --git a/app/policies/project_snippet_policy.rb b/app/policies/project_snippet_policy.rb
index 2a3e4ca174..d9d09eb04c 100644
--- a/app/policies/project_snippet_policy.rb
+++ b/app/policies/project_snippet_policy.rb
@@ -28,7 +28,7 @@ class ProjectSnippetPolicy < BasePolicy
all?(private_snippet | (internal_snippet & external_user),
~project.guest,
~is_author,
- ~full_private_access)
+ ~can?(:read_all_resources))
end.prevent :read_project_snippet
rule { internal_snippet & ~is_author & ~admin }.policy do
diff --git a/app/policies/todo_policy.rb b/app/policies/todo_policy.rb
index f8644217f0..d01a046c34 100644
--- a/app/policies/todo_policy.rb
+++ b/app/policies/todo_policy.rb
@@ -7,4 +7,5 @@ class TodoPolicy < BasePolicy
end
rule { own_todo }.enable :read_todo
+ rule { own_todo }.enable :update_todo
end
diff --git a/app/presenters/clusterable_presenter.rb b/app/presenters/clusterable_presenter.rb
index 34dffbf40f..2306f55f1f 100644
--- a/app/presenters/clusterable_presenter.rb
+++ b/app/presenters/clusterable_presenter.rb
@@ -29,6 +29,18 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated
new_polymorphic_path([clusterable, :cluster], options)
end
+ def aws_api_proxy_path(resource)
+ polymorphic_path([clusterable, :clusters], action: :aws_proxy, resource: resource)
+ end
+
+ def authorize_aws_role_path
+ polymorphic_path([clusterable, :clusters], action: :authorize_aws_role)
+ end
+
+ def revoke_aws_role_path
+ polymorphic_path([clusterable, :clusters], action: :revoke_aws_role)
+ end
+
def create_user_clusters_path
polymorphic_path([clusterable, :clusters], action: :create_user)
end
@@ -37,6 +49,10 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated
polymorphic_path([clusterable, :clusters], action: :create_gcp)
end
+ def create_aws_clusters_path
+ polymorphic_path([clusterable, :clusters], action: :create_aws)
+ end
+
def cluster_status_cluster_path(cluster, params = {})
raise NotImplementedError
end
diff --git a/app/presenters/commit_status_presenter.rb b/app/presenters/commit_status_presenter.rb
index f1182ec26f..66ae840a61 100644
--- a/app/presenters/commit_status_presenter.rb
+++ b/app/presenters/commit_status_presenter.rb
@@ -11,7 +11,9 @@ class CommitStatusPresenter < Gitlab::View::Presenter::Delegated
stale_schedule: 'Delayed job could not be executed by some reason, please try again',
job_execution_timeout: 'The script exceeded the maximum execution time set for the job',
archived_failure: 'The job is archived and cannot be run',
- unmet_prerequisites: 'The job failed to complete prerequisite tasks'
+ unmet_prerequisites: 'The job failed to complete prerequisite tasks',
+ scheduler_failure: 'The scheduler failed to assign job to the runner, please try again or contact system administrator',
+ data_integrity_failure: 'There has been a structural integrity problem detected, please contact system administrator'
}.freeze
private_constant :CALLOUT_FAILURE_MESSAGES
@@ -33,6 +35,6 @@ class CommitStatusPresenter < Gitlab::View::Presenter::Delegated
end
def unrecoverable?
- script_failure? || missing_dependency_failure? || archived_failure?
+ script_failure? || missing_dependency_failure? || archived_failure? || scheduler_failure? || data_integrity_failure?
end
end
diff --git a/app/presenters/instance_clusterable_presenter.rb b/app/presenters/instance_clusterable_presenter.rb
index 908cd17678..c6572e8ce7 100644
--- a/app/presenters/instance_clusterable_presenter.rb
+++ b/app/presenters/instance_clusterable_presenter.rb
@@ -52,6 +52,26 @@ class InstanceClusterablePresenter < ClusterablePresenter
create_gcp_admin_clusters_path
end
+ override :create_aws_clusters_path
+ def create_aws_clusters_path
+ create_aws_admin_clusters_path
+ end
+
+ override :authorize_aws_role_path
+ def authorize_aws_role_path
+ authorize_aws_role_admin_clusters_path
+ end
+
+ override :revoke_aws_role_path
+ def revoke_aws_role_path
+ revoke_aws_role_admin_clusters_path
+ end
+
+ override :aws_api_proxy_path
+ def aws_api_proxy_path(resource)
+ aws_proxy_admin_clusters_path(resource: resource)
+ end
+
override :empty_state_help_text
def empty_state_help_text
s_('ClusterIntegration|Adding an integration will share the cluster across all projects.')
diff --git a/app/presenters/project_presenter.rb b/app/presenters/project_presenter.rb
index 6d370f6241..81018398d5 100644
--- a/app/presenters/project_presenter.rb
+++ b/app/presenters/project_presenter.rb
@@ -21,7 +21,6 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
def statistics_anchors(show_auto_devops_callout:)
[
- license_anchor_data,
commits_anchor_data,
branches_anchor_data,
tags_anchor_data,
@@ -32,6 +31,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
def statistics_buttons(show_auto_devops_callout:)
[
readme_anchor_data,
+ license_anchor_data,
changelog_anchor_data,
contribution_guide_anchor_data,
autodevops_anchor_data(show_auto_devops_callout: show_auto_devops_callout),
@@ -41,15 +41,14 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
end
def empty_repo_statistics_anchors
- [
- license_anchor_data
- ].compact.select { |item| item.is_link }
+ []
end
def empty_repo_statistics_buttons
[
new_file_anchor_data,
readme_anchor_data,
+ license_anchor_data,
changelog_anchor_data,
contribution_guide_anchor_data,
gitlab_ci_anchor_data
@@ -227,17 +226,18 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
icon = statistic_icon('scale')
if repository.license_blob.present?
- AnchorData.new(true,
- icon + content_tag(:strong, license_short_name, class: 'project-stat-value'),
- license_path)
+ AnchorData.new(false,
+ icon + content_tag(:span, license_short_name, class: 'project-stat-value'),
+ license_path,
+ 'default')
else
if current_user && can_current_user_push_to_default_branch?
- AnchorData.new(true,
- content_tag(:span, icon + _('Add license'), class: 'add-license-link d-flex'),
+ AnchorData.new(false,
+ content_tag(:span, statistic_icon + _('Add LICENSE'), class: 'add-license-link d-flex'),
add_license_path)
else
- AnchorData.new(true,
- icon + content_tag(:strong, _('No license. All rights reserved'), class: 'project-stat-value'),
+ AnchorData.new(false,
+ icon + content_tag(:span, _('No license. All rights reserved'), class: 'project-stat-value'),
nil)
end
end
diff --git a/app/presenters/release_presenter.rb b/app/presenters/release_presenter.rb
new file mode 100644
index 0000000000..42463d6dbd
--- /dev/null
+++ b/app/presenters/release_presenter.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+class ReleasePresenter < Gitlab::View::Presenter::Delegated
+ include ActionView::Helpers::UrlHelper
+
+ presents :release
+
+ delegate :project, :tag, to: :release
+
+ def commit_path
+ return unless release.commit && can_download_code?
+
+ project_commit_path(project, release.commit.id)
+ end
+
+ def tag_path
+ return unless can_download_code?
+
+ project_tag_path(project, release.tag)
+ end
+
+ def merge_requests_url
+ return unless release_mr_issue_urls_available?
+
+ project_merge_requests_url(project, params_for_issues_and_mrs)
+ end
+
+ def issues_url
+ return unless release_mr_issue_urls_available?
+
+ project_issues_url(project, params_for_issues_and_mrs)
+ end
+
+ def edit_url
+ return unless release_edit_page_available?
+
+ edit_project_release_url(project, release)
+ end
+
+ private
+
+ def can_download_code?
+ can?(current_user, :download_code, project)
+ end
+
+ def params_for_issues_and_mrs
+ { scope: 'all', state: 'opened', release_tag: release.tag }
+ end
+
+ def release_mr_issue_urls_available?
+ ::Feature.enabled?(:release_mr_issue_urls, project)
+ end
+
+ def release_edit_page_available?
+ ::Feature.enabled?(:release_edit_page, project, default_enabled: true)
+ end
+end
diff --git a/app/presenters/todo_presenter.rb b/app/presenters/todo_presenter.rb
index b57fc712c5..291be7848e 100644
--- a/app/presenters/todo_presenter.rb
+++ b/app/presenters/todo_presenter.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
class TodoPresenter < Gitlab::View::Presenter::Delegated
- include GlobalID::Identification
-
presents :todo
end
diff --git a/app/serializers/cluster_application_entity.rb b/app/serializers/cluster_application_entity.rb
index 2a916b13f5..218bdd21e3 100644
--- a/app/serializers/cluster_application_entity.rb
+++ b/app/serializers/cluster_application_entity.rb
@@ -8,7 +8,9 @@ class ClusterApplicationEntity < Grape::Entity
expose :external_ip, if: -> (e, _) { e.respond_to?(:external_ip) }
expose :external_hostname, if: -> (e, _) { e.respond_to?(:external_hostname) }
expose :hostname, if: -> (e, _) { e.respond_to?(:hostname) }
+ expose :kibana_hostname, if: -> (e, _) { e.respond_to?(:kibana_hostname) }
expose :email, if: -> (e, _) { e.respond_to?(:email) }
+ expose :stack, if: -> (e, _) { e.respond_to?(:stack) }
expose :update_available?, as: :update_available, if: -> (e, _) { e.respond_to?(:update_available?) }
expose :can_uninstall?, as: :can_uninstall
end
diff --git a/app/serializers/container_repositories_serializer.rb b/app/serializers/container_repositories_serializer.rb
index e1ce3c7b3a..bc35a67ff2 100644
--- a/app/serializers/container_repositories_serializer.rb
+++ b/app/serializers/container_repositories_serializer.rb
@@ -2,4 +2,8 @@
class ContainerRepositoriesSerializer < BaseSerializer
entity ContainerRepositoryEntity
+
+ def represent_read_only(resource)
+ represent(resource, except: [:destroy_path])
+ end
end
diff --git a/app/serializers/diff_file_base_entity.rb b/app/serializers/diff_file_base_entity.rb
index ee68b4b98e..302fe3d7c6 100644
--- a/app/serializers/diff_file_base_entity.rb
+++ b/app/serializers/diff_file_base_entity.rb
@@ -89,6 +89,14 @@ class DiffFileBaseEntity < Grape::Entity
expose :viewer, using: DiffViewerEntity
+ expose :old_size do |diff_file|
+ diff_file.old_blob&.raw_size
+ end
+
+ expose :new_size do |diff_file|
+ diff_file.new_blob&.raw_size
+ end
+
private
def memoized_submodule_links(diff_file, options)
diff --git a/app/serializers/diff_file_entity.rb b/app/serializers/diff_file_entity.rb
index 2a5121a226..af7d1172f1 100644
--- a/app/serializers/diff_file_entity.rb
+++ b/app/serializers/diff_file_entity.rb
@@ -53,7 +53,7 @@ class DiffFileEntity < DiffFileBaseEntity
end
# Used for inline diffs
- expose :highlighted_diff_lines, using: DiffLineEntity, if: -> (diff_file, _) { diff_file.text? } do |diff_file|
+ expose :highlighted_diff_lines, using: DiffLineEntity, if: -> (diff_file, options) { inline_diff_view?(options) && diff_file.text? } do |diff_file|
diff_file.diff_lines_for_serializer
end
@@ -62,5 +62,21 @@ class DiffFileEntity < DiffFileBaseEntity
end
# Used for parallel diffs
- expose :parallel_diff_lines, using: DiffLineParallelEntity, if: -> (diff_file, _) { diff_file.text? }
+ expose :parallel_diff_lines, using: DiffLineParallelEntity, if: -> (diff_file, options) { parallel_diff_view?(options) && diff_file.text? }
+
+ private
+
+ def parallel_diff_view?(options)
+ return true unless Feature.enabled?(:single_mr_diff_view)
+
+ # If we're not rendering inline, we must be rendering parallel
+ !inline_diff_view?(options)
+ end
+
+ def inline_diff_view?(options)
+ return true unless Feature.enabled?(:single_mr_diff_view)
+
+ # If nothing is present, inline will be the default.
+ options.fetch(:diff_view, :inline).to_sym == :inline
+ end
end
diff --git a/app/serializers/error_tracking/detailed_error_entity.rb b/app/serializers/error_tracking/detailed_error_entity.rb
new file mode 100644
index 0000000000..8f08f84aa4
--- /dev/null
+++ b/app/serializers/error_tracking/detailed_error_entity.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module ErrorTracking
+ class DetailedErrorEntity < Grape::Entity
+ expose :count,
+ :culprit,
+ :external_base_url,
+ :external_url,
+ :first_release_last_commit,
+ :first_release_short_version,
+ :first_seen,
+ :frequency,
+ :id,
+ :last_release_last_commit,
+ :last_release_short_version,
+ :last_seen,
+ :message,
+ :project_id,
+ :project_name,
+ :project_slug,
+ :short_id,
+ :status,
+ :title,
+ :type,
+ :user_count
+ end
+end
diff --git a/app/serializers/error_tracking/detailed_error_serializer.rb b/app/serializers/error_tracking/detailed_error_serializer.rb
new file mode 100644
index 0000000000..201da16a1a
--- /dev/null
+++ b/app/serializers/error_tracking/detailed_error_serializer.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+module ErrorTracking
+ class DetailedErrorSerializer < BaseSerializer
+ entity DetailedErrorEntity
+ end
+end
diff --git a/app/serializers/error_tracking/error_event_entity.rb b/app/serializers/error_tracking/error_event_entity.rb
new file mode 100644
index 0000000000..6cf0e6e3ae
--- /dev/null
+++ b/app/serializers/error_tracking/error_event_entity.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+module ErrorTracking
+ class ErrorEventEntity < Grape::Entity
+ expose :issue_id, :date_received, :stack_trace_entries
+ end
+end
diff --git a/app/serializers/error_tracking/error_event_serializer.rb b/app/serializers/error_tracking/error_event_serializer.rb
new file mode 100644
index 0000000000..bc4eae1636
--- /dev/null
+++ b/app/serializers/error_tracking/error_event_serializer.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+module ErrorTracking
+ class ErrorEventSerializer < BaseSerializer
+ entity ErrorEventEntity
+ end
+end
diff --git a/app/serializers/issuable_sidebar_extras_entity.rb b/app/serializers/issuable_sidebar_extras_entity.rb
index fb35b7522c..0e1fcc58d7 100644
--- a/app/serializers/issuable_sidebar_extras_entity.rb
+++ b/app/serializers/issuable_sidebar_extras_entity.rb
@@ -3,11 +3,20 @@
class IssuableSidebarExtrasEntity < Grape::Entity
include RequestAwareEntity
include TimeTrackableEntity
+ include NotificationsHelper
expose :participants, using: ::API::Entities::UserBasic do |issuable|
issuable.participants(request.current_user)
end
+ expose :project_emails_disabled do |issuable|
+ issuable.project.emails_disabled?
+ end
+
+ expose :subscribe_disabled_description do |issuable|
+ notification_description(:owner_disabled)
+ end
+
expose :subscribed do |issuable|
issuable.subscribed?(request.current_user, issuable.project)
end
diff --git a/app/serializers/issue_board_entity.rb b/app/serializers/issue_board_entity.rb
index b8f799a745..1389727981 100644
--- a/app/serializers/issue_board_entity.rb
+++ b/app/serializers/issue_board_entity.rb
@@ -2,7 +2,6 @@
class IssueBoardEntity < Grape::Entity
include RequestAwareEntity
- include TimeTrackableEntity
expose :id
expose :iid
diff --git a/app/serializers/job_artifact_report_entity.rb b/app/serializers/job_artifact_report_entity.rb
index 4280351a6b..bdab8f6478 100644
--- a/app/serializers/job_artifact_report_entity.rb
+++ b/app/serializers/job_artifact_report_entity.rb
@@ -8,6 +8,6 @@ class JobArtifactReportEntity < Grape::Entity
expose :size
expose :download_path do |artifact|
- download_project_job_artifacts_path(artifact.job.project, artifact.job, file_type: artifact.file_format)
+ download_project_job_artifacts_path(artifact.job.project, artifact.job, file_type: artifact.file_type)
end
end
diff --git a/app/serializers/merge_request_diff_entity.rb b/app/serializers/merge_request_diff_entity.rb
index 7e3053e588..5c79b165ee 100644
--- a/app/serializers/merge_request_diff_entity.rb
+++ b/app/serializers/merge_request_diff_entity.rb
@@ -21,6 +21,8 @@ class MergeRequestDiffEntity < Grape::Entity
expose :latest?, as: :latest
expose :short_commit_sha do |merge_request_diff|
+ next unless merge_request_diff.head_commit_sha
+
short_sha(merge_request_diff.head_commit_sha)
end
diff --git a/app/serializers/merge_request_poll_widget_entity.rb b/app/serializers/merge_request_poll_widget_entity.rb
index 854349e850..2a61187a85 100644
--- a/app/serializers/merge_request_poll_widget_entity.rb
+++ b/app/serializers/merge_request_poll_widget_entity.rb
@@ -65,6 +65,12 @@ class MergeRequestPollWidgetEntity < IssuableEntity
end
end
+ expose :exposed_artifacts_path do |merge_request|
+ if merge_request.has_exposed_artifacts?
+ exposed_artifacts_project_merge_request_path(merge_request.project, merge_request, format: :json)
+ end
+ end
+
expose :create_issue_to_resolve_discussions_path do |merge_request|
presenter(merge_request).create_issue_to_resolve_discussions_path
end
diff --git a/app/serializers/note_entity.rb b/app/serializers/note_entity.rb
index 1d3b59eb1b..c49dec2a93 100644
--- a/app/serializers/note_entity.rb
+++ b/app/serializers/note_entity.rb
@@ -79,3 +79,5 @@ class NoteEntity < API::Entities::Note
request.current_user
end
end
+
+NoteEntity.prepend_if_ee('EE::NoteEntity')
diff --git a/app/serializers/projects/serverless/service_entity.rb b/app/serializers/projects/serverless/service_entity.rb
index a1e0bf02d1..10360e575b 100644
--- a/app/serializers/projects/serverless/service_entity.rb
+++ b/app/serializers/projects/serverless/service_entity.rb
@@ -44,18 +44,11 @@ module Projects
end
expose :url do |service|
- service.dig('status', 'url') || "http://#{service.dig('status', 'domain')}"
+ knative_06_07_url(service) || knative_05_url(service)
end
expose :description do |service|
- service.dig(
- 'spec',
- 'runLatest',
- 'configuration',
- 'revisionTemplate',
- 'metadata',
- 'annotations',
- 'Description')
+ knative_07_description(service) || knative_05_06_description(service)
end
expose :image do |service|
@@ -67,6 +60,37 @@ module Projects
'template',
'name')
end
+
+ private
+
+ def knative_07_description(service)
+ service.dig(
+ 'spec',
+ 'template',
+ 'metadata',
+ 'annotations',
+ 'Description'
+ )
+ end
+
+ def knative_05_url(service)
+ "http://#{service.dig('status', 'domain')}"
+ end
+
+ def knative_06_07_url(service)
+ service.dig('status', 'url')
+ end
+
+ def knative_05_06_description(service)
+ service.dig(
+ 'spec',
+ 'runLatest',
+ 'configuration',
+ 'revisionTemplate',
+ 'metadata',
+ 'annotations',
+ 'Description')
+ end
end
end
end
diff --git a/app/services/base_service.rb b/app/services/base_service.rb
index c39edd5c11..bc0b968f51 100644
--- a/app/services/base_service.rb
+++ b/app/services/base_service.rb
@@ -50,16 +50,24 @@ class BaseService
private
- def error(message, http_status = nil)
+ # Return a Hash with an `error` status
+ #
+ # message - Error message to include in the Hash
+ # http_status - Optional HTTP status code override (default: nil)
+ # pass_back - Additional attributes to be included in the resulting Hash
+ def error(message, http_status = nil, pass_back: {})
result = {
message: message,
status: :error
- }
+ }.reverse_merge(pass_back)
result[:http_status] = http_status if http_status
result
end
+ # Return a Hash with a `success` status
+ #
+ # pass_back - Additional attributes to be included in the resulting Hash
def success(pass_back = {})
pass_back[:status] = :success
pass_back
diff --git a/app/services/ci/compare_reports_base_service.rb b/app/services/ci/compare_reports_base_service.rb
index 5b76e1824e..83ba70e843 100644
--- a/app/services/ci/compare_reports_base_service.rb
+++ b/app/services/ci/compare_reports_base_service.rb
@@ -1,6 +1,11 @@
# frozen_string_literal: true
module Ci
+ # TODO: when using this class with exposed artifacts we see that there are
+ # 2 responsibilities:
+ # 1. reactive caching interface (same in all cases)
+ # 2. data generator (report comparison in most of the case but not always)
+ # issue: https://gitlab.com/gitlab-org/gitlab/issues/34224
class CompareReportsBaseService < ::BaseService
def execute(base_pipeline, head_pipeline)
comparer = comparer_class.new(get_report(base_pipeline), get_report(head_pipeline))
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index eb4176035d..5778a48bce 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -7,11 +7,14 @@ module Ci
CreateError = Class.new(StandardError)
SEQUENCE = [Gitlab::Ci::Pipeline::Chain::Build,
- Gitlab::Ci::Pipeline::Chain::RemoveUnwantedChatJobs,
Gitlab::Ci::Pipeline::Chain::Validate::Abilities,
Gitlab::Ci::Pipeline::Chain::Validate::Repository,
- Gitlab::Ci::Pipeline::Chain::Validate::Config,
+ Gitlab::Ci::Pipeline::Chain::Config::Content,
+ Gitlab::Ci::Pipeline::Chain::Config::Process,
+ Gitlab::Ci::Pipeline::Chain::RemoveUnwantedChatJobs,
Gitlab::Ci::Pipeline::Chain::Skip,
+ Gitlab::Ci::Pipeline::Chain::EvaluateWorkflowRules,
+ Gitlab::Ci::Pipeline::Chain::Seed,
Gitlab::Ci::Pipeline::Chain::Limit::Size,
Gitlab::Ci::Pipeline::Chain::Populate,
Gitlab::Ci::Pipeline::Chain::Create,
diff --git a/app/services/ci/find_exposed_artifacts_service.rb b/app/services/ci/find_exposed_artifacts_service.rb
new file mode 100644
index 0000000000..5c75af294b
--- /dev/null
+++ b/app/services/ci/find_exposed_artifacts_service.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+module Ci
+ # This class loops through all builds with exposed artifacts and returns
+ # basic information about exposed artifacts for given jobs for the frontend
+ # to display them as custom links in the merge request.
+ #
+ # This service must be used with care.
+ # Looking for exposed artifacts is very slow and should be done asynchronously.
+ class FindExposedArtifactsService < ::BaseService
+ include Gitlab::Routing
+
+ MAX_EXPOSED_ARTIFACTS = 10
+
+ def for_pipeline(pipeline, limit: MAX_EXPOSED_ARTIFACTS)
+ results = []
+
+ pipeline.builds.latest.with_exposed_artifacts.find_each do |job|
+ if job_exposed_artifacts = for_job(job)
+ results << job_exposed_artifacts
+ end
+
+ break if results.size >= limit
+ end
+
+ results
+ end
+
+ def for_job(job)
+ return unless job.has_exposed_artifacts?
+
+ metadata_entries = first_2_metadata_entries_for_artifacts_paths(job)
+ return if metadata_entries.empty?
+
+ {
+ text: job.artifacts_expose_as,
+ url: path_for_entries(metadata_entries, job),
+ job_path: project_job_path(project, job),
+ job_name: job.name
+ }
+ end
+
+ private
+
+ # we don't need to fetch all artifacts entries for a job because
+ # it could contain many. We only need to know whether it has 1 or more
+ # artifacts, so fetching the first 2 would be sufficient.
+ def first_2_metadata_entries_for_artifacts_paths(job)
+ job.artifacts_paths
+ .lazy
+ .map { |path| job.artifacts_metadata_entry(path, recursive: true) }
+ .select { |entry| entry.exists? }
+ .first(2)
+ end
+
+ def path_for_entries(entries, job)
+ return if entries.empty?
+
+ if single_artifact?(entries)
+ file_project_job_artifacts_path(project, job, entries.first.path)
+ else
+ browse_project_job_artifacts_path(project, job)
+ end
+ end
+
+ def single_artifact?(entries)
+ entries.size == 1 && entries.first.file?
+ end
+ end
+end
diff --git a/app/services/ci/generate_exposed_artifacts_report_service.rb b/app/services/ci/generate_exposed_artifacts_report_service.rb
new file mode 100644
index 0000000000..b9bf580bcb
--- /dev/null
+++ b/app/services/ci/generate_exposed_artifacts_report_service.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Ci
+ # TODO: a couple of points with this approach:
+ # + reuses existing architecture and reactive caching
+ # - it's not a report comparison and some comparing features must be turned off.
+ # see CompareReportsBaseService for more notes.
+ # issue: https://gitlab.com/gitlab-org/gitlab/issues/34224
+ class GenerateExposedArtifactsReportService < CompareReportsBaseService
+ def execute(base_pipeline, head_pipeline)
+ data = FindExposedArtifactsService.new(project, current_user).for_pipeline(head_pipeline)
+ {
+ status: :parsed,
+ key: key(base_pipeline, head_pipeline),
+ data: data
+ }
+ rescue => e
+ Gitlab::Sentry.track_acceptable_exception(e, extra: { project_id: project.id })
+ {
+ status: :error,
+ key: key(base_pipeline, head_pipeline),
+ status_reason: _('An error occurred while fetching exposed artifacts.')
+ }
+ end
+
+ def latest?(base_pipeline, head_pipeline, data)
+ data&.fetch(:key, nil) == key(base_pipeline, head_pipeline)
+ end
+ end
+end
diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb
index d8f32ff88c..30e2a66e04 100644
--- a/app/services/ci/register_job_service.rb
+++ b/app/services/ci/register_job_service.rb
@@ -42,26 +42,16 @@ module Ci
end
builds.each do |build|
- next unless runner.can_pick?(build)
+ result = process_build(build, params)
+ next unless result
- begin
- # In case when 2 runners try to assign the same build, second runner will be declined
- # with StateMachines::InvalidTransition or StaleObjectError when doing run! or save method.
- if assign_runner!(build, params)
- register_success(build)
+ if result.valid?
+ register_success(result.build)
- return Result.new(build, true)
- end
- rescue StateMachines::InvalidTransition, ActiveRecord::StaleObjectError
- # We are looping to find another build that is not conflicting
- # It also indicates that this build can be picked and passed to runner.
- # If we don't do it, basically a bunch of runners would be competing for a build
- # and thus we will generate a lot of 409. This will increase
- # the number of generated requests, also will reduce significantly
- # how many builds can be picked by runner in a unit of time.
- # In case we hit the concurrency-access lock,
- # we still have to return 409 in the end,
- # to make sure that this is properly handled by runner.
+ return result
+ else
+ # The usage of valid: is described in
+ # handling of ActiveRecord::StaleObjectError
valid = false
end
end
@@ -73,6 +63,35 @@ module Ci
private
+ def process_build(build, params)
+ return unless runner.can_pick?(build)
+
+ # In case when 2 runners try to assign the same build, second runner will be declined
+ # with StateMachines::InvalidTransition or StaleObjectError when doing run! or save method.
+ if assign_runner!(build, params)
+ Result.new(build, true)
+ end
+ rescue StateMachines::InvalidTransition, ActiveRecord::StaleObjectError
+ # We are looping to find another build that is not conflicting
+ # It also indicates that this build can be picked and passed to runner.
+ # If we don't do it, basically a bunch of runners would be competing for a build
+ # and thus we will generate a lot of 409. This will increase
+ # the number of generated requests, also will reduce significantly
+ # how many builds can be picked by runner in a unit of time.
+ # In case we hit the concurrency-access lock,
+ # we still have to return 409 in the end,
+ # to make sure that this is properly handled by runner.
+ Result.new(nil, false)
+ rescue => ex
+ raise ex unless Feature.enabled?(:ci_doom_build, default_enabled: true)
+
+ scheduler_failure!(build)
+ track_exception_for_build(ex, build)
+
+ # skip, and move to next one
+ nil
+ end
+
def assign_runner!(build, params)
build.runner_id = runner.id
build.runner_session_attributes = params[:session] if params[:session].present?
@@ -96,6 +115,28 @@ module Ci
true
end
+ def scheduler_failure!(build)
+ Gitlab::OptimisticLocking.retry_lock(build, 3) do |subject|
+ subject.drop!(:scheduler_failure)
+ end
+ rescue => ex
+ build.doom!
+
+ # This requires extra exception, otherwise we would loose information
+ # why we cannot perform `scheduler_failure`
+ track_exception_for_build(ex, build)
+ end
+
+ def track_exception_for_build(ex, build)
+ Gitlab::Sentry.track_acceptable_exception(ex, extra: {
+ build_id: build.id,
+ build_name: build.name,
+ build_stage: build.stage,
+ pipeline_id: build.pipeline_id,
+ project_id: build.project_id
+ })
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def builds_for_shared_runner
new_builds.
@@ -108,7 +149,7 @@ module Ci
# this returns builds that are ordered by number of running builds
# we prefer projects that don't use shared runners at all
joins("LEFT JOIN (#{running_builds_for_shared_runners.to_sql}) AS project_builds ON ci_builds.project_id=project_builds.project_id")
- .order('COALESCE(project_builds.running_builds, 0) ASC', 'ci_builds.id ASC')
+ .order(Arel.sql('COALESCE(project_builds.running_builds, 0) ASC'), 'ci_builds.id ASC')
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/services/clusters/applications/base_service.rb b/app/services/clusters/applications/base_service.rb
index 67fb3ac835..c9f7917938 100644
--- a/app/services/clusters/applications/base_service.rb
+++ b/app/services/clusters/applications/base_service.rb
@@ -19,10 +19,18 @@ module Clusters
application.hostname = params[:hostname]
end
+ if application.has_attribute?(:kibana_hostname)
+ application.kibana_hostname = params[:kibana_hostname]
+ end
+
if application.has_attribute?(:email)
application.email = params[:email]
end
+ if application.has_attribute?(:stack)
+ application.stack = params[:stack]
+ end
+
if application.respond_to?(:oauth_application)
application.oauth_application = create_oauth_application(application, request)
end
@@ -60,19 +68,13 @@ module Clusters
end
def invalid_application?
- unknown_application? || (!cluster.project_type? && project_only_application?)
+ unknown_application? || (application_name == Applications::ElasticStack.application_name && !Feature.enabled?(:enable_cluster_application_elastic_stack)) || (application_name == Applications::Crossplane.application_name && !Feature.enabled?(:enable_cluster_application_crossplane))
end
def unknown_application?
Clusters::Cluster::APPLICATIONS.keys.exclude?(application_name)
end
- # These applications will need extra configuration to enable them to work
- # with groups of projects
- def project_only_application?
- Clusters::Cluster::PROJECT_ONLY_APPLICATIONS.include?(application_name)
- end
-
def application_name
params[:application]
end
diff --git a/app/services/clusters/aws/fetch_credentials_service.rb b/app/services/clusters/aws/fetch_credentials_service.rb
new file mode 100644
index 0000000000..2724d4b657
--- /dev/null
+++ b/app/services/clusters/aws/fetch_credentials_service.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Aws
+ class FetchCredentialsService
+ attr_reader :provision_role
+
+ MissingRoleError = Class.new(StandardError)
+
+ def initialize(provision_role, region:, provider: nil)
+ @provision_role = provision_role
+ @region = region
+ @provider = provider
+ end
+
+ def execute
+ raise MissingRoleError.new('AWS provisioning role not configured') unless provision_role.present?
+
+ ::Aws::AssumeRoleCredentials.new(
+ client: client,
+ role_arn: provision_role.role_arn,
+ role_session_name: session_name,
+ external_id: provision_role.role_external_id
+ ).credentials
+ end
+
+ private
+
+ attr_reader :provider, :region
+
+ def client
+ ::Aws::STS::Client.new(credentials: gitlab_credentials, region: region)
+ end
+
+ def gitlab_credentials
+ ::Aws::Credentials.new(access_key_id, secret_access_key)
+ end
+
+ def access_key_id
+ Gitlab::CurrentSettings.eks_access_key_id
+ end
+
+ def secret_access_key
+ Gitlab::CurrentSettings.eks_secret_access_key
+ end
+
+ def session_name
+ if provider.present?
+ "gitlab-eks-cluster-#{provider.cluster_id}-user-#{provision_role.user_id}"
+ else
+ "gitlab-eks-autofill-user-#{provision_role.user_id}"
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/clusters/aws/finalize_creation_service.rb b/app/services/clusters/aws/finalize_creation_service.rb
new file mode 100644
index 0000000000..54f07e1d44
--- /dev/null
+++ b/app/services/clusters/aws/finalize_creation_service.rb
@@ -0,0 +1,139 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Aws
+ class FinalizeCreationService
+ include Gitlab::Utils::StrongMemoize
+
+ attr_reader :provider
+
+ delegate :cluster, to: :provider
+
+ def execute(provider)
+ @provider = provider
+
+ configure_provider
+ create_gitlab_service_account!
+ configure_platform_kubernetes
+ configure_node_authentication!
+
+ cluster.save!
+ rescue ::Aws::CloudFormation::Errors::ServiceError => e
+ log_service_error(e.class.name, provider.id, e.message)
+ provider.make_errored!(s_('ClusterIntegration|Failed to fetch CloudFormation stack: %{message}') % { message: e.message })
+ rescue Kubeclient::HttpError => e
+ log_service_error(e.class.name, provider.id, e.message)
+ provider.make_errored!(s_('ClusterIntegration|Failed to run Kubeclient: %{message}') % { message: e.message })
+ rescue ActiveRecord::RecordInvalid => e
+ log_service_error(e.class.name, provider.id, e.message)
+ provider.make_errored!(s_('ClusterIntegration|Failed to configure EKS provider: %{message}') % { message: e.message })
+ end
+
+ private
+
+ def create_gitlab_service_account!
+ Clusters::Kubernetes::CreateOrUpdateServiceAccountService.gitlab_creator(
+ kube_client,
+ rbac: true
+ ).execute
+ end
+
+ def configure_provider
+ provider.status_event = :make_created
+ end
+
+ def configure_platform_kubernetes
+ cluster.build_platform_kubernetes(
+ api_url: cluster_endpoint,
+ ca_cert: cluster_certificate,
+ token: request_kubernetes_token)
+ end
+
+ def request_kubernetes_token
+ Clusters::Kubernetes::FetchKubernetesTokenService.new(
+ kube_client,
+ Clusters::Kubernetes::GITLAB_ADMIN_TOKEN_NAME,
+ Clusters::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAMESPACE
+ ).execute
+ end
+
+ def kube_client
+ @kube_client ||= build_kube_client!(
+ cluster_endpoint,
+ cluster_certificate
+ )
+ end
+
+ def build_kube_client!(api_url, ca_pem)
+ raise "Incomplete settings" unless api_url
+
+ Gitlab::Kubernetes::KubeClient.new(
+ api_url,
+ auth_options: kubeclient_auth_options,
+ ssl_options: kubeclient_ssl_options(ca_pem),
+ http_proxy_uri: ENV['http_proxy']
+ )
+ end
+
+ def kubeclient_auth_options
+ { bearer_token: Kubeclient::AmazonEksCredentials.token(provider.credentials, cluster.name) }
+ end
+
+ def kubeclient_ssl_options(ca_pem)
+ opts = { verify_ssl: OpenSSL::SSL::VERIFY_PEER }
+
+ if ca_pem.present?
+ opts[:cert_store] = OpenSSL::X509::Store.new
+ opts[:cert_store].add_cert(OpenSSL::X509::Certificate.new(ca_pem))
+ end
+
+ opts
+ end
+
+ def cluster_stack
+ @cluster_stack ||= provider.api_client.describe_stacks(stack_name: provider.cluster.name).stacks.first
+ end
+
+ def stack_output_value(key)
+ cluster_stack.outputs.detect { |output| output.output_key == key }.output_value
+ end
+
+ def node_instance_role_arn
+ stack_output_value('NodeInstanceRole')
+ end
+
+ def cluster_endpoint
+ strong_memoize(:cluster_endpoint) do
+ stack_output_value('ClusterEndpoint')
+ end
+ end
+
+ def cluster_certificate
+ strong_memoize(:cluster_certificate) do
+ Base64.decode64(stack_output_value('ClusterCertificate'))
+ end
+ end
+
+ def configure_node_authentication!
+ kube_client.create_config_map(node_authentication_config)
+ end
+
+ def node_authentication_config
+ Gitlab::Kubernetes::ConfigMaps::AwsNodeAuth.new(node_instance_role_arn).generate
+ end
+
+ def logger
+ @logger ||= Gitlab::Kubernetes::Logger.build
+ end
+
+ def log_service_error(exception, provider_id, message)
+ logger.error(
+ exception: exception.class.name,
+ service: self.class.name,
+ provider_id: provider_id,
+ message: message
+ )
+ end
+ end
+ end
+end
diff --git a/app/services/clusters/aws/provision_service.rb b/app/services/clusters/aws/provision_service.rb
new file mode 100644
index 0000000000..35fe8433b4
--- /dev/null
+++ b/app/services/clusters/aws/provision_service.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Aws
+ class ProvisionService
+ attr_reader :provider
+
+ def execute(provider)
+ @provider = provider
+
+ configure_provider_credentials
+ provision_cluster
+
+ if provider.make_creating
+ WaitForClusterCreationWorker.perform_in(
+ Clusters::Aws::VerifyProvisionStatusService::INITIAL_INTERVAL,
+ provider.cluster_id
+ )
+ else
+ provider.make_errored!("Failed to update provider record; #{provider.errors.full_messages}")
+ end
+ rescue Clusters::Aws::FetchCredentialsService::MissingRoleError
+ provider.make_errored!('Amazon role is not configured')
+ rescue ::Aws::Errors::MissingCredentialsError
+ provider.make_errored!('Amazon credentials are not configured')
+ rescue ::Aws::STS::Errors::ServiceError => e
+ provider.make_errored!("Amazon authentication failed; #{e.message}")
+ rescue ::Aws::CloudFormation::Errors::ServiceError => e
+ provider.make_errored!("Amazon CloudFormation request failed; #{e.message}")
+ end
+
+ private
+
+ def provision_role
+ provider.created_by_user&.aws_role
+ end
+
+ def credentials
+ @credentials ||= Clusters::Aws::FetchCredentialsService.new(
+ provision_role,
+ provider: provider,
+ region: provider.region
+ ).execute
+ end
+
+ def configure_provider_credentials
+ provider.update!(
+ access_key_id: credentials.access_key_id,
+ secret_access_key: credentials.secret_access_key,
+ session_token: credentials.session_token
+ )
+ end
+
+ def provision_cluster
+ provider.api_client.create_stack(
+ stack_name: provider.cluster.name,
+ template_body: stack_template,
+ parameters: parameters,
+ capabilities: ["CAPABILITY_IAM"]
+ )
+ end
+
+ def parameters
+ [
+ parameter('ClusterName', provider.cluster.name),
+ parameter('ClusterRole', provider.role_arn),
+ parameter('ClusterControlPlaneSecurityGroup', provider.security_group_id),
+ parameter('VpcId', provider.vpc_id),
+ parameter('Subnets', provider.subnet_ids.join(',')),
+ parameter('NodeAutoScalingGroupDesiredCapacity', provider.num_nodes.to_s),
+ parameter('NodeInstanceType', provider.instance_type),
+ parameter('KeyName', provider.key_name)
+ ]
+ end
+
+ def parameter(key, value)
+ { parameter_key: key, parameter_value: value }
+ end
+
+ def stack_template
+ File.read(Rails.root.join('vendor', 'aws', 'cloudformation', 'eks_cluster.yaml'))
+ end
+ end
+ end
+end
diff --git a/app/services/clusters/aws/proxy_service.rb b/app/services/clusters/aws/proxy_service.rb
new file mode 100644
index 0000000000..df8fc48000
--- /dev/null
+++ b/app/services/clusters/aws/proxy_service.rb
@@ -0,0 +1,134 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Aws
+ class ProxyService
+ DEFAULT_REGION = 'us-east-1'
+
+ BadRequest = Class.new(StandardError)
+ Response = Struct.new(:status, :body)
+
+ def initialize(role, params:)
+ @role = role
+ @params = params
+ end
+
+ def execute
+ api_response = request_from_api!
+
+ Response.new(:ok, api_response.to_hash)
+ rescue *service_errors
+ Response.new(:bad_request, {})
+ end
+
+ private
+
+ attr_reader :role, :params
+
+ def request_from_api!
+ case requested_resource
+ when 'key_pairs'
+ ec2_client.describe_key_pairs
+
+ when 'instance_types'
+ instance_types
+
+ when 'roles'
+ iam_client.list_roles
+
+ when 'regions'
+ ec2_client.describe_regions
+
+ when 'security_groups'
+ raise BadRequest unless vpc_id.present?
+
+ ec2_client.describe_security_groups(vpc_filter)
+
+ when 'subnets'
+ raise BadRequest unless vpc_id.present?
+
+ ec2_client.describe_subnets(vpc_filter)
+
+ when 'vpcs'
+ ec2_client.describe_vpcs
+
+ else
+ raise BadRequest
+ end
+ end
+
+ def requested_resource
+ params[:resource]
+ end
+
+ def vpc_id
+ params[:vpc_id]
+ end
+
+ def region
+ params[:region] || DEFAULT_REGION
+ end
+
+ def vpc_filter
+ {
+ filters: [{
+ name: "vpc-id",
+ values: [vpc_id]
+ }]
+ }
+ end
+
+ ##
+ # Unfortunately the EC2 API doesn't provide a list of
+ # possible instance types. There is a workaround, using
+ # the Pricing API, but instead of requiring the
+ # user to grant extra permissions for this we use the
+ # values that validate the CloudFormation template.
+ def instance_types
+ {
+ instance_types: cluster_stack_instance_types.map { |type| Hash(instance_type_name: type) }
+ }
+ end
+
+ def cluster_stack_instance_types
+ YAML.safe_load(stack_template).dig('Parameters', 'NodeInstanceType', 'AllowedValues')
+ end
+
+ def stack_template
+ File.read(Rails.root.join('vendor', 'aws', 'cloudformation', 'eks_cluster.yaml'))
+ end
+
+ def ec2_client
+ ::Aws::EC2::Client.new(client_options)
+ end
+
+ def iam_client
+ ::Aws::IAM::Client.new(client_options)
+ end
+
+ def credentials
+ Clusters::Aws::FetchCredentialsService.new(role, region: region).execute
+ end
+
+ def client_options
+ {
+ credentials: credentials,
+ region: region,
+ http_open_timeout: 5,
+ http_read_timeout: 10
+ }
+ end
+
+ def service_errors
+ [
+ BadRequest,
+ Clusters::Aws::FetchCredentialsService::MissingRoleError,
+ ::Aws::Errors::MissingCredentialsError,
+ ::Aws::EC2::Errors::ServiceError,
+ ::Aws::IAM::Errors::ServiceError,
+ ::Aws::STS::Errors::ServiceError
+ ]
+ end
+ end
+ end
+end
diff --git a/app/services/clusters/aws/verify_provision_status_service.rb b/app/services/clusters/aws/verify_provision_status_service.rb
new file mode 100644
index 0000000000..99532662bc
--- /dev/null
+++ b/app/services/clusters/aws/verify_provision_status_service.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Aws
+ class VerifyProvisionStatusService
+ attr_reader :provider
+
+ INITIAL_INTERVAL = 5.minutes
+ POLL_INTERVAL = 1.minute
+ TIMEOUT = 30.minutes
+
+ def execute(provider)
+ @provider = provider
+
+ case cluster_stack.stack_status
+ when 'CREATE_IN_PROGRESS'
+ continue_creation
+ when 'CREATE_COMPLETE'
+ finalize_creation
+ else
+ provider.make_errored!("Unexpected status; #{cluster_stack.stack_status}")
+ end
+ rescue ::Aws::CloudFormation::Errors::ServiceError => e
+ provider.make_errored!("Amazon CloudFormation request failed; #{e.message}")
+ end
+
+ private
+
+ def cluster_stack
+ @cluster_stack ||= provider.api_client.describe_stacks(stack_name: provider.cluster.name).stacks.first
+ end
+
+ def continue_creation
+ if timeout_threshold.future?
+ WaitForClusterCreationWorker.perform_in(POLL_INTERVAL, provider.cluster_id)
+ else
+ provider.make_errored!(_('Kubernetes cluster creation time exceeds timeout; %{timeout}') % { timeout: TIMEOUT })
+ end
+ end
+
+ def timeout_threshold
+ cluster_stack.creation_time + TIMEOUT
+ end
+
+ def finalize_creation
+ Clusters::Aws::FinalizeCreationService.new.execute(provider)
+ end
+ end
+ end
+end
diff --git a/app/services/clusters/destroy_service.rb b/app/services/clusters/destroy_service.rb
new file mode 100644
index 0000000000..a8de04683f
--- /dev/null
+++ b/app/services/clusters/destroy_service.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Clusters
+ class DestroyService
+ attr_reader :current_user, :params
+
+ def initialize(user = nil, params = {})
+ @current_user, @params = user, params.dup
+ @response = {}
+ end
+
+ def execute(cluster)
+ cleanup? ? start_cleanup!(cluster) : destroy_cluster!(cluster)
+
+ @response
+ end
+
+ private
+
+ def cleanup?
+ Gitlab::Utils.to_boolean(params[:cleanup])
+ end
+
+ def start_cleanup!(cluster)
+ cluster.start_cleanup!
+ @response[:message] = _('Kubernetes cluster integration and resources are being removed.')
+ end
+
+ def destroy_cluster!(cluster)
+ cluster.destroy!
+ @response[:message] = _('Kubernetes cluster integration was successfully removed.')
+ end
+ end
+end
diff --git a/app/services/clusters/kubernetes/create_or_update_service_account_service.rb b/app/services/clusters/kubernetes/create_or_update_service_account_service.rb
index 8b8ad924b6..d798dcdcfd 100644
--- a/app/services/clusters/kubernetes/create_or_update_service_account_service.rb
+++ b/app/services/clusters/kubernetes/create_or_update_service_account_service.rb
@@ -49,6 +49,8 @@ module Clusters
create_or_update_knative_serving_role
create_or_update_knative_serving_role_binding
+ create_or_update_crossplane_database_role
+ create_or_update_crossplane_database_role_binding
end
private
@@ -78,6 +80,14 @@ module Clusters
kubeclient.update_role_binding(knative_serving_role_binding_resource)
end
+ def create_or_update_crossplane_database_role
+ kubeclient.update_role(crossplane_database_role_resource)
+ end
+
+ def create_or_update_crossplane_database_role_binding
+ kubeclient.update_role_binding(crossplane_database_role_binding_resource)
+ end
+
def service_account_resource
Gitlab::Kubernetes::ServiceAccount.new(
service_account_name,
@@ -134,6 +144,28 @@ module Clusters
service_account_name: service_account_name
).generate
end
+
+ def crossplane_database_role_resource
+ Gitlab::Kubernetes::Role.new(
+ name: Clusters::Kubernetes::GITLAB_CROSSPLANE_DATABASE_ROLE_NAME,
+ namespace: service_account_namespace,
+ rules: [{
+ apiGroups: %w(database.crossplane.io),
+ resources: %w(postgresqlinstances),
+ verbs: %w(get list create watch)
+ }]
+ ).generate
+ end
+
+ def crossplane_database_role_binding_resource
+ Gitlab::Kubernetes::RoleBinding.new(
+ name: Clusters::Kubernetes::GITLAB_CROSSPLANE_DATABASE_ROLE_BINDING_NAME,
+ role_name: Clusters::Kubernetes::GITLAB_CROSSPLANE_DATABASE_ROLE_NAME,
+ role_kind: :Role,
+ namespace: service_account_namespace,
+ service_account_name: service_account_name
+ ).generate
+ end
end
end
end
diff --git a/app/services/clusters/kubernetes/kubernetes.rb b/app/services/clusters/kubernetes/kubernetes.rb
index 7d5d0c2c1d..d29519999b 100644
--- a/app/services/clusters/kubernetes/kubernetes.rb
+++ b/app/services/clusters/kubernetes/kubernetes.rb
@@ -10,5 +10,7 @@ module Clusters
PROJECT_CLUSTER_ROLE_NAME = 'edit'
GITLAB_KNATIVE_SERVING_ROLE_NAME = 'gitlab-knative-serving-role'
GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME = 'gitlab-knative-serving-rolebinding'
+ GITLAB_CROSSPLANE_DATABASE_ROLE_NAME = 'gitlab-crossplane-database-role'
+ GITLAB_CROSSPLANE_DATABASE_ROLE_BINDING_NAME = 'gitlab-crossplane-database-rolebinding'
end
end
diff --git a/app/services/clusters/update_service.rb b/app/services/clusters/update_service.rb
index 25d26e761b..8cb77040b1 100644
--- a/app/services/clusters/update_service.rb
+++ b/app/services/clusters/update_service.rb
@@ -9,7 +9,55 @@ module Clusters
end
def execute(cluster)
- cluster.update(params)
+ if validate_params(cluster)
+ cluster.update(params)
+ else
+ false
+ end
+ end
+
+ private
+
+ def can_admin_pipeline_for_project?(project)
+ Ability.allowed?(current_user, :admin_pipeline, project)
+ end
+
+ def validate_params(cluster)
+ if params[:management_project_id].present?
+ management_project = management_project_scope(cluster).find_by_id(params[:management_project_id])
+
+ unless management_project
+ cluster.errors.add(:management_project_id, _('Project does not exist or you don\'t have permission to perform this action'))
+
+ return false
+ end
+
+ unless can_admin_pipeline_for_project?(management_project)
+ # Use same message as not found to prevent enumeration
+ cluster.errors.add(:management_project_id, _('Project does not exist or you don\'t have permission to perform this action'))
+
+ return false
+ end
+ end
+
+ true
+ end
+
+ def management_project_scope(cluster)
+ return ::Project.all if cluster.instance_type?
+
+ group =
+ if cluster.group_type?
+ cluster.first_group
+ elsif cluster.project_type?
+ cluster.first_project&.namespace
+ end
+
+ # Prevent users from selecting nested projects until
+ # https://gitlab.com/gitlab-org/gitlab/issues/34650 is resolved
+ include_subgroups = cluster.group_type?
+
+ ::GroupProjectsFinder.new(group: group, current_user: current_user, options: { only_owned: true, include_subgroups: include_subgroups }).execute
end
end
end
diff --git a/app/services/cohorts_service.rb b/app/services/cohorts_service.rb
index 97fbb70f35..dbbe89ef26 100644
--- a/app/services/cohorts_service.rb
+++ b/app/services/cohorts_service.rb
@@ -88,7 +88,7 @@ class CohortsService
User
.where('created_at > ?', MONTHS_INCLUDED.months.ago.end_of_month)
.group(created_at_month, last_activity_on_month)
- .reorder("#{created_at_month} ASC", "#{last_activity_on_month} ASC")
+ .reorder(Arel.sql("#{created_at_month} ASC, #{last_activity_on_month} ASC"))
.count
end
end
diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb
index fbf71f0283..661e654406 100644
--- a/app/services/commits/change_service.rb
+++ b/app/services/commits/change_service.rb
@@ -23,14 +23,15 @@ module Commits
message,
start_project: @start_project,
start_branch_name: @start_branch)
- rescue Gitlab::Git::Repository::CreateTreeError
+ rescue Gitlab::Git::Repository::CreateTreeError => ex
act = action.to_s.dasherize
type = @commit.change_type_title(current_user)
error_msg = "Sorry, we cannot #{act} this #{type} automatically. " \
"This #{type} may already have been #{act}ed, or a more recent " \
"commit may have updated some of its content."
- raise ChangeError, error_msg
+
+ raise ChangeError.new(error_msg, ex.error_code)
end
end
end
diff --git a/app/services/commits/create_service.rb b/app/services/commits/create_service.rb
index b5401a8ea3..b42494563b 100644
--- a/app/services/commits/create_service.rb
+++ b/app/services/commits/create_service.rb
@@ -3,7 +3,15 @@
module Commits
class CreateService < ::BaseService
ValidationError = Class.new(StandardError)
- ChangeError = Class.new(StandardError)
+ class ChangeError < StandardError
+ attr_reader :error_code
+
+ def initialize(message, error_code = nil)
+ super(message)
+
+ @error_code = error_code
+ end
+ end
def initialize(*args)
super
@@ -21,8 +29,9 @@ module Commits
new_commit = create_commit!
success(result: new_commit)
+ rescue ChangeError => ex
+ error(ex.message, pass_back: { error_code: ex.error_code })
rescue ValidationError,
- ChangeError,
Gitlab::Git::Index::IndexError,
Gitlab::Git::CommitError,
Gitlab::Git::PreReceiveError,
diff --git a/app/services/concerns/git/logger.rb b/app/services/concerns/git/logger.rb
new file mode 100644
index 0000000000..7c036212e6
--- /dev/null
+++ b/app/services/concerns/git/logger.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module Git
+ module Logger
+ def log_error(message, save_message_on_model: false)
+ Gitlab::GitLogger.error("#{self.class.name} error (#{merge_request.to_reference(full: true)}): #{message}")
+ merge_request.update(merge_error: message) if save_message_on_model
+ end
+ end
+end
diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb
index 110e589e30..d58cb0f9e2 100644
--- a/app/services/create_branch_service.rb
+++ b/app/services/create_branch_service.rb
@@ -14,7 +14,7 @@ class CreateBranchService < BaseService
if new_branch
success(new_branch)
else
- error('Invalid reference name')
+ error("Invalid reference name: #{branch_name}")
end
rescue Gitlab::Git::PreReceiveError => ex
error(ex.message)
diff --git a/app/services/deployments/after_create_service.rb b/app/services/deployments/after_create_service.rb
index 2572802e6a..e0a4e5419c 100644
--- a/app/services/deployments/after_create_service.rb
+++ b/app/services/deployments/after_create_service.rb
@@ -33,12 +33,21 @@ module Deployments
if environment.save && !environment.stopped?
deployment.update_merge_request_metrics!
+ link_merge_requests(deployment)
end
end
end
private
+ def link_merge_requests(deployment)
+ unless Feature.enabled?(:deployment_merge_requests, deployment.project)
+ return
+ end
+
+ LinkMergeRequestsService.new(deployment).execute
+ end
+
def environment_options
options&.dig(:environment) || {}
end
diff --git a/app/services/deployments/link_merge_requests_service.rb b/app/services/deployments/link_merge_requests_service.rb
new file mode 100644
index 0000000000..7118665929
--- /dev/null
+++ b/app/services/deployments/link_merge_requests_service.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+module Deployments
+ # Service class for linking merge requests to deployments.
+ class LinkMergeRequestsService
+ attr_reader :deployment
+
+ # The number of commits per query for which to find merge requests.
+ COMMITS_PER_QUERY = 5_000
+
+ def initialize(deployment)
+ @deployment = deployment
+ end
+
+ def execute
+ return unless deployment.success?
+
+ if (prev = deployment.previous_environment_deployment)
+ link_merge_requests_for_range(prev.sha, deployment.sha)
+ else
+ # When no previous deployment is found we fall back to linking all merge
+ # requests merged into the deployed branch. This will not always be
+ # accurate, but it's better than having no data.
+ #
+ # We can't use the first commit in the repository as a base to compare
+ # to, as this will not scale to large repositories. For example, GitLab
+ # itself has over 150 000 commits.
+ link_all_merged_merge_requests
+ end
+ end
+
+ def link_merge_requests_for_range(from, to)
+ commits = project
+ .repository
+ .commits_between(from, to)
+ .map(&:id)
+
+ # For some projects the list of commits to deploy may be very large. To
+ # ensure we do not end up running SQL queries with thousands of WHERE IN
+ # values, we run one query per a certain number of commits.
+ #
+ # In most cases this translates to only a single query. For very large
+ # deployment we may end up running a handful of queries to get and insert
+ # the data.
+ commits.each_slice(COMMITS_PER_QUERY) do |slice|
+ merge_requests =
+ project.merge_requests.merged.by_merge_commit_sha(slice)
+
+ deployment.link_merge_requests(merge_requests)
+ end
+ end
+
+ def link_all_merged_merge_requests
+ merge_requests =
+ project.merge_requests.merged.by_target_branch(deployment.ref)
+
+ deployment.link_merge_requests(merge_requests)
+ end
+
+ private
+
+ def project
+ deployment.project
+ end
+ end
+end
diff --git a/app/services/deployments/update_service.rb b/app/services/deployments/update_service.rb
index 7c8215d28f..97b233f16a 100644
--- a/app/services/deployments/update_service.rb
+++ b/app/services/deployments/update_service.rb
@@ -10,7 +10,22 @@ module Deployments
end
def execute
- deployment.update(status: params[:status])
+ # A regular update() does not trigger the state machine transitions, which
+ # we need to ensure merge requests are linked when changing the status to
+ # success. To work around this we use this case statment, using the right
+ # event methods to trigger the transition hooks.
+ case params[:status]
+ when 'running'
+ deployment.run
+ when 'success'
+ deployment.succeed
+ when 'failed'
+ deployment.drop
+ when 'canceled'
+ deployment.cancel
+ else
+ false
+ end
end
end
end
diff --git a/app/services/error_tracking/base_service.rb b/app/services/error_tracking/base_service.rb
new file mode 100644
index 0000000000..430d995233
--- /dev/null
+++ b/app/services/error_tracking/base_service.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+module ErrorTracking
+ class BaseService < ::BaseService
+ def execute
+ unauthorized = check_permissions
+ return unauthorized if unauthorized
+
+ begin
+ response = fetch
+ rescue Sentry::Client::Error => e
+ return error(e.message, :bad_request)
+ rescue Sentry::Client::MissingKeysError => e
+ return error(e.message, :internal_server_error)
+ end
+
+ errors = parse_errors(response)
+ return errors if errors
+
+ success(parse_response(response))
+ end
+
+ private
+
+ def fetch
+ raise NotImplementedError,
+ "#{self.class} does not implement #{__method__}"
+ end
+
+ def parse_response(response)
+ raise NotImplementedError,
+ "#{self.class} does not implement #{__method__}"
+ end
+
+ def check_permissions
+ return error('Error Tracking is not enabled') unless enabled?
+ return error('Access denied', :unauthorized) unless can_read?
+ end
+
+ def parse_errors(response)
+ return error('Not ready. Try again later', :no_content) unless response
+ return error(response[:error], http_status_for(response[:error_type])) if response[:error].present?
+ end
+
+ def http_status_for(error_type)
+ case error_type
+ when ErrorTracking::ProjectErrorTrackingSetting::SENTRY_API_ERROR_TYPE_MISSING_KEYS
+ :internal_server_error
+ else
+ :bad_request
+ end
+ end
+
+ def project_error_tracking_setting
+ project.error_tracking_setting
+ end
+
+ def enabled?
+ project_error_tracking_setting&.enabled?
+ end
+
+ def can_read?
+ can?(current_user, :read_sentry_issue, project)
+ end
+ end
+end
diff --git a/app/services/error_tracking/issue_details_service.rb b/app/services/error_tracking/issue_details_service.rb
new file mode 100644
index 0000000000..368cd4517f
--- /dev/null
+++ b/app/services/error_tracking/issue_details_service.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module ErrorTracking
+ class IssueDetailsService < ErrorTracking::BaseService
+ private
+
+ def fetch
+ project_error_tracking_setting.issue_details(issue_id: params[:issue_id])
+ end
+
+ def parse_response(response)
+ { issue: response[:issue] }
+ end
+ end
+end
diff --git a/app/services/error_tracking/issue_latest_event_service.rb b/app/services/error_tracking/issue_latest_event_service.rb
new file mode 100644
index 0000000000..b6ad8f8028
--- /dev/null
+++ b/app/services/error_tracking/issue_latest_event_service.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module ErrorTracking
+ class IssueLatestEventService < ErrorTracking::BaseService
+ private
+
+ def fetch
+ project_error_tracking_setting.issue_latest_event(issue_id: params[:issue_id])
+ end
+
+ def parse_response(response)
+ { latest_event: response[:latest_event] }
+ end
+ end
+end
diff --git a/app/services/error_tracking/list_issues_service.rb b/app/services/error_tracking/list_issues_service.rb
index 86ab21fa86..2e8c401b8e 100644
--- a/app/services/error_tracking/list_issues_service.rb
+++ b/app/services/error_tracking/list_issues_service.rb
@@ -1,46 +1,22 @@
# frozen_string_literal: true
module ErrorTracking
- class ListIssuesService < ::BaseService
+ class ListIssuesService < ErrorTracking::BaseService
DEFAULT_ISSUE_STATUS = 'unresolved'
DEFAULT_LIMIT = 20
- def execute
- return error('Error Tracking is not enabled') unless enabled?
- return error('Access denied', :unauthorized) unless can_read?
-
- result = project_error_tracking_setting
- .list_sentry_issues(issue_status: issue_status, limit: limit)
-
- # our results are not yet ready
- unless result
- return error('Not ready. Try again later', :no_content)
- end
-
- if result[:error].present?
- return error(result[:error], http_status_from_error_type(result[:error_type]))
- end
-
- success(issues: result[:issues])
- end
-
def external_url
project_error_tracking_setting&.sentry_external_url
end
private
- def http_status_from_error_type(error_type)
- case error_type
- when ErrorTracking::ProjectErrorTrackingSetting::SENTRY_API_ERROR_TYPE_MISSING_KEYS
- :internal_server_error
- else
- :bad_request
- end
+ def fetch
+ project_error_tracking_setting.list_sentry_issues(issue_status: issue_status, limit: limit)
end
- def project_error_tracking_setting
- project.error_tracking_setting
+ def parse_response(response)
+ { issues: response[:issues] }
end
def issue_status
@@ -50,13 +26,5 @@ module ErrorTracking
def limit
params[:limit] || DEFAULT_LIMIT
end
-
- def enabled?
- project_error_tracking_setting&.enabled?
- end
-
- def can_read?
- can?(current_user, :read_sentry_issue, project)
- end
end
end
diff --git a/app/services/error_tracking/list_projects_service.rb b/app/services/error_tracking/list_projects_service.rb
index 92d4ef85ec..09a0b952e8 100644
--- a/app/services/error_tracking/list_projects_service.rb
+++ b/app/services/error_tracking/list_projects_service.rb
@@ -1,44 +1,38 @@
# frozen_string_literal: true
module ErrorTracking
- class ListProjectsService < ::BaseService
+ class ListProjectsService < ErrorTracking::BaseService
def execute
- return error('access denied') unless can_read?
-
- setting = project_error_tracking_setting
-
- unless setting.valid?
- return error(setting.errors.full_messages.join(', '), :bad_request)
+ unless project_error_tracking_setting.valid?
+ return error(project_error_tracking_setting.errors.full_messages.join(', '), :bad_request)
end
- begin
- result = setting.list_sentry_projects
- rescue Sentry::Client::Error => e
- return error(e.message, :bad_request)
- rescue Sentry::Client::MissingKeysError => e
- return error(e.message, :internal_server_error)
- end
-
- success(projects: result[:projects])
+ super
end
private
- def project_error_tracking_setting
- (project.error_tracking_setting || project.build_error_tracking_setting).tap do |setting|
- setting.api_url = ErrorTracking::ProjectErrorTrackingSetting.build_api_url_from(
- api_host: params[:api_host],
- organization_slug: 'org',
- project_slug: 'proj'
- )
-
- setting.token = token(setting)
- setting.enabled = true
- end
+ def fetch
+ project_error_tracking_setting.list_sentry_projects
end
- def can_read?
- can?(current_user, :read_sentry_issue, project)
+ def parse_response(response)
+ { projects: response[:projects] }
+ end
+
+ def project_error_tracking_setting
+ @project_error_tracking_setting ||= begin
+ (super || project.build_error_tracking_setting).tap do |setting|
+ setting.api_url = ErrorTracking::ProjectErrorTrackingSetting.build_api_url_from(
+ api_host: params[:api_host],
+ organization_slug: 'org',
+ project_slug: 'proj'
+ )
+
+ setting.token = token(setting)
+ setting.enabled = true
+ end
+ end
end
def token(setting)
diff --git a/app/services/groups/group_links/create_service.rb b/app/services/groups/group_links/create_service.rb
new file mode 100644
index 0000000000..2ce53fcfe4
--- /dev/null
+++ b/app/services/groups/group_links/create_service.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Groups
+ module GroupLinks
+ class CreateService < BaseService
+ def execute(shared_group)
+ unless group && shared_group &&
+ can?(current_user, :admin_group, shared_group) &&
+ can?(current_user, :read_group, group)
+ return error('Not Found', 404)
+ end
+
+ link = GroupGroupLink.new(
+ shared_group: shared_group,
+ shared_with_group: group,
+ group_access: params[:shared_group_access],
+ expires_at: params[:expires_at]
+ )
+
+ if link.save
+ group.refresh_members_authorized_projects
+ success(link: link)
+ else
+ error(link.errors.full_messages.to_sentence, 409)
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/groups/group_links/destroy_service.rb b/app/services/groups/group_links/destroy_service.rb
new file mode 100644
index 0000000000..29aa8de4e6
--- /dev/null
+++ b/app/services/groups/group_links/destroy_service.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Groups
+ module GroupLinks
+ class DestroyService < BaseService
+ def execute(one_or_more_links)
+ links = Array(one_or_more_links)
+
+ GroupGroupLink.transaction do
+ GroupGroupLink.delete(links)
+
+ groups_to_refresh = links.map(&:shared_with_group)
+ groups_to_refresh.uniq.each do |group|
+ group.refresh_members_authorized_projects
+ end
+
+ Gitlab::AppLogger.info("GroupGroupLinks with ids: #{links.map(&:id)} have been deleted.")
+ rescue => ex
+ Gitlab::AppLogger.error(ex)
+
+ raise
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/groups/import_export/export_service.rb b/app/services/groups/import_export/export_service.rb
new file mode 100644
index 0000000000..26886fc67d
--- /dev/null
+++ b/app/services/groups/import_export/export_service.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+module Groups
+ module ImportExport
+ class ExportService
+ def initialize(group:, user:, params: {})
+ @group = group
+ @current_user = user
+ @params = params
+ @shared = @params[:shared] || Gitlab::ImportExport::Shared.new(@group)
+ end
+
+ def execute
+ save!
+ end
+
+ private
+
+ attr_accessor :shared
+
+ def save!
+ if savers.all?(&:save)
+ notify_success
+ else
+ cleanup_and_notify_error!
+ end
+ end
+
+ def savers
+ [tree_exporter, file_saver]
+ end
+
+ def tree_exporter
+ Gitlab::ImportExport::GroupTreeSaver.new(group: @group, current_user: @current_user, shared: @shared, params: @params)
+ end
+
+ def file_saver
+ Gitlab::ImportExport::Saver.new(exportable: @group, shared: @shared)
+ end
+
+ def cleanup_and_notify_error
+ FileUtils.rm_rf(shared.export_path)
+
+ notify_error
+ end
+
+ def cleanup_and_notify_error!
+ cleanup_and_notify_error
+
+ raise Gitlab::ImportExport::Error.new(shared.errors.to_sentence)
+ end
+
+ def notify_success
+ @shared.logger.info(
+ group_id: @group.id,
+ group_name: @group.name,
+ message: 'Group Import/Export: Export succeeded'
+ )
+ end
+
+ def notify_error
+ @shared.logger.error(
+ group_id: @group.id,
+ group_name: @group.name,
+ error: @shared.errors.join(', '),
+ message: 'Group Import/Export: Export failed'
+ )
+ end
+ end
+ end
+end
diff --git a/app/services/groups/transfer_service.rb b/app/services/groups/transfer_service.rb
index a2e6774f1d..4e7875e049 100644
--- a/app/services/groups/transfer_service.rb
+++ b/app/services/groups/transfer_service.rb
@@ -13,7 +13,7 @@ module Groups
TransferError = Class.new(StandardError)
- attr_reader :error
+ attr_reader :error, :new_parent_group
def initialize(group, user, params = {})
super
@@ -81,7 +81,7 @@ module Groups
# rubocop: enable CodeReuse/ActiveRecord
def group_projects_contain_registry_images?
- @group.has_container_repositories?
+ @group.has_container_repository_including_subgroups?
end
def update_group_attributes
diff --git a/app/services/groups/update_service.rb b/app/services/groups/update_service.rb
index be7502a193..8635b82461 100644
--- a/app/services/groups/update_service.rb
+++ b/app/services/groups/update_service.rb
@@ -43,8 +43,9 @@ module Groups
def renaming_group_with_container_registry_images?
new_path = params[:path]
- new_path && new_path != group.path &&
- group.has_container_repositories?
+ new_path &&
+ new_path != group.path &&
+ group.has_container_repository_including_subgroups?
end
def container_images_error
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 3e17d75c02..8a79c5f889 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -355,7 +355,7 @@ class IssuableBaseService < BaseService
associations =
{
labels: issuable.labels.to_a,
- mentioned_users: issuable.mentioned_users.to_a,
+ mentioned_users: issuable.mentioned_users(current_user).to_a,
assignees: issuable.assignees.to_a
}
associations[:total_time_spent] = issuable.total_time_spent if issuable.respond_to?(:total_time_spent)
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index 528b1ea61b..b98a4d2567 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -61,8 +61,6 @@ module Issues
if added_mentions.present?
notification_service.async.new_mentions_in_issue(issue, added_mentions, current_user)
end
-
- ZoomNotesService.new(issue, project, current_user, old_description: old_associations[:description]).execute
end
def handle_task_changes(issuable)
diff --git a/app/services/issues/zoom_link_service.rb b/app/services/issues/zoom_link_service.rb
index 561c86475e..023d7080e8 100644
--- a/app/services/issues/zoom_link_service.rb
+++ b/app/services/issues/zoom_link_service.rb
@@ -6,32 +6,37 @@ module Issues
super(issue.project, user)
@issue = issue
+ @added_meeting = ZoomMeeting.canonical_meeting(@issue)
end
def add_link(link)
if can_add_link? && (link = parse_link(link))
- track_meeting_added_event
- success(_('Zoom meeting added'), append_to_description(link))
+ begin
+ add_zoom_meeting(link)
+ success(_('Zoom meeting added'))
+ rescue ActiveRecord::RecordNotUnique
+ error(_('Failed to add a Zoom meeting'))
+ end
else
error(_('Failed to add a Zoom meeting'))
end
end
- def can_add_link?
- can? && !link_in_issue_description?
- end
-
def remove_link
if can_remove_link?
- track_meeting_removed_event
- success(_('Zoom meeting removed'), remove_from_description)
+ remove_zoom_meeting
+ success(_('Zoom meeting removed'))
else
error(_('Failed to remove a Zoom meeting'))
end
end
+ def can_add_link?
+ can_update_issue? && !@added_meeting
+ end
+
def can_remove_link?
- can? && link_in_issue_description?
+ can_update_issue? && !!@added_meeting
end
def parse_link(link)
@@ -42,10 +47,6 @@ module Issues
attr_reader :issue
- def issue_description
- issue.description || ''
- end
-
def track_meeting_added_event
::Gitlab::Tracking.event('IncidentManagement::ZoomIntegration', 'add_zoom_meeting', label: 'Issue ID', value: issue.id)
end
@@ -54,39 +55,33 @@ module Issues
::Gitlab::Tracking.event('IncidentManagement::ZoomIntegration', 'remove_zoom_meeting', label: 'Issue ID', value: issue.id)
end
- def success(message, description)
- ServiceResponse
- .success(message: message, payload: { description: description })
+ def add_zoom_meeting(link)
+ ZoomMeeting.create(
+ issue: @issue,
+ project: @issue.project,
+ issue_status: :added,
+ url: link
+ )
+ track_meeting_added_event
+ SystemNoteService.zoom_link_added(@issue, @project, current_user)
+ end
+
+ def remove_zoom_meeting
+ @added_meeting.update(issue_status: :removed)
+ track_meeting_removed_event
+ SystemNoteService.zoom_link_removed(@issue, @project, current_user)
+ end
+
+ def success(message)
+ ServiceResponse.success(message: message)
end
def error(message)
ServiceResponse.error(message: message)
end
- def append_to_description(link)
- "#{issue_description}\n\n#{link}"
- end
-
- def remove_from_description
- link = parse_link(issue_description)
- return issue_description unless link
-
- issue_description.delete_suffix(link).rstrip
- end
-
- def link_in_issue_description?
- link = extract_link_from_issue_description
- return unless link
-
- Gitlab::ZoomLinkExtractor.new(link).match?
- end
-
- def extract_link_from_issue_description
- issue_description[/(\S+)\z/, 1]
- end
-
- def can?
- current_user.can?(:update_issue, project)
+ def can_update_issue?
+ can?(current_user, :update_issue, project)
end
end
end
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index aacc3d6831..00bf69739a 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -29,6 +29,19 @@ module MergeRequests
.execute_for_merge_request(merge_request)
end
+ def source_project
+ @source_project ||= merge_request.source_project
+ end
+
+ def target_project
+ @target_project ||= merge_request.target_project
+ end
+
+ # Don't try to print expensive instance variables.
+ def inspect
+ "#<#{self.class} #{merge_request.to_reference(full: true)}>"
+ end
+
private
def create(merge_request)
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index bf4da01723..456cc58947 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -16,6 +16,14 @@ module MergeRequests
merge_request.source_project = find_source_project
merge_request.target_project = find_target_project
+ # Source project sets the default source branch removal setting
+ merge_request.merge_params['force_remove_source_branch'] =
+ if params.key?(:force_remove_source_branch)
+ params.delete(:force_remove_source_branch)
+ else
+ merge_request.source_project.remove_source_branch_after_merge?
+ end
+
self.params = assign_allowed_merge_params(merge_request, params)
filter_params(merge_request)
diff --git a/app/services/merge_requests/ff_merge_service.rb b/app/services/merge_requests/ff_merge_service.rb
index 479e0fe669..6f1fa607ef 100644
--- a/app/services/merge_requests/ff_merge_service.rb
+++ b/app/services/merge_requests/ff_merge_service.rb
@@ -11,10 +11,16 @@ module MergeRequests
private
def commit
- repository.ff_merge(current_user,
- source,
- merge_request.target_branch,
- merge_request: merge_request)
+ ff_merge = repository.ff_merge(current_user,
+ source,
+ merge_request.target_branch,
+ merge_request: merge_request)
+
+ if merge_request.squash
+ merge_request.update_column(:squash_commit_sha, merge_request.in_progress_merge_commit_sha)
+ end
+
+ ff_merge
rescue Gitlab::Git::PreReceiveError => e
raise MergeError, e.message
rescue StandardError => e
diff --git a/app/services/merge_requests/merge_base_service.rb b/app/services/merge_requests/merge_base_service.rb
index 3f7f8bcdcb..27b5e31faa 100644
--- a/app/services/merge_requests/merge_base_service.rb
+++ b/app/services/merge_requests/merge_base_service.rb
@@ -19,10 +19,12 @@ module MergeRequests
end
def source
- if merge_request.squash
- squash_sha!
- else
- merge_request.diff_head_sha
+ strong_memoize(:source) do
+ if merge_request.squash
+ squash_sha!
+ else
+ merge_request.diff_head_sha
+ end
end
end
@@ -58,16 +60,14 @@ module MergeRequests
end
def squash_sha!
- strong_memoize(:squash_sha) do
- params[:merge_request] = merge_request
- squash_result = ::MergeRequests::SquashService.new(project, current_user, params).execute
+ params[:merge_request] = merge_request
+ squash_result = ::MergeRequests::SquashService.new(project, current_user, params).execute
- case squash_result[:status]
- when :success
- squash_result[:squash_sha]
- when :error
- raise ::MergeRequests::MergeService::MergeError, squash_result[:message]
- end
+ case squash_result[:status]
+ when :success
+ squash_result[:squash_sha]
+ when :error
+ raise ::MergeRequests::MergeService::MergeError, squash_result[:message]
end
end
end
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index 6309052244..a45b4f1142 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -37,6 +37,7 @@ module MergeRequests
def validate!
authorization_check!
error_check!
+ updated_check!
end
def authorization_check!
@@ -60,6 +61,15 @@ module MergeRequests
raise_error(error) if error
end
+ def updated_check!
+ return unless Feature.enabled?(:validate_merge_sha, merge_request.target_project, default_enabled: false)
+
+ unless source_matches?
+ raise_error('Branch has been updated since the merge was requested. '\
+ 'Please review the changes.')
+ end
+ end
+
def commit
log_info("Git merge started on JID #{merge_jid}")
commit_id = try_merge
@@ -125,5 +135,11 @@ module MergeRequests
def merge_request_info
merge_request.to_reference(full: true)
end
+
+ def source_matches?
+ # params-keys are symbols coming from the controller, but when they get
+ # loaded from the database they're strings
+ params.with_indifferent_access[:sha] == merge_request.diff_head_sha
+ end
end
end
diff --git a/app/services/merge_requests/push_options_handler_service.rb b/app/services/merge_requests/push_options_handler_service.rb
index 0168b31005..821558b8d6 100644
--- a/app/services/merge_requests/push_options_handler_service.rb
+++ b/app/services/merge_requests/push_options_handler_service.rb
@@ -4,14 +4,14 @@ module MergeRequests
class PushOptionsHandlerService
LIMIT = 10
- attr_reader :branches, :changes_by_branch, :current_user, :errors,
+ attr_reader :current_user, :errors, :changes,
:project, :push_options, :target_project
def initialize(project, current_user, changes, push_options)
@project = project
@target_project = @project.default_merge_request_target
@current_user = current_user
- @branches = get_branches(changes)
+ @changes = Gitlab::ChangesList.new(changes)
@push_options = push_options
@errors = []
end
@@ -34,8 +34,12 @@ module MergeRequests
private
- def get_branches(raw_changes)
- Gitlab::ChangesList.new(raw_changes).map do |changes|
+ def branches
+ changes_by_branch.keys
+ end
+
+ def changes_by_branch
+ @changes_by_branch ||= changes.each_with_object({}) do |changes, result|
next unless Gitlab::Git.branch_ref?(changes[:ref])
# Deleted branch
@@ -45,8 +49,8 @@ module MergeRequests
branch_name = Gitlab::Git.branch_name(changes[:ref])
next if branch_name == target_project.default_branch
- branch_name
- end.compact.uniq
+ result[branch_name] = changes
+ end
end
def validate_service
@@ -101,7 +105,7 @@ module MergeRequests
project,
current_user,
merge_request.attributes.merge(assignees: merge_request.assignees,
- label_ids: merge_request.label_ids)
+ label_ids: merge_request.label_ids)
).execute
end
@@ -112,7 +116,7 @@ module MergeRequests
merge_request = ::MergeRequests::UpdateService.new(
target_project,
current_user,
- update_params
+ update_params(merge_request)
).execute(merge_request)
collect_errors_from_merge_request(merge_request) unless merge_request.valid?
@@ -130,19 +134,22 @@ module MergeRequests
params.compact!
- if push_options.key?(:merge_when_pipeline_succeeds)
- params.merge!(
- merge_when_pipeline_succeeds: push_options[:merge_when_pipeline_succeeds],
- merge_user: current_user
- )
- end
-
params[:add_labels] = params.delete(:label).keys if params.has_key?(:label)
params[:remove_labels] = params.delete(:unlabel).keys if params.has_key?(:unlabel)
params
end
+ def merge_params(branch)
+ return {} unless push_options.key?(:merge_when_pipeline_succeeds)
+
+ {
+ merge_when_pipeline_succeeds: push_options[:merge_when_pipeline_succeeds],
+ merge_user: current_user,
+ sha: changes_by_branch.dig(branch, :newrev)
+ }
+ end
+
def create_params(branch)
params = base_params
@@ -153,13 +160,15 @@ module MergeRequests
target_project: target_project
)
+ params.merge!(merge_params(branch))
+
params[:target_branch] ||= target_project.default_branch
params
end
- def update_params
- base_params
+ def update_params(merge_request)
+ base_params.merge(merge_params(merge_request.source_branch))
end
def collect_errors_from_merge_request(merge_request)
diff --git a/app/services/merge_requests/rebase_service.rb b/app/services/merge_requests/rebase_service.rb
index 4d36dd4fea..7e9442c0c7 100644
--- a/app/services/merge_requests/rebase_service.rb
+++ b/app/services/merge_requests/rebase_service.rb
@@ -1,9 +1,13 @@
# frozen_string_literal: true
module MergeRequests
- class RebaseService < MergeRequests::WorkingCopyBaseService
+ class RebaseService < MergeRequests::BaseService
+ include Git::Logger
+
REBASE_ERROR = 'Rebase failed. Please rebase locally'
+ attr_reader :merge_request
+
def execute(merge_request)
@merge_request = merge_request
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index b32499629f..bd3fcf85a6 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -152,7 +152,8 @@ module MergeRequests
def abort_ff_merge_requests_with_when_pipeline_succeeds
return unless @project.ff_merge_must_be_possible?
- requests_with_auto_merge_enabled_to(@push.branch_name).each do |merge_request|
+ merge_requests_with_auto_merge_enabled_to(@push.branch_name).each do |merge_request|
+ next unless merge_request.auto_merge_strategy == AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS
next unless merge_request.should_be_rebased?
abort_auto_merge_with_todo(merge_request, 'target branch was updated')
@@ -167,11 +168,11 @@ module MergeRequests
todo_service.merge_request_became_unmergeable(merge_request)
end
- def requests_with_auto_merge_enabled_to(target_branch)
+ def merge_requests_with_auto_merge_enabled_to(target_branch)
@project
.merge_requests
.by_target_branch(target_branch)
- .with_open_merge_when_pipeline_succeeds
+ .with_auto_merge_enabled
end
def mark_pending_todos_done
diff --git a/app/services/merge_requests/squash_service.rb b/app/services/merge_requests/squash_service.rb
index 88ca3b4f5a..d25997c925 100644
--- a/app/services/merge_requests/squash_service.rb
+++ b/app/services/merge_requests/squash_service.rb
@@ -1,7 +1,9 @@
# frozen_string_literal: true
module MergeRequests
- class SquashService < MergeRequests::WorkingCopyBaseService
+ class SquashService < MergeRequests::BaseService
+ include Git::Logger
+
def execute
# If performing a squash would result in no change, then
# immediately return a success message without performing a squash
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index 7c9abb12b6..8a6a711950 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -88,9 +88,9 @@ module MergeRequests
merge_request.update(merge_error: nil)
if merge_request.head_pipeline && merge_request.head_pipeline.active?
- AutoMergeService.new(project, current_user).execute(merge_request, AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS)
+ AutoMergeService.new(project, current_user, { sha: last_diff_sha }).execute(merge_request, AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS)
else
- merge_request.merge_async(current_user.id, {})
+ merge_request.merge_async(current_user.id, { sha: last_diff_sha })
end
end
diff --git a/app/services/merge_requests/working_copy_base_service.rb b/app/services/merge_requests/working_copy_base_service.rb
deleted file mode 100644
index 2d2be1f4c2..0000000000
--- a/app/services/merge_requests/working_copy_base_service.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-# frozen_string_literal: true
-
-module MergeRequests
- class WorkingCopyBaseService < MergeRequests::BaseService
- attr_reader :merge_request
-
- def source_project
- @source_project ||= merge_request.source_project
- end
-
- def target_project
- @target_project ||= merge_request.target_project
- end
-
- def log_error(message, save_message_on_model: false)
- Gitlab::GitLogger.error("#{self.class.name} error (#{merge_request.to_reference(full: true)}): #{message}")
-
- merge_request.update(merge_error: message) if save_message_on_model
- end
-
- # Don't try to print expensive instance variables.
- def inspect
- "#<#{self.class} #{merge_request.to_reference(full: true)}>"
- end
- end
-end
diff --git a/app/services/metrics/dashboard/custom_metric_embed_service.rb b/app/services/metrics/dashboard/custom_metric_embed_service.rb
index 50f070989f..79a556b169 100644
--- a/app/services/metrics/dashboard/custom_metric_embed_service.rb
+++ b/app/services/metrics/dashboard/custom_metric_embed_service.rb
@@ -40,7 +40,7 @@ module Metrics
# All custom metrics are displayed on the system dashboard.
# Nil is acceptable as we'll default to the system dashboard.
def valid_dashboard?(dashboard)
- dashboard.nil? || SystemDashboardService.system_dashboard?(dashboard)
+ dashboard.nil? || ::Metrics::Dashboard::SystemDashboardService.system_dashboard?(dashboard)
end
end
@@ -77,15 +77,14 @@ module Metrics
# There may be multiple metrics, but they should be
# displayed in a single panel/chart.
# @return [ActiveRecord::AssociationRelation]
- # rubocop: disable CodeReuse/ActiveRecord
def metrics
- project.prometheus_metrics.where(
+ PrometheusMetricsFinder.new(
+ project: project,
group: group_key,
title: title,
y_label: y_label
- )
+ ).execute
end
- # rubocop: enable CodeReuse/ActiveRecord
# Returns a symbol representing the group that
# the dashboard's group title belongs to.
diff --git a/app/services/metrics/dashboard/grafana_metric_embed_service.rb b/app/services/metrics/dashboard/grafana_metric_embed_service.rb
new file mode 100644
index 0000000000..60591e9a6f
--- /dev/null
+++ b/app/services/metrics/dashboard/grafana_metric_embed_service.rb
@@ -0,0 +1,160 @@
+# frozen_string_literal: true
+
+# Responsible for returning a gitlab-compatible dashboard
+# containing info based on a grafana dashboard and datasource.
+#
+# Use Gitlab::Metrics::Dashboard::Finder to retrive dashboards.
+module Metrics
+ module Dashboard
+ class GrafanaMetricEmbedService < ::Metrics::Dashboard::BaseService
+ include ReactiveCaching
+
+ SEQUENCE = [
+ ::Gitlab::Metrics::Dashboard::Stages::GrafanaFormatter
+ ].freeze
+
+ self.reactive_cache_key = ->(service) { service.cache_key }
+ self.reactive_cache_lease_timeout = 30.seconds
+ self.reactive_cache_refresh_interval = 30.minutes
+ self.reactive_cache_lifetime = 30.minutes
+ self.reactive_cache_worker_finder = ->(_id, *args) { from_cache(*args) }
+
+ class << self
+ # Determines whether the provided params are sufficient
+ # to uniquely identify a grafana dashboard.
+ def valid_params?(params)
+ [
+ params[:embedded],
+ params[:grafana_url]
+ ].all?
+ end
+
+ def from_cache(project_id, user_id, grafana_url)
+ project = Project.find(project_id)
+ user = User.find(user_id)
+
+ new(project, user, grafana_url: grafana_url)
+ end
+ end
+
+ def get_dashboard
+ with_reactive_cache(*cache_key) { |result| result }
+ end
+
+ # Inherits the primary logic from the parent class and
+ # maintains the service's API while including ReactiveCache
+ def calculate_reactive_cache(*)
+ # This is called with explicit parentheses to prevent
+ # the params passed to #calculate_reactive_cache from
+ # being passed to #get_dashboard (which accepts none)
+ ::Metrics::Dashboard::BaseService
+ .instance_method(:get_dashboard)
+ .bind(self)
+ .call() # rubocop:disable Style/MethodCallWithoutArgsParentheses
+ end
+
+ def cache_key(*args)
+ [project.id, current_user.id, grafana_url]
+ end
+
+ # Required for ReactiveCaching; Usage overridden by
+ # self.reactive_cache_worker_finder
+ def id
+ nil
+ end
+
+ private
+
+ def get_raw_dashboard
+ raise MissingIntegrationError unless client
+
+ grafana_dashboard = fetch_dashboard
+ datasource = fetch_datasource(grafana_dashboard)
+
+ params.merge!(grafana_dashboard: grafana_dashboard, datasource: datasource)
+
+ {}
+ end
+
+ def fetch_dashboard
+ uid = GrafanaUidParser.new(grafana_url, project).parse
+ raise DashboardProcessingError.new('Dashboard uid not found') unless uid
+
+ response = client.get_dashboard(uid: uid)
+
+ parse_json(response.body)
+ end
+
+ def fetch_datasource(dashboard)
+ name = DatasourceNameParser.new(grafana_url, dashboard).parse
+ raise DashboardProcessingError.new('Datasource name not found') unless name
+
+ response = client.get_datasource(name: name)
+
+ parse_json(response.body)
+ end
+
+ def grafana_url
+ params[:grafana_url]
+ end
+
+ def client
+ project.grafana_integration&.client
+ end
+
+ def allowed?
+ Ability.allowed?(current_user, :read_project, project)
+ end
+
+ def sequence
+ SEQUENCE
+ end
+
+ def parse_json(json)
+ JSON.parse(json, symbolize_names: true)
+ rescue JSON::ParserError
+ raise DashboardProcessingError.new('Grafana response contains invalid json')
+ end
+ end
+
+ # Identifies the uid of the dashboard based on url format
+ class GrafanaUidParser
+ def initialize(grafana_url, project)
+ @grafana_url, @project = grafana_url, project
+ end
+
+ def parse
+ @grafana_url.match(uid_regex) { |m| m.named_captures['uid'] }
+ end
+
+ private
+
+ # URLs are expected to look like https://domain.com/d/:uid/other/stuff
+ def uid_regex
+ base_url = @project.grafana_integration.grafana_url.chomp('/')
+
+ %r{(#{Regexp.escape(base_url)}\/d\/(?\w+)\/)}x
+ end
+ end
+
+ # Identifies the name of the datasource for a dashboard
+ # based on the panelId query parameter found in the url
+ class DatasourceNameParser
+ def initialize(grafana_url, grafana_dashboard)
+ @grafana_url, @grafana_dashboard = grafana_url, grafana_dashboard
+ end
+
+ def parse
+ @grafana_dashboard[:dashboard][:panels]
+ .find { |panel| panel[:id].to_s == query_params[:panelId] }
+ .try(:[], :datasource)
+ end
+
+ private
+
+ def query_params
+ Gitlab::Metrics::Dashboard::Url.parse_query(@grafana_url)
+ end
+ end
+ end
+end
diff --git a/app/services/metrics/dashboard/project_dashboard_service.rb b/app/services/metrics/dashboard/project_dashboard_service.rb
index 756d387c0e..b0d54ee934 100644
--- a/app/services/metrics/dashboard/project_dashboard_service.rb
+++ b/app/services/metrics/dashboard/project_dashboard_service.rb
@@ -16,7 +16,8 @@ module Metrics
{
path: filepath,
display_name: name_for_path(filepath),
- default: false
+ default: false,
+ system_dashboard: false
}
end
end
diff --git a/app/services/metrics/dashboard/system_dashboard_service.rb b/app/services/metrics/dashboard/system_dashboard_service.rb
index ccfd9db874..f8dbb8a705 100644
--- a/app/services/metrics/dashboard/system_dashboard_service.rb
+++ b/app/services/metrics/dashboard/system_dashboard_service.rb
@@ -20,7 +20,8 @@ module Metrics
[{
path: SYSTEM_DASHBOARD_PATH,
display_name: SYSTEM_DASHBOARD_NAME,
- default: true
+ default: true,
+ system_dashboard: true
}]
end
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index c136803ef3..9e6cbfa06f 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -42,6 +42,10 @@ module Notes
clear_noteable_diffs_cache(note)
Suggestions::CreateService.new(note).execute
increment_usage_counter(note)
+
+ if Feature.enabled?(:notes_create_service_tracking, project)
+ Gitlab::Tracking.event('Notes::CreateService', 'execute', tracking_data_for(note))
+ end
end
if quick_actions_service.commands_executed_count.to_i > 0
@@ -59,5 +63,16 @@ module Notes
note
end
+
+ private
+
+ def tracking_data_for(note)
+ label = Gitlab.ee? && note.author == User.visual_review_bot ? 'anonymous_visual_review_note' : 'note'
+
+ {
+ label: label,
+ value: note.id
+ }
+ end
end
end
diff --git a/app/services/notes/post_process_service.rb b/app/services/notes/post_process_service.rb
index 48722cc2a7..53b3b57f4a 100644
--- a/app/services/notes/post_process_service.rb
+++ b/app/services/notes/post_process_service.rb
@@ -35,3 +35,5 @@ module Notes
end
end
end
+
+Notes::PostProcessService.prepend_if_ee('EE::Notes::PostProcessService')
diff --git a/app/services/notification_recipient_service.rb b/app/services/notification_recipient_service.rb
index 9afbb678f5..0bdf6a0e6b 100644
--- a/app/services/notification_recipient_service.rb
+++ b/app/services/notification_recipient_service.rb
@@ -181,7 +181,7 @@ module NotificationRecipientService
def add_subscribed_users
return unless target.respond_to? :subscribers
- add_recipients(target.subscribers(project), :subscription, nil)
+ add_recipients(target.subscribers(project), :subscription, NotificationReason::SUBSCRIBED)
end
# rubocop: disable CodeReuse/ActiveRecord
@@ -240,7 +240,7 @@ module NotificationRecipientService
return unless target.respond_to? :labels
(labels || target.labels).each do |label|
- add_recipients(label.subscribers(project), :subscription, nil)
+ add_recipients(label.subscribers(project), :subscription, NotificationReason::SUBSCRIBED)
end
end
end
diff --git a/app/services/preview_markdown_service.rb b/app/services/preview_markdown_service.rb
index 2b4c4ae68e..afe2651b11 100644
--- a/app/services/preview_markdown_service.rb
+++ b/app/services/preview_markdown_service.rb
@@ -16,8 +16,12 @@ class PreviewMarkdownService < BaseService
private
+ def quick_action_types
+ %w(Issue MergeRequest Commit)
+ end
+
def explain_quick_actions(text)
- return text, [] unless %w(Issue MergeRequest Commit).include?(target_type)
+ return text, [] unless quick_action_types.include?(target_type)
quick_actions_service = QuickActions::InterpretService.new(project, current_user)
quick_actions_service.explain(text, find_commands_target)
@@ -51,7 +55,7 @@ class PreviewMarkdownService < BaseService
def find_commands_target
QuickActions::TargetService
- .new(project, current_user)
+ .new(project, current_user, group: params[:group])
.execute(target_type, target_id)
end
@@ -63,3 +67,5 @@ class PreviewMarkdownService < BaseService
params[:target_id]
end
end
+
+PreviewMarkdownService.prepend_if_ee('EE::PreviewMarkdownService')
diff --git a/app/services/projects/container_repository/delete_tags_service.rb b/app/services/projects/container_repository/delete_tags_service.rb
index 5129e2269a..48bd9394dc 100644
--- a/app/services/projects/container_repository/delete_tags_service.rb
+++ b/app/services/projects/container_repository/delete_tags_service.rb
@@ -9,25 +9,11 @@ module Projects
tag_names = params[:tags]
return error('not tags specified') if tag_names.blank?
- if can_use?
- smart_delete(container_repository, tag_names)
- else
- unsafe_delete(container_repository, tag_names)
- end
+ smart_delete(container_repository, tag_names)
end
private
- def unsafe_delete(container_repository, tag_names)
- deleted_tags = tag_names.select do |tag_name|
- container_repository.tag(tag_name).unsafe_delete
- end
-
- return error('could not delete tags') if deleted_tags.empty?
-
- success(deleted: deleted_tags)
- end
-
# Replace a tag on the registry with a dummy tag.
# This is a hack as the registry doesn't support deleting individual
# tags. This code effectively pushes a dummy image and assigns the tag to it.
@@ -36,10 +22,18 @@ module Projects
def smart_delete(container_repository, tag_names)
# generates the blobs for the dummy image
dummy_manifest = container_repository.client.generate_empty_manifest(container_repository.path)
+ return error('could not generate manifest') if dummy_manifest.nil?
# update the manifests of the tags with the new dummy image
- tag_digests = tag_names.map do |name|
- container_repository.client.put_tag(container_repository.path, name, dummy_manifest)
+ deleted_tags = []
+ tag_digests = []
+
+ tag_names.each do |name|
+ digest = container_repository.client.put_tag(container_repository.path, name, dummy_manifest)
+ next unless digest
+
+ deleted_tags << name
+ tag_digests << digest
end
# make sure the digests are the same (it should always be)
@@ -51,16 +45,12 @@ module Projects
# Deletes the dummy image
# All created tag digests are the same since they all have the same dummy image.
# a single delete is sufficient to remove all tags with it
- if container_repository.delete_tag_by_digest(tag_digests.first)
- success(deleted: tag_names)
+ if tag_digests.any? && container_repository.delete_tag_by_digest(tag_digests.first)
+ success(deleted: deleted_tags)
else
error('could not delete tags')
end
end
-
- def can_use?
- Feature.enabled?(:container_registry_smart_delete, project, default_enabled: true)
- end
end
end
end
diff --git a/app/services/projects/hashed_storage/base_attachment_service.rb b/app/services/projects/hashed_storage/base_attachment_service.rb
index 828ab616ba..f8852c206e 100644
--- a/app/services/projects/hashed_storage/base_attachment_service.rb
+++ b/app/services/projects/hashed_storage/base_attachment_service.rb
@@ -16,6 +16,12 @@ module Projects
# Returns the logger currently in use
attr_reader :logger
+ def initialize(project:, old_disk_path:, logger: nil)
+ @project = project
+ @old_disk_path = old_disk_path
+ @logger = logger || Rails.logger # rubocop:disable Gitlab/RailsLogger
+ end
+
# Return whether this operation was skipped or not
#
# @return [Boolean] true if skipped of false otherwise
@@ -23,6 +29,14 @@ module Projects
@skipped
end
+ # Check if target path has discardable content
+ #
+ # @param [String] new_path
+ # @return [Boolean] whether we can discard the target path or not
+ def target_path_discardable?(new_path)
+ false
+ end
+
protected
def move_folder!(old_path, new_path)
@@ -34,8 +48,13 @@ module Projects
end
if File.exist?(new_path)
- logger.error("Cannot move attachments from '#{old_path}' to '#{new_path}', target path already exist (PROJECT_ID=#{project.id})")
- raise AttachmentCannotMoveError, "Target path '#{new_path}' already exists"
+ if target_path_discardable?(new_path)
+ discard_path!(new_path)
+ else
+ logger.error("Cannot move attachments from '#{old_path}' to '#{new_path}', target path already exist (PROJECT_ID=#{project.id})")
+
+ raise AttachmentCannotMoveError, "Target path '#{new_path}' already exists"
+ end
end
# Create base path folder on the new storage layout
@@ -46,6 +65,16 @@ module Projects
true
end
+
+ # Rename a path adding a suffix in order to prevent data-loss.
+ #
+ # @param [String] new_path
+ def discard_path!(new_path)
+ discarded_path = "#{new_path}-#{Time.now.utc.to_i}"
+
+ logger.info("Moving existing empty attachments folder from '#{new_path}' to '#{discarded_path}', (PROJECT_ID=#{project.id})")
+ FileUtils.mv(new_path, discarded_path)
+ end
end
end
end
diff --git a/app/services/projects/hashed_storage/base_repository_service.rb b/app/services/projects/hashed_storage/base_repository_service.rb
index b7e9d3e879..8b1bcaf17b 100644
--- a/app/services/projects/hashed_storage/base_repository_service.rb
+++ b/app/services/projects/hashed_storage/base_repository_service.rb
@@ -10,7 +10,7 @@ module Projects
attr_reader :old_disk_path, :new_disk_path, :old_wiki_disk_path, :old_storage_version, :logger, :move_wiki
- def initialize(project, old_disk_path, logger: nil)
+ def initialize(project:, old_disk_path:, logger: nil)
@project = project
@logger = logger || Gitlab::AppLogger
@old_disk_path = old_disk_path
diff --git a/app/services/projects/hashed_storage/migrate_attachments_service.rb b/app/services/projects/hashed_storage/migrate_attachments_service.rb
index 0cbff28310..3d9d03c4a9 100644
--- a/app/services/projects/hashed_storage/migrate_attachments_service.rb
+++ b/app/services/projects/hashed_storage/migrate_attachments_service.rb
@@ -3,18 +3,19 @@
module Projects
module HashedStorage
class MigrateAttachmentsService < BaseAttachmentService
- def initialize(project, old_disk_path, logger: nil)
- @project = project
- @logger = logger || Rails.logger # rubocop:disable Gitlab/RailsLogger
- @old_disk_path = old_disk_path
+ extend ::Gitlab::Utils::Override
+
+ # List of paths that can be excluded while evaluation if a target can be discarded
+ DISCARDABLE_PATHS = %w(tmp tmp/cache tmp/work).freeze
+
+ def initialize(project:, old_disk_path:, logger: nil)
+ super
+
@skipped = false
end
def execute
- origin = FileUploader.absolute_base_dir(project)
- # It's possible that old_disk_path does not match project.disk_path.
- # For example, that happens when we rename a project
- origin.sub!(/#{Regexp.escape(project.full_path)}\z/, old_disk_path)
+ origin = find_old_attachments_path(project)
project.storage_version = ::Project::HASHED_STORAGE_FEATURES[:attachments]
target = FileUploader.absolute_base_dir(project)
@@ -27,13 +28,38 @@ module Projects
project.save!(validate: false)
yield if block_given?
- else
- # Rollback changes
- project.rollback!
end
result
end
+
+ override :target_path_discardable?
+ # Check if target path has discardable content
+ #
+ # @param [String] new_path
+ # @return [Boolean] whether we can discard the target path or not
+ def target_path_discardable?(new_path)
+ return false unless File.directory?(new_path)
+
+ found = Dir.glob(File.join(new_path, '**', '**'))
+
+ (found - discardable_paths(new_path)).empty?
+ end
+
+ private
+
+ def discardable_paths(new_path)
+ DISCARDABLE_PATHS.collect { |path| File.join(new_path, path) }
+ end
+
+ def find_old_attachments_path(project)
+ origin = FileUploader.absolute_base_dir(project)
+
+ # It's possible that old_disk_path does not match project.disk_path.
+ # For example, that happens when we rename a project
+ #
+ origin.sub(/#{Regexp.escape(project.full_path)}\z/, old_disk_path)
+ end
end
end
end
diff --git a/app/services/projects/hashed_storage/migration_service.rb b/app/services/projects/hashed_storage/migration_service.rb
index f132dca61c..57a775a8f9 100644
--- a/app/services/projects/hashed_storage/migration_service.rb
+++ b/app/services/projects/hashed_storage/migration_service.rb
@@ -14,12 +14,12 @@ module Projects
def execute
# Migrate repository from Legacy to Hashed Storage
unless project.hashed_storage?(:repository)
- return false unless migrate_repository
+ return false unless migrate_repository_service.execute
end
# Migrate attachments from Legacy to Hashed Storage
unless project.hashed_storage?(:attachments)
- return false unless migrate_attachments
+ return false unless migrate_attachments_service.execute
end
true
@@ -27,12 +27,12 @@ module Projects
private
- def migrate_repository
- HashedStorage::MigrateRepositoryService.new(project, old_disk_path, logger: logger).execute
+ def migrate_repository_service
+ HashedStorage::MigrateRepositoryService.new(project: project, old_disk_path: old_disk_path, logger: logger)
end
- def migrate_attachments
- HashedStorage::MigrateAttachmentsService.new(project, old_disk_path, logger: logger).execute
+ def migrate_attachments_service
+ HashedStorage::MigrateAttachmentsService.new(project: project, old_disk_path: old_disk_path, logger: logger)
end
end
end
diff --git a/app/services/projects/hashed_storage/rollback_attachments_service.rb b/app/services/projects/hashed_storage/rollback_attachments_service.rb
index fb09eaa458..4bb8cb605a 100644
--- a/app/services/projects/hashed_storage/rollback_attachments_service.rb
+++ b/app/services/projects/hashed_storage/rollback_attachments_service.rb
@@ -3,14 +3,9 @@
module Projects
module HashedStorage
class RollbackAttachmentsService < BaseAttachmentService
- def initialize(project, logger: nil)
- @project = project
- @logger = logger || Rails.logger # rubocop:disable Gitlab/RailsLogger
- @old_disk_path = project.disk_path
- end
-
def execute
origin = FileUploader.absolute_base_dir(project)
+
project.storage_version = ::Project::HASHED_STORAGE_FEATURES[:repository]
target = FileUploader.absolute_base_dir(project)
diff --git a/app/services/projects/hashed_storage/rollback_service.rb b/app/services/projects/hashed_storage/rollback_service.rb
index ee41aae64a..c437001c44 100644
--- a/app/services/projects/hashed_storage/rollback_service.rb
+++ b/app/services/projects/hashed_storage/rollback_service.rb
@@ -5,32 +5,26 @@ module Projects
class RollbackService < BaseService
attr_reader :logger, :old_disk_path
- def initialize(project, old_disk_path, logger: nil)
- @project = project
- @old_disk_path = old_disk_path
- @logger = logger || Rails.logger # rubocop:disable Gitlab/RailsLogger
- end
-
def execute
# Rollback attachments from Hashed Storage to Legacy
if project.hashed_storage?(:attachments)
- return false unless rollback_attachments
+ return false unless rollback_attachments_service.execute
end
# Rollback repository from Hashed Storage to Legacy
if project.hashed_storage?(:repository)
- rollback_repository
+ rollback_repository_service.execute
end
end
private
- def rollback_attachments
- HashedStorage::RollbackAttachmentsService.new(project, logger: logger).execute
+ def rollback_attachments_service
+ HashedStorage::RollbackAttachmentsService.new(project: project, old_disk_path: old_disk_path, logger: logger)
end
- def rollback_repository
- HashedStorage::RollbackRepositoryService.new(project, old_disk_path, logger: logger).execute
+ def rollback_repository_service
+ HashedStorage::RollbackRepositoryService.new(project: project, old_disk_path: old_disk_path, logger: logger)
end
end
end
diff --git a/app/services/projects/import_export/export_service.rb b/app/services/projects/import_export/export_service.rb
index d3638c5755..8344397f67 100644
--- a/app/services/projects/import_export/export_service.rb
+++ b/app/services/projects/import_export/export_service.rb
@@ -24,7 +24,7 @@ module Projects
def save_all!
if save_exporters
- Gitlab::ImportExport::Saver.save(project: project, shared: shared)
+ Gitlab::ImportExport::Saver.save(exportable: project, shared: shared)
notify_success
else
cleanup_and_notify_error!
diff --git a/app/services/projects/lfs_pointers/lfs_link_service.rb b/app/services/projects/lfs_pointers/lfs_link_service.rb
index 38de2af9c1..a05c76f5e8 100644
--- a/app/services/projects/lfs_pointers/lfs_link_service.rb
+++ b/app/services/projects/lfs_pointers/lfs_link_service.rb
@@ -4,6 +4,9 @@
module Projects
module LfsPointers
class LfsLinkService < BaseService
+ TooManyOidsError = Class.new(StandardError)
+
+ MAX_OIDS = 100_000
BATCH_SIZE = 1000
# Accept an array of oids to link
@@ -12,6 +15,10 @@ module Projects
def execute(oids)
return [] unless project&.lfs_enabled?
+ if oids.size > MAX_OIDS
+ raise TooManyOidsError, 'Too many LFS object ids to link, please push them manually'
+ end
+
# Search and link existing LFS Object
link_existing_lfs_objects(oids)
end
@@ -20,22 +27,27 @@ module Projects
# rubocop: disable CodeReuse/ActiveRecord
def link_existing_lfs_objects(oids)
- all_existing_objects = []
+ linked_existing_objects = []
iterations = 0
- LfsObject.where(oid: oids).each_batch(of: BATCH_SIZE) do |existent_lfs_objects|
+ oids.each_slice(BATCH_SIZE) do |oids_batch|
+ # Load all existing LFS Objects immediately so we don't issue an extra
+ # query for the `.any?`
+ existent_lfs_objects = LfsObject.where(oid: oids_batch).load
next unless existent_lfs_objects.any?
+ rows = existent_lfs_objects
+ .not_linked_to_project(project)
+ .map { |existing_lfs_object| { project_id: project.id, lfs_object_id: existing_lfs_object.id } }
+ Gitlab::Database.bulk_insert(:lfs_objects_projects, rows)
iterations += 1
- not_linked_lfs_objects = existent_lfs_objects.where.not(id: project.all_lfs_objects)
- project.all_lfs_objects << not_linked_lfs_objects
- all_existing_objects += existent_lfs_objects.pluck(:oid)
+ linked_existing_objects += existent_lfs_objects.map(&:oid)
end
- log_lfs_link_results(all_existing_objects.count, iterations)
+ log_lfs_link_results(linked_existing_objects.count, iterations)
- all_existing_objects
+ linked_existing_objects
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index 525fc18b31..718416a03d 100644
--- a/app/services/projects/transfer_service.rb
+++ b/app/services/projects/transfer_service.rb
@@ -13,6 +13,8 @@ module Projects
include Gitlab::ShellAdapter
TransferError = Class.new(StandardError)
+ attr_reader :new_namespace
+
def execute(new_namespace)
@new_namespace = new_namespace
diff --git a/app/services/quick_actions/target_service.rb b/app/services/quick_actions/target_service.rb
index 69464c3c1a..4273acfbf8 100644
--- a/app/services/quick_actions/target_service.rb
+++ b/app/services/quick_actions/target_service.rb
@@ -32,3 +32,5 @@ module QuickActions
end
end
end
+
+QuickActions::TargetService.prepend_if_ee('EE::QuickActions::TargetService')
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index b3eee01ea7..25e3282d3f 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -128,82 +128,37 @@ module SystemNoteService
# Called when 'merge when pipeline succeeds' is executed
def merge_when_pipeline_succeeds(noteable, project, author, sha)
- body = "enabled an automatic merge when the pipeline for #{sha} succeeds"
-
- create_note(NoteSummary.new(noteable, project, author, body, action: 'merge'))
+ ::SystemNotes::MergeRequestsService.new(noteable: noteable, project: project, author: author).merge_when_pipeline_succeeds(sha)
end
# Called when 'merge when pipeline succeeds' is canceled
def cancel_merge_when_pipeline_succeeds(noteable, project, author)
- body = 'canceled the automatic merge'
-
- create_note(NoteSummary.new(noteable, project, author, body, action: 'merge'))
+ ::SystemNotes::MergeRequestsService.new(noteable: noteable, project: project, author: author).cancel_merge_when_pipeline_succeeds
end
# Called when 'merge when pipeline succeeds' is aborted
def abort_merge_when_pipeline_succeeds(noteable, project, author, reason)
- body = "aborted the automatic merge because #{reason}"
-
- ##
- # TODO: Abort message should be sent by the system, not a particular user.
- # See https://gitlab.com/gitlab-org/gitlab-foss/issues/63187.
- create_note(NoteSummary.new(noteable, project, author, body, action: 'merge'))
+ ::SystemNotes::MergeRequestsService.new(noteable: noteable, project: project, author: author).abort_merge_when_pipeline_succeeds(reason)
end
def handle_merge_request_wip(noteable, project, author)
- prefix = noteable.work_in_progress? ? "marked" : "unmarked"
-
- body = "#{prefix} as a **Work In Progress**"
-
- create_note(NoteSummary.new(noteable, project, author, body, action: 'title'))
+ ::SystemNotes::MergeRequestsService.new(noteable: noteable, project: project, author: author).handle_merge_request_wip
end
def add_merge_request_wip_from_commit(noteable, project, author, commit)
- body = "marked as a **Work In Progress** from #{commit.to_reference(project)}"
-
- create_note(NoteSummary.new(noteable, project, author, body, action: 'title'))
+ ::SystemNotes::MergeRequestsService.new(noteable: noteable, project: project, author: author).add_merge_request_wip_from_commit(commit)
end
def resolve_all_discussions(merge_request, project, author)
- body = "resolved all threads"
-
- create_note(NoteSummary.new(merge_request, project, author, body, action: 'discussion'))
+ ::SystemNotes::MergeRequestsService.new(noteable: merge_request, project: project, author: author).resolve_all_discussions
end
def discussion_continued_in_issue(discussion, project, author, issue)
- body = "created #{issue.to_reference} to continue this discussion"
- note_attributes = discussion.reply_attributes.merge(project: project, author: author, note: body)
-
- note = Note.create(note_attributes.merge(system: true, created_at: issue.system_note_timestamp))
- note.system_note_metadata = SystemNoteMetadata.new(action: 'discussion')
-
- note
+ ::SystemNotes::MergeRequestsService.new(project: project, author: author).discussion_continued_in_issue(discussion, issue)
end
def diff_discussion_outdated(discussion, project, author, change_position)
- merge_request = discussion.noteable
- diff_refs = change_position.diff_refs
- version_index = merge_request.merge_request_diffs.viewable.count
- position_on_text = change_position.on_text?
- text_parts = ["changed this #{position_on_text ? 'line' : 'file'} in"]
-
- if version_params = merge_request.version_params_for(diff_refs)
- repository = project.repository
- anchor = position_on_text ? change_position.line_code(repository) : change_position.file_hash
- url = url_helpers.diffs_project_merge_request_path(project, merge_request, version_params.merge(anchor: anchor))
-
- text_parts << "[version #{version_index} of the diff](#{url})"
- else
- text_parts << "version #{version_index} of the diff"
- end
-
- body = text_parts.join(' ')
- note_attributes = discussion.reply_attributes.merge(project: project, author: author, note: body)
-
- note = Note.create(note_attributes.merge(system: true))
- note.system_note_metadata = SystemNoteMetadata.new(action: 'outdated')
-
- note
+ ::SystemNotes::MergeRequestsService.new(project: project, author: author).diff_discussion_outdated(discussion, change_position)
end
def change_title(noteable, project, author, old_title)
@@ -233,9 +188,7 @@ module SystemNoteService
#
# Returns the created Note object
def change_branch(noteable, project, author, branch_type, old_branch, new_branch)
- body = "changed #{branch_type} branch from `#{old_branch}` to `#{new_branch}`"
-
- create_note(NoteSummary.new(noteable, project, author, body, action: 'branch'))
+ ::SystemNotes::MergeRequestsService.new(noteable: noteable, project: project, author: author).change_branch(branch_type, old_branch, new_branch)
end
# Called when a branch in Noteable is added or deleted
@@ -253,16 +206,7 @@ module SystemNoteService
#
# Returns the created Note object
def change_branch_presence(noteable, project, author, branch_type, branch, presence)
- verb =
- if presence == :add
- 'restored'
- else
- 'deleted'
- end
-
- body = "#{verb} #{branch_type} branch `#{branch}`"
-
- create_note(NoteSummary.new(noteable, project, author, body, action: 'branch'))
+ ::SystemNotes::MergeRequestsService.new(noteable: noteable, project: project, author: author).change_branch_presence(branch_type, branch, presence)
end
# Called when a branch is created from the 'new branch' button on a issue
@@ -270,18 +214,11 @@ module SystemNoteService
#
# "created branch `201-issue-branch-button`"
def new_issue_branch(issue, project, author, branch, branch_project: nil)
- branch_project ||= project
- link = url_helpers.project_compare_path(branch_project, from: branch_project.default_branch, to: branch)
-
- body = "created branch [`#{branch}`](#{link}) to address this issue"
-
- create_note(NoteSummary.new(issue, project, author, body, action: 'branch'))
+ ::SystemNotes::MergeRequestsService.new(noteable: issue, project: project, author: author).new_issue_branch(branch, branch_project: branch_project)
end
def new_merge_request(issue, project, author, merge_request)
- body = "created merge request #{merge_request.to_reference(project)} to address this issue"
-
- create_note(NoteSummary.new(issue, project, author, body, action: 'merge'))
+ ::SystemNotes::MergeRequestsService.new(noteable: issue, project: project, author: author).new_merge_request(merge_request)
end
def cross_reference(noteable, mentioner, author)
diff --git a/app/services/system_notes/merge_requests_service.rb b/app/services/system_notes/merge_requests_service.rb
new file mode 100644
index 0000000000..1d17f0ded5
--- /dev/null
+++ b/app/services/system_notes/merge_requests_service.rb
@@ -0,0 +1,145 @@
+# frozen_string_literal: true
+
+module SystemNotes
+ class MergeRequestsService < ::SystemNotes::BaseService
+ # Called when 'merge when pipeline succeeds' is executed
+ def merge_when_pipeline_succeeds(sha)
+ body = "enabled an automatic merge when the pipeline for #{sha} succeeds"
+
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'merge'))
+ end
+
+ # Called when 'merge when pipeline succeeds' is canceled
+ def cancel_merge_when_pipeline_succeeds
+ body = 'canceled the automatic merge'
+
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'merge'))
+ end
+
+ # Called when 'merge when pipeline succeeds' is aborted
+ def abort_merge_when_pipeline_succeeds(reason)
+ body = "aborted the automatic merge because #{reason}"
+
+ ##
+ # TODO: Abort message should be sent by the system, not a particular user.
+ # See https://gitlab.com/gitlab-org/gitlab-foss/issues/63187.
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'merge'))
+ end
+
+ def handle_merge_request_wip
+ prefix = noteable.work_in_progress? ? "marked" : "unmarked"
+
+ body = "#{prefix} as a **Work In Progress**"
+
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'title'))
+ end
+
+ def add_merge_request_wip_from_commit(commit)
+ body = "marked as a **Work In Progress** from #{commit.to_reference(project)}"
+
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'title'))
+ end
+
+ def resolve_all_discussions
+ body = "resolved all threads"
+
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'discussion'))
+ end
+
+ def discussion_continued_in_issue(discussion, issue)
+ body = "created #{issue.to_reference} to continue this discussion"
+ note_attributes = discussion.reply_attributes.merge(project: project, author: author, note: body)
+
+ Note.create(note_attributes.merge(system: true, created_at: issue.system_note_timestamp)).tap do |note|
+ note.system_note_metadata = SystemNoteMetadata.new(action: 'discussion')
+ end
+ end
+
+ def diff_discussion_outdated(discussion, change_position)
+ merge_request = discussion.noteable
+ diff_refs = change_position.diff_refs
+ version_index = merge_request.merge_request_diffs.viewable.count
+ position_on_text = change_position.on_text?
+ text_parts = ["changed this #{position_on_text ? 'line' : 'file'} in"]
+
+ if version_params = merge_request.version_params_for(diff_refs)
+ repository = project.repository
+ anchor = position_on_text ? change_position.line_code(repository) : change_position.file_hash
+ url = url_helpers.diffs_project_merge_request_path(project, merge_request, version_params.merge(anchor: anchor))
+
+ text_parts << "[version #{version_index} of the diff](#{url})"
+ else
+ text_parts << "version #{version_index} of the diff"
+ end
+
+ body = text_parts.join(' ')
+ note_attributes = discussion.reply_attributes.merge(project: project, author: author, note: body)
+
+ Note.create(note_attributes.merge(system: true)).tap do |note|
+ note.system_note_metadata = SystemNoteMetadata.new(action: 'outdated')
+ end
+ end
+
+ # Called when a branch in Noteable is changed
+ #
+ # branch_type - 'source' or 'target'
+ # old_branch - old branch name
+ # new_branch - new branch name
+ #
+ # Example Note text:
+ #
+ # "changed target branch from `Old` to `New`"
+ #
+ # Returns the created Note object
+ def change_branch(branch_type, old_branch, new_branch)
+ body = "changed #{branch_type} branch from `#{old_branch}` to `#{new_branch}`"
+
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'branch'))
+ end
+
+ # Called when a branch in Noteable is added or deleted
+ #
+ # branch_type - :source or :target
+ # branch - branch name
+ # presence - :add or :delete
+ #
+ # Example Note text:
+ #
+ # "restored target branch `feature`"
+ #
+ # Returns the created Note object
+ def change_branch_presence(branch_type, branch, presence)
+ verb =
+ if presence == :add
+ 'restored'
+ else
+ 'deleted'
+ end
+
+ body = "#{verb} #{branch_type} branch `#{branch}`"
+
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'branch'))
+ end
+
+ # Called when a branch is created from the 'new branch' button on a issue
+ # Example note text:
+ #
+ # "created branch `201-issue-branch-button`"
+ def new_issue_branch(branch, branch_project: nil)
+ branch_project ||= project
+ link = url_helpers.project_compare_path(branch_project, from: branch_project.default_branch, to: branch)
+
+ body = "created branch [`#{branch}`](#{link}) to address this issue"
+
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'branch'))
+ end
+
+ def new_merge_request(merge_request)
+ body = "created merge request #{merge_request.to_reference(project)} to address this issue"
+
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'merge'))
+ end
+ end
+end
+
+SystemNotes::MergeRequestsService.prepend_if_ee('::EE::SystemNotes::MergeRequestsService')
diff --git a/app/services/users/signup_service.rb b/app/services/users/signup_service.rb
new file mode 100644
index 0000000000..1031cec44c
--- /dev/null
+++ b/app/services/users/signup_service.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Users
+ class SignupService < BaseService
+ def initialize(current_user, params = {})
+ @user = current_user
+ @params = params.dup
+ end
+
+ def execute
+ assign_attributes
+ inject_validators
+
+ if @user.save
+ success
+ else
+ error(@user.errors.full_messages.join('. '))
+ end
+ end
+
+ private
+
+ def assign_attributes
+ @user.assign_attributes(params) unless params.empty?
+ end
+
+ def inject_validators
+ class << @user
+ validates :role, presence: true
+ validates :setup_for_company, inclusion: { in: [true, false], message: :blank }
+ end
+ end
+ end
+end
diff --git a/app/services/zoom_notes_service.rb b/app/services/zoom_notes_service.rb
deleted file mode 100644
index 983a7fcacd..0000000000
--- a/app/services/zoom_notes_service.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-# frozen_string_literal: true
-
-class ZoomNotesService
- def initialize(issue, project, current_user, old_description: nil)
- @issue = issue
- @project = project
- @current_user = current_user
- @old_description = old_description
- end
-
- def execute
- return if @issue.description == @old_description
-
- if zoom_link_added?
- zoom_link_added_notification
- elsif zoom_link_removed?
- zoom_link_removed_notification
- end
- end
-
- private
-
- def zoom_link_added?
- has_zoom_link?(@issue.description) && !has_zoom_link?(@old_description)
- end
-
- def zoom_link_removed?
- !has_zoom_link?(@issue.description) && has_zoom_link?(@old_description)
- end
-
- def has_zoom_link?(text)
- Gitlab::ZoomLinkExtractor.new(text).match?
- end
-
- def zoom_link_added_notification
- SystemNoteService.zoom_link_added(@issue, @project, @current_user)
- end
-
- def zoom_link_removed_notification
- SystemNoteService.zoom_link_removed(@issue, @project, @current_user)
- end
-end
diff --git a/app/validators/same_project_association_validator.rb b/app/validators/same_project_association_validator.rb
new file mode 100644
index 0000000000..2af2a21fa9
--- /dev/null
+++ b/app/validators/same_project_association_validator.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+# SameProjectAssociationValidator
+#
+# Custom validator to validate that the same project associated with
+# the record is also associated with the value
+#
+# Example:
+# class ZoomMeeting < ApplicationRecord
+# belongs_to :project, optional: false
+# belongs_to :issue, optional: false
+
+# validates :issue, same_project_association: true
+# end
+class SameProjectAssociationValidator < ActiveModel::EachValidator
+ def validate_each(record, attribute, value)
+ return if record.project == value&.project
+
+ record.errors[attribute] << 'must associate the same project'
+ end
+end
diff --git a/app/validators/zoom_url_validator.rb b/app/validators/zoom_url_validator.rb
new file mode 100644
index 0000000000..dc4ca6b950
--- /dev/null
+++ b/app/validators/zoom_url_validator.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+# ZoomUrlValidator
+#
+# Custom validator for zoom urls
+#
+class ZoomUrlValidator < ActiveModel::EachValidator
+ def validate_each(record, attribute, value)
+ return if Gitlab::ZoomLinkExtractor.new(value).links.size == 1
+
+ record.errors.add(:url, 'must contain one valid Zoom URL')
+ end
+end
diff --git a/app/views/admin/abuse_reports/index.html.haml b/app/views/admin/abuse_reports/index.html.haml
index cc29657a43..e3d78b3058 100644
--- a/app/views/admin/abuse_reports/index.html.haml
+++ b/app/views/admin/abuse_reports/index.html.haml
@@ -1,6 +1,18 @@
-- page_title 'Abuse Reports'
-%h3.page-title Abuse Reports
-%hr
+- page_title _('Abuse Reports')
+
+%h3.page-title= _('Abuse Reports')
+
+.row-content-block.second-block
+ = form_tag admin_abuse_reports_path, method: :get, class: 'filter-form' do
+ .filter-categories.flex-fill
+ .filter-item.inline
+ = dropdown_tag(user_dropdown_label(params[:user_id], 'User'),
+ options: { toggle_class: 'js-filter-submit js-user-search',
+ title: _('Filter by user'), filter: true, filterInput: 'input#user-search',
+ dropdown_class: 'dropdown-menu-selectable dropdown-menu-user js-filter-submit',
+ placeholder: _('Search users'),
+ data: { current_user: true, field_name: 'user_id' }})
+
.abuse-reports
- if @abuse_reports.present?
.table-holder
diff --git a/app/views/admin/application_settings/_ci_cd.html.haml b/app/views/admin/application_settings/_ci_cd.html.haml
index 1f5bce19bc..9806090c1a 100644
--- a/app/views/admin/application_settings/_ci_cd.html.haml
+++ b/app/views/admin/application_settings/_ci_cd.html.haml
@@ -53,5 +53,11 @@
= s_('AdminSettings|Environment variables are protected by default')
.form-text.text-muted
= s_('AdminSettings|When creating a new environment variable it will be protected by default.')
+ .form-group
+ = f.label :ci_config_path, _('Default CI configuration path'), class: 'label-bold'
+ = f.text_field :default_ci_config_path, class: 'form-control', placeholder: '.gitlab-ci.yml'
+ %p.form-text.text-muted
+ = _("The default CI configuration path for new projects.").html_safe
+ = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'custom-ci-config-path'), target: '_blank'
= f.submit _('Save changes'), class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_eks.html.haml b/app/views/admin/application_settings/_eks.html.haml
new file mode 100644
index 0000000000..b1f7ed7628
--- /dev/null
+++ b/app/views/admin/application_settings/_eks.html.haml
@@ -0,0 +1,31 @@
+- expanded = integration_expanded?('eks_')
+%section.settings.as-eks.no-animate#js-eks-settings{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4
+ = _('Amazon EKS')
+ %button.btn.js-settings-toggle{ type: 'button' }
+ = expanded ? 'Collapse' : 'Expand'
+ %p
+ = _('Amazon EKS integration allows you to provision EKS clusters from GitLab.')
+
+ .settings-content
+ = form_for @application_setting, url: integrations_admin_application_settings_path(anchor: 'js-eks-settings'), html: { class: 'fieldset-form' } do |f|
+ = form_errors(@application_setting)
+
+ %fieldset
+ .form-group
+ .form-check
+ = f.check_box :eks_integration_enabled, class: 'form-check-input'
+ = f.label :eks_integration_enabled, class: 'form-check-label' do
+ Enable Amazon EKS integration
+ .form-group
+ = f.label :eks_account_id, 'Account ID', class: 'label-bold'
+ = f.text_field :eks_account_id, class: 'form-control'
+ .form-group
+ = f.label :eks_access_key_id, 'Access key ID', class: 'label-bold'
+ = f.text_field :eks_access_key_id, class: 'form-control'
+ .form-group
+ = f.label :eks_secret_access_key, 'Secret access key', class: 'label-bold'
+ = f.password_field :eks_secret_access_key, value: @application_setting.eks_secret_access_key, class: 'form-control'
+
+ = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_outbound.html.haml b/app/views/admin/application_settings/_outbound.html.haml
index ad26f52aea..42528f4012 100644
--- a/app/views/admin/application_settings/_outbound.html.haml
+++ b/app/views/admin/application_settings/_outbound.html.haml
@@ -4,7 +4,7 @@
%fieldset
.form-group
.form-check
- = f.check_box :allow_local_requests_from_web_hooks_and_services, class: 'form-check-input'
+ = f.check_box :allow_local_requests_from_web_hooks_and_services, class: 'form-check-input', data: { qa_selector: 'allow_requests_from_services_checkbox' }
= f.label :allow_local_requests_from_web_hooks_and_services, class: 'form-check-label' do
= _('Allow requests to the local network from web hooks and services')
.form-check
@@ -27,4 +27,4 @@
%span.form-text.text-muted
= _('Resolves IP addresses once and uses them to submit requests')
- = f.submit 'Save changes', class: "btn btn-success"
+ = f.submit 'Save changes', class: "btn btn-success", data: { qa_selector: 'save_changes_button' }
diff --git a/app/views/admin/application_settings/_plantuml.html.haml b/app/views/admin/application_settings/_plantuml.html.haml
index 86dc289dd7..d35774d330 100644
--- a/app/views/admin/application_settings/_plantuml.html.haml
+++ b/app/views/admin/application_settings/_plantuml.html.haml
@@ -1,18 +1,27 @@
-= form_for @application_setting, url: integrations_admin_application_settings_path(anchor: 'js-plantuml-settings'), html: { class: 'fieldset-form' } do |f|
- = form_errors(@application_setting)
+- expanded = integration_expanded?('plantuml_')
+%section.settings.as-plantuml.no-animate#js-plantuml-settings{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4
+ = _('PlantUML')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded ? _('Collapse') : _('Expand')
+ %p
+ = _('Allow rendering of PlantUML diagrams in Asciidoc documents.')
+ .settings-content
+ = form_for @application_setting, url: integrations_admin_application_settings_path(anchor: 'js-plantuml-settings'), html: { class: 'fieldset-form' } do |f|
+ = form_errors(@application_setting) if expanded
- %fieldset
- .form-group
- .form-check
- = f.check_box :plantuml_enabled, class: 'form-check-input'
- = f.label :plantuml_enabled, class: 'form-check-label' do
- Enable PlantUML
- .form-group
- = f.label :plantuml_url, 'PlantUML URL', class: 'label-bold'
- = f.text_field :plantuml_url, class: 'form-control', placeholder: 'http://gitlab.your-plantuml-instance.com:8080'
- .form-text.text-muted
- Allow rendering of
- = link_to "PlantUML", "http://plantuml.com"
- diagrams in Asciidoc documents using an external PlantUML service.
+ %fieldset
+ .form-group
+ .form-check
+ = f.check_box :plantuml_enabled, class: 'form-check-input'
+ = f.label :plantuml_enabled, _('Enable PlantUML'), class: 'form-check-label'
+ .form-group
+ = f.label :plantuml_url, 'PlantUML URL', class: 'label-bold'
+ = f.text_field :plantuml_url, class: 'form-control', placeholder: 'http://gitlab.your-plantuml-instance.com:8080'
+ .form-text.text-muted
+ Allow rendering of
+ = link_to "PlantUML", "http://plantuml.com"
+ diagrams in Asciidoc documents using an external PlantUML service.
- = f.submit 'Save changes', class: "btn btn-success"
+ = f.submit _('Save changes'), class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_repository_mirrors_form.html.haml b/app/views/admin/application_settings/_repository_mirrors_form.html.haml
index 362f4a4246..6e5fa6eb62 100644
--- a/app/views/admin/application_settings/_repository_mirrors_form.html.haml
+++ b/app/views/admin/application_settings/_repository_mirrors_form.html.haml
@@ -7,9 +7,9 @@
.form-check
= f.check_box :mirror_available, class: 'form-check-input'
= f.label :mirror_available, class: 'form-check-label' do
- = _('Allow mirrors to be set up for projects')
+ = _('Allow repository mirroring to be configured by project maintainers')
%span.form-text.text-muted
- = _('If disabled, only admins will be able to set up mirrors in projects.')
+ = _('If disabled, only admins will be able to configure repository mirroring.')
= link_to icon('question-circle'), help_page_path('workflow/repository_mirroring')
= render_if_exists 'admin/application_settings/mirror_settings', form: f
diff --git a/app/views/admin/application_settings/_snowplow.html.haml b/app/views/admin/application_settings/_snowplow.html.haml
index 31fd12d191..a259743327 100644
--- a/app/views/admin/application_settings/_snowplow.html.haml
+++ b/app/views/admin/application_settings/_snowplow.html.haml
@@ -1,4 +1,4 @@
-- expanded = true if !@application_setting.valid? && @application_setting.errors.any? { |k| k.to_s.start_with?('snowplow_') }
+- expanded = integration_expanded?('snowplow_')
%section.settings.as-snowplow.no-animate#js-snowplow-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
@@ -10,7 +10,7 @@
.settings-content
= form_for @application_setting, url: integrations_admin_application_settings_path(anchor: 'js-snowplow-settings'), html: { class: 'fieldset-form' } do |f|
- = form_errors(@application_setting)
+ = form_errors(@application_setting) if expanded
%fieldset
.form-group
@@ -21,10 +21,13 @@
= f.label :snowplow_collector_hostname, _('Collector hostname'), class: 'label-light'
= f.text_field :snowplow_collector_hostname, class: 'form-control', placeholder: 'snowplow.example.com'
.form-group
- = f.label :snowplow_site_id, _('Site ID'), class: 'label-light'
- = f.text_field :snowplow_site_id, class: 'form-control'
+ = f.label :snowplow_app_id, _('App ID'), class: 'label-light'
+ = f.text_field :snowplow_app_id, class: 'form-control'
.form-group
= f.label :snowplow_cookie_domain, _('Cookie domain'), class: 'label-light'
= f.text_field :snowplow_cookie_domain, class: 'form-control'
+ .form-group
+ = f.label :snowplow_iglu_registry_url, _('Iglu registry URL (optional)'), class: 'label-light'
+ = f.text_field :snowplow_iglu_registry_url, class: 'form-control'
= f.submit _('Save changes'), class: 'btn btn-success'
diff --git a/app/views/admin/application_settings/_sourcegraph.html.haml b/app/views/admin/application_settings/_sourcegraph.html.haml
new file mode 100644
index 0000000000..23cda0334a
--- /dev/null
+++ b/app/views/admin/application_settings/_sourcegraph.html.haml
@@ -0,0 +1,38 @@
+- return unless Gitlab::Sourcegraph.feature_available?
+- expanded = integration_expanded?('sourcegraph_')
+
+%section.settings.as-sourcegraph.no-animate#js-sourcegraph-settings{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4
+ = _('Sourcegraph')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded ? _('Collapse') : _('Expand')
+ %p
+ - link_start = ''.html_safe % { url: 'https://sourcegraph.com/' }
+ - link_end = "#{sprite_icon('external-link', size: 12, css_class: 'ml-1 vertical-align-center')} ".html_safe
+ = s_('SourcegraphAdmin|Enable code intelligence powered by %{link_start}Sourcegraph%{link_end} on your GitLab instance\'s code views and merge requests.').html_safe % { link_start: link_start, link_end: link_end }
+ %span
+ = link_to s_('SourcegraphAdmin|More information'), help_page_path('integration/sourcegraph.md'), target: '_blank'
+
+
+ .settings-content
+ = form_for @application_setting, url: integrations_admin_application_settings_path(anchor: 'js-sourcegraph-settings'), html: { class: 'fieldset-form' } do |f|
+ = form_errors(@application_setting)
+
+ %fieldset
+ .form-group
+ .form-check
+ = f.check_box :sourcegraph_enabled, class: 'form-check-input'
+ = f.label :sourcegraph_enabled, s_('SourcegraphAdmin|Enable Sourcegraph'), class: 'form-check-label'
+ .form-group
+ .form-check
+ = f.check_box :sourcegraph_public_only, class: 'form-check-input'
+ = f.label :sourcegraph_public_only, s_('SourcegraphAdmin|Block on private and internal projects'), class: 'form-check-label'
+ .form-text.text-muted
+ = s_('SourcegraphAdmin|If checked, only public projects will have code intelligence and communicate with Sourcegraph.')
+ .form-group
+ = f.label :sourcegraph_url, s_('SourcegraphAdmin|Sourcegraph URL'), class: 'label-bold'
+ = f.text_field :sourcegraph_url, class: 'form-control', placeholder: s_('SourcegraphAdmin|e.g. https://sourcegraph.example.com')
+ .form-text.text-muted
+ = s_('SourcegraphAdmin|Configure the URL to a Sourcegraph instance which can read your GitLab projects.')
+ = f.submit s_('SourcegraphAdmin|Save changes'), class: 'btn btn-success'
diff --git a/app/views/admin/application_settings/_third_party_offers.html.haml b/app/views/admin/application_settings/_third_party_offers.html.haml
index adde09f75e..256b1f74bf 100644
--- a/app/views/admin/application_settings/_third_party_offers.html.haml
+++ b/app/views/admin/application_settings/_third_party_offers.html.haml
@@ -1,13 +1,21 @@
-- application_setting = local_assigns.fetch(:application_setting)
+- expanded = integration_expanded?('hide_third_party_')
+%section.settings.as-third-party-offers.no-animate#js-third-party-offers-settings{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4
+ = _('Third party offers')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded ? _('Collapse') : _('Expand')
+ %p
+ = _('Control the display of third party offers.')
+ .settings-content
-= form_for application_setting, url: integrations_admin_application_settings_path(anchor: 'js-third-party-offers-settings'), html: { class: 'fieldset-form' } do |f|
- = form_errors(application_setting)
+ = form_for @application_setting, url: integrations_admin_application_settings_path(anchor: 'js-third-party-offers-settings'), html: { class: 'fieldset-form' } do |f|
+ = form_errors(@application_setting) if expanded
- %fieldset
- .form-group
- .form-check
- = f.check_box :hide_third_party_offers, class: 'form-check-input'
- = f.label :hide_third_party_offers, class: 'form-check-label' do
- Do not display offers from third parties within GitLab
+ %fieldset
+ .form-group
+ .form-check
+ = f.check_box :hide_third_party_offers, class: 'form-check-input'
+ = f.label :hide_third_party_offers, _('Do not display offers from third parties within GitLab'), class: 'form-check-label'
- = f.submit 'Save changes', class: "btn btn-success"
+ = f.submit _('Save changes'), class: "btn btn-success"
diff --git a/app/views/admin/application_settings/integrations.html.haml b/app/views/admin/application_settings/integrations.html.haml
index 310e86b137..0aa833e49a 100644
--- a/app/views/admin/application_settings/integrations.html.haml
+++ b/app/views/admin/application_settings/integrations.html.haml
@@ -2,30 +2,11 @@
- page_title _("Integrations")
- @content_class = "limit-container-width" unless fluid_layout
-= render_if_exists 'admin/application_settings/elasticsearch_form', expanded: expanded_by_default?
+= render_if_exists 'admin/application_settings/elasticsearch_form'
+= render 'admin/application_settings/plantuml'
+= render 'admin/application_settings/sourcegraph'
+= render_if_exists 'admin/application_settings/slack'
+= render 'admin/application_settings/third_party_offers'
+= render 'admin/application_settings/snowplow'
+= render 'admin/application_settings/eks' if Feature.enabled?(:create_eks_clusters)
-%section.settings.as-plantuml.no-animate#js-plantuml-settings{ class: ('expanded' if expanded_by_default?) }
- .settings-header
- %h4
- = _('PlantUML')
- %button.btn.btn-default.js-settings-toggle{ type: 'button' }
- = expanded_by_default? ? _('Collapse') : _('Expand')
- %p
- = _('Allow rendering of PlantUML diagrams in Asciidoc documents.')
- .settings-content
- = render 'plantuml'
-
-= render_if_exists 'admin/application_settings/slack', expanded: expanded_by_default?
-
-%section.settings.as-third-party-offers.no-animate#js-third-party-offers-settings{ class: ('expanded' if expanded_by_default?) }
- .settings-header
- %h4
- = _('Third party offers')
- %button.btn.btn-default.js-settings-toggle{ type: 'button' }
- = expanded_by_default? ? _('Collapse') : _('Expand')
- %p
- = _('Control the display of third party offers.')
- .settings-content
- = render 'third_party_offers', application_setting: @application_setting
-
-= render_if_exists 'admin/application_settings/snowplow', expanded: expanded_by_default?
diff --git a/app/views/admin/application_settings/network.html.haml b/app/views/admin/application_settings/network.html.haml
index 092834b993..7bd5117219 100644
--- a/app/views/admin/application_settings/network.html.haml
+++ b/app/views/admin/application_settings/network.html.haml
@@ -24,7 +24,7 @@
.settings-content
= render 'ip_limits'
-%section.settings.as-outbound.no-animate#js-outbound-settings{ class: ('expanded' if expanded_by_default?) }
+%section.settings.as-outbound.no-animate#js-outbound-settings{ class: ('expanded' if expanded_by_default?), data: { qa_selector: 'outbound_requests_section' } }
.settings-header
%h4
= _('Outbound requests')
diff --git a/app/views/admin/application_settings/repository.html.haml b/app/views/admin/application_settings/repository.html.haml
index 25f8b6541b..b0934a9d9f 100644
--- a/app/views/admin/application_settings/repository.html.haml
+++ b/app/views/admin/application_settings/repository.html.haml
@@ -5,11 +5,11 @@
%section.settings.as-mirror.no-animate#js-mirror-settings{ class: ('expanded' if expanded_by_default?) }
.settings-header
%h4
- = _('Repository mirror')
+ = _('Repository mirroring')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded_by_default? ? 'Collapse' : 'Expand'
%p
- = _('Configure push mirrors.')
+ = _('Configure repository mirroring.')
.settings-content
= render partial: 'repository_mirrors_form'
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 41147950c4..e5a3c0df9b 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -41,17 +41,38 @@
.info-well
.well-segment.admin-well.admin-well-features
%h4 Features
- = feature_entry(_('Sign up'), href: admin_application_settings_path(anchor: 'js-signup-settings'), enabled: allow_signup?)
- = feature_entry(_('LDAP'), enabled: Gitlab.config.ldap.enabled)
- = feature_entry(_('Gravatar'), href: admin_application_settings_path(anchor: 'js-account-settings'), enabled: gravatar_enabled?)
- = feature_entry(_('OmniAuth'), href: admin_application_settings_path(anchor: 'js-signin-settings'), enabled: Gitlab::Auth.omniauth_enabled?)
- = feature_entry(_('Reply by email'), enabled: Gitlab::IncomingEmail.enabled?)
+ = feature_entry(_('Sign up'),
+ href: admin_application_settings_path(anchor: 'js-signup-settings'),
+ enabled: allow_signup?)
+
+ = feature_entry(_('LDAP'),
+ enabled: Gitlab.config.ldap.enabled)
+
+ = feature_entry(_('Gravatar'),
+ href: admin_application_settings_path(anchor: 'js-account-settings'),
+ enabled: gravatar_enabled?)
+
+ = feature_entry(_('OmniAuth'),
+ href: admin_application_settings_path(anchor: 'js-signin-settings'),
+ enabled: Gitlab::Auth.omniauth_enabled?)
+
+ = feature_entry(_('Reply by email'),
+ enabled: Gitlab::IncomingEmail.enabled?)
= render_if_exists 'admin/dashboard/elastic_and_geo'
- = feature_entry(_('Container Registry'), href: ci_cd_admin_application_settings_path(anchor: 'js-registry-settings'), enabled: Gitlab.config.registry.enabled)
- = feature_entry(_('Gitlab Pages'), href: help_instance_configuration_url, enabled: Gitlab.config.pages.enabled)
- = feature_entry(_('Shared Runners'), href: admin_runners_path, enabled: Gitlab.config.gitlab_ci.shared_runners_enabled)
+ = feature_entry(_('Container Registry'),
+ href: ci_cd_admin_application_settings_path(anchor: 'js-registry-settings'),
+ enabled: Gitlab.config.registry.enabled,
+ doc_href: help_page_path('user/packages/container_registry/index'))
+
+ = feature_entry(_('Gitlab Pages'),
+ enabled: Gitlab.config.pages.enabled,
+ doc_href: help_instance_configuration_url)
+
+ = feature_entry(_('Shared Runners'),
+ href: admin_runners_path,
+ enabled: Gitlab.config.gitlab_ci.shared_runners_enabled)
.col-md-4
.info-well
.well-segment.admin-well
diff --git a/app/views/admin/sessions/_new_base.html.haml b/app/views/admin/sessions/_new_base.html.haml
index 55aea0296e..3d77a439d6 100644
--- a/app/views/admin/sessions/_new_base.html.haml
+++ b/app/views/admin/sessions/_new_base.html.haml
@@ -4,4 +4,4 @@
= password_field_tag :password, nil, class: 'form-control', required: true, title: _('This field is required.'), data: { qa_selector: 'password_field' }
.submit-container.move-submit-down
- = submit_tag _('Enter admin mode'), class: 'btn btn-success', data: { qa_selector: 'sign_in_button' }
+ = submit_tag _('Enter Admin Mode'), class: 'btn btn-success', data: { qa_selector: 'sign_in_button' }
diff --git a/app/views/admin/sessions/_signin_box.html.haml b/app/views/admin/sessions/_signin_box.html.haml
index 69baa76060..1d19915d3c 100644
--- a/app/views/admin/sessions/_signin_box.html.haml
+++ b/app/views/admin/sessions/_signin_box.html.haml
@@ -1,4 +1,4 @@
-- if form_based_providers.any?
+- if any_form_based_providers_enabled?
- if password_authentication_enabled_for_web?
.login-box.tab-pane{ id: 'login-pane', role: 'tabpanel' }
diff --git a/app/views/admin/sessions/_tabs_normal.html.haml b/app/views/admin/sessions/_tabs_normal.html.haml
index f5dedb5ad7..20830051d3 100644
--- a/app/views/admin/sessions/_tabs_normal.html.haml
+++ b/app/views/admin/sessions/_tabs_normal.html.haml
@@ -1,3 +1,3 @@
%ul.nav-links.new-session-tabs.nav-tabs.nav{ role: 'tablist' }
%li.nav-item{ role: 'presentation' }
- %a.nav-link.active{ href: '#login-pane', data: { toggle: 'tab', qa_selector: 'sign_in_tab' }, role: 'tab' }= _('Enter admin mode')
+ %a.nav-link.active{ href: '#login-pane', data: { toggle: 'tab', qa_selector: 'sign_in_tab' }, role: 'tab' }= _('Enter Admin Mode')
diff --git a/app/views/admin/sessions/new.html.haml b/app/views/admin/sessions/new.html.haml
index ee06b4a174..73028e78ea 100644
--- a/app/views/admin/sessions/new.html.haml
+++ b/app/views/admin/sessions/new.html.haml
@@ -1,5 +1,5 @@
- @hide_breadcrumbs = true
-- page_title _('Enter admin mode')
+- page_title _('Enter Admin Mode')
.row.justify-content-center
.col-6.new-session-forms-container
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index 706fa033c5..cd07fee8e5 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -152,7 +152,7 @@
- email = " (#{@user.unconfirmed_email})"
%p This user has an unconfirmed email address#{email}. You may force a confirmation.
%br
- = link_to 'Confirm user', confirm_admin_user_path(@user), method: :put, class: "btn btn-info", data: { confirm: 'Are you sure?' }
+ = link_to 'Confirm user', confirm_admin_user_path(@user), method: :put, class: "btn btn-info", data: { confirm: 'Are you sure?', qa_selector: 'confirm_user_button' }
= render_if_exists 'admin/users/user_detail_note'
diff --git a/app/views/ci/group_variables/_content.html.haml b/app/views/ci/group_variables/_content.html.haml
new file mode 100644
index 0000000000..db5f1021f5
--- /dev/null
+++ b/app/views/ci/group_variables/_content.html.haml
@@ -0,0 +1 @@
+= _("These variables are configured in the parent group settings, and will be active in the current project in addition to the project variables.")
diff --git a/app/views/ci/group_variables/_header.html.haml b/app/views/ci/group_variables/_header.html.haml
new file mode 100644
index 0000000000..71d123ec9f
--- /dev/null
+++ b/app/views/ci/group_variables/_header.html.haml
@@ -0,0 +1,5 @@
+%h5
+ = _('Group variables (inherited)')
+
+%p
+ = render "ci/group_variables/content"
diff --git a/app/views/ci/group_variables/_index.html.haml b/app/views/ci/group_variables/_index.html.haml
new file mode 100644
index 0000000000..c350ba5caf
--- /dev/null
+++ b/app/views/ci/group_variables/_index.html.haml
@@ -0,0 +1,13 @@
+- variables = @project.group.self_and_ancestors.map(&:variables).flatten
+
+.row
+ .col-lg-12
+ .group-variable-list
+ = render 'ci/group_variables/variable_header'
+ - variables.each do |variable|
+ .group-variable-row.d-flex.w-100.border-bottom.pt-2.pb-2
+ .table-section.section-40.append-right-10.key
+ = variable.key
+ .table-section.section-40.append-right-10
+ %a.group-origin-link{ href: group_settings_ci_cd_path(variable.group) }
+ = variable.group.name
diff --git a/app/views/ci/group_variables/_variable_header.html.haml b/app/views/ci/group_variables/_variable_header.html.haml
new file mode 100644
index 0000000000..1a3168cf78
--- /dev/null
+++ b/app/views/ci/group_variables/_variable_header.html.haml
@@ -0,0 +1,5 @@
+.group-variable-keys.d-flex.w-100.align-items-center.pb-2.border-bottom
+ .bold.table-section.section-40.append-right-10
+ = s_('Key')
+ .bold.table-section.section-40.append-right-10
+ = s_('Origin')
diff --git a/app/views/ci/variables/_header.html.haml b/app/views/ci/variables/_header.html.haml
index dbfa0a9e5a..ce4dd5a487 100644
--- a/app/views/ci/variables/_header.html.haml
+++ b/app/views/ci/variables/_header.html.haml
@@ -7,5 +7,5 @@
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
-%p.append-bottom-0
+%p
= render "ci/variables/content"
diff --git a/app/views/ci/variables/_index.html.haml b/app/views/ci/variables/_index.html.haml
index 94102b4dcd..7ae5c48b93 100644
--- a/app/views/ci/variables/_index.html.haml
+++ b/app/views/ci/variables/_index.html.haml
@@ -24,3 +24,8 @@
= n_('Hide value', 'Hide values', @variables.size)
- else
= n_('Reveal value', 'Reveal values', @variables.size)
+ - if !@group && @project.group
+ .settings-header.border-top.prepend-top-20
+ = render 'ci/group_variables/header'
+ .settings-content.pr-0
+ = render 'ci/group_variables/index'
diff --git a/app/views/ci/variables/_url_query_variable_row.html.haml b/app/views/ci/variables/_url_query_variable_row.html.haml
new file mode 100644
index 0000000000..6672a8e5ea
--- /dev/null
+++ b/app/views/ci/variables/_url_query_variable_row.html.haml
@@ -0,0 +1,28 @@
+- form_field = local_assigns.fetch(:form_field, nil)
+- variable = local_assigns.fetch(:variable, nil)
+
+- key = variable[0]
+- value = variable[1]
+- variable_type = variable[2] || "env_var"
+
+- destroy_input_name = "#{form_field}[variables_attributes][][_destroy]"
+- variable_type_input_name = "#{form_field}[variables_attributes][][variable_type]"
+- key_input_name = "#{form_field}[variables_attributes][][key]"
+- value_input_name = "#{form_field}[variables_attributes][][secret_value]"
+
+%li.js-row.ci-variable-row
+ .ci-variable-row-body.border-bottom
+ %input.js-ci-variable-input-destroy{ type: "hidden", name: destroy_input_name }
+ %select.js-ci-variable-input-variable-type.ci-variable-body-item.form-control.select-control.custom-select.table-section.section-15{ name: variable_type_input_name }
+ = options_for_select(ci_variable_type_options, variable_type)
+ %input.js-ci-variable-input-key.ci-variable-body-item.qa-ci-variable-input-key.form-control.table-section.section-15{ type: "text",
+ name: key_input_name,
+ value: key,
+ placeholder: s_('CiVariables|Input variable key') }
+ .ci-variable-body-item.gl-show-field-errors.table-section.section-15.border-top-0.p-0
+ %textarea.js-ci-variable-input-value.js-secret-value.qa-ci-variable-input-value.form-control{ rows: 1,
+ name: value_input_name,
+ placeholder: s_('CiVariables|Input variable value') }
+ = value
+ %button.js-row-remove-button.ci-variable-row-remove-button.table-section.section-5.border-top-0{ type: 'button', 'aria-label': s_('CiVariables|Remove variable row') }
+ = icon('minus-circle')
diff --git a/app/views/clusters/clusters/_advanced_settings.html.haml b/app/views/clusters/clusters/_advanced_settings.html.haml
index 8005dcbf65..493d7a0085 100644
--- a/app/views/clusters/clusters/_advanced_settings.html.haml
+++ b/app/views/clusters/clusters/_advanced_settings.html.haml
@@ -1,3 +1,9 @@
+- group_id = @cluster.group.id if @cluster.group_type?
+
+- if @cluster.project_type?
+ - group_id = @cluster.project.group.id if @cluster.project.group
+ - user_id = @cluster.project.namespace.owner_id unless group_id
+
- if can?(current_user, :admin_cluster, @cluster)
- unless @cluster.provided_by_user?
.append-bottom-20
@@ -7,6 +13,21 @@
- link_gke = link_to(s_('ClusterIntegration|Google Kubernetes Engine'), @cluster.gke_cluster_url, target: '_blank', rel: 'noopener noreferrer')
= s_('ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}').html_safe % { link_gke: link_gke }
+ = form_for @cluster, url: clusterable.cluster_path(@cluster), as: :cluster, html: { class: 'cluster_management_form' } do |field|
+
+ %h5
+ = s_('ClusterIntegration|Cluster management project (alpha)')
+
+ .form-group
+ .form-text.text-muted
+ = project_select_tag('cluster[management_project_id]', class: 'hidden-filter-value', toggle_class: 'js-project-search js-project-filter js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-project js-filter-submit',
+ placeholder: _('Select project'), idAttribute: 'id', data: { order_by: 'last_activity_at', idattribute: 'id', simple_filter: true, allow_clear: true, include_groups: false, include_projects_in_subgroups: true, group_id: group_id, user_id: user_id }, value: @cluster.management_project_id)
+ .text-muted
+ = s_('ClusterIntegration|A cluster management project can be used to run deployment jobs with Kubernetes cluster-admin
privileges.').html_safe
+ = link_to _('More information'), help_page_path('user/clusters/management_project.md'), target: '_blank'
+ .form-group
+ = field.submit _('Save changes'), class: 'btn btn-success qa-save-domain'
+
.sub-section.form-group
%h4.text-danger
= s_('ClusterIntegration|Remove Kubernetes cluster integration')
diff --git a/app/views/clusters/clusters/_banner.html.haml b/app/views/clusters/clusters/_banner.html.haml
index 4b4278075a..7d97aaccbc 100644
--- a/app/views/clusters/clusters/_banner.html.haml
+++ b/app/views/clusters/clusters/_banner.html.haml
@@ -1,10 +1,10 @@
.hidden.js-cluster-error.bs-callout.bs-callout-danger{ role: 'alert' }
- = s_('ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine')
+ = s_('ClusterIntegration|Something went wrong while creating your Kubernetes cluster')
%p.js-error-reason
.hidden.js-cluster-creating.bs-callout.bs-callout-info{ role: 'alert' }
%span.spinner.spinner-dark.spinner-sm{ 'aria-label': 'Loading' }
- %span.prepend-left-4= s_('ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine...')
+ %span.prepend-left-4= s_('ClusterIntegration|Kubernetes cluster is being created...')
.hidden.row.js-cluster-api-unreachable.bs-callout.bs-callout-warning{ role: 'alert' }
.col-11
@@ -19,4 +19,4 @@
%button.js-close-banner.close.cluster-application-banner-close.h-100.m-0= "×"
.hidden.js-cluster-success.bs-callout.bs-callout-success{ role: 'alert' }
- = s_("ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine.")
+ = s_("ClusterIntegration|Kubernetes cluster was successfully created.")
diff --git a/app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml b/app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml
index a9299af8d7..617e5d1d5d 100644
--- a/app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml
+++ b/app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml
@@ -7,6 +7,6 @@
.gcp-signup-offer--copy
%h4= s_('ClusterIntegration|Did you know?')
%p= s_('ClusterIntegration|Every new Google Cloud Platform (GCP) account receives $300 in credit upon %{sign_up_link}. In partnership with Google, GitLab is able to offer an additional $200 for both new and existing GCP accounts to get started with GitLab\'s Google Kubernetes Engine Integration.').html_safe % { sign_up_link: link }
- %a.btn.btn-default{ href: 'https://goo.gl/AaJzRW', target: '_blank', rel: 'noopener noreferrer' }
+ %a.btn.btn-default{ href: 'https://cloud.google.com/partners/partnercredit/?pcn_code=0014M00001h35gDQAQ#contact-form', target: '_blank', rel: 'noopener noreferrer' }
= s_("ClusterIntegration|Apply for credit")
diff --git a/app/views/clusters/clusters/aws/_new.html.haml b/app/views/clusters/clusters/aws/_new.html.haml
new file mode 100644
index 0000000000..795b80bfb6
--- /dev/null
+++ b/app/views/clusters/clusters/aws/_new.html.haml
@@ -0,0 +1,23 @@
+- if !Gitlab::CurrentSettings.eks_integration_enabled?
+ - documentation_link_start = ''.html_safe % { url: help_page_path('user/project/clusters/add_remove_clusters.md',
+ anchor: 'additional-requirements-for-self-managed-instances') }
+ = s_('Amazon authentication is not %{link_start}correctly configured%{link_end}. Ask your GitLab administrator if you want to use this service.').html_safe % { link_start: documentation_link_start, link_end: ' '.html_safe }
+- else
+ .js-create-eks-cluster-form-container{ data: { 'gitlab-managed-cluster-help-path' => help_page_path('user/project/clusters/index.md', anchor: 'gitlab-managed-clusters'),
+ 'create-role-path' => clusterable.authorize_aws_role_path,
+ 'sign-out-path' => clusterable.revoke_aws_role_path,
+ 'create-cluster-path' => clusterable.create_aws_clusters_path,
+ 'get-roles-path' => clusterable.aws_api_proxy_path('roles'),
+ 'get-regions-path' => clusterable.aws_api_proxy_path('regions'),
+ 'get-key-pairs-path' => clusterable.aws_api_proxy_path('key_pairs'),
+ 'get-vpcs-path' => clusterable.aws_api_proxy_path('vpcs'),
+ 'get-subnets-path' => clusterable.aws_api_proxy_path('subnets'),
+ 'get-security-groups-path' => clusterable.aws_api_proxy_path('security_groups'),
+ 'get-instance-types-path' => clusterable.aws_api_proxy_path('instance_types'),
+ 'account-id' => Gitlab::CurrentSettings.eks_account_id,
+ 'external-id' => @aws_role.role_external_id,
+ 'kubernetes-integration-help-path' => help_page_path('user/project/clusters/index'),
+ 'account-and-external-ids-help-path' => help_page_path('user/project/clusters/add_remove_clusters.md', anchor: 'eks-cluster'),
+ 'create-role-arn-help-path' => help_page_path('user/project/clusters/add_remove_clusters.md', anchor: 'eks-cluster'),
+ 'external-link-icon' => icon('external-link'),
+ 'has-credentials' => @aws_role.role_arn.present?.to_s } }
diff --git a/app/views/clusters/clusters/cloud_providers/_cloud_provider_button.html.haml b/app/views/clusters/clusters/cloud_providers/_cloud_provider_button.html.haml
index d4999798c1..56d46580b9 100644
--- a/app/views/clusters/clusters/cloud_providers/_cloud_provider_button.html.haml
+++ b/app/views/clusters/clusters/cloud_providers/_cloud_provider_button.html.haml
@@ -1,8 +1,10 @@
- provider = local_assigns.fetch(:provider)
- logo_path = local_assigns.fetch(:logo_path)
- label = local_assigns.fetch(:label)
+- last = local_assigns.fetch(:last, false)
+- classes = ['btn btn-light btn-outline flex-fill d-inline-flex flex-column justify-content-center align-items-center', ('mr-3' unless last)]
-= link_to clusterable.new_path(provider: provider), class: 'btn gl-button btn-outline flex-fill d-inline-flex flex-column mr-3 justify-content-center align-items-center' do
- .svg-content= image_tag logo_path, alt: label, class: 'gl-w-13 gl-h-13'
+= link_to clusterable.new_path(provider: provider), class: classes do
+ .svg-content.p-2= image_tag logo_path, alt: label, class: 'gl-w-64 gl-h-64'
%span
= label
diff --git a/app/views/clusters/clusters/cloud_providers/_cloud_provider_selector.html.haml b/app/views/clusters/clusters/cloud_providers/_cloud_provider_selector.html.haml
index 7a93a7604f..91925f5f96 100644
--- a/app/views/clusters/clusters/cloud_providers/_cloud_provider_selector.html.haml
+++ b/app/views/clusters/clusters/cloud_providers/_cloud_provider_selector.html.haml
@@ -2,10 +2,10 @@
- eks_label = s_('ClusterIntegration|Amazon EKS')
- create_cluster_label = s_('ClusterIntegration|Create cluster on')
.d-flex.flex-column
- %h5
+ %h5.mb-3
= create_cluster_label
.d-flex
= render partial: 'clusters/clusters/cloud_providers/cloud_provider_button',
- locals: { provider: 'eks', label: eks_label, logo_path: 'illustrations/logos/amazon_eks.svg' }
+ locals: { provider: 'aws', label: eks_label, logo_path: 'illustrations/logos/amazon_eks.svg' }
= render partial: 'clusters/clusters/cloud_providers/cloud_provider_button',
- locals: { provider: 'gke', label: gke_label, logo_path: 'illustrations/logos/google_gke.svg' }
+ locals: { provider: 'gcp', label: gke_label, logo_path: 'illustrations/logos/google_gke.svg', last: true }
diff --git a/app/views/clusters/clusters/eks/_index.html.haml b/app/views/clusters/clusters/eks/_index.html.haml
deleted file mode 100644
index db64698a7f..0000000000
--- a/app/views/clusters/clusters/eks/_index.html.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-.js-create-eks-cluster-form-container{ data: { 'gitlab-managed-cluster-help-path' => help_page_path('user/project/clusters/index.md', anchor: 'gitlab-managed-clusters'),
-'kubernetes-integration-help-path' => help_page_path('user/project/clusters/index') } }
diff --git a/app/views/clusters/clusters/gcp/_form.html.haml b/app/views/clusters/clusters/gcp/_form.html.haml
index cca16ce7ed..95670a2ec8 100644
--- a/app/views/clusters/clusters/gcp/_form.html.haml
+++ b/app/views/clusters/clusters/gcp/_form.html.haml
@@ -64,12 +64,13 @@
%p.form-text.text-muted
= s_('ClusterIntegration|Learn more about %{help_link_start_machine_type}machine types%{help_link_end} and %{help_link_start_pricing}pricing%{help_link_end}.').html_safe % { help_link_start_machine_type: help_link_start % { url: machine_type_link_url }, help_link_start_pricing: help_link_start % { url: pricing_link_url }, help_link_end: help_link_end }
- .form-group
- = provider_gcp_field.check_box :cloud_run, { label: s_('ClusterIntegration|Enable Cloud Run on GKE (beta)'),
- label_class: 'label-bold' }
- .form-text.text-muted
- = s_('ClusterIntegration|Uses the Cloud Run, Istio, and HTTP Load Balancing addons for this cluster.')
- = link_to _('More information'), help_page_path('user/project/clusters/index.md', anchor: 'cloud-run-on-gke'), target: '_blank'
+ - if Feature.enabled?(:create_cloud_run_clusters, clusterable)
+ .form-group
+ = provider_gcp_field.check_box :cloud_run, { label: s_('ClusterIntegration|Enable Cloud Run on GKE (beta)'),
+ label_class: 'label-bold' }
+ .form-text.text-muted
+ = s_('ClusterIntegration|Uses the Cloud Run, Istio, and HTTP Load Balancing addons for this cluster.')
+ = link_to _('More information'), help_page_path('user/project/clusters/index.md', anchor: 'cloud-run-on-gke'), target: '_blank'
.form-group
= field.check_box :managed, { label: s_('ClusterIntegration|GitLab-managed cluster'),
diff --git a/app/views/clusters/clusters/gcp/_new.html.haml b/app/views/clusters/clusters/gcp/_new.html.haml
new file mode 100644
index 0000000000..3d47f4bf2c
--- /dev/null
+++ b/app/views/clusters/clusters/gcp/_new.html.haml
@@ -0,0 +1,7 @@
+= render 'clusters/clusters/gcp/header'
+- if @valid_gcp_token
+ = render 'clusters/clusters/gcp/form'
+- elsif @authorize_url
+ = render 'clusters/clusters/gcp/signin_with_google_button'
+- else
+ = render 'clusters/clusters/gcp/gcp_not_configured'
diff --git a/app/views/clusters/clusters/index.html.haml b/app/views/clusters/clusters/index.html.haml
index 9bab3bf56a..049010cadf 100644
--- a/app/views/clusters/clusters/index.html.haml
+++ b/app/views/clusters/clusters/index.html.haml
@@ -16,7 +16,7 @@
.bs-callout.bs-callout-info
= s_('ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters.')
%strong
- = link_to _('More information'), help_page_path('user/group/clusters/', anchor: 'cluster-precedence')
+ = link_to _('More information'), help_page_path('user/group/clusters/index', anchor: 'cluster-precedence')
.clusters-table.js-clusters-list
.gl-responsive-table-row.table-row-header{ role: "row" }
diff --git a/app/views/clusters/clusters/new.html.haml b/app/views/clusters/clusters/new.html.haml
index 2c23426aaf..cb8cbe4e6f 100644
--- a/app/views/clusters/clusters/new.html.haml
+++ b/app/views/clusters/clusters/new.html.haml
@@ -2,9 +2,6 @@
- page_title _('Kubernetes Cluster')
- create_eks_enabled = Feature.enabled?(:create_eks_clusters)
- active_tab = local_assigns.fetch(:active_tab, 'create')
-- create_on_gke_tab_label = s_('ClusterIntegration|Create new Cluster on GKE')
-- create_on_eks_tab_label = s_('ClusterIntegration|Create new Cluster on EKS')
-- create_new_cluster_label = s_('ClusterIntegration|Create new Cluster')
= javascript_include_tag 'https://apis.google.com/js/api.js'
= render_gcp_signup_offer
@@ -18,14 +15,9 @@
%a.nav-link{ href: '#create-cluster-pane', id: 'create-cluster-tab', class: active_when(active_tab == 'create'), data: { toggle: 'tab' }, role: 'tab' }
%span
- if create_eks_enabled
- - if @gke_selected
- = create_on_gke_tab_label
- - elsif @eks_selected
- = create_on_eks_tab_label
- - else
- = create_new_cluster_label
+ = create_new_cluster_label(provider: params[:provider])
- else
- = create_on_gke_tab_label
+ = create_new_cluster_label(provider: 'gcp')
%li.nav-item{ role: 'presentation' }
%a.nav-link{ href: '#add-cluster-pane', id: 'add-cluster-tab', class: active_when(active_tab == 'add'), data: { toggle: 'tab' }, role: 'tab' }
%span Add existing cluster
@@ -33,27 +25,10 @@
.tab-content.gitlab-tab-content
- if create_eks_enabled
.tab-pane{ id: 'create-cluster-pane', class: active_when(active_tab == 'create'), role: 'tabpanel' }
- - if @gke_selected
- = render 'clusters/clusters/gcp/header'
- - if @valid_gcp_token
- = render 'clusters/clusters/gcp/form'
- - elsif @authorize_url
- = render 'clusters/clusters/gcp/signin_with_google_button'
- - else
- = render 'clusters/clusters/gcp/gcp_not_configured'
- - elsif @eks_selected
- = render 'clusters/clusters/eks/index'
- - else
- = render 'clusters/clusters/cloud_providers/cloud_provider_selector'
+ = render new_cluster_partial(provider: params[:provider])
- else
.tab-pane{ id: 'create-cluster-pane', class: active_when(active_tab == 'create'), role: 'tabpanel' }
- = render 'clusters/clusters/gcp/header'
- - if @valid_gcp_token
- = render 'clusters/clusters/gcp/form'
- - elsif @authorize_url
- = render 'clusters/clusters/gcp/signin_with_google_button'
- - else
- = render 'clusters/clusters/gcp/gcp_not_configured'
+ = render new_cluster_partial(provider: 'gcp')
.tab-pane{ id: 'add-cluster-pane', class: active_when(active_tab == 'add'), role: 'tabpanel' }
= render 'clusters/clusters/user/header'
diff --git a/app/views/clusters/clusters/show.html.haml b/app/views/clusters/clusters/show.html.haml
index 31d5f592d7..5beeaf7259 100644
--- a/app/views/clusters/clusters/show.html.haml
+++ b/app/views/clusters/clusters/show.html.haml
@@ -12,11 +12,13 @@
install_helm_path: clusterable.install_applications_cluster_path(@cluster, :helm),
install_ingress_path: clusterable.install_applications_cluster_path(@cluster, :ingress),
install_cert_manager_path: clusterable.install_applications_cluster_path(@cluster, :cert_manager),
+ install_crossplane_path: clusterable.install_applications_cluster_path(@cluster, :crossplane),
install_prometheus_path: clusterable.install_applications_cluster_path(@cluster, :prometheus),
install_runner_path: clusterable.install_applications_cluster_path(@cluster, :runner),
install_jupyter_path: clusterable.install_applications_cluster_path(@cluster, :jupyter),
install_knative_path: clusterable.install_applications_cluster_path(@cluster, :knative),
update_knative_path: clusterable.update_applications_cluster_path(@cluster, :knative),
+ install_elastic_stack_path: clusterable.install_applications_cluster_path(@cluster, :elastic_stack),
cluster_environments_path: cluster_environments_path,
toggle_status: @cluster.enabled? ? 'true': 'false',
has_rbac: has_rbac_enabled?(@cluster) ? 'true': 'false',
diff --git a/app/views/clusters/clusters/user/_form.html.haml b/app/views/clusters/clusters/user/_form.html.haml
index a6acf948ed..39b6d74d9f 100644
--- a/app/views/clusters/clusters/user/_form.html.haml
+++ b/app/views/clusters/clusters/user/_form.html.haml
@@ -1,7 +1,7 @@
-- more_info_link = link_to _('More information'), help_page_path('user/project/clusters/index.md',
- anchor: 'add-existing-kubernetes-cluster'), target: '_blank'
-- rbac_help_link = link_to _('More information'), help_page_path('user/project/clusters/index.md',
- anchor: 'role-based-access-control-rbac-core-only'), target: '_blank'
+- more_info_link = link_to _('More information'), help_page_path('user/project/clusters/add_remove_clusters.md',
+ anchor: 'add-existing-cluster'), target: '_blank'
+- rbac_help_link = link_to _('More information'), help_page_path('user/project/clusters/add_remove_clusters.md',
+ anchor: 'access-controls'), target: '_blank'
- api_url_help_text = s_('ClusterIntegration|The URL used to access the Kubernetes API.')
- ca_cert_help_text = s_('ClusterIntegration|The Kubernetes certificate used to authenticate to the cluster.')
diff --git a/app/views/clusters/clusters/user/_header.html.haml b/app/views/clusters/clusters/user/_header.html.haml
index 3b9ceaa2b8..b0a24ee464 100644
--- a/app/views/clusters/clusters/user/_header.html.haml
+++ b/app/views/clusters/clusters/user/_header.html.haml
@@ -1,5 +1,5 @@
%h4
= s_('ClusterIntegration|Enter the details for your Kubernetes cluster')
%p
- - link_to_help_page = link_to(s_('ClusterIntegration|documentation'), help_page_path('user/project/clusters/index', anchor: 'add-existing-kubernetes-cluster'), target: '_blank', rel: 'noopener noreferrer')
+ - link_to_help_page = link_to(s_('ClusterIntegration|documentation'), help_page_path('user/project/clusters/add_remove_clusters', anchor: 'add-existing-cluster'), target: '_blank', rel: 'noopener noreferrer')
= s_('ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes').html_safe % { link_to_help_page: link_to_help_page }
diff --git a/app/views/dashboard/projects/_zero_authorized_projects.html.haml b/app/views/dashboard/projects/_zero_authorized_projects.html.haml
index a2b1f0d929..b5f5025b58 100644
--- a/app/views/dashboard/projects/_zero_authorized_projects.html.haml
+++ b/app/views/dashboard/projects/_zero_authorized_projects.html.haml
@@ -3,7 +3,7 @@
.container.section-body
.row
.blank-state-welcome.w-100
- %h2.blank-state-welcome-title
+ %h2.blank-state-welcome-title{ data: { qa_selector: 'welcome_title_content' } }
= _('Welcome to GitLab')
%p.blank-state-text
= _('Faster releases. Better code. Less pain.')
diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml
index 8f6c3ecbe5..fd6d8f3f76 100644
--- a/app/views/devise/sessions/new.html.haml
+++ b/app/views/devise/sessions/new.html.haml
@@ -1,13 +1,13 @@
- page_title "Sign in"
#signin-container
- - if form_based_providers.any?
+ - if any_form_based_providers_enabled?
= render 'devise/shared/tabs_ldap'
- else
- unless experiment_enabled?(:signup_flow)
= render 'devise/shared/tabs_normal'
.tab-content
- - if password_authentication_enabled_for_web? || ldap_enabled? || crowd_enabled?
+ - if password_authentication_enabled_for_web? || ldap_sign_in_enabled? || crowd_enabled?
= render 'devise/shared/signin_box'
-# Signup only makes sense if you can also sign-in
@@ -15,7 +15,7 @@
= render 'devise/shared/signup_box'
-# Show a message if none of the mechanisms above are enabled
- - if !password_authentication_enabled_for_web? && !ldap_enabled? && !(omniauth_enabled? && devise_mapping.omniauthable?)
+ - if !password_authentication_enabled_for_web? && !ldap_sign_in_enabled? && !(omniauth_enabled? && devise_mapping.omniauthable?)
%div
No authentication methods configured.
diff --git a/app/views/devise/shared/_signin_box.html.haml b/app/views/devise/shared/_signin_box.html.haml
index 746d43edba..6ddb7e1ac4 100644
--- a/app/views/devise/shared/_signin_box.html.haml
+++ b/app/views/devise/shared/_signin_box.html.haml
@@ -1,4 +1,4 @@
-- if form_based_providers.any?
+- if any_form_based_providers_enabled?
- if crowd_enabled?
.login-box.tab-pane{ id: "crowd", role: 'tabpanel', class: active_when(form_based_auth_provider_has_active_class?(:crowd)) }
.login-body
diff --git a/app/views/devise/shared/_tabs_ldap.html.haml b/app/views/devise/shared/_tabs_ldap.html.haml
index db54c166a5..b8f0cd2a91 100644
--- a/app/views/devise/shared/_tabs_ldap.html.haml
+++ b/app/views/devise/shared/_tabs_ldap.html.haml
@@ -1,4 +1,4 @@
-%ul.nav-links.new-session-tabs.nav-tabs.nav{ class: ('custom-provider-tabs' if form_based_providers.any?) }
+%ul.nav-links.new-session-tabs.nav-tabs.nav{ class: ('custom-provider-tabs' if any_form_based_providers_enabled?) }
- if crowd_enabled?
%li.nav-item
= link_to "Crowd", "#crowd", class: "nav-link #{active_when(form_based_auth_provider_has_active_class?(:crowd))}", 'data-toggle' => 'tab'
diff --git a/app/views/errors/not_found.html.haml b/app/views/errors/not_found.html.haml
index ae055f398a..13f07e2f5d 100644
--- a/app/views/errors/not_found.html.haml
+++ b/app/views/errors/not_found.html.haml
@@ -11,5 +11,5 @@
= form_tag search_path, method: :get, class: 'form-inline-flex' do |f|
.field
= search_field_tag :search, '', placeholder: _('Search for projects, issues, etc.'), class: 'form-control'
- = button_tag 'Search', class: 'btn btn-success', name: nil, type: 'submit'
+ = button_tag _('Search'), class: 'btn btn-sm btn-success', name: nil, type: 'submit'
= render 'errors/footer'
diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml
index bf077eb09d..1cb1cc45bd 100644
--- a/app/views/groups/issues.html.haml
+++ b/app/views/groups/issues.html.haml
@@ -22,4 +22,10 @@
- if @can_bulk_update
= render_if_exists 'shared/issuable/group_bulk_update_sidebar', group: @group, type: :issues
- = render 'shared/issues'
+ - if Feature.enabled?(:vue_issuables_list, @group)
+ .js-issuables-list{ data: { endpoint: expose_url(api_v4_groups_issues_path(id: @group.id)),
+ 'can-bulk-edit': @can_bulk_update.to_json,
+ 'empty-svg-path': image_path('illustrations/issues.svg'),
+ 'sort-key': @sort } }
+ - else
+ = render 'shared/issues'
diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml
index 23b1a22240..33e68bc766 100644
--- a/app/views/groups/milestones/show.html.haml
+++ b/app/views/groups/milestones/show.html.haml
@@ -1,4 +1,4 @@
= render "header_title"
= render 'shared/milestones/top', milestone: @milestone, group: @group
-= render 'shared/milestones/tabs', milestone: @milestone, show_project_name: true if @milestone.legacy_group_milestone?
+= render 'shared/milestones/tabs', milestone: @milestone, show_project_name: true
= render 'shared/milestones/sidebar', milestone: @milestone, affix_offset: 102
diff --git a/app/views/groups/settings/ci_cd/show.html.haml b/app/views/groups/settings/ci_cd/show.html.haml
index a3f35b72cc..81bd15ed28 100644
--- a/app/views/groups/settings/ci_cd/show.html.haml
+++ b/app/views/groups/settings/ci_cd/show.html.haml
@@ -31,7 +31,8 @@
%button.btn.btn-default.js-settings-toggle{ type: "button" }
= expanded ? _('Collapse') : _('Expand')
%p
- = _('Register and see your runners for this group.')
+ = _("Runners are processes that pick up and execute jobs for GitLab. Here you can register and see your Runners for this project.")
+ = link_to s_('More information'), help_page_path('ci/runners/README')
.settings-content
= render 'groups/runners/index'
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index f1ba804f92..5f8f2333e4 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -218,7 +218,7 @@
%tr
%td.shortcut
%kbd esc
- %td= _('Go back (while searching for files')
+ %td= _('Go back (while searching for files)')
%tr
%td.shortcut
%kbd y
diff --git a/app/views/import/manifest/_form.html.haml b/app/views/import/manifest/_form.html.haml
index 78c7fadb01..b515ce084e 100644
--- a/app/views/import/manifest/_form.html.haml
+++ b/app/views/import/manifest/_form.html.haml
@@ -13,7 +13,7 @@
.form-group
= label_tag :manifest, class: 'label-bold' do
= _('Manifest')
- = file_field_tag :manifest, class: 'form-control-file', required: true
+ = file_field_tag :manifest, class: 'form-control-file w-auto', required: true
.form-text.text-muted
= _('Import multiple repositories by uploading a manifest file.')
= link_to icon('question-circle'), help_page_path('user/project/import/manifest')
diff --git a/app/views/layouts/_flash.html.haml b/app/views/layouts/_flash.html.haml
index 92572f0308..a0b030fa3b 100644
--- a/app/views/layouts/_flash.html.haml
+++ b/app/views/layouts/_flash.html.haml
@@ -3,7 +3,7 @@
- flash.each do |key, value|
-# Don't show a flash message if the message is nil
- if value
- %div{ class: "flash-content flash-#{key} rounded" }
+ %div{ class: "flash-#{key} mb-2" }
%span= value
%div{ class: "close-icon-wrapper js-close-icon" }
= sprite_icon('close', size: 16, css_class: 'close-icon')
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index b8c9f0ae1e..0060d8323b 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -57,7 +57,7 @@
= yield :library_javascripts
= javascript_include_tag locale_path unless I18n.locale == :en
- = webpack_bundle_tag "raven" if Gitlab.config.sentry.enabled
+ = webpack_bundle_tag "sentry" if Gitlab.config.sentry.enabled
- if content_for?(:page_specific_javascripts)
= yield :page_specific_javascripts
@@ -89,4 +89,4 @@
= render 'layouts/google_analytics' if extra_config.has_key?('google_analytics_id')
= render 'layouts/piwik' if extra_config.has_key?('piwik_url') && extra_config.has_key?('piwik_site_id')
- = render_if_exists 'layouts/snowplow'
+ = render 'layouts/snowplow'
diff --git a/app/views/layouts/_mailer.html.haml b/app/views/layouts/_mailer.html.haml
index 6e8294d6ad..24b8138078 100644
--- a/app/views/layouts/_mailer.html.haml
+++ b/app/views/layouts/_mailer.html.haml
@@ -1,80 +1,48 @@
-
+
%html{ lang: "en" }
%head
%meta{ content: "text/html; charset=UTF-8", "http-equiv" => "Content-Type" }/
%meta{ content: "width=device-width, initial-scale=1", name: "viewport" }/
%meta{ content: "IE=edge", "http-equiv" => "X-UA-Compatible" }/
%title= message.subject
- :css
- /* CLIENT-SPECIFIC STYLES */
- body, table, td, a { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
- table, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; }
- img { -ms-interpolation-mode: bicubic; }
- .hidden {
- display: none !important;
- visibility: hidden !important;
- }
- /* iOS BLUE LINKS */
- a[x-apple-data-detectors] {
- color: inherit !important;
- text-decoration: none !important;
- font-size: inherit !important;
- font-family: inherit !important;
- font-weight: inherit !important;
- line-height: inherit !important;
- }
+ -# Avoid premailer processing of client-specific styles (@media tag not supported)
+ -# We need to inline the contents here because mail clients (e.g. iOS Mail, Outlook)
+ -# do not support linked stylesheets.
+ %style{ type: 'text/css', 'data-premailer': 'ignore' }
+ = asset_to_string('mailer_client_specific.css').html_safe
- /* ANDROID MARGIN HACK */
- body { margin:0 !important; }
- div[style*="margin: 16px 0"] { margin:0 !important; }
-
- @media only screen and (max-width: 639px) {
- body, #body {
- min-width: 320px !important;
- }
- table.wrapper {
- width: 100% !important;
- min-width: 320px !important;
- }
- table.wrapper > tbody > tr > td {
- border-left: 0 !important;
- border-right: 0 !important;
- border-radius: 0 !important;
- padding-left: 10px !important;
- padding-right: 10px !important;
- }
- }
- %body{ style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;height:100%;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;" }
- %table#body{ border: "0", cellpadding: "0", cellspacing: "0", style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;" }
+ = stylesheet_link_tag 'mailer.css'
+ %body
+ %table#body{ border: "0", cellpadding: "0", cellspacing: "0" }
%tbody
%tr.line
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#6b4fbb;height:4px;font-size:4px;line-height:4px;" }
+ %td
%tr.header
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
+ %td
= html_header_message
= header_logo
%tr
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;" }
- %table.wrapper{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:640px;margin:0 auto;border-collapse:separate;border-spacing:0;" }
+ %td
+ %table.wrapper{ border: "0", cellpadding: "0", cellspacing: "0" }
%tbody
%tr
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#ffffff;text-align:left;padding:18px 25px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" }
- %table.content{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;border-collapse:separate;border-spacing:0;" }
+ %td.wrapper-cell
+ %table.content{ border: "0", cellpadding: "0", cellspacing: "0" }
%tbody
= yield
= render_if_exists 'layouts/mailer/additional_text'
%tr.footer
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
- %img{ alt: "GitLab", height: "33", src: image_url('mailers/gitlab_footer_logo.gif'), style: "display:block;margin:0 auto 1em;", width: "90" }/
+ %td
+ %img{ alt: "GitLab", height: "33", width: "90", src: image_url('mailers/gitlab_footer_logo.gif') }
%div
- - manage_notifications_link = link_to(_("Manage all notifications"), profile_notifications_url, style: "color:#3777b0;text-decoration:none;")
- - help_link = link_to(_("Help"), help_url, style: "color:#3777b0;text-decoration:none;")
+ - manage_notifications_link = link_to(_("Manage all notifications"), profile_notifications_url, class: 'mng-notif-link')
+ - help_link = link_to(_("Help"), help_url, class: 'help-link')
= _("You're receiving this email because of your account on %{host}. %{manage_notifications_link} · %{help_link}").html_safe % { host: Gitlab.config.gitlab.host, manage_notifications_link: manage_notifications_link, help_link: help_link }
= yield :additional_footer
%tr
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
+ %td.footer-message
= html_footer_message
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index 6cdb85456c..443a73f5cc 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -17,6 +17,4 @@
%div{ class: "#{(container_class unless @no_container)} #{@content_class}" }
.content{ id: "content-body" }
= render "layouts/flash", extra_flash_class: 'limit-container-width'
- - if Gitlab.com?
- = render_if_exists "layouts/privacy_policy_update_callout"
= yield
diff --git a/app/views/layouts/header/_current_user_dropdown.html.haml b/app/views/layouts/header/_current_user_dropdown.html.haml
index efe74ddd90..d15f0ae322 100644
--- a/app/views/layouts/header/_current_user_dropdown.html.haml
+++ b/app/views/layouts/header/_current_user_dropdown.html.haml
@@ -18,6 +18,11 @@
- if current_user_menu?(:profile)
%li
= link_to s_("CurrentUser|Profile"), current_user, class: 'profile-link', data: { user: current_user.username }
+ - if current_user_menu?(:start_trial)
+ %li
+ %a.profile-link{ href: trials_link_url }
+ = s_("CurrentUser|Start a Gold trial")
+ = emoji_icon('rocket')
- if current_user_menu?(:settings)
%li
= link_to s_("CurrentUser|Settings"), profile_path, data: { qa_selector: 'settings_link' }
@@ -35,8 +40,8 @@
%li.d-md-none
= render 'shared/user_dropdown_contributing_link'
= render_if_exists 'shared/user_dropdown_instance_review'
- - if Gitlab.com?
- %li.js-canary-link.d-md-none
+ - if Gitlab.com_but_not_canary?
+ %li.d-md-none
= link_to _("Switch to GitLab Next"), "https://next.gitlab.com/"
- if current_user_menu?(:sign_out)
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index d8697be7f7..5719fb24b8 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -18,8 +18,8 @@
- if logo_text.present?
%span.logo-text.d-none.d-lg-block.prepend-left-8
= logo_text
- - if Gitlab.com?
- = link_to 'https://next.gitlab.com', class: 'label-link js-canary-badge canary-badge bg-transparent hidden', target: :_blank do
+ - if Gitlab.com_and_canary?
+ = link_to 'https://next.gitlab.com', class: 'label-link canary-badge bg-transparent', target: :_blank do
%span.color-label.has-tooltip.badge.badge-pill.green-badge
= _('Next')
diff --git a/app/views/layouts/header/_help_dropdown.html.haml b/app/views/layouts/header/_help_dropdown.html.haml
index 71977b2348..93854c212d 100644
--- a/app/views/layouts/header/_help_dropdown.html.haml
+++ b/app/views/layouts/header/_help_dropdown.html.haml
@@ -12,6 +12,6 @@
%li
= render 'shared/user_dropdown_contributing_link'
= render_if_exists 'shared/user_dropdown_instance_review'
- - if Gitlab.com?
- %li.js-canary-link
+ - if Gitlab.com_but_not_canary?
+ %li
= link_to _("Switch to GitLab Next"), "https://next.gitlab.com/"
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index 5122c2517a..d339751848 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -55,15 +55,15 @@
= nav_link(controller: 'admin/dashboard') do
= link_to admin_root_path, class: 'admin-icon qa-admin-area-link d-xl-none' do
= _('Admin Area')
- - if Feature.enabled?(:user_mode_in_session)
- - if header_link?(:admin_mode)
- = nav_link(controller: 'admin/sessions') do
- = link_to destroy_admin_session_path, class: 'd-lg-none lock-open-icon' do
- = _('Leave admin mode')
- - elsif current_user.admin?
- = nav_link(controller: 'admin/sessions') do
- = link_to new_admin_session_path, class: 'd-lg-none lock-icon' do
- = _('Enter admin mode')
+ - if Feature.enabled?(:user_mode_in_session)
+ - if header_link?(:admin_mode)
+ = nav_link(controller: 'admin/sessions') do
+ = link_to destroy_admin_session_path, class: 'd-lg-none lock-open-icon' do
+ = _('Leave Admin Mode')
+ - elsif current_user.admin?
+ = nav_link(controller: 'admin/sessions') do
+ = link_to new_admin_session_path, class: 'd-lg-none lock-icon' do
+ = _('Enter Admin Mode')
- if Gitlab::Sherlock.enabled?
%li
= link_to sherlock_transactions_path, class: 'admin-icon' do
@@ -74,6 +74,15 @@
= link_to admin_root_path, class: 'admin-icon qa-admin-area-link', title: _('Admin Area'), aria: { label: _('Admin Area') }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= sprite_icon('admin', size: 18)
+ - if Feature.enabled?(:user_mode_in_session)
+ - if header_link?(:admin_mode)
+ = nav_link(controller: 'admin/sessions', html_options: { class: "d-none d-lg-block d-xl-block"}) do
+ = link_to destroy_admin_session_path, title: _('Leave Admin Mode'), aria: { label: _('Leave Admin Mode') }, data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
+ = sprite_icon('lock-open', size: 18)
+ - elsif current_user.admin?
+ = nav_link(controller: 'admin/sessions', html_options: { class: "d-none d-lg-block d-xl-block"}) do
+ = link_to new_admin_session_path, title: _('Enter Admin Mode'), aria: { label: _('Enter Admin Mode') }, data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
+ = sprite_icon('lock', size: 18)
-# Shortcut to Dashboard > Projects
- if dashboard_nav_link?(:projects)
diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml
index 4930c6cf5f..a6d2c89418 100644
--- a/app/views/layouts/nav/sidebar/_group.html.haml
+++ b/app/views/layouts/nav/sidebar/_group.html.haml
@@ -16,13 +16,19 @@
.nav-icon-container
= sprite_icon('home')
%span.nav-item-name
- = _('Overview')
+ - if @group.subgroup?
+ = _('Subgroup overview')
+ - else
+ = _('Group overview')
%ul.sidebar-sub-level-items
= nav_link(path: ['groups#show', 'groups#details', 'groups#activity', 'groups#subgroups'], html_options: { class: "fly-out-top-item" } ) do
= link_to group_path(@group) do
%strong.fly-out-top-item-name
- = _('Overview')
+ - if @group.subgroup?
+ = _('Subgroup overview')
+ - else
+ = _('Group overview')
%li.divider.fly-out-top-item
= nav_link(path: ['groups#show', 'groups#details', 'groups#subgroups'], html_options: { class: 'home' }) do
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index c84bc0b5cd..9b3ad05d0c 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -13,13 +13,13 @@
.nav-icon-container
= sprite_icon('home')
%span.nav-item-name
- = _('Project')
+ = _('Project overview')
%ul.sidebar-sub-level-items
= nav_link(path: 'projects#show', html_options: { class: "fly-out-top-item" } ) do
= link_to project_path(@project) do
%strong.fly-out-top-item-name
- = _('Project')
+ = _('Project overview')
%li.divider.fly-out-top-item
= nav_link(path: 'projects#show') do
= link_to project_path(@project), title: _('Project details'), class: 'shortcuts-project' do
@@ -163,7 +163,7 @@
- if project_nav_tab? :pipelines
= nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :artifacts]) do
- = link_to project_pipelines_path(@project), class: 'shortcuts-pipelines qa-link-pipelines rspec-link-pipelines' do
+ = link_to project_pipelines_path(@project), class: 'shortcuts-pipelines qa-link-pipelines rspec-link-pipelines', data: { qa_selector: 'ci_cd_link' } do
.nav-icon-container
= sprite_icon('rocket')
%span.nav-item-name#js-onboarding-pipelines-link
@@ -247,6 +247,8 @@
%span
= _('Serverless')
+ = render_if_exists 'layouts/nav/sidebar/pod_logs_link' # EE-specific
+
- if project_nav_tab? :clusters
- show_cluster_hint = show_gke_cluster_integration_callout?(@project)
= nav_link(controller: [:clusters, :user, :gcp]) do
@@ -264,7 +266,7 @@
dismiss_endpoint: user_callouts_path } }
- if show_cluster_hint
.feature-highlight-popover-content
- = image_tag 'illustrations/cluster_popover.svg', class: 'feature-highlight-illustration'
+ = image_tag 'illustrations/cluster_popover.svg', class: 'feature-highlight-illustration', lazy: false, alt: _('Kubernetes popover')
.feature-highlight-popover-sub-content
%p= _('Allows you to add and manage Kubernetes clusters.')
%p
@@ -347,7 +349,7 @@
= _('Members')
- if can_edit
= nav_link(controller: [:integrations, :services, :hooks, :hook_logs]) do
- = link_to project_settings_integrations_path(@project), title: _('Integrations') do
+ = link_to project_settings_integrations_path(@project), title: _('Integrations'), data: { qa_selector: 'integrations_settings_link' } do
%span
= _('Integrations')
= nav_link(controller: :repository) do
diff --git a/app/views/notify/member_access_denied_email.html.haml b/app/views/notify/member_access_denied_email.html.haml
index 71c9c50071..11661a423d 100644
--- a/app/views/notify/member_access_denied_email.html.haml
+++ b/app/views/notify/member_access_denied_email.html.haml
@@ -1,4 +1,7 @@
-%p
- Your request to join the
- #{link_to member_source.human_name, member_source.web_url} #{member_source.model_name.singular}
- has been denied.
+%tr
+ %td.text-content
+ %p
+ Your request to join the
+ #{link_to member_source.human_name, member_source.web_url, class: :highlight} #{member_source.model_name.singular}
+ has been #{content_tag :span, 'denied', class: :highlight}.
+
diff --git a/app/views/notify/member_access_granted_email.html.haml b/app/views/notify/member_access_granted_email.html.haml
index 1c50dba9c9..e28a10a243 100644
--- a/app/views/notify/member_access_granted_email.html.haml
+++ b/app/views/notify/member_access_granted_email.html.haml
@@ -1,10 +1,14 @@
- link_end = ''.html_safe
- source_type = member_source.model_name.singular
- leave_link = polymorphic_url([member_source], leave: 1)
-- source_link = link_to(member_source.human_name, member_source.web_url, target: '_blank', rel: 'noopener noreferrer')
+- source_link = link_to(member_source.human_name, member_source.web_url, target: '_blank', rel: 'noopener noreferrer', class: :highlight)
+- access_level = content_tag(:span, member.human_access, class: :highlight)
+
+%tr
+ %td.text-content
+ %p
+ = _('You have been granted %{access_level} access to the %{source_link} %{source_type}.').html_safe % { access_level: access_level, source_link: source_link, source_type: source_type }
+ %p
+ - leave_link_start = ''.html_safe % { url: leave_link }
+ = _('If this was a mistake you can %{leave_link_start}leave the %{source_type}%{link_end}.').html_safe % { source_type: source_type, leave_link_start: leave_link_start, link_end: link_end }
-%p
- = _('You have been granted %{access_level} access to the %{source_link} %{source_type}.').html_safe % { access_level: member.human_access, source_link: source_link, source_type: source_type }
-%p
- - leave_link_start = ' '.html_safe % { url: leave_link }
- = _('If this was a mistake you can %{leave_link_start}leave the %{source_type}%{link_end}.').html_safe % { source_type: source_type, leave_link_start: leave_link_start, link_end: link_end }
diff --git a/app/views/notify/member_access_requested_email.html.haml b/app/views/notify/member_access_requested_email.html.haml
index 76f1f08a0c..43f25af3db 100644
--- a/app/views/notify/member_access_requested_email.html.haml
+++ b/app/views/notify/member_access_requested_email.html.haml
@@ -1,3 +1,6 @@
-%p
- #{link_to member.user.name, member.user} requested #{member.human_access}
- access to the #{link_to member_source.human_name, polymorphic_url([member_source, :members])} #{member_source.model_name.singular}.
+%tr
+ %td.text-content
+ %p
+ #{link_to member.user.name, member.user, class: :highlight} requested #{content_tag :span, member.human_access, class: :highlight}
+ access to the #{link_to member_source.human_name, polymorphic_url([member_source, :members]), class: :highlight} #{member_source.model_name.singular}.
+
diff --git a/app/views/notify/member_invite_accepted_email.html.haml b/app/views/notify/member_invite_accepted_email.html.haml
index 2d1d40881e..0abb79000e 100644
--- a/app/views/notify/member_invite_accepted_email.html.haml
+++ b/app/views/notify/member_invite_accepted_email.html.haml
@@ -1,5 +1,8 @@
-%p
- #{member.invite_email}, now known as
- #{link_to member.user.name, user_url(member.user)},
- has accepted your invitation to join the
- #{link_to member_source.human_name, member_source.web_url} #{member_source.model_name.singular}.
+%tr
+ %td.text-content
+ %p
+ #{content_tag :span, member.invite_email, class: :highlight}, now known as
+ #{link_to member.user.name, user_url(member.user)},
+ has accepted your invitation to join the
+ #{link_to member_source.human_name, member_source.web_url, class: :highlight} #{member_source.model_name.singular}.
+
diff --git a/app/views/notify/member_invite_declined_email.html.haml b/app/views/notify/member_invite_declined_email.html.haml
index aa1b373d1a..5e62676723 100644
--- a/app/views/notify/member_invite_declined_email.html.haml
+++ b/app/views/notify/member_invite_declined_email.html.haml
@@ -1,4 +1,7 @@
-%p
- #{@invite_email}
- has declined your invitation to join the
- #{link_to member_source.human_name, member_source.web_url} #{member_source.model_name.singular}.
+%tr
+ %td.text-content
+ %p
+ #{content_tag :span, @invite_email, class: :highlight}
+ has #{content_tag :span, 'declined', class: :highlight} your invitation to join the
+ #{link_to member_source.human_name, member_source.web_url, class: :highlight} #{member_source.model_name.singular}.
+
diff --git a/app/views/notify/member_invited_email.html.haml b/app/views/notify/member_invited_email.html.haml
index 6730172242..ae3fecf404 100644
--- a/app/views/notify/member_invited_email.html.haml
+++ b/app/views/notify/member_invited_email.html.haml
@@ -1,13 +1,16 @@
-%p
- You have been invited
- - if member.created_by
- by
- = link_to member.created_by.name, user_url(member.created_by)
- to join the
- = link_to member_source.human_name, member_source.public? ? member_source.web_url : invite_url(@token)
- #{member_source.model_name.singular} as #{member.human_access}.
+%tr
+ %td.text-content
+ %p
+ You have been invited
+ - if member.created_by
+ by
+ = link_to member.created_by.name, user_url(member.created_by)
+ to join the
+ = link_to member_source.human_name, member_source.public? ? member_source.web_url : invite_url(@token), class: :highlight
+ #{member_source.model_name.singular} as #{content_tag :span, member.human_access, class: :highlight}.
+
+ %p
+ = link_to 'Accept invitation', invite_url(@token)
+ or
+ = link_to 'decline', decline_invite_url(@token)
-%p
- = link_to 'Accept invitation', invite_url(@token)
- or
- = link_to 'decline', decline_invite_url(@token)
diff --git a/app/views/profiles/preferences/_sourcegraph.html.haml b/app/views/profiles/preferences/_sourcegraph.html.haml
new file mode 100644
index 0000000000..20a904694c
--- /dev/null
+++ b/app/views/profiles/preferences/_sourcegraph.html.haml
@@ -0,0 +1,26 @@
+- return unless Gitlab::Sourcegraph::feature_available? && Gitlab::CurrentSettings.sourcegraph_enabled
+- sourcegraph_url = Gitlab::CurrentSettings.sourcegraph_url
+
+.col-sm-12
+ %hr
+
+.col-lg-4.profile-settings-sidebar
+ %h4.prepend-top-0
+ = s_('Preferences|Integrations')
+ %p
+ = s_('Preferences|Customize integrations with third party services.')
+ = succeed '.' do
+ = link_to _('Learn more'), help_page_path('user/profile/preferences.md', anchor: 'integrations'), target: '_blank'
+.col-lg-8
+ %label.label-bold
+ = s_('Preferences|Sourcegraph')
+ = link_to icon('question-circle'), help_page_path('user/profile/preferences.md', anchor: 'sourcegraph'), target: '_blank', class: 'has-tooltip', title: _('More information')
+ .form-group.form-check
+ = f.check_box :sourcegraph_enabled, class: 'form-check-input'
+ = f.label :sourcegraph_enabled, class: 'form-check-label' do
+ - link_start = ' '.html_safe % { url: sourcegraph_url }
+ - link_end = ' '.html_safe
+ = s_('Preferences|Enable integrated code intelligence on code views').html_safe % { link_start: link_start, link_end: link_end }
+ .form-text.text-muted
+ = sourcegraph_url_message
+ = sourcegraph_experimental_message
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index 84657592cd..bf76b7379d 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -111,6 +111,9 @@
= time_display_label
.form-text.text-muted
= s_('Preferences|For example: 30 mins ago.')
+
+ = render 'sourcegraph', f: f
+
.col-lg-4.profile-settings-sidebar
.col-lg-8
.form-group
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index 68b7efc6fb..cfad274f91 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -94,7 +94,7 @@
- else
= f.text_field :name, label: s_('Profiles|Full name'), required: true, title: s_("Profiles|Using emojis in names seems fun, but please try to set a status message instead"), wrapper: { class: 'col-md-9 qa-full-name rspec-full-name' }, help: s_("Profiles|Enter your name, so people you know can recognize you")
= f.text_field :id, readonly: true, label: s_('Profiles|User ID'), wrapper: { class: 'col-md-3' }
- = f.select :role, ::User.roles.keys.map { |role| [role.titleize, role] }, {}, class: 'input-md'
+ = f.select :role, ::User.roles.keys.map { |role| [role.titleize, role] }, { prompt: _('Select your role') }, required: true, class: 'input-md'
= render_if_exists 'profiles/email_settings', form: f
= f.text_field :skype, class: 'input-md', placeholder: s_("Profiles|username")
diff --git a/app/views/projects/_export.html.haml b/app/views/projects/_export.html.haml
index e4129a91da..2e00632892 100644
--- a/app/views/projects/_export.html.haml
+++ b/app/views/projects/_export.html.haml
@@ -14,7 +14,7 @@
%li= desc
%p= _('The following items will NOT be exported:')
%ul
- %li= _('Job traces and artifacts')
+ %li= _('Job logs and artifacts')
%li= _('Container registry images')
%li= _('CI variables')
%li= _('Webhooks')
diff --git a/app/views/projects/_files.html.haml b/app/views/projects/_files.html.haml
index 95fdad125a..20d4084f42 100644
--- a/app/views/projects/_files.html.haml
+++ b/app/views/projects/_files.html.haml
@@ -15,15 +15,13 @@
= render 'shared/commit_well', commit: commit, ref: ref, project: project
- if is_project_overview
- .project-buttons.append-bottom-default{ class: ("js-keep-hidden-on-navigation" if vue_file_list_enabled?) }
+ .project-buttons.append-bottom-default{ class: ("js-show-on-project-root" if vue_file_list_enabled?) }
= render 'stat_anchor_list', anchors: @project.statistics_buttons(show_auto_devops_callout: show_auto_devops_callout)
- if vue_file_list_enabled?
- #js-tree-list{ data: { project_path: @project.full_path, project_short_path: @project.path, ref: ref, full_name: @project.name_with_namespace } }
+ #js-tree-list{ data: vue_file_list_data(project, ref) }
- if can_edit_tree?
= render 'projects/blob/upload', title: _('Upload New File'), placeholder: _('Upload New File'), button_title: _('Upload file'), form_path: project_create_blob_path(@project, @id), method: :post
= render 'projects/blob/new_dir'
- - if @tree.readme
- = render "projects/tree/readme", readme: @tree.readme
- else
= render 'projects/tree/tree_content', tree: @tree, content_url: content_url
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index e66701676d..daedc52f29 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -3,7 +3,7 @@
- max_project_topic_length = 15
- emails_disabled = @project.emails_disabled?
-.project-home-panel{ class: [("empty-project" if empty_repo), ("js-keep-hidden-on-navigation" if vue_file_list_enabled?)] }
+.project-home-panel{ class: [("empty-project" if empty_repo), ("js-show-on-project-root" if vue_file_list_enabled?)] }
.row.append-bottom-8
.home-panel-title-row.col-md-12.col-lg-6.d-flex
.avatar-container.rect-avatar.s64.home-panel-avatar.append-right-default.float-none
diff --git a/app/views/projects/_merge_request_merge_options_settings.html.haml b/app/views/projects/_merge_request_merge_options_settings.html.haml
index 5ab475822d..047b4dafbf 100644
--- a/app/views/projects/_merge_request_merge_options_settings.html.haml
+++ b/app/views/projects/_merge_request_merge_options_settings.html.haml
@@ -12,3 +12,9 @@
= form.check_box :printing_merge_request_link_enabled, class: 'form-check-input'
= form.label :printing_merge_request_link_enabled, class: 'form-check-label' do
= s_('ProjectSettings|Show link to create/view merge request when pushing from the command line')
+ .form-check.mb-2
+ = form.check_box :remove_source_branch_after_merge, class: 'form-check-input'
+ = form.label :remove_source_branch_after_merge, class: 'form-check-label' do
+ = s_("ProjectSettings|Enable 'Delete source branch' option by default")
+ .descr.text-secondary
+ = s_('ProjectSettings|Existing merge requests and protected branches are not affected')
diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml
index f221576597..30fe5622eb 100644
--- a/app/views/projects/blame/show.html.haml
+++ b/app/views/projects/blame/show.html.haml
@@ -19,7 +19,7 @@
= author_avatar(commit, size: 36, has_tooltip: false)
.commit-row-title
%span.item-title.str-truncated-100
- = link_to_markdown commit.title, project_commit_path(@project, commit.id), class: "cdark", title: commit.title
+ = link_to commit.title, project_commit_path(@project, commit.id), class: "cdark", title: commit.title
.float-right
= link_to commit.short_id, project_commit_path(@project, commit), class: "commit-sha"
diff --git a/app/views/projects/blob/_header.html.haml b/app/views/projects/blob/_header.html.haml
index 84ccd816d8..7724511477 100644
--- a/app/views/projects/blob/_header.html.haml
+++ b/app/views/projects/blob/_header.html.haml
@@ -5,18 +5,19 @@
.file-actions
= render 'projects/blob/viewer_switcher', blob: blob unless blame
+ .btn-group{ role: "group" }<
+ = edit_blob_button
+ = ide_edit_button
+ .btn-group{ role: "group" }<
+ = render_if_exists 'projects/blob/header_file_locks_link'
+ - if current_user
+ = replace_blob_link
+ = delete_blob_link
.btn-group{ role: "group" }<
= copy_blob_source_button(blob) unless blame
= open_raw_blob_button(blob)
= download_blob_button(blob)
= view_on_environment_button(@commit.sha, @path, @environment) if @environment
- .btn-group{ role: "group" }<
- = render_if_exists 'projects/blob/header_file_locks_link'
- = edit_blob_button
- = ide_edit_button
- - if current_user
- = replace_blob_link
- = delete_blob_link
= render 'projects/fork_suggestion'
= render_if_exists 'projects/blob/header_file_locks', project: @project, path: @path
diff --git a/app/views/projects/blob/_markdown_buttons.html.haml b/app/views/projects/blob/_markdown_buttons.html.haml
index 28d1ff9782..44ec2fa69c 100644
--- a/app/views/projects/blob/_markdown_buttons.html.haml
+++ b/app/views/projects/blob/_markdown_buttons.html.haml
@@ -6,7 +6,7 @@
= markdown_toolbar_button({ icon: "link", data: { "md-tag" => "[{text}](url)", "md-select" => "url" }, title: _("Add a link") })
= markdown_toolbar_button({ icon: "list-bulleted", data: { "md-tag" => "* ", "md-prepend" => true }, title: _("Add a bullet list") })
= markdown_toolbar_button({ icon: "list-numbered", data: { "md-tag" => "1. ", "md-prepend" => true }, title: _("Add a numbered list") })
- = markdown_toolbar_button({ icon: "task-done", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: _("Add a task list") })
+ = markdown_toolbar_button({ icon: "list-task", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: _("Add a task list") })
= markdown_toolbar_button({ icon: "table", data: { "md-tag" => "| header | header |\n| ------ | ------ |\n| cell | cell |\n| cell | cell |", "md-prepend" => true }, title: _("Add a table") })
- if show_fullscreen_button
%button.toolbar-btn.toolbar-fullscreen-btn.js-zen-enter.has-tooltip{ type: "button", tabindex: -1, "aria-label": "Go full screen", title: _("Go full screen"), data: { container: "body" } }
diff --git a/app/views/projects/buttons/_clone.html.haml b/app/views/projects/buttons/_clone.html.haml
index abef33ca01..ed22573b23 100644
--- a/app/views/projects/buttons/_clone.html.haml
+++ b/app/views/projects/buttons/_clone.html.haml
@@ -25,5 +25,3 @@
= clipboard_button(target: '#http_project_clone', title: _("Copy URL"), class: "input-group-text btn-default btn-clipboard")
= render_if_exists 'projects/buttons/geo'
= render_if_exists 'projects/buttons/kerberos_clone_field'
-
-= render_if_exists 'shared/geo_info_modal', project: project
diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml
index 96df3cd18f..e8aff58b50 100644
--- a/app/views/projects/buttons/_download.html.haml
+++ b/app/views/projects/buttons/_download.html.haml
@@ -12,11 +12,14 @@
%h5.m-0.dropdown-bold-header= _('Download source code')
.dropdown-menu-content
= render 'projects/buttons/download_links', project: project, ref: ref, archive_prefix: archive_prefix, path: nil
- - if directory? && Feature.enabled?(:git_archive_path, default_enabled: true)
- %section.border-top.pt-1.mt-1
- %h5.m-0.dropdown-bold-header= _('Download this directory')
- .dropdown-menu-content
- = render 'projects/buttons/download_links', project: project, ref: ref, archive_prefix: archive_prefix, path: @path
+ - if Feature.enabled?(:git_archive_path, default_enabled: true)
+ - if vue_file_list_enabled?
+ #js-directory-downloads{ data: { links: directory_download_links(project, ref, archive_prefix).to_json } }
+ - elsif directory?
+ %section.border-top.pt-1.mt-1
+ %h5.m-0.dropdown-bold-header= _('Download this directory')
+ .dropdown-menu-content
+ = render 'projects/buttons/download_links', project: project, ref: ref, archive_prefix: archive_prefix, path: @path
- if pipeline && pipeline.latest_builds_with_artifacts.any?
%section.border-top.pt-1.mt-1
%h5.m-0.dropdown-bold-header= _('Download artifacts')
diff --git a/app/views/projects/buttons/_download_links.html.haml b/app/views/projects/buttons/_download_links.html.haml
index b256d94065..990f3ff526 100644
--- a/app/views/projects/buttons/_download_links.html.haml
+++ b/app/views/projects/buttons/_download_links.html.haml
@@ -1,6 +1,4 @@
-- formats = [['zip', 'btn-primary'], ['tar.gz'], ['tar.bz2'], ['tar']]
-
.btn-group.ml-0.w-100
- - formats.each do |(fmt, extra_class)|
+ - Gitlab::Workhorse::ARCHIVE_FORMATS.each_with_index do |fmt, index|
- archive_path = project_archive_path(project, id: tree_join(ref, archive_prefix), path: path, format: fmt)
- = link_to fmt, external_storage_url_or_path(archive_path), rel: 'nofollow', download: '', class: "btn btn-xs #{extra_class}"
+ = link_to fmt, external_storage_url_or_path(archive_path), rel: 'nofollow', download: '', class: "btn btn-xs #{"btn-primary" if index == 0}"
diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml
index 9744d293c8..c8c9629767 100644
--- a/app/views/projects/compare/_form.html.haml
+++ b/app/views/projects/compare/_form.html.haml
@@ -8,8 +8,9 @@
.input-group-text
= s_("CompareBranches|Source")
= hidden_field_tag :to, params[:to]
- = button_tag type: 'button', title: params[:to], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip monospace", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to], field_name: :to } do
- .dropdown-toggle-text.str-truncated= params[:to] || _("Select branch/tag")
+ = button_tag type: 'button', title: params[:to], class: "btn form-control compare-dropdown-toggle js-compare-dropdown has-tooltip", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to], field_name: :to } do
+ .dropdown-toggle-text.str-truncated.monospace.float-left= params[:to] || _("Select branch/tag")
+ = sprite_icon('arrow-down', size: 16, css_class: 'float-right')
= render 'shared/ref_dropdown'
.compare-ellipsis.inline ...
.form-group.dropdown.compare-form-group.from.js-compare-from-dropdown
@@ -18,8 +19,9 @@
.input-group-text
= s_("CompareBranches|Target")
= hidden_field_tag :from, params[:from]
- = button_tag type: 'button', title: params[:from], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip monospace", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do
- .dropdown-toggle-text.str-truncated= params[:from] || _("Select branch/tag")
+ = button_tag type: 'button', title: params[:from], class: "btn form-control compare-dropdown-toggle js-compare-dropdown has-tooltip", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do
+ .dropdown-toggle-text.str-truncated.monospace.float-left= params[:from] || _("Select branch/tag")
+ = sprite_icon('arrow-down', size: 16, css_class: 'float-right')
= render 'shared/ref_dropdown'
= button_tag s_("CompareBranches|Compare"), class: "btn btn-success commits-compare-btn"
diff --git a/app/views/projects/deployments/_confirm_rollback_modal.html.haml b/app/views/projects/deployments/_confirm_rollback_modal.html.haml
index ff40e404e5..9162827b50 100644
--- a/app/views/projects/deployments/_confirm_rollback_modal.html.haml
+++ b/app/views/projects/deployments/_confirm_rollback_modal.html.haml
@@ -13,7 +13,7 @@
%p= s_('Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?').html_safe % {commit_id: commit_sha}
- else
%p
- = s_('Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?').html_safe % {commit_id: commit_sha}
+ = s_('Environments|This action will run the job defined by %{environment_name} for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?').html_safe % {commit_id: commit_sha, environment_name: @environment.name}
.modal-footer
= button_tag _('Cancel'), type: 'button', class: 'btn btn-cancel', data: { dismiss: 'modal' }
= link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn btn-danger' do
diff --git a/app/views/projects/environments/empty_logs.html.haml b/app/views/projects/environments/empty_logs.html.haml
new file mode 100644
index 0000000000..602dc908b7
--- /dev/null
+++ b/app/views/projects/environments/empty_logs.html.haml
@@ -0,0 +1,14 @@
+- page_title _('Pod logs')
+
+.row.empty-state
+ .col-sm-12
+ .svg-content
+ = image_tag 'illustrations/operations_log_pods_empty.svg'
+ .col-12
+ .text-content
+ %h4.text-center
+ = s_('Environments|No deployed environments')
+ %p.state-description.text-center
+ = s_('Logs|To see the pod logs, deploy your code to an environment.')
+ .text-center
+ = link_to s_('Environments|Learn about environments'), help_page_path('ci/environments'), class: 'btn btn-success'
diff --git a/app/views/projects/environments/empty.html.haml b/app/views/projects/environments/empty_metrics.html.haml
similarity index 64%
rename from app/views/projects/environments/empty.html.haml
rename to app/views/projects/environments/empty_metrics.html.haml
index 129dbbf4e5..dad93290fb 100644
--- a/app/views/projects/environments/empty.html.haml
+++ b/app/views/projects/environments/empty_metrics.html.haml
@@ -7,8 +7,8 @@
.col-12
.text-content
%h4.text-center
- = s_('Metrics|No deployed environments')
+ = s_('Environments|No deployed environments')
%p.state-description
= s_('Metrics|Check out the CI/CD documentation on deploying to an environment')
.text-center
- = link_to s_("Metrics|Learn about environments"), help_page_path('ci/environments'), class: 'btn btn-success'
+ = link_to s_("Environments|Learn about environments"), help_page_path('ci/environments'), class: 'btn btn-success'
diff --git a/app/views/projects/error_tracking/details.html.haml b/app/views/projects/error_tracking/details.html.haml
new file mode 100644
index 0000000000..7015dcdcb0
--- /dev/null
+++ b/app/views/projects/error_tracking/details.html.haml
@@ -0,0 +1,4 @@
+- page_title _('Error Details')
+- add_to_breadcrumbs 'Errors', project_error_tracking_index_path(@project)
+
+#js-error_details{ data: error_details_data(@project, @issue_id) }
diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml
index 6e5e460723..a952db0eea 100644
--- a/app/views/projects/graphs/show.html.haml
+++ b/app/views/projects/graphs/show.html.haml
@@ -1,26 +1,8 @@
- page_title _('Contributors')
-.js-graphs-show{ 'data-project-graph-path': project_graph_path(@project, current_ref, format: :json) }
- .sub-header-block
- .tree-ref-holder.inline.vertical-align-middle
- = render 'shared/ref_switcher', destination: 'graphs'
- = link_to s_('Commits|History'), project_commits_path(@project, current_ref), class: 'btn'
+.sub-header-block.bg-gray-light.gl-p-3
+ .tree-ref-holder.inline.vertical-align-middle
+ = render 'shared/ref_switcher', destination: 'graphs'
+ = link_to s_('Commits|History'), project_commits_path(@project, current_ref), class: 'btn'
- .loading-graph
- .center
- %h3.page-title
- %i.fa.fa-spinner.fa-spin
- = s_('ContributorsPage|Building repository graph.')
- %p.slead
- = s_('ContributorsPage|Please wait a moment, this page will automatically refresh when ready.')
-
- .stat-graph.hide
- .header.clearfix
- %h3#date_header.page-title
- %p.light
- = s_('ContributorsPage|Commits to %{branch_name}, excluding merge commits. Limited to 6,000 commits.') % { branch_name: @ref }
- %input#brush_change{ :type => "hidden" }
- .graphs.row
- #contributors-master.svg-w-100
- #contributors.clearfix
- %ol.contributors-list.svg-w-100.row
+.js-contributors-graph{ class: container_class, 'data-project-graph-path': project_graph_path(@project, current_ref, format: :json),'data-project-branch': current_ref }
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index 367b8c1138..c8ab47888d 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -1,4 +1,5 @@
-%li{ id: dom_id(issue), class: issue_css_classes(issue), url: issue_path(issue), data: { labels: issue.label_ids, id: issue.id } }
+-# DANGER: Any changes to this file need to be reflected in issuables_list/components/issuable.vue!
+%li{ id: dom_id(issue), class: issue_css_classes(issue), url: issue_path(issue), data: { labels: issue.label_ids, id: issue.id, qa_selector: 'issue', qa_issue_title: issue.title } }
.issue-box
- if @can_bulk_update
.issue-check.hidden
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index 0328751c68..0373e37818 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -26,7 +26,7 @@
= render partial: 'shared/label', collection: @prioritized_labels, as: :label, locals: { force_priority: true, subject: @project }
- elsif search.present?
.nothing-here-block
- = _('No prioritised labels with such name or description')
+ = _('No prioritized labels with such name or description')
- if @labels.present?
.other-labels
diff --git a/app/views/projects/merge_requests/_how_to_merge.html.haml b/app/views/projects/merge_requests/_how_to_merge.html.haml
index 57205682bd..9cdbbe7204 100644
--- a/app/views/projects/merge_requests/_how_to_merge.html.haml
+++ b/app/views/projects/merge_requests/_how_to_merge.html.haml
@@ -12,8 +12,8 @@
= clipboard_button(target: "pre#merge-info-1", title: _("Copy commands"))
%pre.dark#merge-info-1
- if @merge_request.for_fork?
+ -# All repo/branch refs have been quoted to allow support for special characters (such as #my-branch)
:preserve
- -# All repo/branch refs have been quoted to allow support for special characters (such as #my-branch)
git fetch "#{h default_url_to_repo(@merge_request.source_project)}" "#{h @merge_request.source_branch}"
git checkout -b "#{h @merge_request.source_project_path}-#{h @merge_request.source_branch}" FETCH_HEAD
- else
diff --git a/app/views/projects/merge_requests/creations/_new_submit.html.haml b/app/views/projects/merge_requests/creations/_new_submit.html.haml
index 543441b947..15c83f9247 100644
--- a/app/views/projects/merge_requests/creations/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/creations/_new_submit.html.haml
@@ -1,15 +1,5 @@
%h3.page-title
New Merge Request
-%p.slead
- - source_title, target_title = format_mr_branch_names(@merge_request)
- From
- %strong.ref-name= source_title
- %span into
- %strong.ref-name= target_title
-
- %span.float-right
- = link_to 'Change branches', mr_change_branches_path(@merge_request)
-%hr
= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form common-note-form js-requires-input js-quick-submit' } do |f|
= render 'shared/issuable/form', f: f, issuable: @merge_request, commits: @commits, presenter: @mr_presenter
= f.hidden_field :source_project_id
diff --git a/app/views/projects/merge_requests/edit.html.haml b/app/views/projects/merge_requests/edit.html.haml
index 03159f123f..318c9d809c 100644
--- a/app/views/projects/merge_requests/edit.html.haml
+++ b/app/views/projects/merge_requests/edit.html.haml
@@ -2,5 +2,4 @@
%h3.page-title
Edit Merge Request #{@merge_request.to_reference}
-%hr
= render 'form'
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 49d3039d0c..5f244d3a6c 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -3,57 +3,8 @@
- page_title @milestone.title, _('Milestones')
- page_description @milestone.description
-.detail-page-header.milestone-page-header
- .status-box{ class: status_box_class(@milestone) }
- - if @milestone.closed?
- = _('Closed')
- - elsif @milestone.expired?
- = _('Past due')
- - elsif @milestone.upcoming?
- = _('Upcoming')
- - else
- = _('Open')
- .header-text-content
- %span.identifier
- %strong
- = _('Milestone')
- - if @milestone.due_date || @milestone.start_date
- = milestone_date_range(@milestone)
- .milestone-buttons
- - if can?(current_user, :admin_milestone, @project)
- = link_to edit_project_milestone_path(@project, @milestone), class: 'btn btn-grouped btn-nr' do
- = _('Edit')
-
- - if @project.group
- %button.js-promote-project-milestone-button.btn.btn-grouped{ data: { toggle: 'modal',
- target: '#promote-milestone-modal',
- milestone_title: @milestone.title,
- group_name: @project.group.name,
- url: promote_project_milestone_path(@milestone.project, @milestone),
- container: 'body' },
- disabled: true,
- type: 'button' }
- = _('Promote')
- #promote-milestone-modal
-
- - if @milestone.active?
- = link_to _('Close milestone'), project_milestone_path(@project, @milestone, milestone: {state_event: :close }), method: :put, class: 'btn btn-close btn-nr btn-grouped'
- - else
- = link_to _('Reopen milestone'), project_milestone_path(@project, @milestone, milestone: {state_event: :activate }), method: :put, class: 'btn btn-reopen btn-nr btn-grouped'
-
- = render 'shared/milestones/delete_button'
-
- %a.btn.btn-default.btn-grouped.float-right.d-block.d-sm-none.js-sidebar-toggle{ href: '#' }
- = icon('angle-double-left')
-
-.detail-page-description.milestone-detail
- %h2.title.qa-milestone-title
- = markdown_field(@milestone, :title)
-
- %div
- - if @milestone.description.present?
- .description.md
- = markdown_field(@milestone, :description)
+= render 'shared/milestones/header', milestone: @milestone
+= render 'shared/milestones/description', milestone: @milestone
= render_if_exists 'shared/milestones/burndown', milestone: @milestone, project: @project
diff --git a/app/views/projects/mirrors/_authentication_method.html.haml b/app/views/projects/mirrors/_authentication_method.html.haml
index ee82d68d39..a6978cba49 100644
--- a/app/views/projects/mirrors/_authentication_method.html.haml
+++ b/app/views/projects/mirrors/_authentication_method.html.haml
@@ -3,10 +3,12 @@
.form-group
= f.label :auth_method, _('Authentication method'), class: 'label-bold'
- = f.select :auth_method,
- options_for_select(auth_options, mirror.auth_method),
- {}, { class: "form-control js-mirror-auth-type qa-authentication-method" }
- = f.hidden_field :auth_method, value: "password", class: "js-hidden-mirror-auth-type"
+ .select-wrapper
+ = f.select :auth_method,
+ options_for_select(auth_options, mirror.auth_method),
+ {}, { class: "form-control select-control js-mirror-auth-type qa-authentication-method" }
+ = icon('chevron-down')
+ = f.hidden_field :auth_method, value: "password", class: "js-hidden-mirror-auth-type"
.form-group
.collapse.js-well-changing-auth
diff --git a/app/views/projects/mirrors/_mirror_repos_form.html.haml b/app/views/projects/mirrors/_mirror_repos_form.html.haml
index b49f1d9315..dd794e03f4 100644
--- a/app/views/projects/mirrors/_mirror_repos_form.html.haml
+++ b/app/views/projects/mirrors/_mirror_repos_form.html.haml
@@ -1,5 +1,7 @@
.form-group
= label_tag :mirror_direction, _('Mirror direction'), class: 'label-light'
- = select_tag :mirror_direction, options_for_select([[_('Push'), 'push']]), class: 'form-control js-mirror-direction qa-mirror-direction', disabled: true
+ .select-wrapper
+ = select_tag :mirror_direction, options_for_select([[_('Push'), 'push']]), class: 'form-control select-control js-mirror-direction qa-mirror-direction', disabled: true
+ = icon('chevron-down')
= render partial: "projects/mirrors/mirror_repos_push", locals: { f: f }
diff --git a/app/views/projects/pages/_access.html.haml b/app/views/projects/pages/_access.html.haml
index 178f0acc5b..08dcba2afd 100644
--- a/app/views/projects/pages/_access.html.haml
+++ b/app/views/projects/pages/_access.html.haml
@@ -13,5 +13,11 @@
- @project.pages_domains.each do |domain|
%p
= external_link(domain.url, domain.url)
+ - unless @project.public_pages?
+ .card-footer.alert-warning
+ - help_page = help_page_path('/user/project/pages/pages_access_control')
+ - link_start = ''.html_safe % { url: help_page }
+ - link_end = ' '.html_safe
+ = s_('GitLabPages|Access Control is enabled for this Pages website; only authorized users will be able to access it. To make your website publicly available, navigate to your project\'s %{strong_start}Settings > General > Visibility%{strong_end} and select %{strong_start}Everyone%{strong_end} in pages section. Read the %{link_start}documentation%{link_end} for more information.').html_safe % { link_start: link_start, link_end: link_end, strong_start: ''.html_safe, strong_end: ' '.html_safe }
.card-footer.alert-primary
= s_('GitLabPages|It may take up to 30 minutes before the site is available after the first deployment.')
diff --git a/app/views/projects/pages/_list.html.haml b/app/views/projects/pages/_list.html.haml
index b05491f2c6..4676c7399f 100644
--- a/app/views/projects/pages/_list.html.haml
+++ b/app/views/projects/pages/_list.html.haml
@@ -21,11 +21,11 @@
%span.badge.badge-danger
= s_('GitLabPages|Expired')
%div
- = link_to s_('GitLabPages|Details'), project_pages_domain_path(@project, domain), class: "btn btn-sm btn-grouped"
+ = link_to s_('GitLabPages|Edit'), edit_project_pages_domain_path(@project, domain), class: "btn btn-sm btn-grouped btn-success btn-inverted"
= link_to s_('GitLabPages|Remove'), project_pages_domain_path(@project, domain), data: { confirm: s_('GitLabPages|Are you sure?')}, method: :delete, class: "btn btn-remove btn-sm btn-grouped"
- if verification_enabled && domain.unverified?
%li.list-group-item.bs-callout-warning
- - details_link_start = "".html_safe
+ - details_link_start = " ".html_safe
- details_link_end = ' '.html_safe
= s_('GitLabPages|%{domain} is not verified. To learn how to verify ownership, visit your %{link_start}domain details%{link_end}.').html_safe % { domain: domain.domain,
link_start: details_link_start,
diff --git a/app/views/projects/pages/show.html.haml b/app/views/projects/pages/show.html.haml
index 0e1f281410..3ec8759784 100644
--- a/app/views/projects/pages/show.html.haml
+++ b/app/views/projects/pages/show.html.haml
@@ -1,23 +1,27 @@
- page_title 'Pages'
-%h3.page-title.with-button
- = s_('GitLabPages|Pages')
+- if @project.pages_enabled?
+ %h3.page-title.with-button
+ = s_('GitLabPages|Pages')
- - if can?(current_user, :update_pages, @project) && (Gitlab.config.pages.external_http || Gitlab.config.pages.external_https)
- = link_to new_project_pages_domain_path(@project), class: 'btn btn-success float-right', title: s_('GitLabPages|New Domain') do
- = s_('GitLabPages|New Domain')
+ - if can?(current_user, :update_pages, @project) && (Gitlab.config.pages.external_http || Gitlab.config.pages.external_https)
+ = link_to new_project_pages_domain_path(@project), class: 'btn btn-success float-right', title: s_('GitLabPages|New Domain') do
+ = s_('GitLabPages|New Domain')
-%p.light
- = s_('GitLabPages|With GitLab Pages you can host your static websites on GitLab. Combined with the power of GitLab CI and the help of GitLab Runner you can deploy static pages for your individual projects, your user or your group.')
-- if Gitlab.config.pages.external_https
- = render 'https_only'
+ %p.light
+ = s_('GitLabPages|With GitLab Pages you can host your static websites on GitLab. Combined with the power of GitLab CI and the help of GitLab Runner you can deploy static pages for your individual projects, your user or your group.')
+ - if Gitlab.config.pages.external_https
+ = render 'https_only'
-%hr.clearfix
+ %hr.clearfix
-= render 'access'
-= render 'use'
-- if Gitlab.config.pages.external_http || Gitlab.config.pages.external_https
- = render 'list'
+ = render 'access'
+ = render 'use'
+ - if Gitlab.config.pages.external_http || Gitlab.config.pages.external_https
+ = render 'list'
+ - else
+ = render 'no_domains'
+ = render 'destroy'
- else
- = render 'no_domains'
-= render 'destroy'
+ .bs-callout.bs-callout-warning
+ = s_('GitLabPages|GitLab Pages are disabled for this project. You can enable them on your project\'s %{strong_start}Settings > General > Visibility%{strong_end} page.').html_safe % { strong_start: ''.html_safe, strong_end: ' '.html_safe }
diff --git a/app/views/projects/pages_domains/_certificate.html.haml b/app/views/projects/pages_domains/_certificate.html.haml
index 42631fca5e..92d30e0b05 100644
--- a/app/views/projects/pages_domains/_certificate.html.haml
+++ b/app/views/projects/pages_domains/_certificate.html.haml
@@ -1,18 +1,63 @@
-- if @domain.auto_ssl_enabled?
- - if @domain.enabled?
- - if @domain.certificate_text
- %pre
- = @domain.certificate_text
- - else
- .bs-callout.bs-callout-info
- = _("GitLab is obtaining a Let's Encrypt SSL certificate for this domain. This process can take some time. Please try again later.")
+- auto_ssl_available = ::Gitlab::LetsEncrypt.enabled?
+- auto_ssl_enabled = @domain.auto_ssl_enabled?
+- auto_ssl_available_and_enabled = auto_ssl_available && auto_ssl_enabled
+- has_user_defined_certificate = @domain.certificate && @domain.certificate_user_provided?
+
+- if auto_ssl_available
+ .form-group.border-section
+ .row
+ .col-sm-2
+ = _('Certificate')
+ .col-sm-10.js-auto-ssl-toggle-container
+ %label{ for: "pages_domain_auto_ssl_enabled_button" }
+ - lets_encrypt_link_url = "https://letsencrypt.org/"
+ - lets_encrypt_link_start = "".html_safe % { lets_encrypt_link_url: lets_encrypt_link_url }
+ - lets_encrypt_link_end = " ".html_safe
+ = _("Automatic certificate management using %{lets_encrypt_link_start}Let's Encrypt%{lets_encrypt_link_end}").html_safe % { lets_encrypt_link_start: lets_encrypt_link_start, lets_encrypt_link_end: lets_encrypt_link_end }
+ %button{ type: "button", id: "pages_domain_auto_ssl_enabled_button",
+ class: "js-project-feature-toggle project-feature-toggle mt-2 #{"is-checked" if auto_ssl_available_and_enabled}",
+ "aria-label": _("Automatic certificate management using Let's Encrypt") }
+ = f.hidden_field :auto_ssl_enabled?, class: "js-project-feature-toggle-input"
+ %span.toggle-icon
+ = sprite_icon("status_success_borderless", size: 16, css_class: "toggle-icon-svg toggle-status-checked")
+ = sprite_icon("status_failed_borderless", size: 16, css_class: "toggle-icon-svg toggle-status-unchecked")
+ %p.text-secondary.mt-3
+ - docs_link_url = help_page_path("user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md")
+ - docs_link_start = "".html_safe % { docs_link_url: docs_link_url }
+ - docs_link_end = " ".html_safe
+ = _("Let's Encrypt is a free, automated, and open certificate authority (CA) that gives digital certificates in order to enable HTTPS (SSL/TLS) for websites. Learn more about Let's Encrypt configuration by following the %{docs_link_start}documentation on GitLab Pages%{docs_link_end}.").html_safe % { docs_link_url: docs_link_url, docs_link_start: docs_link_start, docs_link_end: docs_link_end }
+
+.form-group.border-section.js-shown-unless-auto-ssl{ class: ("d-none" if auto_ssl_available_and_enabled) }
+ - if has_user_defined_certificate
+ .row
+ .col-sm-10.offset-sm-2
+ .card
+ .card-header
+ = _('Certificate')
+ .d-flex.justify-content-between.align-items-center.p-3
+ %span
+ = @domain.subject || _('missing')
+ = link_to _('Remove'),
+ clean_certificate_project_pages_domain_path(@project, @domain),
+ data: { confirm: _('Are you sure?') },
+ class: 'btn btn-remove btn-sm',
+ method: :delete
- else
- .bs-callout.bs-callout-warning
- = _("A Let's Encrypt SSL certificate can not be obtained until your domain is verified.")
-- else
- - if @domain.certificate_text
- %pre
- = @domain.certificate_text
- - else
- .light
- = _("missing")
+ .row
+ .col-sm-10.offset-sm-2
+ = f.label :user_provided_certificate, _("Certificate (PEM)")
+ = f.text_area :user_provided_certificate,
+ rows: 5,
+ class: "form-control js-enabled-unless-auto-ssl",
+ disabled: auto_ssl_available_and_enabled
+ %span.help-inline.text-muted= _("Upload a certificate for your domain with all intermediates")
+ .row
+ .col-sm-10.offset-sm-2
+ = f.label :user_provided_key, _("Key (PEM)")
+ = f.text_area :user_provided_key,
+ rows: 5,
+ class: "form-control js-enabled-unless-auto-ssl",
+ disabled: auto_ssl_available_and_enabled
+ %span.help-inline.text-muted= _("Upload a private key for your certificate")
+
+= render 'lets_encrypt_callout', auto_ssl_available_and_enabled: auto_ssl_available_and_enabled
diff --git a/app/views/projects/pages_domains/_dns.html.haml b/app/views/projects/pages_domains/_dns.html.haml
new file mode 100644
index 0000000000..e4e590f0a9
--- /dev/null
+++ b/app/views/projects/pages_domains/_dns.html.haml
@@ -0,0 +1,33 @@
+- verification_enabled = Gitlab::CurrentSettings.pages_domain_verification_enabled?
+- dns_record = "#{@domain.domain} CNAME #{@domain.project.pages_subdomain}.#{Settings.pages.host}."
+
+.form-group.border-section
+ .row
+ .col-sm-2
+ = _("DNS")
+ .col-sm-10
+ .input-group
+ = text_field_tag :domain_dns, dns_record , class: "monospace js-select-on-focus form-control", readonly: true
+ .input-group-append
+ = clipboard_button(target: '#domain_dns', class: 'btn-default input-group-text d-none d-sm-block')
+ %p.form-text.text-muted
+ = _("To access this domain create a new DNS record")
+- if verification_enabled
+ - verification_record = "#{@domain.verification_domain} TXT #{@domain.keyed_verification_code}"
+ .form-group.border-section
+ .row
+ .col-sm-2
+ = _("Verification status")
+ .col-sm-10
+ .status-badge
+ - text, status = @domain.unverified? ? [_('Unverified'), 'badge-danger'] : [_('Verified'), 'badge-success']
+ .badge{ class: status }
+ = text
+ = link_to sprite_icon("redo"), verify_project_pages_domain_path(@project, @domain), method: :post, class: "btn has-tooltip", title: _("Retry verification")
+ .input-group
+ = text_field_tag :domain_verification, verification_record, class: "monospace js-select-on-focus form-control", readonly: true
+ .input-group-append
+ = clipboard_button(target: '#domain_verification', class: 'btn-default d-none d-sm-block')
+ %p.form-text.text-muted
+ - link_to_help = link_to(_('verify ownership'), help_page_path('user/project/pages/custom_domains_ssl_tls_certification/index.md', anchor: '4-verify-the-domains-ownership'))
+ = _("To %{link_to_help} of your domain, add the above key to a TXT record within to your DNS configuration.").html_safe % { link_to_help: link_to_help }
diff --git a/app/views/projects/pages_domains/_form.html.haml b/app/views/projects/pages_domains/_form.html.haml
index 4aa1e574d9..e06dab9be0 100644
--- a/app/views/projects/pages_domains/_form.html.haml
+++ b/app/views/projects/pages_domains/_form.html.haml
@@ -3,62 +3,25 @@
- @domain.errors.full_messages.each do |msg|
= msg
-.form-group.row
- .col-sm-2.col-form-label
- = f.label :domain, _("Domain")
- .col-sm-10
- = f.text_field :domain, required: true, autocomplete: "off", class: "form-control", disabled: @domain.persisted?
+.form-group.border-section
+ .row
+ - if @domain.persisted?
+ .col-sm-2
+ = _("Domain")
+ .col-sm-10
+ = external_link(@domain.url, @domain.url)
+ - else
+ .col-sm-2
+ = f.label :domain, _("Domain")
+ .col-sm-10
+ .input-group
+ = f.text_field :domain, required: true, autocomplete: "off", class: "form-control"
+
+- if @domain.persisted?
+ = render 'dns'
- if Gitlab.config.pages.external_https
-
- - auto_ssl_available = ::Gitlab::LetsEncrypt.enabled?
- - auto_ssl_enabled = @domain.auto_ssl_enabled?
- - auto_ssl_available_and_enabled = auto_ssl_available && auto_ssl_enabled
-
- - if auto_ssl_available
- .form-group.row
- .col-sm-2.col-form-label
- %label{ for: "pages_domain_auto_ssl_enabled_button" }
- - lets_encrypt_link_url = "https://letsencrypt.org/"
- - lets_encrypt_link_start = "".html_safe % { lets_encrypt_link_url: lets_encrypt_link_url }
- - lets_encrypt_link_end = " ".html_safe
- = _("Automatic certificate management using %{lets_encrypt_link_start}Let's Encrypt%{lets_encrypt_link_end}").html_safe % { lets_encrypt_link_start: lets_encrypt_link_start, lets_encrypt_link_end: lets_encrypt_link_end }
-
- .col-sm-10.js-auto-ssl-toggle-container
- %button{ type: "button", id: "pages_domain_auto_ssl_enabled_button",
- class: "js-project-feature-toggle project-feature-toggle mt-2 #{"is-checked" if auto_ssl_available_and_enabled}",
- "aria-label": _("Automatic certificate management using Let's Encrypt") }
- = f.hidden_field :auto_ssl_enabled?, class: "js-project-feature-toggle-input"
- %span.toggle-icon
- = sprite_icon("status_success_borderless", size: 16, css_class: "toggle-icon-svg toggle-status-checked")
- = sprite_icon("status_failed_borderless", size: 16, css_class: "toggle-icon-svg toggle-status-unchecked")
- %p.text-secondary.mt-3
- - docs_link_url = help_page_path("user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md")
- - docs_link_start = "".html_safe % { docs_link_url: docs_link_url }
- - docs_link_end = " ".html_safe
- = _("Let's Encrypt is a free, automated, and open certificate authority (CA) that gives digital certificates in order to enable HTTPS (SSL/TLS) for websites. Learn more about Let's Encrypt configuration by following the %{docs_link_start}documentation on GitLab Pages%{docs_link_end}.").html_safe % { docs_link_url: docs_link_url, docs_link_start: docs_link_start, docs_link_end: docs_link_end }
-
- .js-shown-unless-auto-ssl{ class: ("d-none" if auto_ssl_available_and_enabled) }
- .form-group.row
- .col-sm-2.col-form-label
- = f.label :user_provided_certificate, _("Certificate (PEM)")
- .col-sm-10
- = f.text_area :user_provided_certificate,
- rows: 5,
- class: "form-control js-enabled-unless-auto-ssl",
- disabled: auto_ssl_available_and_enabled
- %span.help-inline.text-muted= _("Upload a certificate for your domain with all intermediates")
-
- .form-group.row
- .col-sm-2.col-form-label
- = f.label :user_provided_key, _("Key (PEM)")
- .col-sm-10
- = f.text_area :user_provided_key,
- rows: 5,
- class: "form-control js-enabled-unless-auto-ssl",
- disabled: auto_ssl_available_and_enabled
- %span.help-inline.text-muted= _("Upload a private key for your certificate")
-
+ = render 'certificate', f: f
- else
- .nothing-here-block
+ .border-section.nothing-here-block
= _("Support for custom certificates is disabled. Ask your system's administrator to enable it.")
diff --git a/app/views/projects/pages_domains/_lets_encrypt_callout.html.haml b/app/views/projects/pages_domains/_lets_encrypt_callout.html.haml
new file mode 100644
index 0000000000..d6406a78fc
--- /dev/null
+++ b/app/views/projects/pages_domains/_lets_encrypt_callout.html.haml
@@ -0,0 +1,13 @@
+- if @domain.enabled?
+ - if @domain.auto_ssl_enabled && !@domain.certificate
+ .form-group.border-section.js-shown-if-auto-ssl{ class: ("d-none" unless auto_ssl_available_and_enabled) }
+ .row
+ .col-sm-10.offset-sm-2
+ .bs-callout.bs-callout-info.mt-0
+ = _("GitLab is obtaining a Let's Encrypt SSL certificate for this domain. This process can take some time. Please try again later.")
+- else
+ .form-group.border-section.js-shown-if-auto-ssl{ class: ("d-none" unless auto_ssl_available_and_enabled) }
+ .row
+ .col-sm-10.offset-sm-2
+ .bs-callout.bs-callout-warning.mt-0
+ = _("A Let's Encrypt SSL certificate can not be obtained until your domain is verified.")
diff --git a/app/views/projects/pages_domains/edit.html.haml b/app/views/projects/pages_domains/edit.html.haml
index 7c0777e549..a08be65d7e 100644
--- a/app/views/projects/pages_domains/edit.html.haml
+++ b/app/views/projects/pages_domains/edit.html.haml
@@ -1,12 +1,21 @@
- add_to_breadcrumbs _("Pages"), project_pages_path(@project)
- breadcrumb_title @domain.domain
- page_title @domain.domain
+
+- verification_enabled = Gitlab::CurrentSettings.pages_domain_verification_enabled?
+
+- if verification_enabled && @domain.unverified?
+ = content_for :flash_message do
+ .alert.alert-warning
+ .container-fluid.container-limited
+ = _("This domain is not verified. You will need to verify ownership before access is enabled.")
+
%h3.page-title
- = @domain.domain
+ = _('Pages Domain')
= render 'projects/pages_domains/helper_text'
-%hr.clearfix
%div
= form_for [@project.namespace.becomes(Namespace), @project, @domain], html: { class: 'fieldset-form' } do |f|
= render 'form', { f: f }
- .form-actions
+ .form-actions.d-flex.justify-content-between
= f.submit _('Save Changes'), class: "btn btn-success"
+ = link_to _('Cancel'), project_pages_path(@project), class: 'btn btn-default btn-inverse'
diff --git a/app/views/projects/pages_domains/new.html.haml b/app/views/projects/pages_domains/new.html.haml
index e23ccb5d4c..3210bfe923 100644
--- a/app/views/projects/pages_domains/new.html.haml
+++ b/app/views/projects/pages_domains/new.html.haml
@@ -3,7 +3,6 @@
%h3.page-title
= _("New Pages Domain")
= render 'projects/pages_domains/helper_text'
-%hr.clearfix
%div
= form_for [@project.namespace.becomes(Namespace), @project, @domain], html: { class: 'fieldset-form' } do |f|
= render 'form', { f: f }
diff --git a/app/views/projects/pages_domains/show.html.haml b/app/views/projects/pages_domains/show.html.haml
index 33837e21c8..8eec3d5183 100644
--- a/app/views/projects/pages_domains/show.html.haml
+++ b/app/views/projects/pages_domains/show.html.haml
@@ -58,4 +58,4 @@
%td
= _("Certificate")
%td
- = render 'certificate'
+ = render 'lets_encrypt_callout', auto_ssl_available_and_enabled: false
diff --git a/app/views/projects/pipelines/_with_tabs.html.haml b/app/views/projects/pipelines/_with_tabs.html.haml
index 8c3518e3a2..4d8cba5168 100644
--- a/app/views/projects/pipelines/_with_tabs.html.haml
+++ b/app/views/projects/pipelines/_with_tabs.html.haml
@@ -1,3 +1,5 @@
+- test_reports_enabled = Feature.enabled?(:junit_pipeline_view)
+
.tabs-holder
%ul.pipelines-tabs.nav-links.no-top.no-bottom.mobile-separator.nav.nav-tabs
%li.js-pipeline-tab-link
@@ -12,6 +14,11 @@
= link_to failures_project_pipeline_path(@project, @pipeline), data: { target: '#js-tab-failures', action: 'failures', toggle: 'tab' }, class: 'failures-tab' do
= _('Failed Jobs')
%span.badge.badge-pill.js-failures-counter= @pipeline.failed_builds.count
+ - if test_reports_enabled
+ %li.js-tests-tab-link
+ = link_to test_report_project_pipeline_path(@project, @pipeline), data: { target: '#js-tab-tests', action: 'test_report', toggle: 'tab' }, class: 'test-tab' do
+ = s_('TestReports|Tests')
+ %span.badge.badge-pill= pipeline.test_reports.total_count
= render_if_exists "projects/pipelines/tabs_holder", pipeline: @pipeline, project: @project
.tab-content
@@ -32,10 +39,6 @@
%th
= render partial: "projects/stage/stage", collection: pipeline.legacy_stages, as: :stage
- - elsif pipeline.project.builds_enabled? && !pipeline.ci_yaml_file
- .bs-callout.bs-callout-warning
- = _("%{gitlab_ci_yml} not found in this commit") % { gitlab_ci_yml: ".gitlab-ci.yml" }
-
- if @pipeline.failed_builds.present?
#js-tab-failures.build-failures.tab-pane.build-page
%table.table.responsive-table.ci-table.responsive-table-sm-rounded
@@ -71,4 +74,7 @@
%pre.build-trace.build-trace-rounded
%code.bash.js-build-output
= build_summary(build)
+
+ #js-tab-tests.tab-pane
+ #js-pipeline-tests-detail
= render_if_exists "projects/pipelines/tabs_content", pipeline: @pipeline, project: @project
diff --git a/app/views/projects/pipelines/new.html.haml b/app/views/projects/pipelines/new.html.haml
index bfcaa09ae8..a3e46a0939 100644
--- a/app/views/projects/pipelines/new.html.haml
+++ b/app/views/projects/pipelines/new.html.haml
@@ -23,6 +23,13 @@
%label
= s_('Pipeline|Variables')
%ul.ci-variable-list
+ - if params[:var]
+ - params[:var].each do |variable|
+ = render 'ci/variables/url_query_variable_row', form_field: 'pipeline', variable: variable
+ - if params[:file_var]
+ - params[:file_var].each do |variable|
+ - variable.push("file")
+ = render 'ci/variables/url_query_variable_row', form_field: 'pipeline', variable: variable
= render 'ci/variables/variable_row', form_field: 'pipeline', only_key_value: true
.form-text.text-muted
= (s_("Pipeline|Specify variable values to be used in this run. The values specified in %{settings_link} will be used by default.") % {settings_link: settings_link}).html_safe
diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml
index 2b2133b829..f0b3ab24ea 100644
--- a/app/views/projects/pipelines/show.html.haml
+++ b/app/views/projects/pipelines/show.html.haml
@@ -20,4 +20,5 @@
- else
= render "projects/pipelines/with_tabs", pipeline: @pipeline
-.js-pipeline-details-vue{ data: { endpoint: project_pipeline_path(@project, @pipeline, format: :json) } }
+.js-pipeline-details-vue{ data: { endpoint: project_pipeline_path(@project, @pipeline, format: :json),
+ test_report_endpoint: test_report_project_pipeline_path(@project, @pipeline, format: :json) } }
diff --git a/app/views/projects/protected_branches/shared/_branches_list.html.haml b/app/views/projects/protected_branches/shared/_branches_list.html.haml
index 9dff251101..f07de81d7f 100644
--- a/app/views/projects/protected_branches/shared/_branches_list.html.haml
+++ b/app/views/projects/protected_branches/shared/_branches_list.html.haml
@@ -5,6 +5,7 @@
%p.settings-message.text-center
= s_("ProtectedBranch|There are currently no protected branches, protect a branch with the form above.")
- else
+ .flash-container
%table.table.table-bordered
%colgroup
%col{ width: "20%" }
@@ -27,8 +28,6 @@
- if can_admin_project
%th
%tbody
- %tr
- %td.flash-container{ colspan: 5 }
= yield
= paginate @protected_branches, theme: 'gitlab'
diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml
index 959a2423e0..582f3d6fce 100644
--- a/app/views/projects/services/_form.html.haml
+++ b/app/views/projects/services/_form.html.haml
@@ -6,7 +6,6 @@
- hide_class = 'd-none' if @service.activated? != value
%span.js-service-active-status{ class: hide_class, data: { value: value.to_s } }
= boolean_to_icon value
- %p= #{@service.description}.
- if @service.respond_to?(:detailed_description)
%p= @service.detailed_description
diff --git a/app/views/projects/services/_index.html.haml b/app/views/projects/services/_index.html.haml
index 7748a7a6a8..3f33d72d3e 100644
--- a/app/views/projects/services/_index.html.haml
+++ b/app/views/projects/services/_index.html.haml
@@ -21,7 +21,7 @@
%td{ "aria-label" => (service.activated? ? s_("ProjectService|%{service_title}: status on") : s_("ProjectService|%{service_title}: status off")) % { service_title: service.title } }
= boolean_to_icon service.activated?
%td
- = link_to edit_project_service_path(@project, service.to_param) do
+ = link_to edit_project_service_path(@project, service.to_param), { data: { qa_selector: "#{service.title.downcase.gsub(/[\s\(\)]/,'_')}_link" } } do
%strong= service.title
%td.d-none.d-sm-block
= service.description
diff --git a/app/views/projects/services/edit.html.haml b/app/views/projects/services/edit.html.haml
index fc20bc52d1..1e7903535c 100644
--- a/app/views/projects/services/edit.html.haml
+++ b/app/views/projects/services/edit.html.haml
@@ -1,6 +1,7 @@
-- breadcrumb_title s_("ProjectService|Integrations")
+- breadcrumb_title @service.title
- page_title @service.title, s_("ProjectService|Services")
- add_to_breadcrumbs(s_("ProjectService|Settings"), edit_project_path(@project))
+- add_to_breadcrumbs(s_("ProjectService|Integrations"), namespace_project_settings_integrations_path)
= render 'deprecated_message' if @service.deprecation_message
diff --git a/app/views/projects/settings/ci_cd/_form.html.haml b/app/views/projects/settings/ci_cd/_form.html.haml
index 66ed1cadf6..ea815be23c 100644
--- a/app/views/projects/settings/ci_cd/_form.html.haml
+++ b/app/views/projects/settings/ci_cd/_form.html.haml
@@ -98,7 +98,7 @@
%span.input-group-append
.input-group-text /
%p.form-text.text-muted
- = _("A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable")
+ = _("A regular expression that will be used to find the test coverage output in the job log. Leave blank to disable")
= link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'test-coverage-parsing'), target: '_blank'
.bs-callout.bs-callout-info
%p= _("Below are examples of regex for existing tools:")
diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml
index 87000e8270..862db23e85 100644
--- a/app/views/projects/settings/ci_cd/show.html.haml
+++ b/app/views/projects/settings/ci_cd/show.html.haml
@@ -37,7 +37,8 @@
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
- = _("Register and see your runners for this project.")
+ = _("Runners are processes that pick up and execute jobs for GitLab. Here you can register and see your Runners for this project.")
+ = link_to s_('More information'), help_page_path('ci/runners/README')
.settings-content
= render 'projects/runners/index'
diff --git a/app/views/projects/settings/operations/_grafana_integration.html.haml b/app/views/projects/settings/operations/_grafana_integration.html.haml
new file mode 100644
index 0000000000..cd5b5abd9c
--- /dev/null
+++ b/app/views/projects/settings/operations/_grafana_integration.html.haml
@@ -0,0 +1,2 @@
+.js-grafana-integration{ data: { operations_settings_endpoint: project_settings_operations_path(@project),
+ grafana_integration: { url: grafana_integration_url, token: grafana_integration_token, enabled: grafana_integration_enabled?.to_s } } }
diff --git a/app/views/projects/settings/operations/show.html.haml b/app/views/projects/settings/operations/show.html.haml
index 0a7a155bc1..3c955e5f55 100644
--- a/app/views/projects/settings/operations/show.html.haml
+++ b/app/views/projects/settings/operations/show.html.haml
@@ -5,4 +5,5 @@
= render_if_exists 'projects/settings/operations/incidents'
= render 'projects/settings/operations/error_tracking'
= render 'projects/settings/operations/external_dashboard'
+= render 'projects/settings/operations/grafana_integration'
= render_if_exists 'projects/settings/operations/tracing'
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index b58af54543..c5653c3dd5 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -6,7 +6,7 @@
= render partial: 'flash_messages', locals: { project: @project }
-- if !@project.empty_repo? && can?(current_user, :download_code, @project)
+- if !@project.empty_repo? && can?(current_user, :download_code, @project) && !vue_file_list_enabled?
- signatures_path = project_signatures_path(@project, @project.default_branch)
.js-signature-container{ data: { 'signatures-path': signatures_path } }
diff --git a/app/views/projects/tree/_readme.html.haml b/app/views/projects/tree/_readme.html.haml
index 4f6c7e1f9a..fef019e1b6 100644
--- a/app/views/projects/tree/_readme.html.haml
+++ b/app/views/projects/tree/_readme.html.haml
@@ -1,5 +1,5 @@
- if readme.rich_viewer
- %article.file-holder.readme-holder{ id: 'readme', class: [("limited-width-container" unless fluid_layout), ("js-hide-on-navigation" if vue_file_list_enabled?)] }
+ %article.file-holder.readme-holder{ id: 'readme', class: [("limited-width-container" unless fluid_layout), ("js-show-on-root" if vue_file_list_enabled?)] }
.js-file-title.file-title
= blob_icon readme.mode, readme.name
= link_to project_blob_path(@project, tree_join(@ref, readme.path)) do
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index 38422d4533..127734ddfd 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -77,15 +77,21 @@
.tree-controls
= render_if_exists 'projects/tree/lock_link'
- = link_to s_('Commits|History'), project_commits_path(@project, @id), class: 'btn'
+ - if vue_file_list_enabled?
+ #js-tree-history-link.d-inline-block{ data: { history_link: project_commits_path(@project, @ref) } }
+ - else
+ = link_to s_('Commits|History'), project_commits_path(@project, @id), class: 'btn'
= render 'projects/find_file_link'
- if can_create_mr_from_fork
= succeed " " do
- if can_collaborate || current_user&.already_forked?(@project)
- = link_to ide_edit_path(@project, @ref, @path), class: 'btn btn-default qa-web-ide-button' do
- = _('Web IDE')
+ - if vue_file_list_enabled?
+ #js-tree-web-ide-link.d-inline-block
+ - else
+ = link_to ide_edit_path(@project, @ref, @path), class: 'btn btn-default qa-web-ide-button' do
+ = _('Web IDE')
- else
= link_to '#modal-confirm-fork', class: 'btn btn-default qa-web-ide-button', data: { target: '#modal-confirm-fork', toggle: 'modal'} do
= _('Web IDE')
diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml
index 39b29a20df..65f5bc31d2 100644
--- a/app/views/projects/tree/show.html.haml
+++ b/app/views/projects/tree/show.html.haml
@@ -6,7 +6,8 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits")
-.js-signature-container{ data: { 'signatures-path': signatures_path } }
+- unless vue_file_list_enabled?
+ .js-signature-container{ data: { 'signatures-path': signatures_path } }
= render 'projects/last_push'
= render 'projects/files', commit: @last_commit, project: @project, ref: @ref, content_url: project_tree_path(@project, @id)
diff --git a/app/views/registrations/welcome.html.haml b/app/views/registrations/welcome.html.haml
index 02ab974ecc..7b92f5070d 100644
--- a/app/views/registrations/welcome.html.haml
+++ b/app/views/registrations/welcome.html.haml
@@ -1,10 +1,10 @@
-- content_for(:page_title, _('Welcome to GitLab %{username}!' % { username: html_escape(current_user.username) }).html_safe)
+- content_for(:page_title, _('Welcome to GitLab @%{username}!') % { username: current_user.username })
- max_name_length = 128
.text-center.mb-3
- = _('In order to tailor your experience with GitLab we would like to know a bit more about you.').html_safe
+ = _('In order to tailor your experience with GitLab we would like to know a bit more about you.').html_safe
.signup-box.p-3.mb-2
.signup-body
- = form_for(current_user, url: users_sign_up_update_role_path, html: { class: 'new_new_user gl-show-field-errors', 'aria-live' => 'assertive' }) do |f|
+ = form_for(current_user, url: users_sign_up_update_registration_path, html: { class: 'new_new_user gl-show-field-errors', 'aria-live' => 'assertive' }) do |f|
.devise-errors.mt-0
= render 'devise/shared/error_messages', resource: current_user
.name.form-group
@@ -13,5 +13,14 @@
.form-group
= f.label :role, _('Role'), class: 'label-bold'
= f.select :role, ::User.roles.keys.map { |role| [role.titleize, role] }, {}, class: 'form-control'
+ .form-group
+ = f.label :setup_for_company, _('Are you setting up GitLab for a company?'), class: 'label-bold'
+ .d-flex.justify-content-center
+ .w-25
+ = f.radio_button :setup_for_company, true
+ = f.label :setup_for_company, _('Yes'), value: 'true'
+ .w-25
+ = f.radio_button :setup_for_company, false
+ = f.label :setup_for_company, _('No'), value: 'false'
.submit-container.mt-3
= f.submit _('Get started!'), class: 'btn-register btn btn-block mb-0 p-2'
diff --git a/app/views/search/_category.html.haml b/app/views/search/_category.html.haml
index eae2a491ce..84198489e4 100644
--- a/app/views/search/_category.html.haml
+++ b/app/views/search/_category.html.haml
@@ -1,5 +1,5 @@
- users = capture_haml do
- - if search_tabs?(:members)
+ - if show_user_search_tab?
= search_filter_link 'users', _("Users")
.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml
index de9947528c..629a5a045b 100644
--- a/app/views/search/_results.html.haml
+++ b/app/views/search/_results.html.haml
@@ -1,6 +1,7 @@
- if @search_objects.to_a.empty?
= render partial: "search/results/empty"
= render_if_exists 'shared/promotions/promote_advanced_search'
+ = render_if_exists 'search/form_revert_to_basic'
- else
.row-content-block.d-md-flex.text-left.align-items-center
- unless @search_objects.is_a?(Kaminari::PaginatableWithoutCount)
diff --git a/app/views/search/results/_blob.html.haml b/app/views/search/results/_blob.html.haml
index bdad07f36d..4fb72b2695 100644
--- a/app/views/search/results/_blob.html.haml
+++ b/app/views/search/results/_blob.html.haml
@@ -2,6 +2,6 @@
- return unless project
- blob = parse_search_result(blob)
-- blob_link = project_blob_path(project, tree_join(blob.ref, blob.filename))
+- blob_link = project_blob_path(project, tree_join(blob.ref, blob.path))
-= render partial: 'search/results/blob_data', locals: { blob: blob, project: project, file_name: blob.filename, blob_link: blob_link }
+= render partial: 'search/results/blob_data', locals: { blob: blob, project: project, path: blob.path, blob_link: blob_link }
diff --git a/app/views/search/results/_blob_data.html.haml b/app/views/search/results/_blob_data.html.haml
index 36b6ea7bd3..01e4222442 100644
--- a/app/views/search/results/_blob_data.html.haml
+++ b/app/views/search/results/_blob_data.html.haml
@@ -4,7 +4,7 @@
= link_to blob_link do
%i.fa.fa-file
%strong
- = search_blob_title(project, file_name)
+ = search_blob_title(project, path)
- if blob.data
.file-content.code.term{ data: { qa_selector: 'file_text_content' } }
= render 'shared/file_highlight', blob: blob, first_line_number: blob.startline
diff --git a/app/views/search/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml
index f17dae0a94..37f4efee9d 100644
--- a/app/views/search/results/_snippet_blob.html.haml
+++ b/app/views/search/results/_snippet_blob.html.haml
@@ -1,6 +1,7 @@
- snippet_blob = chunk_snippet(snippet_blob, @search_term)
- snippet = snippet_blob[:snippet_object]
- snippet_chunks = snippet_blob[:snippet_chunks]
+- snippet_path = reliable_snippet_path(snippet)
.search-result-row
%span
@@ -11,7 +12,6 @@
= snippet.author_name
%span.light= time_ago_with_tooltip(snippet.created_at)
%h4.snippet-title
- - snippet_path = reliable_snippet_path(snippet)
.file-holder
.js-file-title.file-title
= link_to snippet_path do
diff --git a/app/views/search/results/_snippet_title.html.haml b/app/views/search/results/_snippet_title.html.haml
index 1e01088d9e..7280146720 100644
--- a/app/views/search/results/_snippet_title.html.haml
+++ b/app/views/search/results/_snippet_title.html.haml
@@ -2,10 +2,7 @@
%h4.snippet-title.term
= link_to reliable_snippet_path(snippet_title) do
= truncate(snippet_title.title, length: 60)
- - if snippet_title.private?
- %span.badge.badge-gray
- %i.fa.fa-lock
- = _("private")
+ = snippet_badge(snippet_title)
%span.cgray.monospace.tiny.float-right.term
= snippet_title.file_name
diff --git a/app/views/search/results/_wiki_blob.html.haml b/app/views/search/results/_wiki_blob.html.haml
index b351ecd4ed..9afed2bbec 100644
--- a/app/views/search/results/_wiki_blob.html.haml
+++ b/app/views/search/results/_wiki_blob.html.haml
@@ -2,4 +2,4 @@
- wiki_blob = parse_search_result(wiki_blob)
- wiki_blob_link = project_wiki_path(project, wiki_blob.basename)
-= render partial: 'search/results/blob_data', locals: { blob: wiki_blob, project: project, file_name: wiki_blob.filename, blob_link: wiki_blob_link }
+= render partial: 'search/results/blob_data', locals: { blob: wiki_blob, project: project, path: wiki_blob.path, blob_link: wiki_blob_link }
diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml
index cb83487827..3e80518905 100644
--- a/app/views/shared/_clone_panel.html.haml
+++ b/app/views/shared/_clone_panel.html.haml
@@ -22,6 +22,3 @@
.input-group-append
= clipboard_button(target: '#project_clone', title: _("Copy URL"), class: "input-group-text btn-default btn-clipboard")
- = render_if_exists 'shared/geo_modal_button'
-
-= render_if_exists 'shared/geo_modal', project: project
diff --git a/app/views/shared/_field.html.haml b/app/views/shared/_field.html.haml
index 606d0f241a..a7ad6d6f2c 100644
--- a/app/views/shared/_field.html.haml
+++ b/app/views/shared/_field.html.haml
@@ -16,7 +16,7 @@
= form.label name, title, class: "col-form-label col-sm-2"
.col-sm-10
- if type == 'text'
- = form.text_field name, class: "form-control", placeholder: placeholder, required: required, disabled: disabled
+ = form.text_field name, class: "form-control", placeholder: placeholder, required: required, disabled: disabled, data: { qa_selector: "#{name.downcase.gsub('\s', '')}_field" }
- elsif type == 'textarea'
= form.text_area name, rows: 5, class: "form-control", placeholder: placeholder, required: required, disabled: disabled
- elsif type == 'checkbox'
@@ -24,6 +24,6 @@
- elsif type == 'select'
= form.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control", disabled: disabled}
- elsif type == 'password'
- = form.password_field name, autocomplete: "new-password", placeholder: placeholder, class: "form-control", required: value.blank? && required, disabled: disabled
+ = form.password_field name, autocomplete: "new-password", placeholder: placeholder, class: "form-control", required: value.blank? && required, disabled: disabled, data: { qa_selector: "#{name.downcase.gsub('\s', '')}_field" }
- if help
%span.form-text.text-muted= help
diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml
index 959792718c..9a65981ed5 100644
--- a/app/views/shared/_group_form.html.haml
+++ b/app/views/shared/_group_form.html.haml
@@ -22,11 +22,16 @@
- if parent
%strong= parent.full_path + '/'
= f.hidden_field :parent_id
- = f.text_field :path, placeholder: 'my-awesome-group', class: 'form-control',
+ = f.text_field :path, placeholder: 'my-awesome-group', class: 'form-control js-validate-group-path',
autofocus: local_assigns[:autofocus] || false, required: true,
pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS,
title: _('Please choose a group URL with no special characters.'),
"data-bind-in" => "#{'create_chat_team' if Gitlab.config.mattermost.enabled}"
+ %p.validation-error.gl-field-error.field-validation.hide
+ = _('Group path is already taken. Suggestions: ')
+ %span.gl-path-suggestions
+ %p.validation-success.gl-field-success.field-validation.hide= _('Group path is available.')
+ %p.validation-pending.gl-field-error-ignore.field-validation.hide= _('Checking group path availability...')
- if @group.persisted?
.alert.alert-warning.prepend-top-10
diff --git a/app/views/shared/_mobile_clone_panel.html.haml b/app/views/shared/_mobile_clone_panel.html.haml
index 1e6b6f7c79..2887acf7cd 100644
--- a/app/views/shared/_mobile_clone_panel.html.haml
+++ b/app/views/shared/_mobile_clone_panel.html.haml
@@ -4,7 +4,7 @@
.btn-group.mobile-git-clone.js-mobile-git-clone.btn-block
= clipboard_button(button_text: default_clone_label, text: default_url_to_repo(project), hide_button_icon: true, class: "btn-primary flex-fill bold justify-content-center input-group-text clone-dropdown-btn js-clone-dropdown-label")
- %button.btn.btn-primary.dropdown-toggle.js-dropdown-toggle{ type: "button", data: { toggle: "dropdown" } }
+ %button.btn.btn-primary.dropdown-toggle.js-dropdown-toggle.flex-grow-0.d-flex-center{ type: "button", data: { toggle: "dropdown" } }
= sprite_icon("arrow-down", css_class: "dropdown-btn-icon icon")
%ul.dropdown-menu.dropdown-menu-selectable.dropdown-menu-right.clone-options-dropdown{ data: { dropdown: true } }
- if ssh_enabled?
diff --git a/app/views/shared/_personal_access_tokens_form.html.haml b/app/views/shared/_personal_access_tokens_form.html.haml
index 1d96feda3b..ca0b473add 100644
--- a/app/views/shared/_personal_access_tokens_form.html.haml
+++ b/app/views/shared/_personal_access_tokens_form.html.haml
@@ -19,7 +19,6 @@
= f.label :expires_at, _('Expires at'), class: 'label-bold'
.input-icon-wrapper
= f.text_field :expires_at, class: "datepicker form-control", placeholder: 'YYYY-MM-DD'
- = icon('calendar', { class: 'input-icon-right' })
.form-group
= f.label :scopes, _('Scopes'), class: 'label-bold'
diff --git a/app/views/shared/_service_settings.html.haml b/app/views/shared/_service_settings.html.haml
index 6fa61c1549..627a1eb6ea 100644
--- a/app/views/shared/_service_settings.html.haml
+++ b/app/views/shared/_service_settings.html.haml
@@ -12,7 +12,7 @@
.form-group.row
= form.label :active, "Active", class: "col-form-label col-sm-2"
.col-sm-10
- = form.check_box :active, disabled: disable_fields_service?(@service)
+ = form.check_box :active, disabled: disable_fields_service?(@service), data: { qa_selector: 'active_checkbox' }
- if @service.configurable_events.present?
.form-group.row
diff --git a/app/views/shared/form_elements/_description.html.haml b/app/views/shared/form_elements/_description.html.haml
index be78fd0ccf..9db6184ebc 100644
--- a/app/views/shared/form_elements/_description.html.haml
+++ b/app/views/shared/form_elements/_description.html.haml
@@ -2,6 +2,7 @@
- model = local_assigns.fetch(:model)
- form = local_assigns.fetch(:form)
+- placeholder = model.is_a?(MergeRequest) ? _('Describe the goal of the changes and what reviewers should be aware of.') : _('Write a comment or drag your files here…')
- supports_quick_actions = model.new_record?
- if supports_quick_actions
@@ -16,7 +17,7 @@
= render layout: 'projects/md_preview', locals: { url: preview_url, referenced_users: true } do
= render 'projects/zen', f: form, attr: :description,
classes: 'note-textarea qa-issuable-form-description rspec-issuable-form-description',
- placeholder: "Write a comment or drag your files here…",
+ placeholder: placeholder,
supports_quick_actions: supports_quick_actions
= render 'shared/notes/hints', supports_quick_actions: supports_quick_actions
.clearfix
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 5e2b5f95ee..0fb23adc31 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -12,6 +12,8 @@
= link_to "the #{issuable.class.model_name.human.downcase}", polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), target: "_blank", rel: 'noopener noreferrer'
and make sure your changes will not unintentionally remove theirs
+= render 'shared/issuable/form/branch_chooser', issuable: issuable, form: form
+
.form-group.row
= form.label :title, class: 'col-form-label col-sm-2'
@@ -34,8 +36,6 @@
= render_if_exists 'shared/issuable/approvals', issuable: issuable, presenter: presenter, form: form
-= render 'shared/issuable/form/branch_chooser', issuable: issuable, form: form
-
= render 'shared/issuable/form/merge_params', issuable: issuable
= render 'shared/issuable/form/contribution', issuable: issuable, form: form
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index 9d580930fb..d341520e4a 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -5,155 +5,170 @@
- user_can_admin_list = board && can?(current_user, :admin_list, board.resource_parent)
.issues-filters{ class: ("w-100" if type == :boards_modal) }
- .issues-details-filters.filtered-search-block.d-flex.flex-column.flex-md-row{ class: block_css_class, "v-pre" => type == :boards_modal }
- - if type == :boards
- = render "shared/boards/switcher", board: board
- = form_tag page_filter_path, method: :get, class: 'filter-form js-filter-form w-100' do
- - if params[:search].present?
- = hidden_field_tag :search, params[:search]
- - if @can_bulk_update
- .check-all-holder.d-none.d-sm-block.hidden
- = check_box_tag "check-all-issues", nil, false, class: "check-all-issues left"
- .issues-other-filters.filtered-search-wrapper.d-flex.flex-column.flex-md-row
- .filtered-search-box
- - if type != :boards_modal && type != :boards
- = dropdown_tag(_('Recent searches'),
- options: { wrapper_class: "filtered-search-history-dropdown-wrapper",
- toggle_class: "filtered-search-history-dropdown-toggle-button",
- dropdown_class: "filtered-search-history-dropdown",
- content_class: "filtered-search-history-dropdown-content" }) do
- .js-filtered-search-history-dropdown{ data: { full_path: search_history_storage_prefix } }
- .filtered-search-box-input-container.droplab-dropdown
- .scroll-container
- %ul.tokens-container.list-unstyled
- %li.input-token
- %input.form-control.filtered-search{ search_filter_input_options(type) }
- #js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown
- %ul{ data: { dropdown: true } }
- %li.filter-dropdown-item{ data: { action: 'submit' } }
- %button.btn.btn-link{ type: 'button' }
- = sprite_icon('search')
- %span
- = _('Press Enter or click to search')
- %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
- %li.filter-dropdown-item
- %button.btn.btn-link{ type: 'button' }
- -# Encapsulate static class name `{{icon}}` inside #{} to bypass
- -# haml lint's ClassAttributeWithStaticValue
- %svg
- %use{ 'xlink:href': "#{'{{icon}}'}" }
- %span.js-filter-hint
- {{hint}}
- %span.js-filter-tag.dropdown-light-content
- {{tag}}
- #js-dropdown-author.filtered-search-input-dropdown-menu.dropdown-menu
- - if current_user
+ .issues-details-filters.filtered-search-block.d-flex.flex-column.flex-lg-row{ class: block_css_class, "v-pre" => type == :boards_modal }
+ .d-flex.flex-column.flex-md-row.flex-grow-1.mb-lg-0.mb-md-2.mb-sm-0
+ - if type == :boards
+ = render "shared/boards/switcher", board: board
+ = form_tag page_filter_path, method: :get, class: 'filter-form js-filter-form w-100' do
+ - if params[:search].present?
+ = hidden_field_tag :search, params[:search]
+ - if @can_bulk_update
+ .check-all-holder.d-none.d-sm-block.hidden
+ = check_box_tag "check-all-issues", nil, false, class: "check-all-issues left"
+ .issues-other-filters.filtered-search-wrapper.d-flex.flex-column.flex-md-row
+ .filtered-search-box
+ - if type != :boards_modal && type != :boards
+ = dropdown_tag(_('Recent searches'),
+ options: { wrapper_class: "filtered-search-history-dropdown-wrapper",
+ toggle_class: "filtered-search-history-dropdown-toggle-button",
+ dropdown_class: "filtered-search-history-dropdown",
+ content_class: "filtered-search-history-dropdown-content" }) do
+ .js-filtered-search-history-dropdown{ data: { full_path: search_history_storage_prefix } }
+ .filtered-search-box-input-container.droplab-dropdown
+ .scroll-container
+ %ul.tokens-container.list-unstyled
+ %li.input-token
+ %input.form-control.filtered-search{ search_filter_input_options(type) }
+ #js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown
%ul{ data: { dropdown: true } }
- = render 'shared/issuable/user_dropdown_item',
- user: current_user
- %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
- = render 'shared/issuable/user_dropdown_item',
- user: User.new(username: '{{username}}', name: '{{name}}'),
- avatar: { lazy: true, url: '{{avatar_url}}' }
- #js-dropdown-assignee.filtered-search-input-dropdown-menu.dropdown-menu
- %ul{ data: { dropdown: true } }
- %li.filter-dropdown-item{ data: { value: 'None' } }
- %button.btn.btn-link{ type: 'button' }
- = _('None')
- %li.filter-dropdown-item{ data: { value: 'Any' } }
- %button.btn.btn-link{ type: 'button' }
- = _('Any')
- %li.divider.droplab-item-ignore
+ %li.filter-dropdown-item{ data: { action: 'submit' } }
+ %button.btn.btn-link{ type: 'button' }
+ = sprite_icon('search')
+ %span
+ = _('Press Enter or click to search')
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
+ %li.filter-dropdown-item
+ %button.btn.btn-link{ type: 'button' }
+ -# Encapsulate static class name `{{icon}}` inside #{} to bypass
+ -# haml lint's ClassAttributeWithStaticValue
+ %svg
+ %use{ 'xlink:href': "#{'{{icon}}'}" }
+ %span.js-filter-hint
+ {{hint}}
+ %span.js-filter-tag.dropdown-light-content
+ {{tag}}
+ #js-dropdown-author.filtered-search-input-dropdown-menu.dropdown-menu
- if current_user
+ %ul{ data: { dropdown: true } }
+ = render 'shared/issuable/user_dropdown_item',
+ user: current_user
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
= render 'shared/issuable/user_dropdown_item',
- user: current_user
- %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
- = render 'shared/issuable/user_dropdown_item',
- user: User.new(username: '{{username}}', name: '{{name}}'),
- avatar: { lazy: true, url: '{{avatar_url}}' }
- = render_if_exists 'shared/issuable/approver_dropdown'
- #js-dropdown-milestone.filtered-search-input-dropdown-menu.dropdown-menu
- %ul{ data: { dropdown: true } }
- %li.filter-dropdown-item{ data: { value: 'None' } }
- %button.btn.btn-link{ type: 'button' }
- = _('None')
- %li.filter-dropdown-item{ data: { value: 'Any' } }
- %button.btn.btn-link{ type: 'button' }
- = _('Any')
- %li.filter-dropdown-item{ data: { value: 'Upcoming' } }
- %button.btn.btn-link{ type: 'button' }
- = _('Upcoming')
- %li.filter-dropdown-item{ data: { value: 'Started' } }
- %button.btn.btn-link{ type: 'button' }
- = _('Started')
- %li.divider.droplab-item-ignore
- %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
- %li.filter-dropdown-item
- %button.btn.btn-link.js-data-value{ type: 'button' }
- {{title}}
- #js-dropdown-label.filtered-search-input-dropdown-menu.dropdown-menu
- %ul{ data: { dropdown: true } }
- %li.filter-dropdown-item{ data: { value: 'None' } }
- %button.btn.btn-link{ type: 'button' }
- = _('None')
- %li.filter-dropdown-item{ data: { value: 'Any' } }
- %button.btn.btn-link{ type: 'button' }
- = _('Any')
- %li.divider.droplab-item-ignore
- %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
- %li.filter-dropdown-item
- %button.btn.btn-link{ type: 'button' }
- %span.dropdown-label-box{ style: 'background: {{color}}' }
- %span.label-title.js-data-value
+ user: User.new(username: '{{username}}', name: '{{name}}'),
+ avatar: { lazy: true, url: '{{avatar_url}}' }
+ #js-dropdown-assignee.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul{ data: { dropdown: true } }
+ %li.filter-dropdown-item{ data: { value: 'None' } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('None')
+ %li.filter-dropdown-item{ data: { value: 'Any' } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('Any')
+ %li.divider.droplab-item-ignore
+ - if current_user
+ = render 'shared/issuable/user_dropdown_item',
+ user: current_user
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
+ = render 'shared/issuable/user_dropdown_item',
+ user: User.new(username: '{{username}}', name: '{{name}}'),
+ avatar: { lazy: true, url: '{{avatar_url}}' }
+ = render_if_exists 'shared/issuable/approver_dropdown'
+ #js-dropdown-milestone.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul{ data: { dropdown: true } }
+ %li.filter-dropdown-item{ data: { value: 'None' } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('None')
+ %li.filter-dropdown-item{ data: { value: 'Any' } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('Any')
+ %li.filter-dropdown-item{ data: { value: 'Upcoming' } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('Upcoming')
+ %li.filter-dropdown-item{ data: { value: 'Started' } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('Started')
+ %li.divider.droplab-item-ignore
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
+ %li.filter-dropdown-item
+ %button.btn.btn-link.js-data-value{ type: 'button' }
+ {{title}}
+ #js-dropdown-release.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul{ data: { dropdown: true } }
+ %li.filter-dropdown-item{ data: { value: 'None' } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('None')
+ %li.filter-dropdown-item{ data: { value: 'Any' } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('Any')
+ %li.divider.droplab-item-ignore
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
+ %li.filter-dropdown-item
+ %button.btn.btn-link.js-data-value{ type: 'button' }
+ {{title}}
+ #js-dropdown-label.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul{ data: { dropdown: true } }
+ %li.filter-dropdown-item{ data: { value: 'None' } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('None')
+ %li.filter-dropdown-item{ data: { value: 'Any' } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('Any')
+ %li.divider.droplab-item-ignore
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
+ %li.filter-dropdown-item
+ %button.btn.btn-link{ type: 'button' }
+ %span.dropdown-label-box{ style: 'background: {{color}}' }
+ %span.label-title.js-data-value
+ {{title}}
+ #js-dropdown-my-reaction.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul{ data: { dropdown: true } }
+ %li.filter-dropdown-item{ data: { value: 'None' } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('None')
+ %li.filter-dropdown-item{ data: { value: 'Any' } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('Any')
+ %li.divider.droplab-item-ignore
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
+ %li.filter-dropdown-item
+ %button.btn.btn-link{ type: 'button' }
+ %gl-emoji
+ %span.js-data-value.prepend-left-10
+ {{name}}
+ #js-dropdown-wip.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul.filter-dropdown{ data: { dropdown: true } }
+ %li.filter-dropdown-item{ data: { value: 'yes', capitalize: true } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('Yes')
+ %li.filter-dropdown-item{ data: { value: 'no', capitalize: true } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('No')
+ #js-dropdown-confidential.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul.filter-dropdown{ data: { dropdown: true } }
+ %li.filter-dropdown-item{ data: { value: 'yes', capitalize: true } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('Yes')
+ %li.filter-dropdown-item{ data: { value: 'no', capitalize: true } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('No')
+ #js-dropdown-target-branch.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
+ %li.filter-dropdown-item
+ %button.btn.btn-link.js-data-value.monospace
{{title}}
- #js-dropdown-my-reaction.filtered-search-input-dropdown-menu.dropdown-menu
- %ul{ data: { dropdown: true } }
- %li.filter-dropdown-item{ data: { value: 'None' } }
- %button.btn.btn-link{ type: 'button' }
- = _('None')
- %li.filter-dropdown-item{ data: { value: 'Any' } }
- %button.btn.btn-link{ type: 'button' }
- = _('Any')
- %li.divider.droplab-item-ignore
- %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
- %li.filter-dropdown-item
- %button.btn.btn-link{ type: 'button' }
- %gl-emoji
- %span.js-data-value.prepend-left-10
- {{name}}
- #js-dropdown-wip.filtered-search-input-dropdown-menu.dropdown-menu
- %ul.filter-dropdown{ data: { dropdown: true } }
- %li.filter-dropdown-item{ data: { value: 'yes', capitalize: true } }
- %button.btn.btn-link{ type: 'button' }
- = _('Yes')
- %li.filter-dropdown-item{ data: { value: 'no', capitalize: true } }
- %button.btn.btn-link{ type: 'button' }
- = _('No')
- #js-dropdown-confidential.filtered-search-input-dropdown-menu.dropdown-menu
- %ul.filter-dropdown{ data: { dropdown: true } }
- %li.filter-dropdown-item{ data: { value: 'yes', capitalize: true } }
- %button.btn.btn-link{ type: 'button' }
- = _('Yes')
- %li.filter-dropdown-item{ data: { value: 'no', capitalize: true } }
- %button.btn.btn-link{ type: 'button' }
- = _('No')
- #js-dropdown-target-branch.filtered-search-input-dropdown-menu.dropdown-menu
- %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
- %li.filter-dropdown-item
- %button.btn.btn-link.js-data-value.monospace
- {{title}}
- = render_if_exists 'shared/issuable/filter_weight', type: type
+ = render_if_exists 'shared/issuable/filter_weight', type: type
- %button.clear-search.hidden{ type: 'button' }
- = icon('times')
- .filter-dropdown-container.d-flex.flex-column.flex-md-row
- - if type == :boards
- .js-board-config{ data: { can_admin_list: user_can_admin_list, has_scope: board.scoped? } }
- - if user_can_admin_list
- = render 'shared/issuable/board_create_list_dropdown', board: board
- - if @project
- #js-add-issues-btn.prepend-left-10{ data: { can_admin_list: can?(current_user, :admin_list, @project) } }
- #js-toggle-focus-btn
- - elsif is_not_boards_modal_or_productivity_analytics
- = render 'shared/issuable/sort_dropdown'
+ %button.clear-search.hidden{ type: 'button' }
+ = icon('times')
+ .filter-dropdown-container.d-flex.flex-column.flex-md-row
+ #js-board-labels-toggle
+ - if type == :boards
+ .js-board-config{ data: { can_admin_list: user_can_admin_list, has_scope: board.scoped? } }
+ - if user_can_admin_list
+ = render 'shared/issuable/board_create_list_dropdown', board: board
+ - if @project
+ #js-add-issues-btn.prepend-left-10{ data: { can_admin_list: can?(current_user, :admin_list, @project) } }
+ #js-toggle-focus-btn
+ - elsif is_not_boards_modal_or_productivity_analytics
+ = render 'shared/issuable/sort_dropdown'
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index c8b2adcf08..2170b88c7c 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -141,13 +141,7 @@
.js-sidebar-participants-entry-point
- if signed_in
- - if issuable_sidebar[:project_emails_disabled]
- .block.js-emails-disabled
- .sidebar-collapsed-icon.has-tooltip{ title: notification_description(:owner_disabled), data: { placement: "left", container: "body", boundary: 'viewport' } }
- = notification_setting_icon
- .hide-collapsed= notification_description(:owner_disabled)
- - else
- .js-sidebar-subscriptions-entry-point
+ .js-sidebar-subscriptions-entry-point
- project_ref = issuable_sidebar[:reference]
.block.project-reference
diff --git a/app/views/shared/issuable/form/_branch_chooser.html.haml b/app/views/shared/issuable/form/_branch_chooser.html.haml
index fbc96baa0f..29ac17c43b 100644
--- a/app/views/shared/issuable/form/_branch_chooser.html.haml
+++ b/app/views/shared/issuable/form/_branch_chooser.html.haml
@@ -4,21 +4,20 @@
- return unless issuable.is_a?(MergeRequest)
- return if issuable.closed_without_fork?
-%hr
-- if issuable.new_record?
- .form-group.row
- = form.label :source_branch, class: 'col-form-label col-sm-2'
- .col-sm-10
- .issuable-form-select-holder
- = form.select(:source_branch, [issuable.source_branch], {}, { class: 'source_branch select2 ref-name', disabled: true })
-.form-group.row
- = form.label :target_branch, class: 'col-form-label col-sm-2'
- .col-sm-10.target-branch-select-dropdown-container
- .issuable-form-select-holder
- = form.hidden_field(:target_branch,
- { class: 'target_branch js-target-branch-select ref-name',
- disabled: issuable.new_record?,
- data: { placeholder: "Select branch", endpoint: refs_project_path(@project, sort: 'updated_desc', find: 'branches') }})
+- source_title, target_title = format_mr_branch_names(@merge_request)
+
+.form-group.row.d-flex.gl-pl-3.gl-pr-3.branch-selector
+ .align-self-center
+ %span= s_('From %{source_title} into').html_safe % { source_title: "#{source_title}
".html_safe }
- if issuable.new_record?
+ %code= target_title
- = link_to 'Change branches', mr_change_branches_path(issuable)
+ = link_to _('Change branches'), mr_change_branches_path(issuable)
+ - elsif issuable.for_fork?
+ %code= issuable.target_project_path + ":"
+ - unless issuable.new_record?
+ %span.dropdown.prepend-left-5.d-inline-block
+ = form.hidden_field(:target_branch,
+ { class: 'target_branch js-target-branch-select ref-name mw-xl',
+ data: { placeholder: _('Select branch'), endpoint: refs_project_path(@project, sort: 'updated_desc', find: 'branches') }})
+%hr
diff --git a/app/views/shared/issuable/form/_merge_params.html.haml b/app/views/shared/issuable/form/_merge_params.html.haml
index f0c4acdd07..1b557214e0 100644
--- a/app/views/shared/issuable/form/_merge_params.html.haml
+++ b/app/views/shared/issuable/form/_merge_params.html.haml
@@ -3,17 +3,17 @@
- return unless issuable.is_a?(MergeRequest)
- return if issuable.closed_without_fork?
-- if issuable.can_remove_source_branch?(current_user)
- .form-group.row
- .col-sm-10.offset-sm-2
- .form-check
+.form-group.row
+ .col-sm-2.col-form-label.pt-sm-0
+ %label
+ = _('Merge options')
+ .col-sm-10
+ - if issuable.can_remove_source_branch?(current_user)
+ .form-check.append-bottom-default
= hidden_field_tag 'merge_request[force_remove_source_branch]', '0', id: nil
= check_box_tag 'merge_request[force_remove_source_branch]', '1', issuable.force_remove_source_branch?, class: 'form-check-input'
= label_tag 'merge_request[force_remove_source_branch]', class: 'form-check-label' do
Delete source branch when merge request is accepted.
-
-.form-group.row
- .col-sm-10.offset-sm-2
.form-check
= hidden_field_tag 'merge_request[squash]', '0', id: nil
= check_box_tag 'merge_request[squash]', '1', issuable.squash, class: 'form-check-input'
diff --git a/app/views/shared/members/_access_request_links.html.haml b/app/views/shared/members/_access_request_links.html.haml
index eac743b520..b4b06640bd 100644
--- a/app/views/shared/members/_access_request_links.html.haml
+++ b/app/views/shared/members/_access_request_links.html.haml
@@ -4,7 +4,7 @@
- link_text = source.is_a?(Group) ? _('Leave group') : _('Leave project')
= link_to link_text, polymorphic_path([:leave, source, :members]),
method: :delete,
- data: { confirm: leave_confirmation_message(source) },
+ data: { confirm: leave_confirmation_message(source), qa_selector: 'leave_group_link' },
class: 'access-request-link js-leave-link'
- elsif requester = source.requesters.find_by(user_id: current_user.id) # rubocop: disable CodeReuse/ActiveRecord
= link_to _('Withdraw Access Request'), polymorphic_path([:leave, source, :members]),
diff --git a/app/views/shared/milestones/_description.html.haml b/app/views/shared/milestones/_description.html.haml
new file mode 100644
index 0000000000..5ff110bf94
--- /dev/null
+++ b/app/views/shared/milestones/_description.html.haml
@@ -0,0 +1,8 @@
+.detail-page-description.milestone-detail
+ %h2.title
+ = markdown_field(milestone, :title)
+
+ - if milestone.try(:description).present?
+ %div
+ .description.md
+ = markdown_field(milestone, :description)
diff --git a/app/views/shared/milestones/_header.html.haml b/app/views/shared/milestones/_header.html.haml
new file mode 100644
index 0000000000..2da857261d
--- /dev/null
+++ b/app/views/shared/milestones/_header.html.haml
@@ -0,0 +1,38 @@
+.detail-page-header.milestone-page-header
+ .status-box{ class: status_box_class(milestone) }
+ = milestone_status_string(milestone)
+
+ .header-text-content
+ %span.identifier
+ %strong
+ = _('Milestone')
+ - if milestone.due_date || milestone.start_date
+ = milestone_date_range(milestone)
+
+ .milestone-buttons
+ - if can?(current_user, :admin_milestone, @group || @project)
+ - unless milestone.legacy_group_milestone?
+ = link_to _('Edit'), edit_milestone_path(milestone), class: 'btn btn-grouped'
+
+ - if milestone.project_milestone? && milestone.project.group
+ %button.js-promote-project-milestone-button.btn.btn-grouped{ data: { toggle: 'modal',
+ target: '#promote-milestone-modal',
+ milestone_title: milestone.title,
+ group_name: milestone.project.group.name,
+ url: promote_project_milestone_path(milestone.project, milestone),
+ container: 'body' },
+ disabled: true,
+ type: 'button' }
+ = _('Promote')
+ #promote-milestone-modal
+
+ - if milestone.active?
+ = link_to _('Close milestone'), update_milestone_path(milestone, { state_event: :close }), method: :put, class: 'btn btn-grouped btn-close'
+ - else
+ = link_to _('Reopen milestone'), update_milestone_path(milestone, { state_event: :activate }), method: :put, class: 'btn btn-grouped btn-reopen'
+
+ - unless milestone.legacy_group_milestone?
+ = render 'shared/milestones/delete_button'
+
+ %button.btn.btn-default.btn-grouped.float-right.d-block.d-sm-none.js-sidebar-toggle{ type: 'button' }
+ = icon('angle-double-left')
diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml
index e99aa3f1ee..b324f35c33 100644
--- a/app/views/shared/milestones/_milestone.html.haml
+++ b/app/views/shared/milestones/_milestone.html.haml
@@ -12,8 +12,20 @@
- if @project || milestone.is_a?(GlobalMilestone) || milestone.group_milestone?
- if milestone.due_date || milestone.start_date
- .milestone-range.append-bottom-5
+ .text-tertiary.append-bottom-5
= milestone_date_range(milestone)
+ - recent_releases, total_count, more_count = recent_releases_with_counts(milestone)
+ - unless total_count.zero?
+ .text-tertiary.append-bottom-5.milestone-release-links
+ = icon('rocket')
+ = n_('Release', 'Releases', total_count)
+ - recent_releases.each do |release|
+ = link_to release.name, project_releases_path(release.project, anchor: release.tag)
+ - unless release == recent_releases.last
+ •
+ - if total_count > recent_releases.count
+ •
+ = link_to n_('%{count} more release', '%{count} more releases', more_count) % { count: more_count }, project_releases_path(milestone.project)
%div
= render('shared/milestone_expired', milestone: milestone)
- if milestone.group_milestone?
diff --git a/app/views/shared/milestones/_sidebar.html.haml b/app/views/shared/milestones/_sidebar.html.haml
index 22a6d5e33f..b6656e6283 100644
--- a/app/views/shared/milestones/_sidebar.html.haml
+++ b/app/views/shared/milestones/_sidebar.html.haml
@@ -138,6 +138,27 @@
Merged:
= milestone.merge_requests.merged.count
+ - if project
+ - recent_releases, total_count, more_count = recent_releases_with_counts(milestone)
+ .block.releases
+ .sidebar-collapsed-icon.has-tooltip{ title: milestone_releases_tooltip_text(milestone), data: { container: 'body', placement: 'left', boundary: 'viewport' } }
+ %strong
+ = icon('rocket')
+ %span= total_count
+ .title.hide-collapsed= n_('Release', 'Releases', total_count)
+ .hide-collapsed
+ - if total_count.zero?
+ .no-value= _('None')
+ - else
+ .font-weight-bold
+ - recent_releases.each do |release|
+ = link_to release.name, project_releases_path(project, :anchor => release.tag)
+ - unless release == recent_releases.last
+ %span.font-weight-normal •
+ - if more_count > 0
+ %span.font-weight-normal •
+ = link_to n_('%{count} more release', '%{count} more releases', more_count) % { count: more_count }, project_releases_path(project), class: 'font-weight-normal'
+
- milestone_ref = milestone.try(:to_reference, full: true)
- if milestone_ref.present?
.block.reference
diff --git a/app/views/shared/milestones/_tabs.html.haml b/app/views/shared/milestones/_tabs.html.haml
index b877f66c71..f718c5767d 100644
--- a/app/views/shared/milestones/_tabs.html.haml
+++ b/app/views/shared/milestones/_tabs.html.haml
@@ -1,30 +1,22 @@
-- issues_accessible = milestone.is_a?(GlobalMilestone) || can?(current_user, :read_issue, @project)
-
.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
.fade-left= icon('angle-left')
.fade-right= icon('angle-right')
%ul.nav-links.scrolling-tabs.js-milestone-tabs.nav.nav-tabs
- - if issues_accessible
- %li.nav-item
- = link_to '#tab-issues', class: 'nav-link active', 'data-toggle' => 'tab', 'data-show' => '.tab-issues-buttons' do
- Issues
- %span.badge.badge-pill= milestone.issues_visible_to_user(current_user).size
- %li.nav-item
- = link_to '#tab-merge-requests', class: 'nav-link', 'data-toggle' => 'tab', 'data-endpoint': milestone_merge_request_tab_path(milestone) do
- Merge Requests
- %span.badge.badge-pill= milestone.merge_requests_visible_to_user(current_user).size
- - else
- %li.nav-item
- = link_to '#tab-merge-requests', class: 'nav-link active', 'data-toggle' => 'tab', 'data-endpoint': milestone_merge_request_tab_path(milestone) do
- Merge Requests
- %span.badge.badge-pill= milestone.merge_requests.size
%li.nav-item
- = link_to '#tab-participants', class: 'nav-link', 'data-toggle' => 'tab', 'data-endpoint': milestone_participants_tab_path(milestone) do
- Participants
+ = link_to '#tab-issues', class: 'nav-link active', data: { toggle: 'tab', show: '.tab-issues-buttons' } do
+ = _('Issues')
+ %span.badge.badge-pill= milestone.issues_visible_to_user(current_user).size
+ %li.nav-item
+ = link_to '#tab-merge-requests', class: 'nav-link', data: { toggle: 'tab', endpoint: milestone_tab_path(milestone, 'merge_requests') } do
+ = _('Merge Requests')
+ %span.badge.badge-pill= milestone.merge_requests_visible_to_user(current_user).size
+ %li.nav-item
+ = link_to '#tab-participants', class: 'nav-link', data: { toggle: 'tab', endpoint: milestone_tab_path(milestone, 'participants') } do
+ = _('Participants')
%span.badge.badge-pill= milestone.issue_participants_visible_by_user(current_user).count
%li.nav-item
- = link_to '#tab-labels', class: 'nav-link', 'data-toggle' => 'tab', 'data-endpoint': milestone_labels_tab_path(milestone) do
- Labels
+ = link_to '#tab-labels', class: 'nav-link', data: { toggle: 'tab', endpoint: milestone_tab_path(milestone, 'labels') } do
+ = _('Labels')
%span.badge.badge-pill= milestone.issue_labels_visible_by_user(current_user).count
- issues = milestone.sorted_issues(current_user)
@@ -32,16 +24,11 @@
- show_full_project_name = local_assigns.fetch(:show_full_project_name, false)
.tab-content.milestone-content
- - if issues_accessible
- .tab-pane.active#tab-issues{ data: { sort_endpoint: (sort_issues_project_milestone_path(@project, @milestone) if @project && current_user) } }
- = render 'shared/milestones/issues_tab', issues: issues, show_project_name: show_project_name, show_full_project_name: show_full_project_name
- .tab-pane#tab-merge-requests
- -# loaded async
- = render "shared/milestones/tab_loading"
- - else
- .tab-pane.active#tab-merge-requests
- -# loaded async
- = render "shared/milestones/tab_loading"
+ .tab-pane.active#tab-issues{ data: { sort_endpoint: (sort_issues_project_milestone_path(@project, @milestone) if @project && current_user) } }
+ = render 'shared/milestones/issues_tab', issues: issues, show_project_name: show_project_name, show_full_project_name: show_full_project_name
+ .tab-pane#tab-merge-requests
+ -# loaded async
+ = render "shared/milestones/tab_loading"
.tab-pane#tab-participants
-# loaded async
= render "shared/milestones/tab_loading"
diff --git a/app/views/shared/milestones/_top.html.haml b/app/views/shared/milestones/_top.html.haml
index fd3317341f..12575b30a6 100644
--- a/app/views/shared/milestones/_top.html.haml
+++ b/app/views/shared/milestones/_top.html.haml
@@ -4,54 +4,15 @@
- group = local_assigns[:group]
- is_dynamic_milestone = milestone.legacy_group_milestone? || milestone.dashboard_milestone?
-.detail-page-header.milestone-page-header
- .status-box{ class: "status-box-#{milestone.closed? ? 'closed' : 'open'}" }
- - if milestone.closed?
- Closed
- - elsif milestone.expired?
- Expired
- - else
- Open
-
- .header-text-content
- %span.identifier
- Milestone #{milestone.title}
- - if milestone.due_date || milestone.start_date
- %span.creator
- ·
- = milestone_date_range(milestone)
-
- .milestone-buttons
- - if group
- - if can?(current_user, :admin_milestone, group)
- - if milestone.group_milestone?
- = link_to edit_group_milestone_path(group, milestone), class: "btn btn btn-grouped" do
- Edit
- - if milestone.active?
- = link_to 'Close Milestone', group_milestone_route(milestone, {state_event: :close }), method: :put, class: "btn btn-grouped btn-close"
- - else
- = link_to 'Reopen Milestone', group_milestone_route(milestone, {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen"
-
- - unless is_dynamic_milestone
- = render 'shared/milestones/delete_button'
-
- %a.btn.btn-default.btn-grouped.float-right.d-block.d-sm-none.js-sidebar-toggle{ href: "#" }
- = icon('angle-double-left')
-
+= render 'shared/milestones/header', milestone: milestone
= render 'shared/milestones/deprecation_message' if is_dynamic_milestone
-
-.detail-page-description.milestone-detail
- %h2.title
- = markdown_field(milestone, :title)
- - if milestone.group_milestone? && milestone.description.present?
- %div
- .description.md
- = markdown_field(milestone, :description)
+= render 'shared/milestones/description', milestone: milestone
- if milestone.complete?(current_user) && milestone.active?
.alert.alert-success.prepend-top-default
- - close_msg = group ? 'You may close the milestone now.' : 'Navigate to the project to close the milestone.'
- %span All issues for this milestone are closed. #{close_msg}
+ %span
+ = _('All issues for this milestone are closed.')
+ = group ? _('You may close the milestone now.') : _('Navigate to the project to close the milestone.')
= render_if_exists 'shared/milestones/burndown', milestone: milestone, project: @project
@@ -77,10 +38,3 @@
Open
%td
= milestone.expires_at
-- elsif milestone.group_milestone?
- %br
- View
- = link_to 'Issues', issues_group_path(@group, milestone_title: milestone.title)
- or
- = link_to 'Merge Requests', merge_requests_group_path(@group, milestone_title: milestone.title)
- in this milestone
diff --git a/app/views/shared/notifications/_button.html.haml b/app/views/shared/notifications/_button.html.haml
index 441abd5733..2b3e986a84 100644
--- a/app/views/shared/notifications/_button.html.haml
+++ b/app/views/shared/notifications/_button.html.haml
@@ -17,14 +17,14 @@
.js-notification-toggle-btns
%div{ class: ("btn-group" if notification_setting.custom?) }
- if notification_setting.custom?
- %button.dropdown-new.btn.btn-default.btn-xs.has-tooltip.notifications-btn.text-left#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => aria_label, data: { container: "body", toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } }
+ %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn.text-left#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => aria_label, data: { container: "body", toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } }
= icon("bell", class: "js-notification-loading")
= notification_title(notification_setting.level)
- %button.btn.dropdown-toggle{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
+ %button.btn.dropdown-toggle.d-flex{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
= icon('caret-down')
.sr-only Toggle dropdown
- else
- %button.dropdown-new.btn.btn-default.btn-xs.has-tooltip.notifications-btn#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => aria_label, data: { container: "body", toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
+ %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => aria_label, data: { container: "body", toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
.float-left
= icon("bell", class: "js-notification-loading")
= notification_title(notification_setting.level)
diff --git a/app/views/shared/notifications/_custom_notifications.html.haml b/app/views/shared/notifications/_custom_notifications.html.haml
index 43a87fd839..1fef43c0c3 100644
--- a/app/views/shared/notifications/_custom_notifications.html.haml
+++ b/app/views/shared/notifications/_custom_notifications.html.haml
@@ -1,3 +1,5 @@
+- hide_label = local_assigns.fetch(:hide_label, false)
+
.modal.fade{ tabindex: "-1", role: "dialog", id: notifications_menu_identifier("modal", notification_setting), "aria-labelledby": "custom-notifications-title" }
.modal-dialog
.modal-content
@@ -11,6 +13,7 @@
.container-fluid
= form_for notification_setting, html: { class: "custom-notifications-form" } do |f|
= hidden_setting_source_input(notification_setting)
+ = hidden_field_tag("hide_label", true) if hide_label
.row
.col-lg-4
%h4.prepend-top-0= _('Notification events')
diff --git a/app/views/shared/notifications/_new_button.html.haml b/app/views/shared/notifications/_new_button.html.haml
index 3c8cc02384..363053b5e3 100644
--- a/app/views/shared/notifications/_new_button.html.haml
+++ b/app/views/shared/notifications/_new_button.html.haml
@@ -31,4 +31,4 @@
= render "shared/notifications/notification_dropdown", notification_setting: notification_setting
= content_for :scripts_body do
- = render "shared/notifications/custom_notifications", notification_setting: notification_setting
+ = render "shared/notifications/custom_notifications", notification_setting: notification_setting, hide_label: true
diff --git a/app/views/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml
index d70a163101..59b4facdbe 100644
--- a/app/views/shared/projects/_list.html.haml
+++ b/app/views/shared/projects/_list.html.haml
@@ -32,7 +32,7 @@
- explore_groups_button_label = _('Explore groups')
- explore_groups_button_link = explore_groups_path
-.js-projects-list-holder
+.js-projects-list-holder{ data: { qa_selector: 'projects_list' } }
- if any_projects?(projects)
- load_pipeline_status(projects) if pipeline_status
%ul.projects-list{ class: css_classes }
diff --git a/app/views/shared/runners/_runner_description.html.haml b/app/views/shared/runners/_runner_description.html.haml
index 5935750ca0..a47bbd5532 100644
--- a/app/views/shared/runners/_runner_description.html.haml
+++ b/app/views/shared/runners/_runner_description.html.haml
@@ -1,6 +1,6 @@
.light.prepend-top-default
%p
- = _("A 'Runner' is a process which runs a job. You can set up as many Runners as you need.")
+ = _("You can set up as many Runners as you need to run your jobs.")
%br
= _('Runners can be placed on separate users, servers, and even on your local machine.')
diff --git a/app/views/shared/snippets/_blob.html.haml b/app/views/shared/snippets/_blob.html.haml
index 2132fcbccc..6a5e777706 100644
--- a/app/views/shared/snippets/_blob.html.haml
+++ b/app/views/shared/snippets/_blob.html.haml
@@ -8,7 +8,6 @@
.btn-group{ role: "group" }<
= copy_blob_source_button(blob)
= open_raw_blob_button(blob)
-
- = link_to icon('download'), download_snippet_path(@snippet), target: '_blank', class: "btn btn-sm has-tooltip", title: 'Download', data: { container: 'body' }
+ = download_raw_snippet_button(@snippet)
= render 'projects/blob/content', blob: blob
diff --git a/app/views/shared/snippets/_embed.html.haml b/app/views/shared/snippets/_embed.html.haml
index c7f0511d1d..d2e35511b3 100644
--- a/app/views/shared/snippets/_embed.html.haml
+++ b/app/views/shared/snippets/_embed.html.haml
@@ -17,7 +17,7 @@
.file-actions.d-none.d-sm-block
.btn-group{ role: "group" }<
- = embedded_snippet_raw_button
+ = embedded_raw_snippet_button
= embedded_snippet_download_button
%article.file-holder.snippet-file-content
diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml
index 8d94a87a77..67f177288f 100644
--- a/app/views/shared/snippets/_header.html.haml
+++ b/app/views/shared/snippets/_header.html.haml
@@ -44,7 +44,7 @@
%li
%button.js-share-btn.btn.btn-transparent{ type: 'button' }
%strong.embed-toggle-list-item= _("Share")
- %input.js-snippet-url-area.snippet-embed-input.form-control{ type: "text", autocomplete: 'off', value: snippet_embed }
+ %input.js-snippet-url-area.snippet-embed-input.form-control{ type: "text", autocomplete: 'off', value: snippet_embed_tag(@snippet) }
.input-group-append
= clipboard_button(title: _('Copy'), class: 'js-clipboard-btn snippet-clipboard-btn btn btn-default', target: '.js-snippet-url-area')
.clearfix
diff --git a/app/views/shared/snippets/_snippet.html.haml b/app/views/shared/snippets/_snippet.html.haml
index 0ef626868a..5602ea37b5 100644
--- a/app/views/shared/snippets/_snippet.html.haml
+++ b/app/views/shared/snippets/_snippet.html.haml
@@ -7,8 +7,9 @@
.title
= link_to reliable_snippet_path(snippet) do
= snippet.title
- - if snippet.file_name
- %span.snippet-filename.monospace.d-none.d-sm-inline-block
+ - if snippet.file_name.present?
+ %span.snippet-filename.d-none.d-sm-inline-block.ml-2
+ = sprite_icon('doc-code', size: 16, css_class: 'file-icon align-text-bottom')
= snippet.file_name
%ul.controls
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index b161cc6560..66b5214cfc 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -45,6 +45,9 @@
- gcp_cluster:cluster_project_configure
- gcp_cluster:clusters_applications_wait_for_uninstall_app
- gcp_cluster:clusters_applications_uninstall
+- gcp_cluster:clusters_cleanup_app
+- gcp_cluster:clusters_cleanup_project_namespace
+- gcp_cluster:clusters_cleanup_service_account
- github_import_advance_stage
- github_importer:github_import_import_diff_note
@@ -176,3 +179,4 @@
- import_issues_csv
- project_daily_statistics
- create_evidence
+- group_export
diff --git a/app/workers/authorized_projects_worker.rb b/app/workers/authorized_projects_worker.rb
index 577c439f4a..9492cfe217 100644
--- a/app/workers/authorized_projects_worker.rb
+++ b/app/workers/authorized_projects_worker.rb
@@ -5,6 +5,7 @@ class AuthorizedProjectsWorker
prepend WaitableWorker
feature_category :authentication_and_authorization
+ latency_sensitive_worker!
# This is a workaround for a Ruby 2.3.7 bug. rspec-mocks cannot restore the
# visibility of prepended modules. See https://github.com/rspec/rspec-mocks/issues/1231
diff --git a/app/workers/build_finished_worker.rb b/app/workers/build_finished_worker.rb
index e95b6b38d2..e61f37ddce 100644
--- a/app/workers/build_finished_worker.rb
+++ b/app/workers/build_finished_worker.rb
@@ -5,6 +5,8 @@ class BuildFinishedWorker
include PipelineQueue
queue_namespace :pipeline_processing
+ latency_sensitive_worker!
+ worker_resource_boundary :cpu
# rubocop: disable CodeReuse/ActiveRecord
def perform(build_id)
diff --git a/app/workers/build_hooks_worker.rb b/app/workers/build_hooks_worker.rb
index 15b31acf3e..fa55769e48 100644
--- a/app/workers/build_hooks_worker.rb
+++ b/app/workers/build_hooks_worker.rb
@@ -6,6 +6,7 @@ class BuildHooksWorker
queue_namespace :pipeline_hooks
feature_category :continuous_integration
+ latency_sensitive_worker!
# rubocop: disable CodeReuse/ActiveRecord
def perform(build_id)
diff --git a/app/workers/build_queue_worker.rb b/app/workers/build_queue_worker.rb
index 6584fba4c6..6f75f403e6 100644
--- a/app/workers/build_queue_worker.rb
+++ b/app/workers/build_queue_worker.rb
@@ -6,6 +6,8 @@ class BuildQueueWorker
queue_namespace :pipeline_processing
feature_category :continuous_integration
+ latency_sensitive_worker!
+ worker_resource_boundary :cpu
# rubocop: disable CodeReuse/ActiveRecord
def perform(build_id)
diff --git a/app/workers/build_success_worker.rb b/app/workers/build_success_worker.rb
index ac947f3cf3..b7dbd367fe 100644
--- a/app/workers/build_success_worker.rb
+++ b/app/workers/build_success_worker.rb
@@ -5,6 +5,7 @@ class BuildSuccessWorker
include PipelineQueue
queue_namespace :pipeline_processing
+ latency_sensitive_worker!
# rubocop: disable CodeReuse/ActiveRecord
def perform(build_id)
diff --git a/app/workers/chat_notification_worker.rb b/app/workers/chat_notification_worker.rb
index 3bc2edad62..42a23cd472 100644
--- a/app/workers/chat_notification_worker.rb
+++ b/app/workers/chat_notification_worker.rb
@@ -4,6 +4,11 @@ class ChatNotificationWorker
include ApplicationWorker
feature_category :chatops
+ latency_sensitive_worker!
+ # TODO: break this into multiple jobs
+ # as the `responder` uses external dependencies
+ # See https://gitlab.com/gitlab-com/gl-infra/scalability/issues/34
+ # worker_has_external_dependencies!
RESCHEDULE_INTERVAL = 2.seconds
diff --git a/app/workers/ci/build_schedule_worker.rb b/app/workers/ci/build_schedule_worker.rb
index f22ec4c781..e34f16f46c 100644
--- a/app/workers/ci/build_schedule_worker.rb
+++ b/app/workers/ci/build_schedule_worker.rb
@@ -7,6 +7,7 @@ module Ci
queue_namespace :pipeline_processing
feature_category :continuous_integration
+ worker_resource_boundary :cpu
def perform(build_id)
::Ci::Build.find_by_id(build_id).try do |build|
diff --git a/app/workers/cluster_install_app_worker.rb b/app/workers/cluster_install_app_worker.rb
index 32e2ea7996..0e075b295d 100644
--- a/app/workers/cluster_install_app_worker.rb
+++ b/app/workers/cluster_install_app_worker.rb
@@ -5,6 +5,8 @@ class ClusterInstallAppWorker
include ClusterQueue
include ClusterApplications
+ worker_has_external_dependencies!
+
def perform(app_name, app_id)
find_application(app_name, app_id) do |app|
Clusters::Applications::InstallService.new(app).execute
diff --git a/app/workers/cluster_patch_app_worker.rb b/app/workers/cluster_patch_app_worker.rb
index 0549e81ed0..3f95a76456 100644
--- a/app/workers/cluster_patch_app_worker.rb
+++ b/app/workers/cluster_patch_app_worker.rb
@@ -5,6 +5,8 @@ class ClusterPatchAppWorker
include ClusterQueue
include ClusterApplications
+ worker_has_external_dependencies!
+
def perform(app_name, app_id)
find_application(app_name, app_id) do |app|
Clusters::Applications::PatchService.new(app).execute
diff --git a/app/workers/cluster_project_configure_worker.rb b/app/workers/cluster_project_configure_worker.rb
index ad2437a77e..614029c2b5 100644
--- a/app/workers/cluster_project_configure_worker.rb
+++ b/app/workers/cluster_project_configure_worker.rb
@@ -4,6 +4,8 @@ class ClusterProjectConfigureWorker
include ApplicationWorker
include ClusterQueue
+ worker_has_external_dependencies!
+
def perform(project_id)
# Scheduled for removal in https://gitlab.com/gitlab-org/gitlab-foss/issues/59319
end
diff --git a/app/workers/cluster_provision_worker.rb b/app/workers/cluster_provision_worker.rb
index 59de7903c1..c34284319d 100644
--- a/app/workers/cluster_provision_worker.rb
+++ b/app/workers/cluster_provision_worker.rb
@@ -4,10 +4,16 @@ class ClusterProvisionWorker
include ApplicationWorker
include ClusterQueue
+ worker_has_external_dependencies!
+
def perform(cluster_id)
Clusters::Cluster.find_by_id(cluster_id).try do |cluster|
cluster.provider.try do |provider|
- Clusters::Gcp::ProvisionService.new.execute(provider) if cluster.gcp?
+ if cluster.gcp?
+ Clusters::Gcp::ProvisionService.new.execute(provider)
+ elsif cluster.aws?
+ Clusters::Aws::ProvisionService.new.execute(provider)
+ end
end
end
end
diff --git a/app/workers/cluster_upgrade_app_worker.rb b/app/workers/cluster_upgrade_app_worker.rb
index d1a538859b..cd06f0a222 100644
--- a/app/workers/cluster_upgrade_app_worker.rb
+++ b/app/workers/cluster_upgrade_app_worker.rb
@@ -5,6 +5,8 @@ class ClusterUpgradeAppWorker
include ClusterQueue
include ClusterApplications
+ worker_has_external_dependencies!
+
def perform(app_name, app_id)
find_application(app_name, app_id) do |app|
Clusters::Applications::UpgradeService.new(app).execute
diff --git a/app/workers/cluster_wait_for_app_installation_worker.rb b/app/workers/cluster_wait_for_app_installation_worker.rb
index e8d7e52f70..7155dc6f83 100644
--- a/app/workers/cluster_wait_for_app_installation_worker.rb
+++ b/app/workers/cluster_wait_for_app_installation_worker.rb
@@ -8,6 +8,9 @@ class ClusterWaitForAppInstallationWorker
INTERVAL = 10.seconds
TIMEOUT = 20.minutes
+ worker_has_external_dependencies!
+ worker_resource_boundary :cpu
+
def perform(app_name, app_id)
find_application(app_name, app_id) do |app|
Clusters::Applications::CheckInstallationProgressService.new(app).execute
diff --git a/app/workers/cluster_wait_for_ingress_ip_address_worker.rb b/app/workers/cluster_wait_for_ingress_ip_address_worker.rb
index 6865384df4..14b1651cc7 100644
--- a/app/workers/cluster_wait_for_ingress_ip_address_worker.rb
+++ b/app/workers/cluster_wait_for_ingress_ip_address_worker.rb
@@ -5,6 +5,8 @@ class ClusterWaitForIngressIpAddressWorker
include ClusterQueue
include ClusterApplications
+ worker_has_external_dependencies!
+
def perform(app_name, app_id)
find_application(app_name, app_id) do |app|
Clusters::Applications::CheckIngressIpAddressService.new(app).execute
diff --git a/app/workers/clusters/applications/uninstall_worker.rb b/app/workers/clusters/applications/uninstall_worker.rb
index 85e8ecc4ad..6180998c8d 100644
--- a/app/workers/clusters/applications/uninstall_worker.rb
+++ b/app/workers/clusters/applications/uninstall_worker.rb
@@ -7,6 +7,8 @@ module Clusters
include ClusterQueue
include ClusterApplications
+ worker_has_external_dependencies!
+
def perform(app_name, app_id)
find_application(app_name, app_id) do |app|
Clusters::Applications::UninstallService.new(app).execute
diff --git a/app/workers/clusters/applications/wait_for_uninstall_app_worker.rb b/app/workers/clusters/applications/wait_for_uninstall_app_worker.rb
index 163c99d3c3..7907aa8dff 100644
--- a/app/workers/clusters/applications/wait_for_uninstall_app_worker.rb
+++ b/app/workers/clusters/applications/wait_for_uninstall_app_worker.rb
@@ -10,6 +10,9 @@ module Clusters
INTERVAL = 10.seconds
TIMEOUT = 20.minutes
+ worker_has_external_dependencies!
+ worker_resource_boundary :cpu
+
def perform(app_name, app_id)
find_application(app_name, app_id) do |app|
Clusters::Applications::CheckUninstallProgressService.new(app).execute
diff --git a/app/workers/clusters/cleanup/app_worker.rb b/app/workers/clusters/cleanup/app_worker.rb
new file mode 100644
index 0000000000..1eedf510ba
--- /dev/null
+++ b/app/workers/clusters/cleanup/app_worker.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Cleanup
+ class AppWorker
+ include ApplicationWorker
+ include ClusterQueue
+ include ClusterApplications
+
+ # TODO: Merge with https://gitlab.com/gitlab-org/gitlab/merge_requests/16954
+ # We're splitting the above MR in smaller chunks to facilitate reviews
+ def perform
+ end
+ end
+ end
+end
diff --git a/app/workers/clusters/cleanup/project_namespace_worker.rb b/app/workers/clusters/cleanup/project_namespace_worker.rb
new file mode 100644
index 0000000000..09f2abf5d8
--- /dev/null
+++ b/app/workers/clusters/cleanup/project_namespace_worker.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Cleanup
+ class ProjectNamespaceWorker
+ include ApplicationWorker
+ include ClusterQueue
+ include ClusterApplications
+
+ # TODO: Merge with https://gitlab.com/gitlab-org/gitlab/merge_requests/16954
+ # We're splitting the above MR in smaller chunks to facilitate reviews
+ def perform
+ end
+ end
+ end
+end
diff --git a/app/workers/clusters/cleanup/service_account_worker.rb b/app/workers/clusters/cleanup/service_account_worker.rb
new file mode 100644
index 0000000000..fab6318a80
--- /dev/null
+++ b/app/workers/clusters/cleanup/service_account_worker.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Cleanup
+ class ServiceAccountWorker
+ include ApplicationWorker
+ include ClusterQueue
+ include ClusterApplications
+
+ # TODO: Merge with https://gitlab.com/gitlab-org/gitlab/merge_requests/16954
+ # We're splitting the above MR in smaller chunks to facilitate reviews
+ def perform
+ end
+ end
+ end
+end
diff --git a/app/workers/concerns/gitlab/github_import/object_importer.rb b/app/workers/concerns/gitlab/github_import/object_importer.rb
index b856a9329d..bd0b566658 100644
--- a/app/workers/concerns/gitlab/github_import/object_importer.rb
+++ b/app/workers/concerns/gitlab/github_import/object_importer.rb
@@ -14,6 +14,7 @@ module Gitlab
include NotifyUponDeath
feature_category :importers
+ worker_has_external_dependencies!
end
# project - An instance of `Project` to import the data into.
diff --git a/app/workers/create_pipeline_worker.rb b/app/workers/create_pipeline_worker.rb
index 70412ffd09..a75cc64303 100644
--- a/app/workers/create_pipeline_worker.rb
+++ b/app/workers/create_pipeline_worker.rb
@@ -6,6 +6,8 @@ class CreatePipelineWorker
queue_namespace :pipeline_creation
feature_category :continuous_integration
+ latency_sensitive_worker!
+ worker_resource_boundary :cpu
def perform(project_id, user_id, ref, source, params = {})
project = Project.find(project_id)
diff --git a/app/workers/deployments/finished_worker.rb b/app/workers/deployments/finished_worker.rb
index 79a1caccc9..90bbc19365 100644
--- a/app/workers/deployments/finished_worker.rb
+++ b/app/workers/deployments/finished_worker.rb
@@ -6,6 +6,7 @@ module Deployments
queue_namespace :deployment
feature_category :continuous_delivery
+ worker_resource_boundary :cpu
def perform(deployment_id)
Deployment.find_by_id(deployment_id).try(:execute_hooks)
diff --git a/app/workers/deployments/success_worker.rb b/app/workers/deployments/success_worker.rb
index f652030718..4a29f1aef5 100644
--- a/app/workers/deployments/success_worker.rb
+++ b/app/workers/deployments/success_worker.rb
@@ -6,6 +6,7 @@ module Deployments
queue_namespace :deployment
feature_category :continuous_delivery
+ worker_resource_boundary :cpu
def perform(deployment_id)
Deployment.find_by_id(deployment_id).try do |deployment|
diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb
index c82728be32..b56bf4ed83 100644
--- a/app/workers/email_receiver_worker.rb
+++ b/app/workers/email_receiver_worker.rb
@@ -4,6 +4,7 @@ class EmailReceiverWorker
include ApplicationWorker
feature_category :issue_tracking
+ latency_sensitive_worker!
def perform(raw)
return unless Gitlab::IncomingEmail.enabled?
diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb
index 2231c91a72..f523f5953e 100644
--- a/app/workers/emails_on_push_worker.rb
+++ b/app/workers/emails_on_push_worker.rb
@@ -6,6 +6,8 @@ class EmailsOnPushWorker
attr_reader :email, :skip_premailer
feature_category :source_code_management
+ latency_sensitive_worker!
+ worker_resource_boundary :cpu
def perform(project_id, recipients, push_data, options = {})
options.symbolize_keys!
diff --git a/app/workers/expire_build_artifacts_worker.rb b/app/workers/expire_build_artifacts_worker.rb
index 9545227fa3..383fd30e09 100644
--- a/app/workers/expire_build_artifacts_worker.rb
+++ b/app/workers/expire_build_artifacts_worker.rb
@@ -7,25 +7,6 @@ class ExpireBuildArtifactsWorker
feature_category :continuous_integration
def perform
- if Feature.enabled?(:ci_new_expire_job_artifacts_service, default_enabled: true)
- perform_efficient_artifacts_removal
- else
- perform_legacy_artifacts_removal
- end
- end
-
- def perform_efficient_artifacts_removal
Ci::DestroyExpiredJobArtifactsService.new.execute
end
-
- # rubocop: disable CodeReuse/ActiveRecord
- def perform_legacy_artifacts_removal
- Rails.logger.info 'Scheduling removal of build artifacts' # rubocop:disable Gitlab/RailsLogger
-
- build_ids = Ci::Build.with_expired_artifacts.pluck(:id)
- build_ids = build_ids.map { |build_id| [build_id] }
-
- ExpireBuildInstanceArtifactsWorker.bulk_perform_async(build_ids)
- end
- # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/expire_job_cache_worker.rb b/app/workers/expire_job_cache_worker.rb
index b09d0a5d12..0363429587 100644
--- a/app/workers/expire_job_cache_worker.rb
+++ b/app/workers/expire_job_cache_worker.rb
@@ -5,6 +5,7 @@ class ExpireJobCacheWorker
include PipelineQueue
queue_namespace :pipeline_cache
+ latency_sensitive_worker!
# rubocop: disable CodeReuse/ActiveRecord
def perform(job_id)
diff --git a/app/workers/expire_pipeline_cache_worker.rb b/app/workers/expire_pipeline_cache_worker.rb
index 78e68d7bf4..ab57c59ffd 100644
--- a/app/workers/expire_pipeline_cache_worker.rb
+++ b/app/workers/expire_pipeline_cache_worker.rb
@@ -5,6 +5,8 @@ class ExpirePipelineCacheWorker
include PipelineQueue
queue_namespace :pipeline_cache
+ latency_sensitive_worker!
+ worker_resource_boundary :cpu
# rubocop: disable CodeReuse/ActiveRecord
def perform(pipeline_id)
diff --git a/app/workers/gitlab_shell_worker.rb b/app/workers/gitlab_shell_worker.rb
index 9766331cf4..57e64570c0 100644
--- a/app/workers/gitlab_shell_worker.rb
+++ b/app/workers/gitlab_shell_worker.rb
@@ -5,8 +5,11 @@ class GitlabShellWorker
include Gitlab::ShellAdapter
feature_category :source_code_management
+ latency_sensitive_worker!
def perform(action, *arg)
- gitlab_shell.__send__(action, *arg) # rubocop:disable GitlabSecurity/PublicSend
+ Gitlab::GitalyClient::NamespaceService.allow do
+ gitlab_shell.__send__(action, *arg) # rubocop:disable GitlabSecurity/PublicSend
+ end
end
end
diff --git a/app/workers/group_export_worker.rb b/app/workers/group_export_worker.rb
new file mode 100644
index 0000000000..51dbdc9566
--- /dev/null
+++ b/app/workers/group_export_worker.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class GroupExportWorker
+ include ApplicationWorker
+ include ExceptionBacktrace
+
+ feature_category :source_code_management
+
+ def perform(current_user_id, group_id, params = {})
+ current_user = User.find(current_user_id)
+ group = Group.find(group_id)
+
+ ::Groups::ImportExport::ExportService.new(group: group, user: current_user, params: params).execute
+ end
+end
diff --git a/app/workers/hashed_storage/project_migrate_worker.rb b/app/workers/hashed_storage/project_migrate_worker.rb
index f00a459a09..8c0ec97638 100644
--- a/app/workers/hashed_storage/project_migrate_worker.rb
+++ b/app/workers/hashed_storage/project_migrate_worker.rb
@@ -16,7 +16,7 @@ module HashedStorage
project = Project.without_deleted.find_by(id: project_id)
break unless project
- old_disk_path ||= project.disk_path
+ old_disk_path ||= Storage::LegacyProject.new(project).disk_path
::Projects::HashedStorage::MigrationService.new(project, old_disk_path, logger: logger).execute
end
diff --git a/app/workers/import_issues_csv_worker.rb b/app/workers/import_issues_csv_worker.rb
index d983432031..d2733dc5f5 100644
--- a/app/workers/import_issues_csv_worker.rb
+++ b/app/workers/import_issues_csv_worker.rb
@@ -4,6 +4,7 @@ class ImportIssuesCsvWorker
include ApplicationWorker
feature_category :issue_tracking
+ worker_resource_boundary :cpu
sidekiq_retries_exhausted do |job|
Upload.find(job['args'][2]).destroy
diff --git a/app/workers/mail_scheduler/notification_service_worker.rb b/app/workers/mail_scheduler/notification_service_worker.rb
index 0d06dab3b2..4130ce2587 100644
--- a/app/workers/mail_scheduler/notification_service_worker.rb
+++ b/app/workers/mail_scheduler/notification_service_worker.rb
@@ -8,6 +8,7 @@ module MailScheduler
include MailSchedulerQueue
feature_category :issue_tracking
+ worker_resource_boundary :cpu
def perform(meth, *args)
check_arguments!(args)
diff --git a/app/workers/merge_worker.rb b/app/workers/merge_worker.rb
index 70b909afea..ed88c57e8d 100644
--- a/app/workers/merge_worker.rb
+++ b/app/workers/merge_worker.rb
@@ -4,6 +4,7 @@ class MergeWorker
include ApplicationWorker
feature_category :source_code_management
+ latency_sensitive_worker!
def perform(merge_request_id, current_user_id, params)
params = params.with_indifferent_access
diff --git a/app/workers/namespaces/prune_aggregation_schedules_worker.rb b/app/workers/namespaces/prune_aggregation_schedules_worker.rb
index 16259ffbfa..9a5f533fe9 100644
--- a/app/workers/namespaces/prune_aggregation_schedules_worker.rb
+++ b/app/workers/namespaces/prune_aggregation_schedules_worker.rb
@@ -6,6 +6,7 @@ module Namespaces
include CronjobQueue
feature_category :source_code_management
+ worker_resource_boundary :cpu
# Worker to prune pending rows on Namespace::AggregationSchedule
# It's scheduled to run once a day at 1:05am.
diff --git a/app/workers/new_issue_worker.rb b/app/workers/new_issue_worker.rb
index 1b0fec597e..af9ca332d3 100644
--- a/app/workers/new_issue_worker.rb
+++ b/app/workers/new_issue_worker.rb
@@ -5,6 +5,8 @@ class NewIssueWorker
include NewIssuable
feature_category :issue_tracking
+ latency_sensitive_worker!
+ worker_resource_boundary :cpu
def perform(issue_id, user_id)
return unless objects_found?(issue_id, user_id)
diff --git a/app/workers/new_merge_request_worker.rb b/app/workers/new_merge_request_worker.rb
index 0a5b2f8633..aa3f85c157 100644
--- a/app/workers/new_merge_request_worker.rb
+++ b/app/workers/new_merge_request_worker.rb
@@ -5,6 +5,8 @@ class NewMergeRequestWorker
include NewIssuable
feature_category :source_code_management
+ latency_sensitive_worker!
+ worker_resource_boundary :cpu
def perform(merge_request_id, user_id)
return unless objects_found?(merge_request_id, user_id)
diff --git a/app/workers/new_note_worker.rb b/app/workers/new_note_worker.rb
index d0d2a56373..2a5988a7e3 100644
--- a/app/workers/new_note_worker.rb
+++ b/app/workers/new_note_worker.rb
@@ -4,6 +4,8 @@ class NewNoteWorker
include ApplicationWorker
feature_category :issue_tracking
+ latency_sensitive_worker!
+ worker_resource_boundary :cpu
# Keep extra parameter to preserve backwards compatibility with
# old `NewNoteWorker` jobs (can remove later)
diff --git a/app/workers/new_release_worker.rb b/app/workers/new_release_worker.rb
index 28d2517238..a3a882f934 100644
--- a/app/workers/new_release_worker.rb
+++ b/app/workers/new_release_worker.rb
@@ -7,7 +7,7 @@ class NewReleaseWorker
feature_category :release_orchestration
def perform(release_id)
- release = Release.with_project_and_namespace.find_by_id(release_id)
+ release = Release.preloaded.find_by_id(release_id)
return unless release
NotificationService.new.send_new_release_notifications(release)
diff --git a/app/workers/object_pool/join_worker.rb b/app/workers/object_pool/join_worker.rb
index 9c5161fd55..ddd002eabb 100644
--- a/app/workers/object_pool/join_worker.rb
+++ b/app/workers/object_pool/join_worker.rb
@@ -5,6 +5,8 @@ module ObjectPool
include ApplicationWorker
include ObjectPoolQueue
+ worker_resource_boundary :cpu
+
# The use of pool id is deprecated. Keeping the argument allows old jobs to
# still be performed.
def perform(_pool_id, project_id)
diff --git a/app/workers/pages_domain_removal_cron_worker.rb b/app/workers/pages_domain_removal_cron_worker.rb
index 25e747c78d..b150683105 100644
--- a/app/workers/pages_domain_removal_cron_worker.rb
+++ b/app/workers/pages_domain_removal_cron_worker.rb
@@ -5,6 +5,7 @@ class PagesDomainRemovalCronWorker
include CronjobQueue
feature_category :pages
+ worker_resource_boundary :cpu
def perform
PagesDomain.for_removal.find_each do |domain|
diff --git a/app/workers/pipeline_hooks_worker.rb b/app/workers/pipeline_hooks_worker.rb
index eae1115e60..04abc9c88f 100644
--- a/app/workers/pipeline_hooks_worker.rb
+++ b/app/workers/pipeline_hooks_worker.rb
@@ -5,6 +5,8 @@ class PipelineHooksWorker
include PipelineQueue
queue_namespace :pipeline_hooks
+ latency_sensitive_worker!
+ worker_resource_boundary :cpu
# rubocop: disable CodeReuse/ActiveRecord
def perform(pipeline_id)
diff --git a/app/workers/pipeline_metrics_worker.rb b/app/workers/pipeline_metrics_worker.rb
index 0ddad43b8d..3830522aaa 100644
--- a/app/workers/pipeline_metrics_worker.rb
+++ b/app/workers/pipeline_metrics_worker.rb
@@ -4,6 +4,8 @@ class PipelineMetricsWorker
include ApplicationWorker
include PipelineQueue
+ latency_sensitive_worker!
+
# rubocop: disable CodeReuse/ActiveRecord
def perform(pipeline_id)
Ci::Pipeline.find_by(id: pipeline_id).try do |pipeline|
diff --git a/app/workers/pipeline_notification_worker.rb b/app/workers/pipeline_notification_worker.rb
index e4a18573d2..62ecbc8a04 100644
--- a/app/workers/pipeline_notification_worker.rb
+++ b/app/workers/pipeline_notification_worker.rb
@@ -4,6 +4,9 @@ class PipelineNotificationWorker
include ApplicationWorker
include PipelineQueue
+ latency_sensitive_worker!
+ worker_resource_boundary :cpu
+
# rubocop: disable CodeReuse/ActiveRecord
def perform(pipeline_id, recipients = nil)
pipeline = Ci::Pipeline.find_by(id: pipeline_id)
diff --git a/app/workers/pipeline_process_worker.rb b/app/workers/pipeline_process_worker.rb
index 96f3725dbb..2a36ab992e 100644
--- a/app/workers/pipeline_process_worker.rb
+++ b/app/workers/pipeline_process_worker.rb
@@ -6,6 +6,7 @@ class PipelineProcessWorker
queue_namespace :pipeline_processing
feature_category :continuous_integration
+ latency_sensitive_worker!
# rubocop: disable CodeReuse/ActiveRecord
def perform(pipeline_id, build_ids = nil)
diff --git a/app/workers/pipeline_schedule_worker.rb b/app/workers/pipeline_schedule_worker.rb
index f500ea0835..19c3c5fcc2 100644
--- a/app/workers/pipeline_schedule_worker.rb
+++ b/app/workers/pipeline_schedule_worker.rb
@@ -5,6 +5,7 @@ class PipelineScheduleWorker
include CronjobQueue
feature_category :continuous_integration
+ worker_resource_boundary :cpu
def perform
Ci::PipelineSchedule.runnable_schedules.preloaded.find_in_batches do |schedules|
diff --git a/app/workers/pipeline_success_worker.rb b/app/workers/pipeline_success_worker.rb
index 666331e6cd..5c24f00e0c 100644
--- a/app/workers/pipeline_success_worker.rb
+++ b/app/workers/pipeline_success_worker.rb
@@ -5,6 +5,7 @@ class PipelineSuccessWorker
include PipelineQueue
queue_namespace :pipeline_processing
+ latency_sensitive_worker!
def perform(pipeline_id)
# no-op
diff --git a/app/workers/pipeline_update_worker.rb b/app/workers/pipeline_update_worker.rb
index 13a748e155..5b742461f7 100644
--- a/app/workers/pipeline_update_worker.rb
+++ b/app/workers/pipeline_update_worker.rb
@@ -5,6 +5,7 @@ class PipelineUpdateWorker
include PipelineQueue
queue_namespace :pipeline_processing
+ latency_sensitive_worker!
# rubocop: disable CodeReuse/ActiveRecord
def perform(pipeline_id)
diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb
index a3bc7e5b9c..334a98a001 100644
--- a/app/workers/post_receive.rb
+++ b/app/workers/post_receive.rb
@@ -4,6 +4,8 @@ class PostReceive
include ApplicationWorker
feature_category :source_code_management
+ latency_sensitive_worker!
+ worker_resource_boundary :cpu
def perform(gl_repository, identifier, changes, push_options = {})
project, repo_type = Gitlab::GlRepository.parse(gl_repository)
diff --git a/app/workers/process_commit_worker.rb b/app/workers/process_commit_worker.rb
index 1e4561fc6e..8b4d66ae49 100644
--- a/app/workers/process_commit_worker.rb
+++ b/app/workers/process_commit_worker.rb
@@ -11,6 +11,7 @@ class ProcessCommitWorker
include ApplicationWorker
feature_category :source_code_management
+ latency_sensitive_worker!
# project_id - The ID of the project this commit belongs to.
# user_id - The ID of the user that pushed the commit.
diff --git a/app/workers/project_cache_worker.rb b/app/workers/project_cache_worker.rb
index 57a01c0dd8..ae1d57aa12 100644
--- a/app/workers/project_cache_worker.rb
+++ b/app/workers/project_cache_worker.rb
@@ -3,6 +3,9 @@
# Worker for updating any project specific caches.
class ProjectCacheWorker
include ApplicationWorker
+
+ latency_sensitive_worker!
+
LEASE_TIMEOUT = 15.minutes.to_i
feature_category :source_code_management
diff --git a/app/workers/project_export_worker.rb b/app/workers/project_export_worker.rb
index bbcf3b7271..11f3fed82c 100644
--- a/app/workers/project_export_worker.rb
+++ b/app/workers/project_export_worker.rb
@@ -6,6 +6,7 @@ class ProjectExportWorker
sidekiq_options retry: 3
feature_category :source_code_management
+ worker_resource_boundary :memory
def perform(current_user_id, project_id, after_export_strategy = {}, params = {})
current_user = User.find(current_user_id)
diff --git a/app/workers/project_service_worker.rb b/app/workers/project_service_worker.rb
index 8041404fc7..38a2a7414a 100644
--- a/app/workers/project_service_worker.rb
+++ b/app/workers/project_service_worker.rb
@@ -5,6 +5,7 @@ class ProjectServiceWorker
sidekiq_options dead: false
feature_category :integrations
+ worker_has_external_dependencies!
def perform(hook_id, data)
data = data.with_indifferent_access
diff --git a/app/workers/reactive_caching_worker.rb b/app/workers/reactive_caching_worker.rb
index af4a3def06..f3a83e0e8d 100644
--- a/app/workers/reactive_caching_worker.rb
+++ b/app/workers/reactive_caching_worker.rb
@@ -5,6 +5,14 @@ class ReactiveCachingWorker
feature_category_not_owned!
+ # TODO: The reactive caching worker should be split into
+ # two different workers, one for latency_sensitive jobs without external dependencies
+ # and another worker without latency_sensitivity, but with external dependencies
+ # https://gitlab.com/gitlab-com/gl-infra/scalability/issues/34
+ # This worker should also have `worker_has_external_dependencies!` enabled
+ latency_sensitive_worker!
+ worker_resource_boundary :cpu
+
def perform(class_name, id, *args)
klass = begin
class_name.constantize
diff --git a/app/workers/remove_expired_group_links_worker.rb b/app/workers/remove_expired_group_links_worker.rb
index 147b412b77..a43e6fd11d 100644
--- a/app/workers/remove_expired_group_links_worker.rb
+++ b/app/workers/remove_expired_group_links_worker.rb
@@ -8,5 +8,9 @@ class RemoveExpiredGroupLinksWorker
def perform
ProjectGroupLink.expired.destroy_all # rubocop: disable DestroyAll
+
+ GroupGroupLink.expired.find_in_batches do |link_batch|
+ Groups::GroupLinks::DestroyService.new(nil, nil).execute(link_batch)
+ end
end
end
diff --git a/app/workers/remove_expired_members_worker.rb b/app/workers/remove_expired_members_worker.rb
index 75f06fd9f6..bf209fcec9 100644
--- a/app/workers/remove_expired_members_worker.rb
+++ b/app/workers/remove_expired_members_worker.rb
@@ -5,6 +5,7 @@ class RemoveExpiredMembersWorker
include CronjobQueue
feature_category :authentication_and_authorization
+ worker_resource_boundary :cpu
def perform
Member.expired.find_each do |member|
diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb
index bc2d0366fd..15677fb0a9 100644
--- a/app/workers/repository_import_worker.rb
+++ b/app/workers/repository_import_worker.rb
@@ -7,6 +7,7 @@ class RepositoryImportWorker
include ProjectImportOptions
feature_category :importers
+ worker_has_external_dependencies!
# technical debt: https://gitlab.com/gitlab-org/gitlab/issues/33991
sidekiq_options memory_killer_memory_growth_kb: ENV.fetch('MEMORY_KILLER_REPOSITORY_IMPORT_WORKER_MEMORY_GROWTH_KB', 50).to_i
diff --git a/app/workers/repository_update_remote_mirror_worker.rb b/app/workers/repository_update_remote_mirror_worker.rb
index b4d96546fa..d1dec4cb73 100644
--- a/app/workers/repository_update_remote_mirror_worker.rb
+++ b/app/workers/repository_update_remote_mirror_worker.rb
@@ -6,6 +6,8 @@ class RepositoryUpdateRemoteMirrorWorker
include ApplicationWorker
include Gitlab::ExclusiveLeaseHelpers
+ worker_has_external_dependencies!
+
sidekiq_options retry: 3, dead: false
feature_category :source_code_management
diff --git a/app/workers/stage_update_worker.rb b/app/workers/stage_update_worker.rb
index ea587789d0..de2454128f 100644
--- a/app/workers/stage_update_worker.rb
+++ b/app/workers/stage_update_worker.rb
@@ -5,6 +5,7 @@ class StageUpdateWorker
include PipelineQueue
queue_namespace :pipeline_processing
+ latency_sensitive_worker!
# rubocop: disable CodeReuse/ActiveRecord
def perform(stage_id)
diff --git a/app/workers/stuck_ci_jobs_worker.rb b/app/workers/stuck_ci_jobs_worker.rb
index 971edb1f14..b116965d10 100644
--- a/app/workers/stuck_ci_jobs_worker.rb
+++ b/app/workers/stuck_ci_jobs_worker.rb
@@ -5,6 +5,7 @@ class StuckCiJobsWorker
include CronjobQueue
feature_category :continuous_integration
+ worker_resource_boundary :cpu
EXCLUSIVE_LEASE_KEY = 'stuck_ci_builds_worker_lease'
@@ -72,5 +73,19 @@ class StuckCiJobsWorker
Gitlab::OptimisticLocking.retry_lock(build, 3) do |b|
b.drop(reason)
end
+ rescue => ex
+ build.doom!
+
+ track_exception_for_build(ex, build)
+ end
+
+ def track_exception_for_build(ex, build)
+ Gitlab::Sentry.track_acceptable_exception(ex, extra: {
+ build_id: build.id,
+ build_name: build.name,
+ build_stage: build.stage,
+ pipeline_id: build.pipeline_id,
+ project_id: build.project_id
+ })
end
end
diff --git a/app/workers/stuck_import_jobs_worker.rb b/app/workers/stuck_import_jobs_worker.rb
index 4993cd1220..d9a9a613ca 100644
--- a/app/workers/stuck_import_jobs_worker.rb
+++ b/app/workers/stuck_import_jobs_worker.rb
@@ -5,6 +5,7 @@ class StuckImportJobsWorker
include CronjobQueue
feature_category :importers
+ worker_resource_boundary :cpu
IMPORT_JOBS_EXPIRATION = 15.hours.to_i
diff --git a/app/workers/update_head_pipeline_for_merge_request_worker.rb b/app/workers/update_head_pipeline_for_merge_request_worker.rb
index 77859abfea..e069b16eb9 100644
--- a/app/workers/update_head_pipeline_for_merge_request_worker.rb
+++ b/app/workers/update_head_pipeline_for_merge_request_worker.rb
@@ -6,6 +6,8 @@ class UpdateHeadPipelineForMergeRequestWorker
queue_namespace :pipeline_processing
feature_category :continuous_integration
+ latency_sensitive_worker!
+ worker_resource_boundary :cpu
def perform(merge_request_id)
MergeRequest.find_by_id(merge_request_id).try do |merge_request|
diff --git a/app/workers/update_merge_requests_worker.rb b/app/workers/update_merge_requests_worker.rb
index 8e1703cdd0..acb9535398 100644
--- a/app/workers/update_merge_requests_worker.rb
+++ b/app/workers/update_merge_requests_worker.rb
@@ -4,6 +4,8 @@ class UpdateMergeRequestsWorker
include ApplicationWorker
feature_category :source_code_management
+ latency_sensitive_worker!
+ worker_resource_boundary :cpu
LOG_TIME_THRESHOLD = 90 # seconds
diff --git a/app/workers/wait_for_cluster_creation_worker.rb b/app/workers/wait_for_cluster_creation_worker.rb
index 8aa1d9290f..621125c850 100644
--- a/app/workers/wait_for_cluster_creation_worker.rb
+++ b/app/workers/wait_for_cluster_creation_worker.rb
@@ -4,10 +4,16 @@ class WaitForClusterCreationWorker
include ApplicationWorker
include ClusterQueue
+ worker_has_external_dependencies!
+
def perform(cluster_id)
Clusters::Cluster.find_by_id(cluster_id).try do |cluster|
cluster.provider.try do |provider|
- Clusters::Gcp::VerifyProvisionStatusService.new.execute(provider) if cluster.gcp?
+ if cluster.gcp?
+ Clusters::Gcp::VerifyProvisionStatusService.new.execute(provider)
+ elsif cluster.aws?
+ Clusters::Aws::VerifyProvisionStatusService.new.execute(provider)
+ end
end
end
end
diff --git a/app/workers/web_hook_worker.rb b/app/workers/web_hook_worker.rb
index fd7ca93683..c3fa3162c1 100644
--- a/app/workers/web_hook_worker.rb
+++ b/app/workers/web_hook_worker.rb
@@ -4,6 +4,8 @@ class WebHookWorker
include ApplicationWorker
feature_category :integrations
+ worker_has_external_dependencies!
+
sidekiq_options retry: 4, dead: false
def perform(hook_id, data, hook_name)
diff --git a/bin/secpick b/bin/secpick
index a44867846d..963172987f 100755
--- a/bin/secpick
+++ b/bin/secpick
@@ -103,7 +103,7 @@ module Secpick
options[:branch] = branch
end
- opts.on('-s', '--sha abcd', 'SHA to cherry pick') do |sha|
+ opts.on('-s', '--sha abcd', 'SHA or SHA range to cherry pick') do |sha|
options[:sha] = sha
end
diff --git a/changelogs/unreleased/18126-change-tag-url-for-tag-push-events-in-chat-msg-integration.yaml b/changelogs/unreleased/18126-change-tag-url-for-tag-push-events-in-chat-msg-integration.yaml
new file mode 100644
index 0000000000..c576b57f7c
--- /dev/null
+++ b/changelogs/unreleased/18126-change-tag-url-for-tag-push-events-in-chat-msg-integration.yaml
@@ -0,0 +1,5 @@
+---
+title: "Show tag link whenever it's a tag in chat message integration for push events and pipeline events"
+merge_request: 18126
+author: Mats Estensen
+type: fixed
diff --git a/config/application.rb b/config/application.rb
index 5d7c52c5d8..cad5c8bbe7 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -157,6 +157,8 @@ module Gitlab
config.assets.paths << "#{config.root}/vendor/assets/fonts"
config.assets.precompile << "print.css"
+ config.assets.precompile << "mailer.css"
+ config.assets.precompile << "mailer_client_specific.css"
config.assets.precompile << "notify.css"
config.assets.precompile << "mailers/*.css"
config.assets.precompile << "page_bundles/ide.css"
@@ -247,15 +249,18 @@ module Gitlab
end
# Use caching across all environments
+ # Full list of options:
+ # https://api.rubyonrails.org/classes/ActiveSupport/Cache/RedisCacheStore.html#method-c-new
caching_config_hash = Gitlab::Redis::Cache.params
+ caching_config_hash[:compress] = false
caching_config_hash[:namespace] = Gitlab::Redis::Cache::CACHE_NAMESPACE
caching_config_hash[:expires_in] = 2.weeks # Cache should not grow forever
- if Sidekiq.server? # threaded context
- caching_config_hash[:pool_size] = Sidekiq.options[:concurrency] + 5
+ if Sidekiq.server? || defined?(::Puma) # threaded context
+ caching_config_hash[:pool_size] = Gitlab::Redis::Cache.pool_size
caching_config_hash[:pool_timeout] = 1
end
- config.cache_store = :redis_store, caching_config_hash
+ config.cache_store = :redis_cache_store, caching_config_hash
config.active_job.queue_adapter = :sidekiq
diff --git a/config/dependency_decisions.yml b/config/dependency_decisions.yml
index 5346bf4547..84db15d653 100644
--- a/config/dependency_decisions.yml
+++ b/config/dependency_decisions.yml
@@ -620,3 +620,9 @@
:why: https://github.com/hexorx/countries/blob/master/LICENSE
:versions: []
:when: 2019-09-11 13:08:28.431132000 Z
+- - :whitelist
+ - "(MIT OR CC0-1.0)"
+ - :who:
+ :why:
+ :versions: []
+ :when: 2019-11-08 10:03:31.787226000 Z
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index f6814262b7..a5486e450d 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -8,7 +8,7 @@
# If a setting requires an application restart say so in that screen. #
# If you change this file in a Merge Request, please also create #
# a MR on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests. #
-# For more details see https://gitlab.com/gitlab-org/omnibus-gitlab/blob/0928cfb09f43993fd9454b0b14dbd1924b1407bc/doc/settings/gitlab.yml.md #
+# For more details see https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/gitlab.yml.md #
########################################################################
#
#
@@ -467,6 +467,13 @@ production: &base
# enabled: true
# primary_api_url: http://localhost:5000/ # internal address to the primary registry, will be used by GitLab to directly communicate with primary registry API
+ ## Feature Flag https://docs.gitlab.com/ee/user/project/operations/feature_flags.html
+ feature_flags:
+ unleash:
+ # enabled: false
+ # url: https://gitlab.com/api/v4/feature_flags/unleash/
+ # app_name: gitlab.com # Environment name of your GitLab instance
+ # instance_id: INSTANCE_ID
#
# 2. GitLab CI settings
@@ -494,6 +501,7 @@ production: &base
# bundle exec rake gitlab:ldap:check RAILS_ENV=production
ldap:
enabled: false
+ prevent_ldap_sign_in: false
# This setting controls the number of seconds between LDAP permission checks
# for each user. After this time has expired for a given user, their next
@@ -1024,12 +1032,6 @@ production: &base
# enabled: true
# address: localhost
# port: 8083
- # # blackout_seconds:
- # # defines an interval to block healthcheck,
- # # but continue accepting application requests
- # # this allows Load Balancer to notice service
- # # being shutdown and not interrupt any of the clients
- # blackout_seconds: 10
## Prometheus settings
# Do not modify these settings here. They should be modified in /etc/gitlab/gitlab.rb
@@ -1041,6 +1043,14 @@ production: &base
# enable: true
# listen_address: 'localhost:9090'
+ shutdown:
+ # # blackout_seconds:
+ # # defines an interval to block healthcheck,
+ # # but continue accepting application requests
+ # # this allows Load Balancer to notice service
+ # # being shutdown and not interrupt any of the clients
+ # blackout_seconds: 10
+
#
# 5. Extra customization
# ==========================
diff --git a/config/initializers/0_inflections.rb b/config/initializers/0_inflections.rb
index c0afa207ac..7690eafdc6 100644
--- a/config/initializers/0_inflections.rb
+++ b/config/initializers/0_inflections.rb
@@ -12,18 +12,19 @@
ActiveSupport::Inflector.inflections do |inflect|
inflect.uncountable %w(
award_emoji
- project_statistics
- system_note_metadata
- event_log
- project_auto_devops
- project_registry
- file_registry
- job_artifact_registry
container_repository_registry
design_registry
- vulnerability_feedback
- vulnerabilities_feedback
+ event_log
+ file_registry
group_view
+ job_artifact_registry
+ lfs_object_registry
+ project_auto_devops
+ project_registry
+ project_statistics
+ system_note_metadata
+ vulnerabilities_feedback
+ vulnerability_feedback
)
inflect.acronym 'EE'
end
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 7ee4a4e361..df4f49524b 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -5,6 +5,7 @@ require_relative '../smime_signature_settings'
# Default settings
Settings['ldap'] ||= Settingslogic.new({})
Settings.ldap['enabled'] = false if Settings.ldap['enabled'].nil?
+Settings.ldap['prevent_ldap_sign_in'] = false if Settings.ldap['prevent_ldap_sign_in'].blank?
Gitlab.ee do
Settings.ldap['sync_time'] = 3600 if Settings.ldap['sync_time'].nil?
@@ -307,6 +308,13 @@ Gitlab.ee do
Settings.geo.registry_replication['enabled'] ||= false
end
+#
+# Unleash
+#
+Settings['feature_flags'] ||= Settingslogic.new({})
+Settings.feature_flags['unleash'] ||= Settingslogic.new({})
+Settings.feature_flags.unleash['enabled'] = false if Settings.feature_flags.unleash['enabled'].nil?
+
#
# External merge request diffs
#
@@ -668,7 +676,12 @@ Settings.monitoring['web_exporter'] ||= Settingslogic.new({})
Settings.monitoring.web_exporter['enabled'] ||= false
Settings.monitoring.web_exporter['address'] ||= 'localhost'
Settings.monitoring.web_exporter['port'] ||= 8083
-Settings.monitoring.web_exporter['blackout_seconds'] ||= 10
+
+#
+# Shutdown settings
+#
+Settings['shutdown'] ||= Settingslogic.new({})
+Settings.shutdown['blackout_seconds'] ||= 10
#
# Testing settings
diff --git a/config/initializers/7_prometheus_metrics.rb b/config/initializers/7_prometheus_metrics.rb
index 974eff1a52..d40049970c 100644
--- a/config/initializers/7_prometheus_metrics.rb
+++ b/config/initializers/7_prometheus_metrics.rb
@@ -70,8 +70,15 @@ if defined?(::Unicorn) || defined?(::Puma)
Gitlab::Metrics::Exporter::WebExporter.instance.start
end
- Gitlab::Cluster::LifecycleEvents.on_before_phased_restart do
- # We need to ensure that before we re-exec server
+ # DEPRECATED: TO BE REMOVED
+ # This is needed to implement blackout period of `web_exporter`
+ # https://gitlab.com/gitlab-org/gitlab/issues/35343#note_238479057
+ Gitlab::Cluster::LifecycleEvents.on_before_blackout_period do
+ Gitlab::Metrics::Exporter::WebExporter.instance.mark_as_not_running!
+ end
+
+ Gitlab::Cluster::LifecycleEvents.on_before_graceful_shutdown do
+ # We need to ensure that before we re-exec or shutdown server
# we do stop the exporter
Gitlab::Metrics::Exporter::WebExporter.instance.stop
end
diff --git a/config/initializers/database_config.rb b/config/initializers/database_config.rb
new file mode 100644
index 0000000000..d8c2821066
--- /dev/null
+++ b/config/initializers/database_config.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+# when running on puma, scale connection pool size with the number
+# of threads per worker process
+if defined?(::Puma)
+ db_config = Gitlab::Database.config ||
+ Rails.application.config.database_configuration[Rails.env]
+ puma_options = Puma.cli_config.options
+
+ # We use either the maximum number of threads per worker process, or
+ # the user specified value, whichever is larger.
+ desired_pool_size = [db_config['pool'].to_i, puma_options[:max_threads]].max
+
+ db_config['pool'] = desired_pool_size
+
+ # recreate the connection pool from the new config
+ ActiveRecord::Base.establish_connection(db_config)
+end
diff --git a/config/initializers/health_check.rb b/config/initializers/health_check.rb
index 9f466dc39d..1496f20afc 100644
--- a/config/initializers/health_check.rb
+++ b/config/initializers/health_check.rb
@@ -8,3 +8,15 @@ HealthCheck.setup do |config|
end
end
end
+
+Gitlab::Cluster::LifecycleEvents.on_before_fork do
+ Gitlab::HealthChecks::MasterCheck.register_master
+end
+
+Gitlab::Cluster::LifecycleEvents.on_before_blackout_period do
+ Gitlab::HealthChecks::MasterCheck.finish_master
+end
+
+Gitlab::Cluster::LifecycleEvents.on_worker_start do
+ Gitlab::HealthChecks::MasterCheck.register_worker
+end
diff --git a/config/initializers/lograge.rb b/config/initializers/lograge.rb
index d5d4c58988..a820786273 100644
--- a/config/initializers/lograge.rb
+++ b/config/initializers/lograge.rb
@@ -10,6 +10,11 @@ unless Sidekiq.server?
# unmaintained gem that monkey patches `Time`
config.lograge.formatter = Lograge::Formatters::Json.new
config.lograge.logger = ActiveSupport::Logger.new(filename)
+ config.lograge.before_format = lambda do |data, payload|
+ data.delete(:error)
+ data
+ end
+
# Add request parameters to log output
config.lograge.custom_options = lambda do |event|
params = event.payload[:params]
@@ -36,6 +41,11 @@ unless Sidekiq.server?
payload[:cpu_s] = cpu_s
end
+ # https://github.com/roidrage/lograge#logging-errors--exceptions
+ exception = event.payload[:exception_object]
+
+ ::Gitlab::ExceptionLogFormatter.format!(exception, payload)
+
payload
end
end
diff --git a/config/initializers/rack_attack_git_basic_auth.rb b/config/initializers/rack_attack_git_basic_auth.rb
index 6a72182617..71e5e2969c 100644
--- a/config/initializers/rack_attack_git_basic_auth.rb
+++ b/config/initializers/rack_attack_git_basic_auth.rb
@@ -1,14 +1,14 @@
-rack_attack_enabled = Gitlab.config.rack_attack.git_basic_auth['enabled']
+# Tell the Rack::Attack Rack middleware to maintain an IP blacklist.
+# We update the blacklist in Gitlab::Auth::IpRateLimiter.
+Rack::Attack.blocklist('Git HTTP Basic Auth') do |req|
+ rate_limiter = Gitlab::Auth::IpRateLimiter.new(req.ip)
-unless Rails.env.test? || !rack_attack_enabled
- # Tell the Rack::Attack Rack middleware to maintain an IP blacklist. We will
- # update the blacklist from Grack::Auth#authenticate_user.
- Rack::Attack.blacklist('Git HTTP Basic Auth') do |req|
- Rack::Attack::Allow2Ban.filter(req.ip, Gitlab.config.rack_attack.git_basic_auth) do
- # This block only gets run if the IP was not already banned.
- # Return false, meaning that we do not see anything wrong with the
- # request at this time
- false
- end
+ next false if !rate_limiter.enabled? || rate_limiter.trusted_ip?
+
+ Rack::Attack::Allow2Ban.filter(req.ip, Gitlab.config.rack_attack.git_basic_auth) do
+ # This block only gets run if the IP was not already banned.
+ # Return false, meaning that we do not see anything wrong with the
+ # request at this time
+ false
end
end
diff --git a/config/initializers/rack_attack_logging.rb b/config/initializers/rack_attack_logging.rb
index be7c2175cb..a95cb09755 100644
--- a/config/initializers/rack_attack_logging.rb
+++ b/config/initializers/rack_attack_logging.rb
@@ -2,8 +2,10 @@
#
# Adds logging for all Rack Attack blocks and throttling events.
-ActiveSupport::Notifications.subscribe('rack.attack') do |name, start, finish, request_id, req|
- if [:throttle, :blacklist].include? req.env['rack.attack.match_type']
+ActiveSupport::Notifications.subscribe(/rack_attack/) do |name, start, finish, request_id, payload|
+ req = payload[:request]
+
+ if [:throttle, :blocklist].include? req.env['rack.attack.match_type']
rack_attack_info = {
message: 'Rack_Attack',
env: req.env['rack.attack.match_type'],
diff --git a/config/initializers/rack_attack_new.rb b/config/initializers/rack_attack_new.rb
index 50f9d7971c..92a8bf7943 100644
--- a/config/initializers/rack_attack_new.rb
+++ b/config/initializers/rack_attack_new.rb
@@ -39,48 +39,65 @@ module Gitlab::Throttle
end
class Rack::Attack
+ # Order conditions by how expensive they are:
+ # 1. The most expensive is the `req.unauthenticated?` and
+ # `req.authenticated_user_id` as it performs an expensive
+ # DB/Redis query to validate the request
+ # 2. Slightly less expensive is the need to query DB/Redis
+ # to unmarshal settings (`Gitlab::Throttle.settings`)
+ #
+ # We deliberately skip `/-/health|liveness|readiness`
+ # from Rack Attack as they need to always be accessible
+ # by Load Balancer and additional measure is implemented
+ # (token and whitelisting) to prevent abuse.
throttle('throttle_unauthenticated', Gitlab::Throttle.unauthenticated_options) do |req|
- Gitlab::Throttle.settings.throttle_unauthenticated_enabled &&
- req.unauthenticated? &&
- !req.should_be_skipped? &&
+ if !req.should_be_skipped? &&
+ Gitlab::Throttle.settings.throttle_unauthenticated_enabled &&
+ req.unauthenticated?
req.ip
+ end
end
throttle('throttle_authenticated_api', Gitlab::Throttle.authenticated_api_options) do |req|
- Gitlab::Throttle.settings.throttle_authenticated_api_enabled &&
- req.api_request? &&
+ if req.api_request? &&
+ Gitlab::Throttle.settings.throttle_authenticated_api_enabled
req.authenticated_user_id([:api])
+ end
end
throttle('throttle_authenticated_web', Gitlab::Throttle.authenticated_web_options) do |req|
- Gitlab::Throttle.settings.throttle_authenticated_web_enabled &&
- req.web_request? &&
+ if req.web_request? &&
+ Gitlab::Throttle.settings.throttle_authenticated_web_enabled
req.authenticated_user_id([:api, :rss, :ics])
+ end
end
throttle('throttle_unauthenticated_protected_paths', Gitlab::Throttle.protected_paths_options) do |req|
- req.post? &&
- Gitlab::Throttle.protected_paths_enabled? &&
- req.unauthenticated? &&
- !req.should_be_skipped? &&
- req.protected_path? &&
+ if req.post? &&
+ !req.should_be_skipped? &&
+ req.protected_path? &&
+ Gitlab::Throttle.protected_paths_enabled? &&
+ req.unauthenticated?
req.ip
+ end
end
throttle('throttle_authenticated_protected_paths_api', Gitlab::Throttle.protected_paths_options) do |req|
- req.post? &&
- Gitlab::Throttle.protected_paths_enabled? &&
- req.api_request? &&
- req.protected_path? &&
+ if req.post? &&
+ req.api_request? &&
+ req.protected_path? &&
+ Gitlab::Throttle.protected_paths_enabled?
req.authenticated_user_id([:api])
+ end
end
throttle('throttle_authenticated_protected_paths_web', Gitlab::Throttle.protected_paths_options) do |req|
- req.post? &&
- Gitlab::Throttle.protected_paths_enabled? &&
- req.web_request? &&
- req.protected_path? &&
+ if req.post? &&
+ req.web_request? &&
+ req.protected_path? &&
+ Gitlab::Throttle.protected_paths_enabled?
req.authenticated_user_id([:api, :rss, :ics])
+ end
end
class Request
@@ -100,12 +117,16 @@ class Rack::Attack
path =~ %r{^/api/v\d+/internal/}
end
+ def health_check_request?
+ path =~ %r{^/-/(health|liveness|readiness)}
+ end
+
def should_be_skipped?
- api_internal_request?
+ api_internal_request? || health_check_request?
end
def web_request?
- !api_request?
+ !api_request? && !health_check_request?
end
def protected_path?
diff --git a/config/initializers/validate_puma.rb b/config/initializers/validate_puma.rb
new file mode 100644
index 0000000000..64bd6e7bbc
--- /dev/null
+++ b/config/initializers/validate_puma.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+if defined?(::Puma) && ::Puma.cli_config.options[:workers].to_i.zero?
+ raise 'Puma is only supported in Cluster-mode: workers > 0'
+end
diff --git a/config/locales/en.yml b/config/locales/en.yml
index eff015459e..950529f035 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -19,6 +19,7 @@ en:
project/grafana_integration:
token: "Grafana HTTP API Token"
grafana_url: "Grafana API URL"
+ grafana_enabled: "Grafana integration enabled"
views:
pagination:
previous: "Prev"
diff --git a/config/puma.example.development.rb b/config/puma.example.development.rb
index f23ccc23c9..6f686437f8 100644
--- a/config/puma.example.development.rb
+++ b/config/puma.example.development.rb
@@ -14,9 +14,13 @@ rackup 'config.ru'
pidfile '/home/git/gitlab/tmp/pids/puma.pid'
state_path '/home/git/gitlab/tmp/pids/puma.state'
-stdout_redirect '/home/git/gitlab/log/puma.stdout.log',
- '/home/git/gitlab/log/puma.stderr.log',
- true
+## Uncomment the lines if you would like to write puma stdout & stderr streams
+## to a different location than rails logs.
+## When using GitLab Development Kit, by default, these logs will be consumed
+## by runit and can be accessed using `gdk tail rails-web`
+# stdout_redirect '/home/git/gitlab/log/puma.stdout.log',
+# '/home/git/gitlab/log/puma.stderr.log',
+# true
# Configure "min" to be the minimum number of threads to use to answer
# requests and "max" the maximum.
diff --git a/config/routes.rb b/config/routes.rb
index 5bfae777f1..9fb4d94f06 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -57,7 +57,7 @@ Rails.application.routes.draw do
# Sign up
get 'users/sign_up/welcome' => 'registrations#welcome'
- patch 'users/sign_up/update_role' => 'registrations#update_role'
+ patch 'users/sign_up/update_registration' => 'registrations#update_registration'
# Search
get 'search' => 'search#show'
@@ -142,6 +142,13 @@ Rails.application.routes.draw do
collection do
post :create_user
post :create_gcp
+ post :create_aws
+ post :authorize_aws_role
+ delete :revoke_aws_role
+
+ scope :aws do
+ get 'api/:resource', to: 'clusters#aws_proxy', as: :aws_proxy
+ end
end
member do
diff --git a/config/routes/group.rb b/config/routes/group.rb
index 093cde64c8..437c80b8c9 100644
--- a/config/routes/group.rb
+++ b/config/routes/group.rb
@@ -62,6 +62,8 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
delete :leave, on: :collection
end
+ resources :group_links, only: [:index, :create, :update, :destroy], constraints: { id: /\d+/ }
+
resources :uploads, only: [:create] do
collection do
get ":secret/:filename", action: :show, as: :show, constraints: { filename: %r{[^/]+} }
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 7d51cfd6de..3f913683b0 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -179,7 +179,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
- resources :releases, only: [:index]
+ resources :releases, only: [:index, :edit], param: :tag, constraints: { tag: %r{[^/]+} }
resources :starrers, only: [:index]
resources :forks, only: [:index, :new, :create]
resources :group_links, only: [:index, :create, :update, :destroy], constraints: { id: /\d+/ }
@@ -187,9 +187,35 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
resource :import, only: [:new, :create, :show]
resource :avatar, only: [:show, :destroy]
- get 'grafana/proxy/:datasource_id/*proxy_path',
- to: 'grafana_api#proxy',
- as: :grafana_api
+ scope :grafana, as: :grafana_api do
+ get 'proxy/:datasource_id/*proxy_path', to: 'grafana_api#proxy'
+ get :metrics_dashboard, to: 'grafana_api#metrics_dashboard'
+ end
+
+ resource :mattermost, only: [:new, :create]
+ resource :variables, only: [:show, :update]
+ resources :triggers, only: [:index, :create, :edit, :update, :destroy]
+
+ resource :mirror, only: [:show, :update] do
+ member do
+ get :ssh_host_keys, constraints: { format: :json }
+ post :update_now
+ end
+ end
+
+ resource :cycle_analytics, only: [:show]
+
+ namespace :cycle_analytics do
+ scope :events, controller: 'events' do
+ get :issue
+ get :plan
+ get :code
+ get :test
+ get :review
+ get :staging
+ get :production
+ end
+ end
end
# End of the /-/ scope.
@@ -222,6 +248,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
resources :domains, except: :index, controller: 'pages_domains', constraints: { id: %r{[^/]+} } do
member do
post :verify
+ delete :clean_certificate
end
end
end
@@ -233,8 +260,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
- resource :mattermost, only: [:new, :create]
-
namespace :prometheus do
resources :metrics, constraints: { id: %r{[^\/]+} }, only: [:index, :new, :create, :edit, :update, :destroy] do
get :active_common, on: :collection
@@ -274,6 +299,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get :discussions, format: :json
post :rebase
get :test_reports
+ get :exposed_artifacts
scope constraints: { format: nil }, action: :show do
get :commits, defaults: { tab: 'commits' }
@@ -361,17 +387,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
put '/service_desk' => 'service_desk#update', as: :service_desk_refresh
end
- resource :variables, only: [:show, :update]
-
- resources :triggers, only: [:index, :create, :edit, :update, :destroy]
-
- resource :mirror, only: [:show, :update] do
- member do
- get :ssh_host_keys, constraints: { format: :json }
- post :update_now
- end
- end
-
Gitlab.ee do
resources :push_rules, constraints: { id: /\d+/ }, only: [:update]
end
@@ -430,6 +445,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
Gitlab.ee do
get :logs
+ get '/pods/(:pod_name)/containers/(:container_name)/logs', to: 'environments#k8s_pod_logs', as: :k8s_pod_logs
end
end
@@ -437,6 +453,10 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get :metrics, action: :metrics_redirect
get :folder, path: 'folders/*id', constraints: { format: /(html|json)/ }
get :search
+
+ Gitlab.ee do
+ get :logs, action: :logs_redirect
+ end
end
resources :deployments, only: [:index] do
@@ -455,20 +475,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
- resource :cycle_analytics, only: [:show]
-
- namespace :cycle_analytics do
- scope :events, controller: 'events' do
- get :issue
- get :plan
- get :code
- get :test
- get :review
- get :staging
- get :production
- end
- end
-
namespace :serverless do
scope :functions do
get '/:environment_id/:id', to: 'functions#show'
@@ -609,10 +615,20 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
resources :error_tracking, only: [:index], controller: :error_tracking do
collection do
+ get ':issue_id/details',
+ to: 'error_tracking#details',
+ as: 'details'
+ get ':issue_id/stack_trace',
+ to: 'error_tracking#stack_trace',
+ as: 'stack_trace'
post :list_projects
end
end
+ scope :usage_ping, controller: :usage_ping do
+ post :web_ide_clientside_preview
+ end
+
# Since both wiki and repository routing contains wildcard characters
# its preferable to keep it below all other project routes
draw :wiki
@@ -648,7 +664,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
# Legacy routes.
# Introduced in 12.0.
- # Should be removed after 12.1
+ # Should be removed with https://gitlab.com/gitlab-org/gitlab/issues/28848.
scope(path: '*namespace_id',
as: :namespace,
namespace_id: Gitlab::PathRegex.full_namespace_route_regex) do
@@ -660,7 +676,8 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
:network, :graphs, :autocomplete_sources,
:project_members, :deploy_keys, :deploy_tokens,
:labels, :milestones, :services, :boards, :releases,
- :forks, :group_links, :import, :avatar)
+ :forks, :group_links, :import, :avatar, :mirror,
+ :cycle_analytics, :mattermost, :variables, :triggers)
end
end
end
diff --git a/config/routes/user.rb b/config/routes/user.rb
index d4616c8080..31af321d2b 100644
--- a/config/routes/user.rb
+++ b/config/routes/user.rb
@@ -13,7 +13,7 @@ def override_omniauth(provider, controller, path_prefix = '/users/auth')
end
# Use custom controller for LDAP omniauth callback
-if Gitlab::Auth::LDAP::Config.enabled?
+if Gitlab::Auth::LDAP::Config.sign_in_enabled?
devise_scope :user do
Gitlab::Auth::LDAP::Config.available_servers.each do |server|
override_omniauth(server['provider_name'], 'ldap/omniauth_callbacks')
@@ -55,6 +55,7 @@ scope(constraints: { username: Gitlab::PathRegex.root_namespace_route_regex }) d
get :starred, as: :starred_projects
get :snippets
get :exists
+ get :suggests
get :activity
get '/', to: redirect('%{username}'), as: nil
end
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index b97e8ad67c..b4be61d8a3 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -98,8 +98,10 @@
- [update_namespace_statistics, 1]
- [chaos, 2]
- [create_evidence, 2]
+ - [group_export, 1]
# EE-specific queues
+ - [analytics, 1]
- [ldap_group_sync, 2]
- [create_github_webhook, 2]
- [geo, 1]
@@ -120,3 +122,4 @@
- [update_external_pull_requests, 3]
- [refresh_license_compliance_checks, 2]
- [design_management_new_version, 1]
+ - [epics, 2]
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 25fb6cc5f5..9c7a3f42c9 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -73,7 +73,7 @@ function generateEntries() {
const manualEntries = {
default: defaultEntries,
- raven: './raven/index.js',
+ sentry: './sentry/index.js',
};
return Object.assign(manualEntries, autoEntries);
@@ -299,6 +299,11 @@ module.exports = {
from: path.join(ROOT_PATH, 'node_modules/pdfjs-dist/cmaps/'),
to: path.join(ROOT_PATH, 'public/assets/webpack/cmaps/'),
},
+ {
+ from: path.join(ROOT_PATH, 'node_modules/@sourcegraph/code-host-integration/'),
+ to: path.join(ROOT_PATH, 'public/assets/webpack/sourcegraph/'),
+ ignore: ['package.json'],
+ },
{
from: path.join(
ROOT_PATH,
diff --git a/core-js/internals/shared.js b/core-js/internals/shared.js
index 34d0bc9a17..91c12ff761 100644
--- a/core-js/internals/shared.js
+++ b/core-js/internals/shared.js
@@ -4,7 +4,7 @@ var store = require('../internals/shared-store');
(module.exports = function (key, value) {
return store[key] || (store[key] = value !== undefined ? value : {});
})('versions', []).push({
- version: '3.6.0',
+ version: '3.6.1',
mode: IS_PURE ? 'pure' : 'global',
copyright: '© 2019 Denis Pushkarev (zloirock.ru)'
});
diff --git a/core-js/internals/use-symbol-as-uid.js b/core-js/internals/use-symbol-as-uid.js
index dc0a41ef86..5654bf67ac 100644
--- a/core-js/internals/use-symbol-as-uid.js
+++ b/core-js/internals/use-symbol-as-uid.js
@@ -4,4 +4,4 @@ module.exports = NATIVE_SYMBOL
// eslint-disable-next-line no-undef
&& !Symbol.sham
// eslint-disable-next-line no-undef
- && typeof Symbol() == 'symbol';
+ && typeof Symbol.iterator == 'symbol';
diff --git a/core-js/internals/well-known-symbol.js b/core-js/internals/well-known-symbol.js
index 6c1ea40fc0..3fcc8c3a05 100644
--- a/core-js/internals/well-known-symbol.js
+++ b/core-js/internals/well-known-symbol.js
@@ -7,7 +7,7 @@ var USE_SYMBOL_AS_UID = require('../internals/use-symbol-as-uid');
var WellKnownSymbolsStore = shared('wks');
var Symbol = global.Symbol;
-var createWellKnownSymbol = USE_SYMBOL_AS_UID ? Symbol : uid;
+var createWellKnownSymbol = USE_SYMBOL_AS_UID ? Symbol : Symbol && Symbol.withoutSetter || uid;
module.exports = function (name) {
if (!has(WellKnownSymbolsStore, name)) {
diff --git a/core-js/modules/es.symbol.js b/core-js/modules/es.symbol.js
index d11563d906..3fbac06858 100644
--- a/core-js/modules/es.symbol.js
+++ b/core-js/modules/es.symbol.js
@@ -83,7 +83,7 @@ var wrap = function (tag, description) {
return symbol;
};
-var isSymbol = NATIVE_SYMBOL && typeof $Symbol.iterator == 'symbol' ? function (it) {
+var isSymbol = USE_SYMBOL_AS_UID ? function (it) {
return typeof it == 'symbol';
} : function (it) {
return Object(it) instanceof $Symbol;
@@ -178,12 +178,20 @@ if (!NATIVE_SYMBOL) {
return getInternalState(this).tag;
});
+ redefine($Symbol, 'withoutSetter', function (description) {
+ return wrap(uid(description), description);
+ });
+
propertyIsEnumerableModule.f = $propertyIsEnumerable;
definePropertyModule.f = $defineProperty;
getOwnPropertyDescriptorModule.f = $getOwnPropertyDescriptor;
getOwnPropertyNamesModule.f = getOwnPropertyNamesExternal.f = $getOwnPropertyNames;
getOwnPropertySymbolsModule.f = $getOwnPropertySymbols;
+ wrappedWellKnownSymbolModule.f = function (name) {
+ return wrap(wellKnownSymbol(name), name);
+ };
+
if (DESCRIPTORS) {
// https://github.com/tc39/proposal-Symbol-description
nativeDefineProperty($Symbol[PROTOTYPE], 'description', {
@@ -198,12 +206,6 @@ if (!NATIVE_SYMBOL) {
}
}
-if (!USE_SYMBOL_AS_UID) {
- wrappedWellKnownSymbolModule.f = function (name) {
- return wrap(wellKnownSymbol(name), name);
- };
-}
-
$({ global: true, wrap: true, forced: !NATIVE_SYMBOL, sham: !NATIVE_SYMBOL }, {
Symbol: $Symbol
});
diff --git a/core-js/package.json b/core-js/package.json
index ac52e5381a..9d77ef022d 100644
--- a/core-js/package.json
+++ b/core-js/package.json
@@ -1,7 +1,7 @@
{
"name": "core-js",
"description": "Standard library",
- "version": "3.6.0",
+ "version": "3.6.1",
"repository": {
"type": "git",
"url": "https://github.com/zloirock/core-js.git"
diff --git a/danger/commit_messages/Dangerfile b/danger/commit_messages/Dangerfile
index 064b8c9480..60bc90139a 100644
--- a/danger/commit_messages/Dangerfile
+++ b/danger/commit_messages/Dangerfile
@@ -86,6 +86,12 @@ def unicode_emoji_regex
))x
end
+def count_filtered_commits(commits)
+ commits.count do |commit|
+ !commit.message.start_with?('fixup!', 'squash!')
+ end
+end
+
def lint_commit(commit) # rubocop:disable Metrics/AbcSize
# For now we'll ignore merge commits, as getting rid of those is a problem
# separate from enforcing good commit messages.
@@ -234,7 +240,7 @@ def lint_commit(commit) # rubocop:disable Metrics/AbcSize
fail_commit(
commit,
'Use full URLs instead of short references ' \
- '(`gitlab-org/gitlab-ce#123` or `!123`), as short references are ' \
+ '(`gitlab-org/gitlab#123` or `!123`), as short references are ' \
'displayed as plain text outside of GitLab'
)
@@ -285,7 +291,7 @@ def lint_commits(commits)
end
end
-if git.commits.length > 10 && !ce_upstream?
+if count_filtered_commits(git.commits) > 10 && !ce_upstream?
warn(
'This merge request includes more than 10 commits. ' \
'Please rebase these commits into a smaller number of commits.'
diff --git a/db/fixtures/development/02_users.rb b/db/fixtures/development/02_users.rb
new file mode 100644
index 0000000000..6e0b37d725
--- /dev/null
+++ b/db/fixtures/development/02_users.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+class Gitlab::Seeder::Users
+ include ActionView::Helpers::NumberHelper
+
+ RANDOM_USERS_COUNT = 20
+ MASS_USERS_COUNT = ENV['CI'] ? 10 : 1_000_000
+ MASS_INSERT_USERNAME_START = 'mass_insert_user_'
+
+ attr_reader :opts
+
+ def initialize(opts = {})
+ @opts = opts
+ end
+
+ def seed!
+ Sidekiq::Testing.inline! do
+ create_mass_users!
+ create_random_users!
+ end
+ end
+
+ private
+
+ def create_mass_users!
+ encrypted_password = Devise::Encryptor.digest(User, '12345678')
+
+ Gitlab::Seeder.with_mass_insert(MASS_USERS_COUNT, User) do
+ ActiveRecord::Base.connection.execute <<~SQL
+ INSERT INTO users (username, name, email, confirmed_at, projects_limit, encrypted_password)
+ SELECT
+ '#{MASS_INSERT_USERNAME_START}' || seq,
+ 'Seed user ' || seq,
+ 'seed_user' || seq || '@example.com',
+ to_timestamp(seq),
+ #{MASS_USERS_COUNT},
+ '#{encrypted_password}'
+ FROM generate_series(1, #{MASS_USERS_COUNT}) AS seq
+ SQL
+ end
+
+ relation = User.where(admin: false)
+ Gitlab::Seeder.with_mass_insert(relation.count, Namespace) do
+ ActiveRecord::Base.connection.execute <<~SQL
+ INSERT INTO namespaces (name, path, owner_id)
+ SELECT
+ username,
+ username,
+ id
+ FROM users WHERE NOT admin
+ SQL
+ end
+ end
+
+ def create_random_users!
+ RANDOM_USERS_COUNT.times do |i|
+ begin
+ User.create!(
+ username: FFaker::Internet.user_name,
+ name: FFaker::Name.name,
+ email: FFaker::Internet.email,
+ confirmed_at: DateTime.now,
+ password: '12345678'
+ )
+
+ print '.'
+ rescue ActiveRecord::RecordInvalid
+ print 'F'
+ end
+ end
+ end
+end
+
+Gitlab::Seeder.quiet do
+ users = Gitlab::Seeder::Users.new
+ users.seed!
+end
diff --git a/db/fixtures/development/03_project.rb b/db/fixtures/development/03_project.rb
index 46018cf68a..87ef65276e 100644
--- a/db/fixtures/development/03_project.rb
+++ b/db/fixtures/development/03_project.rb
@@ -1,137 +1,210 @@
require './spec/support/sidekiq'
-# rubocop:disable Rails/Output
+class Gitlab::Seeder::Projects
+ include ActionView::Helpers::NumberHelper
-Sidekiq::Testing.inline! do
- Gitlab::Seeder.quiet do
- Gitlab::Seeder.without_gitaly_timeout do
- project_urls = %w[
- https://gitlab.com/gitlab-org/gitlab-test.git
- https://gitlab.com/gitlab-org/gitlab-shell.git
- https://gitlab.com/gnuwget/wget2.git
- https://gitlab.com/Commit451/LabCoat.git
- https://github.com/jashkenas/underscore.git
- https://github.com/flightjs/flight.git
- https://github.com/twitter/typeahead.js.git
- https://github.com/h5bp/html5-boilerplate.git
- https://github.com/google/material-design-lite.git
- https://github.com/jlevy/the-art-of-command-line.git
- https://github.com/FreeCodeCamp/freecodecamp.git
- https://github.com/google/deepdream.git
- https://github.com/jtleek/datasharing.git
- https://github.com/WebAssembly/design.git
- https://github.com/airbnb/javascript.git
- https://github.com/tessalt/echo-chamber-js.git
- https://github.com/atom/atom.git
- https://github.com/mattermost/mattermost-server.git
- https://github.com/purifycss/purifycss.git
- https://github.com/facebook/nuclide.git
- https://github.com/wbkd/awesome-d3.git
- https://github.com/kilimchoi/engineering-blogs.git
- https://github.com/gilbarbara/logos.git
- https://github.com/reduxjs/redux.git
- https://github.com/awslabs/s2n.git
- https://github.com/arkency/reactjs_koans.git
- https://github.com/twbs/bootstrap.git
- https://github.com/chjj/ttystudio.git
- https://github.com/MostlyAdequate/mostly-adequate-guide.git
- https://github.com/octocat/Spoon-Knife.git
- https://github.com/opencontainers/runc.git
- https://github.com/googlesamples/android-topeka.git
- ]
+ PROJECT_URLS = %w[
+ https://gitlab.com/gitlab-org/gitlab-test.git
+ https://gitlab.com/gitlab-org/gitlab-shell.git
+ https://gitlab.com/gnuwget/wget2.git
+ https://gitlab.com/Commit451/LabCoat.git
+ https://github.com/jashkenas/underscore.git
+ https://github.com/flightjs/flight.git
+ https://github.com/twitter/typeahead.js.git
+ https://github.com/h5bp/html5-boilerplate.git
+ https://github.com/google/material-design-lite.git
+ https://github.com/jlevy/the-art-of-command-line.git
+ https://github.com/FreeCodeCamp/freecodecamp.git
+ https://github.com/google/deepdream.git
+ https://github.com/jtleek/datasharing.git
+ https://github.com/WebAssembly/design.git
+ https://github.com/airbnb/javascript.git
+ https://github.com/tessalt/echo-chamber-js.git
+ https://github.com/atom/atom.git
+ https://github.com/mattermost/mattermost-server.git
+ https://github.com/purifycss/purifycss.git
+ https://github.com/facebook/nuclide.git
+ https://github.com/wbkd/awesome-d3.git
+ https://github.com/kilimchoi/engineering-blogs.git
+ https://github.com/gilbarbara/logos.git
+ https://github.com/reduxjs/redux.git
+ https://github.com/awslabs/s2n.git
+ https://github.com/arkency/reactjs_koans.git
+ https://github.com/twbs/bootstrap.git
+ https://github.com/chjj/ttystudio.git
+ https://github.com/MostlyAdequate/mostly-adequate-guide.git
+ https://github.com/octocat/Spoon-Knife.git
+ https://github.com/opencontainers/runc.git
+ https://github.com/googlesamples/android-topeka.git
+ ]
+ LARGE_PROJECT_URLS = %w[
+ https://github.com/torvalds/linux.git
+ https://gitlab.gnome.org/GNOME/gimp.git
+ https://gitlab.gnome.org/GNOME/gnome-mud.git
+ https://gitlab.com/fdroid/fdroidclient.git
+ https://gitlab.com/inkscape/inkscape.git
+ https://github.com/gnachman/iTerm2.git
+ ]
+ # Consider altering MASS_USERS_COUNT for less
+ # users with projects.
+ MASS_PROJECTS_COUNT_PER_USER = {
+ private: 3, # 3m projects +
+ internal: 1, # 1m projects +
+ public: 1 # 1m projects = 5m total
+ }
+ MASS_INSERT_NAME_START = 'mass_insert_project_'
- large_project_urls = %w[
- https://github.com/torvalds/linux.git
- https://gitlab.gnome.org/GNOME/gimp.git
- https://gitlab.gnome.org/GNOME/gnome-mud.git
- https://gitlab.com/fdroid/fdroidclient.git
- https://gitlab.com/inkscape/inkscape.git
- https://github.com/gnachman/iTerm2.git
- ]
+ def seed!
+ Sidekiq::Testing.inline! do
+ create_real_projects!
+ create_large_projects!
+ create_mass_projects!
+ end
+ end
- def create_project(url, force_latest_storage: false)
- group_path, project_path = url.split('/')[-2..-1]
+ private
- group = Group.find_by(path: group_path)
+ def create_real_projects!
+ # You can specify how many projects you need during seed execution
+ size = ENV['SIZE'].present? ? ENV['SIZE'].to_i : 8
- unless group
- group = Group.new(
- name: group_path.titleize,
- path: group_path
- )
- group.description = FFaker::Lorem.sentence
- group.save!
+ PROJECT_URLS.first(size).each_with_index do |url, i|
+ create_real_project!(url, force_latest_storage: i.even?)
+ end
+ end
- group.add_owner(User.first)
- end
+ def create_large_projects!
+ return unless ENV['LARGE_PROJECTS'].present?
- project_path.gsub!(".git", "")
+ LARGE_PROJECT_URLS.each(&method(:create_real_project!))
- params = {
- import_url: url,
- namespace_id: group.id,
- name: project_path.titleize,
- description: FFaker::Lorem.sentence,
- visibility_level: Gitlab::VisibilityLevel.values.sample,
- skip_disk_validation: true
- }
+ if ENV['FORK'].present?
+ puts "\nGenerating forks"
- if force_latest_storage
- params[:storage_version] = Project::LATEST_STORAGE_VERSION
- end
+ project_name = ENV['FORK'] == 'true' ? 'torvalds/linux' : ENV['FORK']
- project = nil
+ project = Project.find_by_full_path(project_name)
- Sidekiq::Worker.skipping_transaction_check do
- project = Projects::CreateService.new(User.first, params).execute
+ User.offset(1).first(5).each do |user|
+ new_project = ::Projects::ForkService.new(project, user).execute
- # Seed-Fu runs this entire fixture in a transaction, so the `after_commit`
- # hook won't run until after the fixture is loaded. That is too late
- # since the Sidekiq::Testing block has already exited. Force clearing
- # the `after_commit` queue to ensure the job is run now.
- project.send(:_run_after_commit_queue)
- project.import_state.send(:_run_after_commit_queue)
- end
-
- if project.valid? && project.valid_repo?
+ if new_project.valid? && (new_project.valid_repo? || new_project.import_state.scheduled?)
print '.'
else
- puts project.errors.full_messages
- print 'F'
- end
- end
-
- # You can specify how many projects you need during seed execution
- size = ENV['SIZE'].present? ? ENV['SIZE'].to_i : 8
-
- project_urls.first(size).each_with_index do |url, i|
- create_project(url, force_latest_storage: i.even?)
- end
-
- if ENV['LARGE_PROJECTS'].present?
- large_project_urls.each(&method(:create_project))
-
- if ENV['FORK'].present?
- puts "\nGenerating forks"
-
- project_name = ENV['FORK'] == 'true' ? 'torvalds/linux' : ENV['FORK']
-
- project = Project.find_by_full_path(project_name)
-
- User.offset(1).first(5).each do |user|
- new_project = Projects::ForkService.new(project, user).execute
-
- if new_project.valid? && (new_project.valid_repo? || new_project.import_state.scheduled?)
- print '.'
- else
- new_project.errors.full_messages.each do |error|
- puts "#{new_project.full_path}: #{error}"
- end
- print 'F'
- end
+ new_project.errors.full_messages.each do |error|
+ puts "#{new_project.full_path}: #{error}"
end
+ print 'F'
end
end
end
end
+
+ def create_real_project!(url, force_latest_storage: false)
+ group_path, project_path = url.split('/')[-2..-1]
+
+ group = Group.find_by(path: group_path)
+
+ unless group
+ group = Group.new(
+ name: group_path.titleize,
+ path: group_path
+ )
+ group.description = FFaker::Lorem.sentence
+ group.save!
+
+ group.add_owner(User.first)
+ end
+
+ project_path.gsub!(".git", "")
+
+ params = {
+ import_url: url,
+ namespace_id: group.id,
+ name: project_path.titleize,
+ description: FFaker::Lorem.sentence,
+ visibility_level: Gitlab::VisibilityLevel.values.sample,
+ skip_disk_validation: true
+ }
+
+ if force_latest_storage
+ params[:storage_version] = Project::LATEST_STORAGE_VERSION
+ end
+
+ project = nil
+
+ Sidekiq::Worker.skipping_transaction_check do
+ project = ::Projects::CreateService.new(User.first, params).execute
+
+ # Seed-Fu runs this entire fixture in a transaction, so the `after_commit`
+ # hook won't run until after the fixture is loaded. That is too late
+ # since the Sidekiq::Testing block has already exited. Force clearing
+ # the `after_commit` queue to ensure the job is run now.
+ project.send(:_run_after_commit_queue)
+ project.import_state.send(:_run_after_commit_queue)
+ end
+
+ if project.valid? && project.valid_repo?
+ print '.'
+ else
+ puts project.errors.full_messages
+ print 'F'
+ end
+ end
+
+ def create_mass_projects!
+ projects_per_user_count = MASS_PROJECTS_COUNT_PER_USER.values.sum
+ visibility_per_user = ['private'] * MASS_PROJECTS_COUNT_PER_USER.fetch(:private) +
+ ['internal'] * MASS_PROJECTS_COUNT_PER_USER.fetch(:internal) +
+ ['public'] * MASS_PROJECTS_COUNT_PER_USER.fetch(:public)
+ visibility_level_per_user = visibility_per_user.map { |visibility| Gitlab::VisibilityLevel.level_value(visibility) }
+
+ visibility_per_user = visibility_per_user.join(',')
+ visibility_level_per_user = visibility_level_per_user.join(',')
+
+ Gitlab::Seeder.with_mass_insert(User.count * projects_per_user_count, "Projects and relations") do
+ ActiveRecord::Base.connection.execute <<~SQL
+ INSERT INTO projects (name, path, creator_id, namespace_id, visibility_level, created_at, updated_at)
+ SELECT
+ 'Seed project ' || seq || ' ' || ('{#{visibility_per_user}}'::text[])[seq] AS project_name,
+ 'mass_insert_project_' || ('{#{visibility_per_user}}'::text[])[seq] || '_' || seq AS project_path,
+ u.id AS user_id,
+ n.id AS namespace_id,
+ ('{#{visibility_level_per_user}}'::int[])[seq] AS visibility_level,
+ NOW() AS created_at,
+ NOW() AS updated_at
+ FROM users u
+ CROSS JOIN generate_series(1, #{projects_per_user_count}) AS seq
+ JOIN namespaces n ON n.owner_id=u.id
+ SQL
+
+ ActiveRecord::Base.connection.execute <<~SQL
+ INSERT INTO project_features (project_id, merge_requests_access_level, issues_access_level, wiki_access_level,
+ pages_access_level)
+ SELECT
+ id,
+ #{ProjectFeature::ENABLED} AS merge_requests_access_level,
+ #{ProjectFeature::ENABLED} AS issues_access_level,
+ #{ProjectFeature::ENABLED} AS wiki_access_level,
+ #{ProjectFeature::ENABLED} AS pages_access_level
+ FROM projects ON CONFLICT (project_id) DO NOTHING;
+ SQL
+
+ ActiveRecord::Base.connection.execute <<~SQL
+ INSERT INTO routes (source_id, source_type, name, path)
+ SELECT
+ p.id,
+ 'Project',
+ u.name || ' / ' || p.name,
+ u.username || '/' || p.path
+ FROM projects p JOIN users u ON u.id=p.creator_id
+ ON CONFLICT (source_type, source_id) DO NOTHING;
+ SQL
+ end
+ end
+end
+
+Gitlab::Seeder.quiet do
+ projects = Gitlab::Seeder::Projects.new
+ projects.seed!
end
diff --git a/db/fixtures/development/04_labels.rb b/db/fixtures/development/04_labels.rb
index b9ae4098d7..21d552c89f 100644
--- a/db/fixtures/development/04_labels.rb
+++ b/db/fixtures/development/04_labels.rb
@@ -43,7 +43,7 @@ Gitlab::Seeder.quiet do
end
puts "\nGenerating project labels"
- Project.all.find_each do |project|
+ Project.not_mass_generated.find_each do |project|
Gitlab::Seeder::ProjectLabels.new(project).seed!
end
end
diff --git a/db/fixtures/development/05_users.rb b/db/fixtures/development/05_users.rb
deleted file mode 100644
index 101ff3a120..0000000000
--- a/db/fixtures/development/05_users.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-require './spec/support/sidekiq'
-
-Gitlab::Seeder.quiet do
- 20.times do |i|
- begin
- User.create!(
- username: FFaker::Internet.user_name,
- name: FFaker::Name.name,
- email: FFaker::Internet.email,
- confirmed_at: DateTime.now,
- password: '12345678'
- )
-
- print '.'
- rescue ActiveRecord::RecordInvalid
- print 'F'
- end
- end
-
- 5.times do |i|
- begin
- User.create!(
- username: "user#{i}",
- name: "User #{i}",
- email: "user#{i}@example.com",
- confirmed_at: DateTime.now,
- password: '12345678'
- )
- print '.'
- rescue ActiveRecord::RecordInvalid
- print 'F'
- end
- end
-end
diff --git a/db/fixtures/development/06_teams.rb b/db/fixtures/development/06_teams.rb
index b218f4e71f..79ea96bf30 100644
--- a/db/fixtures/development/06_teams.rb
+++ b/db/fixtures/development/06_teams.rb
@@ -3,7 +3,7 @@ require './spec/support/sidekiq'
Sidekiq::Testing.inline! do
Gitlab::Seeder.quiet do
Group.all.each do |group|
- User.all.sample(4).each do |user|
+ User.not_mass_generated.sample(4).each do |user|
if group.add_user(user, Gitlab::Access.values.sample).persisted?
print '.'
else
@@ -12,8 +12,8 @@ Sidekiq::Testing.inline! do
end
end
- Project.all.each do |project|
- User.all.sample(4).each do |user|
+ Project.not_mass_generated.each do |project|
+ User.not_mass_generated.sample(4).each do |user|
if project.add_role(user, Gitlab::Access.sym_options.keys.sample)
print '.'
else
diff --git a/db/fixtures/development/07_milestones.rb b/db/fixtures/development/07_milestones.rb
index 271bfbc97e..1194bb3fe6 100644
--- a/db/fixtures/development/07_milestones.rb
+++ b/db/fixtures/development/07_milestones.rb
@@ -1,7 +1,7 @@
require './spec/support/sidekiq'
Gitlab::Seeder.quiet do
- Project.all.each do |project|
+ Project.not_mass_generated.each do |project|
5.times do |i|
milestone_params = {
title: "v#{i}.0",
diff --git a/db/fixtures/development/10_merge_requests.rb b/db/fixtures/development/10_merge_requests.rb
index 4af545614f..29f2fabbd5 100644
--- a/db/fixtures/development/10_merge_requests.rb
+++ b/db/fixtures/development/10_merge_requests.rb
@@ -4,7 +4,13 @@ Gitlab::Seeder.quiet do
# Limit the number of merge requests per project to avoid long seeds
MAX_NUM_MERGE_REQUESTS = 10
- Project.non_archived.with_merge_requests_enabled.reject(&:empty_repo?).each do |project|
+ projects = Project
+ .non_archived
+ .with_merge_requests_enabled
+ .not_mass_generated
+ .reject(&:empty_repo?)
+
+ projects.each do |project|
branches = project.repository.branch_names.sample(MAX_NUM_MERGE_REQUESTS * 2)
branches.each do |branch_name|
diff --git a/db/fixtures/development/11_keys.rb b/db/fixtures/development/11_keys.rb
index c405ecfdaf..13eadc35e0 100644
--- a/db/fixtures/development/11_keys.rb
+++ b/db/fixtures/development/11_keys.rb
@@ -9,7 +9,7 @@ Sidekiq::Testing.disable! do
# that it falls under `Sidekiq::Testing.disable!`.
Key.skip_callback(:commit, :after, :add_to_shell)
- User.first(10).each do |user|
+ User.not_mass_generated.first(10).each do |user|
key = "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt#{user.id + 100}6k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
key = user.keys.create(
diff --git a/db/fixtures/development/12_snippets.rb b/db/fixtures/development/12_snippets.rb
index a9f4069a0f..0ee9058a20 100644
--- a/db/fixtures/development/12_snippets.rb
+++ b/db/fixtures/development/12_snippets.rb
@@ -25,7 +25,7 @@ end
eos
50.times do |i|
- user = User.all.sample
+ user = User.not_mass_generated.sample
PersonalSnippet.seed(:id, [{
id: i,
diff --git a/db/fixtures/development/14_pipelines.rb b/db/fixtures/development/14_pipelines.rb
index 5c8b681fa9..468caac23f 100644
--- a/db/fixtures/development/14_pipelines.rb
+++ b/db/fixtures/development/14_pipelines.rb
@@ -214,7 +214,7 @@ class Gitlab::Seeder::Pipelines
end
Gitlab::Seeder.quiet do
- Project.all.sample(5).each do |project|
+ Project.not_mass_generated.sample(5).each do |project|
project_builds = Gitlab::Seeder::Pipelines.new(project)
project_builds.seed!
end
diff --git a/db/fixtures/development/16_protected_branches.rb b/db/fixtures/development/16_protected_branches.rb
index 39d466fb43..2b492ac1f6 100644
--- a/db/fixtures/development/16_protected_branches.rb
+++ b/db/fixtures/development/16_protected_branches.rb
@@ -3,7 +3,7 @@ require './spec/support/sidekiq'
Gitlab::Seeder.quiet do
admin_user = User.find(1)
- Project.all.each do |project|
+ Project.not_mass_generated.each do |project|
params = {
name: 'master'
}
diff --git a/db/fixtures/development/17_cycle_analytics.rb b/db/fixtures/development/17_cycle_analytics.rb
index b7ddeef95b..606a4cb1dd 100644
--- a/db/fixtures/development/17_cycle_analytics.rb
+++ b/db/fixtures/development/17_cycle_analytics.rb
@@ -217,7 +217,7 @@ Gitlab::Seeder.quiet do
flag = 'SEED_CYCLE_ANALYTICS'
if ENV[flag]
- Project.find_each do |project|
+ Project.not_mass_generated.find_each do |project|
# This seed naively assumes that every project has a repository, and every
# repository has a `master` branch, which may be the case for a pristine
# GDK seed, but is almost never true for a GDK that's actually had
diff --git a/db/fixtures/development/19_environments.rb b/db/fixtures/development/19_environments.rb
index 3e227928a2..0836380421 100644
--- a/db/fixtures/development/19_environments.rb
+++ b/db/fixtures/development/19_environments.rb
@@ -67,7 +67,7 @@ class Gitlab::Seeder::Environments
end
Gitlab::Seeder.quiet do
- Project.all.sample(5).each do |project|
+ Project.not_mass_generated.sample(5).each do |project|
project_environments = Gitlab::Seeder::Environments.new(project)
project_environments.seed!
end
diff --git a/db/fixtures/development/23_spam_logs.rb b/db/fixtures/development/23_spam_logs.rb
index 81cc13e6b2..4a839f5bc2 100644
--- a/db/fixtures/development/23_spam_logs.rb
+++ b/db/fixtures/development/23_spam_logs.rb
@@ -22,7 +22,7 @@ module Db
end
def self.random_user
- User.find(User.pluck(:id).sample)
+ User.find(User.not_mass_generated.pluck(:id).sample)
end
end
end
diff --git a/db/fixtures/development/24_forks.rb b/db/fixtures/development/24_forks.rb
index 971c6f0d0c..fa16b2a1d9 100644
--- a/db/fixtures/development/24_forks.rb
+++ b/db/fixtures/development/24_forks.rb
@@ -2,8 +2,8 @@ require './spec/support/sidekiq'
Sidekiq::Testing.inline! do
Gitlab::Seeder.quiet do
- User.all.sample(10).each do |user|
- source_project = Project.public_only.sample
+ User.not_mass_generated.sample(10).each do |user|
+ source_project = Project.not_mass_generated.public_only.sample
##
# 03_project.rb might not have created a public project because
diff --git a/db/migrate/20180215181245_users_name_lower_index.rb b/db/migrate/20180215181245_users_name_lower_index.rb
index 3b80601a72..fa1a115a78 100644
--- a/db/migrate/20180215181245_users_name_lower_index.rb
+++ b/db/migrate/20180215181245_users_name_lower_index.rb
@@ -20,10 +20,6 @@ class UsersNameLowerIndex < ActiveRecord::Migration[4.2]
def down
return unless Gitlab::Database.postgresql?
- if supports_drop_index_concurrently?
- execute "DROP INDEX CONCURRENTLY IF EXISTS #{INDEX_NAME}"
- else
- execute "DROP INDEX IF EXISTS #{INDEX_NAME}"
- end
+ execute "DROP INDEX CONCURRENTLY IF EXISTS #{INDEX_NAME}"
end
end
diff --git a/db/migrate/20180504195842_project_name_lower_index.rb b/db/migrate/20180504195842_project_name_lower_index.rb
index 3fe90c3fbb..fa74330d5d 100644
--- a/db/migrate/20180504195842_project_name_lower_index.rb
+++ b/db/migrate/20180504195842_project_name_lower_index.rb
@@ -22,11 +22,7 @@ class ProjectNameLowerIndex < ActiveRecord::Migration[4.2]
return unless Gitlab::Database.postgresql?
disable_statement_timeout do
- if supports_drop_index_concurrently?
- execute "DROP INDEX CONCURRENTLY IF EXISTS #{INDEX_NAME}"
- else
- execute "DROP INDEX IF EXISTS #{INDEX_NAME}"
- end
+ execute "DROP INDEX CONCURRENTLY IF EXISTS #{INDEX_NAME}"
end
end
end
diff --git a/db/migrate/20180902070406_create_group_group_links.rb b/db/migrate/20180902070406_create_group_group_links.rb
new file mode 100644
index 0000000000..95fed0ebf9
--- /dev/null
+++ b/db/migrate/20180902070406_create_group_group_links.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+class CreateGroupGroupLinks < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ create_table :group_group_links do |t|
+ t.timestamps_with_timezone null: false
+
+ t.references :shared_group, null: false,
+ index: false,
+ foreign_key: { on_delete: :cascade,
+ to_table: :namespaces }
+ t.references :shared_with_group, null: false,
+ foreign_key: { on_delete: :cascade,
+ to_table: :namespaces }
+ t.date :expires_at
+ t.index [:shared_group_id, :shared_with_group_id],
+ { unique: true,
+ name: 'index_group_group_links_on_shared_group_and_shared_with_group' }
+ t.integer :group_access, { limit: 2,
+ default: 30, # Gitlab::Access::DEVELOPER
+ null: false }
+ end
+ end
+
+ def down
+ drop_table :group_group_links
+ end
+end
diff --git a/db/migrate/20190703171157_add_sourcing_epic_dates.rb b/db/migrate/20190703171157_add_sourcing_epic_dates.rb
new file mode 100644
index 0000000000..202e2098d5
--- /dev/null
+++ b/db/migrate/20190703171157_add_sourcing_epic_dates.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+class AddSourcingEpicDates < ActiveRecord::Migration[5.1]
+ DOWNTIME = false
+
+ def change
+ add_column :epics, :start_date_sourcing_epic_id, :integer
+ add_column :epics, :due_date_sourcing_epic_id, :integer
+ end
+end
diff --git a/db/migrate/20190703171555_add_sourcing_epic_dates_fks.rb b/db/migrate/20190703171555_add_sourcing_epic_dates_fks.rb
new file mode 100644
index 0000000000..4995a3cd03
--- /dev/null
+++ b/db/migrate/20190703171555_add_sourcing_epic_dates_fks.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class AddSourcingEpicDatesFks < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :epics, :start_date_sourcing_epic_id, where: 'start_date_sourcing_epic_id is not null'
+ add_concurrent_index :epics, :due_date_sourcing_epic_id, where: 'due_date_sourcing_epic_id is not null'
+
+ add_concurrent_foreign_key :epics, :epics, column: :start_date_sourcing_epic_id, on_delete: :nullify
+ add_concurrent_foreign_key :epics, :epics, column: :due_date_sourcing_epic_id, on_delete: :nullify
+ end
+
+ def down
+ remove_foreign_key_if_exists :epics, column: :start_date_sourcing_epic_id
+ remove_foreign_key_if_exists :epics, column: :due_date_sourcing_epic_id
+
+ remove_concurrent_index :epics, :start_date_sourcing_epic_id
+ remove_concurrent_index :epics, :due_date_sourcing_epic_id
+ end
+end
diff --git a/db/migrate/20190805140353_remove_rendundant_index_from_releases.rb b/db/migrate/20190805140353_remove_rendundant_index_from_releases.rb
index fc4bc1a423..477f8a850f 100644
--- a/db/migrate/20190805140353_remove_rendundant_index_from_releases.rb
+++ b/db/migrate/20190805140353_remove_rendundant_index_from_releases.rb
@@ -12,10 +12,13 @@ class RemoveRendundantIndexFromReleases < ActiveRecord::Migration[5.2]
disable_ddl_transaction!
def up
- remove_concurrent_index :releases, :project_id
+ remove_concurrent_index_by_name :releases, 'index_releases_on_project_id'
+
+ # This is an extra index that is not present in db/schema.rb but known to exist on some installs
+ remove_concurrent_index_by_name :releases, 'releases_project_id_idx' if index_exists_by_name?(:releases, 'releases_project_id_idx')
end
def down
- add_concurrent_index :releases, :project_id
+ add_concurrent_index :releases, :project_id, name: 'index_releases_on_project_id'
end
end
diff --git a/db/migrate/20190827222124_add_sourcegraph_configuration_to_application_settings.rb b/db/migrate/20190827222124_add_sourcegraph_configuration_to_application_settings.rb
new file mode 100644
index 0000000000..e624642c2f
--- /dev/null
+++ b/db/migrate/20190827222124_add_sourcegraph_configuration_to_application_settings.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddSourcegraphConfigurationToApplicationSettings < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ def up
+ add_column(:application_settings, :sourcegraph_enabled, :boolean, default: false, null: false)
+ add_column(:application_settings, :sourcegraph_url, :string, null: true, limit: 255)
+ end
+
+ def down
+ remove_column(:application_settings, :sourcegraph_enabled)
+ remove_column(:application_settings, :sourcegraph_url)
+ end
+end
diff --git a/db/migrate/20190910211526_create_packages_conan_file_metadata.rb b/db/migrate/20190910211526_create_packages_conan_file_metadata.rb
new file mode 100644
index 0000000000..0f8dacb72d
--- /dev/null
+++ b/db/migrate/20190910211526_create_packages_conan_file_metadata.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class CreatePackagesConanFileMetadata < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ create_table :packages_conan_file_metadata do |t|
+ t.references :package_file, index: { unique: true }, null: false, foreign_key: { to_table: :packages_package_files, on_delete: :cascade }, type: :bigint
+ t.timestamps_with_timezone
+ t.string "recipe_revision", null: false, default: "0", limit: 255
+ t.string "package_revision", limit: 255
+ t.string "conan_package_reference", limit: 255
+ t.integer "conan_file_type", limit: 2, null: false
+ end
+ end
+end
diff --git a/db/migrate/20190918104731_add_cleanup_status_to_cluster.rb b/db/migrate/20190918104731_add_cleanup_status_to_cluster.rb
new file mode 100644
index 0000000000..0ba9d8e6c8
--- /dev/null
+++ b/db/migrate/20190918104731_add_cleanup_status_to_cluster.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class AddCleanupStatusToCluster < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default(:clusters, :cleanup_status,
+ :smallint,
+ default: 1,
+ allow_null: false)
+ end
+
+ def down
+ remove_column(:clusters, :cleanup_status)
+ end
+end
diff --git a/db/migrate/20190918121135_add_cleanup_status_reason_to_cluster.rb b/db/migrate/20190918121135_add_cleanup_status_reason_to_cluster.rb
new file mode 100644
index 0000000000..4e71905e3a
--- /dev/null
+++ b/db/migrate/20190918121135_add_cleanup_status_reason_to_cluster.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+class AddCleanupStatusReasonToCluster < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ def change
+ add_column :clusters, :cleanup_status_reason, :text
+ end
+end
diff --git a/db/migrate/20190930153535_create_zoom_meetings.rb b/db/migrate/20190930153535_create_zoom_meetings.rb
new file mode 100644
index 0000000000..6b92c53da7
--- /dev/null
+++ b/db/migrate/20190930153535_create_zoom_meetings.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+class CreateZoomMeetings < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ ZOOM_MEETING_STATUS_ADDED = 1
+
+ def change
+ create_table :zoom_meetings do |t|
+ t.references :project, foreign_key: { on_delete: :cascade },
+ null: false
+ t.references :issue, foreign_key: { on_delete: :cascade },
+ null: false
+ t.timestamps_with_timezone null: false
+ t.integer :issue_status, limit: 2, default: 1, null: false
+ t.string :url, limit: 255
+
+ t.index [:issue_id, :issue_status], unique: true,
+ where: "issue_status = #{ZOOM_MEETING_STATUS_ADDED}"
+ end
+ end
+end
diff --git a/db/migrate/20191002123516_create_clusters_applications_elastic_stack.rb b/db/migrate/20191002123516_create_clusters_applications_elastic_stack.rb
new file mode 100644
index 0000000000..8910dc0d9f
--- /dev/null
+++ b/db/migrate/20191002123516_create_clusters_applications_elastic_stack.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class CreateClustersApplicationsElasticStack < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ create_table :clusters_applications_elastic_stacks do |t|
+ t.timestamps_with_timezone null: false
+ t.references :cluster, null: false, index: false, foreign_key: { on_delete: :cascade }
+ t.integer :status, null: false
+ t.string :version, null: false, limit: 255
+ t.string :kibana_hostname, limit: 255
+ t.text :status_reason
+ t.index :cluster_id, unique: true
+ end
+ end
+end
diff --git a/db/migrate/20191003015155_add_self_managed_prometheus_alerts.rb b/db/migrate/20191003015155_add_self_managed_prometheus_alerts.rb
index 94d16e921d..71d1015342 100644
--- a/db/migrate/20191003015155_add_self_managed_prometheus_alerts.rb
+++ b/db/migrate/20191003015155_add_self_managed_prometheus_alerts.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
class AddSelfManagedPrometheusAlerts < ActiveRecord::Migration[5.2]
- # Set this constant to true if this migration requires downtime.
DOWNTIME = false
def change
diff --git a/db/migrate/20191003161031_add_mark_for_deletion_to_projects.rb b/db/migrate/20191003161031_add_mark_for_deletion_to_projects.rb
new file mode 100644
index 0000000000..86d581a438
--- /dev/null
+++ b/db/migrate/20191003161031_add_mark_for_deletion_to_projects.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AddMarkForDeletionToProjects < ActiveRecord::Migration[5.2]
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ def change
+ add_column :projects, :marked_for_deletion_at, :date
+ add_column :projects, :marked_for_deletion_by_user_id, :integer
+ end
+end
diff --git a/db/migrate/20191003161032_add_mark_for_deletion_indexes_to_projects.rb b/db/migrate/20191003161032_add_mark_for_deletion_indexes_to_projects.rb
new file mode 100644
index 0000000000..d6ef6509ff
--- /dev/null
+++ b/db/migrate/20191003161032_add_mark_for_deletion_indexes_to_projects.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddMarkForDeletionIndexesToProjects < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_foreign_key :projects, :users, column: :marked_for_deletion_by_user_id, on_delete: :nullify
+ add_concurrent_index :projects, :marked_for_deletion_by_user_id, where: 'marked_for_deletion_by_user_id IS NOT NULL'
+ end
+
+ def down
+ remove_foreign_key_if_exists :projects, column: :marked_for_deletion_by_user_id
+ remove_concurrent_index :projects, :marked_for_deletion_by_user_id
+ end
+end
diff --git a/db/migrate/20191003195218_add_pendo_enabled_to_application_settings.rb b/db/migrate/20191003195218_add_pendo_enabled_to_application_settings.rb
new file mode 100644
index 0000000000..c5f5a8cd70
--- /dev/null
+++ b/db/migrate/20191003195218_add_pendo_enabled_to_application_settings.rb
@@ -0,0 +1,15 @@
+class AddPendoEnabledToApplicationSettings < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default :application_settings, :pendo_enabled, :boolean, default: false, allow_null: false
+ end
+
+ def down
+ remove_column :application_settings, :pendo_enabled
+ end
+end
diff --git a/db/migrate/20191003195620_add_pendo_url_to_application_settings.rb b/db/migrate/20191003195620_add_pendo_url_to_application_settings.rb
new file mode 100644
index 0000000000..cc0895f8be
--- /dev/null
+++ b/db/migrate/20191003195620_add_pendo_url_to_application_settings.rb
@@ -0,0 +1,9 @@
+class AddPendoUrlToApplicationSettings < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :application_settings, :pendo_url, :string, limit: 255
+ end
+end
diff --git a/db/migrate/20191004080818_add_productivity_analytics_start_date.rb b/db/migrate/20191004080818_add_productivity_analytics_start_date.rb
new file mode 100644
index 0000000000..287b0755bc
--- /dev/null
+++ b/db/migrate/20191004080818_add_productivity_analytics_start_date.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AddProductivityAnalyticsStartDate < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :application_settings, :productivity_analytics_start_date, :datetime_with_timezone
+ end
+end
diff --git a/db/migrate/20191004081520_fill_productivity_analytics_start_date.rb b/db/migrate/20191004081520_fill_productivity_analytics_start_date.rb
new file mode 100644
index 0000000000..9432cd6870
--- /dev/null
+++ b/db/migrate/20191004081520_fill_productivity_analytics_start_date.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+# Expected migration duration: 1 minute
+class FillProductivityAnalyticsStartDate < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :merge_request_metrics, :merged_at,
+ where: "merged_at > '2019-09-01' AND commits_count IS NOT NULL",
+ name: 'fill_productivity_analytics_start_date_tmp_index'
+
+ execute(
+ < '2019-09-01' AND commits_count IS NOT NULL), NOW())
+SQL
+ )
+
+ remove_concurrent_index :merge_request_metrics, :merged_at,
+ name: 'fill_productivity_analytics_start_date_tmp_index'
+ end
+
+ def down
+ execute('UPDATE application_settings SET productivity_analytics_start_date = NULL')
+ end
+end
diff --git a/db/migrate/20191009100244_add_geo_design_repository_counters.rb b/db/migrate/20191009100244_add_geo_design_repository_counters.rb
new file mode 100644
index 0000000000..26387453f8
--- /dev/null
+++ b/db/migrate/20191009100244_add_geo_design_repository_counters.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class AddGeoDesignRepositoryCounters < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ change_table :geo_node_statuses do |t|
+ t.column :design_repositories_count, :integer
+ t.column :design_repositories_synced_count, :integer
+ t.column :design_repositories_failed_count, :integer
+ t.column :design_repositories_registry_count, :integer
+ end
+ end
+end
diff --git a/db/migrate/20191009110124_add_has_exposed_artifacts_to_ci_builds_metadata.rb b/db/migrate/20191009110124_add_has_exposed_artifacts_to_ci_builds_metadata.rb
new file mode 100644
index 0000000000..86c3c540e5
--- /dev/null
+++ b/db/migrate/20191009110124_add_has_exposed_artifacts_to_ci_builds_metadata.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class AddHasExposedArtifactsToCiBuildsMetadata < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def up
+ add_column :ci_builds_metadata, :has_exposed_artifacts, :boolean
+ end
+
+ def down
+ remove_column :ci_builds_metadata, :has_exposed_artifacts
+ end
+end
diff --git a/db/migrate/20191009110757_add_index_to_ci_builds_metadata_has_exposed_artifacts.rb b/db/migrate/20191009110757_add_index_to_ci_builds_metadata_has_exposed_artifacts.rb
new file mode 100644
index 0000000000..6b8c452a62
--- /dev/null
+++ b/db/migrate/20191009110757_add_index_to_ci_builds_metadata_has_exposed_artifacts.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddIndexToCiBuildsMetadataHasExposedArtifacts < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :ci_builds_metadata, [:build_id], where: "has_exposed_artifacts IS TRUE", name: 'index_ci_builds_metadata_on_build_id_and_has_exposed_artifacts'
+ end
+
+ def down
+ remove_concurrent_index_by_name :ci_builds_metadata, 'index_ci_builds_metadata_on_build_id_and_has_exposed_artifacts'
+ end
+end
diff --git a/db/migrate/20191010174846_add_snowplow_iglu_registry_url_to_application_settings.rb b/db/migrate/20191010174846_add_snowplow_iglu_registry_url_to_application_settings.rb
new file mode 100644
index 0000000000..a40ce8dbee
--- /dev/null
+++ b/db/migrate/20191010174846_add_snowplow_iglu_registry_url_to_application_settings.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddSnowplowIgluRegistryUrlToApplicationSettings < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ add_column :application_settings, :snowplow_iglu_registry_url, :string, limit: 255
+ end
+end
diff --git a/db/migrate/20191011084019_add_project_deletion_adjourned_period_to_application_settings.rb b/db/migrate/20191011084019_add_project_deletion_adjourned_period_to_application_settings.rb
new file mode 100644
index 0000000000..79546e3325
--- /dev/null
+++ b/db/migrate/20191011084019_add_project_deletion_adjourned_period_to_application_settings.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AddProjectDeletionAdjournedPeriodToApplicationSettings < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ DEFAULT_NUMBER_OF_DAYS_BEFORE_REMOVAL = 7
+
+ def change
+ add_column :application_settings, :deletion_adjourned_period, :integer, default: DEFAULT_NUMBER_OF_DAYS_BEFORE_REMOVAL, null: false
+ end
+end
diff --git a/db/migrate/20191013100213_add_squash_commit_sha_to_merge_requests.rb b/db/migrate/20191013100213_add_squash_commit_sha_to_merge_requests.rb
new file mode 100644
index 0000000000..0a58f0a89a
--- /dev/null
+++ b/db/migrate/20191013100213_add_squash_commit_sha_to_merge_requests.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddSquashCommitShaToMergeRequests < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ add_column :merge_requests, :squash_commit_sha, :binary
+ end
+end
diff --git a/db/migrate/20191014025629_rename_design_management_version_user_to_author.rb b/db/migrate/20191014025629_rename_design_management_version_user_to_author.rb
new file mode 100644
index 0000000000..2359cc2e82
--- /dev/null
+++ b/db/migrate/20191014025629_rename_design_management_version_user_to_author.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class RenameDesignManagementVersionUserToAuthor < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ rename_column_concurrently :design_management_versions, :user_id, :author_id
+ end
+
+ def down
+ undo_rename_column_concurrently :design_management_versions, :user_id, :author_id
+ end
+end
diff --git a/db/migrate/20191014030730_add_author_index_to_design_management_versions.rb b/db/migrate/20191014030730_add_author_index_to_design_management_versions.rb
new file mode 100644
index 0000000000..30e076f1fe
--- /dev/null
+++ b/db/migrate/20191014030730_add_author_index_to_design_management_versions.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddAuthorIndexToDesignManagementVersions < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :design_management_versions, :author_id, where: 'author_id IS NOT NULL'
+ end
+
+ def down
+ remove_concurrent_index :design_management_versions, :author_id
+ end
+end
diff --git a/db/migrate/20191014132931_remove_index_on_snippets_project_id.rb b/db/migrate/20191014132931_remove_index_on_snippets_project_id.rb
index a1d3ffdb8c..850112b4f0 100644
--- a/db/migrate/20191014132931_remove_index_on_snippets_project_id.rb
+++ b/db/migrate/20191014132931_remove_index_on_snippets_project_id.rb
@@ -8,10 +8,13 @@ class RemoveIndexOnSnippetsProjectId < ActiveRecord::Migration[5.2]
disable_ddl_transaction!
def up
- remove_concurrent_index :snippets, [:project_id]
+ remove_concurrent_index_by_name :snippets, 'index_snippets_on_project_id'
+
+ # This is an extra index that is not present in db/schema.rb but known to exist on some installs
+ remove_concurrent_index_by_name :snippets, :snippets_project_id_idx if index_exists_by_name? :snippets, :snippets_project_id_idx
end
def down
- add_concurrent_index :snippets, [:project_id]
+ add_concurrent_index :snippets, [:project_id], name: 'index_snippets_on_project_id'
end
end
diff --git a/db/migrate/20191016133352_create_ci_subscriptions_projects.rb b/db/migrate/20191016133352_create_ci_subscriptions_projects.rb
new file mode 100644
index 0000000000..00ab2c1919
--- /dev/null
+++ b/db/migrate/20191016133352_create_ci_subscriptions_projects.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class CreateCiSubscriptionsProjects < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ def change
+ create_table :ci_subscriptions_projects do |t|
+ t.references :downstream_project, null: false, index: false, foreign_key: { to_table: :projects, on_delete: :cascade }
+ t.references :upstream_project, null: false, foreign_key: { to_table: :projects, on_delete: :cascade }
+ end
+
+ add_index :ci_subscriptions_projects, [:downstream_project_id, :upstream_project_id],
+ unique: true, name: 'index_ci_subscriptions_projects_unique_subscription'
+ end
+end
diff --git a/db/migrate/20191017001326_create_users_security_dashboard_projects.rb b/db/migrate/20191017001326_create_users_security_dashboard_projects.rb
new file mode 100644
index 0000000000..398401dbee
--- /dev/null
+++ b/db/migrate/20191017001326_create_users_security_dashboard_projects.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class CreateUsersSecurityDashboardProjects < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+ INDEX_NAME = 'users_security_dashboard_projects_unique_index'
+
+ def change
+ create_table :users_security_dashboard_projects, id: false do |t|
+ t.references :user, null: false, foreign_key: { on_delete: :cascade }
+ t.references :project, null: false, index: false, foreign_key: { on_delete: :cascade }
+ end
+
+ add_index :users_security_dashboard_projects, [:project_id, :user_id], name: INDEX_NAME, unique: true
+ end
+end
diff --git a/db/migrate/20191017094449_add_remove_source_branch_after_merge_to_projects.rb b/db/migrate/20191017094449_add_remove_source_branch_after_merge_to_projects.rb
new file mode 100644
index 0000000000..021bf7d987
--- /dev/null
+++ b/db/migrate/20191017094449_add_remove_source_branch_after_merge_to_projects.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddRemoveSourceBranchAfterMergeToProjects < ActiveRecord::Migration[5.1]
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ def up
+ add_column :projects, :remove_source_branch_after_merge, :boolean
+ end
+
+ def down
+ remove_column :projects, :remove_source_branch_after_merge
+ end
+end
diff --git a/db/migrate/20191017134513_add_deployment_merge_requests.rb b/db/migrate/20191017134513_add_deployment_merge_requests.rb
new file mode 100644
index 0000000000..dbe09463d2
--- /dev/null
+++ b/db/migrate/20191017134513_add_deployment_merge_requests.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+class AddDeploymentMergeRequests < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ create_table :deployment_merge_requests, id: false do |t|
+ t.references(
+ :deployment,
+ foreign_key: { on_delete: :cascade },
+ type: :integer,
+ index: false,
+ null: false
+ )
+
+ t.references(
+ :merge_request,
+ foreign_key: { on_delete: :cascade },
+ type: :integer,
+ index: true,
+ null: false
+ )
+
+ t.index(
+ [:deployment_id, :merge_request_id],
+ unique: true,
+ name: 'idx_deployment_merge_requests_unique_index'
+ )
+ end
+ end
+end
diff --git a/db/migrate/20191017191341_create_clusters_applications_crossplane.rb b/db/migrate/20191017191341_create_clusters_applications_crossplane.rb
new file mode 100644
index 0000000000..8dc25c5611
--- /dev/null
+++ b/db/migrate/20191017191341_create_clusters_applications_crossplane.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class CreateClustersApplicationsCrossplane < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ create_table :clusters_applications_crossplane do |t|
+ t.timestamps_with_timezone null: false
+ t.references :cluster, null: false, index: false, foreign_key: { on_delete: :cascade }
+ t.integer :status, null: false
+ t.string :version, null: false, limit: 255
+ t.string :stack, null: false, limit: 255
+ t.text :status_reason
+ t.index :cluster_id, unique: true
+ end
+ end
+end
diff --git a/db/migrate/20191023132005_add_merge_requests_index_on_target_project_and_branch.rb b/db/migrate/20191023132005_add_merge_requests_index_on_target_project_and_branch.rb
new file mode 100644
index 0000000000..a3de3f34c4
--- /dev/null
+++ b/db/migrate/20191023132005_add_merge_requests_index_on_target_project_and_branch.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddMergeRequestsIndexOnTargetProjectAndBranch < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :merge_requests, [:target_project_id, :target_branch],
+ where: "state_id = 1 AND merge_when_pipeline_succeeds = true"
+ end
+
+ def down
+ remove_concurrent_index :merge_requests, [:target_project_id, :target_branch]
+ end
+end
diff --git a/db/migrate/20191023152913_add_default_and_free_plans.rb b/db/migrate/20191023152913_add_default_and_free_plans.rb
new file mode 100644
index 0000000000..4f5f800038
--- /dev/null
+++ b/db/migrate/20191023152913_add_default_and_free_plans.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+class AddDefaultAndFreePlans < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ class Plan < ApplicationRecord
+ end
+
+ def up
+ plan_names.each do |plan_name|
+ Plan.create_with(title: plan_name.titleize).find_or_create_by(name: plan_name)
+ end
+ end
+
+ def down
+ Plan.where(name: plan_names).delete_all
+ end
+
+ private
+
+ def plan_names
+ [
+ ('free' if Gitlab.com?),
+ 'default'
+ ].compact
+ end
+end
diff --git a/db/migrate/20191024134020_add_index_to_zoom_meetings.rb b/db/migrate/20191024134020_add_index_to_zoom_meetings.rb
new file mode 100644
index 0000000000..ef3657b6a5
--- /dev/null
+++ b/db/migrate/20191024134020_add_index_to_zoom_meetings.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddIndexToZoomMeetings < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :zoom_meetings, :issue_status
+ end
+
+ def down
+ remove_concurrent_index :zoom_meetings, :issue_status if index_exists?(:zoom_meetings, :issue_status)
+ end
+end
diff --git a/db/migrate/20191026124116_set_application_settings_default_project_and_snippet_visibility.rb b/db/migrate/20191026124116_set_application_settings_default_project_and_snippet_visibility.rb
new file mode 100644
index 0000000000..9d19279510
--- /dev/null
+++ b/db/migrate/20191026124116_set_application_settings_default_project_and_snippet_visibility.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class SetApplicationSettingsDefaultProjectAndSnippetVisibility < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ change_column_null :application_settings, :default_project_visibility, false, 0
+ change_column_default :application_settings, :default_project_visibility, from: nil, to: 0
+
+ change_column_null :application_settings, :default_snippet_visibility, false, 0
+ change_column_default :application_settings, :default_snippet_visibility, from: nil, to: 0
+ end
+end
diff --git a/db/migrate/20191028162543_add_setup_for_company_to_user_preferences.rb b/db/migrate/20191028162543_add_setup_for_company_to_user_preferences.rb
new file mode 100644
index 0000000000..18a8a2306e
--- /dev/null
+++ b/db/migrate/20191028162543_add_setup_for_company_to_user_preferences.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddSetupForCompanyToUserPreferences < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ add_column :user_preferences, :setup_for_company, :boolean
+ end
+end
diff --git a/db/migrate/20191028184740_rename_snowplow_site_id_to_snowplow_app_id.rb b/db/migrate/20191028184740_rename_snowplow_site_id_to_snowplow_app_id.rb
new file mode 100644
index 0000000000..4e3b2da670
--- /dev/null
+++ b/db/migrate/20191028184740_rename_snowplow_site_id_to_snowplow_app_id.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class RenameSnowplowSiteIdToSnowplowAppId < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ rename_column_concurrently :application_settings, :snowplow_site_id, :snowplow_app_id
+ end
+
+ def down
+ undo_rename_column_concurrently :application_settings, :snowplow_site_id, :snowplow_app_id
+ end
+end
diff --git a/db/migrate/20191029125305_create_packages_conan_metadata.rb b/db/migrate/20191029125305_create_packages_conan_metadata.rb
new file mode 100644
index 0000000000..c6abc509e4
--- /dev/null
+++ b/db/migrate/20191029125305_create_packages_conan_metadata.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class CreatePackagesConanMetadata < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ create_table :packages_conan_metadata do |t|
+ t.references :package, index: { unique: true }, null: false, foreign_key: { to_table: :packages_packages, on_delete: :cascade }, type: :bigint
+ t.timestamps_with_timezone
+ t.string "package_username", null: false, limit: 255
+ t.string "package_channel", null: false, limit: 255
+ end
+ end
+end
diff --git a/db/migrate/20191029191901_add_enabled_to_grafana_integrations.rb b/db/migrate/20191029191901_add_enabled_to_grafana_integrations.rb
new file mode 100644
index 0000000000..8db1172487
--- /dev/null
+++ b/db/migrate/20191029191901_add_enabled_to_grafana_integrations.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+class AddEnabledToGrafanaIntegrations < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default(
+ :grafana_integrations,
+ :enabled,
+ :boolean,
+ allow_null: false,
+ default: false
+ )
+ end
+
+ def down
+ remove_column(:grafana_integrations, :enabled)
+ end
+end
diff --git a/db/migrate/20191030135044_create_plan_limits.rb b/db/migrate/20191030135044_create_plan_limits.rb
new file mode 100644
index 0000000000..291d9824f6
--- /dev/null
+++ b/db/migrate/20191030135044_create_plan_limits.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+class CreatePlanLimits < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ create_table :plan_limits, id: false do |t|
+ t.references :plan, foreign_key: { on_delete: :cascade }, null: false, index: { unique: true }
+ t.integer :ci_active_pipelines, null: false, default: 0
+ t.integer :ci_pipeline_size, null: false, default: 0
+ t.integer :ci_active_jobs, null: false, default: 0
+ end
+ end
+end
diff --git a/db/migrate/20191030152934_move_limits_from_plans.rb b/db/migrate/20191030152934_move_limits_from_plans.rb
new file mode 100644
index 0000000000..020a028f64
--- /dev/null
+++ b/db/migrate/20191030152934_move_limits_from_plans.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class MoveLimitsFromPlans < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def up
+ execute <<~SQL
+ INSERT INTO plan_limits (plan_id, ci_active_pipelines, ci_pipeline_size, ci_active_jobs)
+ SELECT id, COALESCE(active_pipelines_limit, 0), COALESCE(pipeline_size_limit, 0), COALESCE(active_jobs_limit, 0)
+ FROM plans
+ SQL
+ end
+
+ def down
+ execute 'DELETE FROM plan_limits'
+ end
+end
diff --git a/db/migrate/20191101092917_replace_index_on_metrics_merged_at.rb b/db/migrate/20191101092917_replace_index_on_metrics_merged_at.rb
new file mode 100644
index 0000000000..b2baaee2b7
--- /dev/null
+++ b/db/migrate/20191101092917_replace_index_on_metrics_merged_at.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class ReplaceIndexOnMetricsMergedAt < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :merge_request_metrics, :merged_at
+ remove_concurrent_index :merge_request_metrics, [:merged_at, :id]
+ end
+
+ def down
+ add_concurrent_index :merge_request_metrics, [:merged_at, :id]
+ remove_concurrent_index :merge_request_metrics, :merged_at
+ end
+end
diff --git a/db/migrate/20191103202505_add_eks_credentials_to_application_settings.rb b/db/migrate/20191103202505_add_eks_credentials_to_application_settings.rb
new file mode 100644
index 0000000000..3a167b4c67
--- /dev/null
+++ b/db/migrate/20191103202505_add_eks_credentials_to_application_settings.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+class AddEksCredentialsToApplicationSettings < ActiveRecord::Migration[5.2]
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ def change
+ add_column :application_settings, :eks_integration_enabled, :boolean, null: false, default: false
+ add_column :application_settings, :eks_account_id, :string, limit: 128
+ add_column :application_settings, :eks_access_key_id, :string, limit: 128
+ add_column :application_settings, :encrypted_eks_secret_access_key_iv, :string, limit: 255
+ add_column :application_settings, :encrypted_eks_secret_access_key, :text
+ end
+end
diff --git a/db/migrate/20191104205020_add_license_details_to_application_settings.rb b/db/migrate/20191104205020_add_license_details_to_application_settings.rb
new file mode 100644
index 0000000000..f951ae6492
--- /dev/null
+++ b/db/migrate/20191104205020_add_license_details_to_application_settings.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AddLicenseDetailsToApplicationSettings < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :application_settings, :license_trial_ends_on, :date, null: true
+ end
+end
diff --git a/db/migrate/20191105094558_add_report_type_to_vulnerabilities.rb b/db/migrate/20191105094558_add_report_type_to_vulnerabilities.rb
new file mode 100644
index 0000000000..8fb657bf9e
--- /dev/null
+++ b/db/migrate/20191105094558_add_report_type_to_vulnerabilities.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddReportTypeToVulnerabilities < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ add_column :vulnerabilities, :report_type, :integer, limit: 2
+ end
+end
diff --git a/db/migrate/20191105193652_add_index_on_deployments_updated_at.rb b/db/migrate/20191105193652_add_index_on_deployments_updated_at.rb
new file mode 100644
index 0000000000..10371c26dc
--- /dev/null
+++ b/db/migrate/20191105193652_add_index_on_deployments_updated_at.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class AddIndexOnDeploymentsUpdatedAt < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ INDEX_COLUMNS = [:project_id, :updated_at]
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index(:deployments, INDEX_COLUMNS)
+ end
+
+ def down
+ remove_concurrent_index(:deployments, INDEX_COLUMNS)
+ end
+end
diff --git a/db/migrate/20191107173446_add_sourcegraph_admin_and_user_preferences.rb b/db/migrate/20191107173446_add_sourcegraph_admin_and_user_preferences.rb
new file mode 100644
index 0000000000..731ed82c99
--- /dev/null
+++ b/db/migrate/20191107173446_add_sourcegraph_admin_and_user_preferences.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddSourcegraphAdminAndUserPreferences < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ add_column(:application_settings, :sourcegraph_public_only, :boolean, default: true, null: false)
+ add_column(:user_preferences, :sourcegraph_enabled, :boolean)
+ end
+
+ def down
+ remove_column(:application_settings, :sourcegraph_public_only)
+ remove_column(:user_preferences, :sourcegraph_enabled)
+ end
+end
diff --git a/db/migrate/20191107220314_add_index_to_projects_on_marked_for_deletion.rb b/db/migrate/20191107220314_add_index_to_projects_on_marked_for_deletion.rb
new file mode 100644
index 0000000000..06849cf9bf
--- /dev/null
+++ b/db/migrate/20191107220314_add_index_to_projects_on_marked_for_deletion.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddIndexToProjectsOnMarkedForDeletion < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :projects, :marked_for_deletion_at, where: 'marked_for_deletion_at IS NOT NULL'
+ end
+
+ def down
+ remove_concurrent_index :projects, :marked_for_deletion_at
+ end
+end
diff --git a/db/migrate/20191111115229_add_group_id_to_import_export_uploads.rb b/db/migrate/20191111115229_add_group_id_to_import_export_uploads.rb
new file mode 100644
index 0000000000..74ef0f27b3
--- /dev/null
+++ b/db/migrate/20191111115229_add_group_id_to_import_export_uploads.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddGroupIdToImportExportUploads < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ add_column :import_export_uploads, :group_id, :bigint
+ end
+end
diff --git a/db/migrate/20191111115431_add_group_fk_to_import_export_uploads.rb b/db/migrate/20191111115431_add_group_fk_to_import_export_uploads.rb
new file mode 100644
index 0000000000..403de3f33e
--- /dev/null
+++ b/db/migrate/20191111115431_add_group_fk_to_import_export_uploads.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddGroupFkToImportExportUploads < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_foreign_key :import_export_uploads, :namespaces, column: :group_id, on_delete: :cascade
+ add_concurrent_index :import_export_uploads, :group_id, unique: true, where: 'group_id IS NOT NULL'
+ end
+
+ def down
+ remove_foreign_key_without_error(:import_export_uploads, column: :group_id)
+ remove_concurrent_index(:import_export_uploads, :group_id)
+ end
+end
diff --git a/db/migrate/20191111121500_default_ci_config_path.rb b/db/migrate/20191111121500_default_ci_config_path.rb
new file mode 100644
index 0000000000..f391f5ffe9
--- /dev/null
+++ b/db/migrate/20191111121500_default_ci_config_path.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class DefaultCiConfigPath < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def up
+ add_column :application_settings, :default_ci_config_path, :string, limit: 255
+ end
+
+ def down
+ remove_column :application_settings, :default_ci_config_path
+ end
+end
diff --git a/db/migrate/20191112115247_add_cached_markdown_version_to_vulnerabilities.rb b/db/migrate/20191112115247_add_cached_markdown_version_to_vulnerabilities.rb
new file mode 100644
index 0000000000..b0c513737e
--- /dev/null
+++ b/db/migrate/20191112115247_add_cached_markdown_version_to_vulnerabilities.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddCachedMarkdownVersionToVulnerabilities < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ add_column :vulnerabilities, :cached_markdown_version, :integer
+ end
+end
diff --git a/db/migrate/20191112214305_add_indexes_for_projects_api_default_params.rb b/db/migrate/20191112214305_add_indexes_for_projects_api_default_params.rb
new file mode 100644
index 0000000000..3893c0422c
--- /dev/null
+++ b/db/migrate/20191112214305_add_indexes_for_projects_api_default_params.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddIndexesForProjectsApiDefaultParams < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :projects, %i(visibility_level created_at id)
+ remove_concurrent_index_by_name :projects, 'index_projects_on_visibility_level'
+ end
+
+ def down
+ add_concurrent_index :projects, :visibility_level
+ remove_concurrent_index :projects, %i(visibility_level created_at id)
+ end
+end
diff --git a/db/migrate/20191112221821_add_indexes_for_projects_api_default_params_authenticated.rb b/db/migrate/20191112221821_add_indexes_for_projects_api_default_params_authenticated.rb
new file mode 100644
index 0000000000..6ebc6a7285
--- /dev/null
+++ b/db/migrate/20191112221821_add_indexes_for_projects_api_default_params_authenticated.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddIndexesForProjectsApiDefaultParamsAuthenticated < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :projects, %i(created_at id)
+ remove_concurrent_index_by_name :projects, 'index_projects_on_created_at'
+ end
+
+ def down
+ add_concurrent_index :projects, :created_at
+ remove_concurrent_index_by_name :projects, 'index_projects_on_created_at_and_id'
+ end
+end
diff --git a/db/migrate/20191112232338_ensure_no_empty_milestone_titles.rb b/db/migrate/20191112232338_ensure_no_empty_milestone_titles.rb
new file mode 100644
index 0000000000..76cb511424
--- /dev/null
+++ b/db/migrate/20191112232338_ensure_no_empty_milestone_titles.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class EnsureNoEmptyMilestoneTitles < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ loop do
+ rows_updated = exec_update <<~SQL
+ UPDATE milestones SET title = '%BLANK' WHERE id IN (SELECT id FROM milestones WHERE title = '' LIMIT 500)
+ SQL
+ break if rows_updated < 500
+ end
+ end
+
+ def down; end
+end
diff --git a/db/migrate/20191114173508_add_resolved_attributes_to_vulnerabilities.rb b/db/migrate/20191114173508_add_resolved_attributes_to_vulnerabilities.rb
new file mode 100644
index 0000000000..ec45a729eb
--- /dev/null
+++ b/db/migrate/20191114173508_add_resolved_attributes_to_vulnerabilities.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class AddResolvedAttributesToVulnerabilities < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def up
+ add_column :vulnerabilities, :resolved_by_id, :bigint
+ add_column :vulnerabilities, :resolved_at, :datetime_with_timezone
+ end
+
+ def down
+ remove_column :vulnerabilities, :resolved_at
+ remove_column :vulnerabilities, :resolved_by_id
+ end
+end
diff --git a/db/migrate/20191114173602_add_foreign_key_on_resolved_by_id_to_vulnerabilities.rb b/db/migrate/20191114173602_add_foreign_key_on_resolved_by_id_to_vulnerabilities.rb
new file mode 100644
index 0000000000..e0a125ca75
--- /dev/null
+++ b/db/migrate/20191114173602_add_foreign_key_on_resolved_by_id_to_vulnerabilities.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddForeignKeyOnResolvedByIdToVulnerabilities < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :vulnerabilities, :resolved_by_id
+ add_concurrent_foreign_key :vulnerabilities, :users, column: :resolved_by_id, on_delete: :nullify
+ end
+
+ def down
+ remove_foreign_key :vulnerabilities, column: :resolved_by_id
+ remove_concurrent_index :vulnerabilities, :resolved_by_id
+ end
+end
diff --git a/db/migrate/20191115091425_create_vulnerability_issue_links.rb b/db/migrate/20191115091425_create_vulnerability_issue_links.rb
new file mode 100644
index 0000000000..8398b6357c
--- /dev/null
+++ b/db/migrate/20191115091425_create_vulnerability_issue_links.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+class CreateVulnerabilityIssueLinks < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ create_table :vulnerability_issue_links do |t|
+ # index: false because idx_vulnerability_issue_links_on_vulnerability_id_and_issue_id refers the same column
+ t.references :vulnerability, null: false, index: false, foreign_key: { on_delete: :cascade }
+ # index: true is implied
+ t.references :issue, null: false, foreign_key: { on_delete: :cascade }
+ t.integer 'link_type', limit: 2, null: false, default: 1 # 'related'
+ t.index %i[vulnerability_id issue_id],
+ name: 'idx_vulnerability_issue_links_on_vulnerability_id_and_issue_id',
+ unique: true # only one link (and of only one type) is allowed
+ t.index %i[vulnerability_id link_type],
+ name: 'idx_vulnerability_issue_links_on_vulnerability_id_and_link_type',
+ where: 'link_type = 2',
+ unique: true # only one 'created' link per vulnerability is allowed
+ t.timestamps_with_timezone
+ end
+ end
+end
diff --git a/db/post_migrate/20190809072552_set_self_monitoring_project_alerting_token.rb b/db/post_migrate/20190809072552_set_self_monitoring_project_alerting_token.rb
index 0c4faebc54..d10887fb5d 100644
--- a/db/post_migrate/20190809072552_set_self_monitoring_project_alerting_token.rb
+++ b/db/post_migrate/20190809072552_set_self_monitoring_project_alerting_token.rb
@@ -3,71 +3,17 @@
class SetSelfMonitoringProjectAlertingToken < ActiveRecord::Migration[5.2]
DOWNTIME = false
- module Migratable
- module Alerting
- class ProjectAlertingSetting < ApplicationRecord
- self.table_name = 'project_alerting_settings'
-
- belongs_to :project
-
- validates :token, presence: true
-
- attr_encrypted :token,
- mode: :per_attribute_iv,
- key: Settings.attr_encrypted_db_key_base_truncated,
- algorithm: 'aes-256-gcm'
-
- before_validation :ensure_token
-
- private
-
- def ensure_token
- self.token ||= generate_token
- end
-
- def generate_token
- SecureRandom.hex
- end
- end
- end
-
- class Project < ApplicationRecord
- has_one :alerting_setting, inverse_of: :project, class_name: 'Alerting::ProjectAlertingSetting'
- end
-
- class ApplicationSetting < ApplicationRecord
- self.table_name = 'application_settings'
-
- belongs_to :instance_administration_project, class_name: 'Project'
-
- def self.current_without_cache
- last
- end
- end
- end
-
- def setup_alertmanager_token(project)
- return unless License.feature_available?(:prometheus_alerts)
-
- project.create_alerting_setting!
- end
-
def up
- Gitlab.ee do
- project = Migratable::ApplicationSetting.current_without_cache&.instance_administration_project
+ # no-op
+ # Converted to no-op in https://gitlab.com/gitlab-org/gitlab/merge_requests/17049.
- if project
- setup_alertmanager_token(project)
- end
- end
+ # This migration has been made a no-op because the pre-requisite migration
+ # which creates the self-monitoring project has already been removed in
+ # https://gitlab.com/gitlab-org/gitlab/merge_requests/16864. As
+ # such, this migration would do nothing.
end
def down
- Gitlab.ee do
- Migratable::ApplicationSetting.current_without_cache
- &.instance_administration_project
- &.alerting_setting
- &.destroy!
- end
+ # no-op
end
end
diff --git a/db/post_migrate/20190918104222_schedule_productivity_analytics_backfill.rb b/db/post_migrate/20190918104222_schedule_productivity_analytics_backfill.rb
index 23d3bbbc39..cd759735f0 100644
--- a/db/post_migrate/20190918104222_schedule_productivity_analytics_backfill.rb
+++ b/db/post_migrate/20190918104222_schedule_productivity_analytics_backfill.rb
@@ -6,7 +6,7 @@ class ScheduleProductivityAnalyticsBackfill < ActiveRecord::Migration[5.2]
DOWNTIME = false
def up
- # no-op since the scheduling times out on GitLab.com
+ # no-op since the migration was removed
end
def down
diff --git a/db/post_migrate/20190926180443_schedule_epic_issues_after_epics_move.rb b/db/post_migrate/20190926180443_schedule_epic_issues_after_epics_move.rb
new file mode 100644
index 0000000000..86fe0f2668
--- /dev/null
+++ b/db/post_migrate/20190926180443_schedule_epic_issues_after_epics_move.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class ScheduleEpicIssuesAfterEpicsMove < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ INTERVAL = 5.minutes.to_i
+ BATCH_SIZE = 100
+ MIGRATION = 'MoveEpicIssuesAfterEpics'
+
+ disable_ddl_transaction!
+
+ class Epic < ActiveRecord::Base
+ self.table_name = 'epics'
+
+ include ::EachBatch
+ end
+
+ def up
+ return unless ::Gitlab.ee?
+
+ Epic.each_batch(of: BATCH_SIZE) do |batch, index|
+ range = batch.pluck('MIN(id)', 'MAX(id)').first
+ delay = index * INTERVAL
+ BackgroundMigrationWorker.perform_in(delay, MIGRATION, *range)
+ end
+ end
+
+ def down
+ # no need
+ end
+end
diff --git a/db/post_migrate/20191008143850_fix_any_approver_rule_for_projects.rb b/db/post_migrate/20191008143850_fix_any_approver_rule_for_projects.rb
new file mode 100644
index 0000000000..c1f4b7e42a
--- /dev/null
+++ b/db/post_migrate/20191008143850_fix_any_approver_rule_for_projects.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class FixAnyApproverRuleForProjects < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+ BATCH_SIZE = 1000
+
+ disable_ddl_transaction!
+
+ class ApprovalProjectRule < ActiveRecord::Base
+ NON_EXISTENT_RULE_TYPE = 4
+ ANY_APPROVER_RULE_TYPE = 3
+
+ include EachBatch
+
+ self.table_name = 'approval_project_rules'
+
+ scope :any_approver, -> { where(rule_type: ANY_APPROVER_RULE_TYPE) }
+ scope :non_existent_rule_type, -> { where(rule_type: NON_EXISTENT_RULE_TYPE) }
+ end
+
+ def up
+ return unless Gitlab.ee?
+
+ # Remove approval project rule with rule type 4 if the project has a rule with rule_type 3
+ #
+ # Currently, there is no projects on gitlab.com which have both rules with 3 and 4 rule type
+ # There's a code-level validation for a rule, which doesn't allow to create rules with the same names
+ #
+ # But in order to avoid failing the update query due to uniqueness constraint
+ # Let's run the delete query to be sure
+ project_ids = FixAnyApproverRuleForProjects::ApprovalProjectRule.any_approver.select(:project_id)
+ FixAnyApproverRuleForProjects::ApprovalProjectRule
+ .non_existent_rule_type
+ .where(project_id: project_ids)
+ .delete_all
+
+ # Set approval project rule types to 3
+ # Currently there are 18_445 records to be updated
+ FixAnyApproverRuleForProjects::ApprovalProjectRule.non_existent_rule_type.each_batch(of: BATCH_SIZE) do |rules|
+ rules.update_all(rule_type: FixAnyApproverRuleForProjects::ApprovalProjectRule::ANY_APPROVER_RULE_TYPE)
+ end
+ end
+
+ def down
+ # The migration doesn't leave the database in an inconsistent state
+ # And can be run multiple times
+ end
+end
diff --git a/db/post_migrate/20191014030134_cleanup_design_management_version_user_to_author_rename.rb b/db/post_migrate/20191014030134_cleanup_design_management_version_user_to_author_rename.rb
new file mode 100644
index 0000000000..e7132cbeeb
--- /dev/null
+++ b/db/post_migrate/20191014030134_cleanup_design_management_version_user_to_author_rename.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class CleanupDesignManagementVersionUserToAuthorRename < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ cleanup_concurrent_column_rename :design_management_versions, :user_id, :author_id
+ end
+
+ def down
+ undo_cleanup_concurrent_column_rename :design_management_versions, :user_id, :author_id
+ end
+end
diff --git a/db/post_migrate/20191017045817_schedule_fix_gitlab_com_pages_access_level.rb b/db/post_migrate/20191017045817_schedule_fix_gitlab_com_pages_access_level.rb
new file mode 100644
index 0000000000..fc44568ea1
--- /dev/null
+++ b/db/post_migrate/20191017045817_schedule_fix_gitlab_com_pages_access_level.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+# Code of this migration was removed after execution on gitlab.com
+# https://gitlab.com/gitlab-org/gitlab/issues/34018
+# Empty migration is left here to avoid any problems with rolling back
+class ScheduleFixGitlabComPagesAccessLevel < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def up
+ end
+
+ def down
+ end
+end
diff --git a/db/post_migrate/20191017180026_drop_ci_build_trace_sections_id.rb b/db/post_migrate/20191017180026_drop_ci_build_trace_sections_id.rb
new file mode 100644
index 0000000000..0405e23b46
--- /dev/null
+++ b/db/post_migrate/20191017180026_drop_ci_build_trace_sections_id.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class DropCiBuildTraceSectionsId < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def up
+ ##
+ # This column has already been ignored since 12.4
+ # See https://gitlab.com/gitlab-org/gitlab/issues/32569
+ remove_column :ci_build_trace_sections, :id
+ end
+
+ def down
+ ##
+ # We don't backfill serial ids as it's not used in application code
+ # and quite expensive process.
+ add_column :ci_build_trace_sections, :id, :bigint
+ end
+end
diff --git a/db/post_migrate/20191021101942_remove_empty_github_service_templates.rb b/db/post_migrate/20191021101942_remove_empty_github_service_templates.rb
new file mode 100644
index 0000000000..64abe93b3e
--- /dev/null
+++ b/db/post_migrate/20191021101942_remove_empty_github_service_templates.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+## It's expected to delete one record on GitLab.com
+#
+class RemoveEmptyGithubServiceTemplates < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ class Service < ActiveRecord::Base
+ self.table_name = 'services'
+ self.inheritance_column = :_type_disabled
+
+ serialize :properties, JSON
+ end
+
+ def up
+ relationship.where(properties: {}).delete_all
+ end
+
+ def down
+ relationship.find_or_create_by!(properties: {})
+ end
+
+ private
+
+ def relationship
+ RemoveEmptyGithubServiceTemplates::Service.where(template: true, type: 'GithubService')
+ end
+end
diff --git a/db/post_migrate/20191022113635_nullify_feature_flag_plaintext_tokens.rb b/db/post_migrate/20191022113635_nullify_feature_flag_plaintext_tokens.rb
new file mode 100644
index 0000000000..9ade145484
--- /dev/null
+++ b/db/post_migrate/20191022113635_nullify_feature_flag_plaintext_tokens.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+class NullifyFeatureFlagPlaintextTokens < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ class FeatureFlagsClient < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'operations_feature_flags_clients'
+
+ scope :with_encrypted_token, -> { where.not(token_encrypted: nil) }
+ scope :with_plaintext_token, -> { where.not(token: nil) }
+ scope :without_plaintext_token, -> { where(token: nil) }
+ end
+
+ disable_ddl_transaction!
+
+ def up
+ return unless Gitlab.ee?
+
+ # 7357 records to be updated on GitLab.com
+ FeatureFlagsClient.with_encrypted_token.with_plaintext_token.each_batch do |relation|
+ relation.update_all(token: nil)
+ end
+ end
+
+ def down
+ return unless Gitlab.ee?
+
+ # There is no way to restore only the tokens that were NULLifyed in the `up`
+ # but we can do is to restore _all_ of them in case it is needed.
+ say_with_time('Decrypting tokens from operations_feature_flags_clients') do
+ FeatureFlagsClient.with_encrypted_token.without_plaintext_token.find_each do |feature_flags_client|
+ token = Gitlab::CryptoHelper.aes256_gcm_decrypt(feature_flags_client.token_encrypted)
+ feature_flags_client.update_column(:token, token)
+ end
+ end
+ end
+end
diff --git a/db/post_migrate/20191029095537_cleanup_application_settings_snowplow_site_id_rename.rb b/db/post_migrate/20191029095537_cleanup_application_settings_snowplow_site_id_rename.rb
new file mode 100644
index 0000000000..83b4a2af2b
--- /dev/null
+++ b/db/post_migrate/20191029095537_cleanup_application_settings_snowplow_site_id_rename.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class CleanupApplicationSettingsSnowplowSiteIdRename < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ cleanup_concurrent_column_rename :application_settings, :snowplow_site_id, :snowplow_app_id
+ end
+
+ def down
+ undo_cleanup_concurrent_column_rename :application_settings, :snowplow_site_id, :snowplow_app_id
+ end
+end
diff --git a/db/post_migrate/20191030193050_remove_pendo_from_application_settings.rb b/db/post_migrate/20191030193050_remove_pendo_from_application_settings.rb
new file mode 100644
index 0000000000..33bbe6f8ea
--- /dev/null
+++ b/db/post_migrate/20191030193050_remove_pendo_from_application_settings.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class RemovePendoFromApplicationSettings < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ disable_ddl_transaction!
+
+ DOWNTIME = false
+
+ def up
+ remove_column :application_settings, :pendo_enabled
+ remove_column :application_settings, :pendo_url
+ end
+
+ def down
+ add_column_with_default :application_settings, :pendo_enabled, :boolean, default: false, allow_null: false
+ add_column :application_settings, :pendo_url, :string, limit: 255
+ end
+end
diff --git a/db/post_migrate/20191031112603_remove_limits_from_plans.rb b/db/post_migrate/20191031112603_remove_limits_from_plans.rb
new file mode 100644
index 0000000000..30fb6a9d19
--- /dev/null
+++ b/db/post_migrate/20191031112603_remove_limits_from_plans.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class RemoveLimitsFromPlans < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def up
+ remove_column :plans, :active_pipelines_limit
+ remove_column :plans, :pipeline_size_limit
+ remove_column :plans, :active_jobs_limit
+ end
+
+ def down
+ add_column :plans, :active_pipelines_limit, :integer
+ add_column :plans, :pipeline_size_limit, :integer
+ add_column :plans, :active_jobs_limit, :integer
+ end
+end
diff --git a/db/post_migrate/20191105094625_set_report_type_for_vulnerabilities.rb b/db/post_migrate/20191105094625_set_report_type_for_vulnerabilities.rb
new file mode 100644
index 0000000000..6b7a158584
--- /dev/null
+++ b/db/post_migrate/20191105094625_set_report_type_for_vulnerabilities.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+class SetReportTypeForVulnerabilities < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def up
+ # set report_type based on associated vulnerability_occurrences
+ execute <<~SQL
+ UPDATE vulnerabilities
+ SET report_type = vulnerability_occurrences.report_type
+ FROM vulnerability_occurrences
+ WHERE vulnerabilities.id = vulnerability_occurrences.vulnerability_id
+ SQL
+
+ # set default report_type for orphan vulnerabilities (there should be none but...)
+ execute 'UPDATE vulnerabilities SET report_type = 0 WHERE report_type IS NULL'
+
+ change_column_null :vulnerabilities, :report_type, false
+ end
+
+ def down
+ change_column_null :vulnerabilities, :report_type, true
+
+ execute 'UPDATE vulnerabilities SET report_type = NULL'
+ end
+end
diff --git a/db/post_migrate/20191105140942_add_indices_to_abuse_reports.rb b/db/post_migrate/20191105140942_add_indices_to_abuse_reports.rb
new file mode 100644
index 0000000000..2b2d04e8cc
--- /dev/null
+++ b/db/post_migrate/20191105140942_add_indices_to_abuse_reports.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddIndicesToAbuseReports < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :abuse_reports, :user_id
+ end
+
+ def down
+ remove_concurrent_index :abuse_reports, :user_id
+ end
+end
diff --git a/db/post_migrate/20191112115317_change_vulnerabilities_title_html_to_nullable.rb b/db/post_migrate/20191112115317_change_vulnerabilities_title_html_to_nullable.rb
new file mode 100644
index 0000000000..6e0f324741
--- /dev/null
+++ b/db/post_migrate/20191112115317_change_vulnerabilities_title_html_to_nullable.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class ChangeVulnerabilitiesTitleHtmlToNullable < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ change_column_null :vulnerabilities, :title_html, true
+ end
+end
diff --git a/db/post_migrate/20191114173624_set_resolved_state_on_vulnerabilities.rb b/db/post_migrate/20191114173624_set_resolved_state_on_vulnerabilities.rb
new file mode 100644
index 0000000000..b28aecdc0a
--- /dev/null
+++ b/db/post_migrate/20191114173624_set_resolved_state_on_vulnerabilities.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+class SetResolvedStateOnVulnerabilities < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def up
+ execute <<~SQL
+ -- selecting IDs for all non-orphan Findings that either have no feedback or it's a non-dismissal feedback
+ WITH resolved_vulnerability_ids AS (
+ SELECT DISTINCT vulnerability_id AS id
+ FROM vulnerability_occurrences
+ LEFT JOIN vulnerability_feedback ON vulnerability_feedback.project_fingerprint = ENCODE(vulnerability_occurrences.project_fingerprint::bytea, 'HEX')
+ WHERE vulnerability_id IS NOT NULL
+ AND (vulnerability_feedback.id IS NULL OR vulnerability_feedback.feedback_type <> 0)
+ )
+ UPDATE vulnerabilities
+ SET state = 3, resolved_by_id = closed_by_id, resolved_at = NOW()
+ FROM resolved_vulnerability_ids
+ WHERE vulnerabilities.id IN (resolved_vulnerability_ids.id)
+ AND state = 2 -- only 'closed' Vulnerabilities become 'resolved'
+ SQL
+ end
+
+ def down
+ execute <<~SQL
+ UPDATE vulnerabilities
+ SET state = 2, resolved_by_id = NULL, resolved_at = NULL -- state = 'closed'
+ WHERE state = 3 -- 'resolved'
+ SQL
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 1e722b519e..0dce19a29d 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -24,6 +24,7 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
t.datetime "updated_at"
t.text "message_html"
t.integer "cached_markdown_version"
+ t.index ["user_id"], name: "index_abuse_reports_on_user_id"
end
create_table "alerts_service_data", force: :cascade do |t|
@@ -158,8 +159,8 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
t.text "restricted_visibility_levels"
t.boolean "version_check_enabled", default: true
t.integer "max_attachment_size", default: 10, null: false
- t.integer "default_project_visibility"
- t.integer "default_snippet_visibility"
+ t.integer "default_project_visibility", default: 0, null: false
+ t.integer "default_snippet_visibility", default: 0, null: false
t.text "domain_whitelist"
t.boolean "user_oauth_applications", default: true
t.string "after_sign_out_path"
@@ -281,7 +282,6 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
t.boolean "hide_third_party_offers", default: false, null: false
t.boolean "snowplow_enabled", default: false, null: false
t.string "snowplow_collector_hostname"
- t.string "snowplow_site_id"
t.string "snowplow_cookie_domain"
t.boolean "instance_statistics_visibility_private", default: false, null: false
t.boolean "web_ide_clientside_preview_enabled", default: false, null: false
@@ -332,9 +332,23 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
t.boolean "throttle_incident_management_notification_enabled", default: false, null: false
t.integer "throttle_incident_management_notification_period_in_seconds", default: 3600
t.integer "throttle_incident_management_notification_per_period", default: 3600
+ t.string "snowplow_iglu_registry_url", limit: 255
t.integer "push_event_hooks_limit", default: 3, null: false
t.integer "push_event_activities_limit", default: 3, null: false
t.string "custom_http_clone_url_root", limit: 511
+ t.integer "deletion_adjourned_period", default: 7, null: false
+ t.date "license_trial_ends_on"
+ t.boolean "eks_integration_enabled", default: false, null: false
+ t.string "eks_account_id", limit: 128
+ t.string "eks_access_key_id", limit: 128
+ t.string "encrypted_eks_secret_access_key_iv", limit: 255
+ t.text "encrypted_eks_secret_access_key"
+ t.string "snowplow_app_id"
+ t.datetime_with_timezone "productivity_analytics_start_date"
+ t.string "default_ci_config_path", limit: 255
+ t.boolean "sourcegraph_enabled", default: false, null: false
+ t.string "sourcegraph_url", limit: 255
+ t.boolean "sourcegraph_public_only", default: true, null: false
t.text "encrypted_akismet_api_key"
t.string "encrypted_akismet_api_key_iv", limit: 255
t.text "encrypted_elasticsearch_aws_secret_access_key"
@@ -603,7 +617,7 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
t.index ["project_id", "name"], name: "index_ci_build_trace_section_names_on_project_id_and_name", unique: true
end
- create_table "ci_build_trace_sections", id: :serial, force: :cascade do |t|
+ create_table "ci_build_trace_sections", id: false, force: :cascade do |t|
t.integer "project_id", null: false
t.datetime "date_start", null: false
t.datetime "date_end", null: false
@@ -697,7 +711,9 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
t.boolean "interruptible"
t.jsonb "config_options"
t.jsonb "config_variables"
+ t.boolean "has_exposed_artifacts"
t.index ["build_id"], name: "index_ci_builds_metadata_on_build_id", unique: true
+ t.index ["build_id"], name: "index_ci_builds_metadata_on_build_id_and_has_exposed_artifacts", where: "(has_exposed_artifacts IS TRUE)"
t.index ["build_id"], name: "index_ci_builds_metadata_on_build_id_and_interruptible", where: "(interruptible = true)"
t.index ["project_id"], name: "index_ci_builds_metadata_on_project_id"
end
@@ -917,6 +933,13 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
t.index ["project_id"], name: "index_ci_stages_on_project_id"
end
+ create_table "ci_subscriptions_projects", force: :cascade do |t|
+ t.bigint "downstream_project_id", null: false
+ t.bigint "upstream_project_id", null: false
+ t.index ["downstream_project_id", "upstream_project_id"], name: "index_ci_subscriptions_projects_unique_subscription", unique: true
+ t.index ["upstream_project_id"], name: "index_ci_subscriptions_projects_on_upstream_project_id"
+ end
+
create_table "ci_trigger_requests", id: :serial, force: :cascade do |t|
t.integer "trigger_id", null: false
t.text "variables"
@@ -1043,6 +1066,8 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
t.boolean "managed", default: true, null: false
t.boolean "namespace_per_environment", default: true, null: false
t.integer "management_project_id"
+ t.integer "cleanup_status", limit: 2, default: 1, null: false
+ t.text "cleanup_status_reason"
t.index ["enabled"], name: "index_clusters_on_enabled"
t.index ["management_project_id"], name: "index_clusters_on_management_project_id", where: "(management_project_id IS NOT NULL)"
t.index ["user_id"], name: "index_clusters_on_user_id"
@@ -1059,6 +1084,28 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
t.index ["cluster_id"], name: "index_clusters_applications_cert_managers_on_cluster_id", unique: true
end
+ create_table "clusters_applications_crossplane", id: :serial, force: :cascade do |t|
+ t.datetime_with_timezone "created_at", null: false
+ t.datetime_with_timezone "updated_at", null: false
+ t.bigint "cluster_id", null: false
+ t.integer "status", null: false
+ t.string "version", limit: 255, null: false
+ t.string "stack", limit: 255, null: false
+ t.text "status_reason"
+ t.index ["cluster_id"], name: "index_clusters_applications_crossplane_on_cluster_id", unique: true
+ end
+
+ create_table "clusters_applications_elastic_stacks", force: :cascade do |t|
+ t.datetime_with_timezone "created_at", null: false
+ t.datetime_with_timezone "updated_at", null: false
+ t.bigint "cluster_id", null: false
+ t.integer "status", null: false
+ t.string "version", limit: 255, null: false
+ t.string "kibana_hostname", limit: 255
+ t.text "status_reason"
+ t.index ["cluster_id"], name: "index_clusters_applications_elastic_stacks_on_cluster_id", unique: true
+ end
+
create_table "clusters_applications_helm", id: :serial, force: :cascade do |t|
t.integer "cluster_id", null: false
t.datetime "created_at", null: false
@@ -1244,6 +1291,13 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
t.index ["token_encrypted"], name: "index_deploy_tokens_on_token_encrypted", unique: true
end
+ create_table "deployment_merge_requests", id: false, force: :cascade do |t|
+ t.integer "deployment_id", null: false
+ t.integer "merge_request_id", null: false
+ t.index ["deployment_id", "merge_request_id"], name: "idx_deployment_merge_requests_unique_index", unique: true
+ t.index ["merge_request_id"], name: "index_deployment_merge_requests_on_merge_request_id"
+ end
+
create_table "deployments", id: :serial, force: :cascade do |t|
t.integer "iid", null: false
t.integer "project_id", null: false
@@ -1270,6 +1324,7 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
t.index ["project_id", "iid"], name: "index_deployments_on_project_id_and_iid", unique: true
t.index ["project_id", "status", "created_at"], name: "index_deployments_on_project_id_and_status_and_created_at"
t.index ["project_id", "status"], name: "index_deployments_on_project_id_and_status"
+ t.index ["project_id", "updated_at"], name: "index_deployments_on_project_id_and_updated_at"
end
create_table "description_versions", force: :cascade do |t|
@@ -1305,11 +1360,11 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
create_table "design_management_versions", force: :cascade do |t|
t.binary "sha", null: false
t.bigint "issue_id"
- t.integer "user_id"
t.datetime_with_timezone "created_at"
+ t.integer "author_id"
+ t.index ["author_id"], name: "index_design_management_versions_on_author_id", where: "(author_id IS NOT NULL)"
t.index ["issue_id"], name: "index_design_management_versions_on_issue_id"
t.index ["sha", "issue_id"], name: "index_design_management_versions_on_sha_and_issue_id", unique: true
- t.index ["user_id"], name: "index_design_management_versions_on_user_id", where: "(user_id IS NOT NULL)"
end
create_table "draft_notes", force: :cascade do |t|
@@ -1414,15 +1469,19 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
t.integer "parent_id"
t.integer "relative_position"
t.integer "state_id", limit: 2, default: 1, null: false
+ t.integer "start_date_sourcing_epic_id"
+ t.integer "due_date_sourcing_epic_id"
t.index ["assignee_id"], name: "index_epics_on_assignee_id"
t.index ["author_id"], name: "index_epics_on_author_id"
t.index ["closed_by_id"], name: "index_epics_on_closed_by_id"
+ t.index ["due_date_sourcing_epic_id"], name: "index_epics_on_due_date_sourcing_epic_id", where: "(due_date_sourcing_epic_id IS NOT NULL)"
t.index ["end_date"], name: "index_epics_on_end_date"
t.index ["group_id"], name: "index_epics_on_group_id"
t.index ["iid"], name: "index_epics_on_iid"
t.index ["milestone_id"], name: "index_milestone"
t.index ["parent_id"], name: "index_epics_on_parent_id"
t.index ["start_date"], name: "index_epics_on_start_date"
+ t.index ["start_date_sourcing_epic_id"], name: "index_epics_on_start_date_sourcing_epic_id", where: "(start_date_sourcing_epic_id IS NOT NULL)"
end
create_table "events", id: :serial, force: :cascade do |t|
@@ -1637,6 +1696,10 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
t.integer "container_repositories_synced_count"
t.integer "container_repositories_failed_count"
t.integer "container_repositories_registry_count"
+ t.integer "design_repositories_count"
+ t.integer "design_repositories_synced_count"
+ t.integer "design_repositories_failed_count"
+ t.integer "design_repositories_registry_count"
t.index ["geo_node_id"], name: "index_geo_node_statuses_on_geo_node_id", unique: true
end
@@ -1788,6 +1851,7 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
t.string "encrypted_token", limit: 255, null: false
t.string "encrypted_token_iv", limit: 255, null: false
t.string "grafana_url", limit: 1024, null: false
+ t.boolean "enabled", default: false, null: false
t.index ["project_id"], name: "index_grafana_integrations_on_project_id"
end
@@ -1801,6 +1865,17 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
t.index ["key", "value"], name: "index_group_custom_attributes_on_key_and_value"
end
+ create_table "group_group_links", force: :cascade do |t|
+ t.datetime_with_timezone "created_at", null: false
+ t.datetime_with_timezone "updated_at", null: false
+ t.bigint "shared_group_id", null: false
+ t.bigint "shared_with_group_id", null: false
+ t.date "expires_at"
+ t.integer "group_access", limit: 2, default: 30, null: false
+ t.index ["shared_group_id", "shared_with_group_id"], name: "index_group_group_links_on_shared_group_and_shared_with_group", unique: true
+ t.index ["shared_with_group_id"], name: "index_group_group_links_on_shared_with_group_id"
+ end
+
create_table "historical_data", id: :serial, force: :cascade do |t|
t.date "date", null: false
t.integer "active_user_count"
@@ -1826,6 +1901,8 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
t.integer "project_id"
t.text "import_file"
t.text "export_file"
+ t.bigint "group_id"
+ t.index ["group_id"], name: "index_import_export_uploads_on_group_id", unique: true, where: "(group_id IS NOT NULL)"
t.index ["project_id"], name: "index_import_export_uploads_on_project_id"
t.index ["updated_at"], name: "index_import_export_uploads_on_updated_at"
end
@@ -2257,7 +2334,7 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
t.index ["latest_closed_by_id"], name: "index_merge_request_metrics_on_latest_closed_by_id"
t.index ["merge_request_id", "merged_at"], name: "index_merge_request_metrics_on_merge_request_id_and_merged_at", where: "(merged_at IS NOT NULL)"
t.index ["merge_request_id"], name: "index_merge_request_metrics"
- t.index ["merged_at", "id"], name: "index_merge_request_metrics_on_merged_at_and_id"
+ t.index ["merged_at"], name: "index_merge_request_metrics_on_merged_at"
t.index ["merged_by_id"], name: "index_merge_request_metrics_on_merged_by_id"
t.index ["pipeline_id"], name: "index_merge_request_metrics_on_pipeline_id"
end
@@ -2301,6 +2378,7 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
t.boolean "allow_maintainer_to_push"
t.integer "state_id", limit: 2, default: 1, null: false
t.string "rebase_jid"
+ t.binary "squash_commit_sha"
t.index ["assignee_id"], name: "index_merge_requests_on_assignee_id"
t.index ["author_id"], name: "index_merge_requests_on_author_id"
t.index ["created_at"], name: "index_merge_requests_on_created_at"
@@ -2323,6 +2401,7 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
t.index ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid", unique: true
t.index ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid_opened", where: "((state)::text = 'opened'::text)"
t.index ["target_project_id", "merge_commit_sha", "id"], name: "index_merge_requests_on_tp_id_and_merge_commit_sha_and_id"
+ t.index ["target_project_id", "target_branch"], name: "index_merge_requests_on_target_project_id_and_target_branch", where: "((state_id = 1) AND (merge_when_pipeline_succeeds = true))"
t.index ["title"], name: "index_merge_requests_on_title"
t.index ["title"], name: "index_merge_requests_on_title_trigram", opclass: :gin_trgm_ops, using: :gin
t.index ["updated_by_id"], name: "index_merge_requests_on_updated_by_id", where: "(updated_by_id IS NOT NULL)"
@@ -2619,6 +2698,26 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
t.index ["project_id", "token_encrypted"], name: "index_feature_flags_clients_on_project_id_and_token_encrypted", unique: true
end
+ create_table "packages_conan_file_metadata", force: :cascade do |t|
+ t.bigint "package_file_id", null: false
+ t.datetime_with_timezone "created_at", null: false
+ t.datetime_with_timezone "updated_at", null: false
+ t.string "recipe_revision", limit: 255, default: "0", null: false
+ t.string "package_revision", limit: 255
+ t.string "conan_package_reference", limit: 255
+ t.integer "conan_file_type", limit: 2, null: false
+ t.index ["package_file_id"], name: "index_packages_conan_file_metadata_on_package_file_id", unique: true
+ end
+
+ create_table "packages_conan_metadata", force: :cascade do |t|
+ t.bigint "package_id", null: false
+ t.datetime_with_timezone "created_at", null: false
+ t.datetime_with_timezone "updated_at", null: false
+ t.string "package_username", limit: 255, null: false
+ t.string "package_channel", limit: 255, null: false
+ t.index ["package_id"], name: "index_packages_conan_metadata_on_package_id", unique: true
+ end
+
create_table "packages_maven_metadata", force: :cascade do |t|
t.bigint "package_id", null: false
t.datetime_with_timezone "created_at", null: false
@@ -2730,14 +2829,19 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
t.index ["user_id"], name: "index_personal_access_tokens_on_user_id"
end
+ create_table "plan_limits", force: :cascade do |t|
+ t.bigint "plan_id", null: false
+ t.integer "ci_active_pipelines", default: 0, null: false
+ t.integer "ci_pipeline_size", default: 0, null: false
+ t.integer "ci_active_jobs", default: 0, null: false
+ t.index ["plan_id"], name: "index_plan_limits_on_plan_id", unique: true
+ end
+
create_table "plans", id: :serial, force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "name"
t.string "title"
- t.integer "active_pipelines_limit"
- t.integer "pipeline_size_limit"
- t.integer "active_jobs_limit", default: 0
t.index ["name"], name: "index_plans_on_name"
end
@@ -3041,9 +3145,12 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
t.integer "max_pages_size"
t.integer "max_artifacts_size"
t.string "pull_mirror_branch_prefix", limit: 50
+ t.boolean "remove_source_branch_after_merge"
+ t.date "marked_for_deletion_at"
+ t.integer "marked_for_deletion_by_user_id"
t.index "lower((name)::text)", name: "index_projects_on_lower_name"
t.index ["archived", "pending_delete", "merge_requests_require_code_owner_approval"], name: "projects_requiring_code_owner_approval", where: "((pending_delete = false) AND (archived = false) AND (merge_requests_require_code_owner_approval = true))"
- t.index ["created_at"], name: "index_projects_on_created_at"
+ t.index ["created_at", "id"], name: "index_projects_on_created_at_and_id"
t.index ["creator_id"], name: "index_projects_on_creator_id"
t.index ["description"], name: "index_projects_on_description_trigram", opclass: :gin_trgm_ops, using: :gin
t.index ["id", "repository_storage", "last_repository_updated_at"], name: "idx_projects_on_repository_storage_last_repository_updated_at"
@@ -3053,6 +3160,8 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
t.index ["last_repository_check_at"], name: "index_projects_on_last_repository_check_at", where: "(last_repository_check_at IS NOT NULL)"
t.index ["last_repository_check_failed"], name: "index_projects_on_last_repository_check_failed"
t.index ["last_repository_updated_at"], name: "index_projects_on_last_repository_updated_at"
+ t.index ["marked_for_deletion_at"], name: "index_projects_on_marked_for_deletion_at", where: "(marked_for_deletion_at IS NOT NULL)"
+ t.index ["marked_for_deletion_by_user_id"], name: "index_projects_on_marked_for_deletion_by_user_id", where: "(marked_for_deletion_by_user_id IS NOT NULL)"
t.index ["mirror_last_successful_update_at"], name: "index_projects_on_mirror_last_successful_update_at"
t.index ["mirror_user_id"], name: "index_projects_on_mirror_user_id"
t.index ["name"], name: "index_projects_on_name_trigram", opclass: :gin_trgm_ops, using: :gin
@@ -3066,7 +3175,7 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
t.index ["runners_token"], name: "index_projects_on_runners_token"
t.index ["runners_token_encrypted"], name: "index_projects_on_runners_token_encrypted"
t.index ["star_count"], name: "index_projects_on_star_count"
- t.index ["visibility_level"], name: "index_projects_on_visibility_level"
+ t.index ["visibility_level", "created_at", "id"], name: "index_projects_on_visibility_level_and_created_at_and_id"
end
create_table "prometheus_alert_events", force: :cascade do |t|
@@ -3683,6 +3792,8 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
t.boolean "time_format_in_24h"
t.string "projects_sort", limit: 64
t.boolean "show_whitespace_in_diffs", default: true, null: false
+ t.boolean "sourcegraph_enabled"
+ t.boolean "setup_for_company"
t.index ["user_id"], name: "index_user_preferences_on_user_id", unique: true
end
@@ -3822,6 +3933,13 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
t.index ["user_id", "project_id"], name: "index_users_ops_dashboard_projects_on_user_id_and_project_id", unique: true
end
+ create_table "users_security_dashboard_projects", id: false, force: :cascade do |t|
+ t.bigint "user_id", null: false
+ t.bigint "project_id", null: false
+ t.index ["project_id", "user_id"], name: "users_security_dashboard_projects_unique_index", unique: true
+ t.index ["user_id"], name: "index_users_security_dashboard_projects_on_user_id"
+ end
+
create_table "users_star_projects", id: :serial, force: :cascade do |t|
t.integer "project_id", null: false
t.integer "user_id", null: false
@@ -3844,7 +3962,7 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
t.string "title", limit: 255, null: false
- t.text "title_html", null: false
+ t.text "title_html"
t.text "description"
t.text "description_html"
t.bigint "start_date_sourcing_milestone_id"
@@ -3856,6 +3974,10 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
t.boolean "severity_overridden", default: false
t.integer "confidence", limit: 2, null: false
t.boolean "confidence_overridden", default: false
+ t.bigint "resolved_by_id"
+ t.datetime_with_timezone "resolved_at"
+ t.integer "report_type", limit: 2, null: false
+ t.integer "cached_markdown_version"
t.index ["author_id"], name: "index_vulnerabilities_on_author_id"
t.index ["closed_by_id"], name: "index_vulnerabilities_on_closed_by_id"
t.index ["due_date_sourcing_milestone_id"], name: "index_vulnerabilities_on_due_date_sourcing_milestone_id"
@@ -3863,6 +3985,7 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
t.index ["last_edited_by_id"], name: "index_vulnerabilities_on_last_edited_by_id"
t.index ["milestone_id"], name: "index_vulnerabilities_on_milestone_id"
t.index ["project_id"], name: "index_vulnerabilities_on_project_id"
+ t.index ["resolved_by_id"], name: "index_vulnerabilities_on_resolved_by_id"
t.index ["start_date_sourcing_milestone_id"], name: "index_vulnerabilities_on_start_date_sourcing_milestone_id"
t.index ["updated_by_id"], name: "index_vulnerabilities_on_updated_by_id"
end
@@ -3901,6 +4024,17 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
t.index ["project_id", "fingerprint"], name: "index_vulnerability_identifiers_on_project_id_and_fingerprint", unique: true
end
+ create_table "vulnerability_issue_links", force: :cascade do |t|
+ t.bigint "vulnerability_id", null: false
+ t.bigint "issue_id", null: false
+ t.integer "link_type", limit: 2, default: 1, null: false
+ t.datetime_with_timezone "created_at", null: false
+ t.datetime_with_timezone "updated_at", null: false
+ t.index ["issue_id"], name: "index_vulnerability_issue_links_on_issue_id"
+ t.index ["vulnerability_id", "issue_id"], name: "idx_vulnerability_issue_links_on_vulnerability_id_and_issue_id", unique: true
+ t.index ["vulnerability_id", "link_type"], name: "idx_vulnerability_issue_links_on_vulnerability_id_and_link_type", unique: true, where: "(link_type = 2)"
+ end
+
create_table "vulnerability_occurrence_identifiers", force: :cascade do |t|
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
@@ -3996,6 +4130,19 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
t.index ["type"], name: "index_web_hooks_on_type"
end
+ create_table "zoom_meetings", force: :cascade do |t|
+ t.bigint "project_id", null: false
+ t.bigint "issue_id", null: false
+ t.datetime_with_timezone "created_at", null: false
+ t.datetime_with_timezone "updated_at", null: false
+ t.integer "issue_status", limit: 2, default: 1, null: false
+ t.string "url", limit: 255
+ t.index ["issue_id", "issue_status"], name: "index_zoom_meetings_on_issue_id_and_issue_status", unique: true, where: "(issue_status = 1)"
+ t.index ["issue_id"], name: "index_zoom_meetings_on_issue_id"
+ t.index ["issue_status"], name: "index_zoom_meetings_on_issue_status"
+ t.index ["project_id"], name: "index_zoom_meetings_on_project_id"
+ end
+
add_foreign_key "alerts_service_data", "services", on_delete: :cascade
add_foreign_key "allowed_email_domains", "namespaces", column: "group_id", on_delete: :cascade
add_foreign_key "analytics_cycle_analytics_group_stages", "labels", column: "end_event_label_id", on_delete: :cascade
@@ -4086,6 +4233,8 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
add_foreign_key "ci_sources_pipelines", "projects", name: "fk_1e53c97c0a", on_delete: :cascade
add_foreign_key "ci_stages", "ci_pipelines", column: "pipeline_id", name: "fk_fb57e6cc56", on_delete: :cascade
add_foreign_key "ci_stages", "projects", name: "fk_2360681d1d", on_delete: :cascade
+ add_foreign_key "ci_subscriptions_projects", "projects", column: "downstream_project_id", on_delete: :cascade
+ add_foreign_key "ci_subscriptions_projects", "projects", column: "upstream_project_id", on_delete: :cascade
add_foreign_key "ci_trigger_requests", "ci_triggers", column: "trigger_id", name: "fk_b8ec8b7245", on_delete: :cascade
add_foreign_key "ci_triggers", "projects", name: "fk_e3e63f966e", on_delete: :cascade
add_foreign_key "ci_triggers", "users", column: "owner_id", name: "fk_e8e10d1964", on_delete: :cascade
@@ -4101,6 +4250,8 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
add_foreign_key "clusters", "projects", column: "management_project_id", name: "fk_f05c5e5a42", on_delete: :nullify
add_foreign_key "clusters", "users", on_delete: :nullify
add_foreign_key "clusters_applications_cert_managers", "clusters", on_delete: :cascade
+ add_foreign_key "clusters_applications_crossplane", "clusters", on_delete: :cascade
+ add_foreign_key "clusters_applications_elastic_stacks", "clusters", on_delete: :cascade
add_foreign_key "clusters_applications_helm", "clusters", on_delete: :cascade
add_foreign_key "clusters_applications_ingress", "clusters", on_delete: :cascade
add_foreign_key "clusters_applications_jupyter", "clusters", on_delete: :cascade
@@ -4117,6 +4268,8 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
add_foreign_key "dependency_proxy_blobs", "namespaces", column: "group_id", on_delete: :cascade
add_foreign_key "dependency_proxy_group_settings", "namespaces", column: "group_id", on_delete: :cascade
add_foreign_key "deploy_keys_projects", "projects", name: "fk_58a901ca7e", on_delete: :cascade
+ add_foreign_key "deployment_merge_requests", "deployments", on_delete: :cascade
+ add_foreign_key "deployment_merge_requests", "merge_requests", on_delete: :cascade
add_foreign_key "deployments", "clusters", name: "fk_289bba3222", on_delete: :nullify
add_foreign_key "deployments", "projects", name: "fk_b9a3851b82", on_delete: :cascade
add_foreign_key "description_versions", "epics", on_delete: :cascade
@@ -4127,7 +4280,7 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
add_foreign_key "design_management_designs_versions", "design_management_designs", column: "design_id", name: "fk_03c671965c", on_delete: :cascade
add_foreign_key "design_management_designs_versions", "design_management_versions", column: "version_id", name: "fk_f4d25ba00c", on_delete: :cascade
add_foreign_key "design_management_versions", "issues", on_delete: :cascade
- add_foreign_key "design_management_versions", "users", name: "fk_ee16b939e5", on_delete: :nullify
+ add_foreign_key "design_management_versions", "users", column: "author_id", name: "fk_c1440b4896", on_delete: :nullify
add_foreign_key "draft_notes", "merge_requests", on_delete: :cascade
add_foreign_key "draft_notes", "users", column: "author_id", on_delete: :cascade
add_foreign_key "elasticsearch_indexed_namespaces", "namespaces", on_delete: :cascade
@@ -4136,7 +4289,9 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
add_foreign_key "epic_issues", "epics", on_delete: :cascade
add_foreign_key "epic_issues", "issues", on_delete: :cascade
add_foreign_key "epic_metrics", "epics", on_delete: :cascade
+ add_foreign_key "epics", "epics", column: "due_date_sourcing_epic_id", name: "fk_013c9f36ca", on_delete: :nullify
add_foreign_key "epics", "epics", column: "parent_id", name: "fk_25b99c1be3", on_delete: :cascade
+ add_foreign_key "epics", "epics", column: "start_date_sourcing_epic_id", name: "fk_9d480c64b2", on_delete: :nullify
add_foreign_key "epics", "milestones", on_delete: :nullify
add_foreign_key "epics", "namespaces", column: "group_id", name: "fk_f081aa4489", on_delete: :cascade
add_foreign_key "epics", "users", column: "assignee_id", name: "fk_dccd3f98fc", on_delete: :nullify
@@ -4184,7 +4339,10 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
add_foreign_key "gpg_signatures", "projects", on_delete: :cascade
add_foreign_key "grafana_integrations", "projects", on_delete: :cascade
add_foreign_key "group_custom_attributes", "namespaces", column: "group_id", on_delete: :cascade
+ add_foreign_key "group_group_links", "namespaces", column: "shared_group_id", on_delete: :cascade
+ add_foreign_key "group_group_links", "namespaces", column: "shared_with_group_id", on_delete: :cascade
add_foreign_key "identities", "saml_providers", name: "fk_aade90f0fc", on_delete: :cascade
+ add_foreign_key "import_export_uploads", "namespaces", column: "group_id", name: "fk_83319d9721", on_delete: :cascade
add_foreign_key "import_export_uploads", "projects", on_delete: :cascade
add_foreign_key "index_statuses", "projects", name: "fk_74b2492545", on_delete: :cascade
add_foreign_key "insights", "namespaces", on_delete: :cascade
@@ -4270,6 +4428,8 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
add_foreign_key "operations_feature_flag_scopes", "operations_feature_flags", column: "feature_flag_id", on_delete: :cascade
add_foreign_key "operations_feature_flags", "projects", on_delete: :cascade
add_foreign_key "operations_feature_flags_clients", "projects", on_delete: :cascade
+ add_foreign_key "packages_conan_file_metadata", "packages_package_files", column: "package_file_id", on_delete: :cascade
+ add_foreign_key "packages_conan_metadata", "packages_packages", column: "package_id", on_delete: :cascade
add_foreign_key "packages_maven_metadata", "packages_packages", column: "package_id", name: "fk_be88aed360", on_delete: :cascade
add_foreign_key "packages_package_files", "packages_packages", column: "package_id", name: "fk_86f0f182f8", on_delete: :cascade
add_foreign_key "packages_package_metadata", "packages_packages", column: "package_id", on_delete: :cascade
@@ -4280,6 +4440,7 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
add_foreign_key "path_locks", "projects", name: "fk_5265c98f24", on_delete: :cascade
add_foreign_key "path_locks", "users"
add_foreign_key "personal_access_tokens", "users"
+ add_foreign_key "plan_limits", "plans", on_delete: :cascade
add_foreign_key "pool_repositories", "projects", column: "source_project_id", on_delete: :nullify
add_foreign_key "pool_repositories", "shards", on_delete: :restrict
add_foreign_key "project_alerting_settings", "projects", on_delete: :cascade
@@ -4307,6 +4468,7 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
add_foreign_key "project_statistics", "projects", on_delete: :cascade
add_foreign_key "project_tracing_settings", "projects", on_delete: :cascade
add_foreign_key "projects", "pool_repositories", name: "fk_6e5c14658a", on_delete: :nullify
+ add_foreign_key "projects", "users", column: "marked_for_deletion_by_user_id", name: "fk_25d8780d11", on_delete: :nullify
add_foreign_key "prometheus_alert_events", "projects", on_delete: :cascade
add_foreign_key "prometheus_alert_events", "prometheus_alerts", on_delete: :cascade
add_foreign_key "prometheus_alerts", "environments", on_delete: :cascade
@@ -4382,6 +4544,8 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
add_foreign_key "users", "namespaces", column: "managing_group_id", name: "fk_a4b8fefe3e", on_delete: :nullify
add_foreign_key "users_ops_dashboard_projects", "projects", on_delete: :cascade
add_foreign_key "users_ops_dashboard_projects", "users", on_delete: :cascade
+ add_foreign_key "users_security_dashboard_projects", "projects", on_delete: :cascade
+ add_foreign_key "users_security_dashboard_projects", "users", on_delete: :cascade
add_foreign_key "users_star_projects", "projects", name: "fk_22cd27ddfc", on_delete: :cascade
add_foreign_key "vulnerabilities", "epics", name: "fk_1d37cddf91", on_delete: :nullify
add_foreign_key "vulnerabilities", "milestones", column: "due_date_sourcing_milestone_id", name: "fk_7c5bb22a22", on_delete: :nullify
@@ -4391,6 +4555,7 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
add_foreign_key "vulnerabilities", "users", column: "author_id", name: "fk_b1de915a15", on_delete: :nullify
add_foreign_key "vulnerabilities", "users", column: "closed_by_id", name: "fk_cf5c60acbf", on_delete: :nullify
add_foreign_key "vulnerabilities", "users", column: "last_edited_by_id", name: "fk_1302949740", on_delete: :nullify
+ add_foreign_key "vulnerabilities", "users", column: "resolved_by_id", name: "fk_76bc5f5455", on_delete: :nullify
add_foreign_key "vulnerabilities", "users", column: "updated_by_id", name: "fk_7ac31eacb9", on_delete: :nullify
add_foreign_key "vulnerability_feedback", "ci_pipelines", column: "pipeline_id", on_delete: :nullify
add_foreign_key "vulnerability_feedback", "issues", on_delete: :nullify
@@ -4399,6 +4564,8 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
add_foreign_key "vulnerability_feedback", "users", column: "author_id", on_delete: :cascade
add_foreign_key "vulnerability_feedback", "users", column: "comment_author_id", name: "fk_94f7c8a81e", on_delete: :nullify
add_foreign_key "vulnerability_identifiers", "projects", on_delete: :cascade
+ add_foreign_key "vulnerability_issue_links", "issues", on_delete: :cascade
+ add_foreign_key "vulnerability_issue_links", "vulnerabilities", on_delete: :cascade
add_foreign_key "vulnerability_occurrence_identifiers", "vulnerability_identifiers", column: "identifier_id", on_delete: :cascade
add_foreign_key "vulnerability_occurrence_identifiers", "vulnerability_occurrences", column: "occurrence_id", on_delete: :cascade
add_foreign_key "vulnerability_occurrence_pipelines", "ci_pipelines", column: "pipeline_id", on_delete: :cascade
@@ -4410,4 +4577,6 @@ ActiveRecord::Schema.define(version: 2019_11_22_135327) do
add_foreign_key "vulnerability_scanners", "projects", on_delete: :cascade
add_foreign_key "web_hook_logs", "web_hooks", on_delete: :cascade
add_foreign_key "web_hooks", "projects", name: "fk_0c8ca6d9d1", on_delete: :cascade
+ add_foreign_key "zoom_meetings", "issues", on_delete: :cascade
+ add_foreign_key "zoom_meetings", "projects", on_delete: :cascade
end
diff --git a/doc/README.md b/doc/README.md
index 61265f9400..af573a3eb3 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -119,8 +119,8 @@ The following documentation relates to the DevOps **Plan** stage:
| [Related Issues](user/project/issues/related_issues.md) **(STARTER)** | Create a relationship between issues. |
| [Roadmap](user/group/roadmap/index.md) **(ULTIMATE)** | Visualize epic timelines. |
| [Service Desk](user/project/service_desk.md) **(PREMIUM)** | A simple way to allow people to create issues in your GitLab instance without needing their own user account. |
-| [Time Tracking](workflow/time_tracking.md) | Track time spent on issues and merge requests. |
-| [Todos](workflow/todos.md) | Keep track of work requiring attention with a chronological list displayed on a simple dashboard. |
+| [Time Tracking](user/project/time_tracking.md) | Track time spent on issues and merge requests. |
+| [Todos](user/todos.md) | Keep track of work requiring attention with a chronological list displayed on a simple dashboard. |