diff options
author | Stan Hu <stanhu@gmail.com> | 2019-08-09 20:22:22 +0300 |
---|---|---|
committer | Stan Hu <stanhu@gmail.com> | 2019-08-09 20:22:22 +0300 |
commit | 916f255bed04cc7ec68d2df26527b862f0ea73e8 (patch) | |
tree | bf0a2839600b927b42f9585b37b66fbce67f2cef | |
parent | f77ffdafad0a94c8a2af7070710fbfbe88994040 (diff) | |
parent | fe214a217ca95c2c4a53c501f372367fecaf7f99 (diff) |
Merge branch 'master' into sh-break-out-invited-group-members
624 files changed, 6688 insertions, 2666 deletions
diff --git a/.eslintrc.yml b/.eslintrc.yml index 2612fd3371d..95cbc602d5b 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -10,12 +10,15 @@ plugins: - import - html - "@gitlab/i18n" + - "@gitlab/vue-i18n" settings: import/resolver: webpack: config: './config/webpack.config.js' rules: "@gitlab/i18n/no-non-i18n-strings": error + "@gitlab/vue-i18n/no-bare-strings": error + "@gitlab/vue-i18n/no-bare-attribute-strings": error import/no-commonjs: error no-underscore-dangle: - error @@ -36,9 +39,6 @@ rules: vue/use-v-on-exact: off overrides: files: - # Vue is temporarily being disabled until the autofix errors are resolved - # Follow up issue https://gitlab.com/gitlab-org/gitlab-ce/issues/57969 - - '*.vue' - '**/spec/**/*' rules: "@gitlab/i18n/no-non-i18n-strings": off diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9086da10283..9531d10be05 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.6.3-golang-1.11-git-2.21-chrome-73.0-node-12.x-yarn-1.16-postgresql-9.6-graphicsmagick-1.3.29" +image: "dev.gitlab.org:5005/gitlab/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.29" variables: RAILS_ENV: "test" diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS index cb3bc544132..dae3c349ff4 100644 --- a/.gitlab/CODEOWNERS +++ b/.gitlab/CODEOWNERS @@ -6,8 +6,8 @@ /doc/ @axil @marcia @eread @mikelewis # Frontend maintainers should see everything in `app/assets/` -app/assets/ @ClemMakesApps @fatihacet @filipa @iamphill @mikegreiling @timzallmann @kushalpandya @pslaughter -*.scss @annabeldunstone @ClemMakesApps @fatihacet @filipa @iamphill @mikegreiling @timzallmann @kushalpandya @pslaughter +app/assets/ @ClemMakesApps @fatihacet @filipa @mikegreiling @timzallmann @kushalpandya @pslaughter +*.scss @annabeldunstone @ClemMakesApps @fatihacet @filipa @mikegreiling @timzallmann @kushalpandya @pslaughter # Maintainers from the Database team should review changes in `db/` db/ @gl-database diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml index d2148f01441..50f5b58d4e7 100644 --- a/.gitlab/ci/frontend.gitlab-ci.yml +++ b/.gitlab/ci/frontend.gitlab-ci.yml @@ -15,7 +15,7 @@ .gitlab:assets:compile-metadata: <<: *assets-compile-cache extends: .dedicated-no-docs-pull-cache-job - image: dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.6.3-git-2.21-chrome-73.0-node-12.x-yarn-1.16-graphicsmagick-1.3.29-docker-18.06.1 + image: dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.6.3-git-2.22-chrome-73.0-node-12.x-yarn-1.16-graphicsmagick-1.3.29-docker-18.06.1 dependencies: - setup-test-env services: diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml index 8a89232fdd4..aeae5bd26e9 100644 --- a/.gitlab/ci/rails.gitlab-ci.yml +++ b/.gitlab/ci/rails.gitlab-ci.yml @@ -90,7 +90,7 @@ .rspec-metadata-pg-10: &rspec-metadata-pg-10 <<: *rspec-metadata <<: *use-pg-10 - image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.6.3-golang-1.11-git-2.21-chrome-73.0-node-12.x-yarn-1.16-postgresql-10-graphicsmagick-1.3.29" + image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.6.3-golang-1.11-git-2.22-chrome-73.0-node-12.x-yarn-1.16-postgresql-10-graphicsmagick-1.3.29" # DB migration, rollback, and seed jobs .db-migrate-reset: &db-migrate-reset diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml index 61805c2d8d2..d29cb8aa0b0 100644 --- a/.haml-lint_todo.yml +++ b/.haml-lint_todo.yml @@ -7,517 +7,517 @@ # versions of Haml-Lint, may require this file to be generated again. linters: - # Offense count: 2075 NoPlainNodes: enabled: true exclude: - - "app/views/admin/abuse_reports/_abuse_report.html.haml" - - "app/views/admin/abuse_reports/index.html.haml" - - "app/views/admin/appearances/_form.html.haml" - - "app/views/admin/application_settings/_abuse.html.haml" - - "app/views/admin/application_settings/_diff_limits.html.haml" - - "app/views/admin/application_settings/_gitaly.html.haml" - - "app/views/admin/application_settings/_influx.html.haml" - - "app/views/admin/application_settings/_ip_limits.html.haml" - - "app/views/admin/application_settings/_logging.html.haml" - - "app/views/admin/application_settings/_performance.html.haml" - - "app/views/admin/application_settings/_plantuml.html.haml" - - "app/views/admin/application_settings/_prometheus.html.haml" - - "app/views/admin/application_settings/_realtime.html.haml" - - "app/views/admin/application_settings/_repository_check.html.haml" - - "app/views/admin/application_settings/_repository_storage.html.haml" - - "app/views/admin/application_settings/_signin.html.haml" - - "app/views/admin/application_settings/_signup.html.haml" - - "app/views/admin/application_settings/_spam.html.haml" - - "app/views/admin/application_settings/_terminal.html.haml" - - "app/views/admin/application_settings/_usage.html.haml" - - "app/views/admin/application_settings/_visibility_and_access.html.haml" - - "app/views/admin/applications/_delete_form.html.haml" - - "app/views/admin/applications/_form.html.haml" - - "app/views/admin/applications/edit.html.haml" - - "app/views/admin/applications/index.html.haml" - - "app/views/admin/applications/new.html.haml" - - "app/views/admin/applications/show.html.haml" - - "app/views/admin/background_jobs/show.html.haml" - - "app/views/admin/broadcast_messages/index.html.haml" - - "app/views/admin/dashboard/index.html.haml" - - "app/views/admin/deploy_keys/new.html.haml" - - "app/views/admin/groups/show.html.haml" - - "app/views/admin/health_check/show.html.haml" - - "app/views/admin/hook_logs/_index.html.haml" - - "app/views/admin/hook_logs/show.html.haml" - - "app/views/admin/hooks/_form.html.haml" - - "app/views/admin/hooks/edit.html.haml" - - "app/views/admin/hooks/index.html.haml" - - "app/views/admin/labels/_form.html.haml" - - "app/views/admin/logs/show.html.haml" - - "app/views/admin/projects/_projects.html.haml" - - "app/views/admin/projects/show.html.haml" - - "app/views/admin/requests_profiles/index.html.haml" - - "app/views/admin/runners/_runner.html.haml" - - "app/views/admin/runners/index.html.haml" - - "app/views/admin/runners/show.html.haml" - - "app/views/admin/services/_form.html.haml" - - "app/views/admin/services/index.html.haml" - - "app/views/admin/spam_logs/_spam_log.html.haml" - - "app/views/admin/spam_logs/index.html.haml" - - "app/views/admin/system_info/show.html.haml" - - "app/views/admin/users/_access_levels.html.haml" - - "app/views/admin/users/_form.html.haml" - - "app/views/admin/users/_head.html.haml" - - "app/views/admin/users/_profile.html.haml" - - "app/views/admin/users/_projects.html.haml" - - "app/views/admin/users/new.html.haml" - - "app/views/admin/users/projects.html.haml" - - "app/views/admin/users/show.html.haml" - - "app/views/clusters/clusters/_cluster.html.haml" - - "app/views/clusters/clusters/_form.html.haml" - - "app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml" - - "app/views/clusters/clusters/gcp/_form.html.haml" - - "app/views/clusters/clusters/new.html.haml" - - "app/views/dashboard/milestones/index.html.haml" - - "app/views/dashboard/projects/_blank_state_admin_welcome.html.haml" - - "app/views/dashboard/projects/_blank_state_welcome.html.haml" - - "app/views/dashboard/projects/_zero_authorized_projects.html.haml" - - "app/views/dashboard/snippets/index.html.haml" - - "app/views/dashboard/todos/_todo.html.haml" - - "app/views/dashboard/todos/index.html.haml" - - "app/views/devise/confirmations/almost_there.haml" - - "app/views/devise/mailer/_confirmation_instructions_account.html.haml" - - "app/views/devise/mailer/_confirmation_instructions_secondary.html.haml" - - "app/views/devise/mailer/email_changed.html.haml" - - "app/views/devise/mailer/password_change.html.haml" - - "app/views/devise/mailer/reset_password_instructions.html.haml" - - "app/views/devise/mailer/unlock_instructions.html.haml" - - "app/views/devise/passwords/edit.html.haml" - - "app/views/devise/sessions/_new_base.html.haml" - - "app/views/devise/sessions/_new_crowd.html.haml" - - "app/views/devise/sessions/_new_ldap.html.haml" - - "app/views/devise/sessions/new.html.haml" - - "app/views/devise/sessions/two_factor.html.haml" - - "app/views/devise/shared/_omniauth_box.html.haml" - - "app/views/devise/shared/_sign_in_link.html.haml" - - "app/views/devise/shared/_signup_box.html.haml" - - "app/views/devise/shared/_tabs_normal.html.haml" - - "app/views/discussions/_discussion.html.haml" - - "app/views/discussions/_headline.html.haml" - - "app/views/discussions/_notes.html.haml" - - "app/views/discussions/_resolve_all.html.haml" - - "app/views/doorkeeper/applications/_delete_form.html.haml" - - "app/views/doorkeeper/authorized_applications/_delete_form.html.haml" - - "app/views/errors/encoding.html.haml" - - "app/views/errors/git_not_found.html.haml" - - "app/views/errors/omniauth_error.html.haml" - - "app/views/errors/precondition_failed.html.haml" - - "app/views/events/_commit.html.haml" - - "app/views/events/_event_push.atom.haml" - - "app/views/events/event/_push.html.haml" - - "app/views/groups/_create_chat_team.html.haml" - - "app/views/groups/_group_admin_settings.html.haml" - - "app/views/groups/group_members/_new_group_member.html.haml" - - "app/views/groups/group_members/index.html.haml" - - "app/views/groups/labels/edit.html.haml" - - "app/views/groups/labels/new.html.haml" - - "app/views/groups/milestones/edit.html.haml" - - "app/views/groups/milestones/index.html.haml" - - "app/views/groups/milestones/new.html.haml" - - "app/views/groups/projects.html.haml" - - "app/views/groups/runners/edit.html.haml" - - "app/views/groups/settings/_advanced.html.haml" - - "app/views/groups/settings/_lfs.html.haml" - - "app/views/help/_shortcuts.html.haml" - - "app/views/help/index.html.haml" - - "app/views/help/instance_configuration.html.haml" - - "app/views/help/instance_configuration/_gitlab_ci.html.haml" - - "app/views/help/instance_configuration/_gitlab_pages.html.haml" - - "app/views/help/instance_configuration/_ssh_info.html.haml" - - "app/views/help/ui.html.haml" - - "app/views/import/bitbucket/status.html.haml" - - "app/views/import/bitbucket_server/status.html.haml" - - "app/views/instance_statistics/cohorts/_cohorts_table.html.haml" - - "app/views/instance_statistics/cohorts/_usage_ping.html.haml" - - "app/views/invites/show.html.haml" - - "app/views/layouts/_mailer.html.haml" - - "app/views/layouts/header/_default.html.haml" - - "app/views/layouts/header/_new_dropdown.haml" - - "app/views/layouts/mailer/devise.html.haml" - - "app/views/layouts/nav/sidebar/_profile.html.haml" - - "app/views/layouts/notify.html.haml" - - "app/views/notify/_failed_builds.html.haml" - - "app/views/notify/_reassigned_issuable_email.html.haml" - - "app/views/notify/_removal_notification.html.haml" - - "app/views/notify/autodevops_disabled_email.html.haml" - - "app/views/notify/changed_milestone_email.html.haml" - - "app/views/notify/import_issues_csv_email.html.haml" - - "app/views/notify/issue_moved_email.html.haml" - - "app/views/notify/member_access_denied_email.html.haml" - - "app/views/notify/member_invite_accepted_email.html.haml" - - "app/views/notify/member_invite_declined_email.html.haml" - - "app/views/notify/member_invited_email.html.haml" - - "app/views/notify/new_gpg_key_email.html.haml" - - "app/views/notify/new_mention_in_issue_email.html.haml" - - "app/views/notify/new_ssh_key_email.html.haml" - - "app/views/notify/new_user_email.html.haml" - - "app/views/notify/pages_domain_disabled_email.html.haml" - - "app/views/notify/pages_domain_enabled_email.html.haml" - - "app/views/notify/pages_domain_verification_failed_email.html.haml" - - "app/views/notify/pages_domain_verification_succeeded_email.html.haml" - - "app/views/notify/pipeline_failed_email.html.haml" - - "app/views/notify/pipeline_success_email.html.haml" - - "app/views/notify/project_was_exported_email.html.haml" - - "app/views/notify/project_was_moved_email.html.haml" - - "app/views/notify/project_was_not_exported_email.html.haml" - - "app/views/notify/push_to_merge_request_email.html.haml" - - "app/views/notify/remote_mirror_update_failed_email.html.haml" - - "app/views/notify/removed_milestone_issue_email.html.haml" - - "app/views/notify/removed_milestone_merge_request_email.html.haml" - - "app/views/notify/repository_push_email.html.haml" - - "app/views/peek/views/_gc.html.haml" - - "app/views/peek/views/_redis.html.haml" - - "app/views/peek/views/_sidekiq.html.haml" - - "app/views/profiles/_event_table.html.haml" - - "app/views/profiles/active_sessions/_active_session.html.haml" - - "app/views/profiles/active_sessions/index.html.haml" - - "app/views/profiles/audit_log.html.haml" - - "app/views/profiles/chat_names/_chat_name.html.haml" - - "app/views/profiles/chat_names/index.html.haml" - - "app/views/profiles/chat_names/new.html.haml" - - "app/views/profiles/emails/index.html.haml" - - "app/views/profiles/gpg_keys/_key.html.haml" - - "app/views/profiles/gpg_keys/index.html.haml" - - "app/views/profiles/keys/_key.html.haml" - - "app/views/profiles/keys/_key_details.html.haml" - - "app/views/profiles/keys/index.html.haml" - - "app/views/profiles/notifications/show.html.haml" - - "app/views/profiles/passwords/edit.html.haml" - - "app/views/profiles/personal_access_tokens/index.html.haml" - - "app/views/profiles/preferences/show.html.haml" - - "app/views/profiles/show.html.haml" - - "app/views/profiles/two_factor_auths/_codes.html.haml" - - "app/views/profiles/two_factor_auths/codes.html.haml" - - "app/views/profiles/two_factor_auths/create.html.haml" - - "app/views/profiles/two_factor_auths/show.html.haml" - - "app/views/projects/_bitbucket_import_modal.html.haml" - - "app/views/projects/_customize_workflow.html.haml" - - "app/views/projects/_deletion_failed.html.haml" - - "app/views/projects/_fork_suggestion.html.haml" - - "app/views/projects/_gitlab_import_modal.html.haml" - - "app/views/projects/_home_panel.html.haml" - - "app/views/projects/_import_project_pane.html.haml" - - "app/views/projects/_issuable_by_email.html.haml" - - "app/views/projects/_md_preview.html.haml" - - "app/views/projects/_new_project_fields.html.haml" - - "app/views/projects/_readme.html.haml" - - "app/views/projects/artifacts/_tree_file.html.haml" - - "app/views/projects/artifacts/browse.html.haml" - - "app/views/projects/blame/_age_map_legend.html.haml" - - "app/views/projects/blame/show.html.haml" - - "app/views/projects/blob/_editor.html.haml" - - "app/views/projects/blob/_header_content.html.haml" - - "app/views/projects/blob/_new_dir.html.haml" - - "app/views/projects/blob/_remove.html.haml" - - "app/views/projects/blob/_render_error.html.haml" - - "app/views/projects/blob/_template_selectors.html.haml" - - "app/views/projects/blob/_upload.html.haml" - - "app/views/projects/blob/edit.html.haml" - - "app/views/projects/blob/new.html.haml" - - "app/views/projects/blob/preview.html.haml" - - "app/views/projects/blob/viewers/_empty.html.haml" - - "app/views/projects/blob/viewers/_stl.html.haml" - - "app/views/projects/branches/_branch.html.haml" - - "app/views/projects/branches/_commit.html.haml" - - "app/views/projects/branches/_delete_protected_modal.html.haml" - - "app/views/projects/branches/new.html.haml" - - "app/views/projects/ci/builds/_build.html.haml" - - "app/views/projects/ci/lints/_create.html.haml" - - "app/views/projects/commit/_change.html.haml" - - "app/views/projects/commits/_commit.html.haml" - - "app/views/projects/commits/_inline_commit.html.haml" - - "app/views/projects/compare/_form.html.haml" - - "app/views/projects/compare/index.html.haml" - - "app/views/projects/cycle_analytics/_empty_stage.html.haml" - - "app/views/projects/cycle_analytics/_no_access.html.haml" - - "app/views/projects/cycle_analytics/_overview.html.haml" - - "app/views/projects/cycle_analytics/show.html.haml" - - "app/views/projects/deploy_keys/_form.html.haml" - - "app/views/projects/deploy_keys/_index.html.haml" - - "app/views/projects/deploy_keys/edit.html.haml" - - "app/views/projects/deploy_tokens/_revoke_modal.html.haml" - - "app/views/projects/deploy_tokens/_table.html.haml" - - "app/views/projects/deployments/_deployment.html.haml" - - "app/views/projects/diffs/_file_header.html.haml" - - "app/views/projects/diffs/_replaced_image_diff.html.haml" - - "app/views/projects/diffs/_stats.html.haml" - - "app/views/projects/empty.html.haml" - - "app/views/projects/environments/show.html.haml" - - "app/views/projects/forks/error.html.haml" - - "app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml" - - "app/views/projects/graphs/charts.html.haml" - - "app/views/projects/hook_logs/_index.html.haml" - - "app/views/projects/hook_logs/show.html.haml" - - "app/views/projects/hooks/_index.html.haml" - - "app/views/projects/hooks/edit.html.haml" - - "app/views/projects/imports/new.html.haml" - - "app/views/projects/imports/show.html.haml" - - "app/views/projects/issues/_issue.html.haml" - - "app/views/projects/issues/_new_branch.html.haml" - - "app/views/projects/issues/import_csv/_modal.html.haml" - - "app/views/projects/issues/show.html.haml" - - "app/views/projects/jobs/_header.html.haml" - - "app/views/projects/jobs/_table.html.haml" - - "app/views/projects/jobs/index.html.haml" - - "app/views/projects/labels/edit.html.haml" - - "app/views/projects/labels/new.html.haml" - - "app/views/projects/mattermosts/_no_teams.html.haml" - - "app/views/projects/mattermosts/_team_selection.html.haml" - - "app/views/projects/mattermosts/new.html.haml" - - "app/views/projects/merge_requests/_commits.html.haml" - - "app/views/projects/merge_requests/_discussion.html.haml" - - "app/views/projects/merge_requests/_how_to_merge.html.haml" - - "app/views/projects/merge_requests/_merge_request.html.haml" - - "app/views/projects/merge_requests/_mr_title.html.haml" - - "app/views/projects/merge_requests/conflicts/_commit_stats.html.haml" - - "app/views/projects/merge_requests/conflicts/_file_actions.html.haml" - - "app/views/projects/merge_requests/conflicts/_submit_form.html.haml" - - "app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml" - - "app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml" - - "app/views/projects/merge_requests/conflicts/show.html.haml" - - "app/views/projects/merge_requests/creations/_diffs.html.haml" - - "app/views/projects/merge_requests/creations/_new_compare.html.haml" - - "app/views/projects/merge_requests/creations/_new_submit.html.haml" - - "app/views/projects/merge_requests/diffs/_different_base.html.haml" - - "app/views/projects/merge_requests/diffs/_diffs.html.haml" - - "app/views/projects/merge_requests/diffs/_version_controls.html.haml" - - "app/views/projects/merge_requests/invalid.html.haml" - - "app/views/projects/merge_requests/widget/open/_error.html.haml" - - "app/views/projects/mirrors/_regenerate_public_ssh_key_confirm_modal.html.haml" - - "app/views/projects/mirrors/_ssh_host_keys.html.haml" - - "app/views/projects/new.html.haml" - - "app/views/projects/no_repo.html.haml" - - "app/views/projects/pages/_access.html.haml" - - "app/views/projects/pages/_destroy.haml" - - "app/views/projects/pages/_https_only.html.haml" - - "app/views/projects/pages/_list.html.haml" - - "app/views/projects/pages/_no_domains.html.haml" - - "app/views/projects/pages/_use.html.haml" - - "app/views/projects/pages/show.html.haml" - - "app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml" - - "app/views/projects/pipelines/_info.html.haml" - - "app/views/projects/pipelines/charts/_pipelines.haml" - - "app/views/projects/protected_branches/shared/_branches_list.html.haml" - - "app/views/projects/protected_branches/shared/_create_protected_branch.html.haml" - - "app/views/projects/protected_branches/shared/_dropdown.html.haml" - - "app/views/projects/protected_branches/shared/_index.html.haml" - - "app/views/projects/protected_branches/shared/_matching_branch.html.haml" - - "app/views/projects/protected_branches/shared/_protected_branch.html.haml" - - "app/views/projects/protected_branches/show.html.haml" - - "app/views/projects/protected_tags/shared/_create_protected_tag.html.haml" - - "app/views/projects/protected_tags/shared/_dropdown.html.haml" - - "app/views/projects/protected_tags/shared/_index.html.haml" - - "app/views/projects/protected_tags/shared/_matching_tag.html.haml" - - "app/views/projects/protected_tags/shared/_protected_tag.html.haml" - - "app/views/projects/protected_tags/shared/_tags_list.html.haml" - - "app/views/projects/protected_tags/show.html.haml" - - "app/views/projects/registry/repositories/_tag.html.haml" - - "app/views/projects/repositories/_feed.html.haml" - - "app/views/projects/runners/_shared_runners.html.haml" - - "app/views/projects/runners/edit.html.haml" - - "app/views/projects/services/_form.html.haml" - - "app/views/projects/services/_index.html.haml" - - "app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml" - - "app/views/projects/services/mattermost_slash_commands/_help.html.haml" - - "app/views/projects/services/prometheus/_metrics.html.haml" - - "app/views/projects/services/slack_slash_commands/_help.html.haml" - - "app/views/projects/settings/ci_cd/_badge.html.haml" - - "app/views/projects/settings/ci_cd/_form.html.haml" - - "app/views/projects/stage/_stage.html.haml" - - "app/views/projects/tags/index.html.haml" - - "app/views/projects/tags/new.html.haml" - - "app/views/projects/tags/releases/edit.html.haml" - - "app/views/projects/tree/_tree_row.html.haml" - - "app/views/projects/tree/_truncated_notice_tree_row.html.haml" - - "app/views/projects/triggers/_content.html.haml" - - "app/views/projects/triggers/_form.html.haml" - - "app/views/projects/triggers/_index.html.haml" - - "app/views/projects/triggers/_trigger.html.haml" - - "app/views/projects/triggers/edit.html.haml" - - "app/views/projects/wikis/_new.html.haml" - - "app/views/projects/wikis/_pages_wiki_page.html.haml" - - "app/views/projects/wikis/edit.html.haml" - - "app/views/projects/wikis/history.html.haml" - - "app/views/repository_check_mailer/notify.html.haml" - - "app/views/search/_form.html.haml" - - "app/views/search/results/_issue.html.haml" - - "app/views/search/results/_note.html.haml" - - "app/views/search/results/_snippet_blob.html.haml" - - "app/views/search/results/_snippet_title.html.haml" - - "app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml" - - "app/views/shared/_commit_message_container.html.haml" - - "app/views/shared/_confirm_modal.html.haml" - - "app/views/shared/_delete_label_modal.html.haml" - - "app/views/shared/_group_form.html.haml" - - "app/views/shared/_group_tips.html.haml" - - "app/views/shared/_milestone_expired.html.haml" - - "app/views/shared/_no_password.html.haml" - - "app/views/shared/_no_ssh.html.haml" - - "app/views/shared/_outdated_browser.html.haml" - - "app/views/shared/_personal_access_tokens_created_container.html.haml" - - "app/views/shared/_personal_access_tokens_table.html.haml" - - "app/views/shared/_ping_consent.html.haml" - - "app/views/shared/_project_limit.html.haml" - - "app/views/shared/_service_settings.html.haml" - - "app/views/shared/boards/components/_board.html.haml" - - "app/views/shared/boards/components/_sidebar.html.haml" - - "app/views/shared/boards/components/sidebar/_due_date.html.haml" - - "app/views/shared/boards/components/sidebar/_labels.html.haml" - - "app/views/shared/boards/components/sidebar/_milestone.html.haml" - - "app/views/shared/empty_states/_priority_labels.html.haml" - - "app/views/shared/hook_logs/_content.html.haml" - - "app/views/shared/issuable/_assignees.html.haml" - - "app/views/shared/issuable/_board_create_list_dropdown.html.haml" - - "app/views/shared/issuable/_bulk_update_sidebar.html.haml" - - "app/views/shared/issuable/_close_reopen_report_toggle.html.haml" - - "app/views/shared/issuable/_form.html.haml" - - "app/views/shared/issuable/_search_bar.html.haml" - - "app/views/shared/issuable/_sidebar.html.haml" - - "app/views/shared/issuable/form/_default_templates.html.haml" - - "app/views/shared/issuable/form/_issue_assignee.html.haml" - - "app/views/shared/issuable/form/_template_selector.html.haml" - - "app/views/shared/issuable/form/_title.html.haml" - - "app/views/shared/labels/_form.html.haml" - - "app/views/shared/members/_member.html.haml" - - "app/views/shared/milestones/_form_dates.html.haml" - - "app/views/shared/milestones/_issuable.html.haml" - - "app/views/shared/milestones/_milestone.html.haml" - - "app/views/shared/milestones/_sidebar.html.haml" - - "app/views/shared/milestones/_top.html.haml" - - "app/views/shared/notes/_hints.html.haml" - - "app/views/shared/notes/_note.html.haml" - - "app/views/shared/notifications/_button.html.haml" - - "app/views/shared/notifications/_custom_notifications.html.haml" - - "app/views/shared/notifications/_new_button.html.haml" - - "app/views/shared/notifications/_notification_dropdown.html.haml" - - "app/views/shared/plugins/_index.html.haml" - - "app/views/shared/projects/_dropdown.html.haml" - - "app/views/shared/projects/_list.html.haml" - - "app/views/shared/projects/_project.html.haml" - - "app/views/shared/runners/_runner_description.html.haml" - - "app/views/shared/runners/show.html.haml" - - "app/views/shared/snippets/_embed.html.haml" - - "app/views/shared/snippets/_header.html.haml" - - "app/views/shared/snippets/_snippet.html.haml" - - "app/views/shared/tokens/_scopes_list.html.haml" - - "app/views/shared/web_hooks/_form.html.haml" - - "app/views/shared/web_hooks/_test_button.html.haml" - - "app/views/u2f/_authenticate.html.haml" - - "app/views/u2f/_register.html.haml" - - "app/views/users/_deletion_guidance.html.haml" - - "ee/app/views/admin/_namespace_plan_info.html.haml" - - "ee/app/views/admin/application_settings/_elasticsearch_form.html.haml" - - "ee/app/views/admin/application_settings/_slack.html.haml" - - "ee/app/views/admin/application_settings/_snowplow.html.haml" - - "ee/app/views/admin/application_settings/_templates.html.haml" - - "ee/app/views/admin/audit_logs/index.html.haml" - - "ee/app/views/admin/dashboard/stats.html.haml" - - "ee/app/views/admin/emails/show.html.haml" - - "ee/app/views/admin/geo/nodes/edit.html.haml" - - "ee/app/views/admin/geo/nodes/new.html.haml" - - "ee/app/views/admin/geo/projects/_registry_failed.html.haml" - - "ee/app/views/admin/geo/projects/_registry_never.html.haml" - - "ee/app/views/admin/licenses/_breakdown.html.haml" - - "ee/app/views/admin/licenses/_upload_trial_license.html.haml" - - "ee/app/views/admin/licenses/missing.html.haml" - - "ee/app/views/admin/licenses/new.html.haml" - - "ee/app/views/admin/licenses/show.html.haml" - - "ee/app/views/admin/monitoring/ee/_nav.html.haml" - - "ee/app/views/admin/projects/_shared_runner_status.html.haml" - - "ee/app/views/admin/push_rules/show.html.haml" - - "ee/app/views/admin/users/_limits.html.haml" - - "ee/app/views/admin/users/_user_detail_note.html.haml" - - "ee/app/views/dashboard/projects/_blank_state_ee_trial.html.haml" - - "ee/app/views/errors/kerberos_denied.html.haml" - - "ee/app/views/groups/analytics/show.html.haml" - - "ee/app/views/groups/audit_events/index.html.haml" - - "ee/app/views/groups/ee/_settings_nav.html.haml" - - "ee/app/views/groups/epics/_epic.html.haml" - - "ee/app/views/groups/group_members/_ldap_sync.html.haml" - - "ee/app/views/groups/group_members/_sync_button.html.haml" - - "ee/app/views/groups/hooks/_project_hook.html.haml" - - "ee/app/views/groups/hooks/index.html.haml" - - "ee/app/views/groups/ldap_group_links/index.html.haml" - - "ee/app/views/groups/pipeline_quota/index.html.haml" - - "ee/app/views/jira_connect/subscriptions/index.html.haml" - - "ee/app/views/layouts/jira_connect.html.haml" - - "ee/app/views/layouts/nav/ee/_epic_link.html.haml" - - "ee/app/views/layouts/nav/ee/admin/_new_monitoring_sidebar.html.haml" - - "ee/app/views/layouts/service_desk.html.haml" - - "ee/app/views/ldap_group_links/_form.html.haml" - - "ee/app/views/ldap_group_links/_ldap_group_link.html.haml" - - "ee/app/views/ldap_group_links/_ldap_group_links.html.haml" - - "ee/app/views/ldap_group_links/_ldap_group_links_show.html.haml" - - "ee/app/views/ldap_group_links/_ldap_group_links_synchronizations.html.haml" - - "ee/app/views/namespaces/_shared_runner_status.html.haml" - - "ee/app/views/namespaces/_shared_runners_minutes_setting.html.haml" - - "ee/app/views/namespaces/pipelines_quota/_extra_shared_runners_minutes_quota.html.haml" - - "ee/app/views/namespaces/pipelines_quota/_list.haml" - - "ee/app/views/notify/approved_merge_request_email.html.haml" - - "ee/app/views/notify/epic_status_changed_email.html.haml" - - "ee/app/views/notify/issues_csv_email.html.haml" - - "ee/app/views/notify/new_review_email.html.haml" - - "ee/app/views/notify/prometheus_alert_fired_email.html.haml" - - "ee/app/views/notify/send_admin_notification.html.haml" - - "ee/app/views/notify/send_unsubscribed_notification.html.haml" - - "ee/app/views/notify/unapproved_merge_request_email.html.haml" - - "ee/app/views/oauth/geo_auth/error.html.haml" - - "ee/app/views/profiles/pipeline_quota/index.haml" - - "ee/app/views/projects/audit_events/index.html.haml" - - "ee/app/views/projects/blob/_owners.html.haml" - - "ee/app/views/projects/commits/_mirror_status.html.haml" - - "ee/app/views/projects/feature_flags/_configure_feature_flags_modal.html.haml" - - "ee/app/views/projects/issues/_issue_weight.html.haml" - - "ee/app/views/projects/issues/_related_issues.html.haml" - - "ee/app/views/projects/issues/export_csv/_modal.html.haml" - - "ee/app/views/projects/jobs/_shared_runner_limit_warning.html.haml" - - "ee/app/views/projects/merge_requests/_approvals_count.html.haml" - - "ee/app/views/projects/merge_requests/widget/open/_geo.html.haml" - - "ee/app/views/projects/mirrors/_mirrored_repositories_count.html.haml" - - "ee/app/views/projects/protected_branches/ee/_create_protected_branch.html.haml" - - "ee/app/views/projects/protected_branches/ee/_dropdown.html.haml" - - "ee/app/views/projects/protected_branches/ee/_fallback_update_protected_branch.html.haml" - - "ee/app/views/projects/protected_tags/_protected_tag_extra_create_access_levels.haml" - - "ee/app/views/projects/protected_tags/ee/_create_protected_tag.html.haml" - - "ee/app/views/projects/push_rules/_index.html.haml" - - "ee/app/views/projects/services/gitlab_slack_application/_help.html.haml" - - "ee/app/views/projects/services/gitlab_slack_application/_slack_integration_form.html.haml" - - "ee/app/views/projects/services/prometheus/_metrics.html.haml" - - "ee/app/views/projects/settings/slacks/edit.html.haml" - - "ee/app/views/shared/_additional_email_text.html.haml" - - "ee/app/views/shared/_geo_info_modal.html.haml" - - "ee/app/views/shared/_mirror_update_button.html.haml" - - "ee/app/views/shared/_shared_runners_minutes_limit.html.haml" - - "ee/app/views/shared/audit_events/_event_table.html.haml" - - "ee/app/views/shared/boards/components/_list_weight.html.haml" - - "ee/app/views/shared/boards/components/sidebar/_epic.html.haml" - - "ee/app/views/shared/ee/_import_form.html.haml" - - "ee/app/views/shared/epic/_search_bar.html.haml" - - "ee/app/views/shared/issuable/_approvals.html.haml" - - "ee/app/views/shared/issuable/_board_create_list_dropdown.html.haml" - - "ee/app/views/shared/issuable/_filter_weight.html.haml" - - "ee/app/views/shared/issuable/_sidebar_item_epic.haml" - - "ee/app/views/shared/members/ee/_ldap_tag.html.haml" - - "ee/app/views/shared/members/ee/_override_member_buttons.html.haml" - - "ee/app/views/shared/members/ee/_sso_badge.html.haml" - - "ee/app/views/shared/milestones/_burndown.html.haml" - - "ee/app/views/shared/milestones/_weight.html.haml" - - "ee/app/views/shared/promotions/_promote_audit_events.html.haml" - - "ee/app/views/shared/promotions/_promote_burndown_charts.html.haml" - - "ee/app/views/shared/promotions/_promote_csv_export.html.haml" - - "ee/app/views/shared/promotions/_promote_issue_weights.html.haml" - - "ee/app/views/shared/promotions/_promote_repository_features.html.haml" - - "ee/app/views/shared/promotions/_promote_servicedesk.html.haml" - - "ee/app/views/shared/push_rules/_form.html.haml" - - "ee/app/views/unsubscribes/show.html.haml" - - "ee/app/views/admin/users/_auditor_access_level_radio.html.haml" - - "ee/app/views/admin/users/_auditor_user_badge.html.haml" - - "ee/app/views/projects/protected_branches/_update_protected_branch.html.haml" + - 'app/views/admin/abuse_reports/_abuse_report.html.haml' + - 'app/views/admin/abuse_reports/index.html.haml' + - 'app/views/admin/appearances/_form.html.haml' + - 'app/views/admin/application_settings/_abuse.html.haml' + - 'app/views/admin/application_settings/_diff_limits.html.haml' + - 'app/views/admin/application_settings/_gitaly.html.haml' + - 'app/views/admin/application_settings/_influx.html.haml' + - 'app/views/admin/application_settings/_ip_limits.html.haml' + - 'app/views/admin/application_settings/_logging.html.haml' + - 'app/views/admin/application_settings/_performance.html.haml' + - 'app/views/admin/application_settings/_plantuml.html.haml' + - 'app/views/admin/application_settings/_prometheus.html.haml' + - 'app/views/admin/application_settings/_realtime.html.haml' + - 'app/views/admin/application_settings/_repository_check.html.haml' + - 'app/views/admin/application_settings/_repository_storage.html.haml' + - 'app/views/admin/application_settings/_signin.html.haml' + - 'app/views/admin/application_settings/_signup.html.haml' + - 'app/views/admin/application_settings/_spam.html.haml' + - 'app/views/admin/application_settings/_terminal.html.haml' + - 'app/views/admin/application_settings/_usage.html.haml' + - 'app/views/admin/application_settings/_visibility_and_access.html.haml' + - 'app/views/admin/applications/_delete_form.html.haml' + - 'app/views/admin/applications/_form.html.haml' + - 'app/views/admin/applications/edit.html.haml' + - 'app/views/admin/applications/index.html.haml' + - 'app/views/admin/applications/new.html.haml' + - 'app/views/admin/applications/show.html.haml' + - 'app/views/admin/background_jobs/show.html.haml' + - 'app/views/admin/broadcast_messages/index.html.haml' + - 'app/views/admin/dashboard/index.html.haml' + - 'app/views/admin/deploy_keys/new.html.haml' + - 'app/views/admin/groups/show.html.haml' + - 'app/views/admin/health_check/show.html.haml' + - 'app/views/admin/hook_logs/_index.html.haml' + - 'app/views/admin/hook_logs/show.html.haml' + - 'app/views/admin/hooks/_form.html.haml' + - 'app/views/admin/hooks/edit.html.haml' + - 'app/views/admin/hooks/index.html.haml' + - 'app/views/admin/labels/_form.html.haml' + - 'app/views/admin/logs/show.html.haml' + - 'app/views/admin/projects/_projects.html.haml' + - 'app/views/admin/projects/show.html.haml' + - 'app/views/admin/requests_profiles/index.html.haml' + - 'app/views/admin/runners/_runner.html.haml' + - 'app/views/admin/runners/index.html.haml' + - 'app/views/admin/runners/show.html.haml' + - 'app/views/admin/services/_form.html.haml' + - 'app/views/admin/services/index.html.haml' + - 'app/views/admin/spam_logs/_spam_log.html.haml' + - 'app/views/admin/spam_logs/index.html.haml' + - 'app/views/admin/system_info/show.html.haml' + - 'app/views/admin/users/_access_levels.html.haml' + - 'app/views/admin/users/_form.html.haml' + - 'app/views/admin/users/_head.html.haml' + - 'app/views/admin/users/_profile.html.haml' + - 'app/views/admin/users/_projects.html.haml' + - 'app/views/admin/users/new.html.haml' + - 'app/views/admin/users/projects.html.haml' + - 'app/views/admin/users/show.html.haml' + - 'app/views/clusters/clusters/_cluster.html.haml' + - 'app/views/clusters/clusters/_form.html.haml' + - 'app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml' + - 'app/views/clusters/clusters/gcp/_form.html.haml' + - 'app/views/clusters/clusters/new.html.haml' + - 'app/views/dashboard/milestones/index.html.haml' + - 'app/views/dashboard/projects/_blank_state_admin_welcome.html.haml' + - 'app/views/dashboard/projects/_blank_state_welcome.html.haml' + - 'app/views/dashboard/projects/_zero_authorized_projects.html.haml' + - 'app/views/dashboard/snippets/index.html.haml' + - 'app/views/dashboard/todos/_todo.html.haml' + - 'app/views/dashboard/todos/index.html.haml' + - 'app/views/devise/confirmations/almost_there.haml' + - 'app/views/devise/mailer/_confirmation_instructions_account.html.haml' + - 'app/views/devise/mailer/_confirmation_instructions_secondary.html.haml' + - 'app/views/devise/mailer/email_changed.html.haml' + - 'app/views/devise/mailer/password_change.html.haml' + - 'app/views/devise/mailer/reset_password_instructions.html.haml' + - 'app/views/devise/mailer/unlock_instructions.html.haml' + - 'app/views/devise/passwords/edit.html.haml' + - 'app/views/devise/sessions/_new_base.html.haml' + - 'app/views/devise/sessions/_new_crowd.html.haml' + - 'app/views/devise/sessions/_new_ldap.html.haml' + - 'app/views/devise/sessions/new.html.haml' + - 'app/views/devise/sessions/two_factor.html.haml' + - 'app/views/devise/shared/_omniauth_box.html.haml' + - 'app/views/devise/shared/_sign_in_link.html.haml' + - 'app/views/devise/shared/_signup_box.html.haml' + - 'app/views/devise/shared/_tabs_normal.html.haml' + - 'app/views/discussions/_discussion.html.haml' + - 'app/views/discussions/_headline.html.haml' + - 'app/views/discussions/_notes.html.haml' + - 'app/views/discussions/_resolve_all.html.haml' + - 'app/views/doorkeeper/applications/_delete_form.html.haml' + - 'app/views/doorkeeper/authorized_applications/_delete_form.html.haml' + - 'app/views/errors/encoding.html.haml' + - 'app/views/errors/git_not_found.html.haml' + - 'app/views/errors/omniauth_error.html.haml' + - 'app/views/errors/precondition_failed.html.haml' + - 'app/views/events/_commit.html.haml' + - 'app/views/events/_event_push.atom.haml' + - 'app/views/events/event/_push.html.haml' + - 'app/views/groups/_create_chat_team.html.haml' + - 'app/views/groups/_group_admin_settings.html.haml' + - 'app/views/groups/group_members/_new_group_member.html.haml' + - 'app/views/groups/group_members/index.html.haml' + - 'app/views/groups/labels/edit.html.haml' + - 'app/views/groups/labels/new.html.haml' + - 'app/views/groups/milestones/edit.html.haml' + - 'app/views/groups/milestones/index.html.haml' + - 'app/views/groups/milestones/new.html.haml' + - 'app/views/groups/projects.html.haml' + - 'app/views/groups/runners/edit.html.haml' + - 'app/views/groups/settings/_advanced.html.haml' + - 'app/views/groups/settings/_lfs.html.haml' + - 'app/views/help/_shortcuts.html.haml' + - 'app/views/help/index.html.haml' + - 'app/views/help/instance_configuration.html.haml' + - 'app/views/help/instance_configuration/_gitlab_ci.html.haml' + - 'app/views/help/instance_configuration/_gitlab_pages.html.haml' + - 'app/views/help/instance_configuration/_ssh_info.html.haml' + - 'app/views/help/ui.html.haml' + - 'app/views/import/bitbucket/status.html.haml' + - 'app/views/import/bitbucket_server/status.html.haml' + - 'app/views/instance_statistics/cohorts/_cohorts_table.html.haml' + - 'app/views/instance_statistics/cohorts/_usage_ping.html.haml' + - 'app/views/invites/show.html.haml' + - 'app/views/layouts/_mailer.html.haml' + - 'app/views/layouts/header/_default.html.haml' + - 'app/views/layouts/header/_new_dropdown.haml' + - 'app/views/layouts/mailer/devise.html.haml' + - 'app/views/layouts/nav/sidebar/_profile.html.haml' + - 'app/views/layouts/notify.html.haml' + - 'app/views/notify/_failed_builds.html.haml' + - 'app/views/notify/_reassigned_issuable_email.html.haml' + - 'app/views/notify/_removal_notification.html.haml' + - 'app/views/notify/autodevops_disabled_email.html.haml' + - 'app/views/notify/changed_milestone_email.html.haml' + - 'app/views/notify/import_issues_csv_email.html.haml' + - 'app/views/notify/issue_moved_email.html.haml' + - 'app/views/notify/member_access_denied_email.html.haml' + - 'app/views/notify/member_invite_accepted_email.html.haml' + - 'app/views/notify/member_invite_declined_email.html.haml' + - 'app/views/notify/member_invited_email.html.haml' + - 'app/views/notify/new_gpg_key_email.html.haml' + - 'app/views/notify/new_mention_in_issue_email.html.haml' + - 'app/views/notify/new_ssh_key_email.html.haml' + - 'app/views/notify/new_user_email.html.haml' + - 'app/views/notify/pages_domain_disabled_email.html.haml' + - 'app/views/notify/pages_domain_enabled_email.html.haml' + - 'app/views/notify/pages_domain_verification_failed_email.html.haml' + - 'app/views/notify/pages_domain_verification_succeeded_email.html.haml' + - 'app/views/notify/pipeline_failed_email.html.haml' + - 'app/views/notify/pipeline_success_email.html.haml' + - 'app/views/notify/project_was_exported_email.html.haml' + - 'app/views/notify/project_was_moved_email.html.haml' + - 'app/views/notify/project_was_not_exported_email.html.haml' + - 'app/views/notify/push_to_merge_request_email.html.haml' + - 'app/views/notify/remote_mirror_update_failed_email.html.haml' + - 'app/views/notify/removed_milestone_issue_email.html.haml' + - 'app/views/notify/removed_milestone_merge_request_email.html.haml' + - 'app/views/notify/repository_push_email.html.haml' + - 'app/views/peek/views/_gc.html.haml' + - 'app/views/peek/views/_redis.html.haml' + - 'app/views/peek/views/_sidekiq.html.haml' + - 'app/views/profiles/_event_table.html.haml' + - 'app/views/profiles/active_sessions/_active_session.html.haml' + - 'app/views/profiles/active_sessions/index.html.haml' + - 'app/views/profiles/audit_log.html.haml' + - 'app/views/profiles/chat_names/_chat_name.html.haml' + - 'app/views/profiles/chat_names/index.html.haml' + - 'app/views/profiles/chat_names/new.html.haml' + - 'app/views/profiles/emails/index.html.haml' + - 'app/views/profiles/gpg_keys/_key.html.haml' + - 'app/views/profiles/gpg_keys/index.html.haml' + - 'app/views/profiles/keys/_key.html.haml' + - 'app/views/profiles/keys/_key_details.html.haml' + - 'app/views/profiles/keys/index.html.haml' + - 'app/views/profiles/notifications/show.html.haml' + - 'app/views/profiles/passwords/edit.html.haml' + - 'app/views/profiles/personal_access_tokens/index.html.haml' + - 'app/views/profiles/preferences/show.html.haml' + - 'app/views/profiles/show.html.haml' + - 'app/views/profiles/two_factor_auths/_codes.html.haml' + - 'app/views/profiles/two_factor_auths/codes.html.haml' + - 'app/views/profiles/two_factor_auths/create.html.haml' + - 'app/views/profiles/two_factor_auths/show.html.haml' + - 'app/views/projects/_bitbucket_import_modal.html.haml' + - 'app/views/projects/_customize_workflow.html.haml' + - 'app/views/projects/_deletion_failed.html.haml' + - 'app/views/projects/_fork_suggestion.html.haml' + - 'app/views/projects/_gitlab_import_modal.html.haml' + - 'app/views/projects/_home_panel.html.haml' + - 'app/views/projects/_import_project_pane.html.haml' + - 'app/views/projects/_issuable_by_email.html.haml' + - 'app/views/projects/_md_preview.html.haml' + - 'app/views/projects/_new_project_fields.html.haml' + - 'app/views/projects/_readme.html.haml' + - 'app/views/projects/artifacts/_tree_file.html.haml' + - 'app/views/projects/artifacts/browse.html.haml' + - 'app/views/projects/blame/_age_map_legend.html.haml' + - 'app/views/projects/blame/show.html.haml' + - 'app/views/projects/blob/_editor.html.haml' + - 'app/views/projects/blob/_header_content.html.haml' + - 'app/views/projects/blob/_new_dir.html.haml' + - 'app/views/projects/blob/_remove.html.haml' + - 'app/views/projects/blob/_render_error.html.haml' + - 'app/views/projects/blob/_template_selectors.html.haml' + - 'app/views/projects/blob/_upload.html.haml' + - 'app/views/projects/blob/edit.html.haml' + - 'app/views/projects/blob/new.html.haml' + - 'app/views/projects/blob/preview.html.haml' + - 'app/views/projects/blob/viewers/_empty.html.haml' + - 'app/views/projects/blob/viewers/_stl.html.haml' + - 'app/views/projects/branches/_branch.html.haml' + - 'app/views/projects/branches/_commit.html.haml' + - 'app/views/projects/branches/_delete_protected_modal.html.haml' + - 'app/views/projects/branches/new.html.haml' + - 'app/views/projects/ci/builds/_build.html.haml' + - 'app/views/projects/ci/lints/_create.html.haml' + - 'app/views/projects/commit/_change.html.haml' + - 'app/views/projects/commits/_commit.html.haml' + - 'app/views/projects/commits/_inline_commit.html.haml' + - 'app/views/projects/compare/_form.html.haml' + - 'app/views/projects/compare/index.html.haml' + - 'app/views/projects/cycle_analytics/_empty_stage.html.haml' + - 'app/views/projects/cycle_analytics/_no_access.html.haml' + - 'app/views/projects/cycle_analytics/_overview.html.haml' + - 'app/views/projects/cycle_analytics/show.html.haml' + - 'app/views/projects/deploy_keys/_form.html.haml' + - 'app/views/projects/deploy_keys/_index.html.haml' + - 'app/views/projects/deploy_keys/edit.html.haml' + - 'app/views/projects/deploy_tokens/_revoke_modal.html.haml' + - 'app/views/projects/deploy_tokens/_table.html.haml' + - 'app/views/projects/deployments/_deployment.html.haml' + - 'app/views/projects/diffs/_file_header.html.haml' + - 'app/views/projects/diffs/_replaced_image_diff.html.haml' + - 'app/views/projects/diffs/_stats.html.haml' + - 'app/views/projects/empty.html.haml' + - 'app/views/projects/environments/show.html.haml' + - 'app/views/projects/forks/error.html.haml' + - 'app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml' + - 'app/views/projects/graphs/charts.html.haml' + - 'app/views/projects/hook_logs/_index.html.haml' + - 'app/views/projects/hook_logs/show.html.haml' + - 'app/views/projects/hooks/_index.html.haml' + - 'app/views/projects/hooks/edit.html.haml' + - 'app/views/projects/imports/new.html.haml' + - 'app/views/projects/imports/show.html.haml' + - 'app/views/projects/issues/_issue.html.haml' + - 'app/views/projects/issues/_new_branch.html.haml' + - 'app/views/projects/issues/import_csv/_modal.html.haml' + - 'app/views/projects/issues/show.html.haml' + - 'app/views/projects/jobs/_header.html.haml' + - 'app/views/projects/jobs/_table.html.haml' + - 'app/views/projects/jobs/index.html.haml' + - 'app/views/projects/labels/edit.html.haml' + - 'app/views/projects/labels/new.html.haml' + - 'app/views/projects/mattermosts/_no_teams.html.haml' + - 'app/views/projects/mattermosts/_team_selection.html.haml' + - 'app/views/projects/mattermosts/new.html.haml' + - 'app/views/projects/merge_requests/_commits.html.haml' + - 'app/views/projects/merge_requests/_discussion.html.haml' + - 'app/views/projects/merge_requests/_how_to_merge.html.haml' + - 'app/views/projects/merge_requests/_merge_request.html.haml' + - 'app/views/projects/merge_requests/_mr_title.html.haml' + - 'app/views/projects/merge_requests/conflicts/_commit_stats.html.haml' + - 'app/views/projects/merge_requests/conflicts/_file_actions.html.haml' + - 'app/views/projects/merge_requests/conflicts/_submit_form.html.haml' + - 'app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml' + - 'app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml' + - 'app/views/projects/merge_requests/conflicts/show.html.haml' + - 'app/views/projects/merge_requests/creations/_diffs.html.haml' + - 'app/views/projects/merge_requests/creations/_new_compare.html.haml' + - 'app/views/projects/merge_requests/creations/_new_submit.html.haml' + - 'app/views/projects/merge_requests/diffs/_different_base.html.haml' + - 'app/views/projects/merge_requests/diffs/_diffs.html.haml' + - 'app/views/projects/merge_requests/diffs/_version_controls.html.haml' + - 'app/views/projects/merge_requests/invalid.html.haml' + - 'app/views/projects/merge_requests/widget/open/_error.html.haml' + - 'app/views/projects/mirrors/_regenerate_public_ssh_key_confirm_modal.html.haml' + - 'app/views/projects/mirrors/_ssh_host_keys.html.haml' + - 'app/views/projects/new.html.haml' + - 'app/views/projects/no_repo.html.haml' + - 'app/views/projects/pages/_access.html.haml' + - 'app/views/projects/pages/_destroy.haml' + - 'app/views/projects/pages/_https_only.html.haml' + - 'app/views/projects/pages/_list.html.haml' + - 'app/views/projects/pages/_no_domains.html.haml' + - 'app/views/projects/pages/_use.html.haml' + - 'app/views/projects/pages/show.html.haml' + - 'app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml' + - 'app/views/projects/pipelines/_info.html.haml' + - 'app/views/projects/pipelines/charts/_pipelines.haml' + - 'app/views/projects/protected_branches/shared/_branches_list.html.haml' + - 'app/views/projects/protected_branches/shared/_create_protected_branch.html.haml' + - 'app/views/projects/protected_branches/shared/_dropdown.html.haml' + - 'app/views/projects/protected_branches/shared/_index.html.haml' + - 'app/views/projects/protected_branches/shared/_matching_branch.html.haml' + - 'app/views/projects/protected_branches/shared/_protected_branch.html.haml' + - 'app/views/projects/protected_branches/show.html.haml' + - 'app/views/projects/protected_tags/shared/_create_protected_tag.html.haml' + - 'app/views/projects/protected_tags/shared/_dropdown.html.haml' + - 'app/views/projects/protected_tags/shared/_index.html.haml' + - 'app/views/projects/protected_tags/shared/_matching_tag.html.haml' + - 'app/views/projects/protected_tags/shared/_protected_tag.html.haml' + - 'app/views/projects/protected_tags/shared/_tags_list.html.haml' + - 'app/views/projects/protected_tags/show.html.haml' + - 'app/views/projects/registry/repositories/_tag.html.haml' + - 'app/views/projects/repositories/_feed.html.haml' + - 'app/views/projects/runners/_shared_runners.html.haml' + - 'app/views/projects/runners/edit.html.haml' + - 'app/views/projects/services/_form.html.haml' + - 'app/views/projects/services/_index.html.haml' + - 'app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml' + - 'app/views/projects/services/mattermost_slash_commands/_help.html.haml' + - 'app/views/projects/services/prometheus/_metrics.html.haml' + - 'app/views/projects/services/slack_slash_commands/_help.html.haml' + - 'app/views/projects/settings/ci_cd/_badge.html.haml' + - 'app/views/projects/settings/ci_cd/_form.html.haml' + - 'app/views/projects/stage/_stage.html.haml' + - 'app/views/projects/tags/index.html.haml' + - 'app/views/projects/tags/new.html.haml' + - 'app/views/projects/tags/releases/edit.html.haml' + - 'app/views/projects/tree/_tree_row.html.haml' + - 'app/views/projects/tree/_truncated_notice_tree_row.html.haml' + - 'app/views/projects/triggers/_content.html.haml' + - 'app/views/projects/triggers/_form.html.haml' + - 'app/views/projects/triggers/_index.html.haml' + - 'app/views/projects/triggers/_trigger.html.haml' + - 'app/views/projects/triggers/edit.html.haml' + - 'app/views/projects/wikis/_new.html.haml' + - 'app/views/projects/wikis/_pages_wiki_page.html.haml' + - 'app/views/projects/wikis/edit.html.haml' + - 'app/views/projects/wikis/history.html.haml' + - 'app/views/repository_check_mailer/notify.html.haml' + - 'app/views/search/_form.html.haml' + - 'app/views/search/results/_issue.html.haml' + - 'app/views/search/results/_note.html.haml' + - 'app/views/search/results/_snippet_blob.html.haml' + - 'app/views/search/results/_snippet_title.html.haml' + - 'app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml' + - 'app/views/shared/_commit_message_container.html.haml' + - 'app/views/shared/_confirm_modal.html.haml' + - 'app/views/shared/_delete_label_modal.html.haml' + - 'app/views/shared/_group_form.html.haml' + - 'app/views/shared/_group_tips.html.haml' + - 'app/views/shared/_milestone_expired.html.haml' + - 'app/views/shared/_no_password.html.haml' + - 'app/views/shared/_no_ssh.html.haml' + - 'app/views/shared/_outdated_browser.html.haml' + - 'app/views/shared/_personal_access_tokens_created_container.html.haml' + - 'app/views/shared/_personal_access_tokens_table.html.haml' + - 'app/views/shared/_ping_consent.html.haml' + - 'app/views/shared/_project_limit.html.haml' + - 'app/views/shared/_service_settings.html.haml' + - 'app/views/shared/boards/components/_board.html.haml' + - 'app/views/shared/boards/components/_sidebar.html.haml' + - 'app/views/shared/boards/components/sidebar/_due_date.html.haml' + - 'app/views/shared/boards/components/sidebar/_labels.html.haml' + - 'app/views/shared/boards/components/sidebar/_milestone.html.haml' + - 'app/views/shared/empty_states/_priority_labels.html.haml' + - 'app/views/shared/hook_logs/_content.html.haml' + - 'app/views/shared/issuable/_assignees.html.haml' + - 'app/views/shared/issuable/_board_create_list_dropdown.html.haml' + - 'app/views/shared/issuable/_bulk_update_sidebar.html.haml' + - 'app/views/shared/issuable/_close_reopen_report_toggle.html.haml' + - 'app/views/shared/issuable/_form.html.haml' + - 'app/views/shared/issuable/_search_bar.html.haml' + - 'app/views/shared/issuable/_sidebar.html.haml' + - 'app/views/shared/issuable/form/_default_templates.html.haml' + - 'app/views/shared/issuable/form/_issue_assignee.html.haml' + - 'app/views/shared/issuable/form/_template_selector.html.haml' + - 'app/views/shared/issuable/form/_title.html.haml' + - 'app/views/shared/labels/_form.html.haml' + - 'app/views/shared/members/_member.html.haml' + - 'app/views/shared/milestones/_form_dates.html.haml' + - 'app/views/shared/milestones/_issuable.html.haml' + - 'app/views/shared/milestones/_milestone.html.haml' + - 'app/views/shared/milestones/_sidebar.html.haml' + - 'app/views/shared/milestones/_top.html.haml' + - 'app/views/shared/notes/_hints.html.haml' + - 'app/views/shared/notes/_note.html.haml' + - 'app/views/shared/notifications/_button.html.haml' + - 'app/views/shared/notifications/_custom_notifications.html.haml' + - 'app/views/shared/notifications/_new_button.html.haml' + - 'app/views/shared/notifications/_notification_dropdown.html.haml' + - 'app/views/shared/plugins/_index.html.haml' + - 'app/views/shared/projects/_dropdown.html.haml' + - 'app/views/shared/projects/_list.html.haml' + - 'app/views/shared/projects/_project.html.haml' + - 'app/views/shared/runners/_runner_description.html.haml' + - 'app/views/shared/runners/show.html.haml' + - 'app/views/shared/snippets/_embed.html.haml' + - 'app/views/shared/snippets/_header.html.haml' + - 'app/views/shared/snippets/_snippet.html.haml' + - 'app/views/shared/tokens/_scopes_list.html.haml' + - 'app/views/shared/web_hooks/_form.html.haml' + - 'app/views/shared/web_hooks/_test_button.html.haml' + - 'app/views/u2f/_authenticate.html.haml' + - 'app/views/u2f/_register.html.haml' + - 'app/views/users/_deletion_guidance.html.haml' + - 'ee/app/views/admin/_namespace_plan_info.html.haml' + - 'ee/app/views/admin/application_settings/_elasticsearch_form.html.haml' + - 'ee/app/views/admin/application_settings/_slack.html.haml' + - 'ee/app/views/admin/application_settings/_snowplow.html.haml' + - 'ee/app/views/admin/application_settings/_templates.html.haml' + - 'ee/app/views/admin/audit_logs/index.html.haml' + - 'ee/app/views/admin/dashboard/stats.html.haml' + - 'ee/app/views/admin/emails/show.html.haml' + - 'ee/app/views/admin/geo/nodes/edit.html.haml' + - 'ee/app/views/admin/geo/nodes/new.html.haml' + - 'ee/app/views/admin/geo/projects/_registry_failed.html.haml' + - 'ee/app/views/admin/geo/projects/_registry_never.html.haml' + - 'ee/app/views/admin/licenses/_breakdown.html.haml' + - 'ee/app/views/admin/licenses/_upload_trial_license.html.haml' + - 'ee/app/views/admin/licenses/missing.html.haml' + - 'ee/app/views/admin/licenses/new.html.haml' + - 'ee/app/views/admin/licenses/show.html.haml' + - 'ee/app/views/admin/monitoring/ee/_nav.html.haml' + - 'ee/app/views/admin/projects/_shared_runner_status.html.haml' + - 'ee/app/views/admin/push_rules/show.html.haml' + - 'ee/app/views/admin/users/_limits.html.haml' + - 'ee/app/views/admin/users/_user_detail_note.html.haml' + - 'ee/app/views/dashboard/projects/_blank_state_ee_trial.html.haml' + - 'ee/app/views/errors/kerberos_denied.html.haml' + - 'ee/app/views/groups/analytics/show.html.haml' + - 'ee/app/views/groups/audit_events/index.html.haml' + - 'ee/app/views/groups/ee/_settings_nav.html.haml' + - 'ee/app/views/groups/epics/_epic.html.haml' + - 'ee/app/views/groups/group_members/_ldap_sync.html.haml' + - 'ee/app/views/groups/group_members/_sync_button.html.haml' + - 'ee/app/views/groups/hooks/_project_hook.html.haml' + - 'ee/app/views/groups/hooks/index.html.haml' + - 'ee/app/views/groups/ldap_group_links/index.html.haml' + - 'ee/app/views/groups/pipeline_quota/index.html.haml' + - 'ee/app/views/jira_connect/subscriptions/index.html.haml' + - 'ee/app/views/layouts/jira_connect.html.haml' + - 'ee/app/views/layouts/nav/ee/_epic_link.html.haml' + - 'ee/app/views/layouts/nav/ee/admin/_new_monitoring_sidebar.html.haml' + - 'ee/app/views/layouts/service_desk.html.haml' + - 'ee/app/views/ldap_group_links/_form.html.haml' + - 'ee/app/views/ldap_group_links/_ldap_group_link.html.haml' + - 'ee/app/views/ldap_group_links/_ldap_group_links.html.haml' + - 'ee/app/views/ldap_group_links/_ldap_group_links_show.html.haml' + - 'ee/app/views/ldap_group_links/_ldap_group_links_synchronizations.html.haml' + - 'ee/app/views/namespaces/_shared_runner_status.html.haml' + - 'ee/app/views/namespaces/_shared_runners_minutes_setting.html.haml' + - 'ee/app/views/namespaces/pipelines_quota/_extra_shared_runners_minutes_quota.html.haml' + - 'ee/app/views/namespaces/pipelines_quota/_list.haml' + - 'ee/app/views/notify/approved_merge_request_email.html.haml' + - 'ee/app/views/notify/epic_status_changed_email.html.haml' + - 'ee/app/views/notify/issues_csv_email.html.haml' + - 'ee/app/views/notify/new_review_email.html.haml' + - 'ee/app/views/notify/prometheus_alert_fired_email.html.haml' + - 'ee/app/views/notify/send_admin_notification.html.haml' + - 'ee/app/views/notify/send_unsubscribed_notification.html.haml' + - 'ee/app/views/notify/unapproved_merge_request_email.html.haml' + - 'ee/app/views/oauth/geo_auth/error.html.haml' + - 'ee/app/views/profiles/pipeline_quota/index.haml' + - 'ee/app/views/projects/audit_events/index.html.haml' + - 'ee/app/views/projects/blob/_owners.html.haml' + - 'ee/app/views/projects/commits/_mirror_status.html.haml' + - 'ee/app/views/projects/feature_flags/_configure_feature_flags_modal.html.haml' + - 'ee/app/views/projects/issues/_issue_weight.html.haml' + - 'ee/app/views/projects/issues/_related_issues.html.haml' + - 'ee/app/views/projects/issues/export_csv/_modal.html.haml' + - 'ee/app/views/projects/jobs/_shared_runner_limit_warning.html.haml' + - 'ee/app/views/projects/merge_requests/_approvals_count.html.haml' + - 'ee/app/views/projects/merge_requests/widget/open/_geo.html.haml' + - 'ee/app/views/projects/mirrors/_mirrored_repositories_count.html.haml' + - 'ee/app/views/projects/protected_branches/ee/_create_protected_branch.html.haml' + - 'ee/app/views/projects/protected_branches/ee/_dropdown.html.haml' + - 'ee/app/views/projects/protected_branches/ee/_fallback_update_protected_branch.html.haml' + - 'ee/app/views/projects/protected_tags/_protected_tag_extra_create_access_levels.haml' + - 'ee/app/views/projects/protected_tags/ee/_create_protected_tag.html.haml' + - 'ee/app/views/projects/push_rules/_index.html.haml' + - 'ee/app/views/projects/services/gitlab_slack_application/_help.html.haml' + - 'ee/app/views/projects/services/gitlab_slack_application/_slack_integration_form.html.haml' + - 'ee/app/views/projects/services/prometheus/_metrics.html.haml' + - 'ee/app/views/projects/settings/slacks/edit.html.haml' + - 'ee/app/views/shared/_additional_email_text.html.haml' + - 'ee/app/views/shared/_geo_info_modal.html.haml' + - 'ee/app/views/shared/_mirror_update_button.html.haml' + - 'ee/app/views/shared/_shared_runners_minutes_limit.html.haml' + - 'ee/app/views/shared/audit_events/_event_table.html.haml' + - 'ee/app/views/shared/boards/components/_list_weight.html.haml' + - 'ee/app/views/shared/boards/components/sidebar/_epic.html.haml' + - 'ee/app/views/shared/ee/_import_form.html.haml' + - 'ee/app/views/shared/epic/_search_bar.html.haml' + - 'ee/app/views/shared/issuable/_approvals.html.haml' + - 'ee/app/views/shared/issuable/_board_create_list_dropdown.html.haml' + - 'ee/app/views/shared/issuable/_filter_weight.html.haml' + - 'ee/app/views/shared/issuable/_sidebar_item_epic.haml' + - 'ee/app/views/shared/members/ee/_ldap_tag.html.haml' + - 'ee/app/views/shared/members/ee/_override_member_buttons.html.haml' + - 'ee/app/views/shared/members/ee/_sso_badge.html.haml' + - 'ee/app/views/shared/milestones/_burndown.html.haml' + - 'ee/app/views/shared/milestones/_weight.html.haml' + - 'ee/app/views/shared/promotions/_promote_audit_events.html.haml' + - 'ee/app/views/shared/promotions/_promote_burndown_charts.html.haml' + - 'ee/app/views/shared/promotions/_promote_csv_export.html.haml' + - 'ee/app/views/shared/promotions/_promote_issue_weights.html.haml' + - 'ee/app/views/shared/promotions/_promote_repository_features.html.haml' + - 'ee/app/views/shared/promotions/_promote_servicedesk.html.haml' + - 'ee/app/views/shared/push_rules/_form.html.haml' + - 'ee/app/views/unsubscribes/show.html.haml' + - 'ee/app/views/admin/users/_auditor_access_level_radio.html.haml' + - 'ee/app/views/admin/users/_auditor_user_badge.html.haml' + - 'ee/app/views/projects/protected_branches/_update_protected_branch.html.haml' + - 'ee/app/views/analytics/cycle_analytics/show.html.haml' diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 3f4830156cb..373aea97570 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -1.53.0 +1.57.0 diff --git a/app/assets/javascripts/analytics/cycle_analytics/mixins/filter_mixins.js b/app/assets/javascripts/analytics/cycle_analytics/mixins/filter_mixins.js new file mode 100644 index 00000000000..ff8b4c56321 --- /dev/null +++ b/app/assets/javascripts/analytics/cycle_analytics/mixins/filter_mixins.js @@ -0,0 +1 @@ +export default {}; diff --git a/app/assets/javascripts/badges/components/badge.vue b/app/assets/javascripts/badges/components/badge.vue index ae2916e3a3b..eb720f5380b 100644 --- a/app/assets/javascripts/badges/components/badge.vue +++ b/app/assets/javascripts/badges/components/badge.vue @@ -3,6 +3,8 @@ import Icon from '~/vue_shared/components/icon.vue'; import { GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui'; export default { + // name: 'Badge' is a false positive: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/25 + // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings name: 'Badge', components: { Icon, diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue index 787ff110bf8..03a8a92575e 100644 --- a/app/assets/javascripts/boards/components/board_list.vue +++ b/app/assets/javascripts/boards/components/board_list.vue @@ -1,4 +1,5 @@ <script> +/* eslint-disable @gitlab/vue-i18n/no-bare-strings */ import Sortable from 'sortablejs'; import { GlLoadingIcon } from '@gitlab/ui'; import boardNewIssue from './board_new_issue.vue'; diff --git a/app/assets/javascripts/boards/components/modal/header.vue b/app/assets/javascripts/boards/components/modal/header.vue index 7a696035dc8..8cd4840d3d6 100644 --- a/app/assets/javascripts/boards/components/modal/header.vue +++ b/app/assets/javascripts/boards/components/modal/header.vue @@ -1,4 +1,5 @@ <script> +/* eslint-disable @gitlab/vue-i18n/no-bare-strings */ import { __ } from '~/locale'; import ModalFilters from './filters'; import ModalTabs from './tabs.vue'; diff --git a/app/assets/javascripts/boards/components/modal/tabs.vue b/app/assets/javascripts/boards/components/modal/tabs.vue index 2d2920e312e..7430fc96654 100644 --- a/app/assets/javascripts/boards/components/modal/tabs.vue +++ b/app/assets/javascripts/boards/components/modal/tabs.vue @@ -1,4 +1,5 @@ <script> +/* eslint-disable @gitlab/vue-i18n/no-bare-strings */ import ModalStore from '../../stores/modal_store'; import modalMixin from '../../mixins/modal_mixins'; diff --git a/app/assets/javascripts/clusters/components/application_row.vue b/app/assets/javascripts/clusters/components/application_row.vue index cd2121db3b2..64364092016 100644 --- a/app/assets/javascripts/clusters/components/application_row.vue +++ b/app/assets/javascripts/clusters/components/application_row.vue @@ -1,5 +1,6 @@ <script> /* eslint-disable vue/require-default-prop */ +/* eslint-disable @gitlab/vue-i18n/no-bare-strings */ import { GlLink, GlModalDirective } from '@gitlab/ui'; import TimeagoTooltip from '../../vue_shared/components/time_ago_tooltip.vue'; import { s__, __, sprintf } from '~/locale'; diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js index d4b994d4922..671405602cc 100644 --- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js +++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js @@ -1,7 +1,10 @@ import $ from 'jquery'; import Vue from 'vue'; import Cookies from 'js-cookie'; +import { GlEmptyState } from '@gitlab/ui'; +import filterMixins from 'ee_else_ce/analytics/cycle_analytics/mixins/filter_mixins'; import Flash from '../flash'; +import { __ } from '~/locale'; import Translate from '../vue_shared/translate'; import banner from './components/banner.vue'; import stageCodeComponent from './components/stage_code_component.vue'; @@ -11,7 +14,6 @@ import stageStagingComponent from './components/stage_staging_component.vue'; import stageTestComponent from './components/stage_test_component.vue'; import CycleAnalyticsService from './cycle_analytics_service'; import CycleAnalyticsStore from './cycle_analytics_store'; -import { __ } from '~/locale'; Vue.use(Translate); @@ -24,6 +26,7 @@ export default () => { el: '#cycle-analytics', name: 'CycleAnalytics', components: { + GlEmptyState, banner, 'stage-issue-component': stageComponent, 'stage-plan-component': stageComponent, @@ -32,12 +35,15 @@ export default () => { 'stage-review-component': stageReviewComponent, 'stage-staging-component': stageStagingComponent, 'stage-production-component': stageComponent, + GroupsDropdownFilter: () => + import('ee_component/analytics/shared/components/groups_dropdown_filter.vue'), + ProjectsDropdownFilter: () => + import('ee_component/analytics/shared/components/projects_dropdown_filter.vue'), + DateRangeDropdown: () => + import('ee_component/analytics/shared/components/date_range_dropdown.vue'), }, + mixins: [filterMixins], data() { - const cycleAnalyticsService = new CycleAnalyticsService({ - requestPath: cycleAnalyticsEl.dataset.requestPath, - }); - return { store: CycleAnalyticsStore, state: CycleAnalyticsStore.state, @@ -47,7 +53,7 @@ export default () => { hasError: false, startDate: 30, isOverviewDialogDismissed: Cookies.get(OVERVIEW_DIALOG_COOKIE), - service: cycleAnalyticsService, + service: this.createCycleAnalyticsService(cycleAnalyticsEl.dataset.requestPath), }; }, computed: { @@ -124,6 +130,7 @@ export default () => { .fetchStageData({ stage, startDate: this.startDate, + projectIds: this.selectedProjectIds, }) .then(response => { this.isEmptyStage = !response.events.length; @@ -139,6 +146,11 @@ export default () => { this.isOverviewDialogDismissed = true; Cookies.set(OVERVIEW_DIALOG_COOKIE, '1', { expires: 365 }); }, + createCycleAnalyticsService(requestPath) { + return new CycleAnalyticsService({ + requestPath, + }); + }, }, }); }; diff --git a/app/assets/javascripts/diffs/components/commit_item.vue b/app/assets/javascripts/diffs/components/commit_item.vue index 58d5b658b17..c82b4a7abc6 100644 --- a/app/assets/javascripts/diffs/components/commit_item.vue +++ b/app/assets/javascripts/diffs/components/commit_item.vue @@ -49,8 +49,8 @@ export default { return this.author.id ? this.author.id : ''; }, authorUrl() { - // TODO: when the vue i18n rules are merged need to disable @gitlab/i18n/no-non-i18n-strings // name: 'mailto:' is a false positive: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26#possible-false-positives + // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings return this.author.web_url || `mailto:${this.commit.author_email}`; }, authorAvatar() { diff --git a/app/assets/javascripts/diffs/components/compare_versions.vue b/app/assets/javascripts/diffs/components/compare_versions.vue index 363ebad1594..2e57a47f2f7 100644 --- a/app/assets/javascripts/diffs/components/compare_versions.vue +++ b/app/assets/javascripts/diffs/components/compare_versions.vue @@ -1,4 +1,5 @@ <script> +/* eslint-disable @gitlab/vue-i18n/no-bare-strings */ import { mapActions, mapGetters, mapState } from 'vuex'; import { GlTooltipDirective, GlLink, GlButton } from '@gitlab/ui'; import { __ } from '~/locale'; diff --git a/app/assets/javascripts/diffs/components/hidden_files_warning.vue b/app/assets/javascripts/diffs/components/hidden_files_warning.vue index 119e139de21..035c2b3b11e 100644 --- a/app/assets/javascripts/diffs/components/hidden_files_warning.vue +++ b/app/assets/javascripts/diffs/components/hidden_files_warning.vue @@ -1,4 +1,5 @@ <script> +/* eslint-disable @gitlab/vue-i18n/no-bare-strings */ export default { props: { total: { diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue index 813045cb5e4..95e1e8af9b3 100644 --- a/app/assets/javascripts/environments/components/environment_item.vue +++ b/app/assets/javascripts/environments/components/environment_item.vue @@ -1,4 +1,5 @@ <script> +/* eslint-disable @gitlab/vue-i18n/no-bare-strings */ import { __, sprintf } from '~/locale'; import Timeago from 'timeago.js'; import _ from 'underscore'; @@ -286,9 +287,9 @@ export default { * @returns {Boolean|Undefined} */ isLastDeployment() { - // TODO: when the vue i18n rules are merged need to disable @gitlab/i18n/no-non-i18n-strings // name: 'last?' is a false positive: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26#possible-false-positives // Vue i18n ESLint rules issue: https://gitlab.com/gitlab-org/gitlab-ce/issues/63560 + // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings return this.model && this.model.last_deployment && this.model.last_deployment['last?']; }, diff --git a/app/assets/javascripts/environments/components/stop_environment_modal.vue b/app/assets/javascripts/environments/components/stop_environment_modal.vue index c78d86e9b97..2cc3412e075 100644 --- a/app/assets/javascripts/environments/components/stop_environment_modal.vue +++ b/app/assets/javascripts/environments/components/stop_environment_modal.vue @@ -1,4 +1,5 @@ <script> +/* eslint-disable @gitlab/vue-i18n/no-bare-strings */ import { GlTooltipDirective } from '@gitlab/ui'; import GlModal from '~/vue_shared/components/gl_modal.vue'; import { s__, sprintf } from '~/locale'; diff --git a/app/assets/javascripts/error_tracking_settings/components/error_tracking_form.vue b/app/assets/javascripts/error_tracking_settings/components/error_tracking_form.vue index ef1d1e49320..a734e8527dd 100644 --- a/app/assets/javascripts/error_tracking_settings/components/error_tracking_form.vue +++ b/app/assets/javascripts/error_tracking_settings/components/error_tracking_form.vue @@ -36,12 +36,14 @@ export default { <label class="label-bold" for="error-tracking-api-host">{{ __('Sentry API URL') }}</label> <div class="row"> <div class="col-8 col-md-9 gl-pr-0"> + <!-- eslint-disable @gitlab/vue-i18n/no-bare-attribute-strings --> <gl-form-input id="error-tracking-api-host" :value="apiHost" placeholder="https://mysentryserver.com" @input="$emit('update-api-host', $event)" /> + <!-- eslint-enable @gitlab/vue-i18n/no-bare-attribute-strings --> </div> </div> <p class="form-text text-muted"> diff --git a/app/assets/javascripts/ide/components/branches/item.vue b/app/assets/javascripts/ide/components/branches/item.vue index 4dff3f7e755..5c048749060 100644 --- a/app/assets/javascripts/ide/components/branches/item.vue +++ b/app/assets/javascripts/ide/components/branches/item.vue @@ -1,4 +1,5 @@ <script> +/* eslint-disable @gitlab/vue-i18n/no-bare-strings */ import Icon from '~/vue_shared/components/icon.vue'; import Timeago from '~/vue_shared/components/time_ago_tooltip.vue'; import router from '../../ide_router'; diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue b/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue index c8fbc3cb9f1..302adccd759 100644 --- a/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue +++ b/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue @@ -45,6 +45,8 @@ export default { }, computed: { iconName() { + // name: '-solid' is a false positive: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26#possible-false-positives + // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings const suffix = this.stagedList ? '-solid' : ''; return `${getCommitIconMap(this.file).icon}${suffix}`; diff --git a/app/assets/javascripts/ide/components/ide_status_bar.vue b/app/assets/javascripts/ide/components/ide_status_bar.vue index 206b8341aad..326589fa50f 100644 --- a/app/assets/javascripts/ide/components/ide_status_bar.vue +++ b/app/assets/javascripts/ide/components/ide_status_bar.vue @@ -1,4 +1,5 @@ <script> +/* eslint-disable @gitlab/vue-i18n/no-bare-strings */ import { mapActions, mapState, mapGetters } from 'vuex'; import IdeStatusList from 'ee_else_ce/ide/components/ide_status_list.vue'; import icon from '~/vue_shared/components/icon.vue'; diff --git a/app/assets/javascripts/issuable_suggestions/components/item.vue b/app/assets/javascripts/issuable_suggestions/components/item.vue index 9a16b486bf5..7629e04684c 100644 --- a/app/assets/javascripts/issuable_suggestions/components/item.vue +++ b/app/assets/javascripts/issuable_suggestions/components/item.vue @@ -1,4 +1,5 @@ <script> +/* eslint-disable @gitlab/vue-i18n/no-bare-strings */ import _ from 'underscore'; import { GlLink, GlTooltip, GlTooltipDirective } from '@gitlab/ui'; import { __ } from '~/locale'; diff --git a/app/assets/javascripts/issue_show/components/edit_actions.vue b/app/assets/javascripts/issue_show/components/edit_actions.vue index b2f9296c68b..eb51a074f84 100644 --- a/app/assets/javascripts/issue_show/components/edit_actions.vue +++ b/app/assets/javascripts/issue_show/components/edit_actions.vue @@ -1,4 +1,5 @@ <script> +/* eslint-disable @gitlab/vue-i18n/no-bare-strings */ import { __, sprintf } from '~/locale'; import updateMixin from '../mixins/update'; import eventHub from '../event_hub'; diff --git a/app/assets/javascripts/issue_show/components/edited.vue b/app/assets/javascripts/issue_show/components/edited.vue index 14ad8d3b7c9..2c92324d292 100644 --- a/app/assets/javascripts/issue_show/components/edited.vue +++ b/app/assets/javascripts/issue_show/components/edited.vue @@ -1,4 +1,5 @@ <script> +/* eslint-disable @gitlab/vue-i18n/no-bare-strings */ import timeAgoTooltip from '../../vue_shared/components/time_ago_tooltip.vue'; export default { diff --git a/app/assets/javascripts/issue_show/components/fields/description_template.vue b/app/assets/javascripts/issue_show/components/fields/description_template.vue index 6f955928d8e..bc3c81d479e 100644 --- a/app/assets/javascripts/issue_show/components/fields/description_template.vue +++ b/app/assets/javascripts/issue_show/components/fields/description_template.vue @@ -1,4 +1,5 @@ <script> +/* eslint-disable @gitlab/vue-i18n/no-bare-strings */ import $ from 'jquery'; import IssuableTemplateSelectors from '../../../templates/issuable_template_selectors'; diff --git a/app/assets/javascripts/jobs/components/commit_block.vue b/app/assets/javascripts/jobs/components/commit_block.vue index b651a6e4bfb..9fac880c5f8 100644 --- a/app/assets/javascripts/jobs/components/commit_block.vue +++ b/app/assets/javascripts/jobs/components/commit_block.vue @@ -1,4 +1,5 @@ <script> +/* eslint-disable @gitlab/vue-i18n/no-bare-strings */ import { GlLink } from '@gitlab/ui'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; diff --git a/app/assets/javascripts/jobs/components/empty_state.vue b/app/assets/javascripts/jobs/components/empty_state.vue index 275ed80146e..e2bc413e3ce 100644 --- a/app/assets/javascripts/jobs/components/empty_state.vue +++ b/app/assets/javascripts/jobs/components/empty_state.vue @@ -81,7 +81,7 @@ export default { :variables-settings-url="variablesSettingsUrl" /> <div class="text-content"> - <div v-if="action" class="text-center"> + <div v-if="action && !shouldRenderManualVariables" class="text-center"> <gl-link :href="action.path" :data-method="action.method" diff --git a/app/assets/javascripts/jobs/components/job_log.vue b/app/assets/javascripts/jobs/components/job_log.vue index d611b370ab9..a3fbe9338ee 100644 --- a/app/assets/javascripts/jobs/components/job_log.vue +++ b/app/assets/javascripts/jobs/components/job_log.vue @@ -48,9 +48,14 @@ export default { } }, removeEventListener() { - this.$el - .querySelectorAll('.js-section-start') - .forEach(el => el.removeEventListener('click', this.handleSectionClick)); + this.$el.querySelectorAll('.js-section-start').forEach(el => { + const titleSection = el.nextSibling; + titleSection.removeEventListener( + 'click', + this.handleHeaderClick.bind(this, el, el.dataset.section), + ); + el.removeEventListener('click', this.handleSectionClick); + }); }, /** * The collapsible rows are sent in HTML from the backend @@ -58,9 +63,28 @@ export default { * */ handleCollapsibleRows() { - this.$el - .querySelectorAll('.js-section-start') - .forEach(el => el.addEventListener('click', this.handleSectionClick)); + this.$el.querySelectorAll('.js-section-start').forEach(el => { + const titleSection = el.nextSibling; + titleSection.addEventListener( + 'click', + this.handleHeaderClick.bind(this, el, el.dataset.section), + ); + el.addEventListener('click', this.handleSectionClick); + }); + }, + + handleHeaderClick(arrowElement, section) { + this.updateToggleSection(arrowElement, section); + }, + + updateToggleSection(arrow, section) { + // toggle the arrow class + arrow.classList.toggle('fa-caret-right'); + arrow.classList.toggle('fa-caret-down'); + + // hide the sections + const sibilings = this.$el.querySelectorAll(`.js-s-${section}:not(.js-section-header)`); + sibilings.forEach(row => row.classList.toggle('hidden')); }, /** * On click, we toggle the hidden class of @@ -68,14 +92,7 @@ export default { */ handleSectionClick(evt) { const clickedArrow = evt.currentTarget; - // toggle the arrow class - clickedArrow.classList.toggle('fa-caret-right'); - clickedArrow.classList.toggle('fa-caret-down'); - - const { section } = clickedArrow.dataset; - const sibilings = this.$el.querySelectorAll(`.js-s-${section}:not(.js-section-header)`); - - sibilings.forEach(row => row.classList.toggle('hidden')); + this.updateToggleSection(clickedArrow, clickedArrow.dataset.section); }, }, }; diff --git a/app/assets/javascripts/lib/utils/forms.js b/app/assets/javascripts/lib/utils/forms.js new file mode 100644 index 00000000000..106209a2f3a --- /dev/null +++ b/app/assets/javascripts/lib/utils/forms.js @@ -0,0 +1,12 @@ +export const serializeFormEntries = entries => + entries.reduce((acc, { name, value }) => Object.assign(acc, { [name]: value }), {}); + +export const serializeForm = form => { + const fdata = new FormData(form); + const entries = Array.from(fdata.keys()).map(key => { + const val = fdata.getAll(key); + return { name: key, value: val.length === 1 ? val[0] : val }; + }); + + return serializeFormEntries(entries); +}; diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js index 32fd0990374..1336b6a5461 100644 --- a/app/assets/javascripts/lib/utils/url_utility.js +++ b/app/assets/javascripts/lib/utils/url_utility.js @@ -2,8 +2,8 @@ import { join as joinPaths } from 'path'; // Returns an array containing the value(s) of the // of the key passed as an argument -export function getParameterValues(sParam) { - const sPageURL = decodeURIComponent(window.location.search.substring(1)); +export function getParameterValues(sParam, url = window.location) { + const sPageURL = decodeURIComponent(new URL(url).search.substring(1)); return sPageURL.split('&').reduce((acc, urlParam) => { const sParameterName = urlParam.split('='); diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index 01ed27c49fb..43949d5cc86 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -55,7 +55,7 @@ export default class MilestoneSelect { const $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon'); const $value = $block.find('.value'); const $loading = $block.find('.block-loading').fadeOut(); - selectedMilestoneDefault = showAny ? __('Any Milestone') : null; + selectedMilestoneDefault = showAny ? '' : null; selectedMilestoneDefault = showNo && defaultNo ? __('No Milestone') : selectedMilestoneDefault; selectedMilestone = $dropdown.data('selected') || selectedMilestoneDefault; @@ -74,16 +74,14 @@ export default class MilestoneSelect { if (showAny) { extraOptions.push({ id: null, - // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings - name: 'Any', + name: null, title: __('Any Milestone'), }); } if (showNo) { extraOptions.push({ id: -1, - // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings - name: 'None', + name: __('No Milestone'), title: __('No Milestone'), }); } diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue index 2feb545199b..782e4310f3e 100644 --- a/app/assets/javascripts/monitoring/components/dashboard.vue +++ b/app/assets/javascripts/monitoring/components/dashboard.vue @@ -3,6 +3,7 @@ import { GlButton, GlDropdown, GlDropdownItem, + GlFormGroup, GlModal, GlModalDirective, GlTooltipDirective, @@ -18,8 +19,8 @@ import MonitorSingleStatChart from './charts/single_stat.vue'; import PanelType from './panel_type.vue'; import GraphGroup from './graph_group.vue'; import EmptyState from './empty_state.vue'; -import { sidebarAnimationDuration, timeWindows, timeWindowsKeyNames } from '../constants'; -import { getTimeDiff } from '../utils'; +import { sidebarAnimationDuration, timeWindows } from '../constants'; +import { getTimeDiff, getTimeWindow } from '../utils'; let sidebarMutationObserver; @@ -34,6 +35,7 @@ export default { GlButton, GlDropdown, GlDropdownItem, + GlFormGroup, GlModal, }, directives: { @@ -147,6 +149,7 @@ export default { selectedTimeWindow: '', selectedTimeWindowKey: '', formIsValid: null, + timeWindows: {}, }; }, computed: { @@ -184,17 +187,6 @@ export default { currentDashboard: this.currentDashboard, projectPath: this.projectPath, }); - - this.timeWindows = timeWindows; - this.selectedTimeWindowKey = - _.escape(getParameterValues('time_window')[0]) || timeWindowsKeyNames.eightHours; - - // Set default time window if the selectedTimeWindowKey is bogus - if (!Object.keys(this.timeWindows).includes(this.selectedTimeWindowKey)) { - this.selectedTimeWindowKey = timeWindowsKeyNames.eightHours; - } - - this.selectedTimeWindow = this.timeWindows[this.selectedTimeWindowKey]; }, beforeDestroy() { if (sidebarMutationObserver) { @@ -205,7 +197,20 @@ export default { if (!this.hasMetrics) { this.setGettingStartedEmptyState(); } else { - this.fetchData(getTimeDiff(this.selectedTimeWindow)); + const defaultRange = getTimeDiff(); + const start = getParameterValues('start')[0] || defaultRange.start; + const end = getParameterValues('end')[0] || defaultRange.end; + + const range = { + start, + end, + }; + + this.timeWindows = timeWindows; + this.selectedTimeWindowKey = getTimeWindow(range); + this.selectedTimeWindow = this.timeWindows[this.selectedTimeWindowKey]; + + this.fetchData(range); sidebarMutationObserver = new MutationObserver(this.onSidebarMutation); sidebarMutationObserver.observe(document.querySelector('.layout-page'), { @@ -259,7 +264,8 @@ export default { return this.timeWindows[key] === this.selectedTimeWindow; }, setTimeWindowParameter(key) { - return `?time_window=${key}`; + const { start, end } = getTimeDiff(key); + return `?start=${encodeURIComponent(start)}&end=${encodeURIComponent(end)}`; }, groupHasData(group) { return this.chartsWithData(group.metrics).length > 0; @@ -274,107 +280,136 @@ export default { <template> <div class="prometheus-graphs"> - <div class="gl-p-3 border-bottom bg-gray-light d-flex justify-content-between"> - <div - v-if="environmentsEndpoint" - class="dropdowns d-flex align-items-center justify-content-between" - > - <div v-if="multipleDashboardsEnabled" class="d-flex align-items-center"> - <label class="mb-0">{{ __('Dashboard') }}</label> - <gl-dropdown - class="ml-2 mr-3 js-dashboards-dropdown" - toggle-class="dropdown-menu-toggle" - :text="selectedDashboardText" + <div class="gl-p-3 pb-0 border-bottom bg-gray-light"> + <div class="row"> + <template v-if="environmentsEndpoint"> + <gl-form-group + v-if="multipleDashboardsEnabled" + :label="__('Dashboard')" + label-size="sm" + label-for="monitor-dashboards-dropdown" + class="col-sm-12 col-md-4 col-lg-2" > - <gl-dropdown-item - v-for="dashboard in allDashboards" - :key="dashboard.path" - :active="dashboard.path === currentDashboard" - active-class="is-active" - :href="`?dashboard=${dashboard.path}`" + <gl-dropdown + id="monitor-dashboards-dropdown" + class="mb-0 d-flex js-dashboards-dropdown" + toggle-class="dropdown-menu-toggle" + :text="selectedDashboardText" > - {{ dashboard.display_name || dashboard.path }} - </gl-dropdown-item> - </gl-dropdown> - </div> - <div class="d-flex align-items-center"> - <strong>{{ s__('Metrics|Environment') }}</strong> - <gl-dropdown - class="prepend-left-10 js-environments-dropdown" - toggle-class="dropdown-menu-toggle" - :text="currentEnvironmentName" - :disabled="environments.length === 0" + <gl-dropdown-item + v-for="dashboard in allDashboards" + :key="dashboard.path" + :active="dashboard.path === currentDashboard" + active-class="is-active" + :href="`?dashboard=${dashboard.path}`" + >{{ dashboard.display_name || dashboard.path }}</gl-dropdown-item + > + </gl-dropdown> + </gl-form-group> + + <gl-form-group + :label="s__('Metrics|Environment')" + label-size="sm" + label-for="monitor-environments-dropdown" + class="col-sm-6 col-md-4 col-lg-2" > - <gl-dropdown-item - v-for="environment in environments" - :key="environment.id" - :active="environment.name === currentEnvironmentName" - active-class="is-active" - :href="environment.metrics_path" - >{{ environment.name }}</gl-dropdown-item + <gl-dropdown + id="monitor-environments-dropdown" + class="mb-0 d-flex js-environments-dropdown" + toggle-class="dropdown-menu-toggle" + :text="currentEnvironmentName" + :disabled="environments.length === 0" > - </gl-dropdown> - </div> - <div v-if="!showEmptyState" class="d-flex align-items-center prepend-left-8"> - <strong>{{ s__('Metrics|Show last') }}</strong> - <gl-dropdown - class="prepend-left-10 js-time-window-dropdown" - toggle-class="dropdown-menu-toggle" - :text="selectedTimeWindow" + <gl-dropdown-item + v-for="environment in environments" + :key="environment.id" + :active="environment.name === currentEnvironmentName" + active-class="is-active" + :href="environment.metrics_path" + >{{ environment.name }}</gl-dropdown-item + > + </gl-dropdown> + </gl-form-group> + + <gl-form-group + v-if="!showEmptyState" + :label="s__('Metrics|Show last')" + label-size="sm" + label-for="monitor-time-window-dropdown" + class="col-sm-6 col-md-4 col-lg-2" > - <gl-dropdown-item - v-for="(value, key) in timeWindows" - :key="key" - :active="activeTimeWindow(key)" - :href="setTimeWindowParameter(key)" - active-class="active" - >{{ value }}</gl-dropdown-item + <gl-dropdown + id="monitor-time-window-dropdown" + class="mb-0 d-flex js-time-window-dropdown" + toggle-class="dropdown-menu-toggle" + :text="selectedTimeWindow" > - </gl-dropdown> - </div> - </div> - <div class="d-flex"> - <div v-if="addingMetricsAvailable"> - <gl-button - v-gl-modal="$options.addMetric.modalId" - class="js-add-metric-button text-success border-success" - >{{ $options.addMetric.title }}</gl-button - > - <gl-modal - ref="addMetricModal" - :modal-id="$options.addMetric.modalId" - :title="$options.addMetric.title" - > - <form ref="customMetricsForm" :action="customMetricsPath" method="post"> - <custom-metrics-form-fields - :validate-query-path="validateQueryPath" - form-operation="post" - @formValidation="setFormValidity" - /> - </form> - <div slot="modal-footer"> - <gl-button @click="hideAddMetricModal">{{ __('Cancel') }}</gl-button> - <gl-button - :disabled="!formIsValid" - variant="success" - @click="submitCustomMetricsForm" - >{{ __('Save changes') }}</gl-button + <gl-dropdown-item + v-for="(value, key) in timeWindows" + :key="key" + :active="activeTimeWindow(key)" + :href="setTimeWindowParameter(key)" + active-class="active" + >{{ value }}</gl-dropdown-item > - </div> - </gl-modal> - </div> - <gl-button - v-if="externalDashboardUrl.length" - class="js-external-dashboard-link prepend-left-8" - variant="primary" - :href="externalDashboardUrl" - target="_blank" + </gl-dropdown> + </gl-form-group> + </template> + + <gl-form-group + v-if="addingMetricsAvailable || externalDashboardUrl.length" + label-for="prometheus-graphs-dropdown-buttons" + class="dropdown-buttons col-lg d-lg-flex align-items-end" > - {{ __('View full dashboard') }} - <icon name="external-link" /> - </gl-button> + <div id="prometheus-graphs-dropdown-buttons"> + <gl-button + v-if="addingMetricsAvailable" + v-gl-modal="$options.addMetric.modalId" + class="mr-2 mt-1 js-add-metric-button text-success border-success" + > + {{ $options.addMetric.title }} + </gl-button> + <gl-modal + v-if="addingMetricsAvailable" + ref="addMetricModal" + :modal-id="$options.addMetric.modalId" + :title="$options.addMetric.title" + > + <form ref="customMetricsForm" :action="customMetricsPath" method="post"> + <custom-metrics-form-fields + :validate-query-path="validateQueryPath" + form-operation="post" + @formValidation="setFormValidity" + /> + </form> + <div slot="modal-footer"> + <gl-button @click="hideAddMetricModal">{{ __('Cancel') }}</gl-button> + <gl-button + :disabled="!formIsValid" + variant="success" + @click="submitCustomMetricsForm" + > + {{ __('Save changes') }} + </gl-button> + </div> + </gl-modal> + + <gl-button + v-if="externalDashboardUrl.length" + class="mt-1 js-external-dashboard-link" + variant="primary" + :href="externalDashboardUrl" + target="_blank" + rel="noopener noreferrer" + > + {{ __('View full dashboard') }} + <icon name="external-link" /> + </gl-button> + </div> + </gl-form-group> </div> </div> + <div v-if="!showEmptyState"> <graph-group v-for="(groupData, index) in groups" diff --git a/app/assets/javascripts/monitoring/components/embed.vue b/app/assets/javascripts/monitoring/components/embed.vue index e17f03de0fd..9e85b0633fe 100644 --- a/app/assets/javascripts/monitoring/components/embed.vue +++ b/app/assets/javascripts/monitoring/components/embed.vue @@ -1,8 +1,9 @@ <script> import { mapActions, mapState } from 'vuex'; +import { getParameterValues, removeParams } from '~/lib/utils/url_utility'; import GraphGroup from './graph_group.vue'; import MonitorAreaChart from './charts/area.vue'; -import { sidebarAnimationDuration, timeWindowsKeyNames, timeWindows } from '../constants'; +import { sidebarAnimationDuration } from '../constants'; import { getTimeDiff } from '../utils'; let sidebarMutationObserver; @@ -19,10 +20,17 @@ export default { }, }, data() { + const defaultRange = getTimeDiff(); + const start = getParameterValues('start', this.dashboardUrl)[0] || defaultRange.start; + const end = getParameterValues('end', this.dashboardUrl)[0] || defaultRange.end; + + const params = { + start, + end, + }; + return { - params: { - ...getTimeDiff(timeWindows[timeWindowsKeyNames.eightHours]), - }, + params, elWidth: 0, }; }, @@ -73,7 +81,7 @@ export default { prometheusEndpointEnabled: true, }); this.setEndpoints({ - dashboardEndpoint: this.dashboardUrl, + dashboardEndpoint: removeParams(['start', 'end'], this.dashboardUrl), }); this.setShowErrorBanner(false); }, diff --git a/app/assets/javascripts/monitoring/constants.js b/app/assets/javascripts/monitoring/constants.js index 605c95e6da5..d7d89522732 100644 --- a/app/assets/javascripts/monitoring/constants.js +++ b/app/assets/javascripts/monitoring/constants.js @@ -21,11 +21,19 @@ export const timeWindows = { oneWeek: __('1 week'), }; -export const timeWindowsKeyNames = { - thirtyMinutes: 'thirtyMinutes', - threeHours: 'threeHours', - eightHours: 'eightHours', - oneDay: 'oneDay', - threeDays: 'threeDays', - oneWeek: 'oneWeek', +export const secondsIn = { + thirtyMinutes: 60 * 30, + threeHours: 60 * 60 * 3, + eightHours: 60 * 60 * 8, + oneDay: 60 * 60 * 24 * 1, + threeDays: 60 * 60 * 24 * 3, + oneWeek: 60 * 60 * 24 * 7 * 1, }; + +export const timeWindowsKeyNames = Object.keys(secondsIn).reduce( + (otherTimeWindows, timeWindow) => ({ + ...otherTimeWindows, + [timeWindow]: timeWindow, + }), + {}, +); diff --git a/app/assets/javascripts/monitoring/stores/actions.js b/app/assets/javascripts/monitoring/stores/actions.js index 245cc2eaca3..0cbad179f17 100644 --- a/app/assets/javascripts/monitoring/stores/actions.js +++ b/app/assets/javascripts/monitoring/stores/actions.js @@ -151,7 +151,7 @@ function fetchPrometheusResult(prometheusEndpoint, params) { */ export const fetchPrometheusMetric = ({ commit }, { metric, params }) => { const { start, end } = params; - const timeDiff = end - start; + const timeDiff = (new Date(end) - new Date(start)) / 1000; const minStep = 60; const queryDataPoints = 600; diff --git a/app/assets/javascripts/monitoring/utils.js b/app/assets/javascripts/monitoring/utils.js index 478e2b3d06c..46b01f753f8 100644 --- a/app/assets/javascripts/monitoring/utils.js +++ b/app/assets/javascripts/monitoring/utils.js @@ -1,35 +1,24 @@ -import { timeWindows } from './constants'; +import { secondsIn, timeWindowsKeyNames } from './constants'; -/** - * method that converts a predetermined time window to minutes - * defaults to 8 hours as the default option - * @param {String} timeWindow - The time window to convert to minutes - * @returns {number} The time window in minutes - */ -const getTimeDifferenceSeconds = timeWindow => { - switch (timeWindow) { - case timeWindows.thirtyMinutes: - return 60 * 30; - case timeWindows.threeHours: - return 60 * 60 * 3; - case timeWindows.oneDay: - return 60 * 60 * 24 * 1; - case timeWindows.threeDays: - return 60 * 60 * 24 * 3; - case timeWindows.oneWeek: - return 60 * 60 * 24 * 7 * 1; - default: - return 60 * 60 * 8; - } -}; +export const getTimeDiff = timeWindow => { + const end = Math.floor(Date.now() / 1000); // convert milliseconds to seconds + const difference = secondsIn[timeWindow] || secondsIn.eightHours; + const start = end - difference; -export const getTimeDiff = selectedTimeWindow => { - const end = Date.now() / 1000; // convert milliseconds to seconds - const start = end - getTimeDifferenceSeconds(selectedTimeWindow); - - return { start, end }; + return { + start: new Date(start * 1000).toISOString(), + end: new Date(end * 1000).toISOString(), + }; }; +export const getTimeWindow = ({ start, end }) => + Object.entries(secondsIn).reduce((acc, [timeRange, value]) => { + if (end - start === value) { + return timeRange; + } + return acc; + }, timeWindowsKeyNames.eightHours); + /** * This method is used to validate if the graph data format for a chart component * that needs a time series as a response from a prometheus query (query_range) is diff --git a/app/assets/javascripts/mr_popover/components/mr_popover.vue b/app/assets/javascripts/mr_popover/components/mr_popover.vue index c203cb0667c..b81600660f6 100644 --- a/app/assets/javascripts/mr_popover/components/mr_popover.vue +++ b/app/assets/javascripts/mr_popover/components/mr_popover.vue @@ -1,4 +1,5 @@ <script> +/* eslint-disable @gitlab/vue-i18n/no-bare-strings */ import { GlPopover, GlSkeletonLoading } from '@gitlab/ui'; import Icon from '../../vue_shared/components/icon.vue'; import CiIcon from '../../vue_shared/components/ci_icon.vue'; @@ -7,7 +8,8 @@ import query from '../queries/merge_request.query.graphql'; import { mrStates, humanMRStates } from '../constants'; export default { - name: 'MRPopover', + // name: 'MRPopover' is a false positive: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/25 + name: 'MRPopover', // eslint-disable-line @gitlab/i18n/no-non-i18n-strings components: { GlPopover, GlSkeletonLoading, @@ -102,9 +104,11 @@ export default { <ci-icon v-if="detailedStatus" :status="detailedStatus" /> </div> <h5 class="my-2">{{ mergeRequestTitle }}</h5> + <!-- eslint-disable @gitlab/vue-i18n/no-bare-strings --> <div class="text-secondary"> {{ `${projectPath}!${mergeRequestIID}` }} </div> + <!-- eslint-enable @gitlab/vue-i18n/no-bare-strings --> </div> </gl-popover> </template> diff --git a/app/assets/javascripts/notebook/cells/markdown.vue b/app/assets/javascripts/notebook/cells/markdown.vue index 61eabbcb8b2..9e4a92426ee 100644 --- a/app/assets/javascripts/notebook/cells/markdown.vue +++ b/app/assets/javascripts/notebook/cells/markdown.vue @@ -43,7 +43,7 @@ renderer.paragraph = t => { if (typeof katex !== 'undefined') { const katexString = text .replace(/&/g, '&') - .replace(/&=&/g, '\\space=\\space') + .replace(/&=&/g, '\\space=\\space') // eslint-disable-line @gitlab/i18n/no-non-i18n-strings .replace(/<(\/?)em>/g, '_'); const regex = new RegExp(katexRegexString, 'gi'); const matchLocation = katexString.search(regex); diff --git a/app/assets/javascripts/notebook/cells/output/image.vue b/app/assets/javascripts/notebook/cells/output/image.vue index f1130275525..842d9e8da0d 100644 --- a/app/assets/javascripts/notebook/cells/output/image.vue +++ b/app/assets/javascripts/notebook/cells/output/image.vue @@ -25,7 +25,7 @@ export default { }, computed: { imgSrc() { - return `data:${this.outputType};base64,${this.rawCode}`; + return `data:${this.outputType};base64,${this.rawCode}`; // eslint-disable-line @gitlab/i18n/no-non-i18n-strings }, showOutput() { return this.index === 0; diff --git a/app/assets/javascripts/notebook/index.vue b/app/assets/javascripts/notebook/index.vue index e7056c03e4a..4a3c1a28279 100644 --- a/app/assets/javascripts/notebook/index.vue +++ b/app/assets/javascripts/notebook/index.vue @@ -39,7 +39,7 @@ export default { }, methods: { cellType(type) { - return `${type}-cell`; + return `${type}-cell`; // eslint-disable-line @gitlab/i18n/no-non-i18n-strings }, }, }; diff --git a/app/assets/javascripts/notes/components/diff_with_note.vue b/app/assets/javascripts/notes/components/diff_with_note.vue index 164e79c6294..df537ba1ed2 100644 --- a/app/assets/javascripts/notes/components/diff_with_note.vue +++ b/app/assets/javascripts/notes/components/diff_with_note.vue @@ -1,4 +1,5 @@ <script> +/* eslint-disable @gitlab/vue-i18n/no-bare-strings */ import { mapState, mapActions } from 'vuex'; import DiffFileHeader from '~/diffs/components/diff_file_header.vue'; import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue'; diff --git a/app/assets/javascripts/notes/components/discussion_filter.vue b/app/assets/javascripts/notes/components/discussion_filter.vue index eb3fbbe1385..743684e7046 100644 --- a/app/assets/javascripts/notes/components/discussion_filter.vue +++ b/app/assets/javascripts/notes/components/discussion_filter.vue @@ -61,7 +61,7 @@ export default { }, methods: { ...mapActions(['filterDiscussion', 'setCommentsDisabled', 'setTargetNoteHash']), - selectFilter(value) { + selectFilter(value, persistFilter = true) { const filter = parseInt(value, 10); // close dropdown @@ -69,7 +69,11 @@ export default { if (filter === this.currentValue) return; this.currentValue = filter; - this.filterDiscussion({ path: this.getNotesDataByProp('discussionsPath'), filter }); + this.filterDiscussion({ + path: this.getNotesDataByProp('discussionsPath'), + filter, + persistFilter, + }); this.toggleCommentsForm(); }, toggleDropdown() { @@ -85,7 +89,7 @@ export default { const hash = getLocationHash(); if (/^note_/.test(hash) && this.currentValue !== DISCUSSION_FILTERS_DEFAULT_VALUE) { - this.selectFilter(this.defaultValue); + this.selectFilter(this.defaultValue, false); this.toggleDropdown(); // close dropdown this.setTargetNoteHash(hash); } diff --git a/app/assets/javascripts/notes/components/note_edited_text.vue b/app/assets/javascripts/notes/components/note_edited_text.vue index 15ce49d7c31..1af5af5c470 100644 --- a/app/assets/javascripts/notes/components/note_edited_text.vue +++ b/app/assets/javascripts/notes/components/note_edited_text.vue @@ -1,4 +1,5 @@ <script> +/* eslint-disable @gitlab/vue-i18n/no-bare-strings */ import timeAgoTooltip from '../../vue_shared/components/time_ago_tooltip.vue'; export default { diff --git a/app/assets/javascripts/notes/services/notes_service.js b/app/assets/javascripts/notes/services/notes_service.js index 9e0392110b6..3d239d8cb6b 100644 --- a/app/assets/javascripts/notes/services/notes_service.js +++ b/app/assets/javascripts/notes/services/notes_service.js @@ -5,8 +5,11 @@ import * as constants from '../constants'; Vue.use(VueResource); export default { - fetchDiscussions(endpoint, filter) { - const config = filter !== undefined ? { params: { notes_filter: filter } } : null; + fetchDiscussions(endpoint, filter, persistFilter = true) { + const config = + filter !== undefined + ? { params: { notes_filter: filter, persist_filter: persistFilter } } + : null; return Vue.http.get(endpoint, config); }, replyToDiscussion(endpoint, data) { diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js index 762a87ce0ff..b7857997d42 100644 --- a/app/assets/javascripts/notes/stores/actions.js +++ b/app/assets/javascripts/notes/stores/actions.js @@ -46,9 +46,9 @@ export const setNotesFetchedState = ({ commit }, state) => export const toggleDiscussion = ({ commit }, data) => commit(types.TOGGLE_DISCUSSION, data); -export const fetchDiscussions = ({ commit, dispatch }, { path, filter }) => +export const fetchDiscussions = ({ commit, dispatch }, { path, filter, persistFilter }) => service - .fetchDiscussions(path, filter) + .fetchDiscussions(path, filter, persistFilter) .then(res => res.json()) .then(discussions => { commit(types.SET_INITIAL_DISCUSSIONS, discussions); @@ -411,9 +411,9 @@ export const setLoadingState = ({ commit }, data) => { commit(types.SET_NOTES_LOADING_STATE, data); }; -export const filterDiscussion = ({ dispatch }, { path, filter }) => { +export const filterDiscussion = ({ dispatch }, { path, filter, persistFilter }) => { dispatch('setLoadingState', true); - dispatch('fetchDiscussions', { path, filter }) + dispatch('fetchDiscussions', { path, filter, persistFilter }) .then(() => { dispatch('setLoadingState', false); dispatch('setNotesFetchedState', true); diff --git a/app/assets/javascripts/operation_settings/components/external_dashboard.vue b/app/assets/javascripts/operation_settings/components/external_dashboard.vue index 3c5de189d51..e90e27a402a 100644 --- a/app/assets/javascripts/operation_settings/components/external_dashboard.vue +++ b/app/assets/javascripts/operation_settings/components/external_dashboard.vue @@ -53,12 +53,15 @@ export default { label-for="full-dashboard-url" :description="s__('ExternalMetrics|Enter the URL of the dashboard you want to link to')" > + <!-- placeholder with a url is a false positive --> + <!-- eslint-disable @gitlab/vue-i18n/no-bare-attribute-strings --> <gl-form-input id="full-dashboard-url" v-model="userDashboardUrl" placeholder="https://my-org.gitlab.io/my-dashboards" @keydown.enter.native.prevent="updateExternalDashboardUrl" /> + <!-- eslint-enable @gitlab/vue-i18n/no-bare-attribute-strings --> </gl-form-group> <gl-button variant="success" @click="updateExternalDashboardUrl"> {{ __('Save Changes') }} diff --git a/app/assets/javascripts/pages/projects/project.js b/app/assets/javascripts/pages/projects/project.js index f0d529758d5..332b6811af6 100644 --- a/app/assets/javascripts/pages/projects/project.js +++ b/app/assets/javascripts/pages/projects/project.js @@ -1,9 +1,10 @@ -/* eslint-disable func-names, no-var, no-return-assign, one-var, object-shorthand, vars-on-top */ +/* eslint-disable func-names, no-var, no-return-assign, object-shorthand, vars-on-top */ import $ from 'jquery'; import Cookies from 'js-cookie'; import { __ } from '~/locale'; -import { visitUrl } from '~/lib/utils/url_utility'; +import { visitUrl, mergeUrlParams } from '~/lib/utils/url_utility'; +import { serializeForm } from '~/lib/utils/forms'; import axios from '~/lib/utils/axios_utils'; import flash from '~/flash'; import projectSelect from '../../project_select'; @@ -107,9 +108,10 @@ export default class Project { refLink.href = '#'; return $('.js-project-refs-dropdown').each(function() { - var $dropdown, selected; - $dropdown = $(this); - selected = $dropdown.data('selected'); + var $dropdown = $(this); + var selected = $dropdown.data('selected'); + var fieldName = $dropdown.data('fieldName'); + var shouldVisit = Boolean($dropdown.data('visit')); return $dropdown.glDropdown({ data(term, callback) { axios @@ -127,7 +129,7 @@ export default class Project { filterRemote: true, filterByText: true, inputFieldName: $dropdown.data('inputFieldName'), - fieldName: $dropdown.data('fieldName'), + fieldName, renderRow: function(ref) { var li = refListItem.cloneNode(false); @@ -158,15 +160,12 @@ export default class Project { clicked: function(options) { const { e } = options; e.preventDefault(); - if ($('input[name="ref"]').length) { + if ($(`input[name="${fieldName}"]`).length) { var $form = $dropdown.closest('form'); - - var $visit = $dropdown.data('visit'); - var shouldVisit = $visit ? true : $visit; var action = $form.attr('action'); - var divider = action.indexOf('?') === -1 ? '?' : '&'; + if (shouldVisit) { - visitUrl(`${action}${divider}${$form.serialize()}`); + visitUrl(mergeUrlParams(serializeForm($form[0]), action)); } } }, 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 b4d24f3aa36..627d37bac68 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,4 +1,5 @@ <script> +/* eslint-disable @gitlab/vue-i18n/no-bare-strings */ import settingsMixin from 'ee_else_ce/pages/projects/shared/permissions/mixins/settings_pannel_mixin'; import { __ } from '~/locale'; import projectFeatureSetting from './project_feature_setting.vue'; diff --git a/app/assets/javascripts/pages/search/show/search.js b/app/assets/javascripts/pages/search/show/search.js index d5a8e712d6b..81b6225cb18 100644 --- a/app/assets/javascripts/pages/search/show/search.js +++ b/app/assets/javascripts/pages/search/show/search.js @@ -2,6 +2,7 @@ import $ from 'jquery'; import Flash from '~/flash'; import Api from '~/api'; import { __ } from '~/locale'; +import Project from '~/pages/projects/project'; export default class Search { constructor() { @@ -37,9 +38,6 @@ export default class Search { text(obj) { return obj.full_name; }, - toggleLabel(obj) { - return `${$groupDropdown.data('defaultLabel')} ${obj.full_name}`; - }, clicked: () => Search.submitSearch(), }); @@ -70,11 +68,10 @@ export default class Search { text(obj) { return obj.name_with_namespace; }, - toggleLabel(obj) { - return `${$projectDropdown.data('defaultLabel')} ${obj.name_with_namespace}`; - }, clicked: () => Search.submitSearch(), }); + + Project.initRefSwitcher(); } eventListeners() { diff --git a/app/assets/javascripts/pages/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js index 1d8b388e935..4ac4efec45d 100644 --- a/app/assets/javascripts/pages/users/user_tabs.js +++ b/app/assets/javascripts/pages/users/user_tabs.js @@ -143,7 +143,7 @@ export default class UserTabs { this.loadOverviewTab(); } - const loadableActions = ['groups', 'contributed', 'projects', 'snippets']; + const loadableActions = ['groups', 'contributed', 'projects', 'starred', 'snippets']; if (loadableActions.indexOf(action) > -1) { this.loadTab(action, endpoint); } diff --git a/app/assets/javascripts/performance_bar/components/detailed_metric.vue b/app/assets/javascripts/performance_bar/components/detailed_metric.vue index 73524827c5d..a271284dd89 100644 --- a/app/assets/javascripts/performance_bar/components/detailed_metric.vue +++ b/app/assets/javascripts/performance_bar/components/detailed_metric.vue @@ -66,7 +66,7 @@ export default { <template v-if="detailsList.length"> <tr v-for="(item, index) in detailsList" :key="index"> <td> - <span>{{ item.duration }}ms</span> + <span>{{ sprintf(__('%{duration}ms'), { duration: item.duration }) }}</span> </td> <td> <div class="js-toggle-container"> 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 13aa8844172..9ad6e75b86b 100644 --- a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue +++ b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue @@ -76,7 +76,6 @@ export default { if (this.hasHost && this.currentRequest.details.host.canary) { return glEmojiTag('baby_chick'); } - return ''; }, }, diff --git a/app/assets/javascripts/pipelines/components/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipeline_url.vue index 3f021a26ec5..e01080b04d6 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_url.vue +++ b/app/assets/javascripts/pipelines/components/pipeline_url.vue @@ -102,7 +102,11 @@ export default { <span v-if="pipeline.flags.detached_merge_request_pipeline" v-gl-tooltip - :title="__('This pipeline is run on the source branch')" + :title=" + __( + 'The code of a detached pipeline is tested against the source branch instead of merged results', + ) + " class="js-pipeline-url-detached badge badge-info" > {{ __('detached') }} diff --git a/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue b/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue index 2ab0ad4d013..3f07b77ed32 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue @@ -1,4 +1,5 @@ <script> +/* eslint-disable @gitlab/vue-i18n/no-bare-strings */ import { GlLink, GlTooltipDirective } from '@gitlab/ui'; import Icon from '~/vue_shared/components/icon.vue'; diff --git a/app/assets/javascripts/registry/components/app.vue b/app/assets/javascripts/registry/components/app.vue index 38519c220c5..efbf0a4e3cf 100644 --- a/app/assets/javascripts/registry/components/app.vue +++ b/app/assets/javascripts/registry/components/app.vue @@ -81,9 +81,11 @@ export default { ); }, dockerBuildCommand() { + // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings return `docker build -t ${this.repositoryUrl} .`; }, dockerPushCommand() { + // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings return `docker push ${this.repositoryUrl}`; }, }, diff --git a/app/assets/javascripts/releases/components/release_block.vue b/app/assets/javascripts/releases/components/release_block.vue index 0031ba04d78..7580c2d0ad0 100644 --- a/app/assets/javascripts/releases/components/release_block.vue +++ b/app/assets/javascripts/releases/components/release_block.vue @@ -1,4 +1,5 @@ <script> +/* eslint-disable @gitlab/vue-i18n/no-bare-strings */ import _ from 'underscore'; import { GlTooltipDirective, GlLink, GlBadge } from '@gitlab/ui'; import Icon from '~/vue_shared/components/icon.vue'; diff --git a/app/assets/javascripts/reports/components/modal.vue b/app/assets/javascripts/reports/components/modal.vue index 162421b037f..cb9c1642608 100644 --- a/app/assets/javascripts/reports/components/modal.vue +++ b/app/assets/javascripts/reports/components/modal.vue @@ -1,4 +1,5 @@ <script> +// import { sprintf, __ } from '~/locale'; import Modal from '~/vue_shared/components/gl_modal.vue'; import LoadingButton from '~/vue_shared/components/loading_button.vue'; import CodeBlock from '~/vue_shared/components/code_block.vue'; @@ -47,9 +48,9 @@ export default { </a> </template> - <template v-else-if="field.type === $options.fieldTypes.miliseconds"> - {{ field.value }} ms - </template> + <template v-else-if="field.type === $options.fieldTypes.miliseconds">{{ + sprintf(__('%{value} ms'), { value: field.value }) + }}</template> <template v-else-if="field.type === $options.fieldTypes.text"> {{ field.value }} diff --git a/app/assets/javascripts/reports/components/report_link.vue b/app/assets/javascripts/reports/components/report_link.vue index 052bc53d610..e32e1ac49ca 100644 --- a/app/assets/javascripts/reports/components/report_link.vue +++ b/app/assets/javascripts/reports/components/report_link.vue @@ -1,4 +1,5 @@ <script> +/* eslint-disable @gitlab/vue-i18n/no-bare-strings */ export default { name: 'ReportIssueLink', props: { diff --git a/app/assets/javascripts/repository/components/last_commit.vue b/app/assets/javascripts/repository/components/last_commit.vue index 26493556063..e2060d4aeec 100644 --- a/app/assets/javascripts/repository/components/last_commit.vue +++ b/app/assets/javascripts/repository/components/last_commit.vue @@ -1,4 +1,5 @@ <script> +/* eslint-disable @gitlab/vue-i18n/no-bare-strings */ import { GlTooltipDirective, GlLink, GlButton, GlLoadingIcon } from '@gitlab/ui'; import { sprintf, s__ } from '~/locale'; import Icon from '../../vue_shared/components/icon.vue'; diff --git a/app/assets/javascripts/repository/components/table/row.vue b/app/assets/javascripts/repository/components/table/row.vue index 6029460d975..171841178a3 100644 --- a/app/assets/javascripts/repository/components/table/row.vue +++ b/app/assets/javascripts/repository/components/table/row.vue @@ -115,6 +115,7 @@ export default { <component :is="linkComponent" :to="routerLinkTo" :href="url" class="str-truncated"> {{ fullPath }} </component> + <!-- eslint-disable-next-line @gitlab/vue-i18n/no-bare-strings --> <gl-badge v-if="lfsOid" variant="default" class="label-lfs ml-1">LFS</gl-badge> <template v-if="isSubmodule"> @ <gl-link :href="submoduleTreeUrl" class="commit-sha">{{ shortSha }}</gl-link> diff --git a/app/assets/javascripts/sidebar/components/assignees/assignees.vue b/app/assets/javascripts/sidebar/components/assignees/assignees.vue index 805c21d0965..631e2e28d4d 100644 --- a/app/assets/javascripts/sidebar/components/assignees/assignees.vue +++ b/app/assets/javascripts/sidebar/components/assignees/assignees.vue @@ -3,6 +3,8 @@ import { __, sprintf } from '~/locale'; import tooltip from '~/vue_shared/directives/tooltip'; export default { + // name: 'Assignees' is a false positive: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26#possible-false-positives + // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings name: 'Assignees', directives: { tooltip, diff --git a/app/assets/javascripts/star.js b/app/assets/javascripts/star.js index 70f89152f70..97afeecd8ac 100644 --- a/app/assets/javascripts/star.js +++ b/app/assets/javascripts/star.js @@ -18,7 +18,7 @@ export default class Star { const isStarred = $starSpan.hasClass('starred'); $this .parent() - .find('.star-count') + .find('.count') .text(data.star_count); if (isStarred) { diff --git a/app/assets/javascripts/visual_review_toolbar/components/comment.js b/app/assets/javascripts/visual_review_toolbar/components/comment.js index 20effc1751d..a03dc14b319 100644 --- a/app/assets/javascripts/visual_review_toolbar/components/comment.js +++ b/app/assets/javascripts/visual_review_toolbar/components/comment.js @@ -1,5 +1,5 @@ import { nextView } from '../store'; -import { localStorage, COMMENT_BOX, LOGOUT } from '../shared'; +import { localStorage, COMMENT_BOX, LOGOUT, STORAGE_MR_ID, STORAGE_TOKEN } from '../shared'; import { clearNote } from './note'; import { buttonClearStyles } from './utils'; import { addForm } from './wrapper'; @@ -27,8 +27,8 @@ const comment = state => { // If we reach a design where we can logout from multiple views, promote this // to it's own package const logoutUser = state => { - localStorage.removeItem('token'); - localStorage.removeItem('mergeRequestId'); + localStorage.removeItem(STORAGE_TOKEN); + localStorage.removeItem(STORAGE_MR_ID); state.token = ''; state.mergeRequestId = ''; diff --git a/app/assets/javascripts/visual_review_toolbar/components/comment_mr_note.js b/app/assets/javascripts/visual_review_toolbar/components/comment_mr_note.js index f71ffbf4f20..da67763261c 100644 --- a/app/assets/javascripts/visual_review_toolbar/components/comment_mr_note.js +++ b/app/assets/javascripts/visual_review_toolbar/components/comment_mr_note.js @@ -1,5 +1,5 @@ import { nextView } from '../store'; -import { localStorage, CHANGE_MR_ID_BUTTON, COMMENT_BOX } from '../shared'; +import { localStorage, CHANGE_MR_ID_BUTTON, COMMENT_BOX, STORAGE_MR_ID } from '../shared'; import { clearNote } from './note'; import { buttonClearStyles } from './utils'; import { addForm } from './wrapper'; @@ -18,7 +18,7 @@ const selectedMrNote = state => { }; const clearMrId = state => { - localStorage.removeItem('mergeRequestId'); + localStorage.removeItem(STORAGE_MR_ID); state.mergeRequestId = ''; }; diff --git a/app/assets/javascripts/visual_review_toolbar/components/comment_storage.js b/app/assets/javascripts/visual_review_toolbar/components/comment_storage.js index 32a9e7e2f05..49c9400437e 100644 --- a/app/assets/javascripts/visual_review_toolbar/components/comment_storage.js +++ b/app/assets/javascripts/visual_review_toolbar/components/comment_storage.js @@ -1,7 +1,7 @@ import { selectCommentBox } from './utils'; -import { sessionStorage } from '../shared'; +import { sessionStorage, STORAGE_COMMENT } from '../shared'; -const getSavedComment = () => sessionStorage.getItem('comment') || ''; +const getSavedComment = () => sessionStorage.getItem(STORAGE_COMMENT) || ''; const saveComment = () => { const currentComment = selectCommentBox(); @@ -9,12 +9,12 @@ const saveComment = () => { // This may be added to any view via top-level beforeunload listener // so let's skip if it does not apply if (currentComment && currentComment.value) { - sessionStorage.setItem('comment', currentComment.value); + sessionStorage.setItem(STORAGE_COMMENT, currentComment.value); } }; const clearSavedComment = () => { - sessionStorage.removeItem('comment'); + sessionStorage.removeItem(STORAGE_COMMENT); }; export { getSavedComment, saveComment, clearSavedComment }; diff --git a/app/assets/javascripts/visual_review_toolbar/components/login.js b/app/assets/javascripts/visual_review_toolbar/components/login.js index 4a6976ef2fd..20ab01bc690 100644 --- a/app/assets/javascripts/visual_review_toolbar/components/login.js +++ b/app/assets/javascripts/visual_review_toolbar/components/login.js @@ -1,5 +1,5 @@ import { nextView } from '../store'; -import { localStorage, LOGIN, TOKEN_BOX } from '../shared'; +import { localStorage, LOGIN, TOKEN_BOX, STORAGE_TOKEN } from '../shared'; import { clearNote, postError } from './note'; import { rememberBox, submitButton } from './form_elements'; import { selectRemember, selectToken } from './utils'; @@ -22,7 +22,7 @@ const storeToken = (token, state) => { const rememberMe = selectRemember().checked; if (rememberMe) { - localStorage.setItem('token', token); + localStorage.setItem(STORAGE_TOKEN, token); } state.token = token; diff --git a/app/assets/javascripts/visual_review_toolbar/components/mr_id.js b/app/assets/javascripts/visual_review_toolbar/components/mr_id.js index f51e9631dd2..695b3af8aa0 100644 --- a/app/assets/javascripts/visual_review_toolbar/components/mr_id.js +++ b/app/assets/javascripts/visual_review_toolbar/components/mr_id.js @@ -1,5 +1,5 @@ import { nextView } from '../store'; -import { MR_ID, MR_ID_BUTTON, localStorage } from '../shared'; +import { MR_ID, MR_ID_BUTTON, STORAGE_MR_ID, localStorage } from '../shared'; import { clearNote, postError } from './note'; import { rememberBox, submitButton } from './form_elements'; import { selectForm, selectMrBox, selectRemember } from './utils'; @@ -23,7 +23,7 @@ const storeMR = (id, state) => { const rememberMe = selectRemember().checked; if (rememberMe) { - localStorage.setItem('mergeRequestId', id); + localStorage.setItem(STORAGE_MR_ID, id); } state.mergeRequestId = id; diff --git a/app/assets/javascripts/visual_review_toolbar/shared/constants.js b/app/assets/javascripts/visual_review_toolbar/shared/constants.js index a56ea378b14..0d5b666ab0a 100644 --- a/app/assets/javascripts/visual_review_toolbar/shared/constants.js +++ b/app/assets/javascripts/visual_review_toolbar/shared/constants.js @@ -15,6 +15,12 @@ const REMEMBER_ITEM = 'gitlab-remember-item'; const REVIEW_CONTAINER = 'gitlab-review-container'; const TOKEN_BOX = 'gitlab-token'; +// Storage keys +const STORAGE_PREFIX = '--gitlab'; // Using `--` to make the prefix more unique +const STORAGE_MR_ID = `${STORAGE_PREFIX}-merge-request-id`; +const STORAGE_TOKEN = `${STORAGE_PREFIX}-token`; +const STORAGE_COMMENT = `${STORAGE_PREFIX}-comment`; + // colors — these are applied programmatically // rest of styles belong in ./styles const BLACK = 'rgba(46, 46, 46, 1)'; @@ -39,6 +45,9 @@ export { REMEMBER_ITEM, REVIEW_CONTAINER, TOKEN_BOX, + STORAGE_MR_ID, + STORAGE_TOKEN, + STORAGE_COMMENT, BLACK, CLEAR, MUTED, diff --git a/app/assets/javascripts/visual_review_toolbar/shared/index.js b/app/assets/javascripts/visual_review_toolbar/shared/index.js index 751eae74dde..d8ccb170592 100644 --- a/app/assets/javascripts/visual_review_toolbar/shared/index.js +++ b/app/assets/javascripts/visual_review_toolbar/shared/index.js @@ -14,6 +14,9 @@ import { REMEMBER_ITEM, REVIEW_CONTAINER, TOKEN_BOX, + STORAGE_MR_ID, + STORAGE_TOKEN, + STORAGE_COMMENT, BLACK, CLEAR, MUTED, @@ -41,6 +44,9 @@ export { REMEMBER_ITEM, REVIEW_CONTAINER, TOKEN_BOX, + STORAGE_MR_ID, + STORAGE_TOKEN, + STORAGE_COMMENT, BLACK, CLEAR, MUTED, diff --git a/app/assets/javascripts/visual_review_toolbar/store/state.js b/app/assets/javascripts/visual_review_toolbar/store/state.js index 741a5c7d99c..b7853bb0723 100644 --- a/app/assets/javascripts/visual_review_toolbar/store/state.js +++ b/app/assets/javascripts/visual_review_toolbar/store/state.js @@ -1,5 +1,5 @@ import { comment, login, mrForm } from '../components'; -import { localStorage, COMMENT_BOX, LOGIN, MR_ID } from '../shared'; +import { localStorage, COMMENT_BOX, LOGIN, MR_ID, STORAGE_MR_ID, STORAGE_TOKEN } from '../shared'; const state = { browser: '', @@ -74,8 +74,8 @@ const initializeState = (wind, doc) => { }; const getInitialView = () => { - const token = localStorage.getItem('token'); - const mrId = localStorage.getItem('mergeRequestId'); + const token = localStorage.getItem(STORAGE_TOKEN); + const mrId = localStorage.getItem(STORAGE_MR_ID); if (token) { state.token = token; diff --git a/app/assets/javascripts/visual_review_toolbar/styles/toolbar.css b/app/assets/javascripts/visual_review_toolbar/styles/toolbar.css index e5732fd5d93..d1a8d66ef40 100644 --- a/app/assets/javascripts/visual_review_toolbar/styles/toolbar.css +++ b/app/assets/javascripts/visual_review_toolbar/styles/toolbar.css @@ -42,7 +42,7 @@ position: fixed; bottom: 1rem; right: 1rem; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Noto Sans', Ubuntu, Cantarell, 'Helvetica Neue', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; font-size: .8rem; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue index 5c7859828d8..bb6921225c2 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue @@ -14,6 +14,8 @@ import ReviewAppLink from './review_app_link.vue'; import MRWidgetService from '../services/mr_widget_service'; export default { + // name: 'Deployment' is a false positive: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26#possible-false-positives + // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings name: 'Deployment', components: { LoadingButton, diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue index 13e4b061fda..d0df8309dc7 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue @@ -34,8 +34,8 @@ export default { <template> <div class="d-flex align-self-start"> <div class="square s24 h-auto d-flex-center append-right-default"> - <div v-if="isLoading" class="mr-widget-icon"> - <gl-loading-icon size="sm" /> + <div v-if="isLoading" class="mr-widget-icon d-inline-flex"> + <gl-loading-icon size="md" class="mr-loading-icon d-inline-flex" /> </div> <ci-icon v-else :status="statusObj" :size="24" /> </div> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue index b9562fbc260..fb07c03e34d 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue @@ -1,4 +1,5 @@ <script> +/* eslint-disable @gitlab/vue-i18n/no-bare-strings */ import Flash from '~/flash'; import tooltip from '~/vue_shared/directives/tooltip'; import { s__, __ } from '~/locale'; @@ -84,6 +85,8 @@ export default { .removeSourceBranch() .then(res => res.data) .then(data => { + // False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26 + // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings if (data.message === 'Branch was deleted') { eventHub.$emit('MRWidgetUpdateRequested', () => { this.isMakingRequest = false; diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue index 8d415c1bbea..f7848a5fced 100644 --- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue +++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue @@ -40,6 +40,8 @@ import { setFaviconOverlay } from '../lib/utils/common_utils'; export default { el: '#js-vue-mr-widget', + // False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/25 + // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings name: 'MRWidget', components: { 'mr-widget-header': WidgetHeader, diff --git a/app/assets/javascripts/vue_shared/components/changed_file_icon.vue b/app/assets/javascripts/vue_shared/components/changed_file_icon.vue index 15cb0bd9792..cdf2d1020ba 100644 --- a/app/assets/javascripts/vue_shared/components/changed_file_icon.vue +++ b/app/assets/javascripts/vue_shared/components/changed_file_icon.vue @@ -44,6 +44,8 @@ export default { }, computed: { changedIcon() { + // False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26 + // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings const suffix = !this.file.changed && this.file.staged && !this.showStagedIcon ? '-solid' : ''; if (this.forceModifiedIcon) return `file-modified${suffix}`; diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue index fcf2f950501..4d27d1c9179 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/header.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue @@ -38,10 +38,11 @@ export default { computed: { mdTable() { return [ - '| header | header |', + // False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26 + '| header | header |', // eslint-disable-line @gitlab/i18n/no-non-i18n-strings '| ------ | ------ |', - '| cell | cell |', - '| cell | cell |', + '| cell | cell |', // eslint-disable-line @gitlab/i18n/no-non-i18n-strings + '| cell | cell |', // eslint-disable-line @gitlab/i18n/no-non-i18n-strings ].join('\n'); }, mdSuggestion() { diff --git a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue index 8ce5b615795..5140184eb8e 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue @@ -1,4 +1,5 @@ <script> +/* eslint-disable @gitlab/vue-i18n/no-bare-strings */ import { GlLink } from '@gitlab/ui'; export default { diff --git a/app/assets/javascripts/vue_shared/components/select2_select.vue b/app/assets/javascripts/vue_shared/components/select2_select.vue index 6d2612556ff..eb741d238b5 100644 --- a/app/assets/javascripts/vue_shared/components/select2_select.vue +++ b/app/assets/javascripts/vue_shared/components/select2_select.vue @@ -3,6 +3,8 @@ import $ from 'jquery'; import 'select2'; export default { + // False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26 + // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings name: 'Select2Select', props: { options: { diff --git a/app/assets/javascripts/vue_shared/components/stacked_progress_bar.vue b/app/assets/javascripts/vue_shared/components/stacked_progress_bar.vue index 8ba6b73f928..af4eb2de7f8 100644 --- a/app/assets/javascripts/vue_shared/components/stacked_progress_bar.vue +++ b/app/assets/javascripts/vue_shared/components/stacked_progress_bar.vue @@ -78,6 +78,8 @@ export default { return percent; }, barStyle(percent) { + // False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26 + // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings return `width: ${percent}%;`; }, getTooltip(label, count) { diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 262c0bf5ed2..6fc742871e7 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -124,11 +124,6 @@ float: left; padding-left: $gl-padding-8; } - - .section-header ~ .section.line { - margin-left: $gl-padding; - display: block; - } } .build-header { diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss index d72dd8671cc..45408c9ab3c 100644 --- a/app/assets/stylesheets/pages/members.scss +++ b/app/assets/stylesheets/pages/members.scss @@ -58,36 +58,9 @@ } } -.member-search-form { - position: relative; - - @include media-breakpoint-up(sm) { - float: right; - } - - .dropdown { - width: 100%; - margin-top: 5px; - - .dropdown-menu-toggle { - vertical-align: middle; - width: 100%; - } - - @include media-breakpoint-up(sm) { - margin-top: 0; - width: 155px; - } - } - - .form-control { - width: 100%; - padding-right: 35px; - - @include media-breakpoint-up(sm) { - width: 250px; - } - } +.member-access-text { + margin-left: auto; + line-height: 43px; } .member-search-btn { @@ -173,7 +146,7 @@ padding-bottom: 1px; } - .flex-project-members-form { + .flex-users-form { flex-wrap: nowrap; white-space: nowrap; margin-left: auto; diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index cb7913ee54e..3c1e384d6ed 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -208,6 +208,10 @@ font-size: 22px; } + .mr-loading-icon { + margin: 3px 0; + } + .ci-status-icon svg { margin: 3px 0; position: relative; diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index b9b8eabf909..6c03dbb56a7 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -806,7 +806,7 @@ $note-form-margin-left: 72px; border-radius: $border-radius-base; border: 1px solid $border-gray-normal; color: $note-disabled-comment-color; - padding: 90px 0; + padding: $gl-padding-8 0; &.discussion-locked { border: 0; diff --git a/app/assets/stylesheets/pages/prometheus.scss b/app/assets/stylesheets/pages/prometheus.scss index 05a4cc168a8..72f1b5307ec 100644 --- a/app/assets/stylesheets/pages/prometheus.scss +++ b/app/assets/stylesheets/pages/prometheus.scss @@ -1,17 +1,17 @@ .prometheus-graphs { - .dropdowns { - .dropdown-menu-toggle { - svg { - position: absolute; - right: 5%; - top: 25%; - } + .dropdown-buttons { + > div { + margin-left: auto; } + } - .dropdown-menu-toggle, - .dropdown-menu { - width: 240px; - } + .col-form-label { + line-height: 1; + padding-top: 0; + } + + .form-group { + margin-bottom: map-get($spacing-scale, 3); } } diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss index dbf600df9d6..58e46cfb70f 100644 --- a/app/assets/stylesheets/pages/search.scss +++ b/app/assets/stylesheets/pages/search.scss @@ -186,15 +186,12 @@ input[type='checkbox']:hover { } } -.search-holder { - @include media-breakpoint-up(sm) { - display: flex; - } +.search-field-holder, +.project-filter-form { + flex: 1 0 auto; + position: relative; - .search-field-holder, - .project-filter-form { - flex: 1 0 auto; - position: relative; + .search-holder & { margin-right: 0; @include media-breakpoint-up(sm) { @@ -202,6 +199,7 @@ input[type='checkbox']:hover { } } + .search-icon { position: absolute; left: 10px; @@ -215,9 +213,16 @@ input[type='checkbox']:hover { padding-left: $gl-padding + 15px; padding-right: $gl-padding + 15px; } +} + +.search-holder { + @include media-breakpoint-up(sm) { + display: flex; + } .btn-search, - .btn-success { + .btn-success, + .dropdown-menu-toggle { width: 100%; margin-top: 5px; @@ -236,9 +241,6 @@ input[type='checkbox']:hover { } .dropdown-menu-toggle { - width: 100%; - margin-top: 5px; - @include media-breakpoint-up(sm) { width: 180px; margin-top: 0; @@ -262,6 +264,25 @@ input[type='checkbox']:hover { } } +.search-page-form { + .dropdown-menu-toggle, + .btn-search { + width: 100%; + } + + .dropdown-menu-toggle { + @include media-breakpoint-up(lg) { + width: 240px; + } + } + + .btn-search { + @include media-breakpoint-up(lg) { + width: auto; + } + } +} + // Disable webkit input icons, link to solution: https://stackoverflow.com/questions/9421551/how-do-i-remove-all-default-webkit-search-field-styling /* stylelint-disable property-no-vendor-prefix */ input[type='search']::-webkit-search-decoration, diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss index 3b62121eb0d..79de1d78a6e 100644 --- a/app/assets/stylesheets/pages/settings.scss +++ b/app/assets/stylesheets/pages/settings.scss @@ -272,7 +272,7 @@ } .custom-monitored-metrics { - .card-title { + .card-header { display: flex; align-items: center; @@ -292,17 +292,6 @@ } } - .loading-metrics, - .empty-metrics { - padding: 30px 10px; - - p, - .btn { - margin-top: 10px; - margin-bottom: 0; - } - } - .loading-metrics .metrics-load-spinner { color: $gl-gray-700; } diff --git a/app/assets/stylesheets/pages/users.scss b/app/assets/stylesheets/pages/users.scss new file mode 100644 index 00000000000..3b018c1e087 --- /dev/null +++ b/app/assets/stylesheets/pages/users.scss @@ -0,0 +1,105 @@ +.user-sort-dropdown { + margin-left: $gl-padding-8; +} + +.user-search-form { + position: relative; + + @include media-breakpoint-up(sm) { + float: right; + } + + .dropdown { + width: 100%; + margin-top: 5px; + + .dropdown-menu-toggle { + vertical-align: middle; + width: 100%; + } + + @include media-breakpoint-up(sm) { + margin-top: 0; + width: 155px; + } + } + + .form-control { + width: 100%; + padding-right: 35px; + + @include media-breakpoint-up(sm) { + width: 250px; + } + } +} + +.user-search-btn { + position: absolute; + right: 4px; + top: 0; + height: 35px; + padding-left: 10px; + padding-right: 10px; + color: $gray-darkest; + background: transparent; + border: 0; + outline: 0; +} + +.flex-users-panel { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + + @include media-breakpoint-down(sm) { + display: block; + + .flex-project-title { + vertical-align: top; + display: inline-block; + max-width: 90%; + } + } + + .flex-project-title { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + .badge.badge-pill { + height: 17px; + line-height: 16px; + margin-right: 5px; + padding-top: 1px; + padding-bottom: 1px; + } + + .flex-users-form { + flex-wrap: nowrap; + white-space: nowrap; + margin-left: auto; + } +} + +.content-list.members-list li { + display: flex; + justify-content: space-between; + + .list-item-name { + float: none; + display: flex; + flex: 1; + } +} + +.card-body .user-info { + float: left; + + .user { + color: $gl-text-color; + font-weight: $gl-font-weight-bold; + } +} diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb index 398cb728e05..b86e4451a7e 100644 --- a/app/controllers/concerns/issuable_actions.rb +++ b/app/controllers/concerns/issuable_actions.rb @@ -127,7 +127,8 @@ module IssuableActions # GitLab Geo does not expect database UPDATE or INSERT statements to happen # on GET requests. # This is just a fail-safe in case notes_filter is sent via GET request in GitLab Geo. - if Gitlab::Database.read_only? + # In some cases, we also force the filter to not be persisted with the `persist_filter` param + if Gitlab::Database.read_only? || params[:persist_filter] == 'false' notes_filter_param || current_user&.notes_filter_for(issuable) else notes_filter = current_user&.set_notes_filter(notes_filter_param, issuable) || notes_filter_param diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index ccd54b369fa..b709ac85e39 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -11,7 +11,7 @@ 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) + push_frontend_feature_flag(:environment_metrics_use_prometheus_endpoint, default_enabled: true) push_frontend_feature_flag(:environment_metrics_show_multiple_dashboards) push_frontend_feature_flag(:environment_metrics_additional_panel_types) push_frontend_feature_flag(:prometheus_computed_alerts) @@ -165,7 +165,8 @@ class Projects::EnvironmentsController < Projects::ApplicationController project, current_user, environment, - embedded: params[:embedded] + dashboard_path: params[:dashboard], + **dashboard_params.to_h.symbolize_keys ) elsif Feature.enabled?(:environment_metrics_show_multiple_dashboards, project) result = dashboard_finder.find( @@ -233,6 +234,10 @@ class Projects::EnvironmentsController < Projects::ApplicationController params.require([:start, :end]) end + def dashboard_params + params.permit(:embedded, :group, :title, :y_label) + end + def dashboard_finder Gitlab::Metrics::Dashboard::Finder end diff --git a/app/controllers/projects/starrers_controller.rb b/app/controllers/projects/starrers_controller.rb new file mode 100644 index 00000000000..c8facea1d70 --- /dev/null +++ b/app/controllers/projects/starrers_controller.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +class Projects::StarrersController < Projects::ApplicationController + include SortingHelper + + def index + @starrers = UsersStarProjectsFinder.new(@project, params, current_user: @current_user).execute + + # Normally the number of public starrers is equal to the number of visible + # starrers. We need to fix the counts in two cases: when the current user + # is an admin (and can see everything) and when the current user has a + # private profile and has starred the project (and can see itself). + @public_count = + if @current_user&.admin? + @starrers.with_public_profile.count + elsif @current_user&.private_profile && has_starred_project?(@starrers) + @starrers.size - 1 + else + @starrers.size + end + + @total_count = @project.starrers.size + @private_count = @total_count - @public_count + + @sort = params[:sort].presence || sort_value_name + @starrers = @starrers.sort_by_attribute(@sort).page(params[:page]) + end + + private + + def has_starred_project?(starrers) + starrers.first { |starrer| starrer.user_id == current_user.id } + end +end diff --git a/app/controllers/projects/variables_controller.rb b/app/controllers/projects/variables_controller.rb index 646728e8167..1dffc57fcf0 100644 --- a/app/controllers/projects/variables_controller.rb +++ b/app/controllers/projects/variables_controller.rb @@ -38,6 +38,6 @@ class Projects::VariablesController < Projects::ApplicationController end def variable_params_attributes - %i[id variable_type key secret_value protected masked _destroy] + %i[id variable_type key secret_value protected masked environment_scope _destroy] end end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 072d62ddf38..91e0efcf45f 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -17,7 +17,7 @@ class UsersController < ApplicationController prepend_before_action(only: [:show]) { authenticate_sessionless_user!(:rss) } before_action :user, except: [:exists] before_action :authorize_read_user_profile!, - only: [:calendar, :calendar_activities, :groups, :projects, :contributed_projects, :snippets] + only: [:calendar, :calendar_activities, :groups, :projects, :contributed_projects, :starred_projects, :snippets] def show respond_to do |format| @@ -57,27 +57,30 @@ class UsersController < ApplicationController def projects load_projects - skip_pagination = Gitlab::Utils.to_boolean(params[:skip_pagination]) - skip_namespace = Gitlab::Utils.to_boolean(params[:skip_namespace]) - compact_mode = Gitlab::Utils.to_boolean(params[:compact_mode]) - - respond_to do |format| - format.html { render 'show' } - format.json do - pager_json("shared/projects/_list", @projects.count, projects: @projects, skip_pagination: skip_pagination, skip_namespace: skip_namespace, compact_mode: compact_mode) - end - end + present_projects(@projects) end def contributed load_contributed_projects + present_projects(@contributed_projects) + end + + def starred + load_starred_projects + + present_projects(@starred_projects) + end + + def present_projects(projects) + skip_pagination = Gitlab::Utils.to_boolean(params[:skip_pagination]) + skip_namespace = Gitlab::Utils.to_boolean(params[:skip_namespace]) + compact_mode = Gitlab::Utils.to_boolean(params[:compact_mode]) + respond_to do |format| format.html { render 'show' } format.json do - render json: { - html: view_to_html_string("shared/projects/_list", projects: @contributed_projects) - } + pager_json("shared/projects/_list", projects.count, projects: projects, skip_pagination: skip_pagination, skip_namespace: skip_namespace, compact_mode: compact_mode) end end end @@ -120,6 +123,10 @@ class UsersController < ApplicationController ContributedProjectsFinder.new(user).execute(current_user) end + def starred_projects + StarredProjectsFinder.new(user, current_user: current_user).execute + end + def contributions_calendar @contributions_calendar ||= Gitlab::ContributionsCalendar.new(user, current_user) end @@ -145,6 +152,12 @@ class UsersController < ApplicationController prepare_projects_for_rendering(@contributed_projects) end + def load_starred_projects + @starred_projects = starred_projects + + prepare_projects_for_rendering(@starred_projects) + end + def load_groups @groups = JoinedGroupsFinder.new(user).execute(current_user) diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 86970ae3219..1773ac2d508 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -484,19 +484,22 @@ class IssuableFinder # rubocop: disable CodeReuse/ActiveRecord def by_milestone(items) - return items unless milestones? - return items if filter_by_any_milestone? - - if filter_by_no_milestone? - items.left_joins_milestones.where(milestone_id: nil) - elsif filter_by_upcoming_milestone? - upcoming_ids = Milestone.upcoming_ids(projects, related_groups) - items.left_joins_milestones.where(milestone_id: upcoming_ids) - elsif filter_by_started_milestone? - items.left_joins_milestones.merge(Milestone.started) - else - items.with_milestone(params[:milestone_title]) + if milestones? + if filter_by_no_milestone? + items = items.left_joins_milestones.where(milestone_id: [-1, nil]) + elsif filter_by_any_milestone? + items = items.any_milestone + elsif filter_by_upcoming_milestone? + upcoming_ids = Milestone.upcoming_ids(projects, related_groups) + items = items.left_joins_milestones.where(milestone_id: upcoming_ids) + elsif filter_by_started_milestone? + items = items.left_joins_milestones.merge(Milestone.started) + else + items = items.with_milestone(params[:milestone_title]) + end end + + items end # rubocop: enable CodeReuse/ActiveRecord diff --git a/app/finders/starred_projects_finder.rb b/app/finders/starred_projects_finder.rb new file mode 100644 index 00000000000..fcb469d1d17 --- /dev/null +++ b/app/finders/starred_projects_finder.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class StarredProjectsFinder < ProjectsFinder + def initialize(user, params: {}, current_user: nil) + super( + params: params, + current_user: current_user, + project_ids_relation: user.starred_projects.select(:id) + ) + end +end diff --git a/app/finders/users_star_projects_finder.rb b/app/finders/users_star_projects_finder.rb new file mode 100644 index 00000000000..49c4e087b4b --- /dev/null +++ b/app/finders/users_star_projects_finder.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +class UsersStarProjectsFinder + include CustomAttributesFilter + + attr_accessor :params + + def initialize(project, params = {}, current_user: nil) + @params = params + @project = project + @current_user = current_user + end + + def execute + stars = UsersStarProject.all + stars = by_project(stars) + stars = by_search(stars) + stars = filter_visible_profiles(stars) + + stars + end + + private + + def by_search(items) + params[:search].present? ? items.search(params[:search]) : items + end + + def by_project(items) + items.by_project(@project) + end + + def filter_visible_profiles(items) + items.with_visible_profile(@current_user) + end +end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 8d0079a4dd3..71c9c121e48 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -601,6 +601,11 @@ module ProjectsHelper end end + def filter_starrer_path(options = {}) + options = params.slice(:sort).merge(options).permit! + "#{request.path}?#{options.to_param}" + end + def sidebar_projects_paths %w[ projects#show diff --git a/app/helpers/services_helper.rb b/app/helpers/services_helper.rb index 01ccf163b45..d4b50b7ecfb 100644 --- a/app/helpers/services_helper.rb +++ b/app/helpers/services_helper.rb @@ -39,7 +39,7 @@ module ServicesHelper end def disable_fields_service?(service) - service.is_a?(KubernetesService) || (!current_controller?("admin/services") && service.deprecated?) + !current_controller?("admin/services") && service.deprecated? end extend self diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb index 15f35645c78..a4eb76a2359 100644 --- a/app/helpers/sorting_helper.rb +++ b/app/helpers/sorting_helper.rb @@ -167,6 +167,15 @@ module SortingHelper } end + def starrers_sort_options_hash + { + sort_value_name => sort_title_name, + sort_value_name_desc => sort_title_name_desc, + sort_value_recently_created => sort_title_recently_starred, + sort_value_oldest_created => sort_title_oldest_starred + } + end + def sortable_item(item, path, sorted_by) link_to item, path, class: sorted_by == item ? 'is-active' : '' end @@ -327,6 +336,10 @@ module SortingHelper s_('SortOptions|Oldest sign in') end + def sort_title_oldest_starred + s_('SortOptions|Oldest starred') + end + def sort_title_oldest_updated s_('SortOptions|Oldest updated') end @@ -347,6 +360,10 @@ module SortingHelper s_('SortOptions|Recent sign in') end + def sort_title_recently_starred + s_('SortOptions|Recently starred') + end + def sort_title_recently_updated s_('SortOptions|Last updated') end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index 73ca17c6605..e38e3378e07 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -89,7 +89,7 @@ module UsersHelper tabs = [] if can?(current_user, :read_user_profile, @user) - tabs += [:overview, :activity, :groups, :contributed, :projects, :snippets] + tabs += [:overview, :activity, :groups, :contributed, :projects, :starred, :snippets] end tabs diff --git a/app/models/active_session.rb b/app/models/active_session.rb index fdd210f0fba..00192b1da59 100644 --- a/app/models/active_session.rb +++ b/app/models/active_session.rb @@ -136,8 +136,10 @@ class ActiveSession # only the single key entries are automatically expired by redis, the # lookup entries in the set need to be removed manually. session_ids_and_entries = session_ids.zip(entries) - session_ids_and_entries.reject { |_session_id, entry| entry }.each do |session_id, _entry| - redis.srem(lookup_key_name(user.id), session_id) + redis.pipelined do + session_ids_and_entries.reject { |_session_id, entry| entry }.each do |session_id, _entry| + redis.srem(lookup_key_name(user.id), session_id) + end end entries.compact diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 9dbcef8abaa..cb6346421ec 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -10,6 +10,8 @@ class ApplicationSetting < ApplicationRecord 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 + belongs_to :instance_administration_project, class_name: "Project" + # Include here so it can override methods from # `add_authentication_token_field` # We don't prepend for now because otherwise we'll need to diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb index a77bbef0fca..760872d3e6b 100644 --- a/app/models/ci/variable.rb +++ b/app/models/ci/variable.rb @@ -6,6 +6,7 @@ module Ci include HasVariable include Presentable include Maskable + prepend HasEnvironmentScope belongs_to :project diff --git a/app/models/concerns/cacheable_attributes.rb b/app/models/concerns/cacheable_attributes.rb index 53dff2adfc3..0c800621a55 100644 --- a/app/models/concerns/cacheable_attributes.rb +++ b/app/models/concerns/cacheable_attributes.rb @@ -5,6 +5,8 @@ module CacheableAttributes included do after_commit { self.class.expire } + + private_class_method :request_store_cache_key end class_methods do @@ -32,7 +34,11 @@ module CacheableAttributes end def cached - Gitlab::SafeRequestStore[:"#{name}_cached_attributes"] ||= retrieve_from_cache + Gitlab::SafeRequestStore[request_store_cache_key] ||= retrieve_from_cache + end + + def request_store_cache_key + :"#{name}_cached_attributes" end def retrieve_from_cache @@ -58,6 +64,7 @@ module CacheableAttributes end def expire + Gitlab::SafeRequestStore.delete(request_store_cache_key) cache_backend.delete(cache_key) rescue # Gracefully handle when Redis is not available. For example, diff --git a/app/models/concerns/has_environment_scope.rb b/app/models/concerns/has_environment_scope.rb new file mode 100644 index 00000000000..9553abe4dd3 --- /dev/null +++ b/app/models/concerns/has_environment_scope.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +module HasEnvironmentScope + extend ActiveSupport::Concern + + prepended do + validates( + :environment_scope, + presence: true, + format: { with: ::Gitlab::Regex.environment_scope_regex, + message: ::Gitlab::Regex.environment_scope_regex_message } + ) + + ## + # Select rows which have a scope that matches the given environment name. + # Rows are ordered by relevance, by default. The most relevant row is + # placed at the end of a list. + # + # options: + # - relevant_only: (boolean) + # You can get the most relevant row only. Other rows are not be + # selected even if its scope matches the environment name. + # This is equivalent to using `#last` from SQL standpoint. + # + scope :on_environment, -> (environment_name, relevant_only: false) do + order_direction = relevant_only ? 'DESC' : 'ASC' + + where = <<~SQL + environment_scope IN (:wildcard, :environment_name) OR + :environment_name LIKE + #{::Gitlab::SQL::Glob.to_like('environment_scope')} + SQL + + order = <<~SQL + CASE environment_scope + WHEN :wildcard THEN 0 + WHEN :environment_name THEN 2 + ELSE 1 + END #{order_direction} + SQL + + values = { + wildcard: '*', + environment_name: environment_name + } + + sanitized_order_sql = sanitize_sql_array([order, values]) + + # The query is trying to find variables with scopes matching the + # current environment name. Suppose the environment name is + # 'review/app', and we have variables with environment scopes like: + # * variable A: review + # * variable B: review/app + # * variable C: review/* + # * variable D: * + # And the query should find variable B, C, and D, because it would + # try to convert the scope into a LIKE pattern for each variable: + # * A: review + # * B: review/app + # * C: review/% + # * D: % + # Note that we'll match % and _ literally therefore we'll escape them. + # In this case, B, C, and D would match. We also want to prioritize + # the exact matched name, and put * last, and everything else in the + # middle. So the order should be: D < C < B + relation = where(where, values) + .order(Arel.sql(sanitized_order_sql)) # `order` cannot escape for us! + + relation = relation.limit(1) if relevant_only + + relation + end + end + + def environment_scope=(new_environment_scope) + super(new_environment_scope.to_s.strip) + end +end diff --git a/app/models/concerns/maskable.rb b/app/models/concerns/maskable.rb index e0f2c41b836..d70e47bc4ff 100644 --- a/app/models/concerns/maskable.rb +++ b/app/models/concerns/maskable.rb @@ -7,9 +7,10 @@ module Maskable # * No escape characters # * No variables # * No spaces - # * Minimal length of 8 characters from the Base64 alphabets (RFC4648) + # * Minimal length of 8 characters + # * Characters must be from the Base64 alphabet (RFC4648) with the addition of @ and : # * Absolutely no fun is allowed - REGEX = /\A[a-zA-Z0-9_+=\/-]{8,}\z/.freeze + REGEX = /\A[a-zA-Z0-9_+=\/@:-]{8,}\z/.freeze included do validates :masked, inclusion: { in: [true, false] } diff --git a/app/models/issue.rb b/app/models/issue.rb index 164858dc432..bc5ec94081b 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -43,6 +43,7 @@ class Issue < ApplicationRecord validates :project, presence: true alias_attribute :parent_ids, :project_id + alias_method :issuing_parent, :project scope :in_projects, ->(project_ids) { where(project_id: project_ids) } diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 5e8a6a7d5e5..4c4883fc022 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -192,6 +192,7 @@ class MergeRequest < ApplicationRecord alias_attribute :project, :target_project alias_attribute :project_id, :target_project_id alias_attribute :auto_merge_enabled, :merge_when_pipeline_succeeds + alias_method :issuing_parent, :target_project def self.reference_prefix '!' diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 60266992ee1..2ad2838111e 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -4,8 +4,8 @@ class Milestone < ApplicationRecord # Represents a "No Milestone" state used for filtering Issues and Merge # Requests that have no milestone assigned. MilestoneStruct = Struct.new(:title, :name, :id) - None = MilestoneStruct.new('No Milestone', 'No Milestone', -1) - Any = MilestoneStruct.new('Any Milestone', '', nil) + None = MilestoneStruct.new('No Milestone', 'No Milestone', 0) + Any = MilestoneStruct.new('Any Milestone', '', -1) Upcoming = MilestoneStruct.new('Upcoming', '#upcoming', -2) Started = MilestoneStruct.new('Started', '#started', -3) diff --git a/app/models/project.rb b/app/models/project.rb index 960795b73cb..a6e43efa1f3 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -162,7 +162,6 @@ class Project < ApplicationRecord has_one :bugzilla_service has_one :gitlab_issue_tracker_service, inverse_of: :project has_one :external_wiki_service - has_one :kubernetes_service, inverse_of: :project has_one :prometheus_service, inverse_of: :project has_one :mock_ci_service has_one :mock_deployment_service @@ -1828,11 +1827,16 @@ class Project < ApplicationRecord end def ci_variables_for(ref:, environment: nil) - # EE would use the environment - if protected_for?(ref) - variables + result = if protected_for?(ref) + variables + else + variables.unprotected + end + + if environment + result.on_environment(environment) else - variables.unprotected + result.where(environment_scope: '*') end end diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb deleted file mode 100644 index 9f5c226f4c9..00000000000 --- a/app/models/project_services/kubernetes_service.rb +++ /dev/null @@ -1,133 +0,0 @@ -# frozen_string_literal: true - -class KubernetesService < Service - default_value_for :category, 'deployment' - - # Namespace defaults to the project path, but can be overridden in case that - # is an invalid or inappropriate name - prop_accessor :namespace - - # Access to kubernetes is directly through the API - prop_accessor :api_url - - # Bearer authentication - # TODO: user/password auth, client certificates - prop_accessor :token - - # Provide a custom CA bundle for self-signed deployments - prop_accessor :ca_pem - - with_options presence: true, if: :activated? do - validates :api_url, public_url: true - validates :token - end - - before_validation :enforce_namespace_to_lower_case - - attr_accessor :skip_deprecation_validation - - validate :deprecation_validation, unless: :skip_deprecation_validation - - validates :namespace, - allow_blank: true, - length: 1..63, - if: :activated?, - format: { - with: Gitlab::Regex.kubernetes_namespace_regex, - message: Gitlab::Regex.kubernetes_namespace_regex_message - } - - def self.supported_events - %w() - end - - def can_test? - false - end - - def initialize_properties - self.properties = {} if properties.nil? - end - - def title - 'Kubernetes' - end - - def description - 'Kubernetes / OpenShift integration' - end - - def self.to_param - 'kubernetes' - end - - def fields - [ - { type: 'text', - name: 'api_url', - title: 'API URL', - placeholder: 'Kubernetes API URL, like https://kube.example.com/' }, - { type: 'textarea', - name: 'ca_pem', - title: 'CA Certificate', - placeholder: 'Certificate Authority bundle (PEM format)' }, - { type: 'text', - name: 'namespace', - title: 'Project namespace (optional/unique)', - placeholder: namespace_placeholder }, - { type: 'text', - name: 'token', - title: 'Token', - placeholder: 'Service token' } - ] - end - - def deprecated? - true - end - - def editable? - false - end - - def deprecation_message - content = if project - _("Kubernetes service integration has been disabled. Fields on this page are not used by GitLab, you can configure your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page") % { - url: Gitlab::Routing.url_helpers.project_clusters_path(project) - } - else - _("The instance-level Kubernetes service integration is disabled. Your data has been migrated to an <a href=\"%{url}\"/>instance-level cluster</a>.") % { - url: Gitlab::Routing.url_helpers.admin_clusters_path - } - end - - content.html_safe - end - - TEMPLATE_PLACEHOLDER = 'Kubernetes namespace'.freeze - - private - - def namespace_placeholder - default_namespace || TEMPLATE_PLACEHOLDER - end - - def default_namespace - return unless project - - slug = "#{project.path}-#{project.id}".downcase - slug.gsub(/[^-a-z0-9]/, '-').gsub(/^-+/, '') - end - - def enforce_namespace_to_lower_case - self.namespace = self.namespace&.downcase - end - - def deprecation_validation - return if active_changed?(from: true, to: false) || (new_record? && !active?) - - if deprecated? - errors[:base] << deprecation_message - end - end -end diff --git a/app/models/prometheus_metric.rb b/app/models/prometheus_metric.rb index b8e7673dcf5..c7786500c5c 100644 --- a/app/models/prometheus_metric.rb +++ b/app/models/prometheus_metric.rb @@ -32,6 +32,10 @@ class PrometheusMetric < ApplicationRecord Gitlab::Prometheus::Metric.new(id: id, title: title, required_metrics: required_metrics, weight: 0, y_label: y_label, queries: queries) end + def to_metric_hash + queries.first.merge(metric_id: id) + end + def queries [ { diff --git a/app/models/prometheus_metric_enums.rb b/app/models/prometheus_metric_enums.rb index 6cb22cc69cd..d58f825f222 100644 --- a/app/models/prometheus_metric_enums.rb +++ b/app/models/prometheus_metric_enums.rb @@ -9,13 +9,17 @@ module PrometheusMetricEnums aws_elb: -3, nginx: -4, kubernetes: -5, - nginx_ingress: -6, + nginx_ingress: -6 + }.merge(custom_groups).freeze + end - # custom/user groups + # custom/user groups + def self.custom_groups + { business: 0, response: 1, system: 2 - } + }.freeze end def self.group_details @@ -50,16 +54,20 @@ module PrometheusMetricEnums group_title: _('System metrics (Kubernetes)'), required_metrics: %w(container_memory_usage_bytes container_cpu_usage_seconds_total), priority: 5 - }.freeze, + }.freeze + }.merge(custom_group_details).freeze + end - # custom/user groups + # custom/user groups + def self.custom_group_details + { business: { group_title: _('Business metrics (Custom)'), priority: 0 }.freeze, response: { group_title: _('Response metrics (Custom)'), - priority: -5 + priority: -5 }.freeze, system: { group_title: _('System metrics (Custom)'), diff --git a/app/models/service.rb b/app/models/service.rb index 752467622f2..f6d8fb1fb46 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -260,7 +260,6 @@ class Service < ApplicationRecord hipchat irker jira - kubernetes mattermost_slash_commands mattermost packagist diff --git a/app/models/user.rb b/app/models/user.rb index 4630552e02e..ac83c8e3256 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -282,6 +282,17 @@ class User < ApplicationRecord scope :for_todos, -> (todos) { where(id: todos.select(:user_id)) } scope :with_emails, -> { preload(:emails) } scope :with_dashboard, -> (dashboard) { where(dashboard: dashboard) } + scope :with_public_profile, -> { where(private_profile: false) } + + def self.with_visible_profile(user) + return with_public_profile if user.nil? + + if user.admin? + all + else + with_public_profile.or(where(id: user.id)) + end + end # Limits the users to those that have TODOs, optionally in the given state. # diff --git a/app/models/users_star_project.rb b/app/models/users_star_project.rb index 9be6bd2e6f3..3c7a805cc5c 100644 --- a/app/models/users_star_project.rb +++ b/app/models/users_star_project.rb @@ -1,10 +1,37 @@ # frozen_string_literal: true class UsersStarProject < ApplicationRecord + include Sortable + belongs_to :project, counter_cache: :star_count, touch: true belongs_to :user validates :user, presence: true validates :user_id, uniqueness: { scope: [:project_id] } validates :project, presence: true + + alias_attribute :starred_since, :created_at + + scope :order_user_name_asc, -> { joins(:user).merge(User.order_name_asc) } + scope :order_user_name_desc, -> { joins(:user).merge(User.order_name_desc) } + scope :by_project, -> (project) { where(project_id: project.id) } + scope :with_visible_profile, -> (user) { joins(:user).merge(User.with_visible_profile(user)) } + scope :with_public_profile, -> { joins(:user).merge(User.with_public_profile) } + + class << self + def sort_by_attribute(method) + order_method = method || 'id_desc' + + case order_method.to_s + when 'name_asc' then order_user_name_asc + when 'name_desc' then order_user_name_desc + else + order_by(order_method) + end + end + + def search(query) + joins(:user).merge(User.search(query)) + end + end end diff --git a/app/presenters/blob_presenter.rb b/app/presenters/blob_presenter.rb index f85c1a237a6..2cf3278d240 100644 --- a/app/presenters/blob_presenter.rb +++ b/app/presenters/blob_presenter.rb @@ -3,13 +3,12 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated presents :blob - def highlight(since: nil, to: nil, plain: nil) + def highlight(plain: nil) load_all_blob_data Gitlab::Highlight.highlight( blob.path, - limited_blob_data(since: since, to: to), - since: since, + blob.data, language: blob.language_from_gitattributes, plain: plain ) @@ -24,18 +23,4 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated def load_all_blob_data blob.load_all_data! if blob.respond_to?(:load_all_data!) end - - def limited_blob_data(since: nil, to: nil) - return blob.data if since.blank? || to.blank? - - limited_blob_lines(since, to).join - end - - def limited_blob_lines(since, to) - all_lines[since - 1..to - 1] - end - - def all_lines - @all_lines ||= blob.data.lines - end end diff --git a/app/presenters/blobs/unfold_presenter.rb b/app/presenters/blobs/unfold_presenter.rb index f4672d22fc9..21a1e1309e0 100644 --- a/app/presenters/blobs/unfold_presenter.rb +++ b/app/presenters/blobs/unfold_presenter.rb @@ -21,19 +21,20 @@ module Blobs load_all_blob_data @subject = blob + @all_lines = blob.data.lines super(params) if full? - self.attributes = { since: 1, to: all_lines.size, bottom: false, unfold: false, offset: 0, indent: 0 } + self.attributes = { since: 1, to: @all_lines.size, bottom: false, unfold: false, offset: 0, indent: 0 } end end # Returns an array of Gitlab::Diff::Line with match line added def diff_lines - diff_lines = limited_blob_lines(since, to).map.with_index do |line, index| - full_line = line.delete("\n") + diff_lines = lines.map.with_index do |line, index| + full_line = limited_blob_lines[index].delete("\n") - Gitlab::Diff::Line.new(full_line, nil, nil, nil, nil, rich_text: lines[index]) + Gitlab::Diff::Line.new(full_line, nil, nil, nil, nil, rich_text: line) end add_match_line(diff_lines) @@ -42,7 +43,7 @@ module Blobs end def lines - @lines ||= highlight(since: since, to: to).lines.map(&:html_safe) + @lines ||= limit(highlight.lines).map(&:html_safe) end def match_line_text @@ -58,7 +59,7 @@ module Blobs def add_match_line(diff_lines) return unless unfold? - if bottom? && to < all_lines.size + if bottom? && to < @all_lines.size old_pos = to - offset new_pos = to elsif since != 1 @@ -72,5 +73,15 @@ module Blobs bottom? ? diff_lines.push(match_line) : diff_lines.unshift(match_line) end + + def limited_blob_lines + @limited_blob_lines ||= limit(@all_lines) + end + + def limit(lines) + return lines if full? + + lines[since - 1..to - 1] + end end end diff --git a/app/serializers/variable_entity.rb b/app/serializers/variable_entity.rb index 4d48e13cfca..8b19925f153 100644 --- a/app/serializers/variable_entity.rb +++ b/app/serializers/variable_entity.rb @@ -7,4 +7,5 @@ class VariableEntity < Grape::Entity expose :protected?, as: :protected expose :masked?, as: :masked + expose :environment_scope end diff --git a/app/services/create_snippet_service.rb b/app/services/create_snippet_service.rb index 6f1fce4989e..6e5bf823cc7 100644 --- a/app/services/create_snippet_service.rb +++ b/app/services/create_snippet_service.rb @@ -23,6 +23,7 @@ class CreateSnippetService < BaseService if snippet.save UserAgentDetailService.new(snippet, @request).create + Gitlab::UsageDataCounters::SnippetCounter.count(:create) end snippet diff --git a/app/services/issuable/bulk_update_service.rb b/app/services/issuable/bulk_update_service.rb index 6d215d7a3b9..273a12f386a 100644 --- a/app/services/issuable/bulk_update_service.rb +++ b/app/services/issuable/bulk_update_service.rb @@ -29,7 +29,7 @@ module Issuable items.each do |issuable| next unless can?(current_user, :"update_#{type}", issuable) - update_class.new(issuable.project, current_user, params).execute(issuable) + update_class.new(issuable.issuing_parent, current_user, params).execute(issuable) end { diff --git a/app/services/members/base_service.rb b/app/services/members/base_service.rb index e78affff797..5d69418fb7d 100644 --- a/app/services/members/base_service.rb +++ b/app/services/members/base_service.rb @@ -51,7 +51,9 @@ module Members def enqueue_delete_todos(member) type = member.is_a?(GroupMember) ? 'Group' : 'Project' # don't enqueue immediately to prevent todos removal in case of a mistake - TodosDestroyer::EntityLeaveWorker.perform_in(Todo::WAIT_FOR_DELETE, member.user_id, member.source_id, type) + member.run_after_commit_or_now do + TodosDestroyer::EntityLeaveWorker.perform_in(Todo::WAIT_FOR_DELETE, member.user_id, member.source_id, type) + end end end end diff --git a/app/services/metrics/dashboard/base_embed_service.rb b/app/services/metrics/dashboard/base_embed_service.rb new file mode 100644 index 00000000000..8bb5f4892cb --- /dev/null +++ b/app/services/metrics/dashboard/base_embed_service.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +# Base class for embed services. Contains a few basic helper +# methods that the embed services share. +module Metrics + module Dashboard + class BaseEmbedService < ::Metrics::Dashboard::BaseService + def cache_key + "dynamic_metrics_dashboard_#{identifiers}" + end + + protected + + def dashboard_path + params[:dashboard_path].presence || + ::Metrics::Dashboard::SystemDashboardService::SYSTEM_DASHBOARD_PATH + end + + def group + params[:group] + end + + def title + params[:title] + end + + def y_label + params[:y_label] + end + + def identifiers + [dashboard_path, group, title, y_label].join('|') + end + end + end +end diff --git a/app/services/metrics/dashboard/base_service.rb b/app/services/metrics/dashboard/base_service.rb index b331bf51874..8a42675c66d 100644 --- a/app/services/metrics/dashboard/base_service.rb +++ b/app/services/metrics/dashboard/base_service.rb @@ -5,17 +5,14 @@ module Metrics module Dashboard class BaseService < ::BaseService - PROCESSING_ERROR = Gitlab::Metrics::Dashboard::Stages::BaseStage::DashboardProcessingError - NOT_FOUND_ERROR = Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError + include Gitlab::Metrics::Dashboard::Errors def get_dashboard return error('Insufficient permissions.', :unauthorized) unless allowed? success(dashboard: process_dashboard) - rescue NOT_FOUND_ERROR - error("#{dashboard_path} could not be found.", :not_found) - rescue PROCESSING_ERROR => e - error(e.message, :unprocessable_entity) + rescue StandardError => e + handle_errors(e) end # Summary of all known dashboards for the service. diff --git a/app/services/metrics/dashboard/custom_metric_embed_service.rb b/app/services/metrics/dashboard/custom_metric_embed_service.rb new file mode 100644 index 00000000000..50f070989fc --- /dev/null +++ b/app/services/metrics/dashboard/custom_metric_embed_service.rb @@ -0,0 +1,123 @@ +# frozen_string_literal: true + +# Responsible for returning a dashboard containing specified +# custom metrics. Creates panels based on the matching metrics +# stored in the database. +# +# Use Gitlab::Metrics::Dashboard::Finder to retrive dashboards. +module Metrics + module Dashboard + class CustomMetricEmbedService < ::Metrics::Dashboard::BaseEmbedService + extend ::Gitlab::Utils::Override + include Gitlab::Utils::StrongMemoize + include Gitlab::Metrics::Dashboard::Defaults + + class << self + # Determines whether the provided params are sufficient + # to uniquely identify a panel composed of user-defined + # custom metrics from the DB. + def valid_params?(params) + [ + params[:embedded], + valid_dashboard?(params[:dashboard_path]), + valid_group_title?(params[:group]), + params[:title].present?, + params.has_key?(:y_label) + ].all? + end + + private + + # A group title is valid if it is one of the limited + # options the user can select in the UI. + def valid_group_title?(group) + PrometheusMetricEnums + .custom_group_details + .map { |_, details| details[:group_title] } + .include?(group) + end + + # 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) + end + end + + # Returns a new dashboard with only the matching + # metrics from the system dashboard, stripped of + # group info. + # + # Note: This overrides the method #raw_dashboard, + # which means the result will not be cached. This + # is because we are inserting DB info into the + # dashboard before post-processing. This ensures + # we aren't acting on deleted or out-of-date metrics. + # + # @return [Hash] + override :raw_dashboard + def raw_dashboard + panels_not_found!(identifiers) if panels.empty? + + { 'panel_groups' => [{ 'panels' => panels }] } + end + + private + + # Generated dashboard panels for each metric which + # matches the provided input. + # @return [Array<Hash>] + def panels + strong_memoize(:panels) do + metrics.map { |metric| panel_for_metric(metric) } + end + end + + # Metrics which match the provided inputs. + # There may be multiple metrics, but they should be + # displayed in a single panel/chart. + # @return [ActiveRecord::AssociationRelation<PromtheusMetric>] + # rubocop: disable CodeReuse/ActiveRecord + def metrics + project.prometheus_metrics.where( + group: group_key, + title: title, + y_label: y_label + ) + end + # rubocop: enable CodeReuse/ActiveRecord + + # Returns a symbol representing the group that + # the dashboard's group title belongs to. + # It will be one of the keys found under + # PrometheusMetricEnums.custom_groups. + # + # @return [String] + def group_key + strong_memoize(:group_key) do + PrometheusMetricEnums + .group_details + .find { |_, details| details[:group_title] == group } + .first + .to_s + end + end + + # Returns a representation of a PromtheusMetric + # as a dashboard panel. As the panel is generated + # on the fly, we're using default values for info + # not represented in the DB. + # + # @return [Hash] + def panel_for_metric(metric) + { + type: DEFAULT_PANEL_TYPE, + weight: DEFAULT_PANEL_WEIGHT, + title: metric.title, + y_label: metric.y_label, + metrics: [metric.to_metric_hash] + } + end + end + end +end diff --git a/app/services/metrics/dashboard/default_embed_service.rb b/app/services/metrics/dashboard/default_embed_service.rb index 8b01b44fc98..e1bd98bd5c2 100644 --- a/app/services/metrics/dashboard/default_embed_service.rb +++ b/app/services/metrics/dashboard/default_embed_service.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true # Responsible for returning a filtered system dashboard -# containing only the default embedded metrics. In future, -# this class may be updated to support filtering to -# alternate metrics/panels. +# containing only the default embedded metrics. This class +# operates by selecting metrics directly from the system +# dashboard. # # Why isn't this filtering in a processing stage? By filtering # here, we ensure the dynamically-determined dashboard is cached. @@ -11,7 +11,7 @@ # Use Gitlab::Metrics::Dashboard::Finder to retrive dashboards. module Metrics module Dashboard - class DefaultEmbedService < ::Metrics::Dashboard::BaseService + class DefaultEmbedService < ::Metrics::Dashboard::BaseEmbedService # For the default filtering for embedded metrics, # uses the 'id' key in dashboard-yml definition for # identification. @@ -33,10 +33,6 @@ module Metrics { 'panel_groups' => [{ 'panels' => panels }] } end - def cache_key - "dynamic_metrics_dashboard_#{metric_identifiers.join('_')}" - end - private # Returns an array of the panels groups on the @@ -58,6 +54,10 @@ module Metrics def metric_identifiers DEFAULT_EMBEDDED_METRICS_IDENTIFIERS end + + def identifiers + metric_identifiers.join('|') + end end end end diff --git a/app/services/metrics/dashboard/dynamic_embed_service.rb b/app/services/metrics/dashboard/dynamic_embed_service.rb new file mode 100644 index 00000000000..db5b7c9e32a --- /dev/null +++ b/app/services/metrics/dashboard/dynamic_embed_service.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +# Responsible for returning a filtered project dashboard +# containing only the request-provided metrics. The result +# is then cached for future requests. Metrics are identified +# based on a combination of identifiers for now, but the ideal +# would be similar to the approach in DefaultEmbedService, but +# a single unique identifier is not currently available across +# all metric types (custom, project-defined, cluster, or system). +# +# Use Gitlab::Metrics::Dashboard::Finder to retrive dashboards. +module Metrics + module Dashboard + class DynamicEmbedService < ::Metrics::Dashboard::BaseEmbedService + include Gitlab::Utils::StrongMemoize + + class << self + # Determines whether the provided params are sufficient + # to uniquely identify a panel from a yml-defined dashboard. + # + # See https://docs.gitlab.com/ee/user/project/integrations/prometheus.html#defining-custom-dashboards-per-project + # for additional info on defining custom dashboards. + def valid_params?(params) + [ + params[:embedded], + params[:group].present?, + params[:title].present?, + params[:y_label] + ].all? + end + end + + # Returns a new dashboard with only the matching + # metrics from the system dashboard, stripped of groups. + # @return [Hash] + def get_raw_dashboard + not_found! if panels.empty? + + { 'panel_groups' => [{ 'panels' => panels }] } + end + + private + + def panels + strong_memoize(:panels) do + not_found! unless base_dashboard + not_found! unless groups = base_dashboard['panel_groups'] + not_found! unless matching_group = find_group(groups) + not_found! unless all_panels = matching_group['panels'] + + find_panels(all_panels) + end + end + + def base_dashboard + strong_memoize(:base_dashboard) do + Gitlab::Metrics::Dashboard::Finder.find_raw(project, dashboard_path: dashboard_path) + end + end + + def find_group(groups) + groups.find do |candidate_group| + candidate_group['group'] == group + end + end + + def find_panels(all_panels) + all_panels.select do |panel| + panel['title'] == title && panel['y_label'] == y_label + end + end + + def not_found! + panels_not_found!(identifiers) + end + end + end +end diff --git a/app/services/notes/base_service.rb b/app/services/notes/base_service.rb index c1260837c12..b4d04c47cc0 100644 --- a/app/services/notes/base_service.rb +++ b/app/services/notes/base_service.rb @@ -9,5 +9,9 @@ module Notes note.noteable.diffs.clear_cache end end + + def increment_usage_counter(note) + Gitlab::UsageDataCounters::NoteCounter.count(:create, note.noteable_type) + end end end diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index 194c4a43dbc..a09272f0d83 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -41,6 +41,7 @@ module Notes todo_service.new_note(note, current_user) clear_noteable_diffs_cache(note) Suggestions::CreateService.new(note).execute + increment_usage_counter(note) end if quick_actions_service.commands_executed_count.to_i > 0 diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb index a1279bfb3a3..5893b8eedff 100644 --- a/app/services/projects/destroy_service.rb +++ b/app/services/projects/destroy_service.rb @@ -173,6 +173,7 @@ module Projects end def remove_registry_tags + return true unless Gitlab.config.registry.enabled return false unless remove_legacy_registry_tags project.container_repositories.find_each do |container_repository| diff --git a/app/services/self_monitoring/project/create_service.rb b/app/services/self_monitoring/project/create_service.rb index 8ffd22de127..c925c6a1610 100644 --- a/app/services/self_monitoring/project/create_service.rb +++ b/app/services/self_monitoring/project/create_service.rb @@ -4,16 +4,22 @@ module SelfMonitoring module Project class CreateService < ::BaseService include Stepable + include Gitlab::Utils::StrongMemoize - DEFAULT_VISIBILITY_LEVEL = Gitlab::VisibilityLevel::INTERNAL - DEFAULT_NAME = 'GitLab Instance Administration' - DEFAULT_DESCRIPTION = <<~HEREDOC + VISIBILITY_LEVEL = Gitlab::VisibilityLevel::INTERNAL + PROJECT_NAME = 'GitLab Instance Administration' + PROJECT_DESCRIPTION = <<~HEREDOC This project is automatically generated and will be used to help monitor this GitLab instance. HEREDOC + GROUP_NAME = 'GitLab Instance Administrators' + GROUP_PATH = 'gitlab-instance-administrators' + steps :validate_admins, + :create_group, :create_project, - :add_project_members, + :save_project_id, + :add_group_members, :add_to_whitelist, :add_prometheus_manual_configuration @@ -36,20 +42,60 @@ module SelfMonitoring success end + def create_group + if project_created? + log_info(_('Instance administrators group already exists')) + @group = application_settings.instance_administration_project.owner + return success(group: @group) + end + + admin_user = group_owner + @group = ::Groups::CreateService.new(admin_user, create_group_params).execute + + if @group.persisted? + success(group: @group) + else + error('Could not create group') + end + end + def create_project - admin_user = project_owner + if project_created? + log_info(_('Instance administration project already exists')) + @project = application_settings.instance_administration_project + return success(project: project) + end + + admin_user = group_owner @project = ::Projects::CreateService.new(admin_user, create_project_params).execute if project.persisted? success(project: project) else - log_error("Could not create self-monitoring project. Errors: #{project.errors.full_messages}") - error('Could not create project') + log_error(_("Could not create instance administration project. Errors: %{errors}") % { errors: project.errors.full_messages }) + error(_('Could not create project')) end end - def add_project_members - members = project.add_users(project_maintainers, Gitlab::Access::MAINTAINER) + def save_project_id + return success if project_created? + + result = ApplicationSettings::UpdateService.new( + application_settings, + group_owner, + { instance_administration_project_id: @project.id } + ).execute + + if result + success + else + log_error(_("Could not save instance administration project ID, errors: %{errors}") % { errors: application_settings.errors.full_messages }) + error(_('Could not save project ID')) + end + end + + def add_group_members + members = @group.add_users(group_maintainers, Gitlab::Access::MAINTAINER) errors = members.flat_map { |member| member.errors.full_messages } if errors.any? @@ -68,14 +114,15 @@ module SelfMonitoring return error(_('Prometheus listen_address is not a valid URI')) unless uri result = ApplicationSettings::UpdateService.new( - Gitlab::CurrentSettings.current_application_settings, - project_owner, - outbound_local_requests_whitelist: [uri.normalized_host] + application_settings, + group_owner, + add_to_outbound_local_requests_whitelist: [uri.normalized_host] ).execute if result success else + log_error(_("Could not add prometheus URL to whitelist, errors: %{errors}") % { errors: application_settings.errors.full_messages }) error(_('Could not add prometheus URL to whitelist')) end end @@ -94,6 +141,17 @@ module SelfMonitoring success end + def application_settings + strong_memoize(:application_settings) do + Gitlab::CurrentSettings.expire_current_application_settings + Gitlab::CurrentSettings.current_application_settings + end + end + + def project_created? + application_settings.instance_administration_project.present? + end + def parse_url(uri_string) Addressable::URI.parse(uri_string) rescue Addressable::URI::InvalidURIError, TypeError @@ -114,21 +172,30 @@ module SelfMonitoring @instance_admins ||= User.admins.active end - def project_owner + def group_owner instance_admins.first end - def project_maintainers - # Exclude the first so that the project_owner is not added again as a member. - instance_admins - [project_owner] + def group_maintainers + # Exclude the first so that the group_owner is not added again as a member. + instance_admins - [group_owner] + end + + def create_group_params + { + name: GROUP_NAME, + path: "#{GROUP_PATH}-#{SecureRandom.hex(4)}", + visibility_level: VISIBILITY_LEVEL + } end def create_project_params { initialize_with_readme: true, - visibility_level: DEFAULT_VISIBILITY_LEVEL, - name: DEFAULT_NAME, - description: DEFAULT_DESCRIPTION + visibility_level: VISIBILITY_LEVEL, + name: PROJECT_NAME, + description: PROJECT_DESCRIPTION, + namespace_id: @group.id } end diff --git a/app/services/update_snippet_service.rb b/app/services/update_snippet_service.rb index 15bc1046a4e..2969c360de5 100644 --- a/app/services/update_snippet_service.rb +++ b/app/services/update_snippet_service.rb @@ -25,6 +25,8 @@ class UpdateSnippetService < BaseService snippet.assign_attributes(params) spam_check(snippet, current_user) - snippet.save + snippet.save.tap do |succeeded| + Gitlab::UsageDataCounters::SnippetCounter.count(:update) if succeeded + end end end diff --git a/app/views/admin/services/_form.html.haml b/app/views/admin/services/_form.html.haml index ab08d5c4906..495ee6a04ea 100644 --- a/app/views/admin/services/_form.html.haml +++ b/app/views/admin/services/_form.html.haml @@ -6,6 +6,5 @@ = form_for :service, url: admin_application_settings_service_path, method: :put, html: { class: 'fieldset-form' } do |form| = render 'shared/service_settings', form: form, subject: @service - - unless @service.is_a?(KubernetesService) - .footer-block.row-content-block - = form.submit 'Save', class: 'btn btn-success' + .footer-block.row-content-block + = form.submit 'Save', class: 'btn btn-success' diff --git a/app/views/ci/variables/_environment_scope.html.haml b/app/views/ci/variables/_environment_scope.html.haml new file mode 100644 index 00000000000..15e61d85881 --- /dev/null +++ b/app/views/ci/variables/_environment_scope.html.haml @@ -0,0 +1,21 @@ +- form_field = local_assigns.fetch(:form_field, nil) +- variable = local_assigns.fetch(:variable, nil) + +- if @project + - environment_scope = variable&.environment_scope || '*' + - environment_scope_label = environment_scope == '*' ? s_('CiVariable|All environments') : environment_scope + + %input{ type: "hidden", name: "#{form_field}[variables_attributes][][environment_scope]", value: environment_scope } + = dropdown_tag(environment_scope_label, + options: { wrapper_class: 'ci-variable-body-item js-variable-environment-dropdown-wrapper', + toggle_class: 'js-variable-environment-toggle wide', + filter: true, + dropdown_class: "dropdown-menu-selectable", + placeholder: s_('CiVariable|Search environments'), + footer_content: true, + data: { selected: environment_scope } }) do + %ul.dropdown-footer-list + %li + %button{ class: "dropdown-create-new-item-button js-dropdown-create-new-item", title: s_('CiVariable|New environment') } + = s_('CiVariable|Create wildcard') + %code diff --git a/app/views/ci/variables/_environment_scope_header.html.haml b/app/views/ci/variables/_environment_scope_header.html.haml new file mode 100644 index 00000000000..4ba4ceec16c --- /dev/null +++ b/app/views/ci/variables/_environment_scope_header.html.haml @@ -0,0 +1,2 @@ +.bold.table-section.section-15.append-right-10 + = s_('CiVariables|Scope') diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index 4a9e05951f9..055c46334bb 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -36,11 +36,11 @@ .d-flex.flex-column.flex-md-row.row-content-block.second-block %span.flex-grow-1.align-self-md-center.col-form-label = _("Members with access to %{strongStart}%{group_name}%{strongEnd}").html_safe % { group_name: @group.name, strongStart: '<strong>', strongEnd: '</strong>' } - = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do + = form_tag group_group_members_path(@group), method: :get, class: 'form-inline user-search-form' do .form-group.flex-grow .position-relative.mr-md-2 = search_field_tag :search, params[:search], { placeholder: _('Search'), class: 'form-control', spellcheck: false } - %button.member-search-btn.border-left{ type: "submit", "aria-label" => _("Submit search") } + %button.user-search-btn.border-left{ type: "submit", "aria-label" => _("Submit search") } = icon("search") - if can_manage_members = label_tag '2fa', '2FA', class: 'col-form-label label-bold pr-md-2' @@ -56,11 +56,11 @@ .d-flex.flex-column.flex-md-row.row-content-block.second-block %span.flex-grow-1 = _("Members with pending access to %{strongStart}%{group_name}%{strongEnd}").html_safe % { group_name: @group.name, strongStart: '<strong>', strongEnd: '</strong>' } - = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do + = form_tag group_group_members_path(@group), method: :get, class: 'form-inline user-search-form' do .form-group .position-relative.mr-md-2 = search_field_tag :search_invited, params[:search_invited], { placeholder: _('Search'), class: 'form-control', spellcheck: false } - %button.member-search-btn.border-left{ type: "submit", "aria-label" => _("Submit search") } + %button.user-search-btn.border-left{ type: "submit", "aria-label" => _("Submit search") } = icon("search") %ul.content-list.members-list = render partial: 'shared/members/member', collection: @invited_members, as: :member diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml index 2163446425c..bf077eb09d2 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -1,4 +1,4 @@ -- @can_bulk_update = can?(current_user, :admin_issue, @group) +- @can_bulk_update = can?(current_user, :admin_issue, @group) && @group.feature_available?(:group_bulk_edit) - page_title "Issues" = content_for :meta_tags do diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml index b5a2bab4799..0780fab513b 100644 --- a/app/views/groups/merge_requests.html.haml +++ b/app/views/groups/merge_requests.html.haml @@ -1,4 +1,4 @@ -- @can_bulk_update = can?(current_user, :admin_merge_request, @group) +- @can_bulk_update = can?(current_user, :admin_merge_request, @group) && @group.feature_available?(:group_bulk_edit) - page_title "Merge Requests" diff --git a/app/views/projects/buttons/_star.html.haml b/app/views/projects/buttons/_star.html.haml index 090d1549aa7..02e5297528b 100644 --- a/app/views/projects/buttons/_star.html.haml +++ b/app/views/projects/buttons/_star.html.haml @@ -8,7 +8,8 @@ = sprite_icon('star-o', { css_class: 'icon' }) %span= s_('ProjectOverview|Star') %span.star-count.count-badge-count.d-flex.align-items-center - = @project.star_count + = link_to project_starrers_path(@project), title: n_(s_('ProjectOverview|Starrer'), s_('ProjectOverview|Starrers'), @project.star_count), class: 'count' do + = @project.star_count - else .count-badge.d-inline-flex.align-item-stretch.append-right-8 @@ -16,4 +17,5 @@ = sprite_icon('star-o', { css_class: 'icon' }) %span= s_('ProjectOverview|Star') %span.star-count.count-badge-count.d-flex.align-items-center - = @project.star_count + = link_to project_starrers_path(@project), title: n_(s_('ProjectOverview|Starrer'), s_('ProjectOverview|Starrers'), @project.star_count), class: 'count' do + = @project.star_count diff --git a/app/views/projects/forks/_fork_button.html.haml b/app/views/projects/forks/_fork_button.html.haml index 3f0798a898d..c7ed6a5094d 100644 --- a/app/views/projects/forks/_fork_button.html.haml +++ b/app/views/projects/forks/_fork_button.html.haml @@ -5,9 +5,9 @@ .bordered-box.fork-thumbnail.text-center.prepend-left-default.append-right-default.prepend-top-default.append-bottom-default.forked = link_to project_path(forked_project) do - if /no_((\w*)_)*avatar/.match(avatar) - = group_icon(namespace, class: "avatar rect-avatar s100 identicon") + = group_icon(namespace, class: "avatar rect-avatar s100 identicon mx-auto") - else - .avatar-container.s100 + .avatar-container.s100.mx-auto = image_tag(avatar, class: "avatar s100") %h5.prepend-top-default = namespace.human_name @@ -18,9 +18,9 @@ class: ("disabled has-tooltip" unless can_create_project), title: (_('You have reached your project limit') unless can_create_project) do - if /no_((\w*)_)*avatar/.match(avatar) - = group_icon(namespace, class: "avatar rect-avatar s100 identicon") + = group_icon(namespace, class: "avatar rect-avatar s100 identicon mx-auto") - else - .avatar-container.s100 + .avatar-container.s100.mx-auto = image_tag(avatar, class: "avatar s100") %h5.prepend-top-default = namespace.human_name diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml index 5d307d6a70d..2b56ada8b73 100644 --- a/app/views/projects/pipelines/_info.html.haml +++ b/app/views/projects/pipelines/_info.html.haml @@ -43,7 +43,7 @@ } } Auto DevOps - if @pipeline.detached_merge_request_pipeline? - %span.js-pipeline-url-mergerequest.badge.badge-info.has-tooltip{ title: "This pipeline is run on the source branch" } + %span.js-pipeline-url-mergerequest.badge.badge-info.has-tooltip{ title: "The code of a detached pipeline is tested against the source branch instead of merged results" } detached - if @pipeline.stuck? %span.js-pipeline-url-stuck.badge.badge-warning diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index f220299ec30..5310c1fad01 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -6,11 +6,11 @@ %span.flex-project-title = _("Members of <strong>%{project_name}</strong>").html_safe % { project_name: sanitize(project.name, tags: []) } %span.badge.badge-pill= members.total_count - = form_tag project_project_members_path(project), method: :get, class: 'form-inline member-search-form flex-project-members-form' do + = form_tag project_project_members_path(project), method: :get, class: 'form-inline user-search-form flex-users-form' do .form-group .position-relative = search_field_tag :search, params[:search], { placeholder: _('Find existing members by name'), class: 'form-control', spellcheck: false } - %button.member-search-btn{ type: "submit", "aria-label" => _("Submit search") } + %button.user-search-btn{ type: "submit", "aria-label" => _("Submit search") } = icon("search") = render 'shared/members/sort_dropdown' %ul.content-list.members-list.qa-members-list diff --git a/app/views/projects/services/prometheus/_metrics.html.haml b/app/views/projects/services/prometheus/_metrics.html.haml index 3aefb3fdbb9..7685dee08fc 100644 --- a/app/views/projects/services/prometheus/_metrics.html.haml +++ b/app/views/projects/services/prometheus/_metrics.html.haml @@ -8,15 +8,16 @@ .col-lg-9 .card.js-panel-monitored-metrics{ data: { active_metrics: active_common_project_prometheus_metrics_path(project, :json), metrics_help_path: help_page_path('user/project/integrations/prometheus_library/index') } } .card-header - = s_('PrometheusService|Common metrics') + %strong + = s_('PrometheusService|Common metrics') %span.badge.badge-pill.js-monitored-count 0 .card-body .loading-metrics.js-loading-metrics - %p.prepend-top-10.prepend-left-10 + %p.m-3 = icon('spinner spin', class: 'metrics-load-spinner') = s_('PrometheusService|Finding and configuring metrics...') .empty-metrics.hidden.js-empty-metrics - %p.text-tertiary.prepend-top-10.prepend-left-10 + %p.text-tertiary.m-3 = s_('PrometheusService|Waiting for your first deployment to an environment to find common metrics') %ul.list-unstyled.metrics-list.hidden.js-metrics-list diff --git a/app/views/projects/starrers/_starrer.html.haml b/app/views/projects/starrers/_starrer.html.haml new file mode 100644 index 00000000000..377d62f8abd --- /dev/null +++ b/app/views/projects/starrers/_starrer.html.haml @@ -0,0 +1,19 @@ +- starrer = local_assigns.fetch(:starrer) + +.col-lg-3.col-md-4.col-sm-12 + .card + .card-body + = image_tag avatar_icon_for_user(starrer.user, 40), class: "avatar s40", alt: '' + + .user-info + .block-truncated + = link_to starrer.user.name, user_path(starrer.user), class: 'user js-user-link', data: { user_id: starrer.user.id } + + .block-truncated + %span.cgray= starrer.user.to_reference + + - if starrer.user == current_user + %span.badge.badge-success.prepend-left-5= _("It's you") + + .block-truncated + = time_ago_with_tooltip(starrer.starred_since) diff --git a/app/views/projects/starrers/index.html.haml b/app/views/projects/starrers/index.html.haml new file mode 100644 index 00000000000..e55ed99f643 --- /dev/null +++ b/app/views/projects/starrers/index.html.haml @@ -0,0 +1,32 @@ +- page_title _("Starrers") + +.top-area.adjust + .nav-text + - full_count_title = "#{@public_count} public and #{@private_count} private" + #{pluralize(@total_count, 'starrer')}: #{full_count_title} + - if @starrers.size > 0 || params[:search].present? + .nav-controls + = form_tag request.original_url, method: :get, class: 'form-inline user-search-form flex-users-form' do + .form-group + .position-relative + = search_field_tag :search, params[:search], { placeholder: _('Search'), class: 'form-control', spellcheck: false } + %button.user-search-btn{ type: "submit", "aria-label" => _("Submit search") } + = icon("search") + .dropdown.inline.user-sort-dropdown + = dropdown_toggle(starrers_sort_options_hash[@sort], { toggle: 'dropdown' }) + %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable + %li.dropdown-header + = _("Sort by") + - starrers_sort_options_hash.each do |value, title| + %li + = link_to filter_starrer_path(sort: value), class: ("is-active" if @sort == value) do + = title +- if @starrers.size > 0 + .row.prepend-top-10 + = render partial: 'starrer', collection: @starrers, as: :starrer + = paginate @starrers, theme: 'gitlab' +- else + - if params[:search].present? + .nothing-here-block= _('No starrers matched your search') + - else + .nothing-here-block= _('Nobody has starred this repository yet') diff --git a/app/views/search/_filter.html.haml b/app/views/search/_filter.html.haml index c8b6a3258ab..bee4aff605f 100644 --- a/app/views/search/_filter.html.haml +++ b/app/views/search/_filter.html.haml @@ -2,10 +2,11 @@ = hidden_field_tag :group_id, params[:group_id] - if params[:project_id].present? = hidden_field_tag :project_id, params[:project_id] -.dropdown - %button.dropdown-menu-toggle.js-search-group-dropdown{ type: "button", data: { toggle: "dropdown", default_label: _('Group:'), group_id: params[:group_id] } } +.dropdown.form-group.mb-lg-0.mx-lg-1 + %label.d-block{ for: "dashboard_search_group" } + = _("Group") + %button.dropdown-menu-toggle.js-search-group-dropdown.mt-0{ type: "button", id: "dashboard_search_group", data: { toggle: "dropdown", group_id: params[:group_id] } } %span.dropdown-toggle-text - = _("Group:") - if @group.present? = @group.name - else @@ -17,10 +18,11 @@ = dropdown_content = dropdown_loading -.dropdown.project-filter - %button.dropdown-menu-toggle.js-search-project-dropdown{ type: "button", data: { toggle: "dropdown", default_label: _('Project:') } } +.dropdown.project-filter.form-group.mb-lg-0.mx-lg-1 + %label.d-block{ for: "dashboard_search_project" } + = _("Project") + %button.dropdown-menu-toggle.js-search-project-dropdown.mt-0{ type: "button", id: "dashboard_search_project", data: { toggle: "dropdown"} } %span.dropdown-toggle-text - = _("Project:") - if @project.present? = @project.full_name - else diff --git a/app/views/search/_form.html.haml b/app/views/search/_form.html.haml index db0dcc8adfb..464db94b7f4 100644 --- a/app/views/search/_form.html.haml +++ b/app/views/search/_form.html.haml @@ -1,16 +1,21 @@ -= form_tag search_path, method: :get, class: 'js-search-form' do |f| += form_tag search_path, method: :get, class: 'search-page-form js-search-form' do |f| = hidden_field_tag :snippets, params[:snippets] = hidden_field_tag :scope, params[:scope] + = hidden_field_tag :repository_ref, params[:repository_ref] - .search-holder - .search-field-holder - = search_field_tag :search, params[:search], placeholder: _("Search for projects, issues, etc."), class: "form-control search-text-input js-search-input", id: "dashboard_search", autofocus: true, spellcheck: false - = icon("search", class: "search-icon") - %button.search-clear.js-search-clear{ class: ("hidden" if !params[:search].present?), type: "button", tabindex: "-1" } - = icon("times-circle") - %span.sr-only - = _("Clear search") + .d-lg-flex.align-items-end + .search-field-holder.form-group.mr-lg-1.mb-lg-0 + %label{ for: "dashboard_search" } + = _("What are you searching for?") + .position-relative + = search_field_tag :search, params[:search], placeholder: _("Search for projects, issues, etc."), class: "form-control search-text-input js-search-input", id: "dashboard_search", autofocus: true, spellcheck: false + = icon("search", class: "search-icon") + %button.search-clear.js-search-clear{ class: ("hidden" if !params[:search].present?), type: "button", tabindex: "-1" } + = icon("times-circle") + %span.sr-only + = _("Clear search") - unless params[:snippets].eql? 'true' = render 'filter' - = button_tag _("Search"), class: "btn btn-success btn-search" - = render_if_exists 'search/form_elasticsearch' + .d-flex-center.flex-column.flex-lg-row + = button_tag _("Search"), class: "btn btn-success btn-search form-control mt-lg-0 ml-lg-1 align-self-end" + = render_if_exists 'search/form_elasticsearch' diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml index cb8a8a24be8..de9947528cf 100644 --- a/app/views/search/_results.html.haml +++ b/app/views/search/_results.html.haml @@ -2,17 +2,25 @@ = render partial: "search/results/empty" = render_if_exists 'shared/promotions/promote_advanced_search' - else - .row-content-block + .row-content-block.d-md-flex.text-left.align-items-center - unless @search_objects.is_a?(Kaminari::PaginatableWithoutCount) = search_entries_info(@search_objects, @scope, @search_term) - unless @show_snippets - if @project - - link_to_project = link_to(@project.full_name, [@project.namespace.becomes(Namespace), @project]) - = _("in project %{link_to_project}").html_safe % { link_to_project: link_to_project } + - link_to_project = link_to(@project.full_name, [@project.namespace.becomes(Namespace), @project], class: 'ml-md-1') + - if @scope == 'blobs' + - repository_ref = params[:repository_ref].to_s.presence || @project.default_branch + = s_("SearchCodeResults|in") + .mx-md-1 + = render partial: "shared/ref_switcher", locals: { ref: repository_ref, form_path: request.fullpath, field_name: 'repository_ref' } + = s_('SearchCodeResults|of %{link_to_project}').html_safe % { link_to_project: link_to_project } + - else + = _("in project %{link_to_project}").html_safe % { link_to_project: link_to_project } - elsif @group - - link_to_group = link_to(@group.name, @group) + - link_to_group = link_to(@group.name, @group, class: 'ml-md-1') = _("in group %{link_to_group}").html_safe % { link_to_group: link_to_group } = render_if_exists 'shared/promotions/promote_advanced_search' + .results.prepend-top-10 - if @scope == 'commits' %ul.content-list.commit-list diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml index 3260d05f509..9235678bc1d 100644 --- a/app/views/search/show.html.haml +++ b/app/views/search/show.html.haml @@ -1,6 +1,10 @@ - @hide_top_links = true -- breadcrumb_title _("Search") - page_title @search_term +- @hide_breadcrumbs = true + +.page-title-holder.d-flex.align-items-center + %h1.page-title< + = _('Search') .prepend-top-default = render 'search/form' diff --git a/app/views/shared/_ref_switcher.html.haml b/app/views/shared/_ref_switcher.html.haml index 7cbc5810c10..74e0a088656 100644 --- a/app/views/shared/_ref_switcher.html.haml +++ b/app/views/shared/_ref_switcher.html.haml @@ -1,12 +1,19 @@ -- dropdown_toggle_text = @ref || @project.default_branch -= form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form" do - = hidden_field_tag :destination, destination +- return unless @project + +- ref = local_assigns.fetch(:ref, @ref) +- form_path = local_assigns.fetch(:form_path, switch_project_refs_path(@project)) +- dropdown_toggle_text = ref || @project.default_branch +- field_name = local_assigns.fetch(:field_name, 'ref') + += form_tag form_path, method: :get, class: "project-refs-form" do + - if defined?(destination) + = hidden_field_tag :destination, destination - if defined?(path) = hidden_field_tag :path, path - @options && @options.each do |key, value| = hidden_field_tag key, value, id: nil .dropdown - = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_project_path(@project, sort: 'updated_desc'), field_name: 'ref', submit_form_on_click: true, visit: true }, { toggle_class: "js-project-refs-dropdown qa-branches-select" } + = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: ref, refs_url: refs_project_path(@project, sort: 'updated_desc'), field_name: field_name, submit_form_on_click: true, visit: true }, { toggle_class: "js-project-refs-dropdown qa-branches-select" } .dropdown-menu.dropdown-menu-selectable.git-revision-dropdown.dropdown-menu-paging.qa-branches-dropdown{ class: ("dropdown-menu-right" if local_assigns[:align_right]) } .dropdown-page-one = dropdown_title _("Switch branch/tag") diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 07a7b5ce9de..214e87052da 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -33,8 +33,6 @@ = render_if_exists 'shared/issuable/approvals', issuable: issuable, presenter: presenter, form: form -= render_if_exists "shared/issuable/form/merge_request_blocks", issuable: issuable, form: form - = render 'shared/issuable/form/branch_chooser', issuable: issuable, form: form = render 'shared/issuable/form/merge_params', issuable: issuable diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index 483652852b6..bca5db16bd3 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -1,4 +1,5 @@ - project = @target_project || @project +- edit_context = local_assigns.fetch(:edit_context, nil) || project - show_create = local_assigns.fetch(:show_create, true) - extra_options = local_assigns.fetch(:extra_options, true) - filter_submit = local_assigns.fetch(:filter_submit, true) @@ -8,7 +9,7 @@ - classes = local_assigns.fetch(:classes, []) - selected = local_assigns.fetch(:selected, nil) - dropdown_title = local_assigns.fetch(:dropdown_title, "Filter by label") -- dropdown_data = label_dropdown_data(@project, labels: labels_filter_path_with_defaults, default_label: "Labels") +- dropdown_data = label_dropdown_data(edit_context, labels: labels_filter_path_with_defaults(only_group_labels: edit_context.is_a?(Group)), default_label: "Labels") - dropdown_data.merge!(data_options) - label_name = local_assigns.fetch(:label_name, "Labels") - no_default_styles = local_assigns.fetch(:no_default_styles, false) diff --git a/app/views/shared/issuable/form/_metadata.html.haml b/app/views/shared/issuable/form/_metadata.html.haml index 1e03440a5dc..90a6a98235d 100644 --- a/app/views/shared/issuable/form/_metadata.html.haml +++ b/app/views/shared/issuable/form/_metadata.html.haml @@ -23,6 +23,7 @@ = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false }, dropdown_title: "Select label" = render_if_exists "shared/issuable/form/weight", issuable: issuable, form: form + = render_if_exists "shared/issuable/form/merge_request_blocks", issuable: issuable, form: form - if has_due_date .col-lg-6 diff --git a/app/views/shared/members/_sort_dropdown.html.haml b/app/views/shared/members/_sort_dropdown.html.haml index 77f05edab5a..feca109dade 100644 --- a/app/views/shared/members/_sort_dropdown.html.haml +++ b/app/views/shared/members/_sort_dropdown.html.haml @@ -1,5 +1,5 @@ = label_tag :sort_by, 'Sort by', class: 'col-form-label label-bold pr-2' -.dropdown.inline.qa-member-sort-dropdown +.dropdown.inline.qa-user-sort-dropdown = dropdown_toggle(member_sort_options_hash[@sort], { toggle: 'dropdown' }) %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable %li.dropdown-header diff --git a/app/views/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml index 67cb1aa549c..bb05658c719 100644 --- a/app/views/shared/projects/_list.html.haml +++ b/app/views/shared/projects/_list.html.haml @@ -17,15 +17,20 @@ - contributed_projects_illustration_path = 'illustrations/profile-page/contributed-projects.svg' - contributed_projects_current_user_empty_message_header = s_('UserProfile|Explore public groups to find projects to contribute to.') - contributed_projects_visitor_empty_message = s_('UserProfile|This user hasn\'t contributed to any projects') +- starred_projects_illustration_path = 'illustrations/starred_empty.svg' +- starred_projects_current_user_empty_message_header = s_('UserProfile|Star projects to track their progress and show your appreciation.') +- starred_projects_visitor_empty_message = s_('UserProfile|This user hasn\'t starred any projects') - own_projects_illustration_path = 'illustrations/profile-page/personal-project.svg' - own_projects_current_user_empty_message_header = s_('UserProfile|You haven\'t created any personal projects.') - own_projects_current_user_empty_message_description = s_('UserProfile|Your projects can be available publicly, internally, or privately, at your choice.') - own_projects_visitor_empty_message = s_('UserProfile|This user doesn\'t have any personal projects') - explore_page_empty_message = s_('UserProfile|Explore public groups to find projects to contribute to.') -- primary_button_label = _('New project') -- primary_button_link = new_project_path -- secondary_button_label = _('Explore groups') -- secondary_button_link = explore_groups_path +- new_project_button_label = _('New project') +- new_project_button_link = new_project_path +- explore_projects_button_label = _('Explore projects') +- explore_projects_button_link = explore_projects_path +- explore_groups_button_label = _('Explore groups') +- explore_groups_button_link = explore_groups_path .js-projects-list-holder - if any_projects?(projects) @@ -48,15 +53,21 @@ - if @contributed_projects = render partial: 'shared/empty_states/profile_tabs', locals: { illustration_path: contributed_projects_illustration_path, current_user_empty_message_header: contributed_projects_current_user_empty_message_header, - primary_button_label: primary_button_label, - primary_button_link: primary_button_link, - secondary_button_label: secondary_button_label, - secondary_button_link: secondary_button_link, + primary_button_label: new_project_button_label, + primary_button_link: new_project_button_link, + secondary_button_label: explore_groups_button_label, + secondary_button_link: explore_groups_button_link, visitor_empty_message: contributed_projects_visitor_empty_message } + - elsif @starred_projects + = render partial: 'shared/empty_states/profile_tabs', locals: { illustration_path: starred_projects_illustration_path, + current_user_empty_message_header: starred_projects_current_user_empty_message_header, + primary_button_label: explore_projects_button_label, + primary_button_link: explore_projects_button_link, + visitor_empty_message: starred_projects_visitor_empty_message } - else = render partial: 'shared/empty_states/profile_tabs', locals: { illustration_path: own_projects_illustration_path, current_user_empty_message_header: own_projects_current_user_empty_message_header, current_user_empty_message_description: own_projects_current_user_empty_message_description, - primary_button_label: primary_button_label, - primary_button_link: primary_button_link, + primary_button_label: new_project_button_label, + primary_button_link: new_project_button_link, visitor_empty_message: defined?(explore_page) && explore_page ? explore_page_empty_message : own_projects_visitor_empty_message } diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml index f40a9cffb29..b7474d891dc 100644 --- a/app/views/shared/projects/_project.html.haml +++ b/app/views/shared/projects/_project.html.haml @@ -63,7 +63,9 @@ - if project.archived %span.d-flex.icon-wrapper.badge.badge-warning archived - if stars - %span.d-flex.align-items-center.icon-wrapper.stars.has-tooltip{ data: { container: 'body', placement: 'top' }, title: _('Stars') } + = link_to project_starrers_path(project), + class: "d-flex align-items-center icon-wrapper stars has-tooltip", + title: _('Stars'), data: { container: 'body', placement: 'top' } do = sprite_icon('star', size: 14, css_class: 'append-right-4') = number_with_delimiter(project.star_count) - if forks diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index b3a73030859..73bee7c2586 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -111,6 +111,10 @@ %li.js-projects-tab = link_to user_projects_path, data: { target: 'div#projects', action: 'projects', toggle: 'tab', endpoint: user_projects_path(format: :json) } do = s_('UserProfile|Personal projects') + - if profile_tab?(:starred) + %li.js-starred-tab + = link_to user_starred_projects_path, data: { target: 'div#starred', action: 'starred', toggle: 'tab', endpoint: user_starred_projects_path(format: :json) } do + = s_('UserProfile|Starred projects') - if profile_tab?(:snippets) %li.js-snippets-tab = link_to user_snippets_path, data: { target: 'div#snippets', action: 'snippets', toggle: 'tab', endpoint: user_snippets_path(format: :json) } do @@ -142,6 +146,10 @@ #projects.tab-pane -# This tab is always loaded via AJAX + - if profile_tab?(:starred) + #starred.tab-pane + -# This tab is always loaded via AJAX + - if profile_tab?(:snippets) #snippets.tab-pane -# This tab is always loaded via AJAX diff --git a/changelogs/unreleased/20137-starrers.yml b/changelogs/unreleased/20137-starrers.yml new file mode 100644 index 00000000000..d597b06f224 --- /dev/null +++ b/changelogs/unreleased/20137-starrers.yml @@ -0,0 +1,5 @@ +--- +title: Make starred projects and starrers of a project publicly visible +merge_request: 24690 +author: +type: added diff --git a/changelogs/unreleased/39217-remove-kubernetes-service-integration.yml b/changelogs/unreleased/39217-remove-kubernetes-service-integration.yml new file mode 100644 index 00000000000..e13e3e86a37 --- /dev/null +++ b/changelogs/unreleased/39217-remove-kubernetes-service-integration.yml @@ -0,0 +1,5 @@ +--- +title: Remove Kubernetes service integration page +merge_request: 31365 +author: +type: removed diff --git a/changelogs/unreleased/4221-board-milestone-should-persist-any-none-properly.yml b/changelogs/unreleased/4221-board-milestone-should-persist-any-none-properly.yml deleted file mode 100644 index d50c59bf607..00000000000 --- a/changelogs/unreleased/4221-board-milestone-should-persist-any-none-properly.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: For milestone filters, treat Any as No Filter (using null). Use -1 for No Milestone -merge_request: -author: -type: changed diff --git a/changelogs/unreleased/47814-search-view-labels.yml b/changelogs/unreleased/47814-search-view-labels.yml new file mode 100644 index 00000000000..b4f10150d13 --- /dev/null +++ b/changelogs/unreleased/47814-search-view-labels.yml @@ -0,0 +1,5 @@ +--- +title: Moved labels out of fields on Search page +merge_request: 31137 +author: +type: fixed diff --git a/changelogs/unreleased/59712-resolve-the-search-problem-issue.yml b/changelogs/unreleased/59712-resolve-the-search-problem-issue.yml new file mode 100644 index 00000000000..964962cb817 --- /dev/null +++ b/changelogs/unreleased/59712-resolve-the-search-problem-issue.yml @@ -0,0 +1,5 @@ +--- +title: Add branch/tags/commits dropdown filter on the search page for searching codes +merge_request: 28282 +author: minghuan lei +type: changed diff --git a/changelogs/unreleased/60449-reduce-gitaly-calls-when-rendering-commits-in-md.yml b/changelogs/unreleased/60449-reduce-gitaly-calls-when-rendering-commits-in-md.yml new file mode 100644 index 00000000000..ef11e8743f6 --- /dev/null +++ b/changelogs/unreleased/60449-reduce-gitaly-calls-when-rendering-commits-in-md.yml @@ -0,0 +1,5 @@ +--- +title: Batch processing of commit refs in markdown processing +merge_request: 31037 +author: +type: performance diff --git a/changelogs/unreleased/61445-prevent-persisting-auto-switch-discussion-filter.yml b/changelogs/unreleased/61445-prevent-persisting-auto-switch-discussion-filter.yml new file mode 100644 index 00000000000..58e29212462 --- /dev/null +++ b/changelogs/unreleased/61445-prevent-persisting-auto-switch-discussion-filter.yml @@ -0,0 +1,6 @@ +--- +title: Prevent discussion filter from persisting to `Show all activity` when opening + links to notes +merge_request: 31229 +author: +type: fixed diff --git a/changelogs/unreleased/62137-add-tooltip-to-improve-clarity-of-detached-label-state-in-the-merge-request-pipeline.yml b/changelogs/unreleased/62137-add-tooltip-to-improve-clarity-of-detached-label-state-in-the-merge-request-pipeline.yml new file mode 100644 index 00000000000..ccc3195e6ae --- /dev/null +++ b/changelogs/unreleased/62137-add-tooltip-to-improve-clarity-of-detached-label-state-in-the-merge-request-pipeline.yml @@ -0,0 +1,5 @@ +--- +title: Updated the detached pipeline badge tooltip text to offer a better explanation +merge_request: 31626 +author: +type: other diff --git a/changelogs/unreleased/62609-test-summary-loading-spinner-is-too-large.yml b/changelogs/unreleased/62609-test-summary-loading-spinner-is-too-large.yml new file mode 100644 index 00000000000..f8e4a26dad8 --- /dev/null +++ b/changelogs/unreleased/62609-test-summary-loading-spinner-is-too-large.yml @@ -0,0 +1,5 @@ +--- +title: Adjust size and align MR-widget loading icon +merge_request: 31503 +author: +type: fixed diff --git a/changelogs/unreleased/62973-specify-time-frame-in-shareable-link-for-embedding-metrics.yml b/changelogs/unreleased/62973-specify-time-frame-in-shareable-link-for-embedding-metrics.yml new file mode 100644 index 00000000000..aaf0ddfa48d --- /dev/null +++ b/changelogs/unreleased/62973-specify-time-frame-in-shareable-link-for-embedding-metrics.yml @@ -0,0 +1,5 @@ +--- +title: Allow links to metrics dashboard at a specific time +merge_request: 31283 +author: +type: added diff --git a/changelogs/unreleased/63181-collapsible-line.yml b/changelogs/unreleased/63181-collapsible-line.yml new file mode 100644 index 00000000000..c13d4eeab6c --- /dev/null +++ b/changelogs/unreleased/63181-collapsible-line.yml @@ -0,0 +1,5 @@ +--- +title: Makes collapsible title clickable in job log +merge_request: +author: +type: added diff --git a/changelogs/unreleased/63671-remove-extra-padding-from-the-disabled-comment-area.yml b/changelogs/unreleased/63671-remove-extra-padding-from-the-disabled-comment-area.yml new file mode 100644 index 00000000000..ab116674ced --- /dev/null +++ b/changelogs/unreleased/63671-remove-extra-padding-from-the-disabled-comment-area.yml @@ -0,0 +1,5 @@ +--- +title: Remove extra padding from disabled comment box +merge_request: 31603 +author: +type: fixed diff --git a/changelogs/unreleased/63888-snippets-usage-ping-for-create-smau.yml b/changelogs/unreleased/63888-snippets-usage-ping-for-create-smau.yml new file mode 100644 index 00000000000..1a5a552b120 --- /dev/null +++ b/changelogs/unreleased/63888-snippets-usage-ping-for-create-smau.yml @@ -0,0 +1,5 @@ +--- +title: Count snippet creation, update and comment events +merge_request: 30930 +author: +type: added diff --git a/changelogs/unreleased/64730-metrics-dashboard-menu-is-cramped-with-new-features-enabled.yml b/changelogs/unreleased/64730-metrics-dashboard-menu-is-cramped-with-new-features-enabled.yml new file mode 100644 index 00000000000..c564b98bb41 --- /dev/null +++ b/changelogs/unreleased/64730-metrics-dashboard-menu-is-cramped-with-new-features-enabled.yml @@ -0,0 +1,5 @@ +--- +title: Improve layout of dropdowns in the metrics dashboard page +merge_request: 31239 +author: +type: fixed diff --git a/changelogs/unreleased/65152-selective-highlight.yml b/changelogs/unreleased/65152-selective-highlight.yml deleted file mode 100644 index 371dbbd5924..00000000000 --- a/changelogs/unreleased/65152-selective-highlight.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Support selective highlighting of lines -merge_request: 31361 -author: -type: performance diff --git a/changelogs/unreleased/65700-document-max-replication-slots-pg-ha.yml b/changelogs/unreleased/65700-document-max-replication-slots-pg-ha.yml new file mode 100644 index 00000000000..bacc3f5fc11 --- /dev/null +++ b/changelogs/unreleased/65700-document-max-replication-slots-pg-ha.yml @@ -0,0 +1,5 @@ +--- +title: Add max_replication_slots to PG HA documentation +merge_request: 31534 +author: +type: other diff --git a/changelogs/unreleased/65705-two-buttons.yml b/changelogs/unreleased/65705-two-buttons.yml new file mode 100644 index 00000000000..b92e28f9d68 --- /dev/null +++ b/changelogs/unreleased/65705-two-buttons.yml @@ -0,0 +1,5 @@ +--- +title: Prevent duplicated trigger action button +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/add-release-to-github-importer.yml b/changelogs/unreleased/add-release-to-github-importer.yml new file mode 100644 index 00000000000..d11e7c725f7 --- /dev/null +++ b/changelogs/unreleased/add-release-to-github-importer.yml @@ -0,0 +1,5 @@ +--- +title: Add a field for released_at to GH importer +merge_request: 31496 +author: +type: fixed diff --git a/changelogs/unreleased/bring-scoped-environment-variables-to-core.yml b/changelogs/unreleased/bring-scoped-environment-variables-to-core.yml new file mode 100644 index 00000000000..48dfe662206 --- /dev/null +++ b/changelogs/unreleased/bring-scoped-environment-variables-to-core.yml @@ -0,0 +1,5 @@ +--- +title: Bring scoped environment variables to core +merge_request: 30779 +author: +type: changed diff --git a/changelogs/unreleased/filter-title-description-and-body-from-logs.yml b/changelogs/unreleased/filter-title-description-and-body-from-logs.yml new file mode 100644 index 00000000000..8b592790629 --- /dev/null +++ b/changelogs/unreleased/filter-title-description-and-body-from-logs.yml @@ -0,0 +1,5 @@ +--- +title: Filter title, description, and body parameters from logs +merge_request: +author: +type: changed diff --git a/changelogs/unreleased/fix-job-log-formatting.yml b/changelogs/unreleased/fix-job-log-formatting.yml new file mode 100644 index 00000000000..0dd545aaecc --- /dev/null +++ b/changelogs/unreleased/fix-job-log-formatting.yml @@ -0,0 +1,5 @@ +--- +title: Fix job logs where style changes were broken down into separate lines +merge_request: 31674 +author: +type: fixed diff --git a/changelogs/unreleased/gitaly-version-v1.57.0.yml b/changelogs/unreleased/gitaly-version-v1.57.0.yml new file mode 100644 index 00000000000..65596199e16 --- /dev/null +++ b/changelogs/unreleased/gitaly-version-v1.57.0.yml @@ -0,0 +1,5 @@ +--- +title: Upgrade to Gitaly v1.57.0 +merge_request: 31568 +author: +type: changed diff --git a/changelogs/unreleased/mc-feature-add-at-colon-variable-masking.yml b/changelogs/unreleased/mc-feature-add-at-colon-variable-masking.yml new file mode 100644 index 00000000000..9b8eff8e043 --- /dev/null +++ b/changelogs/unreleased/mc-feature-add-at-colon-variable-masking.yml @@ -0,0 +1,5 @@ +--- +title: 'Allows masking @ and : characters.' +merge_request: 31065 +author: +type: changed diff --git a/changelogs/unreleased/rails-template-update.yml b/changelogs/unreleased/rails-template-update.yml new file mode 100644 index 00000000000..53eb57c84ff --- /dev/null +++ b/changelogs/unreleased/rails-template-update.yml @@ -0,0 +1,5 @@ +--- +title: Update 'Ruby on Rails' project template +merge_request: 31310 +author: +type: other diff --git a/changelogs/unreleased/sh-add-gitaly-and-rugged-data-sidekiq.yml b/changelogs/unreleased/sh-add-gitaly-and-rugged-data-sidekiq.yml new file mode 100644 index 00000000000..d2143e83045 --- /dev/null +++ b/changelogs/unreleased/sh-add-gitaly-and-rugged-data-sidekiq.yml @@ -0,0 +1,5 @@ +--- +title: Add Gitaly and Rugged call timing in Sidekiq logs +merge_request: 31651 +author: +type: other diff --git a/changelogs/unreleased/sh-add-missing-csp-report-uri.yml b/changelogs/unreleased/sh-add-missing-csp-report-uri.yml new file mode 100644 index 00000000000..656eb8e9c37 --- /dev/null +++ b/changelogs/unreleased/sh-add-missing-csp-report-uri.yml @@ -0,0 +1,5 @@ +--- +title: Add missing report-uri to CSP config +merge_request: 31593 +author: +type: fixed diff --git a/changelogs/unreleased/sh-disable-registry-delete.yml b/changelogs/unreleased/sh-disable-registry-delete.yml new file mode 100644 index 00000000000..180b983e07c --- /dev/null +++ b/changelogs/unreleased/sh-disable-registry-delete.yml @@ -0,0 +1,5 @@ +--- +title: Don't attempt to contact registry if it is disabled +merge_request: 31553 +author: +type: fixed diff --git a/changelogs/unreleased/sh-rename-githost-to-gitjson.yml b/changelogs/unreleased/sh-rename-githost-to-gitjson.yml new file mode 100644 index 00000000000..24fcd1e9781 --- /dev/null +++ b/changelogs/unreleased/sh-rename-githost-to-gitjson.yml @@ -0,0 +1,5 @@ +--- +title: Rename githost.log -> git_json.log +merge_request: 31634 +author: +type: changed diff --git a/changelogs/unreleased/speed-up-labels-api.yml b/changelogs/unreleased/speed-up-labels-api.yml new file mode 100644 index 00000000000..d5ac4313414 --- /dev/null +++ b/changelogs/unreleased/speed-up-labels-api.yml @@ -0,0 +1,5 @@ +--- +title: Remove counts from default labels API responses +merge_request: 31543 +author: +type: changed diff --git a/changelogs/unreleased/visual-review-tools-constant-storage-keys.yml b/changelogs/unreleased/visual-review-tools-constant-storage-keys.yml new file mode 100644 index 00000000000..4c9b048aaa3 --- /dev/null +++ b/changelogs/unreleased/visual-review-tools-constant-storage-keys.yml @@ -0,0 +1,5 @@ +--- +title: Fix visual review app storage keys +merge_request: 31427 +author: +type: fixed diff --git a/config/application.rb b/config/application.rb index 21cb79f7851..47c5ab71285 100644 --- a/config/application.rb +++ b/config/application.rb @@ -105,10 +105,23 @@ module Gitlab # - Sentry DSN (:sentry_dsn) # - File content from Web Editor (:content) # - Jira shared secret (:sharedSecret) + # - Titles, bodies, and descriptions for notes, issues, etc. # - # NOTE: It is **IMPORTANT** to also update gitlab-workhorse's filter when adding parameters here to not - # introduce another security vulnerability: https://gitlab.com/gitlab-org/gitlab-workhorse/issues/182 - config.filter_parameters += [/token$/, /password/, /secret/, /key$/, /^note$/, /^text$/] + # NOTE: It is **IMPORTANT** to also update labkit's filter when + # adding parameters here to not introduce another security + # vulnerability: + # https://gitlab.com/gitlab-org/labkit/blob/master/mask/matchers.go + config.filter_parameters += [ + /token$/, + /password/, + /secret/, + /key$/, + /^body$/, + /^description$/, + /^note$/, + /^text$/, + /^title$/ + ] config.filter_parameters += %i( certificate encrypted_key diff --git a/config/initializers/lograge.rb b/config/initializers/lograge.rb index 3d84b4e44ce..346725e4080 100644 --- a/config/initializers/lograge.rb +++ b/config/initializers/lograge.rb @@ -27,19 +27,7 @@ unless Sidekiq.server? queue_duration: event.payload[:queue_duration] } - gitaly_calls = Gitlab::GitalyClient.get_request_count - - if gitaly_calls > 0 - payload[:gitaly_calls] = gitaly_calls - payload[:gitaly_duration] = Gitlab::GitalyClient.query_time_ms - end - - rugged_calls = Gitlab::RuggedInstrumentation.query_count - - if rugged_calls > 0 - payload[:rugged_calls] = rugged_calls - payload[:rugged_duration_ms] = Gitlab::RuggedInstrumentation.query_time_ms - end + ::Gitlab::InstrumentationHelper.add_instrumentation_data(payload) payload[:response] = event.payload[:response] if event.payload[:response] payload[Labkit::Correlation::CorrelationId::LOG_KEY] = Labkit::Correlation::CorrelationId.current_id diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb index 9f88fb9895b..7217f098fd9 100644 --- a/config/initializers/sidekiq.rb +++ b/config/initializers/sidekiq.rb @@ -39,6 +39,7 @@ Sidekiq.configure_server do |config| chain.add Gitlab::SidekiqMiddleware::RequestStoreMiddleware unless ENV['SIDEKIQ_REQUEST_STORE'] == '0' chain.add Gitlab::SidekiqMiddleware::BatchLoader chain.add Gitlab::SidekiqMiddleware::CorrelationLogger + chain.add Gitlab::SidekiqMiddleware::InstrumentationLogger chain.add Gitlab::SidekiqStatus::ServerMiddleware end diff --git a/config/routes/project.rb b/config/routes/project.rb index 3113cb172f7..380ecad001d 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -170,7 +170,9 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do get :recent end end + resources :releases, only: [:index] + resources :starrers, only: [:index] resources :forks, only: [:index, :new, :create] resources :group_links, only: [:index, :create, :update, :destroy], constraints: { id: /\d+/ } diff --git a/config/routes/user.rb b/config/routes/user.rb index 80f266aa8f9..3f768d5d384 100644 --- a/config/routes/user.rb +++ b/config/routes/user.rb @@ -59,6 +59,7 @@ scope(constraints: { username: Gitlab::PathRegex.root_namespace_route_regex }) d get :groups get :projects get :contributed, as: :contributed_projects + get :starred, as: :starred_projects get :snippets get :exists get :activity diff --git a/db/migrate/20190703001120_default_milestone_to_nil.rb b/db/migrate/20190703001120_default_milestone_to_nil.rb deleted file mode 100644 index 6a1c3603d9d..00000000000 --- a/db/migrate/20190703001120_default_milestone_to_nil.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -class DefaultMilestoneToNil < ActiveRecord::Migration[5.1] - DOWNTIME = false - - def up - execute(update_board_milestones_query) - end - - def down - # no-op - end - - private - - # Only 105 records to update, as of 2019/07/18 - def update_board_milestones_query - <<~HEREDOC - UPDATE boards - SET milestone_id = NULL - WHERE boards.milestone_id = -1 - HEREDOC - end -end diff --git a/db/migrate/20190724112147_add_column_for_self_monitoring_project_id.rb b/db/migrate/20190724112147_add_column_for_self_monitoring_project_id.rb new file mode 100644 index 00000000000..ce249a527e6 --- /dev/null +++ b/db/migrate/20190724112147_add_column_for_self_monitoring_project_id.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class AddColumnForSelfMonitoringProjectId < ActiveRecord::Migration[5.2] + DOWNTIME = false + + def change + add_reference( + :application_settings, + :instance_administration_project, + index: { name: 'index_applicationsettings_on_instance_administration_project_id' }, + foreign_key: { to_table: :projects, on_delete: :nullify } + ) + end +end diff --git a/db/migrate/20190801060809_delete_kubernetes_services.rb b/db/migrate/20190801060809_delete_kubernetes_services.rb new file mode 100644 index 00000000000..018976584d4 --- /dev/null +++ b/db/migrate/20190801060809_delete_kubernetes_services.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class DeleteKubernetesServices < ActiveRecord::Migration[5.2] + DOWNTIME = false + + def up + Service.where(type: "KubernetesService").delete_all + end + + def down + # no-op + end +end diff --git a/db/migrate/20190801193427_rename_application_settings_snowplow_collector_uri_column.rb b/db/migrate/20190801193427_rename_application_settings_snowplow_collector_uri_column.rb new file mode 100644 index 00000000000..a4bd5841a46 --- /dev/null +++ b/db/migrate/20190801193427_rename_application_settings_snowplow_collector_uri_column.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class RenameApplicationSettingsSnowplowCollectorUriColumn < ActiveRecord::Migration[5.2] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + rename_column_concurrently :application_settings, :snowplow_collector_uri, :snowplow_collector_hostname + end + + def down + cleanup_concurrent_column_rename :application_settings, :snowplow_collector_hostname, :snowplow_collector_uri + end +end diff --git a/db/migrate/20190802195602_add_timestamps_columns_to_geo_nodes.rb b/db/migrate/20190802195602_add_timestamps_columns_to_geo_nodes.rb new file mode 100644 index 00000000000..b95d9037afe --- /dev/null +++ b/db/migrate/20190802195602_add_timestamps_columns_to_geo_nodes.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class AddTimestampsColumnsToGeoNodes < ActiveRecord::Migration[5.2] + DOWNTIME = false + + def change + add_column(:geo_nodes, :created_at, :datetime_with_timezone, null: true) + add_column(:geo_nodes, :updated_at, :datetime_with_timezone, null: true) + end +end diff --git a/db/migrate/20190806071559_remove_epic_issues_default_relative_position.rb b/db/migrate/20190806071559_remove_epic_issues_default_relative_position.rb new file mode 100644 index 00000000000..f6db90f6637 --- /dev/null +++ b/db/migrate/20190806071559_remove_epic_issues_default_relative_position.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class RemoveEpicIssuesDefaultRelativePosition < ActiveRecord::Migration[5.2] + DOWNTIME = false + + def change + change_column_null :epic_issues, :relative_position, true + change_column_default :epic_issues, :relative_position, from: 1073741823, to: nil + end +end diff --git a/db/post_migrate/20190802091750_cleanup_application_settings_snowplow_collector_uri_rename.rb b/db/post_migrate/20190802091750_cleanup_application_settings_snowplow_collector_uri_rename.rb new file mode 100644 index 00000000000..768ece925c6 --- /dev/null +++ b/db/post_migrate/20190802091750_cleanup_application_settings_snowplow_collector_uri_rename.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class CleanupApplicationSettingsSnowplowCollectorUriRename < ActiveRecord::Migration[5.2] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + cleanup_concurrent_column_rename :application_settings, :snowplow_collector_uri, :snowplow_collector_hostname + end + + def down + rename_column_concurrently :application_settings, :snowplow_collector_hostname, :snowplow_collector_uri + end +end diff --git a/db/schema.rb b/db/schema.rb index 828e36aa96c..6674f412140 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2019_08_02_235445) do +ActiveRecord::Schema.define(version: 2019_08_06_071559) do # These are extensions that must be enabled in order to support this database enable_extension "pg_trgm" @@ -195,7 +195,6 @@ ActiveRecord::Schema.define(version: 2019_08_02_235445) do t.boolean "pseudonymizer_enabled", default: false, null: false t.boolean "hide_third_party_offers", default: false, null: false t.boolean "snowplow_enabled", default: false, null: false - t.string "snowplow_collector_uri" t.string "snowplow_site_id" t.string "snowplow_cookie_domain" t.boolean "instance_statistics_visibility_private", default: false, null: false @@ -231,8 +230,11 @@ ActiveRecord::Schema.define(version: 2019_08_02_235445) do t.integer "raw_blob_request_limit", default: 300, null: false t.boolean "allow_local_requests_from_web_hooks_and_services", default: false, null: false t.boolean "allow_local_requests_from_system_hooks", default: true, null: false + t.bigint "instance_administration_project_id" + t.string "snowplow_collector_hostname" t.index ["custom_project_templates_group_id"], name: "index_application_settings_on_custom_project_templates_group_id" t.index ["file_template_project_id"], name: "index_application_settings_on_file_template_project_id" + t.index ["instance_administration_project_id"], name: "index_applicationsettings_on_instance_administration_project_id" t.index ["usage_stats_set_by_user_id"], name: "index_application_settings_on_usage_stats_set_by_user_id" end @@ -1192,7 +1194,7 @@ ActiveRecord::Schema.define(version: 2019_08_02_235445) do create_table "epic_issues", id: :serial, force: :cascade do |t| t.integer "epic_id", null: false t.integer "issue_id", null: false - t.integer "relative_position", default: 1073741823, null: false + t.integer "relative_position" t.index ["epic_id"], name: "index_epic_issues_on_epic_id" t.index ["issue_id"], name: "index_epic_issues_on_issue_id", unique: true end @@ -1448,6 +1450,8 @@ ActiveRecord::Schema.define(version: 2019_08_02_235445) do t.string "internal_url" t.string "name", null: false t.integer "container_repositories_max_capacity", default: 10, null: false + t.datetime_with_timezone "created_at" + t.datetime_with_timezone "updated_at" t.index ["access_key"], name: "index_geo_nodes_on_access_key" t.index ["name"], name: "index_geo_nodes_on_name", unique: true t.index ["primary"], name: "index_geo_nodes_on_primary" @@ -3623,6 +3627,7 @@ ActiveRecord::Schema.define(version: 2019_08_02_235445) do add_foreign_key "application_settings", "namespaces", column: "custom_project_templates_group_id", on_delete: :nullify add_foreign_key "application_settings", "projects", column: "file_template_project_id", name: "fk_ec757bd087", on_delete: :nullify + add_foreign_key "application_settings", "projects", column: "instance_administration_project_id", on_delete: :nullify add_foreign_key "application_settings", "users", column: "usage_stats_set_by_user_id", name: "fk_964370041d", on_delete: :nullify add_foreign_key "approval_merge_request_rule_sources", "approval_merge_request_rules", on_delete: :cascade add_foreign_key "approval_merge_request_rule_sources", "approval_project_rules", on_delete: :cascade diff --git a/doc/README.md b/doc/README.md index af675582a99..edce03baec9 100644 --- a/doc/README.md +++ b/doc/README.md @@ -25,7 +25,7 @@ No matter how you use GitLab, we have documentation for you. | [**User Documentation**](user/index.md)<br/>Discover features and concepts for GitLab users. | [**Administrator documentation**](administration/index.md)<br/>Everything GitLab self-managed administrators need to know. | | [**Contributing to GitLab**](#contributing-to-gitlab)<br/>At GitLab, everyone can contribute! | [**New to Git and GitLab?**](#new-to-git-and-gitlab)<br/>We have resources to get you started. | | [**Building an integration with GitLab?**](#building-an-integration-with-gitlab)<br/>Consult our automation and integration documentation. | [**Coming to GitLab from another platform?**](#coming-to-gitlab-from-another-platform)<br/>Consult our handy guides. | -| [**Install GitLab**](https://about.gitlab.com/install/)<br/>Installation options for different platforms. | [**Subscribe to GitLab**](#subscribe-to-gitlab)<br/>Get access to more features. | +| [**Install GitLab**](https://about.gitlab.com/install/)<br/>Installation options for different platforms. | [**Customers**](subscriptions/index.md)<br/>Information for new and existing customers. | | [**Update GitLab**](update/README.md)<br/>Update your GitLab self-managed instance to the latest version. | [**GitLab Releases**](https://about.gitlab.com/releases/)<br/>What's new in GitLab. | ## Popular Documentation @@ -38,7 +38,7 @@ Have a look at some of our most popular documentation resources: | [GitLab CI/CD examples](ci/examples/README.md) | Get up to speed quickly with common CI/CD scenarios. | | [GitLab Container Registry](user/project/container_registry.md) | Host containers within GitLab. | | [GitLab Pages](user/project/pages/index.md) | Host static websites for your projects with GitLab. | -| [GitLab.com settings](user/gitlab_com/index.md) | Settings for [GitLab.com](#gitlabcom). | +| [GitLab.com settings](user/gitlab_com/index.md) | Settings for GitLab.com. | | [Kubernetes integration](user/project/clusters/index.md) | Use GitLab with Kubernetes. | | [SSH authentication](ssh/README.md) | Secure your network communications. | | [Using Docker images](ci/docker/using_docker_images.md) | Build and test your applications with Docker. | @@ -279,7 +279,7 @@ The following documentation relates to the DevOps **Release** stage: | [Canary Deployments](user/project/canary_deployments.md) **(PREMIUM)** | Employ a popular CI strategy where a small portion of the fleet is updated to the new version first. | | [Deploy Boards](user/project/deploy_boards.md) **(PREMIUM)** | View the current health and status of each CI environment running on Kubernetes, displaying the status of the pods in the deployment. | | [Environments and deployments](ci/environments.md) | With environments, you can control the continuous deployment of your software within GitLab. | -| [Environment-specific variables](ci/variables/README.md#limiting-environment-scopes-of-environment-variables-premium) **(PREMIUM)** | Limit scope of variables to specific environments. | +| [Environment-specific variables](ci/variables/README.md#limiting-environment-scopes-of-environment-variables) | Limit scope of variables to specific environments. | | [GitLab CI/CD](ci/README.md) | Explore the features and capabilities of Continuous Deployment and Delivery with GitLab. | | [GitLab Pages](user/project/pages/index.md) | Build, test, and deploy a static site directly from GitLab. | | [Protected Runners](ci/runners/README.md#protected-runners) | Select Runners to only pick jobs for protected branches and tags. | @@ -361,90 +361,6 @@ The following documentation relates to the DevOps **Secure** stage: | [Project Security Dashboard](user/application_security/security_dashboard/index.md) **(ULTIMATE)** | View the latest security reports for your project. | | [Static Application Security Testing (SAST)](user/application_security/sast/index.md) **(ULTIMATE)** | Analyze source code for known vulnerabilities. | -## Subscribe to GitLab - -There are two ways to use GitLab: - -- [GitLab self-managed](#gitlab-self-managed): Install, administer, and maintain your own GitLab instance. -- [GitLab.com](#gitlabcom): GitLab's SaaS offering. You don't need to install anything to use GitLab.com, - you only need to [sign up](https://gitlab.com/users/sign_in) and start using GitLab straight away. - -For more information on managing your subscription and [Customers Portal](https://customers.gitlab.com) account, please see [Getting Started with Subscriptions](getting-started/subscription.md). - -The following sections outline tiers and features within GitLab self-managed and GitLab.com. - -<div align="right"> - <a type="button" class="btn btn-default" href="#overview"> - Back to Overview <i class="fa fa-angle-double-up" aria-hidden="true"></i> - </a> -</div> - -### GitLab self-managed - -With GitLab self-managed, you deploy your own GitLab instance on-premises or on a cloud of your choice. -GitLab self-managed is available for [free and with paid subscriptions](https://about.gitlab.com/pricing/#self-managed) in the following tiers: - -| Tier | Includes | -|:---------|:-----------------------------------------------| -| Core | Core features. | -| Starter | Core and Starter features. | -| Premium | Core, Starter, and Premium features. | -| Ultimate | Core, Starter, Premium, and Ultimate features. | - -The following resources are available for more information on GitLab self-managed: - -- [Feature comparison](https://about.gitlab.com/pricing/self-managed/feature-comparison/), for information on what features are available at each tier. -- [GitLab pricing page](https://about.gitlab.com/pricing/#self-managed), for subscription information and a free trial. -- Our [product marketing page](https://about.gitlab.com/handbook/marketing/product-marketing/), for additional information including: - - How [different tiers are licensed](https://about.gitlab.com/handbook/marketing/product-marketing/#tiers). - - The different [GitLab distributions](https://about.gitlab.com/handbook/marketing/product-marketing/#distributions). - -<div align="right"> - <a type="button" class="btn btn-default" href="#overview"> - Back to Overview <i class="fa fa-angle-double-up" aria-hidden="true"></i> - </a> -</div> - -### GitLab.com - -GitLab.com is hosted, managed, and administered by GitLab, Inc., with -[free and paid subscriptions](https://about.gitlab.com/pricing/) for individuals -and teams in the following tiers: - -| Tier | Includes same features available in | -|:-------|:----------------------------------------------------| -| Free | [Core](#gitlab-self-managed) self-managed tier. | -| Bronze | [Starter](#gitlab-self-managed) self-managed tier. | -| Silver | [Premium](#gitlab-self-managed) self-managed tier. | -| Gold | [Ultimate](#gitlab-self-managed) self-managed tier. | - -GitLab.com subscriptions grant access -to the same features available in GitLab self-managed, **except -[administration](administration/index.md) tools and settings**. - -GitLab.com allows you to apply your subscription to a group or your personal user. - -When applied to a **group**, the group, all subgroups, and all projects under the selected group on GitLab.com will have the features of the associated plan. It is recommended to go with a group plan when managing projects and users of an organization. - -When associated with a **personal userspace** instead, all projects will have features with the subscription applied, but as it is not a group, group features will not be available. - -TIP: **Tip:** -To support the open source community and encourage the development of open source projects, GitLab grants access to **Gold** features for all GitLab.com **public** projects, regardless of the subscription. - -The following resources are available for more information on GitLab.com: - -- [Feature comparison](https://about.gitlab.com/pricing/gitlab-com/feature-comparison/), for information on what features are available at each tier. -- [GitLab pricing page](https://about.gitlab.com/pricing/), for subscription information and a free trial. -- Our [product marketing page](https://about.gitlab.com/handbook/marketing/product-marketing/), for additional information including: - - How [different tiers are licensed](https://about.gitlab.com/handbook/marketing/product-marketing/#tiers). - - The different [GitLab distributions](https://about.gitlab.com/handbook/marketing/product-marketing/#distributions). - -<div align="right"> - <a type="button" class="btn btn-default" href="#overview"> - Back to Overview <i class="fa fa-angle-double-up" aria-hidden="true"></i> - </a> -</div> - ## New to Git and GitLab? Working with new systems can be daunting. diff --git a/doc/administration/auth/img/google_secure_ldap_add_step_1.png b/doc/administration/auth/img/google_secure_ldap_add_step_1.png Binary files differindex fd254443d75..bee9c602a14 100644 --- a/doc/administration/auth/img/google_secure_ldap_add_step_1.png +++ b/doc/administration/auth/img/google_secure_ldap_add_step_1.png diff --git a/doc/administration/auth/img/google_secure_ldap_add_step_2.png b/doc/administration/auth/img/google_secure_ldap_add_step_2.png Binary files differindex 611a21ae03c..b127410cb8c 100644 --- a/doc/administration/auth/img/google_secure_ldap_add_step_2.png +++ b/doc/administration/auth/img/google_secure_ldap_add_step_2.png diff --git a/doc/administration/auth/img/google_secure_ldap_client_settings.png b/doc/administration/auth/img/google_secure_ldap_client_settings.png Binary files differindex 3c0b3f3d4bd..868e6645f56 100644 --- a/doc/administration/auth/img/google_secure_ldap_client_settings.png +++ b/doc/administration/auth/img/google_secure_ldap_client_settings.png diff --git a/doc/administration/geo/disaster_recovery/img/checksum-differences-admin-project-page.png b/doc/administration/geo/disaster_recovery/img/checksum-differences-admin-project-page.png Binary files differindex fd51523104b..0a7b841897b 100644 --- a/doc/administration/geo/disaster_recovery/img/checksum-differences-admin-project-page.png +++ b/doc/administration/geo/disaster_recovery/img/checksum-differences-admin-project-page.png diff --git a/doc/administration/geo/disaster_recovery/img/checksum-differences-admin-projects.png b/doc/administration/geo/disaster_recovery/img/checksum-differences-admin-projects.png Binary files differindex b2a6da69d3d..85759d903a4 100644 --- a/doc/administration/geo/disaster_recovery/img/checksum-differences-admin-projects.png +++ b/doc/administration/geo/disaster_recovery/img/checksum-differences-admin-projects.png diff --git a/doc/administration/geo/replication/img/geo_architecture.png b/doc/administration/geo/replication/img/geo_architecture.png Binary files differindex d318cd5d0f4..aac63be41ff 100644 --- a/doc/administration/geo/replication/img/geo_architecture.png +++ b/doc/administration/geo/replication/img/geo_architecture.png diff --git a/doc/administration/geo/replication/troubleshooting.md b/doc/administration/geo/replication/troubleshooting.md index 28abfff973d..fe1557fd8b5 100644 --- a/doc/administration/geo/replication/troubleshooting.md +++ b/doc/administration/geo/replication/troubleshooting.md @@ -230,7 +230,7 @@ sudo gitlab-ctl reconfigure This will increase the timeout to three hours (10800 seconds). Choose a time long enough to accommodate a full clone of your largest repositories. -### Reseting Geo **secondary** node replication +### Resetting Geo **secondary** node replication If you get a **secondary** node in a broken state and want to reset the replication state, to start again from scratch, there are a few steps that can help you: @@ -524,6 +524,20 @@ If it doesn't exist or inadvertent changes have been made to it, run `sudo gitla If this path is mounted on a remote volume, please check your volume configuration and that it has correct permissions. +### An existing tracking database cannot be reused + +Geo cannot reuse an existing tracking database. + +It is safest to use a fresh secondary, or reset the whole secondary by following +[Resetting Geo secondary node replication](#resetting-geo-secondary-node-replication). + +If you are not concerned about possible orphaned directories and files, then you +can simply reset the existing tracking database with: + +```sh +sudo gitlab-rake geo:db:reset +``` + ### Geo node has a database that is writable which is an indication it is not configured for replication with the primary node. This error refers to a problem with the database replica on a **secondary** node, diff --git a/doc/administration/high_availability/database.md b/doc/administration/high_availability/database.md index f7a1f425b40..7c9e02d889e 100644 --- a/doc/administration/high_availability/database.md +++ b/doc/administration/high_availability/database.md @@ -327,6 +327,7 @@ When installing the GitLab package, do not supply `EXTERNAL_URL` value. postgresql['sql_user_password'] = 'POSTGRESQL_PASSWORD_HASH' # Replace X with value of number of db nodes + 1 postgresql['max_wal_senders'] = X + postgresql['max_replication_slots'] = X # Replace XXX.XXX.XXX.XXX/YY with Network Address postgresql['trust_auth_cidr_addresses'] = %w(XXX.XXX.XXX.XXX/YY) diff --git a/doc/administration/high_availability/img/fully-distributed.png b/doc/administration/high_availability/img/fully-distributed.png Binary files differindex ad23207134e..c3cd2bf24f0 100644 --- a/doc/administration/high_availability/img/fully-distributed.png +++ b/doc/administration/high_availability/img/fully-distributed.png diff --git a/doc/administration/high_availability/img/horizontal.png b/doc/administration/high_availability/img/horizontal.png Binary files differindex c3bd489d96f..75d08e1097a 100644 --- a/doc/administration/high_availability/img/horizontal.png +++ b/doc/administration/high_availability/img/horizontal.png diff --git a/doc/administration/high_availability/img/hybrid.png b/doc/administration/high_availability/img/hybrid.png Binary files differindex 7d4a56bf0ea..8dd9923e597 100644 --- a/doc/administration/high_availability/img/hybrid.png +++ b/doc/administration/high_availability/img/hybrid.png diff --git a/doc/administration/img/custom_hooks_error_msg.png b/doc/administration/img/custom_hooks_error_msg.png Binary files differindex 4f25c471908..e7d5c157d08 100644 --- a/doc/administration/img/custom_hooks_error_msg.png +++ b/doc/administration/img/custom_hooks_error_msg.png diff --git a/doc/administration/index.md b/doc/administration/index.md index f7f9d753e58..91a4d5097f2 100644 --- a/doc/administration/index.md +++ b/doc/administration/index.md @@ -188,3 +188,5 @@ Learn how to install, configure, update, and maintain your GitLab instance. - Useful [diagnostics tools](troubleshooting/diagnostics_tools.md) that are sometimes used by the GitLab Support team. - [Troubleshooting ElasticSearch](troubleshooting/elasticsearch.md): Tips to troubleshoot ElasticSearch. +- [Kubernetes troubleshooting](troubleshooting/kubernetes_cheat_sheet.md): Commands and tips useful + for troubleshooting Kubernetes-related issues. diff --git a/doc/administration/logs.md b/doc/administration/logs.md index 31876dd178a..47abbc512e0 100644 --- a/doc/administration/logs.md +++ b/doc/administration/logs.md @@ -151,14 +151,15 @@ etc. For example: {"severity":"ERROR","time":"2018-11-23T15:42:11.647Z","exception":"Kubeclient::HttpError","error_code":null,"service":"Clusters::Applications::InstallService","app_id":2,"project_ids":[19],"group_ids":[],"message":"SSL_connect returned=1 errno=0 state=error: certificate verify failed (unable to get local issuer certificate)"} ``` -## `githost.log` +## `git_json.log` -This file lives in `/var/log/gitlab/gitlab-rails/githost.log` for -Omnibus GitLab packages or in `/home/git/gitlab/log/githost.log` for +This file lives in `/var/log/gitlab/gitlab-rails/git_json.log` for +Omnibus GitLab packages or in `/home/git/gitlab/log/git_json.log` for installations from source. NOTE: **Note:** -After 12.2, this file will be stored in JSON format. +After 12.2, this file was renamed from `githost.log` to +`git_json.log` and stored in JSON format. GitLab has to interact with Git repositories but in some rare cases something can go wrong and in this case you will know what exactly diff --git a/doc/administration/monitoring/performance/img/performance_bar.png b/doc/administration/monitoring/performance/img/performance_bar.png Binary files differindex 89b09054d46..d1187fd879a 100644 --- a/doc/administration/monitoring/performance/img/performance_bar.png +++ b/doc/administration/monitoring/performance/img/performance_bar.png diff --git a/doc/administration/monitoring/performance/img/performance_bar_gitaly_calls.png b/doc/administration/monitoring/performance/img/performance_bar_gitaly_calls.png Binary files differindex 265178729c4..2c43201cbd0 100644 --- a/doc/administration/monitoring/performance/img/performance_bar_gitaly_calls.png +++ b/doc/administration/monitoring/performance/img/performance_bar_gitaly_calls.png diff --git a/doc/administration/monitoring/performance/img/performance_bar_redis_calls.png b/doc/administration/monitoring/performance/img/performance_bar_redis_calls.png Binary files differindex 78dd7594adf..ecb2dffbf8d 100644 --- a/doc/administration/monitoring/performance/img/performance_bar_redis_calls.png +++ b/doc/administration/monitoring/performance/img/performance_bar_redis_calls.png diff --git a/doc/administration/monitoring/performance/img/performance_bar_rugged_calls.png b/doc/administration/monitoring/performance/img/performance_bar_rugged_calls.png Binary files differindex f4068268137..210f80a713d 100644 --- a/doc/administration/monitoring/performance/img/performance_bar_rugged_calls.png +++ b/doc/administration/monitoring/performance/img/performance_bar_rugged_calls.png diff --git a/doc/administration/monitoring/performance/img/performance_bar_sql_queries.png b/doc/administration/monitoring/performance/img/performance_bar_sql_queries.png Binary files differindex dab323eb066..6b571f4e85c 100644 --- a/doc/administration/monitoring/performance/img/performance_bar_sql_queries.png +++ b/doc/administration/monitoring/performance/img/performance_bar_sql_queries.png diff --git a/doc/administration/monitoring/performance/img/request_profile_result.png b/doc/administration/monitoring/performance/img/request_profile_result.png Binary files differindex 3b34f207974..9176a0b49fd 100644 --- a/doc/administration/monitoring/performance/img/request_profile_result.png +++ b/doc/administration/monitoring/performance/img/request_profile_result.png diff --git a/doc/administration/operations/img/sidekiq-cluster.png b/doc/administration/operations/img/sidekiq-cluster.png Binary files differindex 4eb1849010e..3899385eb8f 100644 --- a/doc/administration/operations/img/sidekiq-cluster.png +++ b/doc/administration/operations/img/sidekiq-cluster.png diff --git a/doc/administration/pages/img/lets_encrypt_integration_v12_1.png b/doc/administration/pages/img/lets_encrypt_integration_v12_1.png Binary files differindex 5ab63074e12..0f3ca25ce55 100644 --- a/doc/administration/pages/img/lets_encrypt_integration_v12_1.png +++ b/doc/administration/pages/img/lets_encrypt_integration_v12_1.png diff --git a/doc/administration/troubleshooting/kubernetes_cheat_sheet.md b/doc/administration/troubleshooting/kubernetes_cheat_sheet.md new file mode 100644 index 00000000000..95cdb1508fa --- /dev/null +++ b/doc/administration/troubleshooting/kubernetes_cheat_sheet.md @@ -0,0 +1,251 @@ +--- +type: reference +--- + +# Kubernetes, GitLab and You + +This is a list of useful information regarding Kubernetes that the GitLab Support +Team sometimes uses while troubleshooting. GitLab is making this public, so that anyone +can make use of the Support team's collected knowledge + +CAUTION: **Caution:** +These commands **can alter or break** your Kubernetes components so use these at your own risk. + +If you are on a [paid tier](https://about.gitlab.com/pricing/) and are not sure how +to use these commands, it is best to [contact Support](https://about.gitlab.com/support/) +and they will assist you with any issues you are having. + +## Generic kubernetes commands + +- How to authorize to your GCP project (can be especially useful if you have projects + under different GCP accounts): + + ```bash + gcloud auth login + ``` + +- How to access Kubernetes dashboard: + + ```bash + # for minikube: + minikube dashboard —url + # for non-local installations if access via kubectl is configured: + kubectl proxy + ``` + +- How to ssh to a Kubernetes node and enter the container as root + <https://github.com/kubernetes/kubernetes/issues/30656>: + + - For GCP, you may find the node name and run `gcloud compute ssh node-name`. + - List containers using `docker ps`. + - Enter container using `docker exec --user root -ti container-id bash`. + +- How to copy a file from local machine to a pod: + + ```bash + kubectl cp file-name pod-name:./destination-path + ``` + +- What to do with pods in `CrashLoopBackoff` status: + + - Check logs via Kubernetes dashboard. + - Check logs via `kubectl`: + + ```bash + kubectl logs <unicorn pod> -c dependencies + ``` + +- How to tail all Kubernetes cluster events in real time: + + ```bash + kubectl get events -w --all-namespaces + ``` + +- How to get logs of the previously terminated pod instance: + + ```bash + kubectl logs <pod-name> --previous + ``` + + NOTE: **Note:** + No logs are kept in the containers/pods themselves, everything is written to stdout. + This is the principle of Kubernetes, read [Twelve-factor app](https://12factor.net/) + for details. + +## Gitlab-specific kubernetes information + +- Minimal config that can be used to test a Kubernetes helm chart can be found + [here](https://gitlab.com/charts/gitlab/issues/620). + +- Tailing logs of a separate pod. An example for a unicorn pod: + + ```bash + kubectl logs gitlab-unicorn-7656fdd6bf-jqzfs -c unicorn + ``` + +- It is not possible to get all the logs via `kubectl` at once, like with `gitlab-ctl tail`, + but a number of third-party tools can be used to do it: + + - [Kubetail](https://github.com/johanhaleby/kubetail) + - [kail: kubernetes tail](https://github.com/boz/kail) + - [stern](https://github.com/wercker/stern) + +- Check all events in the `gitlab` namespace (the namespace name can be different if you + specified a different one when deploying the helm chart): + + ```bash + kubectl get events -w --namespace=gitlab + ``` + +- Most of the useful GitLab tools (console, rake tasks, etc) are found in the task-runner + pod. You may enter it and run commands inside or run them from the outside: + + ```bash + # find the pod + kubectl get pods | grep task-runner + + # enter it + kubectl exec -it <task-runner-pod-name> bash + + # open rails console + # rails console can be also called from other GitLab pods + /srv/gitlab/bin/rails console + + # source-style commands should also work + /srv/gitlab && bundle exec rake gitlab:check RAILS_ENV=production + + # run GitLab check. Note that the output can be confusing and invalid because of the specific structure of GitLab installed via helm chart + /usr/local/bin/gitlab-rake gitlab:check + + # open console without entering pod + kubectl exec -it <task-runner-pod-name> /srv/gitlab/bin/rails console + + # check the status of DB migrations + kubectl exec -it <task-runner-pod-name> /usr/local/bin/gitlab-rake db:migrate:status + ``` + + You can also use `gitlab-rake`, instead of `/usr/local/bin/gitlab-rake`. + +- Troubleshooting **Operations > Kubernetes** integration: + + - Check the output of `kubectl get events -w --all-namespaces`. + - Check the logs of pods within `gitlab-managed-apps` namespace. + - On the side of GitLab check sidekiq log and kubernetes log. When GitLab is installed + via helm chart, kubernetes.log can be found inside the sidekiq pod. + +- How to get your initial admin password <https://docs.gitlab.com/charts/installation/deployment.html#initial-login>: + + ```bash + # find the name of the secret containing the password + kubectl get secrets | grep initial-root + # decode it + kubectl get secret <secret-name> -ojsonpath={.data.password} | base64 --decode ; echo + ``` + +- How to connect to a GitLab postgres database: + + ```bash + kubectl exec -it <task-runner-pod-name> -- /srv/gitlab/bin/rails dbconsole -p + ``` + +- How to get info about helm installation status: + + ```bash + helm status name-of-installation + ``` + +- How to update GitLab installed using helm chart: + + ```bash + helm repo upgrade + + # get current values and redirect them to yaml file (analogue of gitlab.rb values) + helm get values <release name> > gitlab.yaml + + # run upgrade itself + helm upgrade <release name> <chart path> -f gitlab.yaml + ``` + + After <https://canary.gitlab.com/charts/gitlab/issues/780> is fixed, it should + be possible to use [Updating GitLab using the Helm Chart](https://docs.gitlab.com/ee/install/kubernetes/gitlab_chart.html#updating-gitlab-using-the-helm-chart) + for upgrades. + +- How to apply changes to GitLab config: + + - Modify the `gitlab.yaml` file. + - Run the following command to apply changes: + + ```bash + helm upgrade <release name> <chart path> -f gitlab.yaml + ``` + +## Installation of minimal GitLab config via minukube on macOS + +This section is based on [Developing for Kubernetes with Minikube](https://gitlab.com/charts/gitlab/blob/master/doc/minikube/index.md) +and [Helm](https://gitlab.com/charts/gitlab/blob/master/doc/helm/index.md). Refer +to those documents for details. + +- Install kubectl via Homebrew: + + ```bash + brew install kubernetes-cli + ``` + +- Install minikube via Homebrew: + + ```bash + brew cask install minikube + ``` + +- Start minikube and configure it. If minikube cannot start, try running `minikube delete && minikube start` + and repeat the steps: + + ```bash + minikube start --cpus 3 --memory 8192 # minimum amount for GitLab to work + minikube addons enable ingress + minikube addons enable kube-dns + ``` + +- Install helm via Homebrew and initialize it: + + ```bash + brew install kubernetes-helm + helm init --service-account tiller + ``` + +- Copy the file <https://gitlab.com/charts/gitlab/raw/master/examples/values-minikube-minimum.yaml> + to your workstation. + +- Find the IP address in the output of `minikube ip` and update the yaml file with + this IP address. + +- Install the GitLab helm chart: + + ```bash + helm repo add gitlab https://charts.gitlab.io + helm install --name gitlab -f <path-to-yaml-file> gitlab/gitlab + ``` + + If you want to modify some GitLab settings, you can use the above-mentioned config + as a base and create your own yaml file. + +- Monitor the installation progress via `helm status gitlab` and `minikube dashboard`. + The installation could take up to 20-30 minutes depending on the amount of resources + on your workstation. + +- When all the pods show either a `Running` or `Completed` status, get the GitLab password as + described in [Initial login](https://docs.gitlab.com/ee/install/kubernetes/gitlab_chart.html#initial-login), + and log in to GitLab via the UI. It will be accessible via `https://gitlab.domain` + where `domain` is the value provided in the yaml file. + +<!-- ## Troubleshooting + +Include any troubleshooting steps that you can foresee. If you know beforehand what issues +one might have when setting this up, or when something is changed, or on upgrading, it's +important to describe those, too. Think of things that may go wrong and include them here. +This is important to minimize requests for support, and to avoid doc comments with +questions that you know someone might ask. + +Each scenario can be a third-level heading, e.g. `### Getting error message X`. +If you have none to add when creating a doc, leave this section in place +but commented out to help encourage others to add to it in the future. --> diff --git a/doc/api/README.md b/doc/api/README.md index 6cd89e34921..0195ce3912a 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -4,149 +4,10 @@ Automate GitLab via a simple and powerful API. The main GitLab API is a [REST](https://en.wikipedia.org/wiki/Representational_state_transfer) API. Therefore, documentation in this section assumes knowledge of REST concepts. -## API resources - -Available API resources can be grouped in the following contexts: - -- [Projects](#project-resources). -- [Groups](#group-resources). -- [Standalone](#standalone-resources). - -See also: - -- [V3 to V4](v3_to_v4.md). -- Adding [deploy keys for multiple projects](deploy_key_multiple_projects.md). - -### Project resources - -The following API resources are available in the project context: - -| Resource | Available endpoints | -|:--------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [Access requests](access_requests.md) | `/projects/:id/access_requests` (also available for groups) | -| [Award emoji](award_emoji.md) | `/projects/:id/issues/.../award_emoji`, `/projects/:id/merge_requests/.../award_emoji`, `/projects/:id/snippets/.../award_emoji` | -| [Branches](branches.md) | `/projects/:id/repository/branches/`, `/projects/:id/repository/merged_branches` | -| [Commits](commits.md) | `/projects/:id/repository/commits`, `/projects/:id/statuses` | -| [Container Registry](container_registry.md) | `/projects/:id/registry/repositories` | -| [Custom attributes](custom_attributes.md) | `/projects/:id/custom_attributes` (also available for groups and users) | -| [Dependencies](dependencies.md) **(ULTIMATE)** | `/projects/:id/dependencies` -| [Deploy keys](deploy_keys.md) | `/projects/:id/deploy_keys` (also available standalone) | -| [Deployments](deployments.md) | `/projects/:id/deployments` | -| [Discussions](discussions.md) (threaded comments) | `/projects/:id/issues/.../discussions`, `/projects/:id/snippets/.../discussions`, `/projects/:id/merge_requests/.../discussions`, `/projects/:id/commits/.../discussions` (also available for groups) | -| [Environments](environments.md) | `/projects/:id/environments` | -| [Events](events.md) | `/projects/:id/events` (also available for users and standalone) | -| [Issues](issues.md) | `/projects/:id/issues` (also available for groups and standalone) | -| [Issues Statistics](issues_statistics.md) | `/projects/:id/issues_statistics` (also available for groups and standalone) | -| [Issue boards](boards.md) | `/projects/:id/boards` | -| [Issue links](issue_links.md) **(STARTER)** | `/projects/:id/issues/.../links` | -| [Jobs](jobs.md) | `/projects/:id/jobs`, `/projects/:id/pipelines/.../jobs` | -| [Labels](labels.md) | `/projects/:id/labels` | -| [Managed licenses](managed_licenses.md) **(ULTIMATE)** | `/projects/:id/managed_licenses` | -| [Members](members.md) | `/projects/:id/members` (also available for groups) | -| [Merge request approvals](merge_request_approvals.md) **(STARTER)** | `/projects/:id/approvals`, `/projects/:id/merge_requests/.../approvals` | -| [Merge requests](merge_requests.md) | `/projects/:id/merge_requests` (also available for groups and standalone) | -| [Notes](notes.md) (comments) | `/projects/:id/issues/.../notes`, `/projects/:id/snippets/.../notes`, `/projects/:id/merge_requests/.../notes` (also available for groups) | -| [Notification settings](notification_settings.md) | `/projects/:id/notification_settings` (also available for groups and standalone) | -| [Packages](packages.md) **(PREMIUM)** | `/projects/:id/packages` | -| [Pages domains](pages_domains.md) | `/projects/:id/pages` (also available standalone) | -| [Pipelines](pipelines.md) | `/projects/:id/pipelines` | -| [Pipeline schedules](pipeline_schedules.md) | `/projects/:id/pipeline_schedules` | -| [Pipeline triggers](pipeline_triggers.md) | `/projects/:id/triggers` | -| [Projects](projects.md) including setting Webhooks | `/projects`, `/projects/:id/hooks` (also available for users) | -| [Project badges](project_badges.md) | `/projects/:id/badges` | -| [Project clusters](project_clusters.md) | `/projects/:id/clusters` | -| [Project-level variables](project_level_variables.md) | `/projects/:id/variables` | -| [Project import/export](project_import_export.md) | `/projects/:id/export`, `/projects/import`, `/projects/:id/import` | -| [Project milestones](milestones.md) | `/projects/:id/milestones` | -| [Project snippets](project_snippets.md) | `/projects/:id/snippets` | -| [Project templates](project_templates.md) | `/projects/:id/templates` | -| [Protected branches](protected_branches.md) | `/projects/:id/protected_branches` | -| [Protected tags](protected_tags.md) | `/projects/:id/protected_tags` | -| [Releases](releases/index.md) | `/projects/:id/releases` | -| [Release links](releases/links.md) | `/projects/:id/releases/.../assets/links` | -| [Repositories](repositories.md) | `/projects/:id/repository` | -| [Repository files](repository_files.md) | `/projects/:id/repository/files` | -| [Repository submodules](repository_submodules.md) | `/projects/:id/repository/submodules` | -| [Resource label events](resource_label_events.md) | `/projects/:id/issues/.../resource_label_events`, `/projects/:id/merge_requests/.../resource_label_events` (also available for groups) | -| [Runners](runners.md) | `/projects/:id/runners` (also available standalone) | -| [Search](search.md) | `/projects/:id/search` (also available for groups and standalone) | -| [Services](services.md) | `/projects/:id/services` | -| [Tags](tags.md) | `/projects/:id/repository/tags` | -| [Vulnerabilities](vulnerabilities.md) **(ULTIMATE)** | `/projects/:id/vulnerabilities` (also available for groups) | -| [Wikis](wikis.md) | `/projects/:id/wikis` | - -### Group resources - -The following API resources are available in the group context: - -| Resource | Available endpoints | -|:-----------------------------------------------------------------|:---------------------------------------------------------------------------------| -| [Access requests](access_requests.md) | `/groups/:id/access_requests/` (also available for projects) | -| [Custom attributes](custom_attributes.md) | `/groups/:id/custom_attributes` (also available for projects and users) | -| [Discussions](discussions.md) (threaded comments) **(ULTIMATE)** | `/groups/:id/epics/.../discussions` (also available for projects) | -| [Epic issues](epic_issues.md) **(ULTIMATE)** | `/groups/:id/epics/.../issues` | -| [Epic links](epic_links.md) **(ULTIMATE)** | `/groups/:id/epics/.../epics` | -| [Epics](epics.md) **(ULTIMATE)** | `/groups/:id/epics` | -| [Groups](groups.md) | `/groups`, `/groups/.../subgroups` | -| [Group badges](group_badges.md) | `/groups/:id/badges` | -| [Group issue boards](group_boards.md) | `/groups/:id/boards` | -| [Group labels](group_labels.md) | `/groups/:id/labels` | -| [Group-level variables](group_level_variables.md) | `/groups/:id/variables` | -| [Group milestones](group_milestones.md) | `/groups/:id/milestones` | -| [Issues](issues.md) | `/groups/:id/issues` (also available for projects and standalone) | -| [Issues Statistics](issues_statistics.md) | `/groups/:id/issues_statistics` (also available for projects and standalone) | -| [Members](members.md) | `/groups/:id/members` (also available for projects) | -| [Merge requests](merge_requests.md) | `/groups/:id/merge_requests` (also available for projects and standalone) | -| [Notes](notes.md) (comments) | `/groups/:id/epics/.../notes` (also available for projects) | -| [Notification settings](notification_settings.md) | `/groups/:id/notification_settings` (also available for projects and standalone) | -| [Resource label events](resource_label_events.md) | `/groups/:id/epics/.../resource_label_events` (also available for projects) | -| [Search](search.md) | `/groups/:id/search` (also available for projects and standalone) | - -### Standalone resources - -The following API resources are available outside of project and group contexts (including `/users`): - -| Resource | Available endpoints | -|:--------------------------------------------------|:------------------------------------------------------------------------| -| [Applications](applications.md) | `/applications` | -| [Avatar](avatar.md) | `/avatar` | -| [Broadcast messages](broadcast_messages.md) | `/broadcast_messages` | -| [Code snippets](snippets.md) | `/snippets` | -| [Custom attributes](custom_attributes.md) | `/users/:id/custom_attributes` (also available for groups and projects) | -| [Deploy keys](deploy_keys.md) | `/deploy_keys` (also available for projects) | -| [Events](events.md) | `/events`, `/users/:id/events` (also available for projects) | -| [Feature flags](features.md) | `/features` | -| [Geo Nodes](geo_nodes.md) **(PREMIUM ONLY)** | `/geo_nodes` | -| [Import repository from GitHub](import.md) | `/import/github` | -| [Issues](issues.md) | `/issues` (also available for groups and projects) | -| [Issues Statistics](issues_statistics.md) | `/issues_statistics` (also available for groups and projects) | -| [Keys](keys.md) | `/keys` | -| [License](license.md) **(CORE ONLY)** | `/license` | -| [Markdown](markdown.md) | `/markdown` | -| [Merge requests](merge_requests.md) | `/merge_requests` (also available for groups and projects) | -| [Namespaces](namespaces.md) | `/namespaces` | -| [Notification settings](notification_settings.md) | `/notification_settings` (also available for groups and projects) | -| [Pages domains](pages_domains.md) | `/pages/domains` (also available for projects) | -| [Projects](projects.md) | `/users/:id/projects` (also available for projects) | -| [Runners](runners.md) | `/runners` (also available for projects) | -| [Search](search.md) | `/search` (also available for groups and projects) | -| [Settings](settings.md) | `/application/settings` | -| [Sidekiq metrics](sidekiq_metrics.md) | `/sidekiq` | -| [Suggestions](suggestions.md) | `/suggestions` | -| [System hooks](system_hooks.md) | `/hooks` | -| [Todos](todos.md) | `/todos` | -| [Users](users.md) | `/users` | -| [Validate `.gitlab-ci.yml` file](lint.md) | `/lint` | -| [Version](version.md) | `/version` | - -### Templates API resources - -Endpoints are available for: - -- [Dockerfile templates](templates/dockerfiles.md). -- [`.gitignore` templates](templates/gitignores.md). -- [GitLab CI YAML templates](templates/gitlab_ci_ymls.md). -- [Open source license templates](templates/licenses.md). +## Available API resources + +For a list of the available resources and their endpoints, see +[API resources](api_resources.md). ## SCIM **(SILVER ONLY)** @@ -697,10 +558,10 @@ programming languages. Visit the [GitLab website] for a complete list. ## Rate limits -For administrator documentation on rate limit settings, check out +For administrator documentation on rate limit settings, see [Rate limits](../security/rate_limits.md). To find the settings that are specifically used by GitLab.com, see -[GitLab.com-specific rate limits](../user/gitlab_com/index.md). +[GitLab.com-specific rate limits](../user/gitlab_com/index.md#gitlabcom-specific-rate-limits). [GitLab website]: https://about.gitlab.com/applications/#api-clients "Clients using the GitLab API" [lib-api-url]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/lib/api/api.rb diff --git a/doc/api/api_resources.md b/doc/api/api_resources.md new file mode 100644 index 00000000000..b32f11464ef --- /dev/null +++ b/doc/api/api_resources.md @@ -0,0 +1,144 @@ +# API resources + +Available resources for the [GitLab API](README.md) can be grouped in the following contexts: + +- [Projects](#project-resources). +- [Groups](#group-resources). +- [Standalone](#standalone-resources). + +See also: + +- [V3 to V4](v3_to_v4.md). +- Adding [deploy keys for multiple projects](deploy_key_multiple_projects.md). +- [API Resources for various templates](#templates-api-resources). + +## Project resources + +The following API resources are available in the project context: + +| Resource | Available endpoints | +|:--------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [Access requests](access_requests.md) | `/projects/:id/access_requests` (also available for groups) | +| [Award emoji](award_emoji.md) | `/projects/:id/issues/.../award_emoji`, `/projects/:id/merge_requests/.../award_emoji`, `/projects/:id/snippets/.../award_emoji` | +| [Branches](branches.md) | `/projects/:id/repository/branches/`, `/projects/:id/repository/merged_branches` | +| [Commits](commits.md) | `/projects/:id/repository/commits`, `/projects/:id/statuses` | +| [Container Registry](container_registry.md) | `/projects/:id/registry/repositories` | +| [Custom attributes](custom_attributes.md) | `/projects/:id/custom_attributes` (also available for groups and users) | +| [Dependencies](dependencies.md) **(ULTIMATE)** | `/projects/:id/dependencies` +| [Deploy keys](deploy_keys.md) | `/projects/:id/deploy_keys` (also available standalone) | +| [Deployments](deployments.md) | `/projects/:id/deployments` | +| [Discussions](discussions.md) (threaded comments) | `/projects/:id/issues/.../discussions`, `/projects/:id/snippets/.../discussions`, `/projects/:id/merge_requests/.../discussions`, `/projects/:id/commits/.../discussions` (also available for groups) | +| [Environments](environments.md) | `/projects/:id/environments` | +| [Events](events.md) | `/projects/:id/events` (also available for users and standalone) | +| [Issues](issues.md) | `/projects/:id/issues` (also available for groups and standalone) | +| [Issues Statistics](issues_statistics.md) | `/projects/:id/issues_statistics` (also available for groups and standalone) | +| [Issue boards](boards.md) | `/projects/:id/boards` | +| [Issue links](issue_links.md) **(STARTER)** | `/projects/:id/issues/.../links` | +| [Jobs](jobs.md) | `/projects/:id/jobs`, `/projects/:id/pipelines/.../jobs` | +| [Labels](labels.md) | `/projects/:id/labels` | +| [Managed licenses](managed_licenses.md) **(ULTIMATE)** | `/projects/:id/managed_licenses` | +| [Members](members.md) | `/projects/:id/members` (also available for groups) | +| [Merge request approvals](merge_request_approvals.md) **(STARTER)** | `/projects/:id/approvals`, `/projects/:id/merge_requests/.../approvals` | +| [Merge requests](merge_requests.md) | `/projects/:id/merge_requests` (also available for groups and standalone) | +| [Notes](notes.md) (comments) | `/projects/:id/issues/.../notes`, `/projects/:id/snippets/.../notes`, `/projects/:id/merge_requests/.../notes` (also available for groups) | +| [Notification settings](notification_settings.md) | `/projects/:id/notification_settings` (also available for groups and standalone) | +| [Packages](packages.md) **(PREMIUM)** | `/projects/:id/packages` | +| [Pages domains](pages_domains.md) | `/projects/:id/pages` (also available standalone) | +| [Pipelines](pipelines.md) | `/projects/:id/pipelines` | +| [Pipeline schedules](pipeline_schedules.md) | `/projects/:id/pipeline_schedules` | +| [Pipeline triggers](pipeline_triggers.md) | `/projects/:id/triggers` | +| [Projects](projects.md) including setting Webhooks | `/projects`, `/projects/:id/hooks` (also available for users) | +| [Project badges](project_badges.md) | `/projects/:id/badges` | +| [Project clusters](project_clusters.md) | `/projects/:id/clusters` | +| [Project-level variables](project_level_variables.md) | `/projects/:id/variables` | +| [Project import/export](project_import_export.md) | `/projects/:id/export`, `/projects/import`, `/projects/:id/import` | +| [Project milestones](milestones.md) | `/projects/:id/milestones` | +| [Project snippets](project_snippets.md) | `/projects/:id/snippets` | +| [Project templates](project_templates.md) | `/projects/:id/templates` | +| [Protected branches](protected_branches.md) | `/projects/:id/protected_branches` | +| [Protected tags](protected_tags.md) | `/projects/:id/protected_tags` | +| [Releases](releases/index.md) | `/projects/:id/releases` | +| [Release links](releases/links.md) | `/projects/:id/releases/.../assets/links` | +| [Repositories](repositories.md) | `/projects/:id/repository` | +| [Repository files](repository_files.md) | `/projects/:id/repository/files` | +| [Repository submodules](repository_submodules.md) | `/projects/:id/repository/submodules` | +| [Resource label events](resource_label_events.md) | `/projects/:id/issues/.../resource_label_events`, `/projects/:id/merge_requests/.../resource_label_events` (also available for groups) | +| [Runners](runners.md) | `/projects/:id/runners` (also available standalone) | +| [Search](search.md) | `/projects/:id/search` (also available for groups and standalone) | +| [Services](services.md) | `/projects/:id/services` | +| [Tags](tags.md) | `/projects/:id/repository/tags` | +| [Vulnerabilities](vulnerabilities.md) **(ULTIMATE)** | `/projects/:id/vulnerabilities` (also available for groups) | +| [Wikis](wikis.md) | `/projects/:id/wikis` | + +## Group resources + +The following API resources are available in the group context: + +| Resource | Available endpoints | +|:-----------------------------------------------------------------|:---------------------------------------------------------------------------------| +| [Access requests](access_requests.md) | `/groups/:id/access_requests/` (also available for projects) | +| [Custom attributes](custom_attributes.md) | `/groups/:id/custom_attributes` (also available for projects and users) | +| [Discussions](discussions.md) (threaded comments) **(ULTIMATE)** | `/groups/:id/epics/.../discussions` (also available for projects) | +| [Epic issues](epic_issues.md) **(ULTIMATE)** | `/groups/:id/epics/.../issues` | +| [Epic links](epic_links.md) **(ULTIMATE)** | `/groups/:id/epics/.../epics` | +| [Epics](epics.md) **(ULTIMATE)** | `/groups/:id/epics` | +| [Groups](groups.md) | `/groups`, `/groups/.../subgroups` | +| [Group badges](group_badges.md) | `/groups/:id/badges` | +| [Group issue boards](group_boards.md) | `/groups/:id/boards` | +| [Group labels](group_labels.md) | `/groups/:id/labels` | +| [Group-level variables](group_level_variables.md) | `/groups/:id/variables` | +| [Group milestones](group_milestones.md) | `/groups/:id/milestones` | +| [Issues](issues.md) | `/groups/:id/issues` (also available for projects and standalone) | +| [Issues Statistics](issues_statistics.md) | `/groups/:id/issues_statistics` (also available for projects and standalone) | +| [Members](members.md) | `/groups/:id/members` (also available for projects) | +| [Merge requests](merge_requests.md) | `/groups/:id/merge_requests` (also available for projects and standalone) | +| [Notes](notes.md) (comments) | `/groups/:id/epics/.../notes` (also available for projects) | +| [Notification settings](notification_settings.md) | `/groups/:id/notification_settings` (also available for projects and standalone) | +| [Resource label events](resource_label_events.md) | `/groups/:id/epics/.../resource_label_events` (also available for projects) | +| [Search](search.md) | `/groups/:id/search` (also available for projects and standalone) | + +## Standalone resources + +The following API resources are available outside of project and group contexts (including `/users`): + +| Resource | Available endpoints | +|:--------------------------------------------------|:------------------------------------------------------------------------| +| [Applications](applications.md) | `/applications` | +| [Avatar](avatar.md) | `/avatar` | +| [Broadcast messages](broadcast_messages.md) | `/broadcast_messages` | +| [Code snippets](snippets.md) | `/snippets` | +| [Custom attributes](custom_attributes.md) | `/users/:id/custom_attributes` (also available for groups and projects) | +| [Deploy keys](deploy_keys.md) | `/deploy_keys` (also available for projects) | +| [Events](events.md) | `/events`, `/users/:id/events` (also available for projects) | +| [Feature flags](features.md) | `/features` | +| [Geo Nodes](geo_nodes.md) **(PREMIUM ONLY)** | `/geo_nodes` | +| [Import repository from GitHub](import.md) | `/import/github` | +| [Issues](issues.md) | `/issues` (also available for groups and projects) | +| [Issues Statistics](issues_statistics.md) | `/issues_statistics` (also available for groups and projects) | +| [Keys](keys.md) | `/keys` | +| [License](license.md) **(CORE ONLY)** | `/license` | +| [Markdown](markdown.md) | `/markdown` | +| [Merge requests](merge_requests.md) | `/merge_requests` (also available for groups and projects) | +| [Namespaces](namespaces.md) | `/namespaces` | +| [Notification settings](notification_settings.md) | `/notification_settings` (also available for groups and projects) | +| [Pages domains](pages_domains.md) | `/pages/domains` (also available for projects) | +| [Projects](projects.md) | `/users/:id/projects` (also available for projects) | +| [Runners](runners.md) | `/runners` (also available for projects) | +| [Search](search.md) | `/search` (also available for groups and projects) | +| [Settings](settings.md) | `/application/settings` | +| [Sidekiq metrics](sidekiq_metrics.md) | `/sidekiq` | +| [Suggestions](suggestions.md) | `/suggestions` | +| [System hooks](system_hooks.md) | `/hooks` | +| [Todos](todos.md) | `/todos` | +| [Users](users.md) | `/users` | +| [Validate `.gitlab-ci.yml` file](lint.md) | `/lint` | +| [Version](version.md) | `/version` | + +## Templates API resources + +Endpoints are available for: + +- [Dockerfile templates](templates/dockerfiles.md). +- [`.gitignore` templates](templates/gitignores.md). +- [GitLab CI YAML templates](templates/gitlab_ci_ymls.md). +- [Open source license templates](templates/licenses.md). diff --git a/doc/api/deploy_key_multiple_projects.md b/doc/api/deploy_key_multiple_projects.md index 0c9e3e66cae..85df972746e 100644 --- a/doc/api/deploy_key_multiple_projects.md +++ b/doc/api/deploy_key_multiple_projects.md @@ -1,29 +1,5 @@ -# Adding deploy keys to multiple projects via API +--- +redirect_to: deploy_keys.md#adding-deploy-keys-to-multiple-projects +--- -If you want to easily add the same deploy key to multiple projects in the same -group, this can be achieved quite easily with the API. - -First, find the ID of the projects you're interested in, by either listing all -projects: - -``` -curl --header 'PRIVATE-TOKEN: <your_access_token>' https://gitlab.example.com/api/v4/projects -``` - -Or finding the ID of a group and then listing all projects in that group: - -``` -curl --header 'PRIVATE-TOKEN: <your_access_token>' https://gitlab.example.com/api/v4/groups - -# For group 1234: -curl --header 'PRIVATE-TOKEN: <your_access_token>' https://gitlab.example.com/api/v4/groups/1234 -``` - -With those IDs, add the same deploy key to all: - -``` -for project_id in 321 456 987; do - curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --header "Content-Type: application/json" \ - --data '{"title": "my key", "key": "ssh-rsa AAAA..."}' https://gitlab.example.com/api/v4/projects/${project_id}/deploy_keys -done -``` +This document was moved to [another location](deploy_keys.md#adding-deploy-keys-to-multiple-projects). diff --git a/doc/api/deploy_keys.md b/doc/api/deploy_keys.md index 41f6ab436e8..94351e1a300 100644 --- a/doc/api/deploy_keys.md +++ b/doc/api/deploy_keys.md @@ -203,3 +203,32 @@ Example response: "created_at" : "2015-08-29T12:44:31.550Z" } ``` +## Adding deploy keys to multiple projects + +If you want to easily add the same deploy key to multiple projects in the same +group, this can be achieved quite easily with the API. + +First, find the ID of the projects you're interested in, by either listing all +projects: + +``` +curl --header 'PRIVATE-TOKEN: <your_access_token>' https://gitlab.example.com/api/v4/projects +``` + +Or finding the ID of a group and then listing all projects in that group: + +``` +curl --header 'PRIVATE-TOKEN: <your_access_token>' https://gitlab.example.com/api/v4/groups + +# For group 1234: +curl --header 'PRIVATE-TOKEN: <your_access_token>' https://gitlab.example.com/api/v4/groups/1234 +``` + +With those IDs, add the same deploy key to all: + +``` +for project_id in 321 456 987; do + curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --header "Content-Type: application/json" \ + --data '{"title": "my key", "key": "ssh-rsa AAAA..."}' https://gitlab.example.com/api/v4/projects/${project_id}/deploy_keys +done +``` diff --git a/doc/api/group_labels.md b/doc/api/group_labels.md index 3d4b099b49e..e2ba0dea642 100644 --- a/doc/api/group_labels.md +++ b/doc/api/group_labels.md @@ -12,12 +12,13 @@ Get all labels for a given group. GET /groups/:id/labels ``` -| Attribute | Type | Required | Description | -| --------- | ---- | -------- | ----------- | -| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user. | +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user. | +| `with_counts` | boolean | no | Whether or not to include issue and merge request counts. Defaults to `false`. _([Introduced in GitLab 12.2](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/31543))_ | ```bash -curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/labels +curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/labels?with_counts=true ``` Example response: diff --git a/doc/api/labels.md b/doc/api/labels.md index 9d10d383bf9..5db0edcf14d 100644 --- a/doc/api/labels.md +++ b/doc/api/labels.md @@ -8,12 +8,13 @@ Get all labels for a given project. GET /projects/:id/labels ``` -| Attribute | Type | Required | Description | -| --------- | ------- | -------- | --------------------- | -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | +| Attribute | Type | Required | Description | +| --------- | ------- | -------- | --------------------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | +| `with_counts` | boolean | no | Whether or not to include issue and merge request counts. Defaults to `false`. _([Introduced in GitLab 12.2](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/31543))_ | ```bash -curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/labels +curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/labels?with_counts=true ``` Example response: diff --git a/doc/api/project_level_variables.md b/doc/api/project_level_variables.md index eab905bbc5f..591911bb8ec 100644 --- a/doc/api/project_level_variables.md +++ b/doc/api/project_level_variables.md @@ -74,7 +74,7 @@ POST /projects/:id/variables | `variable_type` | string | no | The type of a variable. Available types are: `env_var` (default) and `file` | | `protected` | boolean | no | Whether the variable is protected | | `masked` | boolean | no | Whether the variable is masked | -| `environment_scope` | string | no | The `environment_scope` of the variable **(PREMIUM)** | +| `environment_scope` | string | no | The `environment_scope` of the variable | ``` curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/variables" --form "key=NEW_VARIABLE" --form "value=new value" @@ -108,7 +108,7 @@ PUT /projects/:id/variables/:key | `variable_type` | string | no | The type of a variable. Available types are: `env_var` (default) and `file` | | `protected` | boolean | no | Whether the variable is protected | | `masked` | boolean | no | Whether the variable is masked | -| `environment_scope` | string | no | The `environment_scope` of the variable **(PREMIUM)** | +| `environment_scope` | string | no | The `environment_scope` of the variable | ``` curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/variables/NEW_VARIABLE" --form "value=updated value" diff --git a/doc/api/projects.md b/doc/api/projects.md index ba7e28c279b..70df44ec0fd 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -465,6 +465,194 @@ GET /users/:user_id/projects ] ``` +## List projects starred by a user + +Get a list of visible projects owned by the given user. When accessed without authentication, only public projects are returned. + +``` +GET /users/:user_id/starred_projects +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `user_id` | string | yes | The ID or username of the user. | +| `archived` | boolean | no | Limit by archived status. | +| `visibility` | string | no | Limit by visibility `public`, `internal`, or `private`. | +| `order_by` | string | no | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at`. | +| `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc`. | +| `search` | string | no | Return list of projects matching the search criteria. | +| `simple` | boolean | no | Return only limited fields for each project. This is a no-op without authentication as then _only_ simple fields are returned.. | +| `owned` | boolean | no | Limit by projects explicitly owned by the current user. | +| `membership` | boolean | no | Limit by projects that the current user is a member of. | +| `starred` | boolean | no | Limit by projects starred by the current user. | +| `statistics` | boolean | no | Include project statistics. | +| `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (admins only). | +| `with_issues_enabled` | boolean | no | Limit by enabled issues feature. | +| `with_merge_requests_enabled` | boolean | no | Limit by enabled merge requests feature. | +| `min_access_level` | integer | no | Limit by current user minimal [access level](members.md). | + +```bash +curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/users/5/starred_projects" +``` + +Example response: + +```json +[ + { + "id": 4, + "description": null, + "default_branch": "master", + "visibility": "private", + "ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git", + "http_url_to_repo": "http://example.com/diaspora/diaspora-client.git", + "web_url": "http://example.com/diaspora/diaspora-client", + "readme_url": "http://example.com/diaspora/diaspora-client/blob/master/README.md", + "tag_list": [ + "example", + "disapora client" + ], + "owner": { + "id": 3, + "name": "Diaspora", + "created_at": "2013-09-30T13:46:02Z" + }, + "name": "Diaspora Client", + "name_with_namespace": "Diaspora / Diaspora Client", + "path": "diaspora-client", + "path_with_namespace": "diaspora/diaspora-client", + "issues_enabled": true, + "open_issues_count": 1, + "merge_requests_enabled": true, + "jobs_enabled": true, + "wiki_enabled": true, + "snippets_enabled": false, + "resolve_outdated_diff_discussions": false, + "container_registry_enabled": false, + "created_at": "2013-09-30T13:46:02Z", + "last_activity_at": "2013-09-30T13:46:02Z", + "creator_id": 3, + "namespace": { + "id": 3, + "name": "Diaspora", + "path": "diaspora", + "kind": "group", + "full_path": "diaspora" + }, + "import_status": "none", + "archived": false, + "avatar_url": "http://example.com/uploads/project/avatar/4/uploads/avatar.png", + "shared_runners_enabled": true, + "forks_count": 0, + "star_count": 0, + "runners_token": "b8547b1dc37721d05889db52fa2f02", + "public_jobs": true, + "shared_with_groups": [], + "only_allow_merge_if_pipeline_succeeds": false, + "only_allow_merge_if_all_discussions_are_resolved": false, + "request_access_enabled": false, + "merge_method": "merge", + "statistics": { + "commit_count": 37, + "storage_size": 1038090, + "repository_size": 1038090, + "lfs_objects_size": 0, + "job_artifacts_size": 0 + }, + "_links": { + "self": "http://example.com/api/v4/projects", + "issues": "http://example.com/api/v4/projects/1/issues", + "merge_requests": "http://example.com/api/v4/projects/1/merge_requests", + "repo_branches": "http://example.com/api/v4/projects/1/repository_branches", + "labels": "http://example.com/api/v4/projects/1/labels", + "events": "http://example.com/api/v4/projects/1/events", + "members": "http://example.com/api/v4/projects/1/members" + } + }, + { + "id": 6, + "description": null, + "default_branch": "master", + "visibility": "private", + "ssh_url_to_repo": "git@example.com:brightbox/puppet.git", + "http_url_to_repo": "http://example.com/brightbox/puppet.git", + "web_url": "http://example.com/brightbox/puppet", + "readme_url": "http://example.com/brightbox/puppet/blob/master/README.md", + "tag_list": [ + "example", + "puppet" + ], + "owner": { + "id": 4, + "name": "Brightbox", + "created_at": "2013-09-30T13:46:02Z" + }, + "name": "Puppet", + "name_with_namespace": "Brightbox / Puppet", + "path": "puppet", + "path_with_namespace": "brightbox/puppet", + "issues_enabled": true, + "open_issues_count": 1, + "merge_requests_enabled": true, + "jobs_enabled": true, + "wiki_enabled": true, + "snippets_enabled": false, + "resolve_outdated_diff_discussions": false, + "container_registry_enabled": false, + "created_at": "2013-09-30T13:46:02Z", + "last_activity_at": "2013-09-30T13:46:02Z", + "creator_id": 3, + "namespace": { + "id": 4, + "name": "Brightbox", + "path": "brightbox", + "kind": "group", + "full_path": "brightbox" + }, + "import_status": "none", + "import_error": null, + "permissions": { + "project_access": { + "access_level": 10, + "notification_level": 3 + }, + "group_access": { + "access_level": 50, + "notification_level": 3 + } + }, + "archived": false, + "avatar_url": null, + "shared_runners_enabled": true, + "forks_count": 0, + "star_count": 0, + "runners_token": "b8547b1dc37721d05889db52fa2f02", + "public_jobs": true, + "shared_with_groups": [], + "only_allow_merge_if_pipeline_succeeds": false, + "only_allow_merge_if_all_discussions_are_resolved": false, + "request_access_enabled": false, + "merge_method": "merge", + "statistics": { + "commit_count": 12, + "storage_size": 2066080, + "repository_size": 2066080, + "lfs_objects_size": 0, + "job_artifacts_size": 0 + }, + "_links": { + "self": "http://example.com/api/v4/projects", + "issues": "http://example.com/api/v4/projects/1/issues", + "merge_requests": "http://example.com/api/v4/projects/1/merge_requests", + "repo_branches": "http://example.com/api/v4/projects/1/repository_branches", + "labels": "http://example.com/api/v4/projects/1/labels", + "events": "http://example.com/api/v4/projects/1/events", + "members": "http://example.com/api/v4/projects/1/members" + } + } +] +``` + ## Get single project Get a specific project. This endpoint can be accessed without authentication if @@ -1155,6 +1343,51 @@ Example response: } ``` +## List Starrers of a project + +List the users who starred the specified project. + +``` +GET /projects/:id/starrers +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `search` | string | no | Search for specific users. | + +```bash +curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/starrers" +``` + +Example responses: + +```json +[ + { + "starred_since": "2019-01-28T14:47:30.642Z", + "user": + { + "id": 1, + "username": "jane_smith", + "name": "Jane Smith", + "state": "active", + "avatar_url": "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg", + "web_url": "http://localhost:3000/jane_smith" + } + }, + "starred_since": "2018-01-02T11:40:26.570Z", + "user": + { + "id": 2, + "username": "janine_smith", + "name": "Janine Smith", + "state": "blocked", + "avatar_url": "http://gravatar.com/../e32131cd8.jpeg", + "web_url": "http://localhost:3000/janine_smith" + } +] +``` + ## Languages Get languages used in a project with percentage value. @@ -1676,18 +1909,20 @@ GET /projects/:id/push_rule "author_email_regex": "", "file_name_regex": "", "max_file_size": 5, - "commit_committer_check": false + "commit_committer_check": false, + "reject_unsigned_commits": false } ``` Users on GitLab [Premium, Silver, or higher](https://about.gitlab.com/pricing/) will also see -the `commit_committer_check` parameter: +the `commit_committer_check` and `reject_unsigned_commits` parameters: ```json { "id": 1, "project_id": 3, - "commit_committer_check": false + "commit_committer_check": false, + "reject_unsigned_commits": false ... } ``` @@ -1713,6 +1948,7 @@ POST /projects/:id/push_rule | `file_name_regex` **(STARTER)** | string | no | All commited filenames must **not** match this, e.g. `(jar|exe)$` | | `max_file_size` **(STARTER)** | integer | no | Maximum file size (MB) | | `commit_committer_check` **(PREMIUM)** | boolean | no | Users can only push commits to this repository that were committed with one of their own verified emails. | +| `reject_unsigned_commits` **(PREMIUM)** | boolean | no | Reject commit when it is not signed through GPG. | ### Edit project push rule @@ -1735,6 +1971,7 @@ PUT /projects/:id/push_rule | `file_name_regex` **(STARTER)** | string | no | All commited filenames must **not** match this, e.g. `(jar|exe)$` | | `max_file_size` **(STARTER)** | integer | no | Maximum file size (MB) | | `commit_committer_check` **(PREMIUM)** | boolean | no | Users can only push commits to this repository that were committed with one of their own verified emails. | +| `reject_unsigned_commits` **(PREMIUM)** | boolean | no | Reject commits when they are not GPG signed. | ### Delete project push rule diff --git a/doc/api/releases/img/upcoming_release_v12_1.png b/doc/api/releases/img/upcoming_release_v12_1.png Binary files differindex 8bd8573ce84..cc3070fd19d 100644 --- a/doc/api/releases/img/upcoming_release_v12_1.png +++ b/doc/api/releases/img/upcoming_release_v12_1.png diff --git a/doc/api/services.md b/doc/api/services.md index 45b49d7eb92..7d025cd3bdf 100644 --- a/doc/api/services.md +++ b/doc/api/services.md @@ -595,44 +595,6 @@ Remove all previously Jira settings from a project. DELETE /projects/:id/services/jira ``` -## Kubernetes - -Kubernetes / OpenShift integration - -CAUTION: **Warning:** -Kubernetes service integration has been deprecated in GitLab 10.3. API service endpoints will continue to work as long as the Kubernetes service is active, however if the service is inactive API endpoints will automatically return a `400 Bad Request`. Read [GitLab 10.3 release post](https://about.gitlab.com/2017/12/22/gitlab-10-3-released/#kubernetes-integration-service) for more information. - -### Create/Edit Kubernetes service - -Set Kubernetes service for a project. - -``` -PUT /projects/:id/services/kubernetes -``` - -Parameters: - -- `namespace` (**required**) - The Kubernetes namespace to use -- `api_url` (**required**) - The URL to the Kubernetes cluster API. For example, `https://kubernetes.example.com` -- `token` (**required**) - The service token to authenticate against the Kubernetes cluster with -- `ca_pem` (optional) - A custom certificate authority bundle to verify the Kubernetes cluster with (PEM format) - -### Delete Kubernetes service - -Delete Kubernetes service for a project. - -``` -DELETE /projects/:id/services/kubernetes -``` - -### Get Kubernetes service settings - -Get Kubernetes service settings for a project. - -``` -GET /projects/:id/services/kubernetes -``` - ## Slack slash commands Ability to receive slash commands from a Slack chat instance. diff --git a/doc/ci/README.md b/doc/ci/README.md index 6923d07bb1d..f3006528d01 100644 --- a/doc/ci/README.md +++ b/doc/ci/README.md @@ -53,6 +53,9 @@ the following documents: - [GitLab CI/CD basic workflow](introduction/index.md#basic-cicd-workflow). - [Step-by-step guide for writing `.gitlab-ci.yml` for the first time](../user/project/pages/getting_started_part_four.md). +If you're coming over from Jenkins, you can also check out our handy [reference](jenkins/index.md) +for converting your pipelines. + You can also get started by using one of the [`.gitlab-ci.yml` templates](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/lib/gitlab/ci/templates) available through the UI. You can use them by creating a new file, diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md index 278a0d6e934..2cbad5f101c 100644 --- a/doc/ci/docker/using_docker_build.md +++ b/doc/ci/docker/using_docker_build.md @@ -98,9 +98,62 @@ The second approach is to use the special docker-in-docker (dind) (`docker`) and run the job script in context of that image in privileged mode. -NOTE: **Note:** `docker-compose` is not part of docker-in-docker (dind). In case you'd like to use `docker-compose` in your CI builds, please follow the [installation instructions for docker-compose](https://docs.docker.com/compose/install/) provided by docker. +NOTE: **Note:** +`docker-compose` is not part of docker-in-docker (dind). To use `docker-compose` in your +CI builds, follow the `docker-compose` +[installation instructions](https://docs.docker.com/compose/install/). -In order to do that, follow the steps: +DANGER: **Danger:** +By enabling `--docker-privileged`, you are effectively disabling all of +the security mechanisms of containers and exposing your host to privilege +escalation which can lead to container breakout. For more information, check +out the official Docker documentation on +[Runtime privilege and Linux capabilities][docker-cap]. + +Docker-in-Docker works well, and is the recommended configuration, but it is +not without its own challenges: + +- When using docker-in-docker, each job is in a clean environment without the past + history. Concurrent jobs work fine because every build gets it's own + instance of Docker engine so they won't conflict with each other. But this + also means jobs can be slower because there's no caching of layers. +- By default, `docker:dind` uses `--storage-driver vfs` which is the slowest + form offered. To use a different driver, see + [Using the overlayfs driver](#using-the-overlayfs-driver). +- Since the `docker:dind` container and the runner container don't share their + root filesystem, the job's working directory can be used as a mount point for + child containers. For example, if you have files you want to share with a + child container, you may create a subdirectory under `/builds/$CI_PROJECT_PATH` + and use it as your mount point (for a more thorough explanation, check [issue + #41227](https://gitlab.com/gitlab-org/gitlab-ce/issues/41227)): + + ```yaml + variables: + MOUNT_POINT: /builds/$CI_PROJECT_PATH/mnt + + script: + - mkdir -p "$MOUNT_POINT" + - docker run -v "$MOUNT_POINT:/mnt" my-docker-image + ``` + +An example project using this approach can be found here: <https://gitlab.com/gitlab-examples/docker>. + +In the examples below, we are using Docker images tags to specify a +specific version, such as `docker:19.03.1`. If tags like `docker:stable` +are used, you have no control over what version is going to be used and this +can lead to unpredictable behavior, especially when new versions are +released. + +#### TLS enabled + +NOTE: **Note** +This requires GitLab Runner 11.11 or higher. + +The Docker daemon supports connection over TLS and it's done by default +for Docker 19.03.1 or higher. This is the **suggested** way to use the +docker-in-docker service and +[GitLab.com Shared Runners](../../user/gitlab_com/index.html#shared-runners) +support this. 1. Install [GitLab Runner](https://docs.gitlab.com/runner/install). @@ -113,22 +166,21 @@ In order to do that, follow the steps: --registration-token REGISTRATION_TOKEN \ --executor docker \ --description "My Docker Runner" \ - --docker-image "docker:stable" \ - --docker-privileged + --docker-image "docker:19.03.1" \ + --docker-privileged \ + --docker-volumes "/certs/client" ``` The above command will register a new Runner to use the special - `docker:stable` image which is provided by Docker. **Notice that it's using - the `privileged` mode to start the build and service containers.** If you - want to use [docker-in-docker] mode, you always have to use `privileged = true` - in your Docker containers. + `docker:19.03.1` image, which is provided by Docker. **Notice that it's + using the `privileged` mode to start the build and service + containers.** If you want to use [docker-in-docker] mode, you always + have to use `privileged = true` in your Docker containers. - DANGER: **Danger:** - By enabling `--docker-privileged`, you are effectively disabling all of - the security mechanisms of containers and exposing your host to privilege - escalation which can lead to container breakout. For more information, check - out the official Docker documentation on - [Runtime privilege and Linux capabilities][docker-cap]. + This will also mount `/certs/client` for the service and build + container, which is needed for the docker client to use the + certificates inside of that directory. For more information how + Docker with TLS works check <https://hub.docker.com/_/docker/#tls>. The above command will create a `config.toml` entry similar to this: @@ -139,41 +191,48 @@ In order to do that, follow the steps: executor = "docker" [runners.docker] tls_verify = false - image = "docker:stable" + image = "docker:19.03.1" privileged = true disable_cache = false - volumes = ["/cache"] + volumes = ["/certs/client", "/cache"] [runners.cache] - Insecure = false - ``` + [runners.cache.s3] + [runners.cache.gcs] + ``` 1. You can now use `docker` in the build script (note the inclusion of the - `docker:dind` service): + `docker:19.03.1-dind` service): ```yaml - image: docker:stable + image: docker:19.03.1 variables: - # When using dind service we need to instruct docker, to talk with the - # daemon started inside of the service. The daemon is available with - # a network connection instead of the default /var/run/docker.sock socket. + # When using dind service, we need to instruct docker, to talk with + # the daemon started inside of the service. The daemon is available + # with a network connection instead of the default + # /var/run/docker.sock socket. docker:19.03.1 does this automatically + # by setting the DOCKER_HOST in + # https://github.com/docker-library/docker/blob/d45051476babc297257df490d22cbd806f1b11e4/19.03.1/docker-entrypoint.sh#L23-L29 # # The 'docker' hostname is the alias of the service container as described at - # https://docs.gitlab.com/ee/ci/docker/using_docker_images.html#accessing-the-services + # https://docs.gitlab.com/ee/ci/docker/using_docker_images.html#accessing-the-services. # - # Note that if you're using the Kubernetes executor, the variable should be set to - # tcp://localhost:2375/ because of how the Kubernetes executor connects services - # to the job container - # DOCKER_HOST: tcp://localhost:2375/ + # Note that if you're using the Kubernetes executor, the variable + # should be set to tcp://localhost:2376/ because of how the + # Kubernetes executor connects services to the job container + # DOCKER_HOST: tcp://localhost:2376/ # - # For non-Kubernetes executors, we use tcp://docker:2375/ - DOCKER_HOST: tcp://docker:2375/ # When using dind, it's wise to use the overlayfs driver for # improved performance. DOCKER_DRIVER: overlay2 + # Specify to Docker where to create the certificates, Docker will + # create them automatically on boot, and will create + # `/certs/client` that will be shared between the service and job + # container, thanks to volume mount from config.toml + DOCKER_TLS_CERTDIR: "/certs" services: - - docker:dind + - docker:19.03.1-dind before_script: - docker info @@ -185,33 +244,70 @@ In order to do that, follow the steps: - docker run my-docker-image /script/to/run/tests ``` -Docker-in-Docker works well, and is the recommended configuration, but it is -not without its own challenges: +#### TLS disabled -- When using docker-in-docker, each job is in a clean environment without the past - history. Concurrent jobs work fine because every build gets it's own - instance of Docker engine so they won't conflict with each other. But this - also means jobs can be slower because there's no caching of layers. -- By default, `docker:dind` uses `--storage-driver vfs` which is the slowest - form offered. To use a different driver, see - [Using the overlayfs driver](#using-the-overlayfs-driver). -- Since the `docker:dind` container and the runner container don't share their - root filesystem, the job's working directory can be used as a mount point for - children containers. For example, if you have files you want to share with a - child container, you may create a subdirectory under `/builds/$CI_PROJECT_PATH` - and use it as your mount point (for a more thorough explanation, check [issue - #41227](https://gitlab.com/gitlab-org/gitlab-ce/issues/41227)): +Sometimes there are legitimate reasons why you might want to disable TLS. +For example, you have no control over the GitLab Runner configuration +that you are using. - ```yaml - variables: - MOUNT_POINT: /builds/$CI_PROJECT_PATH/mnt +Assuming that the Runner `config.toml` is similar to: - script: - - mkdir -p "$MOUNT_POINT" - - docker run -v "$MOUNT_POINT:/mnt" my-docker-image - ``` +```toml +[[runners]] + url = "https://gitlab.com/" + token = TOKEN + executor = "docker" + [runners.docker] + tls_verify = false + image = "docker:19.03.1" + privileged = true + disable_cache = false + volumes = ["/cache"] + [runners.cache] + [runners.cache.s3] + [runners.cache.gcs] +``` -An example project using this approach can be found here: <https://gitlab.com/gitlab-examples/docker>. +You can now use `docker` in the build script (note the inclusion of the +`docker:19.03.1-dind` service): + +```yaml +image: docker:19.03.1 + +variables: + # When using dind service we need to instruct docker, to talk with the + # daemon started inside of the service. The daemon is available with + # a network connection instead of the default /var/run/docker.sock socket. + # + # The 'docker' hostname is the alias of the service container as described at + # https://docs.gitlab.com/ee/ci/docker/using_docker_images.html#accessing-the-services + # + # Note that if you're using the Kubernetes executor, the variable should be set to + # tcp://localhost:2375/ because of how the Kubernetes executor connects services + # to the job container + # DOCKER_HOST: tcp://localhost:2375/ + # + # For non-Kubernetes executors, we use tcp://docker:2375/ + DOCKER_HOST: tcp://docker:2375/ + # When using dind, it's wise to use the overlayfs driver for + # improved performance. + DOCKER_DRIVER: overlay2 + # + # This will instruct Docker not to start over TLS. + DOCKER_TLS_CERTDIR: "" + +services: + - docker:19.03.1-dind + +before_script: + - docker info + +build: + stage: build + script: + - docker build -t my-docker-image . + - docker run my-docker-image /script/to/run/tests +``` ### Use Docker socket binding diff --git a/doc/ci/environments.md b/doc/ci/environments.md index f86ca8f74f2..61559e69182 100644 --- a/doc/ci/environments.md +++ b/doc/ci/environments.md @@ -692,7 +692,7 @@ with `review/` would have that particular variable. Some GitLab features can behave differently for each environment. For example, you can -[create a secret variable to be injected only into a production environment](variables/README.md#limiting-environment-scopes-of-environment-variables-premium). **(PREMIUM)** +[create a secret variable to be injected only into a production environment](variables/README.md#limiting-environment-scopes-of-environment-variables). In most cases, these features use the _environment specs_ mechanism, which offers an efficient way to implement scoping within each environment group. diff --git a/doc/ci/examples/end_to_end_testing_webdriverio/img/deployed_dependency_update.png b/doc/ci/examples/end_to_end_testing_webdriverio/img/deployed_dependency_update.png Binary files differindex c45d70d7f7a..9fe1739f37e 100644 --- a/doc/ci/examples/end_to_end_testing_webdriverio/img/deployed_dependency_update.png +++ b/doc/ci/examples/end_to_end_testing_webdriverio/img/deployed_dependency_update.png diff --git a/doc/ci/img/collapsible_log.png b/doc/ci/img/collapsible_log.png Binary files differindex 2785033b349..d2a570e246e 100644 --- a/doc/ci/img/collapsible_log.png +++ b/doc/ci/img/collapsible_log.png diff --git a/doc/ci/img/deployments_view.png b/doc/ci/img/deployments_view.png Binary files differindex 12090434bef..9e2b7e89577 100644 --- a/doc/ci/img/deployments_view.png +++ b/doc/ci/img/deployments_view.png diff --git a/doc/ci/img/environments_available.png b/doc/ci/img/environments_available.png Binary files differindex 48fc6effc2d..6c64e9398f7 100644 --- a/doc/ci/img/environments_available.png +++ b/doc/ci/img/environments_available.png diff --git a/doc/ci/img/environments_mr_review_app.png b/doc/ci/img/environments_mr_review_app.png Binary files differindex 6a7b7ce5679..86c20d8d3b6 100644 --- a/doc/ci/img/environments_mr_review_app.png +++ b/doc/ci/img/environments_mr_review_app.png diff --git a/doc/ci/img/manual_job_variables.png b/doc/ci/img/manual_job_variables.png Binary files differindex c7d62477cdd..a5ed351fdcd 100644 --- a/doc/ci/img/manual_job_variables.png +++ b/doc/ci/img/manual_job_variables.png diff --git a/doc/ci/introduction/img/gitlab_workflow_example_11_9.png b/doc/ci/introduction/img/gitlab_workflow_example_11_9.png Binary files differindex 204e9c462e5..f3fb9444b55 100644 --- a/doc/ci/introduction/img/gitlab_workflow_example_11_9.png +++ b/doc/ci/introduction/img/gitlab_workflow_example_11_9.png diff --git a/doc/ci/introduction/img/gitlab_workflow_example_extended_11_11.png b/doc/ci/introduction/img/gitlab_workflow_example_extended_11_11.png Binary files differindex 5089a1088c5..a0874f66eaa 100644 --- a/doc/ci/introduction/img/gitlab_workflow_example_extended_11_11.png +++ b/doc/ci/introduction/img/gitlab_workflow_example_extended_11_11.png diff --git a/doc/ci/jenkins/index.md b/doc/ci/jenkins/index.md new file mode 100644 index 00000000000..093b3568a78 --- /dev/null +++ b/doc/ci/jenkins/index.md @@ -0,0 +1,204 @@ +--- +comments: false +type: index, howto +--- + +# Migrating from Jenkins + +A lot of GitLab users have successfully migrated to GitLab CI/CD from Jenkins. To make this +easier if you're just getting started, we've collected several resources here that you might find useful +before diving in. + +First of all, our [Quick Start Guide](../quick_start/README.md) contains a good overview of how GitLab CI/CD works. +You may also be interested in [Auto DevOps](../../topics/autodevops/index.md) which can potentially be used to build, test, +and deploy your applications with little to no configuration needed at all. + +Otherwise, read on for important information that will help you get the ball rolling. Welcome +to GitLab! + +## Important differences + +There are some high level differences between the products worth mentioning: + +- With GitLab you don't need a root `pipeline` keyword to wrap everything. +- All jobs within a single stage always run in parallel, and all stages run in sequence. We are planning + to allow certain jobs to break this sequencing as needed with our [directed acyclic graph](https://gitlab.com/gitlab-org/gitlab-ce/issues/47063) + feature. +- The `.gitlab-ci.yml` file is checked in to the root of your repository, much like a Jenkinsfile, but + is in the YAML format (see [complete reference](../yaml/README.md)) instead of a Groovy DSL. It's most + analagous to the declarative Jenkinsfile format. +- GitLab comes with a [container registry](../../user/project/container_registry.md), and we recommend using + container images to set up your build environment. + +## Artifact publishing + +Artifacts may work a bit differently than you've used them with Jenkins. In GitLab, any job can define +a set of artifacts to be saved by using the `artifacts:` keyword. This can be configured to point to a file +or set of files that can then be persisted from job to job. Read more on our detailed [artifacts documentation](../../user/project/pipelines/job_artifacts.html) + +```yaml +pdf: + script: xelatex mycv.tex + artifacts: + paths: + - ./mycv.pdf + - ./output/ + expire_in: 1 week +``` + +Additionally, we have package management features like a built-in container, NPM, and Maven registry that you +can leverage. You can see the complete list of packaging features (which includes links to documentation) +in the [Packaging section of our documentation](../../README.md#package). + +## Integrated features + +Where you may have used plugins to get things like code quality, unit tests, security scanning, and so on working in Jenkins, +GitLab takes advantage of our connected ecosystem to automatically pull these kinds of results into +your Merge Requests, pipeline details pages, and other locations. You may find that you actually don't +need to configure anything to have these appear. + +If they aren't working as expected, or if you'd like to see what's available, our [CI feature index](../README.md#feature-set) has the full list +of bundled features and links to the documentation for each. + +## Converting Declarative Jenkinsfiles + +Declarative Jenkinsfiles contain "Sections" and "Directives" which are used to control the behavior of your +pipelines. There are equivalents for all of these in GitLab, which we've documented below. + +This section is based on the [Jenkinsfile syntax documentation](https://jenkins.io/doc/book/pipeline/syntax/) +and is meant to be a mapping of concepts there to concepts in GitLab. + +### Sections + +#### `agent` + +The agent section is used to define how a pipeline will be executed. For GitLab, we use the [GitLab Runner](../runners/README.md) +to provide this capability. You can configure your own runners in Kubernetes or on any host, or take advantage +of our shared runner fleet (note that the shared runner fleet is only available for GitLab.com users.) The link above will bring you to the documenation which will describe how to get +up and running quickly. We also support using [tags](../runners/README.md#using-tags) to direct different jobs +to different Runners (execution agents). + +The `agent` section also allows you to define which Docker images should be used for execution, for which we use +the [`image`](../yaml/README.md#image) keyword. The `image` can be set on a single job or at the top level, in which +case it will apply to all jobs in the pipeline. + +```yaml +my_job: + image: alpine + ... +``` + +#### `post` + +The `post` section defines the actions that should be performed at the end of the pipeline. GitLab also supports +this through the use of stages. You can define your stages as follows, and any jobs assigned to the `before_pipeline` +or `after_pipeline` stages will run as expected. You can call these stages anything you like. + +```yaml +stages: + - before_pipeline + - build + - test + - deploy + - after_pipeline +``` + +Setting a step to be performed before and after any job can be done via the [`before_script` and `after_script` keywords](../yaml/README.md#before_script-and-after_script). + +```yaml +default: + before_script: + - echo "I run before any jobs starts in the entire pipeline, and can be responsible for setting up the environment." +``` + +#### `stages` + +GitLab CI also lets you define stages, but is a little bit more free-form to configure. The GitLab [`stages` keyword](../yaml/README.md#stages) +is a top level setting that enumerates the list of stages, but you are not required to nest individual jobs underneath +the `stages` section. Any job defined in the `.gitlab-ci.yml` can be made a part of any stage through use of the +[`stage:` keyword](../yaml/README.md#stage). + +Note that, unless otherwise specified, every pipeline is instantiated with a `build`, `test`, and `deploy` stage +which are run in that order. Jobs that have no `stage` defined are placed by default in the `test` stage. +Of course, each job that refers to a stage must refer to a stage that exists in the pipeline configuration. + +```yaml +stages: + - build + - test + - deploy + +my_job: + stage: build + ... +``` + +#### `steps` + +The `steps` section is equivalent to the [`script` section](../yaml/README.md#script) of an individual job. This is +a simple YAML array with each line representing an individual command to be run. + +```yaml +my_job: + script: + - echo "hello! the current time is:" + - time + ... +``` + +### Directives + +#### `environment` + +In GitLab, we use the [`variables` keyword](../yaml/README.md#variables) to define different variables at runtime. +These can also be set up through the GitLab UI, under CI/CD settings. See also our [general documentation on variables](../variables/README.md), +including the section on [protected variables](../variables/README.md#protected-environment-variables) which can be used +to limit access to certain variables to certain environments or runners. + +```yaml +variables: + POSTGRES_USER: user + POSTGRES_PASSWORD: testing_password +``` + +#### `options` + +Here, options for different things exist associated with the object in question itself. For example, options related +to jobs are defined in relation to the job itself. If you're looking for a certain option, you should be able to find +where it's located by searching our [complete configuration reference](../yaml/README.md) page. + +#### `parameters` + +GitLab does not require you to define which variables you want to be available when starting a manual job. A user +can provide any variables they like. + +#### `triggers` / `cron` + +Because GitLab is integrated tightly with git, SCM polling options for triggers are not needed. We support an easy to use +[syntax for scheduling pipelines](../../user/project/pipelines/schedules.md). + +#### `tools` + +GitLab does not support a separate `tools` directive. Our best-practice reccomendation is to use pre-built +container images, which can be cached, and can be built to already contain the tools you need for your pipelines. Pipelines can +be set up to automatically build these images as needed and deploy them to the [container registry](../../user/project/container_registry.md). + +If you're not using container images with Docker/Kubernetes, for example on Mac or FreeBSD, then the `shell` executor does require you to +set up your environment either in advance or as part of the jobs. You could create a `before_script` +action that handles this for you. + +#### `input` + +Similar to the `parameters` keyword, this is not needed because a manual job can always be provided runtime +variable entry. + +#### `when` + +GitLab does support a [`when` keyword](../yaml/README.md#when) which is used to indicate when a job should be +run in case of (or despite) failure, but most of the logic for controlling pipelines can be found in +our very powerful [`only/except` rules system](../yaml/README.md#onlyexcept-basic) (see also our [advanced syntax](../yaml/README.md#onlyexcept-basic)) + +```yaml +my_job: + only: branches +```
\ No newline at end of file diff --git a/doc/ci/merge_request_pipelines/img/merge_request.png b/doc/ci/merge_request_pipelines/img/merge_request.png Binary files differindex d03fdc6a885..bb64e17cc91 100644 --- a/doc/ci/merge_request_pipelines/img/merge_request.png +++ b/doc/ci/merge_request_pipelines/img/merge_request.png diff --git a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/img/merge_request_pipeline.png b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/img/merge_request_pipeline.png Binary files differindex 58d5581f628..6d4b66824e1 100644 --- a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/img/merge_request_pipeline.png +++ b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/img/merge_request_pipeline.png diff --git a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/img/merge_request_pipeline_config.png b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/img/merge_request_pipeline_config.png Binary files differindex 0a84e61d284..3ee9d8ec93b 100644 --- a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/img/merge_request_pipeline_config.png +++ b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/img/merge_request_pipeline_config.png diff --git a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_cancel_v12_0.png b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_cancel_v12_0.png Binary files differindex 1561fdcc7a5..d7720ac1143 100644 --- a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_cancel_v12_0.png +++ b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_cancel_v12_0.png diff --git a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_config_v12_0.png b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_config_v12_0.png Binary files differindex fb0af43556e..9da959ad440 100644 --- a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_config_v12_0.png +++ b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_config_v12_0.png diff --git a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_failure.png b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_failure.png Binary files differindex a8916e5721c..8a795fff432 100644 --- a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_failure.png +++ b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_failure.png diff --git a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_immediate_merge.png b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_immediate_merge.png Binary files differindex 65ff7e3d674..03bc61129ba 100644 --- a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_immediate_merge.png +++ b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_immediate_merge.png diff --git a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_position_v12_0.png b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_position_v12_0.png Binary files differindex 70916bc0e00..ec4b157d428 100644 --- a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_position_v12_0.png +++ b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_position_v12_0.png diff --git a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_start_v12_0.png b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_start_v12_0.png Binary files differindex f20108157d2..a4d0c8cf0e6 100644 --- a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_start_v12_0.png +++ b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_start_v12_0.png diff --git a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_start_when_pipeline_succeeds_v12_0.png b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_start_when_pipeline_succeeds_v12_0.png Binary files differindex 62c2f2f5ff5..45762b8e85e 100644 --- a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_start_when_pipeline_succeeds_v12_0.png +++ b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_start_when_pipeline_succeeds_v12_0.png diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md index 0480b83d183..5626428bfc3 100644 --- a/doc/ci/quick_start/README.md +++ b/doc/ci/quick_start/README.md @@ -13,6 +13,10 @@ NOTE: **Note:** Please keep in mind that only project Maintainers and Admin users have the permissions to access a project's settings. +NOTE: **Note:** +Coming over to GitLab from Jenkins? Check out our [reference](../jenkins/index.md) +for converting your pre-existing pipelines over to our format. + GitLab offers a [continuous integration][ci] service. If you [add a `.gitlab-ci.yml` file][yaml] to the root directory of your repository, and configure your GitLab project to use a [Runner], then each commit or diff --git a/doc/ci/review_apps/img/review_button.png b/doc/ci/review_apps/img/review_button.png Binary files differindex 0b231c50858..4f1cf4d7cfd 100644 --- a/doc/ci/review_apps/img/review_button.png +++ b/doc/ci/review_apps/img/review_button.png diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index c63b1e104ed..d741482b662 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -94,7 +94,7 @@ This means that the value of the variable will be hidden in job logs, though it must match certain requirements to do so: - The value must be in a single line. -- The value must only consist of characters from the Base64 alphabet, defined in [RFC4648](https://tools.ietf.org/html/rfc4648). +- The value must only consist of characters from the Base64 alphabet ([RFC4648](https://tools.ietf.org/html/rfc4648)) with the addition of `@` and `:`. - The value must be at least 8 characters long. - The value must not use variables. @@ -393,7 +393,7 @@ Protected variables can be added by going to your project's Once you set them, they will be available for all subsequent pipelines. -### Limiting environment scopes of environment variables **(PREMIUM)** +### Limiting environment scopes of environment variables You can limit the environment scope of a variable by [defining which environments][envs] it can be available for. diff --git a/doc/ci/variables/img/custom_variables_output.png b/doc/ci/variables/img/custom_variables_output.png Binary files differindex 29f5c63b3d9..797e9ec07b9 100644 --- a/doc/ci/variables/img/custom_variables_output.png +++ b/doc/ci/variables/img/custom_variables_output.png diff --git a/doc/ci/variables/img/new_custom_variables_example.png b/doc/ci/variables/img/new_custom_variables_example.png Binary files differindex efe104efe4c..bb60e6bab21 100644 --- a/doc/ci/variables/img/new_custom_variables_example.png +++ b/doc/ci/variables/img/new_custom_variables_example.png diff --git a/doc/ci/variables/img/override_variable_manual_pipeline.png b/doc/ci/variables/img/override_variable_manual_pipeline.png Binary files differindex 3c8c59720cf..c77c5cb7764 100644 --- a/doc/ci/variables/img/override_variable_manual_pipeline.png +++ b/doc/ci/variables/img/override_variable_manual_pipeline.png diff --git a/doc/ci/variables/img/variable_types_usage_example.png b/doc/ci/variables/img/variable_types_usage_example.png Binary files differindex 0e8bde891fe..c2ae32fd048 100644 --- a/doc/ci/variables/img/variable_types_usage_example.png +++ b/doc/ci/variables/img/variable_types_usage_example.png diff --git a/doc/customization/help_message/help_text.png b/doc/customization/help_message/help_text.png Binary files differindex 99697a106bf..5fcabcdb757 100644 --- a/doc/customization/help_message/help_text.png +++ b/doc/customization/help_message/help_text.png diff --git a/doc/customization/system_header_and_footer_messages/appearance.png b/doc/customization/system_header_and_footer_messages/appearance.png Binary files differindex fd315bb6c07..d5a66bcb9f1 100644 --- a/doc/customization/system_header_and_footer_messages/appearance.png +++ b/doc/customization/system_header_and_footer_messages/appearance.png diff --git a/doc/development/architecture.md b/doc/development/architecture.md index 091edb6ac40..87405bc2fec 100644 --- a/doc/development/architecture.md +++ b/doc/development/architecture.md @@ -568,7 +568,7 @@ Usage: /etc/init.d/postgresql {start|stop|restart|reload|force-reload|status} [v gitlabhq (includes Unicorn and Sidekiq logs) -- `/home/git/gitlab/log/` contains `application.log`, `production.log`, `sidekiq.log`, `unicorn.stdout.log`, `githost.log` and `unicorn.stderr.log` normally. +- `/home/git/gitlab/log/` contains `application.log`, `production.log`, `sidekiq.log`, `unicorn.stdout.log`, `git_json.log` and `unicorn.stderr.log` normally. gitlab-shell diff --git a/doc/development/database_review.md b/doc/development/database_review.md index 3d10a0c84e5..3f1b359cb0b 100644 --- a/doc/development/database_review.md +++ b/doc/development/database_review.md @@ -90,12 +90,20 @@ and details for a database reviewer: - Ensure that migrations execute in a transaction or only contain concurrent index/foreign key helpers (with transactions disabled) - Check consistency with `db/schema.rb` and that migrations are [reversible](migration_style_guide.md#reversibility) + - Check queries timing (If any): Queries executed in a migration + need to fit comfortable within `15s` - preferably much less than that - on GitLab.com. +- Check [background migrations](background_migrations.md): - For data migrations, establish a time estimate for execution -- Check post deploy migration - - Make sure we can expect post deploy migrations to finish within 1 hour max. -- Check background migrations + - They should only be used when migrating data in larger tables. + - If a single `update` is below than `1s` the query can be placed + directly in a regular migration (inside `db/migrate`). - Review queries (for example, make sure batch sizes are fine) - Establish a time estimate for execution + - Because execution time can be longer than for a regular migration, + it's suggested to treat background migrations as post migrations: + place them in `db/post_migrate` instead of `db/migrate`. Keep in mind + that post migrations are executed post-deployment in production. +- Check [timing guidelines for migrations](#timing-guidelines-for-migrations) - Query performance - Check for any obviously complex queries and queries the author specifically points out for review (if any) @@ -110,3 +118,17 @@ and details for a database reviewer: (eg indexes, columns), you can use a [one-off instance from the restore pipeline](https://ops.gitlab.net/gitlab-com/gl-infra/gitlab-restore/postgres-gprd) in order to establish a proper testing environment. + +### Timing guidelines for migrations + +In general, migrations for a single deploy shouldn't take longer than +1 hour for GitLab.com. The following guidelines are not hard rules, they were +estimated to keep migration timing to a minimum. + +NOTE: **Note:** Keep in mind that all runtimes should be measured against GitLab.com. + +| Migration Type | Execution Time Recommended | Notes | +|----|----|---| +| Regular migrations on `db/migrate` | `3 minutes` | A valid exception are index creation as this can take a long time. | +| Post migrations on `db/post_migrate` | `10 minutes` | | +| Background migrations | --- | Since these are suitable for larger tables, it's not possible to set a precise timing guideline, however, any query must stay well below `10s` of execution time. | diff --git a/doc/development/i18n/proofreader.md b/doc/development/i18n/proofreader.md index 910d7296057..492e3d48164 100644 --- a/doc/development/i18n/proofreader.md +++ b/doc/development/i18n/proofreader.md @@ -80,6 +80,7 @@ are very appreciative of the work done by translators and proofreaders! - Russian - Nikita Grylov - [GitLab](https://gitlab.com/nixel2007), [Crowdin](https://crowdin.com/profile/nixel2007) - Alexy Lustin - [GitLab](https://gitlab.com/allustin), [Crowdin](https://crowdin.com/profile/lustin) + - Mark Minakou - [GitLab](https://gitlab.com/sandzhaj), [Crowdin](https://crowdin.com/profile/sandzhaj) - NickVolynkin - [Crowdin](https://crowdin.com/profile/NickVolynkin) - Serbian (Cyrillic) - Proofreaders needed. diff --git a/doc/development/img/architecture_simplified.png b/doc/development/img/architecture_simplified.png Binary files differindex 1698c167c5e..1ad57b65468 100644 --- a/doc/development/img/architecture_simplified.png +++ b/doc/development/img/architecture_simplified.png diff --git a/doc/development/img/distributed_tracing_jaeger_ui.png b/doc/development/img/distributed_tracing_jaeger_ui.png Binary files differindex 57517dacced..dcd18b1ec9f 100644 --- a/doc/development/img/distributed_tracing_jaeger_ui.png +++ b/doc/development/img/distributed_tracing_jaeger_ui.png diff --git a/doc/development/img/distributed_tracing_performance_bar.png b/doc/development/img/distributed_tracing_performance_bar.png Binary files differindex c9998cedd2d..8c819045104 100644 --- a/doc/development/img/distributed_tracing_performance_bar.png +++ b/doc/development/img/distributed_tracing_performance_bar.png diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md index 0c7601b415e..3181b3a88cc 100644 --- a/doc/development/migration_style_guide.md +++ b/doc/development/migration_style_guide.md @@ -10,9 +10,7 @@ migrations are written carefully, can be applied online and adhere to the style guide below. Migrations are **not** allowed to require GitLab installations to be taken -offline unless _absolutely necessary_. Downtime assumptions should be based on -the behaviour of a migration when performed using PostgreSQL, as various -operations in MySQL may require downtime without there being alternatives. +offline unless _absolutely necessary_. When downtime is necessary the migration has to be approved by: @@ -343,10 +341,7 @@ class AddOptionsToBuildMetadata < ActiveRecord::Migration[5.0] end ``` -On MySQL the `JSON` and `JSONB` is translated to `TEXT 1MB`, as `JSONB` is PostgreSQL only feature. - -For above reason you have to use a serializer to provide a translation layer -in order to support PostgreSQL and MySQL seamlessly: +You have to use a serializer to provide a translation layer: ```ruby class BuildMetadata @@ -356,7 +351,7 @@ end ## Testing -Make sure that your migration works with MySQL and PostgreSQL with data. An +Make sure that your migration works for databases with data. An empty database does not guarantee that your migration is correct. Make sure your migration can be reversed. diff --git a/doc/install/aws/index.md b/doc/install/aws/index.md index fed3b1ca595..358ba971049 100644 --- a/doc/install/aws/index.md +++ b/doc/install/aws/index.md @@ -610,7 +610,7 @@ To back up GitLab: 1. Take a backup: ```sh - sudo gitlab-rake gitlab:backup:create + sudo gitlab-backup create ``` ### Restoring GitLab from a backup @@ -628,7 +628,7 @@ released, you can update your GitLab instance: 1. Take a backup: ```sh - sudo gitlab-rake gitlab:backup:create + sudo gitlab-backup create ``` 1. Update the repositories and install GitLab: diff --git a/doc/install/installation.md b/doc/install/installation.md index a12edef4e41..72a3514e2d5 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -134,7 +134,7 @@ Make sure you have the right version of Git installed: # Install Git sudo apt-get install -y git-core -# Make sure Git is version 2.21.0 or higher +# Make sure Git is version 2.22.0 or higher git --version ``` @@ -171,9 +171,9 @@ sudo make install # Download and compile from source cd /tmp -curl --remote-name --location --progress https://www.kernel.org/pub/software/scm/git/git-2.21.0.tar.gz -echo '85eca51c7404da75e353eba587f87fea9481ba41e162206a6f70ad8118147bee git-2.21.0.tar.gz' | shasum -a256 -c - && tar -xzf git-2.21.0.tar.gz -cd git-2.21.0/ +curl --remote-name --location --progress https://www.kernel.org/pub/software/scm/git/git-2.22.0.tar.gz +echo 'a4b7e4365bee43caa12a38d646d2c93743d755d1cea5eab448ffb40906c9da0b' git-2.22.0.tar.gz' | shasum -a256 -c - && tar -xzf git-2.22.0.tar.gz +cd git-2.22.0/ ./configure --with-libpcre make prefix=/usr/local all diff --git a/doc/install/requirements.md b/doc/install/requirements.md index cfabc09646d..086f3b1b843 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -92,7 +92,8 @@ We recommend having at least [2GB of swap on your server](https://askubuntu.com/ enough available RAM. Having swap will help reduce the chance of errors occurring if your available memory changes. We also recommend [configuring the kernel's swappiness setting](https://askubuntu.com/a/103916) to a low value like `10` to make the most of your RAM while still having the swap -available when needed. +available when needed. +Our [Memory Team](https://about.gitlab.com/handbook/engineering/development/enablement/memory/) is actively working to reduce this requirement. NOTE: **Note:** The 25 workers of Sidekiq will show up as separate processes in your process overview (such as `top` or `htop`) but they share the same RAM allocation since Sidekiq is a multithreaded application. Please see the section below about Unicorn workers for information about how many you need of those. diff --git a/doc/integration/img/limit_namespace_filter.png b/doc/integration/img/limit_namespace_filter.png Binary files differindex 88f5caa41db..437aecad467 100644 --- a/doc/integration/img/limit_namespace_filter.png +++ b/doc/integration/img/limit_namespace_filter.png diff --git a/doc/integration/img/limit_namespaces_projects_options.png b/doc/integration/img/limit_namespaces_projects_options.png Binary files differindex 488341f7e92..fa666c7491e 100644 --- a/doc/integration/img/limit_namespaces_projects_options.png +++ b/doc/integration/img/limit_namespaces_projects_options.png diff --git a/doc/integration/img/salesforce_app_details.png b/doc/integration/img/salesforce_app_details.png Binary files differindex 00e66f07282..c7a4084102e 100644 --- a/doc/integration/img/salesforce_app_details.png +++ b/doc/integration/img/salesforce_app_details.png diff --git a/doc/integration/img/salesforce_app_secret_details.png b/doc/integration/img/salesforce_app_secret_details.png Binary files differindex fad2a4a1f97..8734a7a5cbb 100644 --- a/doc/integration/img/salesforce_app_secret_details.png +++ b/doc/integration/img/salesforce_app_secret_details.png diff --git a/doc/integration/img/salesforce_oauth_app_details.png b/doc/integration/img/salesforce_oauth_app_details.png Binary files differindex a5fb680cca6..e29c55df656 100644 --- a/doc/integration/img/salesforce_oauth_app_details.png +++ b/doc/integration/img/salesforce_oauth_app_details.png diff --git a/doc/legal/corporate_contributor_license_agreement.md b/doc/legal/corporate_contributor_license_agreement.md index 7f08188bd65..8c1f4a126b1 100644 --- a/doc/legal/corporate_contributor_license_agreement.md +++ b/doc/legal/corporate_contributor_license_agreement.md @@ -2,28 +2,28 @@ You accept and agree to the following terms and conditions for Your present and future Contributions submitted to GitLab B.V.. Except for the license granted herein to GitLab B.V. and recipients of software distributed by GitLab B.V., You reserve all right, title, and interest in and to Your Contributions. -1. Definitions. +- **Definitions:** - "You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner that is making this Agreement with GitLab B.V.. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + "You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner that is making this Agreement with GitLab B.V.. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - "Contribution" shall mean the code, documentation or other original works of authorship, including any modifications or additions to an existing work, that is submitted by You to GitLab B.V. for inclusion in, or documentation of, any of the products owned or managed by GitLab B.V. (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to GitLab B.V. or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, GitLab B.V. for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution." + "Contribution" shall mean the code, documentation or other original works of authorship, including any modifications or additions to an existing work, that is submitted by You to GitLab B.V. for inclusion in, or documentation of, any of the products owned or managed by GitLab B.V. (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to GitLab B.V. or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, GitLab B.V. for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution." -2. Grant of Copyright License. +- **Grant of Copyright License:** -Subject to the terms and conditions of this Agreement, You hereby grant to GitLab B.V. and to recipients of software distributed by GitLab B.V. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works. + Subject to the terms and conditions of this Agreement, You hereby grant to GitLab B.V. and to recipients of software distributed by GitLab B.V. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works. -3. Grant of Patent License. +- **Grant of Patent License:** -Subject to the terms and conditions of this Agreement, You hereby grant to GitLab B.V. and to recipients of software distributed by GitLab B.V. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed. + Subject to the terms and conditions of this Agreement, You hereby grant to GitLab B.V. and to recipients of software distributed by GitLab B.V. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed. -4. You represent that You are legally entitled to grant the above license. You represent further that each of Your employees is authorized to submit Contributions on Your behalf, but excluding employees that are designated in writing by You as "Not authorized to submit Contributions on behalf of [name of Your corporation here]." Such designations of exclusion for unauthorized employees are to be submitted via email to legal@gitlab.com. + You represent that You are legally entitled to grant the above license. You represent further that each of Your employees is authorized to submit Contributions on Your behalf, but excluding employees that are designated in writing by You as "Not authorized to submit Contributions on behalf of (name of Your corporation here)." Such designations of exclusion for unauthorized employees are to be submitted via email to legal@gitlab.com. It is Your responsibility to notify GitLab B.V. when any change is required to the list of designated employees excluded from submitting Contributions on Your behalf. Such notification should also be sent via email to legal@gitlab.com. -5. You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of others). +- **Contributions:** -6. You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. + You represent that each of Your Contributions is Your original creation. + + Should You wish to submit work that is not Your original creation, You may submit it to GitLab B.V. separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: (named here)". -7. Should You wish to submit work that is not Your original creation, You may submit it to GitLab B.V. separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [named here]". - -8. It is Your responsibility to notify GitLab.com when any change is required to the list of designated employees excluded from submitting Contributions on Your behalf per Section 4. Such notification should be sent via email to legal@gitlab.com. + You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. This text is licensed under the [Creative Commons Attribution 3.0 License](https://creativecommons.org/licenses/by/3.0/) and the original source is the Google Open Source Programs Office. diff --git a/doc/legal/individual_contributor_license_agreement.md b/doc/legal/individual_contributor_license_agreement.md index 59803aea080..7a071414629 100644 --- a/doc/legal/individual_contributor_license_agreement.md +++ b/doc/legal/individual_contributor_license_agreement.md @@ -2,24 +2,30 @@ You accept and agree to the following terms and conditions for Your present and future Contributions submitted to GitLab B.V.. Except for the license granted herein to GitLab B.V. and recipients of software distributed by GitLab B.V., You reserve all right, title, and interest in and to Your Contributions. -1. Definitions. +- **Definitions:** - "You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner that is making this Agreement with GitLab B.V.. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + "You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner that is making this Agreement with GitLab B.V.. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - "Contribution" shall mean any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to GitLab B.V. for inclusion in, or documentation of, any of the products owned or managed by GitLab B.V. (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to GitLab B.V. or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, GitLab B.V. for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution." + "Contribution" shall mean any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to GitLab B.V. for inclusion in, or documentation of, any of the products owned or managed by GitLab B.V. (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to GitLab B.V. or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, GitLab B.V. for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution." -2. Grant of Copyright License. Subject to the terms and conditions of this Agreement, You hereby grant to GitLab B.V. and to recipients of software distributed by GitLab B.V. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works. +- **Grant of Copyright License:** -3. Grant of Patent License. Subject to the terms and conditions of this Agreement, You hereby grant to GitLab B.V. and to recipients of software distributed by GitLab B.V. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed. + Subject to the terms and conditions of this Agreement, You hereby grant to GitLab B.V. and to recipients of software distributed by GitLab B.V. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works. -4. You represent that you are legally entitled to grant the above license. If your employer(s) has rights to intellectual property that you create that includes your Contributions, you represent that you have received permission to make Contributions on behalf of that employer, that your employer has waived such rights for your Contributions to GitLab B.V., or that your employer has executed a separate Corporate CLA with GitLab B.V.. +- **Grant of Patent License:** -5. You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of others). You represent that Your Contribution submissions include complete details of any third-party license or other restriction (including, but not limited to, related patents and trademarks) of which you are personally aware and which are associated with any part of Your Contributions. + Subject to the terms and conditions of this Agreement, You hereby grant to GitLab B.V. and to recipients of software distributed by GitLab B.V. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed. -6. You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON- INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. + You represent that you are legally entitled to grant the above license. If your employer(s) has rights to intellectual property that you create that includes your Contributions, you represent that you have received permission to make Contributions on behalf of that employer, that your employer has waived such rights for your Contributions to GitLab B.V., or that your employer has executed a separate Corporate CLA with GitLab B.V.. -7. Should You wish to submit work that is not Your original creation, You may submit it to GitLab B.V. separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [insert_name_here]". +- **Contributions:** -8. You agree to notify GitLab B.V. of any facts or circumstances of which you become aware that would make these representations inaccurate in any respect. + You represent that each of Your Contributions is Your original creation. You represent that Your Contribution submissions include complete details of any third-party license or other restriction (including, but not limited to, related patents and trademarks) of which you are personally aware and which are associated with any part of Your Contributions. + + Should You wish to submit work that is not Your original creation, You may submit it to GitLab B.V. separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: (insert_name_here)". + + You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON- INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. + +You agree to notify GitLab B.V. of any facts or circumstances of which you become aware that would make these representations inaccurate in any respect. This text is licensed under the [Creative Commons Attribution 3.0 License](https://creativecommons.org/licenses/by/3.0/) and the original source is the Google Open Source Programs Office. diff --git a/doc/migrate_ci_to_ce/README.md b/doc/migrate_ci_to_ce/README.md index 51ff56b3541..9947c19a667 100644 --- a/doc/migrate_ci_to_ce/README.md +++ b/doc/migrate_ci_to_ce/README.md @@ -67,7 +67,7 @@ Also check on your GitLab server. ``` # On your GitLab server: # Omnibus -sudo gitlab-rake gitlab:backup:create SKIP=repositories,uploads +sudo gitlab-backup create SKIP=repositories,uploads # Source cd /home/git/gitlab diff --git a/doc/public_access/public_access.md b/doc/public_access/public_access.md index ea4702acc41..0142e5075cc 100644 --- a/doc/public_access/public_access.md +++ b/doc/public_access/public_access.md @@ -12,7 +12,7 @@ public access directory (`/public` under your GitLab instance), like at [https:/ ### Public projects -Public projects can be cloned **without any** authentication. +Public projects can be cloned **without any** authentication over https. They will be listed in the public access directory (`/public`) for all users. diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index f8da09e5fe1..51f04df27c7 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -77,7 +77,7 @@ You are highly advised to [read about storing configuration files](#storing-conf Use this command if you've installed GitLab with the Omnibus package: ```sh -sudo gitlab-rake gitlab:backup:create +sudo gitlab-backup create ``` Use this if you've installed GitLab from source: @@ -89,7 +89,7 @@ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production If you are running GitLab within a Docker container, you can run the backup from the host: ```sh -docker exec -t <container name> gitlab-rake gitlab:backup:create +docker exec -t <container name> gitlab-backup create ``` If you are using the [GitLab helm chart](https://gitlab.com/charts/gitlab) on a @@ -199,7 +199,7 @@ To use the `copy` strategy instead of the default streaming strategy, specify `STRATEGY=copy` in the Rake task command. For example: ```sh -sudo gitlab-rake gitlab:backup:create STRATEGY=copy +sudo gitlab-backup create STRATEGY=copy ``` ### Backup filename @@ -207,7 +207,7 @@ sudo gitlab-rake gitlab:backup:create STRATEGY=copy By default a backup file is created according to the specification in [the Backup timestamp](#backup-timestamp) section above. You can however override the `[TIMESTAMP]` part of the filename by setting the `BACKUP` environment variable. For example: ```sh -sudo gitlab-rake gitlab:backup:create BACKUP=dump +sudo gitlab-backup create BACKUP=dump ``` The resulting file will then be `dump_gitlab_backup.tar`. This is useful for systems that make use of rsync and incremental backups, and will result in considerably faster transfer speeds. @@ -219,7 +219,7 @@ To make sure the generated archive is intelligently transferable by rsync, the ` Note that the `--rsyncable` option in `gzip` is not guaranteed to be available on all distributions. To verify that it is available in your distribution you can run `gzip --help` or consult the man pages. ```sh -sudo gitlab-rake gitlab:backup:create BACKUP=dump GZIP_RSYNCABLE=yes +sudo gitlab-backup create BACKUP=dump GZIP_RSYNCABLE=yes ``` ### Excluding specific directories from the backup @@ -244,7 +244,7 @@ will be skipped during a backup. For Omnibus GitLab packages: ```sh -sudo gitlab-rake gitlab:backup:create SKIP=db,uploads +sudo gitlab-backup create SKIP=db,uploads ``` For installations from source: @@ -448,8 +448,8 @@ Note: This option only works for remote storage. If you want to group your backu you can pass a `DIRECTORY` environment variable: ``` -sudo gitlab-rake gitlab:backup:create DIRECTORY=daily -sudo gitlab-rake gitlab:backup:create DIRECTORY=weekly +sudo gitlab-backup create DIRECTORY=daily +sudo gitlab-backup create DIRECTORY=weekly ``` ### Uploading to locally mounted shares @@ -566,7 +566,7 @@ crontab -e There, add the following line to schedule the backup for everyday at 2 AM: ``` -0 2 * * * /opt/gitlab/bin/gitlab-rake gitlab:backup:create CRON=1 +0 2 * * * /opt/gitlab/bin/gitlab-backup create CRON=1 ``` You may also want to set a limited lifetime for backups to prevent regular @@ -726,7 +726,7 @@ restore: ```shell # This command will overwrite the contents of your GitLab database! -sudo gitlab-rake gitlab:backup:restore BACKUP=1493107454_2018_04_25_10.6.4-ce +sudo gitlab-backup restore BACKUP=1493107454_2018_04_25_10.6.4-ce ``` Next, restore `/etc/gitlab/gitlab-secrets.json` if necessary as mentioned above. @@ -760,7 +760,7 @@ backup location (default location is `/var/opt/gitlab/backups`). For docker installations, the restore task can be run from host: ```sh -docker exec -it <name of container> gitlab-rake gitlab:backup:restore +docker exec -it <name of container> gitlab-backup restore ``` The GitLab helm chart uses a different process, documented in @@ -966,7 +966,7 @@ want to run the chown against your custom location instead of While running the backup, you may receive a gzip error: ```sh -sudo /opt/gitlab/bin/gitlab-rake gitlab:backup:create +sudo /opt/gitlab/bin/gitlab-backup create Dumping ... ... gzip: stdout: Input/output error diff --git a/doc/raketasks/web_hooks.md b/doc/raketasks/web_hooks.md index a498e9793c1..cc1166a04cc 100644 --- a/doc/raketasks/web_hooks.md +++ b/doc/raketasks/web_hooks.md @@ -53,3 +53,8 @@ sudo gitlab-rake gitlab:web_hook:list NAMESPACE=acme # source installations bundle exec rake gitlab:web_hook:list NAMESPACE=acme RAILS_ENV=production ``` + +## Local requests in webhooks + +[Requests to local network by webhooks](../security/webhooks.md) can be allowed +or blocked by an administrator. diff --git a/doc/security/img/outbound_requests_section_v12_2.png b/doc/security/img/outbound_requests_section_v12_2.png Binary files differindex 4fd3c7d9fce..3dc99868a35 100644 --- a/doc/security/img/outbound_requests_section_v12_2.png +++ b/doc/security/img/outbound_requests_section_v12_2.png diff --git a/doc/security/img/whitelist.png b/doc/security/img/whitelist.png Binary files differnew file mode 100644 index 00000000000..897000e804d --- /dev/null +++ b/doc/security/img/whitelist.png diff --git a/doc/security/rack_attack.md b/doc/security/rack_attack.md index c772f783f71..b99bfb16829 100644 --- a/doc/security/rack_attack.md +++ b/doc/security/rack_attack.md @@ -20,9 +20,9 @@ For more information on how to use these options see the [Rack Attack README](ht NOTE: **Note:** See [User and IP rate limits](../user/admin_area/settings/user_and_ip_rate_limits.md) -for simpler throttles that are configured in UI. +for simpler limits that are configured in the UI. -NOTE: **Note:** Starting with 11.2, Rack Attack is disabled by default. If your +NOTE: **Note:** Starting with GitLab 11.2, Rack Attack is disabled by default. If your instance is not exposed to the public internet, it is recommended that you leave Rack Attack disabled. @@ -31,13 +31,13 @@ Rack Attack disabled. If set up as described in the [Settings](#settings) section below, two behaviors will be enabled: -- Protected paths will be throttled -- Failed authentications for Git and container registry requests will trigger a temporary IP ban +- Protected paths will be throttled. +- Failed authentications for Git and container registry requests will trigger a temporary IP ban. ### Protected paths throttle -GitLab responds with HTTP status code 429 to POST requests at protected paths -over 10 requests per minute per IP address. +GitLab responds with HTTP status code `429` to POST requests at protected paths +that exceed 10 requests per minute per IP address. By default, protected paths are: @@ -62,16 +62,16 @@ Retry-After: 60 For example, the following are limited to a maximum 10 requests per minute: -- user sign-in -- user sign-up (if enabled) -- user password reset +- User sign-in +- User sign-up (if enabled) +- User password reset -After trying for 10 times, the client will -have to wait a minute before to be able to try again. +After 10 requests, the client must wait a minute before it can +try again. ### Git and container registry failed authentication ban -GitLab responds with HTTP status code 403 for 1 hour, if 30 failed +GitLab responds with HTTP status code `403` for 1 hour, if 30 failed authentication requests were received in a 3-minute period from a single IP address. This applies only to Git requests and container registry (`/jwt/auth`) requests @@ -145,7 +145,7 @@ If you want more restrictive/relaxed throttle rules, edit For example, more relaxed throttle rules will be if you set `limit: 3` and `period: 1.seconds` (this will allow 3 requests per second). You can also add other paths to the protected list by adding to `paths_to_be_protected` -variable. If you change any of these settings do not forget to restart your +variable. If you change any of these settings you must restart your GitLab instance. ## Remove blocked IPs from Rack Attack via Redis diff --git a/doc/security/webhooks.md b/doc/security/webhooks.md index 7ece9407ac0..e39bc9a9626 100644 --- a/doc/security/webhooks.md +++ b/doc/security/webhooks.md @@ -45,6 +45,36 @@ NOTE: **Note:** set up by administrators. However, you can turn this off by disabling the **Allow requests to the local network from system hooks** option. +## Whitelist for local requests + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/44496) in GitLab 12.2 + +You can allow certain domains and IP addresses to be accessible to both *system hooks* +and *webhooks* even when local requests are not allowed by adding them to the +whitelist. Navigate to **Admin Area > Settings > Network** (`/admin/application_settings/network`) +and expand **Outbound requests**: + +![Outbound local requests whitelist](img/whitelist.png) + +The whilelist entries can be separated by semicolons, commas or whitespaces +(including newlines) and be in different formats like hostnames, IP addresses and/or +IP ranges. IPv6 is supported. Hostnames that contain unicode characters should +use IDNA encoding. + +The whitelist can hold a maximum of 1000 entries. Each entry can be a maximum of +255 characters. + +Example: + +```text +example.com;gitlab.example.com +127.0.0.1,1:0:0:0:0:0:0:1 +127.0.0.0/8 1:0:0:0:0:0:0:0/124 +``` + +NOTE: **Note:** +Wildcards (`*.example.com`) and ports (`127.0.0.1:3000`) are not currently supported. + <!-- ## Troubleshooting Include any troubleshooting steps that you can foresee. If you know beforehand what issues diff --git a/doc/subscriptions/billing_table.png b/doc/subscriptions/billing_table.png Binary files differdeleted file mode 100644 index acd1b6193ec..00000000000 --- a/doc/subscriptions/billing_table.png +++ /dev/null diff --git a/doc/subscriptions/index.md b/doc/subscriptions/index.md index 68e62fff106..fc36b961b3f 100644 --- a/doc/subscriptions/index.md +++ b/doc/subscriptions/index.md @@ -2,126 +2,268 @@ type: index, reference --- -# Subscription setup and management +# Customers -This page will help get you started with your new subscription or manage an existing one, whether you have subscribed to GitLab.com or self-managed GitLab. +This section contains information for: -To subscribe, upgrade, or read more about the types of subscriptions, please see [Subscribe to GitLab](../README.md#subscribe-to-gitlab) on the GitLab Documentation landing page. +- New customers about choosing [which GitLab](#which-gitlab) is right for you. +- Existing customers about [managing subscriptions](#managing-subscriptions). -## Set up GitLab +Also see our [subscription FAQ](https://about.gitlab.com/pricing/licensing-faq/). -Learn how GitLab helps you in the stages of the DevOps lifecycle by learning more [about the GitLab product](https://about.gitlab.com/product/), [GitLab features](https://about.gitlab.com/features/), and [GitLab Documentation](../README.md). +## Which GitLab? -### Self-managed: Install GitLab +There are two ways to use GitLab: -Take a look at [installing GitLab](https://about.gitlab.com/install/) and our [administrator documentation](../administration/index.md). Then, follow the instructions below under [Your subscription](#your-subscription) to apply your license file. +- [GitLab.com](#gitlabcom): GitLab's SaaS offering. You don't need to install + anything to use GitLab.com, you only need to + [sign up](https://gitlab.com/users/sign_in) and start using GitLab straight away. +- [GitLab self-managed](#gitlab-self-managed): Install, administer, and maintain + your own GitLab instance. -### GitLab.com: Create a user and group +The following sections outline tiers and features within GitLab.com +and GitLab self-managed. -Start with creating a user account for yourself using our [sign up page](https://gitlab.com/users/sign_in#register-pane). +### GitLab.com + +GitLab.com is hosted, managed, and administered by GitLab, Inc., with +[free and paid subscriptions](https://about.gitlab.com/pricing/) for individuals +and teams in the following tiers: -[GitLab groups](../user/group/index.md) help assemble related projects together allowing you to grant members access to several projects at once. A group is not required if you plan on having [projects](../user/project/) inside a personal namespace. +| Tier | Includes same features available in | +|:-------|:----------------------------------------------------| +| Free | [Core](#gitlab-self-managed) self-managed tier. | +| Bronze | [Starter](#gitlab-self-managed) self-managed tier. | +| Silver | [Premium](#gitlab-self-managed) self-managed tier. | +| Gold | [Ultimate](#gitlab-self-managed) self-managed tier. | -## Your subscription +GitLab.com subscriptions grant access +to the same features available in GitLab self-managed, **except +[administration](../administration/index.md) tools and settings**. -You can view and manage subscriptions through our [Customers portal](https://customers.gitlab.com/). Information on applying your subscription is below. +GitLab.com allows you to apply your subscription to a group or your personal user. -Please also see our [subscription FAQ](https://about.gitlab.com/pricing/licensing-faq/) +When applied to: -### View subscription and seats +- A **group**, the group, all subgroups, and all projects under the selected + group on GitLab.com will have the features of the associated plan. It is + recommended to go with a group plan when managing projects and users of an + organization. +- A **personal userspace** instead, all projects will have features with the + subscription applied, but as it is not a group, group features will not be available. -To view and manage the subscriptions you have purchased and the number of seats associated with the subscription, please visit and log into the [Customers’ Portal](https://customers.gitlab.com/subscriptions). For more information, please see our [subscription FAQ](https://about.gitlab.com/pricing/licensing-faq/) and [pricing page](https://about.gitlab.com/pricing/), which includes information on our [true-up pricing policy](https://about.gitlab.com/handbook/product/pricing/#true-up-pricing) when adding more users than at the time of purchase. +TIP: **Tip:** +To support the open source community and encourage the development of open +source projects, GitLab grants access to **Gold** features for all GitLab.com +**public** projects, regardless of the subscription. -Please note that this account may have the same email, but is a _separate_ login from your GitLab.com account. If the two accounts are linked together, then you can use the "sign in with GitLab.com account" link underneath the `Sign In` button. +The following resources are available for more information on GitLab.com: -### Change billing information +- [Feature comparison](https://about.gitlab.com/pricing/gitlab-com/feature-comparison/), for information on what features are available at each tier. +- [GitLab pricing page](https://about.gitlab.com/pricing/), for subscription information and a free trial. +- Our [product marketing page](https://about.gitlab.com/handbook/marketing/product-marketing/), for additional information including: + - How [different tiers are licensed](https://about.gitlab.com/handbook/marketing/product-marketing/#tiers). + - The different [GitLab distributions](https://about.gitlab.com/handbook/marketing/product-marketing/#distributions). + +#### Subscribing to GitLab.com + +To subscribe to GitLab.com: + +1. Create a user account for yourself using our + [sign up page](https://gitlab.com/users/sign_in#register-pane). +1. Create a [group](../user/group/index.md). GitLab groups help assemble related + projects together allowing you to grant members access to several projects + at once. A group is not required if you plan on having projects inside a personal + namespace. +1. Create additional users and + [add them to the group](../user/group/index.md#add-users-to-a-group). +1. Select the **Bronze**, **Silver**, or **Gold** GitLab.com plan through the + [GitLab Subscription Manager](https://customers.gitlab.com/). +1. Link your GitLab.com account with your GitLab Subscription Manager account. + Once signed into the GitLab Subscription Manager, if your account is not + already linked, you will prompted to link your account with a + **Link my GitLab Account** button. +1. Associate the group with the subscription. + +TIP: **Tip:** +You can also go to the [**My Account**](https://customers.gitlab.com/customers/edit) +page to add or change the GitLab.com account link. + +### GitLab self-managed + +With GitLab self-managed, you deploy your own GitLab instance on-premises or on a cloud of your choice. +GitLab self-managed is available for [free and with paid subscriptions](https://about.gitlab.com/pricing/#self-managed) in the following tiers: + +| Tier | Includes | +|:---------|:-----------------------------------------------| +| Core | Core features. | +| Starter | Core and Starter features. | +| Premium | Core, Starter, and Premium features. | +| Ultimate | Core, Starter, Premium, and Ultimate features. | + +The following resources are available for more information on GitLab self-managed: + +- [Feature comparison](https://about.gitlab.com/pricing/self-managed/feature-comparison/), for information on what features are available at each tier. +- [GitLab pricing page](https://about.gitlab.com/pricing/#self-managed), for subscription information and a free trial. +- Our [product marketing page](https://about.gitlab.com/handbook/marketing/product-marketing/), for additional information including: + - How [different tiers are licensed](https://about.gitlab.com/handbook/marketing/product-marketing/#tiers). + - The different [GitLab distributions](https://about.gitlab.com/handbook/marketing/product-marketing/#distributions). -In the customers portal, go to the `My Account` page, then revise the `Account Details` information and click on the `Update Account` button. +#### Subscribing through GitLab self-managed -Future purchases will use the information in this section. The email listed in this section is used for the Customers Portal login and for license related email communication. +To subscribe to GitLab through a self-managed installation: + +1. [Install](https://about.gitlab.com/install/) GitLab. +1. Complete the installation with + [administration tasks](https://docs.gitlab.com/ee/administration/). +1. Select the **Starter**, **Premium**, or **Ultimate** self-managed plan + through the [GitLab Subscription Manager](https://customers.gitlab.com/). +1. Apply your license file. After purchase, a license file is sent to the email + address associated to the GitLab Subscription Manager account, + which needs to be + [uploaded to your GitLab instance](../user/admin_area/license.md#uploading-your-license). + +TIP: **Tip:** +If you are purchasing a subscription for an existing **Core** self-managed +instance, ensure you are purchasing enough seats to +[cover your users](../user/admin_area/index.md#administering-users). + +## Managing subscriptions + +You can view and manage subscriptions through our +[GitLab Subscription Manager](https://customers.gitlab.com/). -### Self-managed: Apply your license file +### View subscription and seats + +Visit the +[GitLab Subscription Manager](https://customers.gitlab.com/subscriptions) to +view and manage: + +- The subscriptions you have purchased. +- The number of seats associated with the subscription. +- Retrieve copies of invoices. +- Change the credit card on file. -After purchase, the license file is sent to the email address tied to the Customers portal account, which needs to be [uploaded to the GitLab instance](../user/admin_area/license.md#uploading-your-license). +For more information, please see our: -### Link your GitLab.com account with your Customers Portal account +- [Subscription FAQ](https://about.gitlab.com/pricing/licensing-faq/). +- [Pricing page](https://about.gitlab.com/pricing/), which includes information + on our [true-up pricing policy](https://about.gitlab.com/handbook/product/pricing/#true-up-pricing) + when adding more users other than at the time of purchase. NOTE: **Note:** -This is *required* for GitLab.com subscriptions. +The GitLab Subscription Manager account can have the same email address as your +GitLab.com account, but is a _separate_ login. If the two accounts are +linked together, you can use the **Or sign in with GitLab.com** +link underneath the **Sign In** button. -Once signed into the customers portal, if your account is not already linked, you should be prompted to link your account with a "Link my GitLab Account" button. +### Change billing information -You can also go to the [My Account](https://customers.gitlab.com/customers/edit) page to add or change the GitLab.com account link. +To change billing information: -### Change the linked GitLab.com account for your Customers Portal account +1. Log in to [GitLab Subscription Manager](https://customers.gitlab.com/customers/sign_in). +1. Go to the **My Account** page. +1. Make the required changes to the **Account Details** information. +1. Click **Update Account**. -To change which GitLab.com account is associated with a Customers Portal account, please follow these steps: +NOTE: **Note:** +Future purchases will use the information in this section. +The email listed in this section is used for the GitLab Subscription Manager +login and for license-related email communication. -1. Log into the [Customers Portal](https://customers.gitlab.com/customers/sign_in). -1. In a separate browser tab, visit [GitLab.com](https://gitlab.com) to ensure you are not logged in, or if you are, log out. -1. Back on the Customers Portal page, click [My Account](https://customers.gitlab.com/customers/edit) in the top menu. -1. Under `Your GitLab.com account`, click the `Change linked account` button. -1. Have the user you want associated log in to their [GitLab.com](https://gitlab.com) account. +### Manage GitLab.com account -### GitLab.com: Associate your namespace with your subscription +This section provided information specific to managing subscriptions with a +GitLab.com account. -Once your GitLab.com account is linked, you can go to your [Subscriptions](https://customers.gitlab.com/subscriptions) page to choose or change the namespace your subscription applies to. +#### Change linked account -Please note that you need to be a group owner to associate a group to your subscription. +To change the GitLab.com account associated with a GitLab Subscription Manager +account: -### GitLab.com: Upgrade your subscription plan +1. Log in to the + [GitLab Subscription Manager](https://customers.gitlab.com/customers/sign_in). +1. Go to [GitLab.com](https://gitlab.com) in a separate browser tab. Ensure you + are not logged in. +1. On the GitLab Subscription Manager page, click + [**My Account**](https://customers.gitlab.com/customers/edit) in the top menu. +1. Under **Your GitLab.com account**, click **Change linked account** button. +1. Log in to [GitLab.com](https://gitlab.com) account to link to. -GitLab.com subscriptions can be upgraded directly through the [Subscriptions portal](https://customers.gitlab.com/subscriptions). +#### Change associated namespace -The Subscriptions portal provides an **Upgrade** button below each GitLab.com -subscription, which will lead you to a simple -checkout process. +With a linked GitLab.com account, go to the +[**Subscriptions**](https://customers.gitlab.com/subscriptions) page to choose +or change the namespace your subscription applies to. -### Confirm or upgrade your GitLab.com subscription details within GitLab +NOTE: **Note:** +Please note that you need to be a group owner to associate a group to your +subscription. -To see the status of your GitLab.com subscription, you can click on the Billings -section of the relevant namespace: +### Confirm or upgrade your subscription -- For individuals, this is located at <https://gitlab.com/profile/billings> under - in your Settings, -- For groups, this is located under the group's Settings dropdown, under Billing. +To see the status of your GitLab.com subscription, you can click on the +**Billings** section of the relevant namespace: -For groups, you can see details of your subscription - including your current -plan - in the included table: +- For individuals: + 1. Go to **User Avatar > Settings**. + 1. Click **Billing**. +- For groups, go to the group's **Settings** dropdown, under **Billing**. -![Billing table](billing_table.png) +The following table describes details of your subscription for groups: | Field | Description | | ------ | ------ | -| Seats in subscription | If this is a paid plan, this represents the number of seats you've paid to support in your group. | -| Seats currently in use | The number of active seats currently in use. | -| Max seats used | The highest number of seats you've used. If this exceeds the seats in subscription, you may owe an additional fee for the additional users. | +| Seats in subscription | If this is a paid plan, represents the number of seats you've paid to support in your group. | +| Seats currently in use | Number of active seats currently in use. | +| Max seats used | Highest number of seats you've used. If this exceeds the seats in subscription, you may owe an additional fee for the additional users. | | Seats owed | If your max seats used exceeds the seats in your subscription, you'll owe an additional fee for the users you've added. | -| Subscription start date | The date your subscription started. If this is for a Free plan, this is the date you transitioned off your group's paid plan. | -| Subscription end date | The date your current subscription will end. This does not apply to Free plans. | +| Subscription start date | Date your subscription started. If this is for a Free plan, is the date you transitioned off your group's paid plan. | +| Subscription end date | Date your current subscription will end. Does not apply to Free plans. | -### Subscription changes and your data +## Subscription changes and your data -When your subscription or trial expires, GitLab does not delete your data, however, depending on the tier and feature, it may become inaccessible. Please note that some features may not behave as expected if a graceful fallback is not currently implemented, such as [environment specific variables not being passed](https://gitlab.com/gitlab-org/gitlab-ce/issues/52825). +When your subscription or trial expires, GitLab does not delete your data. + +However, depending on the tier and feature, your data may become inaccessible. + +Please note that some features may not behave as expected if a graceful +fallback is not currently implemented. For example, +[environment specific variables not being passed](https://gitlab.com/gitlab-org/gitlab-ce/issues/52825). If you renew or upgrade, your data will again be accessible. -For self-managed customers, there is a two-week grace period when your features will continue to work as-is, after which the entire instance will become read only. However, if you remove the license, you will immediately revert to Core features. +### Self-managed data + +For self-managed customers, there is a two-week grace period when your features +will continue to work as-is, after which the entire instance will become read +only. + +However, if you remove the license, you will immediately revert to Core +features. ## Need help? -[GitLab's Documentation](https://docs.gitlab.com/) offers a wide range of topics covering the use and administration of GitLab. +[GitLab's Documentation](https://docs.gitlab.com/) offers a wide range of +topics covering the use and administration of GitLab. -We also encourage all users to search our project trackers for known issues and existing feature requests in: +We also encourage all users to search our project trackers for known issues and +existing feature requests in: -- [GitLab CE](https://gitlab.com/gitlab-org/gitlab-ce/issues/) for features included in all tiers, and -- [GitLab EE](https://gitlab.com/gitlab-org/gitlab-ee/issues/) for paid-tier features. +- [GitLab CE](https://gitlab.com/gitlab-org/gitlab-ce/issues/) for features + included in all tiers. +- [GitLab EE](https://gitlab.com/gitlab-org/gitlab-ee/issues/) for paid-tier + features. -These issues are the best avenue for getting updates on specific product plans and for communicating directly with the relevant GitLab team members. +These issues are the best avenue for getting updates on specific product plans +and for communicating directly with the relevant GitLab team members. ### Contacting Support -Learn more about the tiers of [GitLab Support](https://about.gitlab.com/support/) or [submit a request via the Support Portal](https://support.gitlab.com/hc/en-us/requests/new). +Learn more about: + +- The tiers of [GitLab Support](https://about.gitlab.com/support/). +- [Submit a request via the Support Portal](https://support.gitlab.com/hc/en-us/requests/new). <!-- ## Troubleshooting diff --git a/doc/system_hooks/system_hooks.md b/doc/system_hooks/system_hooks.md index e8bd35fba5c..1e9eb15533a 100644 --- a/doc/system_hooks/system_hooks.md +++ b/doc/system_hooks/system_hooks.md @@ -644,6 +644,11 @@ X-Gitlab-Event: System Hook } ``` +## Local requests in system hooks + +[Requests to local network by system hooks](../security/webhooks.md) can be allowed +or blocked by an administrator. + <!-- ## Troubleshooting Include any troubleshooting steps that you can foresee. If you know beforehand what issues diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md index 63a72f8f193..95220d6364c 100644 --- a/doc/topics/autodevops/index.md +++ b/doc/topics/autodevops/index.md @@ -190,7 +190,7 @@ Those environments are tied to jobs that use [Auto Deploy](#auto-deploy), so except for the environment scope, they would also need to have a different domain they would be deployed to. This is why you need to define a separate `KUBE_INGRESS_BASE_DOMAIN` variable for all the above -[based on the environment](../../ci/variables/README.md#limiting-environment-scopes-of-environment-variables-premium). +[based on the environment](../../ci/variables/README.md#limiting-environment-scopes-of-environment-variables). The following table is an example of how the three different clusters would be configured. @@ -662,10 +662,10 @@ repo or by specifying a project variable: You can also make use of the `HELM_UPGRADE_EXTRA_ARGS` environment variable to override the default values in the `values.yaml` file in the [default Helm chart](https://gitlab.com/gitlab-org/charts/auto-deploy-app). To apply your own `values.yaml` file to all Helm upgrade commands in Auto Deploy set `HELM_UPGRADE_EXTRA_ARGS` to `--values my-values.yaml`. -### Custom Helm chart per environment **(PREMIUM)** +### Custom Helm chart per environment You can specify the use of a custom Helm chart per environment by scoping the environment variable -to the desired environment. See [Limiting environment scopes of variables](../../ci/variables/README.md#limiting-environment-scopes-of-environment-variables-premium). +to the desired environment. See [Limiting environment scopes of variables](../../ci/variables/README.md#limiting-environment-scopes-of-environment-variables). ### Customizing `.gitlab-ci.yml` diff --git a/doc/user/admin_area/img/abuse_report_blocked_user.png b/doc/user/admin_area/img/abuse_report_blocked_user.png Binary files differindex 0cb4c7bb8ac..435d8dfe821 100644 --- a/doc/user/admin_area/img/abuse_report_blocked_user.png +++ b/doc/user/admin_area/img/abuse_report_blocked_user.png diff --git a/doc/user/admin_area/img/abuse_reports_page.png b/doc/user/admin_area/img/abuse_reports_page.png Binary files differindex 81dbe976cda..30e932211cb 100644 --- a/doc/user/admin_area/img/abuse_reports_page.png +++ b/doc/user/admin_area/img/abuse_reports_page.png diff --git a/doc/user/admin_area/img/broadcast_messages.png b/doc/user/admin_area/img/broadcast_messages.png Binary files differindex 926d38ae049..f0ae92f8c17 100644 --- a/doc/user/admin_area/img/broadcast_messages.png +++ b/doc/user/admin_area/img/broadcast_messages.png diff --git a/doc/user/admin_area/img/license_details.png b/doc/user/admin_area/img/license_details.png Binary files differindex 2085bb437ad..3e980d9316d 100644 --- a/doc/user/admin_area/img/license_details.png +++ b/doc/user/admin_area/img/license_details.png diff --git a/doc/user/admin_area/index.md b/doc/user/admin_area/index.md index f5e6bff67c5..9bc0f64b68d 100644 --- a/doc/user/admin_area/index.md +++ b/doc/user/admin_area/index.md @@ -272,7 +272,7 @@ The **Logs** page provides access to the following log files: | Log file | Contents | | :---------------------- | :------- | | `application.log` | GitLab user activity | -| `githost.log` | Failed GitLab interaction with Git repositories | +| `git_json.log` | Failed GitLab interaction with Git repositories | | `production.log` | Requests received from Unicorn, and the actions taken to serve those requests | | `sidekiq.log` | Background jobs | | `repocheck.log` | Repository activity | diff --git a/doc/user/admin_area/settings/email.md b/doc/user/admin_area/settings/email.md index 1f07a4dfdc6..490163c1816 100644 --- a/doc/user/admin_area/settings/email.md +++ b/doc/user/admin_area/settings/email.md @@ -17,11 +17,10 @@ The logo in the header of some emails can be customized, see the [logo customiza The additional text will appear at the bottom of any email and can be used for legal/auditing/compliance reasons. -1. Go to **Admin area > Settings** (`/admin/application_settings`). -1. Under the **Email** section, change the **Additional text** field. -1. Hit **Save** for the changes to take effect. - -![Admin email settings](img/email_settings.png) +1. Go to **Admin Area > Settings > Preferences** (`/admin/application_settings/preferences`). +1. Expand the **Email** section. +1. Enter your text in the **Additional text** field. +1. Click **Save**. [ee-5031]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/5031 [eep]: https://about.gitlab.com/pricing/ @@ -35,11 +34,13 @@ This configuration option sets the email hostname for [private commit emails](.. In order to change this option: -1. Go to **Admin area > Settings** (`/admin/application_settings`). -1. Under the **Email** section, change the **Custom hostname (for private commit emails)** field. -1. Hit **Save** for the changes to take effect. +1. Go to **Admin Area > Settings > Preferences** (`/admin/application_settings/preferences`). +1. Expand the **Email** section. +1. Enter the desire hostname in the **Custom hostname (for private commit emails)** field. +1. Click **Save changes**. -NOTE: **Note**: Once the hostname gets configured, every private commit email using the previous hostname, will not get +NOTE: **Note:** +Once the hostname gets configured, every private commit email using the previous hostname, will not get recognized by GitLab. This can directly conflict with certain [Push rules](../../../push_rules/push_rules.md) such as `Check whether author is a GitLab user` and `Check whether committer is the current authenticated user`. @@ -53,4 +54,4 @@ questions that you know someone might ask. Each scenario can be a third-level heading, e.g. `### Getting error message X`. If you have none to add when creating a doc, leave this section in place -but commented out to help encourage others to add to it in the future. -->
\ No newline at end of file +but commented out to help encourage others to add to it in the future. --> diff --git a/doc/user/admin_area/settings/img/additional_minutes.png b/doc/user/admin_area/settings/img/additional_minutes.png Binary files differindex d148ed79b92..b159b98c9ce 100644 --- a/doc/user/admin_area/settings/img/additional_minutes.png +++ b/doc/user/admin_area/settings/img/additional_minutes.png diff --git a/doc/user/admin_area/settings/img/admin_required_pipeline.png b/doc/user/admin_area/settings/img/admin_required_pipeline.png Binary files differindex 58488674d51..501b1e3ba0a 100644 --- a/doc/user/admin_area/settings/img/admin_required_pipeline.png +++ b/doc/user/admin_area/settings/img/admin_required_pipeline.png diff --git a/doc/user/admin_area/settings/img/buy_btn.png b/doc/user/admin_area/settings/img/buy_btn.png Binary files differindex 0cc88b8a48f..4fd05c0fba7 100644 --- a/doc/user/admin_area/settings/img/buy_btn.png +++ b/doc/user/admin_area/settings/img/buy_btn.png diff --git a/doc/user/admin_area/settings/img/buy_minutes_card.png b/doc/user/admin_area/settings/img/buy_minutes_card.png Binary files differindex cf4ad34ead7..cab098300cd 100644 --- a/doc/user/admin_area/settings/img/buy_minutes_card.png +++ b/doc/user/admin_area/settings/img/buy_minutes_card.png diff --git a/doc/user/admin_area/settings/img/email_confirmation.png b/doc/user/admin_area/settings/img/email_confirmation.png Binary files differindex 4d888da3416..987aa10c3ce 100644 --- a/doc/user/admin_area/settings/img/email_confirmation.png +++ b/doc/user/admin_area/settings/img/email_confirmation.png diff --git a/doc/user/admin_area/settings/img/email_settings.png b/doc/user/admin_area/settings/img/email_settings.png Binary files differdeleted file mode 100644 index ed0a80d10ce..00000000000 --- a/doc/user/admin_area/settings/img/email_settings.png +++ /dev/null diff --git a/doc/user/admin_area/settings/img/user_and_ip_rate_limits.png b/doc/user/admin_area/settings/img/user_and_ip_rate_limits.png Binary files differindex 2bd85a2fd96..53dc0e4ac87 100644 --- a/doc/user/admin_area/settings/img/user_and_ip_rate_limits.png +++ b/doc/user/admin_area/settings/img/user_and_ip_rate_limits.png diff --git a/doc/user/application_security/img/create_issue_with_list_hover.png b/doc/user/application_security/img/create_issue_with_list_hover.png Binary files differindex 7d70e8299f5..4c38862e68f 100644 --- a/doc/user/application_security/img/create_issue_with_list_hover.png +++ b/doc/user/application_security/img/create_issue_with_list_hover.png diff --git a/doc/user/application_security/img/dismissed_info.png b/doc/user/application_security/img/dismissed_info.png Binary files differindex 64d5cf26ed2..b4470b664d2 100644 --- a/doc/user/application_security/img/dismissed_info.png +++ b/doc/user/application_security/img/dismissed_info.png diff --git a/doc/user/application_security/img/interactive_reports.png b/doc/user/application_security/img/interactive_reports.png Binary files differindex 373b39104db..1b2ef0d3da9 100644 --- a/doc/user/application_security/img/interactive_reports.png +++ b/doc/user/application_security/img/interactive_reports.png diff --git a/doc/user/application_security/img/vulnerability_solution.png b/doc/user/application_security/img/vulnerability_solution.png Binary files differindex 7443b9b6eea..d86b89a5f99 100644 --- a/doc/user/application_security/img/vulnerability_solution.png +++ b/doc/user/application_security/img/vulnerability_solution.png diff --git a/doc/user/application_security/license_management/img/license_management_add_license.png b/doc/user/application_security/license_management/img/license_management_add_license.png Binary files differindex 1e1a698515b..c9a5dc14c57 100644 --- a/doc/user/application_security/license_management/img/license_management_add_license.png +++ b/doc/user/application_security/license_management/img/license_management_add_license.png diff --git a/doc/user/application_security/license_management/img/license_management_decision.png b/doc/user/application_security/license_management/img/license_management_decision.png Binary files differindex 0763130c375..fbf90bec7fd 100644 --- a/doc/user/application_security/license_management/img/license_management_decision.png +++ b/doc/user/application_security/license_management/img/license_management_decision.png diff --git a/doc/user/application_security/license_management/img/license_management_search.png b/doc/user/application_security/license_management/img/license_management_search.png Binary files differindex 7b6006cef9d..b3ffd8d95a1 100644 --- a/doc/user/application_security/license_management/img/license_management_search.png +++ b/doc/user/application_security/license_management/img/license_management_search.png diff --git a/doc/user/application_security/license_management/img/license_management_settings.png b/doc/user/application_security/license_management/img/license_management_settings.png Binary files differindex 1a2bfa78a03..2e3e8888e93 100644 --- a/doc/user/application_security/license_management/img/license_management_settings.png +++ b/doc/user/application_security/license_management/img/license_management_settings.png diff --git a/doc/user/application_security/sast/analyzers.md b/doc/user/application_security/sast/analyzers.md index cb533538047..f730a25a9fc 100644 --- a/doc/user/application_security/sast/analyzers.md +++ b/doc/user/application_security/sast/analyzers.md @@ -17,19 +17,19 @@ This is achieved by implementing the [common API](https://gitlab.com/gitlab-org/ SAST supports the following official analyzers: -- [Bandit](https://gitlab.com/gitlab-org/security-products/analyzers/bandit) -- [Brakeman](https://gitlab.com/gitlab-org/security-products/analyzers/brakeman) -- [ESLint (Javascript)](https://gitlab.com/gitlab-org/security-products/analyzers/eslint) -- [SpotBugs with the Find Sec Bugs plugin (Ant, Gradle and wrapper, Grails, Maven and wrapper, SBT)](https://gitlab.com/gitlab-org/security-products/analyzers/spotbugs) -- [Flawfinder](https://gitlab.com/gitlab-org/security-products/analyzers/flawfinder) -- [Gosec](https://gitlab.com/gitlab-org/security-products/analyzers/gosec) -- [NodeJsScan](https://gitlab.com/gitlab-org/security-products/analyzers/nodejs-scan) -- [PHP CS security-audit](https://gitlab.com/gitlab-org/security-products/analyzers/phpcs-security-audit) -- [Secrets (Gitleaks, TruffleHog & Diffence secret detectors)](https://gitlab.com/gitlab-org/security-products/analyzers/secrets) -- [Security Code Scan (.NET)](https://gitlab.com/gitlab-org/security-products/analyzers/security-code-scan) -- [TSLint (Typescript)](https://gitlab.com/gitlab-org/security-products/analyzers/tslint) -- [Sobelow (Elixir Phoenix)](https://gitlab.com/gitlab-org/security-products/analyzers/sobelow) -- [PMD (Apex only)](https://gitlab.com/gitlab-org/security-products/analyzers/pmd-apex) +- [`bandit`](https://gitlab.com/gitlab-org/security-products/analyzers/bandit) (Bandit) +- [`brakeman`](https://gitlab.com/gitlab-org/security-products/analyzers/brakeman) (Brakeman) +- [`eslint`](https://gitlab.com/gitlab-org/security-products/analyzers/eslint) (ESLint (Javascript)) +- [`flawfinder`](https://gitlab.com/gitlab-org/security-products/analyzers/flawfinder) (Flawfinder) +- [`gosec`](https://gitlab.com/gitlab-org/security-products/analyzers/gosec) (Gosec) +- [`nodejs-scan`](https://gitlab.com/gitlab-org/security-products/analyzers/nodejs-scan) (NodeJsScan) +- [`phpcs-security-audit`](https://gitlab.com/gitlab-org/security-products/analyzers/phpcs-security-audit) (PHP CS security-audit) +- [`pmd-apex`](https://gitlab.com/gitlab-org/security-products/analyzers/pmd-apex) (PMD (Apex only)) +- [`secrets`](https://gitlab.com/gitlab-org/security-products/analyzers/secrets) (Secrets (Gitleaks, TruffleHog & Diffence secret detectors)) +- [`security-code-scan`](https://gitlab.com/gitlab-org/security-products/analyzers/security-code-scan) (Security Code Scan (.NET)) +- [`sobelow`](https://gitlab.com/gitlab-org/security-products/analyzers/sobelow) (Sobelow (Elixir Phoenix)) +- [`spotbugs`](https://gitlab.com/gitlab-org/security-products/analyzers/spotbugs) (SpotBugs with the Find Sec Bugs plugin (Ant, Gradle and wrapper, Grails, Maven and wrapper, SBT)) +- [`tslint`](https://gitlab.com/gitlab-org/security-products/analyzers/tslint) (TSLint (Typescript)) The analyzers are published as Docker images that SAST will use to launch dedicated containers for each analysis. diff --git a/doc/user/application_security/security_dashboard/img/group_security_dashboard.png b/doc/user/application_security/security_dashboard/img/group_security_dashboard.png Binary files differindex 40689861e2a..85ab124f74c 100644 --- a/doc/user/application_security/security_dashboard/img/group_security_dashboard.png +++ b/doc/user/application_security/security_dashboard/img/group_security_dashboard.png diff --git a/doc/user/application_security/security_dashboard/img/project_security_dashboard.png b/doc/user/application_security/security_dashboard/img/project_security_dashboard.png Binary files differindex 89b310895d3..baa136fd885 100644 --- a/doc/user/application_security/security_dashboard/img/project_security_dashboard.png +++ b/doc/user/application_security/security_dashboard/img/project_security_dashboard.png diff --git a/doc/user/clusters/img/jupyter-gitclone.png b/doc/user/clusters/img/jupyter-gitclone.png Binary files differindex 41d467f806a..aff194dea43 100644 --- a/doc/user/clusters/img/jupyter-gitclone.png +++ b/doc/user/clusters/img/jupyter-gitclone.png diff --git a/doc/user/discussions/img/automatically_resolve_outdated_discussions.png b/doc/user/discussions/img/automatically_resolve_outdated_discussions.png Binary files differindex d31216a7e2e..a6fc4b0aef1 100644 --- a/doc/user/discussions/img/automatically_resolve_outdated_discussions.png +++ b/doc/user/discussions/img/automatically_resolve_outdated_discussions.png diff --git a/doc/user/discussions/img/btn_new_issue_for_all_threads.png b/doc/user/discussions/img/btn_new_issue_for_all_threads.png Binary files differindex f24c84a2348..5d86e0ca016 100644 --- a/doc/user/discussions/img/btn_new_issue_for_all_threads.png +++ b/doc/user/discussions/img/btn_new_issue_for_all_threads.png diff --git a/doc/user/discussions/img/commit_comment_mr_context.png b/doc/user/discussions/img/commit_comment_mr_context.png Binary files differindex 7b87d6e44d7..68f58a57757 100644 --- a/doc/user/discussions/img/commit_comment_mr_context.png +++ b/doc/user/discussions/img/commit_comment_mr_context.png diff --git a/doc/user/discussions/img/commit_comment_mr_discussions_tab.png b/doc/user/discussions/img/commit_comment_mr_discussions_tab.png Binary files differindex 4798ff4b658..d88f08eae26 100644 --- a/doc/user/discussions/img/commit_comment_mr_discussions_tab.png +++ b/doc/user/discussions/img/commit_comment_mr_discussions_tab.png diff --git a/doc/user/discussions/img/discussion_comment.png b/doc/user/discussions/img/discussion_comment.png Binary files differindex 685c30e5004..3fec5962363 100644 --- a/doc/user/discussions/img/discussion_comment.png +++ b/doc/user/discussions/img/discussion_comment.png diff --git a/doc/user/discussions/img/image_resolved_discussion.png b/doc/user/discussions/img/image_resolved_discussion.png Binary files differindex 9feded27c92..f6e5a3b66ae 100644 --- a/doc/user/discussions/img/image_resolved_discussion.png +++ b/doc/user/discussions/img/image_resolved_discussion.png diff --git a/doc/user/discussions/img/merge_request_commits_tab.png b/doc/user/discussions/img/merge_request_commits_tab.png Binary files differindex 065d4be61f0..267f3a720dc 100644 --- a/doc/user/discussions/img/merge_request_commits_tab.png +++ b/doc/user/discussions/img/merge_request_commits_tab.png diff --git a/doc/user/discussions/img/mr_review_resolve.png b/doc/user/discussions/img/mr_review_resolve.png Binary files differindex fc6299961a5..ced33682459 100644 --- a/doc/user/discussions/img/mr_review_resolve.png +++ b/doc/user/discussions/img/mr_review_resolve.png diff --git a/doc/user/discussions/img/mr_review_resolve2.png b/doc/user/discussions/img/mr_review_resolve2.png Binary files differindex 1794b682911..2f0be3b6d06 100644 --- a/doc/user/discussions/img/mr_review_resolve2.png +++ b/doc/user/discussions/img/mr_review_resolve2.png diff --git a/doc/user/discussions/img/mr_review_second_comment.png b/doc/user/discussions/img/mr_review_second_comment.png Binary files differindex 204cc840d9e..f345c52e941 100644 --- a/doc/user/discussions/img/mr_review_second_comment.png +++ b/doc/user/discussions/img/mr_review_second_comment.png diff --git a/doc/user/discussions/img/mr_review_second_comment_added.png b/doc/user/discussions/img/mr_review_second_comment_added.png Binary files differindex aa15ca7fb98..61b45431c9e 100644 --- a/doc/user/discussions/img/mr_review_second_comment_added.png +++ b/doc/user/discussions/img/mr_review_second_comment_added.png diff --git a/doc/user/discussions/img/mr_review_start.png b/doc/user/discussions/img/mr_review_start.png Binary files differindex 0f52bee7d89..08b4c6bb82b 100644 --- a/doc/user/discussions/img/mr_review_start.png +++ b/doc/user/discussions/img/mr_review_start.png diff --git a/doc/user/discussions/img/mr_review_unresolve.png b/doc/user/discussions/img/mr_review_unresolve.png Binary files differindex 3441efc1572..4bef38f7808 100644 --- a/doc/user/discussions/img/mr_review_unresolve.png +++ b/doc/user/discussions/img/mr_review_unresolve.png diff --git a/doc/user/discussions/img/mr_review_unresolve2.png b/doc/user/discussions/img/mr_review_unresolve2.png Binary files differindex a824b806e4a..79da61bb556 100644 --- a/doc/user/discussions/img/mr_review_unresolve2.png +++ b/doc/user/discussions/img/mr_review_unresolve2.png diff --git a/doc/user/discussions/img/multi-line-suggestion-preview.png b/doc/user/discussions/img/multi-line-suggestion-preview.png Binary files differindex 4288d0ba034..476c50b9098 100644 --- a/doc/user/discussions/img/multi-line-suggestion-preview.png +++ b/doc/user/discussions/img/multi-line-suggestion-preview.png diff --git a/doc/user/discussions/img/multi-line-suggestion-syntax.png b/doc/user/discussions/img/multi-line-suggestion-syntax.png Binary files differindex df0c99b84ef..80424d1f056 100644 --- a/doc/user/discussions/img/multi-line-suggestion-syntax.png +++ b/doc/user/discussions/img/multi-line-suggestion-syntax.png diff --git a/doc/user/discussions/img/new_issue_for_thread.png b/doc/user/discussions/img/new_issue_for_thread.png Binary files differindex 2264da0b5b5..28b76adf7fe 100644 --- a/doc/user/discussions/img/new_issue_for_thread.png +++ b/doc/user/discussions/img/new_issue_for_thread.png diff --git a/doc/user/discussions/img/onion_skin_view.png b/doc/user/discussions/img/onion_skin_view.png Binary files differindex 9bb4428184e..81bb4a2c85a 100644 --- a/doc/user/discussions/img/onion_skin_view.png +++ b/doc/user/discussions/img/onion_skin_view.png diff --git a/doc/user/discussions/img/only_allow_merge_if_all_threads_are_resolved.png b/doc/user/discussions/img/only_allow_merge_if_all_threads_are_resolved.png Binary files differindex 9314e3a6490..bd0aaca79b2 100644 --- a/doc/user/discussions/img/only_allow_merge_if_all_threads_are_resolved.png +++ b/doc/user/discussions/img/only_allow_merge_if_all_threads_are_resolved.png diff --git a/doc/user/discussions/img/pending_review_comment.png b/doc/user/discussions/img/pending_review_comment.png Binary files differindex 812e4ac966a..70a66b3f4f0 100644 --- a/doc/user/discussions/img/pending_review_comment.png +++ b/doc/user/discussions/img/pending_review_comment.png diff --git a/doc/user/discussions/img/preview_issue_for_thread.png b/doc/user/discussions/img/preview_issue_for_thread.png Binary files differindex 1517902c61c..a9d7ab598be 100644 --- a/doc/user/discussions/img/preview_issue_for_thread.png +++ b/doc/user/discussions/img/preview_issue_for_thread.png diff --git a/doc/user/discussions/img/preview_issue_for_threads.png b/doc/user/discussions/img/preview_issue_for_threads.png Binary files differindex 8359ab3143c..8757decb178 100644 --- a/doc/user/discussions/img/preview_issue_for_threads.png +++ b/doc/user/discussions/img/preview_issue_for_threads.png diff --git a/doc/user/discussions/img/reply_to_comment_button.png b/doc/user/discussions/img/reply_to_comment_button.png Binary files differindex d362b19785c..d327d1c3e27 100644 --- a/doc/user/discussions/img/reply_to_comment_button.png +++ b/doc/user/discussions/img/reply_to_comment_button.png diff --git a/doc/user/discussions/img/resolve_comment_button.png b/doc/user/discussions/img/resolve_comment_button.png Binary files differindex 0319ec999fd..0a3ed03a69c 100644 --- a/doc/user/discussions/img/resolve_comment_button.png +++ b/doc/user/discussions/img/resolve_comment_button.png diff --git a/doc/user/discussions/img/resolve_thread_button.png b/doc/user/discussions/img/resolve_thread_button.png Binary files differindex 873c302f570..ca0a3e50550 100644 --- a/doc/user/discussions/img/resolve_thread_button.png +++ b/doc/user/discussions/img/resolve_thread_button.png diff --git a/doc/user/discussions/img/resolve_thread_issue_notice.png b/doc/user/discussions/img/resolve_thread_issue_notice.png Binary files differindex c2a8fdebee7..30a65b8fbd4 100644 --- a/doc/user/discussions/img/resolve_thread_issue_notice.png +++ b/doc/user/discussions/img/resolve_thread_issue_notice.png diff --git a/doc/user/discussions/img/resolve_thread_open_issue.png b/doc/user/discussions/img/resolve_thread_open_issue.png Binary files differindex be2a4365297..2dd4ea3cb1b 100644 --- a/doc/user/discussions/img/resolve_thread_open_issue.png +++ b/doc/user/discussions/img/resolve_thread_open_issue.png diff --git a/doc/user/discussions/img/review_comment_quickactions.png b/doc/user/discussions/img/review_comment_quickactions.png Binary files differindex df5c4dd0fcc..276def6381f 100644 --- a/doc/user/discussions/img/review_comment_quickactions.png +++ b/doc/user/discussions/img/review_comment_quickactions.png diff --git a/doc/user/discussions/img/review_preview.png b/doc/user/discussions/img/review_preview.png Binary files differindex 1b91506b477..e69a58dbb91 100644 --- a/doc/user/discussions/img/review_preview.png +++ b/doc/user/discussions/img/review_preview.png diff --git a/doc/user/discussions/img/swipe_view.png b/doc/user/discussions/img/swipe_view.png Binary files differindex 287d52a0811..e6f5e5053af 100644 --- a/doc/user/discussions/img/swipe_view.png +++ b/doc/user/discussions/img/swipe_view.png diff --git a/doc/user/discussions/img/thread_view.png b/doc/user/discussions/img/thread_view.png Binary files differindex 8c1fd9d5acf..e2db44aa604 100644 --- a/doc/user/discussions/img/thread_view.png +++ b/doc/user/discussions/img/thread_view.png diff --git a/doc/user/discussions/img/threads_resolved.png b/doc/user/discussions/img/threads_resolved.png Binary files differindex 6ac815cf874..ffb1233f2ee 100644 --- a/doc/user/discussions/img/threads_resolved.png +++ b/doc/user/discussions/img/threads_resolved.png diff --git a/doc/user/discussions/img/two_up_view.png b/doc/user/discussions/img/two_up_view.png Binary files differindex 062a96723dd..3b6ddfbe1be 100644 --- a/doc/user/discussions/img/two_up_view.png +++ b/doc/user/discussions/img/two_up_view.png diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md index e6c27c33654..928950126da 100644 --- a/doc/user/gitlab_com/index.md +++ b/doc/user/gitlab_com/index.md @@ -316,7 +316,8 @@ with details, such as the affected IP address. ### HAProxy API throttle -GitLab.com responds with HTTP status code 429 to API requests over 10 requests +GitLab.com responds with HTTP status code `429` to API requests that exceed 10 +requests per second per IP address. The following example headers are included for all API requests: @@ -335,10 +336,12 @@ Source: ### Rack Attack initializer +Details of rate limits enforced by [Rack Attack](../../security/rack_attack.md). + #### Protected paths throttle -GitLab.com responds with HTTP status code 429 to POST requests at protected -paths over 10 requests per **minute** per IP address. +GitLab.com responds with HTTP status code `429` to POST requests at protected +paths that exceed 10 requests per **minute** per IP address. See the source below for which paths are protected. This includes user creation, user confirmation, user sign in, and password reset. diff --git a/doc/user/group/bulk_editing/img/bulk-editing.png b/doc/user/group/bulk_editing/img/bulk-editing.png Binary files differindex d08503dc312..ca1662a781b 100644 --- a/doc/user/group/bulk_editing/img/bulk-editing.png +++ b/doc/user/group/bulk_editing/img/bulk-editing.png diff --git a/doc/user/group/bulk_editing/index.md b/doc/user/group/bulk_editing/index.md index c8715577eb2..ea48b0b9fef 100644 --- a/doc/user/group/bulk_editing/index.md +++ b/doc/user/group/bulk_editing/index.md @@ -1,25 +1,31 @@ -# Bulk editing issue and merge request milestones **(PREMIUM)** +# Bulk editing issues, merge requests, and epics at the group level **(PREMIUM)** -> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/7249) for issues in - [GitLab Premium](https://about.gitlab.com/pricing/) 12.1. -> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/12719) for merge - requests in GitLab [GitLab Premium](https://about.gitlab.com/pricing/) 12.2. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/7249) for issues in [GitLab Premium](https://about.gitlab.com/pricing/) 12.1. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/12719) for merge requests in [GitLab Premium](https://about.gitlab.com/pricing/) 12.2. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/7250) for epics in [GitLab Premium](https://about.gitlab.com/pricing/) 12.2. -Milestones can be updated simultaneously across multiple issues or merge requests by using the bulk editing feature. +## Editing milestones and labels -![Bulk editing](img/bulk-editing.png) +> **Notes:** +> +> - A permission level of `Reporter` or higher is required in order to manage issues. +> - A permission level of `Developer` or higher is required in order to manage merge requests. +> - A permission level of `Reporter` or higher is required in order to manage epics. + +By using the bulk editing feature: -NOTE: **Note:** -A permission level of `Reporter` or higher is required in order to manage issues, and -a permission level of `Developer` or higher is required in order to manage merge requests. +- Milestones can be updated simultaneously across multiple issues or merge requests. +- Labels can be updated simultaneously across multiple issues, merge requests, or epics. + +![Bulk editing](img/bulk-editing.png) -To bulk update group issue or merge request milestones: +To bulk update group issues, merge requests, or epics: -1. Navigate to the issues or merge requests list. -1. Click the **Edit issues** or **Edit merge requests** button. - - This will open a sidebar on the right-hand side of your screen where an editable field - for milestones will be displayed. - - Checkboxes will also appear beside each issue or merge request. -1. Check the checkbox beside each issue to be edited. -1. Select the desired milestone from the sidebar. +1. Navigate to the issues, merge requests, or epics list. +1. Click **Edit issues**, **Edit merge requests**, or **Edit epics**. + - This will open a sidebar on the right-hand side where editable fields + for milestones and labels will be displayed. + - Checkboxes will also appear beside each issue, merge request, or epic. +1. Check the checkbox beside each issue, merge request, or epic to be edited. +1. Select the desired new values from the sidebar. 1. Click **Update all**. diff --git a/doc/user/group/clusters/index.md b/doc/user/group/clusters/index.md index 625c5440ec0..d41f44f85cc 100644 --- a/doc/user/group/clusters/index.md +++ b/doc/user/group/clusters/index.md @@ -86,7 +86,7 @@ The domain should have a wildcard DNS configured to the Ingress IP address. When adding more than one Kubernetes cluster to your project, you need to differentiate them with an environment scope. The environment scope associates clusters with [environments](../../../ci/environments.md) similar to how the -[environment-specific variables](../../../ci/variables/README.md#limiting-environment-scopes-of-environment-variables-premium) +[environment-specific variables](../../../ci/variables/README.md#limiting-environment-scopes-of-environment-variables) work. While evaluating which environment matches the environment scope of a diff --git a/doc/user/group/epics/img/bulk_editing.png b/doc/user/group/epics/img/bulk_editing.png Binary files differnew file mode 100644 index 00000000000..85610bef83e --- /dev/null +++ b/doc/user/group/epics/img/bulk_editing.png diff --git a/doc/user/group/epics/img/button_reopen_epic.png b/doc/user/group/epics/img/button_reopen_epic.png Binary files differindex 7a953189270..9d1be88549d 100644 --- a/doc/user/group/epics/img/button_reopen_epic.png +++ b/doc/user/group/epics/img/button_reopen_epic.png diff --git a/doc/user/group/epics/img/containing_epic.png b/doc/user/group/epics/img/containing_epic.png Binary files differindex 5ed693e6d6a..dc13d55e2bc 100644 --- a/doc/user/group/epics/img/containing_epic.png +++ b/doc/user/group/epics/img/containing_epic.png diff --git a/doc/user/group/epics/img/epic_view.png b/doc/user/group/epics/img/epic_view.png Binary files differindex dbda98e4351..c55d302ec29 100644 --- a/doc/user/group/epics/img/epic_view.png +++ b/doc/user/group/epics/img/epic_view.png diff --git a/doc/user/group/epics/index.md b/doc/user/group/epics/index.md index 4ab562b655f..5968b91c9b7 100644 --- a/doc/user/group/epics/index.md +++ b/doc/user/group/epics/index.md @@ -97,6 +97,22 @@ have a [start or due date](#start-date-and-due-date), then you can see a Drag and drop to reorder issues and child epics. New issues and child epics added to an epic appear at the top of the list. +## Updating epics + +### Using bulk editing + +To apply labels across multiple epics: + +1. Go to the Epics list. +1. Click **Edit epics**. + - Checkboxes will appear beside each epic. + - A sidebar on the right-hand side will appear, with an editable field for labels. +1. Check the checkbox beside each epic to be edited. +1. Select the desired labels. +1. Click **Update all**. + +![bulk editing](img/bulk_editing.png) + ## Deleting an epic NOTE: **Note:** diff --git a/doc/user/group/img/group_file_template_dropdown.png b/doc/user/group/img/group_file_template_dropdown.png Binary files differindex d9caae1a223..f0586772218 100644 --- a/doc/user/group/img/group_file_template_dropdown.png +++ b/doc/user/group/img/group_file_template_dropdown.png diff --git a/doc/user/group/img/group_file_template_settings.png b/doc/user/group/img/group_file_template_settings.png Binary files differindex ca42f7726db..5e07974bc86 100644 --- a/doc/user/group/img/group_file_template_settings.png +++ b/doc/user/group/img/group_file_template_settings.png diff --git a/doc/user/group/insights/img/insights_example_stacked_bar_chart.png b/doc/user/group/insights/img/insights_example_stacked_bar_chart.png Binary files differindex 791d0e4bcdf..0e338b99e4c 100644 --- a/doc/user/group/insights/img/insights_example_stacked_bar_chart.png +++ b/doc/user/group/insights/img/insights_example_stacked_bar_chart.png diff --git a/doc/user/group/insights/img/insights_group_configuration.png b/doc/user/group/insights/img/insights_group_configuration.png Binary files differindex 0af0073e448..d181a1e94c3 100644 --- a/doc/user/group/insights/img/insights_group_configuration.png +++ b/doc/user/group/insights/img/insights_group_configuration.png diff --git a/doc/user/group/insights/img/insights_sidebar_link.png b/doc/user/group/insights/img/insights_sidebar_link.png Binary files differindex 64bbd1fc4d3..f7b0c2daae3 100644 --- a/doc/user/group/insights/img/insights_sidebar_link.png +++ b/doc/user/group/insights/img/insights_sidebar_link.png diff --git a/doc/user/group/roadmap/img/roadmap_view.png b/doc/user/group/roadmap/img/roadmap_view.png Binary files differindex ff41a2e0441..2be3849ba1b 100644 --- a/doc/user/group/roadmap/img/roadmap_view.png +++ b/doc/user/group/roadmap/img/roadmap_view.png diff --git a/doc/user/group/saml_sso/img/group_saml_configuration_information.png b/doc/user/group/saml_sso/img/group_saml_configuration_information.png Binary files differindex 98b83d0cb0f..e03c50ceb01 100644 --- a/doc/user/group/saml_sso/img/group_saml_configuration_information.png +++ b/doc/user/group/saml_sso/img/group_saml_configuration_information.png diff --git a/doc/user/group/saml_sso/img/group_saml_settings.png b/doc/user/group/saml_sso/img/group_saml_settings.png Binary files differindex 8c5dbe36f98..487be4faeba 100644 --- a/doc/user/group/saml_sso/img/group_saml_settings.png +++ b/doc/user/group/saml_sso/img/group_saml_settings.png diff --git a/doc/user/group/saml_sso/img/scim_advanced.png b/doc/user/group/saml_sso/img/scim_advanced.png Binary files differindex 3b70e3fbe83..c9e095dc89a 100644 --- a/doc/user/group/saml_sso/img/scim_advanced.png +++ b/doc/user/group/saml_sso/img/scim_advanced.png diff --git a/doc/user/group/saml_sso/img/scim_attribute_mapping.png b/doc/user/group/saml_sso/img/scim_attribute_mapping.png Binary files differindex dad459d8c28..933d8fb6f36 100644 --- a/doc/user/group/saml_sso/img/scim_attribute_mapping.png +++ b/doc/user/group/saml_sso/img/scim_attribute_mapping.png diff --git a/doc/user/group/saml_sso/img/scim_name_identifier_mapping.png b/doc/user/group/saml_sso/img/scim_name_identifier_mapping.png Binary files differindex 85e5648816e..f9c63970f16 100644 --- a/doc/user/group/saml_sso/img/scim_name_identifier_mapping.png +++ b/doc/user/group/saml_sso/img/scim_name_identifier_mapping.png diff --git a/doc/user/group/saml_sso/img/scim_provisioning_status.png b/doc/user/group/saml_sso/img/scim_provisioning_status.png Binary files differindex 4b8887b5418..41466ec9276 100644 --- a/doc/user/group/saml_sso/img/scim_provisioning_status.png +++ b/doc/user/group/saml_sso/img/scim_provisioning_status.png diff --git a/doc/user/group/saml_sso/img/scim_token.png b/doc/user/group/saml_sso/img/scim_token.png Binary files differindex 7eb52bf6ea2..bf9347716e9 100644 --- a/doc/user/group/saml_sso/img/scim_token.png +++ b/doc/user/group/saml_sso/img/scim_token.png diff --git a/doc/user/group/saml_sso/img/unlink_group_saml.png b/doc/user/group/saml_sso/img/unlink_group_saml.png Binary files differindex 0561443b5f4..9d53a9bf407 100644 --- a/doc/user/group/saml_sso/img/unlink_group_saml.png +++ b/doc/user/group/saml_sso/img/unlink_group_saml.png diff --git a/doc/user/operations_dashboard/img/index_operations_dashboard_top_bar_icon.png b/doc/user/operations_dashboard/img/index_operations_dashboard_top_bar_icon.png Binary files differindex 9d6a509ea72..d4db5e88672 100644 --- a/doc/user/operations_dashboard/img/index_operations_dashboard_top_bar_icon.png +++ b/doc/user/operations_dashboard/img/index_operations_dashboard_top_bar_icon.png diff --git a/doc/user/operations_dashboard/img/index_operations_dashboard_with_projects.png b/doc/user/operations_dashboard/img/index_operations_dashboard_with_projects.png Binary files differindex ee33eee8134..7dbec81c18a 100644 --- a/doc/user/operations_dashboard/img/index_operations_dashboard_with_projects.png +++ b/doc/user/operations_dashboard/img/index_operations_dashboard_with_projects.png diff --git a/doc/user/profile/img/active_sessions_list.png b/doc/user/profile/img/active_sessions_list.png Binary files differindex 1e242ac4710..41173c7eee5 100644 --- a/doc/user/profile/img/active_sessions_list.png +++ b/doc/user/profile/img/active_sessions_list.png diff --git a/doc/user/profile/index.md b/doc/user/profile/index.md index 06710065dfd..e5ccc8ee758 100644 --- a/doc/user/profile/index.md +++ b/doc/user/profile/index.md @@ -27,6 +27,7 @@ On your profile page, you will see the following information: - Groups: [groups](../group/index.md) you're a member of - Contributed projects: [projects](../project/index.md) you contributed to - Personal projects: your personal projects (respecting the project's visibility level) +- Starred projects: projects you starred - Snippets: your personal code [snippets](../snippets.md#personal-snippets) ## Profile settings @@ -91,6 +92,7 @@ The following information will be hidden from the user profile page (`https://gi - Groups tab - Contributed projects tab - Personal projects tab +- Starred projects tab - Snippets tab To enable private profile: diff --git a/doc/user/project/clusters/img/k8s_cluster_monitoring.png b/doc/user/project/clusters/img/k8s_cluster_monitoring.png Binary files differindex e449893a606..0a8c5043c65 100644 --- a/doc/user/project/clusters/img/k8s_cluster_monitoring.png +++ b/doc/user/project/clusters/img/k8s_cluster_monitoring.png diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md index 7dfd0d04637..ffaa07cb3a4 100644 --- a/doc/user/project/clusters/index.md +++ b/doc/user/project/clusters/index.md @@ -438,7 +438,7 @@ NOTE: **Note:** Environment-specific resources are only created if your cluster is [managed by GitLab](#gitlab-managed-clusters). NOTE: **Note:** -If your project was created before GitLab 12.2 it will use a single namespace for all project environments. +If your cluster was created before GitLab 12.2, it will use a single namespace for all project environments. #### Security of GitLab Runners @@ -468,7 +468,7 @@ If you don't want to use GitLab Runner in privileged mode, either: When adding more than one Kubernetes cluster to your project, you need to differentiate them with an environment scope. The environment scope associates clusters with [environments](../../../ci/environments.md) similar to how the -[environment-specific variables](../../../ci/variables/README.md#limiting-environment-scopes-of-environment-variables-premium) work. +[environment-specific variables](../../../ci/variables/README.md#limiting-environment-scopes-of-environment-variables) work. The default environment scope is `*`, which means all jobs, regardless of their environment, will use that cluster. Each scope can only be used by a single @@ -652,6 +652,9 @@ NOTE: **NOTE:** Prior to GitLab 11.5, `KUBE_TOKEN` was the Kubernetes token of the main service account of the cluster integration. +NOTE: **Note:** +If your cluster was created before GitLab 12.2, default `KUBE_NAMESPACE` will be set to `<project_name>-<project_id>`. + ### Troubleshooting Before the deployment jobs starts, GitLab creates the following specifically for diff --git a/doc/user/project/clusters/serverless/img/dns-entry.png b/doc/user/project/clusters/serverless/img/dns-entry.png Binary files differindex 351e40b77d5..7b5d6497f0e 100644 --- a/doc/user/project/clusters/serverless/img/dns-entry.png +++ b/doc/user/project/clusters/serverless/img/dns-entry.png diff --git a/doc/user/project/clusters/serverless/img/function-details-loaded.png b/doc/user/project/clusters/serverless/img/function-details-loaded.png Binary files differindex 34465c5c087..2f0d61f8032 100644 --- a/doc/user/project/clusters/serverless/img/function-details-loaded.png +++ b/doc/user/project/clusters/serverless/img/function-details-loaded.png diff --git a/doc/user/project/clusters/serverless/img/function-endpoint.png b/doc/user/project/clusters/serverless/img/function-endpoint.png Binary files differindex f3c7ae7a00d..a38fe2cb6c2 100644 --- a/doc/user/project/clusters/serverless/img/function-endpoint.png +++ b/doc/user/project/clusters/serverless/img/function-endpoint.png diff --git a/doc/user/project/clusters/serverless/img/function-execution.png b/doc/user/project/clusters/serverless/img/function-execution.png Binary files differindex 93b0b6d802d..f60dd277081 100644 --- a/doc/user/project/clusters/serverless/img/function-execution.png +++ b/doc/user/project/clusters/serverless/img/function-execution.png diff --git a/doc/user/project/clusters/serverless/img/install-knative.png b/doc/user/project/clusters/serverless/img/install-knative.png Binary files differindex ecc2f8fb481..1dc830848f2 100644 --- a/doc/user/project/clusters/serverless/img/install-knative.png +++ b/doc/user/project/clusters/serverless/img/install-knative.png diff --git a/doc/user/project/clusters/serverless/img/serverless-page.png b/doc/user/project/clusters/serverless/img/serverless-page.png Binary files differindex a872fda7740..8dce3cb1f70 100644 --- a/doc/user/project/clusters/serverless/img/serverless-page.png +++ b/doc/user/project/clusters/serverless/img/serverless-page.png diff --git a/doc/user/project/deploy_tokens/img/deploy_tokens.png b/doc/user/project/deploy_tokens/img/deploy_tokens.png Binary files differindex 421aa1ab3e5..493de8e0fce 100644 --- a/doc/user/project/deploy_tokens/img/deploy_tokens.png +++ b/doc/user/project/deploy_tokens/img/deploy_tokens.png diff --git a/doc/user/project/img/cycle_analytics_landing_page.png b/doc/user/project/img/cycle_analytics_landing_page.png Binary files differindex cf46098b9a4..c0c07e84a82 100644 --- a/doc/user/project/img/cycle_analytics_landing_page.png +++ b/doc/user/project/img/cycle_analytics_landing_page.png diff --git a/doc/user/project/img/deploy_boards_kubernetes_label.png b/doc/user/project/img/deploy_boards_kubernetes_label.png Binary files differindex 19785c74d07..130ff2caa8a 100644 --- a/doc/user/project/img/deploy_boards_kubernetes_label.png +++ b/doc/user/project/img/deploy_boards_kubernetes_label.png diff --git a/doc/user/project/img/file_lock.png b/doc/user/project/img/file_lock.png Binary files differindex 82699a12ffd..e881442630b 100644 --- a/doc/user/project/img/file_lock.png +++ b/doc/user/project/img/file_lock.png diff --git a/doc/user/project/img/file_lock_merge_request_error_message.png b/doc/user/project/img/file_lock_merge_request_error_message.png Binary files differindex 4ef04b15bef..64bcc86ac0d 100644 --- a/doc/user/project/img/file_lock_merge_request_error_message.png +++ b/doc/user/project/img/file_lock_merge_request_error_message.png diff --git a/doc/user/project/img/file_lock_repository_view.png b/doc/user/project/img/file_lock_repository_view.png Binary files differindex a2cab0decab..ced14198da9 100644 --- a/doc/user/project/img/file_lock_repository_view.png +++ b/doc/user/project/img/file_lock_repository_view.png diff --git a/doc/user/project/img/issue_boards_multiple.png b/doc/user/project/img/issue_boards_multiple.png Binary files differindex 4b1a8356dc9..e6183360610 100644 --- a/doc/user/project/img/issue_boards_multiple.png +++ b/doc/user/project/img/issue_boards_multiple.png diff --git a/doc/user/project/img/labels_default_v12_1.png b/doc/user/project/img/labels_default_v12_1.png Binary files differindex 4a0a6ba9ce0..b36b5dac80b 100644 --- a/doc/user/project/img/labels_default_v12_1.png +++ b/doc/user/project/img/labels_default_v12_1.png diff --git a/doc/user/project/img/labels_delete_v12_1.png b/doc/user/project/img/labels_delete_v12_1.png Binary files differindex 2e0ea934519..566e0519fbe 100644 --- a/doc/user/project/img/labels_delete_v12_1.png +++ b/doc/user/project/img/labels_delete_v12_1.png diff --git a/doc/user/project/img/labels_epic_sidebar_v12_1.png b/doc/user/project/img/labels_epic_sidebar_v12_1.png Binary files differindex 7e05c6d1ce4..13fbab33e26 100644 --- a/doc/user/project/img/labels_epic_sidebar_v12_1.png +++ b/doc/user/project/img/labels_epic_sidebar_v12_1.png diff --git a/doc/user/project/img/labels_generate_default_v12_1.png b/doc/user/project/img/labels_generate_default_v12_1.png Binary files differindex 48adc9a5699..cbdda2ab4dd 100644 --- a/doc/user/project/img/labels_generate_default_v12_1.png +++ b/doc/user/project/img/labels_generate_default_v12_1.png diff --git a/doc/user/project/img/labels_group_issues_v12_1.png b/doc/user/project/img/labels_group_issues_v12_1.png Binary files differindex bfe425f20ac..3f8f93c1dfd 100644 --- a/doc/user/project/img/labels_group_issues_v12_1.png +++ b/doc/user/project/img/labels_group_issues_v12_1.png diff --git a/doc/user/project/img/labels_key_value_v12_1.png b/doc/user/project/img/labels_key_value_v12_1.png Binary files differindex 81d5c416c95..52906b7b458 100644 --- a/doc/user/project/img/labels_key_value_v12_1.png +++ b/doc/user/project/img/labels_key_value_v12_1.png diff --git a/doc/user/project/img/labels_list_v12_1.png b/doc/user/project/img/labels_list_v12_1.png Binary files differindex c414ab42fbc..47359d05f7f 100644 --- a/doc/user/project/img/labels_list_v12_1.png +++ b/doc/user/project/img/labels_list_v12_1.png diff --git a/doc/user/project/img/labels_prioritized_v12_1.png b/doc/user/project/img/labels_prioritized_v12_1.png Binary files differindex 8ea69949d8e..512c5d59a5a 100644 --- a/doc/user/project/img/labels_prioritized_v12_1.png +++ b/doc/user/project/img/labels_prioritized_v12_1.png diff --git a/doc/user/project/img/labels_promotion_v12_1.png b/doc/user/project/img/labels_promotion_v12_1.png Binary files differindex 238d50981ae..7cd8ff24e02 100644 --- a/doc/user/project/img/labels_promotion_v12_1.png +++ b/doc/user/project/img/labels_promotion_v12_1.png diff --git a/doc/user/project/img/labels_subscriptions_v12_1.png b/doc/user/project/img/labels_subscriptions_v12_1.png Binary files differindex 57c437ac8a4..fa83b7db414 100644 --- a/doc/user/project/img/labels_subscriptions_v12_1.png +++ b/doc/user/project/img/labels_subscriptions_v12_1.png diff --git a/doc/user/project/img/service_desk_disabled.png b/doc/user/project/img/service_desk_disabled.png Binary files differindex 3ae64dcbe96..edae7e76986 100644 --- a/doc/user/project/img/service_desk_disabled.png +++ b/doc/user/project/img/service_desk_disabled.png diff --git a/doc/user/project/img/service_desk_enabled.png b/doc/user/project/img/service_desk_enabled.png Binary files differindex 329348e4b52..9c143ff58cd 100644 --- a/doc/user/project/img/service_desk_enabled.png +++ b/doc/user/project/img/service_desk_enabled.png diff --git a/doc/user/project/img/service_desk_issue_tracker.png b/doc/user/project/img/service_desk_issue_tracker.png Binary files differindex 485751fab0a..02d18c9debb 100644 --- a/doc/user/project/img/service_desk_issue_tracker.png +++ b/doc/user/project/img/service_desk_issue_tracker.png diff --git a/doc/user/project/insights/img/insights_example_bar_chart.png b/doc/user/project/insights/img/insights_example_bar_chart.png Binary files differindex eb96eb4a90f..5fd24265b32 100644 --- a/doc/user/project/insights/img/insights_example_bar_chart.png +++ b/doc/user/project/insights/img/insights_example_bar_chart.png diff --git a/doc/user/project/insights/img/insights_example_bar_time_series_chart.png b/doc/user/project/insights/img/insights_example_bar_time_series_chart.png Binary files differindex 28aa81939d8..2e32df3e2b1 100644 --- a/doc/user/project/insights/img/insights_example_bar_time_series_chart.png +++ b/doc/user/project/insights/img/insights_example_bar_time_series_chart.png diff --git a/doc/user/project/insights/img/insights_example_line_chart.png b/doc/user/project/insights/img/insights_example_line_chart.png Binary files differindex 131dbedd35e..9460b104339 100644 --- a/doc/user/project/insights/img/insights_example_line_chart.png +++ b/doc/user/project/insights/img/insights_example_line_chart.png diff --git a/doc/user/project/insights/img/insights_example_pie_chart.png b/doc/user/project/insights/img/insights_example_pie_chart.png Binary files differindex 518ed7338f9..3480bce6738 100644 --- a/doc/user/project/insights/img/insights_example_pie_chart.png +++ b/doc/user/project/insights/img/insights_example_pie_chart.png diff --git a/doc/user/project/insights/img/insights_example_stacked_bar_chart.png b/doc/user/project/insights/img/insights_example_stacked_bar_chart.png Binary files differindex aafec4b394e..a3fb50488f5 100644 --- a/doc/user/project/insights/img/insights_example_stacked_bar_chart.png +++ b/doc/user/project/insights/img/insights_example_stacked_bar_chart.png diff --git a/doc/user/project/insights/img/insights_sidebar_link.png b/doc/user/project/insights/img/insights_sidebar_link.png Binary files differindex aadb5745992..9fc449baf68 100644 --- a/doc/user/project/insights/img/insights_sidebar_link.png +++ b/doc/user/project/insights/img/insights_sidebar_link.png diff --git a/doc/user/project/insights/img/project_insights.png b/doc/user/project/insights/img/project_insights.png Binary files differindex 2d0292dda54..83674c94110 100644 --- a/doc/user/project/insights/img/project_insights.png +++ b/doc/user/project/insights/img/project_insights.png diff --git a/doc/user/project/integrations/img/github_configuration.png b/doc/user/project/integrations/img/github_configuration.png Binary files differindex 9f2d47921c7..5798b826681 100644 --- a/doc/user/project/integrations/img/github_configuration.png +++ b/doc/user/project/integrations/img/github_configuration.png diff --git a/doc/user/project/integrations/img/jira_add_user_to_group.png b/doc/user/project/integrations/img/jira_add_user_to_group.png Binary files differindex d8cf541a81e..b63a851a987 100644 --- a/doc/user/project/integrations/img/jira_add_user_to_group.png +++ b/doc/user/project/integrations/img/jira_add_user_to_group.png diff --git a/doc/user/project/integrations/img/jira_added_user_to_group.png b/doc/user/project/integrations/img/jira_added_user_to_group.png Binary files differindex b3e29a65d6e..f5120a8d42e 100644 --- a/doc/user/project/integrations/img/jira_added_user_to_group.png +++ b/doc/user/project/integrations/img/jira_added_user_to_group.png diff --git a/doc/user/project/integrations/img/jira_api_token.png b/doc/user/project/integrations/img/jira_api_token.png Binary files differindex 29689271bf7..d9d37713a4d 100644 --- a/doc/user/project/integrations/img/jira_api_token.png +++ b/doc/user/project/integrations/img/jira_api_token.png diff --git a/doc/user/project/integrations/img/jira_api_token_menu.png b/doc/user/project/integrations/img/jira_api_token_menu.png Binary files differindex 1aca1d78f36..a10a59a243d 100644 --- a/doc/user/project/integrations/img/jira_api_token_menu.png +++ b/doc/user/project/integrations/img/jira_api_token_menu.png diff --git a/doc/user/project/integrations/img/jira_create_new_group.png b/doc/user/project/integrations/img/jira_create_new_group.png Binary files differindex 84be3a94a45..4ab7a5eae4e 100644 --- a/doc/user/project/integrations/img/jira_create_new_group.png +++ b/doc/user/project/integrations/img/jira_create_new_group.png diff --git a/doc/user/project/integrations/img/jira_create_new_user.png b/doc/user/project/integrations/img/jira_create_new_user.png Binary files differindex 8460dc98ef9..c74933298e3 100644 --- a/doc/user/project/integrations/img/jira_create_new_user.png +++ b/doc/user/project/integrations/img/jira_create_new_user.png diff --git a/doc/user/project/integrations/img/jira_group_access.png b/doc/user/project/integrations/img/jira_group_access.png Binary files differindex 58cf114bd55..e33f2eed242 100644 --- a/doc/user/project/integrations/img/jira_group_access.png +++ b/doc/user/project/integrations/img/jira_group_access.png diff --git a/doc/user/project/integrations/img/jira_issue_reference.png b/doc/user/project/integrations/img/jira_issue_reference.png Binary files differindex a3e80c1b054..db8bc4f0bb9 100644 --- a/doc/user/project/integrations/img/jira_issue_reference.png +++ b/doc/user/project/integrations/img/jira_issue_reference.png diff --git a/doc/user/project/integrations/img/jira_merge_request_close.png b/doc/user/project/integrations/img/jira_merge_request_close.png Binary files differindex 1c089c94207..9a176daf5f4 100644 --- a/doc/user/project/integrations/img/jira_merge_request_close.png +++ b/doc/user/project/integrations/img/jira_merge_request_close.png diff --git a/doc/user/project/integrations/img/jira_service_page.png b/doc/user/project/integrations/img/jira_service_page.png Binary files differindex 80dd65ea24e..76fd5f4641c 100644 --- a/doc/user/project/integrations/img/jira_service_page.png +++ b/doc/user/project/integrations/img/jira_service_page.png diff --git a/doc/user/project/integrations/img/jira_user_management_link.png b/doc/user/project/integrations/img/jira_user_management_link.png Binary files differindex 43ef18da6c8..caecd1d71fc 100644 --- a/doc/user/project/integrations/img/jira_user_management_link.png +++ b/doc/user/project/integrations/img/jira_user_management_link.png diff --git a/doc/user/project/integrations/img/mattermost_configuration.png b/doc/user/project/integrations/img/mattermost_configuration.png Binary files differindex 75ef0310f2d..6abf5c5c8d6 100644 --- a/doc/user/project/integrations/img/mattermost_configuration.png +++ b/doc/user/project/integrations/img/mattermost_configuration.png diff --git a/doc/user/project/integrations/img/prometheus_dashboard_area_panel_type.png b/doc/user/project/integrations/img/prometheus_dashboard_area_panel_type.png Binary files differindex 7260b11f07b..9fdb6dfddac 100644 --- a/doc/user/project/integrations/img/prometheus_dashboard_area_panel_type.png +++ b/doc/user/project/integrations/img/prometheus_dashboard_area_panel_type.png diff --git a/doc/user/project/integrations/img/prometheus_dashboard_single_stat_panel_type.png b/doc/user/project/integrations/img/prometheus_dashboard_single_stat_panel_type.png Binary files differindex ce4c54f909d..2d7dfb27b49 100644 --- a/doc/user/project/integrations/img/prometheus_dashboard_single_stat_panel_type.png +++ b/doc/user/project/integrations/img/prometheus_dashboard_single_stat_panel_type.png diff --git a/doc/user/project/integrations/img/prometheus_service_alerts.png b/doc/user/project/integrations/img/prometheus_service_alerts.png Binary files differindex a81dfe7da14..609c5e5196c 100644 --- a/doc/user/project/integrations/img/prometheus_service_alerts.png +++ b/doc/user/project/integrations/img/prometheus_service_alerts.png diff --git a/doc/user/project/integrations/img/slack_configuration.png b/doc/user/project/integrations/img/slack_configuration.png Binary files differindex a14d2969488..10d2cda6dc7 100644 --- a/doc/user/project/integrations/img/slack_configuration.png +++ b/doc/user/project/integrations/img/slack_configuration.png diff --git a/doc/user/project/issues/img/adding_note_to_design_1.png b/doc/user/project/issues/img/adding_note_to_design_1.png Binary files differindex dcc23b4d034..aa50bbb69ce 100644 --- a/doc/user/project/issues/img/adding_note_to_design_1.png +++ b/doc/user/project/issues/img/adding_note_to_design_1.png diff --git a/doc/user/project/issues/img/adding_note_to_design_2.png b/doc/user/project/issues/img/adding_note_to_design_2.png Binary files differindex c6a868bfb51..37cefeb1a15 100644 --- a/doc/user/project/issues/img/adding_note_to_design_2.png +++ b/doc/user/project/issues/img/adding_note_to_design_2.png diff --git a/doc/user/project/issues/img/comment-or-discussion.png b/doc/user/project/issues/img/comment-or-discussion.png Binary files differindex ccecc9fa39f..a29014c984c 100644 --- a/doc/user/project/issues/img/comment-or-discussion.png +++ b/doc/user/project/issues/img/comment-or-discussion.png diff --git a/doc/user/project/issues/img/confidential_mr_branch_dropdown_v12_1.png b/doc/user/project/issues/img/confidential_mr_branch_dropdown_v12_1.png Binary files differindex 7c24226a6c4..1f4ad5c42bb 100644 --- a/doc/user/project/issues/img/confidential_mr_branch_dropdown_v12_1.png +++ b/doc/user/project/issues/img/confidential_mr_branch_dropdown_v12_1.png diff --git a/doc/user/project/issues/img/confidential_mr_dropdown_v12_1.png b/doc/user/project/issues/img/confidential_mr_dropdown_v12_1.png Binary files differindex d6d391c6dd9..7b7bd599a71 100644 --- a/doc/user/project/issues/img/confidential_mr_dropdown_v12_1.png +++ b/doc/user/project/issues/img/confidential_mr_dropdown_v12_1.png diff --git a/doc/user/project/issues/img/create_mr_from_issue.png b/doc/user/project/issues/img/create_mr_from_issue.png Binary files differindex 680c42b5df0..d05a678cd17 100644 --- a/doc/user/project/issues/img/create_mr_from_issue.png +++ b/doc/user/project/issues/img/create_mr_from_issue.png diff --git a/doc/user/project/issues/img/design_management_v12_2.png b/doc/user/project/issues/img/design_management_v12_2.png Binary files differindex 6da747a3f21..ad803df4e63 100644 --- a/doc/user/project/issues/img/design_management_v12_2.png +++ b/doc/user/project/issues/img/design_management_v12_2.png diff --git a/doc/user/project/issues/img/issues_main_view_numbered.png b/doc/user/project/issues/img/issues_main_view_numbered.png Binary files differindex 16cb6b497b0..92b9df44972 100644 --- a/doc/user/project/issues/img/issues_main_view_numbered.png +++ b/doc/user/project/issues/img/issues_main_view_numbered.png diff --git a/doc/user/project/issues/img/reopen-issue.png b/doc/user/project/issues/img/reopen-issue.png Binary files differindex 1749be31239..fc48742afe0 100644 --- a/doc/user/project/issues/img/reopen-issue.png +++ b/doc/user/project/issues/img/reopen-issue.png diff --git a/doc/user/project/issues/img/report-abuse.png b/doc/user/project/issues/img/report-abuse.png Binary files differindex f189cbf1d36..f8cef22da03 100644 --- a/doc/user/project/issues/img/report-abuse.png +++ b/doc/user/project/issues/img/report-abuse.png diff --git a/doc/user/project/issues/img/show-all-activity.png b/doc/user/project/issues/img/show-all-activity.png Binary files differindex c43ba75ce25..55c6f5ab5db 100644 --- a/doc/user/project/issues/img/show-all-activity.png +++ b/doc/user/project/issues/img/show-all-activity.png diff --git a/doc/user/project/merge_requests/blocking_merge_requests.md b/doc/user/project/merge_requests/blocking_merge_requests.md deleted file mode 100644 index 0506a7cb4a5..00000000000 --- a/doc/user/project/merge_requests/blocking_merge_requests.md +++ /dev/null @@ -1,133 +0,0 @@ ---- -type: reference, concepts ---- - -# Blocking merge requests **(PREMIUM)** - -> Introduced in GitLab Premium 12.2 - -Blocking merge requests allow dependencies between MRs to be expressed. If a -merge request is blocked by another MR, it cannot be merged until that blocking -MR is itself merged. - -NOTE: **Note:** -Blocking merge requests are a **PREMIUM** feature, but this restriction is only -enforced for the blocked merge request. A merge request in a **CORE** or -**STARTER** project can block a **PREMIUM** merge request, but not vice-versa. - -## Use cases - -* Ensure changes to a library are merged before changes to a project that - imports the library -* Prevent a documentation-only merge request from being merged before the MR - implementing the feature to be documented -* Require an MR updating a permissions matrix to be merged before merging an - MR from someone who hasn't yet been granted permissions - -It is common for a single logical change to span several merge requests. These -MRs may all be in a single project, or they may be spread out across multiple -projects, and the order in which they are merged can be significant. - -For example, given a project `mycorp/awesome-project` that imports a library -at `myfriend/awesome-lib`, adding a feature in `awesome-project` may **also** -require changes to `awesome-lib`, and so necessitate two merge requests. Merging -the `awesome-project` MR before the `awesome-lib` one would break the `master` -branch. - -The `awesome-project` MR could be [marked as WIP](work_in_progress_merge_requests.md), -and the reason for the WIP stated included in the comments. However, this -requires the state of the `awesome-lib` MR to be manually tracked, and doesn't -scale well if the `awesome-project` MR depends on changes to **several** other -projects. - -By marking the `awesome-project` MR as blocked on the `awesome-lib` MR instead, -the status of the dependency is automatically tracked by GitLab, and the WIP -state can be used to communicate the readiness of the code in each individual -MR instead. - -## Configuration - -To continue the above example, you can configure a block when creating the -new MR in `awesome-project` (or by editing it, if it already exists). The block -needs to be configured on the MR that will be **blocked**, rather than on the -**blocking** MR. There is a "Blocking merge requests" section in the form: - -![Blocking merge requests form control](img/edit_blocking_merge_requests.png) - -Anyone who can edit a merge request can change the list of blocking merge -requests. - -New blocks can be added by reference, by URL, or by using autcompletion. To -remove a block, press the "X" by its reference. - -As blocks can be specified across projects, it's possible that someone else has -added a block for a merge request in a project you don't have access to. These -are shown as a simple count: - -![Blocking merge requests form control with inaccessible MRs](img/edit_blocking_merge_requests_inaccessible.png) - -If necessary, you can remove all the blocks like this by pressing the "X", just -as you would for a single, visible block. - -Once you're finished, press the "Save changes" button to submit the request, or -"Cancel" to return without making any changes. - -The list of configured blocks, and the status of each one, is shown in the merge -request widget: - -![Blocking merge requests in merge request widget](img/show_blocking_merge_requests_in_mr_widget.png) - -Until all blocking merge requests have, themselves, been merged, the "Merge" -button will be disabled. In particular, note that **closed** merge requests -still block their dependents - it is impossible to automatically determine if -merge requests that were blocked by that MR when it was open, are still blocked -when it is closed. - -If a merge request has been closed **and** the block is no longer relevant, it -must be removed as a blocking MR, following the instructions above, before -merge. - -## Limitations - -* API support: [gitlab-ee#12551](https://gitlab.com/gitlab-org/gitlab-ee/issues/12551) -* Blocking relationships are not preserved across project export/import: [gitlab-ee#12549](https://gitlab.com/gitlab-org/gitlab-ee/issues/12549) -* Complex merge order dependencies are not supported: [gitlab-ee#11393](https://gitlab.com/gitlab-org/gitlab-ee/issues/11393) - -The last item merits a little more explanation. Blocking merge requests can be -described as a graph of dependencies. The simplest possible graph has one -merge request blocking another: - -```mermaid -graph LR; - myfriend/awesome-lib!10-->mycorp/awesome-project!100; -``` - -A more complex (and still supported) graph might have several MRs blocking -another from being merged: - -```mermaid -graph LR; - myfriend/awesome-lib!10-->mycorp/awesome-project!100; - herfriend/another-lib!1-->mycorp/awesome-project!100; -``` - -We also support one MR blocking several others from being merged: - -```mermaid -graph LR; - herfriend/another-lib!1-->myfriend/awesome-lib!10; - herfriend/another-lib!1-->mycorp/awesome-project!100; -``` - -What is **not** supported is a "deep", or "nested" graph of dependencies, e.g.: - -```mermaid -graph LR; - herfriend/another-lib!1-->myfriend/awesome-lib!10; - myfriend/awesome-lib!10-->mycorp/awesome-project!100; -``` - -In this example, `myfriend/awesome-lib!10` would be blocked from being merged by -`herfriend/another-lib!1`, and would also block `mycorp/awesome-project!100` -from being merged. This is **not** yet supported. - diff --git a/doc/user/project/merge_requests/img/allow_collaboration.png b/doc/user/project/merge_requests/img/allow_collaboration.png Binary files differindex e40e8a6b11c..cc13493646d 100644 --- a/doc/user/project/merge_requests/img/allow_collaboration.png +++ b/doc/user/project/merge_requests/img/allow_collaboration.png diff --git a/doc/user/project/merge_requests/img/allow_collaboration_after_save.png b/doc/user/project/merge_requests/img/allow_collaboration_after_save.png Binary files differindex 4ba4c84c8c5..bc7678b21ec 100644 --- a/doc/user/project/merge_requests/img/allow_collaboration_after_save.png +++ b/doc/user/project/merge_requests/img/allow_collaboration_after_save.png diff --git a/doc/user/project/merge_requests/img/approvals_premium_mr_widget.png b/doc/user/project/merge_requests/img/approvals_premium_mr_widget.png Binary files differindex b6dc86f312e..2598cc71c33 100644 --- a/doc/user/project/merge_requests/img/approvals_premium_mr_widget.png +++ b/doc/user/project/merge_requests/img/approvals_premium_mr_widget.png diff --git a/doc/user/project/merge_requests/img/approvals_premium_project_edit.png b/doc/user/project/merge_requests/img/approvals_premium_project_edit.png Binary files differindex b6f6188b9cd..6a09412533f 100644 --- a/doc/user/project/merge_requests/img/approvals_premium_project_edit.png +++ b/doc/user/project/merge_requests/img/approvals_premium_project_edit.png diff --git a/doc/user/project/merge_requests/img/approvals_starter_project_edit.png b/doc/user/project/merge_requests/img/approvals_starter_project_edit.png Binary files differindex 868b9d58740..4c554d846cc 100644 --- a/doc/user/project/merge_requests/img/approvals_starter_project_edit.png +++ b/doc/user/project/merge_requests/img/approvals_starter_project_edit.png diff --git a/doc/user/project/merge_requests/img/approvals_starter_project_empty.png b/doc/user/project/merge_requests/img/approvals_starter_project_empty.png Binary files differindex 7375820224c..fc88a59a745 100644 --- a/doc/user/project/merge_requests/img/approvals_starter_project_empty.png +++ b/doc/user/project/merge_requests/img/approvals_starter_project_empty.png diff --git a/doc/user/project/merge_requests/img/approve.png b/doc/user/project/merge_requests/img/approve.png Binary files differindex e68259ac5c2..e2641f48c7a 100644 --- a/doc/user/project/merge_requests/img/approve.png +++ b/doc/user/project/merge_requests/img/approve.png diff --git a/doc/user/project/merge_requests/img/approve_additionally.png b/doc/user/project/merge_requests/img/approve_additionally.png Binary files differindex 3db5a9159e5..bab0cd4e041 100644 --- a/doc/user/project/merge_requests/img/approve_additionally.png +++ b/doc/user/project/merge_requests/img/approve_additionally.png diff --git a/doc/user/project/merge_requests/img/create_from_email.png b/doc/user/project/merge_requests/img/create_from_email.png Binary files differindex 5cb2afaf976..14eef473e27 100644 --- a/doc/user/project/merge_requests/img/create_from_email.png +++ b/doc/user/project/merge_requests/img/create_from_email.png diff --git a/doc/user/project/merge_requests/img/cross-project-dependencies-edit-inaccessible.png b/doc/user/project/merge_requests/img/cross-project-dependencies-edit-inaccessible.png Binary files differnew file mode 100644 index 00000000000..2dc02634fd8 --- /dev/null +++ b/doc/user/project/merge_requests/img/cross-project-dependencies-edit-inaccessible.png diff --git a/doc/user/project/merge_requests/img/cross-project-dependencies-edit.png b/doc/user/project/merge_requests/img/cross-project-dependencies-edit.png Binary files differnew file mode 100644 index 00000000000..362e7e0ead2 --- /dev/null +++ b/doc/user/project/merge_requests/img/cross-project-dependencies-edit.png diff --git a/doc/user/project/merge_requests/img/cross-project-dependencies-view.png b/doc/user/project/merge_requests/img/cross-project-dependencies-view.png Binary files differnew file mode 100644 index 00000000000..e00231c839b --- /dev/null +++ b/doc/user/project/merge_requests/img/cross-project-dependencies-view.png diff --git a/doc/user/project/merge_requests/img/edit_blocking_merge_requests.png b/doc/user/project/merge_requests/img/edit_blocking_merge_requests.png Binary files differdeleted file mode 100644 index 0fe26d602e3..00000000000 --- a/doc/user/project/merge_requests/img/edit_blocking_merge_requests.png +++ /dev/null diff --git a/doc/user/project/merge_requests/img/edit_blocking_merge_requests_inaccessible.png b/doc/user/project/merge_requests/img/edit_blocking_merge_requests_inaccessible.png Binary files differdeleted file mode 100644 index a1003c41c22..00000000000 --- a/doc/user/project/merge_requests/img/edit_blocking_merge_requests_inaccessible.png +++ /dev/null diff --git a/doc/user/project/merge_requests/img/filter_approver_merge_requests.png b/doc/user/project/merge_requests/img/filter_approver_merge_requests.png Binary files differindex 9c386391a4f..4c28ee17f47 100644 --- a/doc/user/project/merge_requests/img/filter_approver_merge_requests.png +++ b/doc/user/project/merge_requests/img/filter_approver_merge_requests.png diff --git a/doc/user/project/merge_requests/img/filter_wip_merge_requests.png b/doc/user/project/merge_requests/img/filter_wip_merge_requests.png Binary files differindex 8df6a3c9a29..0989b41e2a4 100644 --- a/doc/user/project/merge_requests/img/filter_wip_merge_requests.png +++ b/doc/user/project/merge_requests/img/filter_wip_merge_requests.png diff --git a/doc/user/project/merge_requests/img/merge_when_pipeline_succeeds_only_if_succeeds_settings.png b/doc/user/project/merge_requests/img/merge_when_pipeline_succeeds_only_if_succeeds_settings.png Binary files differindex ea3aff59aa1..ed374b11fbd 100644 --- a/doc/user/project/merge_requests/img/merge_when_pipeline_succeeds_only_if_succeeds_settings.png +++ b/doc/user/project/merge_requests/img/merge_when_pipeline_succeeds_only_if_succeeds_settings.png diff --git a/doc/user/project/merge_requests/img/multiple_assignees_for_merge_requests_sidebar.png b/doc/user/project/merge_requests/img/multiple_assignees_for_merge_requests_sidebar.png Binary files differindex 9ae6e350798..dde2680ed74 100644 --- a/doc/user/project/merge_requests/img/multiple_assignees_for_merge_requests_sidebar.png +++ b/doc/user/project/merge_requests/img/multiple_assignees_for_merge_requests_sidebar.png diff --git a/doc/user/project/merge_requests/img/remove_approval.png b/doc/user/project/merge_requests/img/remove_approval.png Binary files differindex 6083e1745ef..b178d26cf85 100644 --- a/doc/user/project/merge_requests/img/remove_approval.png +++ b/doc/user/project/merge_requests/img/remove_approval.png diff --git a/doc/user/project/merge_requests/img/show_blocking_merge_requests_in_mr_widget.png b/doc/user/project/merge_requests/img/show_blocking_merge_requests_in_mr_widget.png Binary files differdeleted file mode 100644 index a1241f9e38c..00000000000 --- a/doc/user/project/merge_requests/img/show_blocking_merge_requests_in_mr_widget.png +++ /dev/null diff --git a/doc/user/project/merge_requests/img/squash_mr_message.png b/doc/user/project/merge_requests/img/squash_mr_message.png Binary files differindex 8734cab29aa..8c7dc7886f7 100644 --- a/doc/user/project/merge_requests/img/squash_mr_message.png +++ b/doc/user/project/merge_requests/img/squash_mr_message.png diff --git a/doc/user/project/merge_requests/img/wip_blocked_accept_button.png b/doc/user/project/merge_requests/img/wip_blocked_accept_button.png Binary files differindex b6d38d85165..ab2c8425b83 100644 --- a/doc/user/project/merge_requests/img/wip_blocked_accept_button.png +++ b/doc/user/project/merge_requests/img/wip_blocked_accept_button.png diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md index f78ec9d96e6..7ff30d1b813 100644 --- a/doc/user/project/merge_requests/index.md +++ b/doc/user/project/merge_requests/index.md @@ -47,7 +47,7 @@ With **[GitLab Enterprise Edition][ee]**, you can also: - Analyze your dependencies for vulnerabilities with [Dependency Scanning](../../application_security/dependency_scanning/index.md) **(ULTIMATE)** - Analyze your Docker images for vulnerabilities with [Container Scanning](../../application_security/container_scanning/index.md) **(ULTIMATE)** - Determine the performance impact of changes with [Browser Performance Testing](#browser-performance-testing-premium) **(PREMIUM)** -- Specify merge order dependencies with [Blocking Merge Requests](#blocking-merge-requests-premium) **(PREMIUM)** +- Specify merge order dependencies with [Cross-project Merge Request Dependencies](#cross-project-merge-request-dependencies-premium) **(PREMIUM)** ## Use cases @@ -451,20 +451,20 @@ GitLab runs the [Sitespeed.io container][sitespeed-container] and displays the d [Read more about Browser Performance Testing.](browser_performance_testing.md) -## Blocking Merge Requests **(PREMIUM)** +## Cross-project Merge Request Dependencies **(PREMIUM)** > Introduced in [GitLab Premium][products] 12.2. -A single logical change may be split across several merge requests, and perhaps -even across several projects. When this happens, the order in which MRs are -merged is important. +A single logical change may be split across several merge requests, across +several projects. When this happens, the order in which MRs are merged is +important. -GitLab allows you to specify that a merge request is blocked by other MRs. With +GitLab allows you to specify that a merge request depends on other MRs. With this relationship in place, the merge request cannot be merged until all of its -blockers have also been merged, helping to maintain the consistency of a single -logical change. +dependencies have also been merged, helping to maintain the consistency of a +single logical change. -[Read more about Blocking Merge Requests.](blocking_merge_requests.md) +[Read more about cross-project merge request dependencies.](merge_request_dependencies.md) ## Security reports **(ULTIMATE)** diff --git a/doc/user/project/merge_requests/merge_request_dependencies.md b/doc/user/project/merge_requests/merge_request_dependencies.md new file mode 100644 index 00000000000..45cb56dfb6b --- /dev/null +++ b/doc/user/project/merge_requests/merge_request_dependencies.md @@ -0,0 +1,143 @@ +--- +type: reference, concepts +--- + +# Cross-project merge request dependencies **(PREMIUM)** + +> Introduced in GitLab Premium 12.2 + +Cross-project merge request dependencies allows a required order of merging +between merge requests in different projects to be expressed. If a +merge request "depends on" another, then it cannot be merged until its +dependency is itself merged. + +NOTE: **Note:** +Merge requests dependencies are a **PREMIUM** feature, but this restriction is +only enforced for the dependent merge request. A merge request in a **CORE** or +**STARTER** project can be a dependency of a **PREMIUM** merge request, but not +vice-versa. + +NOTE: **Note:** +A merge request can only depend on merge requests in a different project. Two +merge requests in the same project cannot depend on each other. + +## Use cases + +* Ensure changes to a library are merged before changes to a project that + imports the library +* Prevent a documentation-only merge request from being merged before the merge request + implementing the feature to be documented +* Require an merge request updating a permissions matrix to be merged before merging an + merge request from someone who hasn't yet been granted permissions + +It is common for a single logical change to span several merge requests, spread +out across multiple projects, and the order in which they are merged can be +significant. + +For example, given a project `mycorp/awesome-project` that imports a library +at `myfriend/awesome-lib`, adding a feature in `awesome-project` may **also** +require changes to `awesome-lib`, and so necessitate two merge requests. Merging +the `awesome-project` merge request before the `awesome-lib` one would +break the `master`branch. + +The `awesome-project` merge request could be [marked as +WIP](work_in_progress_merge_requests.md), +and the reason for the WIP stated included in the comments. However, this +requires the state of the `awesome-lib` merge request to be manually +tracked, and doesn't scale well if the `awesome-project` merge request +depends on changes to **several** other projects. + +By making the `awesome-project` merge request depend on the +`awesome-lib` merge request instead, this relationship is +automatically tracked by GitLab, and the WIP state can be used to +communicate the readiness of the code in each individual merge request +instead. + +## Configuration + +To continue the above example, you can configure a dependency when creating the +new merge request in `awesome-project` (or by editing it, if it already exists). +The dependency needs to be configured on the **dependent** merge +request. There is a "Cross-project dependencies" section in the form: + +![Cross-project dependencies form control](img/cross-project-dependencies-edit.png) + +Anyone who can edit a merge request can change the list of dependencies. + +New dependencies can be added by reference, or by URL. To remove a dependency, +press the "X" by its reference. + +As dependencies are specified across projects, it's possible that someone else +has added a dependency for a merge request in a project you don't have access to. +These are shown as a simple count: + +![Cross-project dependencies form control with inaccessible merge requests](img/cross-project-dependencies-edit-inaccessible.png) + +If necessary, you can remove all the dependencies like this by pressing the "X", +just as you would for a single, visible dependency. + +Once you're finished, press the "Save changes" button to submit the request, or +"Cancel" to return without making any changes. + +The list of configured dependencies, and the status of each one, is shown in the +merge request widget: + +![Cross-project dependencies in merge request widget](img/cross-project-dependencies-view.png) + +Until all dependencies have, themselves, been merged, the "Merge" +button will be disabled for the dependent merge request. In +particular, note that **closed** merge request still prevent their +dependents from being merged - it is impossible to automatically +determine whether the dependency expressed by a closed merge request +has been satisfied in some other way or not. + +If a merge request has been closed **and** the dependency is no longer relevant, +it must be removed as a dependency, following the instructions above, before +merge. + +## Limitations + +* API support: [gitlab-ee#12551](https://gitlab.com/gitlab-org/gitlab-ee/issues/12551) +* Dependencies are not preserved across project export/import: [gitlab-ee#12549](https://gitlab.com/gitlab-org/gitlab-ee/issues/12549) +* Complex merge order dependencies are not supported: [gitlab-ee#11393](https://gitlab.com/gitlab-org/gitlab-ee/issues/11393) + +The last item merits a little more explanation. Dependencies between merge +requests can be described as a graph of relationships. The simplest possible +graph has one merge request that depends upon another: + +```mermaid +graph LR; + myfriend/awesome-lib!10-->mycorp/awesome-project!100; +``` + +A more complex (and still supported) graph might have one merge request that +directly depends upon several others: + +```mermaid +graph LR; + myfriend/awesome-lib!10-->mycorp/awesome-project!100; + herfriend/another-lib!1-->mycorp/awesome-project!100; +``` + +Several different merge requests can also directly depend upon the +same merge request: + + +```mermaid +graph LR; + herfriend/another-lib!1-->myfriend/awesome-lib!10; + herfriend/another-lib!1-->mycorp/awesome-project!100; +``` + +What is **not** supported is a "deep", or "nested" graph of dependencies, e.g.: + +```mermaid +graph LR; + herfriend/another-lib!1-->myfriend/awesome-lib!10; + myfriend/awesome-lib!10-->mycorp/awesome-project!100; +``` + +In this example, `myfriend/awesome-lib!10` depends on `herfriend/another-lib!1`, +and is itself a dependent of `mycorp/awesome-project!100`. This means that +`myfriend/awesome-lib!10` becomes an **indirect** dependency of +`mycorp/awesome-project!100`, which is not yet supported. diff --git a/doc/user/project/operations/feature_flags.md b/doc/user/project/operations/feature_flags.md index fdc1e978291..19ccde6e16a 100644 --- a/doc/user/project/operations/feature_flags.md +++ b/doc/user/project/operations/feature_flags.md @@ -74,9 +74,9 @@ To define specs for each environment: 1. Navigate to your project's **Operations > Feature Flags**. 1. Click on the **New Feature Flag** button or edit an existing flag. -1. Set the status of the default [spec](../../../ci/environments.md#scoping-environments-with-specs-premium) (`*`). This status will be used for _all_ environments. +1. Set the status of the default [spec](../../../ci/environments.md#scoping-environments-with-specs-premium) (`*`). Choose a rollout strategy. This status and rollout strategy combination will be used for _all_ environments. 1. If you want to enable/disable the feature on a specific environment, create a new [spec](../../../ci/environments.md#scoping-environments-with-specs-premium) and type the environment name. -1. Set the status of the additional spec. This status takes precedence over the default spec's status since we always use the most specific match available. +1. Set the status and rollout strategy of the additional spec. This status and rollout strategy combination takes precedence over the default spec since we always use the most specific match available. 1. Click **Create feature flag** or **Update feature flag**. ![Feature flag specs list](img/specs_list.png) @@ -85,6 +85,33 @@ NOTE: **NOTE** We'd highly recommend you to use the [Environment](../../../ci/environments.md) feature in order to quickly assess which flag is enabled per environment. +## Rollout Strategy + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/8240) in GitLab 12.2. + +The selected rollout strategy affects which users will experience the feature enabled. + +The status of an environment spec ultimately determines whether or not a feature is enabled at all. +For instance, a feature will always be disabled for every user if the matching environment spec has a disabled status, regardless of the chosen rollout strategy. +However, a feature will be enabled for 50% of logged-in users if the matching environment spec has an enabled status along with a **Percent rollout (logged in users)** strategy set to 50%. + +### All users + +Enables the feature for all users. + +**All users** is implemented using the Unleash [default](https://unleash.github.io/docs/activation_strategy#default) activation strategy. + +### Percent rollout (logged in users) + +**Percent rollout (logged in users)** enables the feature for a percentage of authenticated users. Set a value of 15%, for example, to enable the feature for 15% of authenticated users. + +A rollout percentage may be between 0% and 100%. + +CAUTION: **Caution:** +If this strategy is selected, then the Unleash client **must** be given a user id for the feature to be enabled. See the [Ruby example](#ruby-application-example) below. + +**Percent rollout (logged in users)** is implemented using the Unleash [gradualRolloutUserId](https://unleash.github.io/docs/activation_strategy#gradualrolloutuserid) activation strategy. + ## Integrating with your application In order to use Feature Flags, you need to first @@ -175,3 +202,34 @@ func main() { log.Fatal(http.ListenAndServe(":8080", nil)) } ``` + +### Ruby application example + +Here's an example of how to integrate the feature flags in a Ruby application. + +The Unleash client is given a user id for use with a **Percent rollout (logged in users)** rollout strategy. + +```ruby +#!/usr/bin/env ruby + +require 'unleash' +require 'unleash/context' + +unleash = Unleash::Client.new({ + url: 'http://gitlab.com/api/v4/feature_flags/unleash/42', + app_name: 'production', + instance_id: '29QmjsW6KngPR5JNPMWx' +}) + +unleash_context = Unleash::Context.new +# Replace "123" with the id of an authenticated user. +# Note that the context's user id must be a string: +# https://unleash.github.io/docs/unleash_context +unleash_context.user_id = "123" + +if unleash.is_enabled?("my_feature_name", unleash_context) + puts "Feature enabled" +else + puts "hello, world!" +end +``` diff --git a/doc/user/project/operations/img/error_tracking_list.png b/doc/user/project/operations/img/error_tracking_list.png Binary files differindex aa0f9867fdb..194c7b440a4 100644 --- a/doc/user/project/operations/img/error_tracking_list.png +++ b/doc/user/project/operations/img/error_tracking_list.png diff --git a/doc/user/project/operations/img/external_dashboard_link.png b/doc/user/project/operations/img/external_dashboard_link.png Binary files differindex 4fb8bce7cd0..82c5e05e467 100644 --- a/doc/user/project/operations/img/external_dashboard_link.png +++ b/doc/user/project/operations/img/external_dashboard_link.png diff --git a/doc/user/project/operations/img/external_dashboard_settings.png b/doc/user/project/operations/img/external_dashboard_settings.png Binary files differindex 8dc380f01e2..e1b7fa56a1c 100644 --- a/doc/user/project/operations/img/external_dashboard_settings.png +++ b/doc/user/project/operations/img/external_dashboard_settings.png diff --git a/doc/user/project/operations/img/feature_flags_list.png b/doc/user/project/operations/img/feature_flags_list.png Binary files differindex 5313a163fec..f3e85b9ce44 100644 --- a/doc/user/project/operations/img/feature_flags_list.png +++ b/doc/user/project/operations/img/feature_flags_list.png diff --git a/doc/user/project/operations/img/specs_list.png b/doc/user/project/operations/img/specs_list.png Binary files differindex 9630c907cfc..43d069c09ce 100644 --- a/doc/user/project/operations/img/specs_list.png +++ b/doc/user/project/operations/img/specs_list.png diff --git a/doc/user/project/packages/img/npm_package_view.png b/doc/user/project/packages/img/npm_package_view.png Binary files differindex 8baf7d0ef9f..e0634718c02 100644 --- a/doc/user/project/packages/img/npm_package_view.png +++ b/doc/user/project/packages/img/npm_package_view.png diff --git a/doc/user/project/pages/custom_domains_ssl_tls_certification/img/get_domain_verification_code_v12_0.png b/doc/user/project/pages/custom_domains_ssl_tls_certification/img/get_domain_verification_code_v12_0.png Binary files differindex b4dcdc9bb60..3b93572a8f1 100644 --- a/doc/user/project/pages/custom_domains_ssl_tls_certification/img/get_domain_verification_code_v12_0.png +++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/img/get_domain_verification_code_v12_0.png diff --git a/doc/user/project/pages/custom_domains_ssl_tls_certification/img/lets_encrypt_integration_v12_1.png b/doc/user/project/pages/custom_domains_ssl_tls_certification/img/lets_encrypt_integration_v12_1.png Binary files differindex 2e825e84d92..5eab04a61a9 100644 --- a/doc/user/project/pages/custom_domains_ssl_tls_certification/img/lets_encrypt_integration_v12_1.png +++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/img/lets_encrypt_integration_v12_1.png diff --git a/doc/user/project/pages/custom_domains_ssl_tls_certification/img/retry_domain_verification_v12_0.png b/doc/user/project/pages/custom_domains_ssl_tls_certification/img/retry_domain_verification_v12_0.png Binary files differindex db8f25bc6c3..5f2f100e883 100644 --- a/doc/user/project/pages/custom_domains_ssl_tls_certification/img/retry_domain_verification_v12_0.png +++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/img/retry_domain_verification_v12_0.png diff --git a/doc/user/project/pages/img/pages_project_templates_v11_8.png b/doc/user/project/pages/img/pages_project_templates_v11_8.png Binary files differindex a645d28260b..61cae88b5a8 100644 --- a/doc/user/project/pages/img/pages_project_templates_v11_8.png +++ b/doc/user/project/pages/img/pages_project_templates_v11_8.png diff --git a/doc/user/project/pages/img/remove_pages.png b/doc/user/project/pages/img/remove_pages.png Binary files differindex 60f76f15f93..d6c37ef30cd 100644 --- a/doc/user/project/pages/img/remove_pages.png +++ b/doc/user/project/pages/img/remove_pages.png diff --git a/doc/user/project/pipelines/img/pipeline_schedule_variables.png b/doc/user/project/pipelines/img/pipeline_schedule_variables.png Binary files differindex 29846206491..ce3c3dc6af1 100644 --- a/doc/user/project/pipelines/img/pipeline_schedule_variables.png +++ b/doc/user/project/pipelines/img/pipeline_schedule_variables.png diff --git a/doc/user/project/pipelines/img/pipeline_schedules_new_form.png b/doc/user/project/pipelines/img/pipeline_schedules_new_form.png Binary files differindex e135dd51070..993fbf8ca00 100644 --- a/doc/user/project/pipelines/img/pipeline_schedules_new_form.png +++ b/doc/user/project/pipelines/img/pipeline_schedules_new_form.png diff --git a/doc/user/project/releases/img/releases.png b/doc/user/project/releases/img/releases.png Binary files differindex f8b1b7305ad..da5bcd9d913 100644 --- a/doc/user/project/releases/img/releases.png +++ b/doc/user/project/releases/img/releases.png diff --git a/doc/user/project/repository/img/download_source_code.png b/doc/user/project/repository/img/download_source_code.png Binary files differindex 17f2cb4b3e8..56808061980 100644 --- a/doc/user/project/repository/img/download_source_code.png +++ b/doc/user/project/repository/img/download_source_code.png diff --git a/doc/user/project/settings/img/general_settings.png b/doc/user/project/settings/img/general_settings.png Binary files differindex 4ff6fff5ca3..f88a158d2be 100644 --- a/doc/user/project/settings/img/general_settings.png +++ b/doc/user/project/settings/img/general_settings.png diff --git a/doc/user/project/settings/img/import_export_download_export.png b/doc/user/project/settings/img/import_export_download_export.png Binary files differindex ab81c87bf5f..c7ab7565fc7 100644 --- a/doc/user/project/settings/img/import_export_download_export.png +++ b/doc/user/project/settings/img/import_export_download_export.png diff --git a/doc/user/project/settings/img/import_export_export_button.png b/doc/user/project/settings/img/import_export_export_button.png Binary files differindex 9e368739695..6933e3edfcc 100644 --- a/doc/user/project/settings/img/import_export_export_button.png +++ b/doc/user/project/settings/img/import_export_export_button.png diff --git a/doc/user/project/settings/img/import_export_mail_link.png b/doc/user/project/settings/img/import_export_mail_link.png Binary files differindex 985c37650d3..1bd9a071178 100644 --- a/doc/user/project/settings/img/import_export_mail_link.png +++ b/doc/user/project/settings/img/import_export_mail_link.png diff --git a/doc/user/project/settings/img/import_export_new_project.png b/doc/user/project/settings/img/import_export_new_project.png Binary files differindex fc1f73c5d6e..0e2365ecb68 100644 --- a/doc/user/project/settings/img/import_export_new_project.png +++ b/doc/user/project/settings/img/import_export_new_project.png diff --git a/doc/user/project/settings/img/import_export_select_file.png b/doc/user/project/settings/img/import_export_select_file.png Binary files differindex e3e1a5ef980..90a3e8d5c4e 100644 --- a/doc/user/project/settings/img/import_export_select_file.png +++ b/doc/user/project/settings/img/import_export_select_file.png diff --git a/doc/user/project/web_ide/img/terminal_status.png b/doc/user/project/web_ide/img/terminal_status.png Binary files differindex c37aa02b07a..91c341a9854 100644 --- a/doc/user/project/web_ide/img/terminal_status.png +++ b/doc/user/project/web_ide/img/terminal_status.png diff --git a/doc/user/reserved_names.md b/doc/user/reserved_names.md index 9e8475d8294..68532ccee65 100644 --- a/doc/user/reserved_names.md +++ b/doc/user/reserved_names.md @@ -40,19 +40,18 @@ It is currently not possible to create a project with the following names: Currently the following names are reserved as top level groups: -- 503.html - \- - .well-known - 404.html - 422.html - 500.html - 502.html +- 503.html - abuse_reports - admin - api - apple-touch-icon-precomposed.png - apple-touch-icon.png -- files - assets - autocomplete - ci @@ -61,12 +60,14 @@ Currently the following names are reserved as top level groups: - explore - favicon.ico - favicon.png +- files - groups - health_check - help - import - invites - jwt +- login - notification_settings - oauth - profile @@ -78,8 +79,6 @@ Currently the following names are reserved as top level groups: - sent_notifications - slash-command-logo.png - snippets -- u -- unicorn_test - unsubscribes - uploads - users @@ -88,22 +87,5 @@ Currently the following names are reserved as top level groups: These group names are unavailable as subgroup names: - \- -- activity -- analytics -- audit_events -- avatar -- edit -- group_members -- hooks -- issues -- labels -- ldap -- ldap_group_links -- merge_requests -- milestones -- notification_setting -- pipeline_quota -- projects -- subgroups [reserved]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/path_regex.rb diff --git a/doc/workflow/img/notification_global_settings.png b/doc/workflow/img/notification_global_settings.png Binary files differindex 72f7418f1f8..699f726c442 100644 --- a/doc/workflow/img/notification_global_settings.png +++ b/doc/workflow/img/notification_global_settings.png diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 2f5ce3d4003..09253ab6b0e 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -77,6 +77,11 @@ module API expose :last_activity_on, as: :last_activity_at # Back-compat end + class UserStarsProject < Grape::Entity + expose :starred_since + expose :user, using: Entities::UserBasic + end + class Identity < Grape::Entity expose :provider, :extern_uid end @@ -1085,16 +1090,18 @@ module API end class Label < LabelBasic - expose :open_issues_count do |label, options| - label.open_issues_count(options[:current_user]) - end + with_options if: lambda { |_, options| options[:with_counts] } do + expose :open_issues_count do |label, options| + label.open_issues_count(options[:current_user]) + end - expose :closed_issues_count do |label, options| - label.closed_issues_count(options[:current_user]) - end + expose :closed_issues_count do |label, options| + label.closed_issues_count(options[:current_user]) + end - expose :open_merge_requests_count do |label, options| - label.open_merge_requests_count(options[:current_user]) + expose :open_merge_requests_count do |label, options| + label.open_merge_requests_count(options[:current_user]) + end end expose :subscribed do |label, options| @@ -1341,6 +1348,7 @@ module API expose :variable_type, :key, :value expose :protected?, as: :protected, if: -> (entity, _) { entity.respond_to?(:protected?) } expose :masked?, as: :masked, if: -> (entity, _) { entity.respond_to?(:masked?) } + expose :environment_scope, if: -> (entity, _) { entity.respond_to?(:environment_scope) } end class Pipeline < PipelineBasic diff --git a/lib/api/group_labels.rb b/lib/api/group_labels.rb index 0dbc5f45a68..79a44941c81 100644 --- a/lib/api/group_labels.rb +++ b/lib/api/group_labels.rb @@ -16,6 +16,8 @@ module API success Entities::GroupLabel end params do + optional :with_counts, type: Boolean, default: false, + desc: 'Include issue and merge request counts' use :pagination end get ':id/labels' do diff --git a/lib/api/helpers/label_helpers.rb b/lib/api/helpers/label_helpers.rb index c11e7d614ab..896b0aba52b 100644 --- a/lib/api/helpers/label_helpers.rb +++ b/lib/api/helpers/label_helpers.rb @@ -19,7 +19,11 @@ module API end def get_labels(parent, entity) - present paginate(available_labels_for(parent)), with: entity, current_user: current_user, parent: parent + present paginate(available_labels_for(parent)), + with: entity, + current_user: current_user, + parent: parent, + with_counts: params[:with_counts] end def create_label(parent, entity) diff --git a/lib/api/helpers/notes_helpers.rb b/lib/api/helpers/notes_helpers.rb index 7124ac0c5c3..6bf9057fad7 100644 --- a/lib/api/helpers/notes_helpers.rb +++ b/lib/api/helpers/notes_helpers.rb @@ -74,14 +74,14 @@ module API end def find_noteable(parent_type, parent_id, noteable_type, noteable_id) - params = params_by_noteable_type_and_id(noteable_type, noteable_id) + params = finder_params_by_noteable_type_and_id(noteable_type, noteable_id, parent_id) - noteable = NotesFinder.new(current_user, params.merge(project: user_project)).target + noteable = NotesFinder.new(current_user, params).target noteable = nil unless can?(current_user, noteable_read_ability_name(noteable), noteable) noteable || not_found!(noteable_type) end - def params_by_noteable_type_and_id(type, id) + def finder_params_by_noteable_type_and_id(type, id, parent_id) target_type = type.name.underscore { target_type: target_type }.tap do |h| if %w(issue merge_request).include?(target_type) @@ -89,9 +89,15 @@ module API else h[:target_id] = id end + + add_parent_to_finder_params(h, type, parent_id) end end + def add_parent_to_finder_params(finder_params, noteable_type, parent_id) + finder_params[:project] = user_project + end + def noteable_parent(noteable) public_send("user_#{noteable.class.parent_class.to_s.underscore}") # rubocop:disable GitlabSecurity/PublicSend end diff --git a/lib/api/helpers/services_helpers.rb b/lib/api/helpers/services_helpers.rb index c4ecf55969c..422db5c7a50 100644 --- a/lib/api/helpers/services_helpers.rb +++ b/lib/api/helpers/services_helpers.rb @@ -489,32 +489,6 @@ module API desc: 'The ID of a transition that moves issues to a closed state. You can find this number under the Jira workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot][trans]). By default, this ID is set to `2`' } ], - 'kubernetes' => [ - { - required: true, - name: :namespace, - type: String, - desc: 'The Kubernetes namespace to use' - }, - { - required: true, - name: :api_url, - type: String, - desc: 'The URL to the Kubernetes cluster API, e.g., https://kubernetes.example.com' - }, - { - required: true, - name: :token, - type: String, - desc: 'The service token to authenticate against the Kubernetes cluster with' - }, - { - required: false, - name: :ca_pem, - type: String, - desc: 'A custom certificate authority bundle to verify the Kubernetes cluster with (PEM format)' - } - ], 'mattermost-slash-commands' => [ { required: true, @@ -739,7 +713,6 @@ module API ::HipchatService, ::IrkerService, ::JiraService, - ::KubernetesService, ::MattermostSlashCommandsService, ::SlackSlashCommandsService, ::PackagistService, diff --git a/lib/api/helpers/variables_helpers.rb b/lib/api/helpers/variables_helpers.rb deleted file mode 100644 index 78a92d0f5a6..00000000000 --- a/lib/api/helpers/variables_helpers.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module API - module Helpers - module VariablesHelpers - extend ActiveSupport::Concern - extend Grape::API::Helpers - - params :optional_params_ee do - end - end - end -end diff --git a/lib/api/labels.rb b/lib/api/labels.rb index d729d3ee625..c183198d3c6 100644 --- a/lib/api/labels.rb +++ b/lib/api/labels.rb @@ -15,6 +15,8 @@ module API success Entities::ProjectLabel end params do + optional :with_counts, type: Boolean, default: false, + desc: 'Include issue and merge request counts' use :pagination end get ':id/labels' do diff --git a/lib/api/project_import.rb b/lib/api/project_import.rb index 71891e43dcc..bb1b037c08f 100644 --- a/lib/api/project_import.rb +++ b/lib/api/project_import.rb @@ -59,6 +59,7 @@ module API } override_params = import_params.delete(:override_params) + filter_attributes_using_license!(override_params) if override_params project = ::Projects::GitlabProjectsImportService.new( current_user, project_params, override_params diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 0923d31f5ff..996205d4b7b 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -115,6 +115,22 @@ module API present_projects load_projects end + + desc 'Get projects starred by a user' do + success Entities::BasicProjectDetails + end + params do + requires :user_id, type: String, desc: 'The ID or username of the user' + use :collection_params + use :statistics_params + end + get ":user_id/starred_projects" do + user = find_user(params[:user_id]) + not_found!('User') unless user + + starred_projects = StarredProjectsFinder.new(user, params: project_finder_params, current_user: current_user).execute + present_projects starred_projects + end end resource :projects do @@ -358,6 +374,19 @@ module API end end + desc 'Get the users who starred a project' do + success Entities::UserBasic + end + params do + optional :search, type: String, desc: 'Return list of users matching the search criteria' + use :pagination + end + get ':id/starrers' do + starrers = UsersStarProjectsFinder.new(user_project, params, current_user: current_user).execute + + present paginate(starrers), with: Entities::UserStarsProject + end + desc 'Get languages in project repository' get ':id/languages' do ::Projects::RepositoryLanguagesService diff --git a/lib/api/variables.rb b/lib/api/variables.rb index af1d7936556..f022b9e665a 100644 --- a/lib/api/variables.rb +++ b/lib/api/variables.rb @@ -7,8 +7,6 @@ module API before { authenticate! } before { authorize! :admin_build, user_project } - helpers Helpers::VariablesHelpers - helpers do def filter_variable_parameters(params) # This method exists so that EE can more easily filter out certain @@ -59,8 +57,7 @@ module API optional :protected, type: Boolean, desc: 'Whether the variable is protected' optional :masked, type: Boolean, desc: 'Whether the variable is masked' optional :variable_type, type: String, values: Ci::Variable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var' - - use :optional_params_ee + optional :environment_scope, type: String, desc: 'The environment_scope of the variable' end post ':id/variables' do variable_params = declared_params(include_missing: false) @@ -84,8 +81,7 @@ module API optional :protected, type: Boolean, desc: 'Whether the variable is protected' optional :masked, type: Boolean, desc: 'Whether the variable is masked' optional :variable_type, type: String, values: Ci::Variable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file' - - use :optional_params_ee + optional :environment_scope, type: String, desc: 'The environment_scope of the variable' end # rubocop: disable CodeReuse/ActiveRecord put ':id/variables/:key' do diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb index 0224dd8fcd1..52af28ce8ec 100644 --- a/lib/banzai/filter/abstract_reference_filter.rb +++ b/lib/banzai/filter/abstract_reference_filter.rb @@ -337,6 +337,24 @@ module Banzai @current_project_namespace_path ||= project&.namespace&.full_path end + def records_per_parent + @_records_per_project ||= {} + + @_records_per_project[object_class.to_s.underscore] ||= begin + hash = Hash.new { |h, k| h[k] = {} } + + parent_per_reference.each do |path, parent| + record_ids = references_per_parent[path] + + parent_records(parent, record_ids).each do |record| + hash[parent][record_identifier(record)] = record + end + end + + hash + end + end + private def full_project_path(namespace, project_ref) diff --git a/lib/banzai/filter/commit_reference_filter.rb b/lib/banzai/filter/commit_reference_filter.rb index c3e5ac41cb8..e1d7b36b9a2 100644 --- a/lib/banzai/filter/commit_reference_filter.rb +++ b/lib/banzai/filter/commit_reference_filter.rb @@ -19,12 +19,11 @@ module Banzai end def find_object(project, id) - return unless project.is_a?(Project) + return unless project.is_a?(Project) && project.valid_repo? - if project && project.valid_repo? - # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/43894 - Gitlab::GitalyClient.allow_n_plus_1_calls { project.commit(id) } - end + _, record = records_per_parent[project].detect { |k, _v| Gitlab::Git.shas_eql?(k, id) } + + record end def referenced_merge_request_commit_shas @@ -66,6 +65,14 @@ module Banzai private + def record_identifier(record) + record.id + end + + def parent_records(parent, ids) + parent.commits_by(oids: ids.to_a) + end + def noteable context[:noteable] end diff --git a/lib/banzai/filter/issuable_reference_filter.rb b/lib/banzai/filter/issuable_reference_filter.rb index 2963cba91e8..b91ba9f7256 100644 --- a/lib/banzai/filter/issuable_reference_filter.rb +++ b/lib/banzai/filter/issuable_reference_filter.rb @@ -3,22 +3,8 @@ module Banzai module Filter class IssuableReferenceFilter < AbstractReferenceFilter - def records_per_parent - @records_per_project ||= {} - - @records_per_project[object_class.to_s.underscore] ||= begin - hash = Hash.new { |h, k| h[k] = {} } - - parent_per_reference.each do |path, parent| - record_ids = references_per_parent[path] - - parent_records(parent, record_ids).each do |record| - hash[parent][record.iid.to_i] = record - end - end - - hash - end + def record_identifier(record) + record.iid.to_i end def find_object(parent, iid) diff --git a/lib/gitlab/ci/ansi2html.rb b/lib/gitlab/ci/ansi2html.rb index 7e348763e81..382b8896dbd 100644 --- a/lib/gitlab/ci/ansi2html.rb +++ b/lib/gitlab/ci/ansi2html.rb @@ -218,7 +218,7 @@ module Gitlab return if @sections.include?(section) @sections << section - write_raw %{<div class="js-section-start fa fa-caret-down append-right-8 cursor-pointer" data-timestamp="#{timestamp}" data-section="#{data_section_names}" role="button"></div>} + write_raw %{<div class="js-section-start fa fa-caret-down pr-2 cursor-pointer" data-timestamp="#{timestamp}" data-section="#{data_section_names}" role="button"></div>} @lineno_in_section = 0 end @@ -306,7 +306,7 @@ module Gitlab css_classes << "section" css_classes << if @lineno_in_section == 0 - "js-section-header section-header" + "js-section-header section-header cursor-pointer" else "line" end diff --git a/lib/gitlab/ci/templates/Maven.gitlab-ci.yml b/lib/gitlab/ci/templates/Maven.gitlab-ci.yml index 13ab98d3a16..84bb0ff3b33 100644 --- a/lib/gitlab/ci/templates/Maven.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Maven.gitlab-ci.yml @@ -1,5 +1,3 @@ -# This file is a template, and might need editing before it works on your project. - # Build JAVA applications using Apache Maven (http://maven.apache.org) # For docker image tags see https://hub.docker.com/_/maven/ # diff --git a/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml index c963d6ed1c4..2afc99d0bf8 100644 --- a/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml @@ -25,6 +25,7 @@ container_scanning: # https://hub.docker.com/r/arminc/clair-local-scan/tags CLAIR_LOCAL_SCAN_VERSION: v2.0.8_0ed98e9ead65a51ba53f7cc53fa5e80c92169207 CLAIR_EXECUTABLE_VERSION: v12 + CLAIR_EXECUTABLE_SHA: 44f2a3fdd7b0d102c98510e7586f6956edc89ab72c6943980f92f4979f7f4081 ## Disable the proxy for clair-local-scan, otherwise Container Scanning will ## fail when a proxy is used. NO_PROXY: ${DOCKER_SERVICE},localhost @@ -44,6 +45,7 @@ container_scanning: - apk add -U wget ca-certificates - docker pull ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} - wget https://github.com/arminc/clair-scanner/releases/download/${CLAIR_EXECUTABLE_VERSION}/clair-scanner_linux_amd64 + - echo "${CLAIR_EXECUTABLE_SHA} clair-scanner_linux_amd64" | sha256sum -c - mv clair-scanner_linux_amd64 clair-scanner - chmod +x clair-scanner - touch clair-whitelist.yml diff --git a/lib/gitlab/content_security_policy/config_loader.rb b/lib/gitlab/content_security_policy/config_loader.rb index b2f3345d33a..ff844645b11 100644 --- a/lib/gitlab/content_security_policy/config_loader.rb +++ b/lib/gitlab/content_security_policy/config_loader.rb @@ -5,7 +5,7 @@ module Gitlab class ConfigLoader DIRECTIVES = %w(base_uri child_src connect_src default_src font_src form_action frame_ancestors frame_src img_src manifest_src - media_src object_src script_src style_src worker_src).freeze + media_src object_src report_uri script_src style_src worker_src).freeze def self.default_settings_hash { diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb index 32d5e4b9ea3..6ce47650562 100644 --- a/lib/gitlab/current_settings.rb +++ b/lib/gitlab/current_settings.rb @@ -7,6 +7,11 @@ module Gitlab Gitlab::SafeRequestStore.fetch(:current_application_settings) { ensure_application_settings! } end + def expire_current_application_settings + ::ApplicationSetting.expire + Gitlab::SafeRequestStore.delete(:current_application_settings) + end + def clear_in_memory_application_settings! @in_memory_application_settings = nil end diff --git a/lib/gitlab/git_logger.rb b/lib/gitlab/git_logger.rb index ded5349be01..545451f0dc9 100644 --- a/lib/gitlab/git_logger.rb +++ b/lib/gitlab/git_logger.rb @@ -3,7 +3,7 @@ module Gitlab class GitLogger < JsonLogger def self.file_name_noext - 'githost' + 'git_json' end end end diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb index 4783832961d..e6cbfb00f60 100644 --- a/lib/gitlab/gitaly_client.rb +++ b/lib/gitlab/gitaly_client.rb @@ -240,7 +240,7 @@ module Gitlab # Ensures that Gitaly is not being abuse through n+1 misuse etc def self.enforce_gitaly_request_limits(call_site) - # Only count limits in request-response environments (not sidekiq for example) + # Only count limits in request-response environments return unless Gitlab::SafeRequestStore.active? # This is this actual number of times this call was made. Used for information purposes only diff --git a/lib/gitlab/github_import/importer/releases_importer.rb b/lib/gitlab/github_import/importer/releases_importer.rb index 0e7c9ee0d00..9d925581441 100644 --- a/lib/gitlab/github_import/importer/releases_importer.rb +++ b/lib/gitlab/github_import/importer/releases_importer.rb @@ -36,6 +36,7 @@ module Gitlab description: description_for(release), created_at: release.created_at, updated_at: release.updated_at, + released_at: release.published_at, project_id: project.id } end diff --git a/lib/gitlab/highlight.rb b/lib/gitlab/highlight.rb index 1f49a26f0a2..381f1dd4e55 100644 --- a/lib/gitlab/highlight.rb +++ b/lib/gitlab/highlight.rb @@ -6,16 +6,15 @@ module Gitlab TIMEOUT_FOREGROUND = 3.seconds MAXIMUM_TEXT_HIGHLIGHT_SIZE = 1.megabyte - def self.highlight(blob_name, blob_content, since: nil, language: nil, plain: false) - new(blob_name, blob_content, since: since, language: language) + def self.highlight(blob_name, blob_content, language: nil, plain: false) + new(blob_name, blob_content, language: language) .highlight(blob_content, continue: false, plain: plain) end attr_reader :blob_name - def initialize(blob_name, blob_content, since: nil, language: nil) + def initialize(blob_name, blob_content, language: nil) @formatter = Rouge::Formatters::HTMLGitlab - @since = since @language = language @blob_name = blob_name @blob_content = blob_content @@ -54,13 +53,13 @@ module Gitlab end def highlight_plain(text) - @formatter.format(Rouge::Lexers::PlainText.lex(text), since: @since).html_safe + @formatter.format(Rouge::Lexers::PlainText.lex(text)).html_safe end def highlight_rich(text, continue: true) tag = lexer.tag tokens = lexer.lex(text, continue: continue) - Timeout.timeout(timeout_time) { @formatter.format(tokens, tag: tag, since: @since).html_safe } + Timeout.timeout(timeout_time) { @formatter.format(tokens, tag: tag).html_safe } rescue Timeout::Error => e Gitlab::Sentry.track_exception(e) highlight_plain(text) diff --git a/lib/gitlab/instrumentation_helper.rb b/lib/gitlab/instrumentation_helper.rb new file mode 100644 index 00000000000..e6a5facb2a5 --- /dev/null +++ b/lib/gitlab/instrumentation_helper.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Gitlab + module InstrumentationHelper + extend self + + KEYS = %i(gitaly_calls gitaly_duration rugged_calls rugged_duration_ms).freeze + + def add_instrumentation_data(payload) + gitaly_calls = Gitlab::GitalyClient.get_request_count + + if gitaly_calls > 0 + payload[:gitaly_calls] = gitaly_calls + payload[:gitaly_duration] = Gitlab::GitalyClient.query_time_ms + end + + rugged_calls = Gitlab::RuggedInstrumentation.query_count + + if rugged_calls > 0 + payload[:rugged_calls] = rugged_calls + payload[:rugged_duration_ms] = Gitlab::RuggedInstrumentation.query_time_ms + end + end + end +end diff --git a/lib/gitlab/metrics/dashboard/defaults.rb b/lib/gitlab/metrics/dashboard/defaults.rb new file mode 100644 index 00000000000..3c39a7c6911 --- /dev/null +++ b/lib/gitlab/metrics/dashboard/defaults.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +# Central point for managing default attributes from within +# the metrics dashboard module. +module Gitlab + module Metrics + module Dashboard + module Defaults + DEFAULT_PANEL_TYPE = 'area-chart' + DEFAULT_PANEL_WEIGHT = 0 + end + end + end +end diff --git a/lib/gitlab/metrics/dashboard/errors.rb b/lib/gitlab/metrics/dashboard/errors.rb new file mode 100644 index 00000000000..1739a4e6738 --- /dev/null +++ b/lib/gitlab/metrics/dashboard/errors.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +# Central point for managing errors from within the metrics +# dashboard module. Handles errors from dashboard retrieval +# and processing steps, as well as defines shared error classes. +module Gitlab + module Metrics + module Dashboard + module Errors + PanelNotFoundError = Class.new(StandardError) + + PROCESSING_ERROR = Gitlab::Metrics::Dashboard::Stages::BaseStage::DashboardProcessingError + NOT_FOUND_ERROR = Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError + + def handle_errors(error) + case error + when PROCESSING_ERROR + error(error.message, :unprocessable_entity) + when NOT_FOUND_ERROR + error("#{dashboard_path} could not be found.", :not_found) + when PanelNotFoundError + error(error.message, :not_found) + else + raise error + end + end + + def panels_not_found!(opts) + raise PanelNotFoundError.new("No panels matching properties #{opts}") + end + end + end + end +end diff --git a/lib/gitlab/metrics/dashboard/finder.rb b/lib/gitlab/metrics/dashboard/finder.rb index 1373830844b..66c4d662a6c 100644 --- a/lib/gitlab/metrics/dashboard/finder.rb +++ b/lib/gitlab/metrics/dashboard/finder.rb @@ -12,21 +12,37 @@ module Gitlab # @param project [Project] # @param user [User] # @param environment [Environment] - # @param opts - dashboard_path [String] Path at which the - # dashboard can be found. Nil values will - # default to the system dashboard. - # @param opts - embedded [Boolean] Determines whether the + # @param options - embedded [Boolean] Determines whether the # dashboard is to be rendered as part of an # issue or location other than the primary # metrics dashboard UI. Returns only the # Memory/CPU charts of the system dash. + # @param options - dashboard_path [String] Path at which the + # dashboard can be found. Nil values will + # default to the system dashboard. + # @param options - group [String] Title of the group + # to which a panel might belong. Used by + # embedded dashboards. + # @param options - title [String] Title of the panel. + # Used by embedded dashboards. + # @param options - y_label [String] Y-Axis label of + # a panel. Used by embedded dashboards. # @return [Hash] - def find(project, user, environment, dashboard_path: nil, embedded: false) - service_for_path(dashboard_path, embedded: embedded) - .new(project, user, environment: environment, dashboard_path: dashboard_path) + def find(project, user, environment, options = {}) + service_for(options) + .new(project, user, options.merge(environment: environment)) .get_dashboard end + # Returns a dashboard without any supplemental info. + # Returns only full, yml-defined dashboards. + # @return [Hash] + def find_raw(project, dashboard_path: nil) + service_for(dashboard_path: dashboard_path) + .new(project, nil, dashboard_path: dashboard_path) + .raw_dashboard + end + # Summary of all known dashboards. # @return [Array<Hash>] ex) [{ path: String, # display_name: String, @@ -46,13 +62,6 @@ module Gitlab private - def service_for_path(dashboard_path, embedded:) - return embed_service if embedded - return system_service if system_dashboard?(dashboard_path) - - project_service - end - def system_service ::Metrics::Dashboard::SystemDashboardService end @@ -61,12 +70,8 @@ module Gitlab ::Metrics::Dashboard::ProjectDashboardService end - def embed_service - ::Metrics::Dashboard::DefaultEmbedService - end - - def system_dashboard?(filepath) - !filepath || system_service.system_dashboard?(filepath) + def service_for(options) + Gitlab::Metrics::Dashboard::ServiceSelector.call(options) end end end diff --git a/lib/gitlab/metrics/dashboard/service_selector.rb b/lib/gitlab/metrics/dashboard/service_selector.rb new file mode 100644 index 00000000000..934ba9145a2 --- /dev/null +++ b/lib/gitlab/metrics/dashboard/service_selector.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +# Responsible for determining which dashboard service should +# be used to fetch or generate a dashboard hash. +# The services can be considered in two categories - embeds +# and dashboards. Embeds are all portions of dashboards. +module Gitlab + module Metrics + module Dashboard + class ServiceSelector + SERVICES = ::Metrics::Dashboard + + class << self + include Gitlab::Utils::StrongMemoize + + # Returns a class which inherits from the BaseService + # class that can be used to obtain a dashboard. + # @return [Gitlab::Metrics::Dashboard::Services::BaseService] + def call(params) + return SERVICES::CustomMetricEmbedService if custom_metric_embed?(params) + return SERVICES::DynamicEmbedService if dynamic_embed?(params) + return SERVICES::DefaultEmbedService if params[:embedded] + return SERVICES::SystemDashboardService if system_dashboard?(params[:dashboard_path]) + return SERVICES::ProjectDashboardService if params[:dashboard_path] + + default_service + end + + private + + def default_service + SERVICES::SystemDashboardService + end + + def system_dashboard?(filepath) + SERVICES::SystemDashboardService.system_dashboard?(filepath) + end + + def custom_metric_embed?(params) + SERVICES::CustomMetricEmbedService.valid_params?(params) + end + + def dynamic_embed?(params) + SERVICES::DynamicEmbedService.valid_params?(params) + end + end + end + end + end +end diff --git a/lib/gitlab/metrics/dashboard/stages/base_stage.rb b/lib/gitlab/metrics/dashboard/stages/base_stage.rb index 0db7b176e8d..514ed50e58d 100644 --- a/lib/gitlab/metrics/dashboard/stages/base_stage.rb +++ b/lib/gitlab/metrics/dashboard/stages/base_stage.rb @@ -5,11 +5,11 @@ module Gitlab module Dashboard module Stages class BaseStage + include Gitlab::Metrics::Dashboard::Defaults + DashboardProcessingError = Class.new(StandardError) LayoutError = Class.new(DashboardProcessingError) - DEFAULT_PANEL_TYPE = 'area-chart' - attr_reader :project, :environment, :dashboard def initialize(project, environment, dashboard) diff --git a/lib/gitlab/metrics/dashboard/stages/project_metrics_inserter.rb b/lib/gitlab/metrics/dashboard/stages/project_metrics_inserter.rb index 221610a14d1..643be309992 100644 --- a/lib/gitlab/metrics/dashboard/stages/project_metrics_inserter.rb +++ b/lib/gitlab/metrics/dashboard/stages/project_metrics_inserter.rb @@ -97,7 +97,7 @@ module Gitlab end def new_metric(metric) - metric.queries.first.merge(metric_id: metric.id) + metric.to_metric_hash end end end diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb index 827f4f77f36..5e77d31760d 100644 --- a/lib/gitlab/project_search_results.rb +++ b/lib/gitlab/project_search_results.rb @@ -134,9 +134,11 @@ module Gitlab project.repository.commit(key) if Commit.valid_hash?(key) end + # rubocop: disable CodeReuse/ActiveRecord def project_ids_relation - project + Project.where(id: project).select(:id).reorder(nil) end + # rubocop: enabled CodeReuse/ActiveRecord def filter_milestones_by_project(milestones) return Milestone.none unless Ability.allowed?(@current_user, :read_milestone, @project) diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index 21614ea003e..e6372a42dda 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -46,6 +46,18 @@ module Gitlab "can contain only letters, digits, '-', '_', '/', '$', '{', '}', '.', and spaces, but it cannot start or end with '/'" end + def environment_scope_regex_chars + "#{environment_name_regex_chars}\\*" + end + + def environment_scope_regex + @environment_scope_regex ||= /\A[#{environment_scope_regex_chars}]+\z/.freeze + end + + def environment_scope_regex_message + "can contain only letters, digits, '-', '_', '/', '$', '{', '}', '.', '*' and spaces" + end + def kubernetes_namespace_regex /\A[a-z0-9]([-a-z0-9]*[a-z0-9])?\z/ end diff --git a/lib/gitlab/sidekiq_logging/structured_logger.rb b/lib/gitlab/sidekiq_logging/structured_logger.rb index d556d5ef129..60782306ade 100644 --- a/lib/gitlab/sidekiq_logging/structured_logger.rb +++ b/lib/gitlab/sidekiq_logging/structured_logger.rb @@ -15,9 +15,9 @@ module Gitlab yield - Sidekiq.logger.info log_job_done(started_at, base_payload) + Sidekiq.logger.info log_job_done(job, started_at, base_payload) rescue => job_exception - Sidekiq.logger.warn log_job_done(started_at, base_payload, job_exception) + Sidekiq.logger.warn log_job_done(job, started_at, base_payload, job_exception) raise end @@ -28,6 +28,10 @@ module Gitlab "#{payload['class']} JID-#{payload['jid']}" end + def add_instrumentation_keys!(job, output_payload) + output_payload.merge!(job.slice(*::Gitlab::InstrumentationHelper::KEYS)) + end + def log_job_start(started_at, payload) payload['message'] = "#{base_message(payload)}: start" payload['job_status'] = 'start' @@ -35,14 +39,15 @@ module Gitlab # Old gitlab-shell messages don't provide enqueued_at/created_at attributes enqueued_at = payload['enqueued_at'] || payload['created_at'] if enqueued_at - payload['scheduling_latency_s'] = elapsed(Time.iso8601(enqueued_at).to_f) + payload['scheduling_latency_s'] = elapsed_by_absolute_time(Time.iso8601(enqueued_at)) end payload end - def log_job_done(started_at, payload, job_exception = nil) + def log_job_done(job, started_at, payload, job_exception = nil) payload = payload.dup + add_instrumentation_keys!(job, payload) payload['duration'] = elapsed(started_at) payload['completed_at'] = Time.now.utc @@ -84,6 +89,10 @@ module Gitlab end end + def elapsed_by_absolute_time(start) + (Time.now.utc - start).to_f.round(3) + end + def elapsed(start) (current_time - start).round(3) end diff --git a/lib/gitlab/sidekiq_middleware/instrumentation_logger.rb b/lib/gitlab/sidekiq_middleware/instrumentation_logger.rb new file mode 100644 index 00000000000..979a3fce7e6 --- /dev/null +++ b/lib/gitlab/sidekiq_middleware/instrumentation_logger.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Gitlab + module SidekiqMiddleware + class InstrumentationLogger + def call(worker, job, queue) + yield + + # The Sidekiq logger is called outside the middleware block, so + # we need to modify the job hash to pass along this information + # since RequestStore is only active in the Sidekiq middleware. + # + # Modifying the job hash in a middleware is permitted by Sidekiq + # because Sidekiq keeps a pristine copy of the original hash + # before sending it to the middleware: + # https://github.com/mperham/sidekiq/blob/53bd529a0c3f901879925b8390353129c465b1f2/lib/sidekiq/processor.rb#L115-L118 + ::Gitlab::InstrumentationHelper.add_instrumentation_data(job) + end + end + end +end diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb index d5657c474c8..7e3a695e52a 100644 --- a/lib/gitlab/usage_data.rb +++ b/lib/gitlab/usage_data.rb @@ -140,6 +140,8 @@ module Gitlab [ Gitlab::UsageDataCounters::WikiPageCounter, Gitlab::UsageDataCounters::WebIdeCounter, + Gitlab::UsageDataCounters::NoteCounter, + Gitlab::UsageDataCounters::SnippetCounter, Gitlab::UsageDataCounters::SearchCounter ] end diff --git a/lib/gitlab/usage_data_counters/base_counter.rb b/lib/gitlab/usage_data_counters/base_counter.rb new file mode 100644 index 00000000000..2b52571c3cc --- /dev/null +++ b/lib/gitlab/usage_data_counters/base_counter.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Gitlab::UsageDataCounters + class BaseCounter + extend RedisCounter + + UnknownEvent = Class.new(StandardError) + + class << self + def redis_key(event) + Gitlab::Sentry.track_exception(UnknownEvent, extra: { event: event }) unless known_events.include?(event.to_s) + + "USAGE_#{prefix}_#{event}".upcase + end + + def count(event) + increment(redis_key event) + end + + def read(event) + total_count(redis_key event) + end + + def totals + known_events.map { |e| ["#{prefix}_#{e}".to_sym, read(e)] }.to_h + end + + private + + def known_events + self::KNOWN_EVENTS + end + + def prefix + self::PREFIX + end + end + end +end diff --git a/lib/gitlab/usage_data_counters/note_counter.rb b/lib/gitlab/usage_data_counters/note_counter.rb new file mode 100644 index 00000000000..e93a0bcfa27 --- /dev/null +++ b/lib/gitlab/usage_data_counters/note_counter.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Gitlab::UsageDataCounters + class NoteCounter < BaseCounter + KNOWN_EVENTS = %w[create].freeze + PREFIX = 'note' + COUNTABLE_TYPES = %w[Snippet].freeze + + class << self + def redis_key(event, noteable_type) + "#{super(event)}_#{noteable_type}".upcase + end + + def count(event, noteable_type) + return unless countable?(noteable_type) + + increment(redis_key(event, noteable_type)) + end + + def read(event, noteable_type) + return 0 unless countable?(noteable_type) + + total_count(redis_key(event, noteable_type)) + end + + def totals + { + snippet_comment: read(:create, 'Snippet') + } + end + + private + + def countable?(noteable_type) + COUNTABLE_TYPES.include?(noteable_type.to_s) + end + end + end +end diff --git a/lib/gitlab/usage_data_counters/snippet_counter.rb b/lib/gitlab/usage_data_counters/snippet_counter.rb new file mode 100644 index 00000000000..e4d234ce4d9 --- /dev/null +++ b/lib/gitlab/usage_data_counters/snippet_counter.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module Gitlab::UsageDataCounters + class SnippetCounter < BaseCounter + KNOWN_EVENTS = %w[create update].freeze + PREFIX = 'snippet' + end +end diff --git a/lib/gitlab/usage_data_counters/wiki_page_counter.rb b/lib/gitlab/usage_data_counters/wiki_page_counter.rb index c8b59a3160c..9cfe0be5bab 100644 --- a/lib/gitlab/usage_data_counters/wiki_page_counter.rb +++ b/lib/gitlab/usage_data_counters/wiki_page_counter.rb @@ -1,32 +1,8 @@ # frozen_string_literal: true module Gitlab::UsageDataCounters - class WikiPageCounter - extend RedisCounter - - KNOWN_EVENTS = %w[create update delete].map(&:freeze).freeze - - UnknownEvent = Class.new(StandardError) - - class << self - # Each event gets a unique Redis key - def redis_key(event) - raise UnknownEvent, event unless KNOWN_EVENTS.include?(event.to_s) - - "USAGE_WIKI_PAGES_#{event}".upcase - end - - def count(event) - increment(redis_key event) - end - - def read(event) - total_count(redis_key event) - end - - def totals - KNOWN_EVENTS.map { |e| ["wiki_pages_#{e}".to_sym, read(e)] }.to_h - end - end + class WikiPageCounter < BaseCounter + KNOWN_EVENTS = %w[create update delete].freeze + PREFIX = 'wiki_pages' end end diff --git a/lib/rouge/formatters/html_gitlab.rb b/lib/rouge/formatters/html_gitlab.rb index 0d4ac504428..e2a7d3ef5ba 100644 --- a/lib/rouge/formatters/html_gitlab.rb +++ b/lib/rouge/formatters/html_gitlab.rb @@ -8,8 +8,8 @@ module Rouge # Creates a new <tt>Rouge::Formatter::HTMLGitlab</tt> instance. # # [+tag+] The tag (language) of the lexer used to generate the formatted tokens - def initialize(tag: nil, since: nil) - @line_number = since || 1 + def initialize(tag: nil) + @line_number = 1 @tag = tag end diff --git a/lib/system_check/app/git_version_check.rb b/lib/system_check/app/git_version_check.rb index 467711fb74e..08c8df9b044 100644 --- a/lib/system_check/app/git_version_check.rb +++ b/lib/system_check/app/git_version_check.rb @@ -7,7 +7,7 @@ module SystemCheck set_check_pass -> { "yes (#{self.current_version})" } def self.required_version - @required_version ||= Gitlab::VersionInfo.parse('2.21.0') + @required_version ||= Gitlab::VersionInfo.parse('2.22.0') end def self.current_version diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 7828c7a5eac..c44dffcb05b 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -145,6 +145,9 @@ msgid_plural "%{count} participants" msgstr[0] "" msgstr[1] "" +msgid "%{duration}ms" +msgstr "" + msgid "%{edit_in_new_fork_notice} Try to cherry-pick this commit again." msgstr "" @@ -285,6 +288,9 @@ msgstr "" msgid "%{username}'s avatar" msgstr "" +msgid "%{value} ms" +msgstr "" + msgid "%{verb} %{time_spent_value} spent time." msgstr "" @@ -2260,6 +2266,9 @@ msgstr "" msgid "CiVariables|Remove variable row" msgstr "" +msgid "CiVariables|Scope" +msgstr "" + msgid "CiVariables|Specify variable values to be used in this run. The values specified in %{linkStart}CI/CD settings%{linkEnd} will be used as default" msgstr "" @@ -2281,15 +2290,24 @@ msgstr "" msgid "CiVariable|All environments" msgstr "" +msgid "CiVariable|Create wildcard" +msgstr "" + msgid "CiVariable|Error occurred while saving variables" msgstr "" msgid "CiVariable|Masked" msgstr "" +msgid "CiVariable|New environment" +msgstr "" + msgid "CiVariable|Protected" msgstr "" +msgid "CiVariable|Search environments" +msgstr "" + msgid "CiVariable|Toggle masked" msgstr "" @@ -3300,6 +3318,9 @@ msgstr "" msgid "Could not add prometheus URL to whitelist" msgstr "" +msgid "Could not add prometheus URL to whitelist, errors: %{errors}" +msgstr "" + msgid "Could not authorize chat nickname. Try again!" msgstr "" @@ -3312,6 +3333,12 @@ msgstr "" msgid "Could not create Wiki Repository at this time. Please try again later." msgstr "" +msgid "Could not create instance administration project. Errors: %{errors}" +msgstr "" + +msgid "Could not create project" +msgstr "" + msgid "Could not delete chat nickname %{chat_name}." msgstr "" @@ -3327,6 +3354,12 @@ msgstr "" msgid "Could not revoke personal access token %{personal_access_token_name}." msgstr "" +msgid "Could not save instance administration project ID, errors: %{errors}" +msgstr "" + +msgid "Could not save project ID" +msgstr "" + msgid "Coverage" msgstr "" @@ -5266,9 +5299,6 @@ msgstr "" msgid "Group was successfully updated." msgstr "" -msgid "Group:" -msgstr "" - msgid "Group: %{group_name}" msgstr "" @@ -5796,6 +5826,12 @@ msgstr "" msgid "Instance Statistics visibility" msgstr "" +msgid "Instance administration project already exists" +msgstr "" + +msgid "Instance administrators group already exists" +msgstr "" + msgid "Instance does not support multiple Kubernetes clusters" msgstr "" @@ -6141,9 +6177,6 @@ msgstr "" msgid "Kubernetes error: %{error_code}" msgstr "" -msgid "Kubernetes service integration has been disabled. Fields on this page are not used by GitLab, you can configure your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page" -msgstr "" - msgid "LDAP" msgstr "" @@ -7221,12 +7254,18 @@ msgstr "" msgid "No schedules" msgstr "" +msgid "No starrers matched your search" +msgstr "" + msgid "No template" msgstr "" msgid "No, directly import the existing email addresses and usernames." msgstr "" +msgid "Nobody has starred this repository yet" +msgstr "" + msgid "None" msgstr "" @@ -8498,9 +8537,6 @@ msgstr "" msgid "Project visibility level will be changed to match namespace rules when transferring to a group." msgstr "" -msgid "Project:" -msgstr "" - msgid "ProjectActivityRSS|Subscribe" msgstr "" @@ -8540,6 +8576,12 @@ msgstr "" msgid "ProjectOverview|Star" msgstr "" +msgid "ProjectOverview|Starrer" +msgstr "" + +msgid "ProjectOverview|Starrers" +msgstr "" + msgid "ProjectOverview|Unstar" msgstr "" @@ -9636,6 +9678,12 @@ msgstr "" msgid "SearchAutocomplete|in this project" msgstr "" +msgid "SearchCodeResults|in" +msgstr "" + +msgid "SearchCodeResults|of %{link_to_project}" +msgstr "" + msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\"" msgstr "" @@ -10265,6 +10313,9 @@ msgstr "" msgid "SortOptions|Oldest sign in" msgstr "" +msgid "SortOptions|Oldest starred" +msgstr "" + msgid "SortOptions|Oldest updated" msgstr "" @@ -10280,6 +10331,9 @@ msgstr "" msgid "SortOptions|Recent sign in" msgstr "" +msgid "SortOptions|Recently starred" +msgstr "" + msgid "SortOptions|Sort direction" msgstr "" @@ -10373,6 +10427,9 @@ msgstr "" msgid "StarredProjectsEmptyState|You don't have starred projects yet." msgstr "" +msgid "Starrers" +msgstr "" + msgid "Stars" msgstr "" @@ -10849,6 +10906,9 @@ msgstr "" msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git." msgstr "" +msgid "The code of a detached pipeline is tested against the source branch instead of merged results" +msgstr "" + msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request." msgstr "" @@ -10900,9 +10960,6 @@ msgstr "" msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination." msgstr "" -msgid "The instance-level Kubernetes service integration is disabled. Your data has been migrated to an <a href=\"%{url}\"/>instance-level cluster</a>." -msgstr "" - msgid "The invitation could not be accepted." msgstr "" @@ -11347,9 +11404,6 @@ msgstr "" msgid "This page will be removed in a future release." msgstr "" -msgid "This pipeline is run on the source branch" -msgstr "" - msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}" msgstr "" @@ -12215,6 +12269,12 @@ msgstr "" msgid "UserProfile|Snippets in GitLab can either be private, internal, or public." msgstr "" +msgid "UserProfile|Star projects to track their progress and show your appreciation." +msgstr "" + +msgid "UserProfile|Starred projects" +msgstr "" + msgid "UserProfile|Subscribe" msgstr "" @@ -12227,6 +12287,9 @@ msgstr "" msgid "UserProfile|This user hasn't contributed to any projects" msgstr "" +msgid "UserProfile|This user hasn't starred any projects" +msgstr "" + msgid "UserProfile|View all" msgstr "" @@ -12449,6 +12512,9 @@ msgstr "" msgid "Welcome to your Issue Board!" msgstr "" +msgid "What are you searching for?" +msgstr "" + msgid "When a runner is locked, it cannot be assigned to other projects" msgstr "" diff --git a/package.json b/package.json index c368cf7c8e4..7ff4e74137b 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "eslint": "eslint --max-warnings 0 --report-unused-disable-directives --ext .js,.vue .", "eslint-fix": "eslint --max-warnings 0 --report-unused-disable-directives --ext .js,.vue --fix .", "eslint-report": "eslint --max-warnings 0 --ext .js,.vue --format html --output-file ./eslint-report.html --no-inline-config .", + "file-coverage": "scripts/frontend/file_test_coverage.js", "prejest": "yarn check-dependencies", "jest": "jest", "jest-debug": "node --inspect-brk node_modules/.bin/jest --runInBand", @@ -147,6 +148,7 @@ "@babel/plugin-transform-modules-commonjs": "^7.2.0", "@gitlab/eslint-config": "^1.6.0", "@gitlab/eslint-plugin-i18n": "^1.1.0", + "@gitlab/eslint-plugin-vue-i18n": "^1.2.0", "@vue/test-utils": "^1.0.0-beta.25", "axios-mock-adapter": "^1.15.0", "babel-jest": "^24.1.0", diff --git a/scripts/frontend/file_test_coverage.js b/scripts/frontend/file_test_coverage.js new file mode 100755 index 00000000000..7d1eb45d4bc --- /dev/null +++ b/scripts/frontend/file_test_coverage.js @@ -0,0 +1,88 @@ +#!/usr/bin/env node + +/** + * Counts the number of frontend test files and compares them against the number of application files. + * + * Example output: + * + * Source files: 1551 + * Test files: 716 + * Coverage: 46.16% + */ + +const fs = require('fs'); +const path = require('path'); + +const sourceDirectories = ['app/assets/javascripts']; +const testDirectories = ['spec/javascripts', 'spec/frontend']; + +if (fs.existsSync('ee')) { + sourceDirectories.forEach(dir => { + sourceDirectories.push(`ee/${dir}`); + }); + + testDirectories.forEach(dir => { + testDirectories.push(`ee/${dir}`); + }); +} + +let numSourceFiles = 0; +let numTestFiles = 0; + +const isVerbose = process.argv.some(arg => arg === '-v'); + +const countSourceFiles = path => + forEachFileIn(path, fileName => { + if (fileName.endsWith('.vue') || fileName.endsWith('.js')) { + if (isVerbose) { + console.log(`source file: ${fileName}`); + } + + numSourceFiles += 1; + } + }); + +const countTestFiles = path => + forEachFileIn(path, fileName => { + if (fileName.endsWith('_spec.js')) { + if (isVerbose) { + console.log(`test file: ${fileName}`); + } + + numTestFiles += 1; + } + }); + +function forEachFileIn(dirPath, callback) { + fs.readdir(dirPath, (err, files) => { + if (err) { + console.error(err); + } + + if (!files) { + return; + } + + files.forEach(fileName => { + const absolutePath = path.join(dirPath, fileName); + const stats = fs.statSync(absolutePath); + if (stats.isFile()) { + callback(absolutePath); + } else if (stats.isDirectory()) { + forEachFileIn(absolutePath, callback); + } + }); + }); +} + +console.log(`Source directories: ${sourceDirectories.join(', ')}`); +console.log(`Test directories: ${testDirectories.join(', ')}`); + +sourceDirectories.forEach(countSourceFiles); +testDirectories.forEach(countTestFiles); + +process.on('exit', () => { + console.log(`Source files: ${numSourceFiles}`); + console.log(`Test files: ${numTestFiles}`); + console.log(`Coverage: ${((100 * numTestFiles) / numSourceFiles).toFixed(2)}%`); +}); diff --git a/scripts/lint-rugged b/scripts/lint-rugged index d862571c1c5..1b3fb54f70b 100755 --- a/scripts/lint-rugged +++ b/scripts/lint-rugged @@ -19,6 +19,7 @@ ALLOWED = [ 'config/initializers/peek.rb', 'config/initializers/lograge.rb', 'lib/gitlab/grape_logging/loggers/perf_logger.rb', + 'lib/gitlab/instrumentation_helper.rb', 'lib/gitlab/rugged_instrumentation.rb', 'lib/peek/views/rugged.rb' ].freeze diff --git a/spec/controllers/metrics_controller_spec.rb b/spec/controllers/metrics_controller_spec.rb index 84027119491..7fb3578cd0a 100644 --- a/spec/controllers/metrics_controller_spec.rb +++ b/spec/controllers/metrics_controller_spec.rb @@ -5,12 +5,19 @@ require 'spec_helper' describe MetricsController do include StubENV - let(:metrics_multiproc_dir) { Dir.mktmpdir } + let(:metrics_multiproc_dir) { @metrics_multiproc_dir } let(:whitelisted_ip) { '127.0.0.1' } let(:whitelisted_ip_range) { '10.0.0.0/24' } let(:ip_in_whitelisted_range) { '10.0.0.1' } let(:not_whitelisted_ip) { '10.0.1.1' } + around do |example| + Dir.mktmpdir do |path| + @metrics_multiproc_dir = path + example.run + end + end + before do stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') allow(Prometheus::Client.configuration).to receive(:multiprocess_files_dir).and_return(metrics_multiproc_dir) diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb index 8872e8d38e7..b3852355d77 100644 --- a/spec/controllers/projects/environments_controller_spec.rb +++ b/spec/controllers/projects/environments_controller_spec.rb @@ -518,10 +518,10 @@ describe Projects::EnvironmentsController do end end - shared_examples_for 'the default dynamic dashboard' do + shared_examples_for 'specified dashboard embed' do |expected_titles| it_behaves_like '200 response' - it 'contains only the Memory and CPU charts' do + it 'contains only the specified charts' do get :metrics_dashboard, params: environment_params(dashboard_params) dashboard = json_response['dashboard'] @@ -531,10 +531,14 @@ describe Projects::EnvironmentsController do expect(dashboard['dashboard']).to be_nil expect(dashboard['panel_groups'].length).to eq 1 expect(panel_group['group']).to be_nil - expect(titles).to eq ['Memory Usage (Total)', 'Core Usage (Total)'] + expect(titles).to eq expected_titles end end + shared_examples_for 'the default dynamic dashboard' do + it_behaves_like 'specified dashboard embed', ['Memory Usage (Total)', 'Core Usage (Total)'] + end + shared_examples_for 'dashboard can be specified' do context 'when dashboard is specified' do let(:dashboard_path) { '.gitlab/dashboards/test.yml' } @@ -551,7 +555,7 @@ describe Projects::EnvironmentsController do end context 'when the specified dashboard is the default dashboard' do - let(:dashboard_path) { ::Metrics::Dashboard::SystemDashboardService::SYSTEM_DASHBOARD_PATH } + let(:dashboard_path) { system_dashboard_path } it_behaves_like 'the default dashboard' end @@ -564,12 +568,40 @@ describe Projects::EnvironmentsController do it_behaves_like 'the default dynamic dashboard' - context 'when the dashboard is specified' do - let(:dashboard_params) { { format: :json, embedded: true, dashboard: '.gitlab/dashboards/fake.yml' } } + context 'when incomplete dashboard params are provided' do + let(:dashboard_params) { { format: :json, embedded: true, title: 'Title' } } + + # The title param should be ignored. + it_behaves_like 'the default dynamic dashboard' + end + + context 'when invalid params are provided' do + let(:dashboard_params) { { format: :json, embedded: true, metric_id: 16 } } - # The dashboard param should be ignored. + # The superfluous param should be ignored. it_behaves_like 'the default dynamic dashboard' end + + context 'when the dashboard is correctly specified' do + let(:dashboard_params) do + { + format: :json, + embedded: true, + dashboard: system_dashboard_path, + group: business_metric_title, + title: 'title', + y_label: 'y_label' + } + end + + it_behaves_like 'error response', :not_found + + context 'and exists' do + let!(:metric) { create(:prometheus_metric, project: project) } + + it_behaves_like 'specified dashboard embed', ['title'] + end + end end end diff --git a/spec/controllers/projects/starrers_controller_spec.rb b/spec/controllers/projects/starrers_controller_spec.rb new file mode 100644 index 00000000000..59d258e99ce --- /dev/null +++ b/spec/controllers/projects/starrers_controller_spec.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Projects::StarrersController do + let(:user) { create(:user) } + let(:private_user) { create(:user, private_profile: true) } + let(:admin) { create(:user, admin: true) } + let(:project) { create(:project, :public, :repository) } + + before do + user.toggle_star(project) + private_user.toggle_star(project) + end + + describe 'GET index' do + def get_starrers + get :index, + params: { + namespace_id: project.namespace, + project_id: project + } + end + + context 'when project is public' do + before do + project.update_attribute(:visibility_level, Project::PUBLIC) + end + + context 'when no user is logged in' do + before do + get_starrers + end + + it 'only public starrers are visible' do + user_ids = assigns[:starrers].map { |s| s['user_id'] } + expect(user_ids).to include(user.id) + expect(user_ids).not_to include(private_user.id) + end + + it 'public/private starrers counts are correct' do + expect(assigns[:public_count]).to eq(1) + expect(assigns[:private_count]).to eq(1) + end + end + + context 'when private user is logged in' do + before do + sign_in(private_user) + + get_starrers + end + + it 'their star is also visible' do + user_ids = assigns[:starrers].map { |s| s['user_id'] } + expect(user_ids).to include(user.id, private_user.id) + end + + it 'public/private starrers counts are correct' do + expect(assigns[:public_count]).to eq(1) + expect(assigns[:private_count]).to eq(1) + end + end + + context 'when admin is logged in' do + before do + sign_in(admin) + + get_starrers + end + + it 'all stars are visible' do + user_ids = assigns[:starrers].map { |s| s['user_id'] } + expect(user_ids).to include(user.id, private_user.id) + end + + it 'public/private starrers counts are correct' do + expect(assigns[:public_count]).to eq(1) + expect(assigns[:private_count]).to eq(1) + end + end + end + + context 'when project is private' do + before do + project.update(visibility_level: Project::PRIVATE) + end + + it 'starrers are not visible for non logged in users' do + get_starrers + + expect(assigns[:starrers]).to be_blank + end + + context 'when user is logged in' do + before do + sign_in(project.creator) + end + + it 'only public starrers are visible' do + get_starrers + + user_ids = assigns[:starrers].map { |s| s['user_id'] } + expect(user_ids).to include(user.id) + expect(user_ids).not_to include(private_user.id) + end + end + end + end +end diff --git a/spec/controllers/projects/variables_controller_spec.rb b/spec/controllers/projects/variables_controller_spec.rb index a2a09e2580f..21e106660d0 100644 --- a/spec/controllers/projects/variables_controller_spec.rb +++ b/spec/controllers/projects/variables_controller_spec.rb @@ -36,5 +36,70 @@ describe Projects::VariablesController do end include_examples 'PATCH #update updates variables' + + context 'with environment scope' do + let!(:variable) { create(:ci_variable, project: project, environment_scope: 'custom_scope') } + + let(:variable_attributes) do + { id: variable.id, + key: variable.key, + secret_value: variable.value, + protected: variable.protected?.to_s, + environment_scope: variable.environment_scope } + end + let(:new_variable_attributes) do + { key: 'new_key', + secret_value: 'dummy_value', + protected: 'false', + environment_scope: 'new_scope' } + end + + context 'with same key and different environment scope' do + let(:variables_attributes) do + [ + variable_attributes, + new_variable_attributes.merge(key: variable.key) + ] + end + + it 'does not update the existing variable' do + expect { subject }.not_to change { variable.reload.value } + end + + it 'creates the new variable' do + expect { subject }.to change { owner.variables.count }.by(1) + end + + it 'returns a successful response including all variables' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('variables') + end + end + + context 'with same key and same environment scope' do + let(:variables_attributes) do + [ + variable_attributes, + new_variable_attributes.merge(key: variable.key, environment_scope: variable.environment_scope) + ] + end + + it 'does not update the existing variable' do + expect { subject }.not_to change { variable.reload.value } + end + + it 'does not create the new variable' do + expect { subject }.not_to change { owner.variables.count } + end + + it 'returns a bad request response' do + subject + + expect(response).to have_gitlab_http_status(:bad_request) + end + end + end end end diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index afe27aaf1fb..ea89555b0d5 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -325,10 +325,6 @@ FactoryBot.define do jira_service end - factory :kubernetes_project, parent: :project do - kubernetes_service - end - factory :mock_deployment_project, parent: :project do mock_deployment_service end diff --git a/spec/factories/services.rb b/spec/factories/services.rb index 5ef39b3e818..f3e662ad4f5 100644 --- a/spec/factories/services.rb +++ b/spec/factories/services.rb @@ -16,18 +16,6 @@ FactoryBot.define do ) end - factory :kubernetes_service do - project - type 'KubernetesService' - active true - properties({ - api_url: 'https://kubernetes.example.com', - token: 'a' * 40 - }) - - skip_deprecation_validation true - end - factory :mock_deployment_service do project type 'MockDeploymentService' diff --git a/spec/features/admin/admin_browses_logs_spec.rb b/spec/features/admin/admin_browses_logs_spec.rb index 5a2df89aeb7..2b97362c8e9 100644 --- a/spec/features/admin/admin_browses_logs_spec.rb +++ b/spec/features/admin/admin_browses_logs_spec.rb @@ -11,7 +11,7 @@ describe 'Admin browses logs' do visit admin_logs_path expect(page).to have_link 'application.log' - expect(page).to have_link 'githost.log' + expect(page).to have_link 'git_json.log' expect(page).to have_link 'test.log' expect(page).to have_link 'sidekiq.log' expect(page).to have_link 'repocheck.log' diff --git a/spec/features/groups/members/search_members_spec.rb b/spec/features/groups/members/search_members_spec.rb index d2d084c9174..9c17aac09e8 100644 --- a/spec/features/groups/members/search_members_spec.rb +++ b/spec/features/groups/members/search_members_spec.rb @@ -19,9 +19,9 @@ describe 'Search group member' do end it 'renders member users' do - page.within '.member-search-form' do + page.within '.user-search-form' do fill_in 'search', with: member.name - find('.member-search-btn').click + find('.user-search-btn').click end group_members_list = find(".card .content-list") diff --git a/spec/features/groups/members/sort_members_spec.rb b/spec/features/groups/members/sort_members_spec.rb index 510e7f2f05e..76709199942 100644 --- a/spec/features/groups/members/sort_members_spec.rb +++ b/spec/features/groups/members/sort_members_spec.rb @@ -19,7 +19,7 @@ describe 'Groups > Members > Sort members' do expect(first_member).to include(owner.name) expect(second_member).to include(developer.name) - expect(page).to have_css('.qa-member-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending') + expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending') end it 'sorts by access level ascending' do @@ -27,7 +27,7 @@ describe 'Groups > Members > Sort members' do expect(first_member).to include(developer.name) expect(second_member).to include(owner.name) - expect(page).to have_css('.qa-member-sort-dropdown .dropdown-toggle-text', text: 'Access level, ascending') + expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Access level, ascending') end it 'sorts by access level descending' do @@ -35,7 +35,7 @@ describe 'Groups > Members > Sort members' do expect(first_member).to include(owner.name) expect(second_member).to include(developer.name) - expect(page).to have_css('.qa-member-sort-dropdown .dropdown-toggle-text', text: 'Access level, descending') + expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Access level, descending') end it 'sorts by last joined' do @@ -43,7 +43,7 @@ describe 'Groups > Members > Sort members' do expect(first_member).to include(developer.name) expect(second_member).to include(owner.name) - expect(page).to have_css('.qa-member-sort-dropdown .dropdown-toggle-text', text: 'Last joined') + expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Last joined') end it 'sorts by oldest joined' do @@ -51,7 +51,7 @@ describe 'Groups > Members > Sort members' do expect(first_member).to include(owner.name) expect(second_member).to include(developer.name) - expect(page).to have_css('.qa-member-sort-dropdown .dropdown-toggle-text', text: 'Oldest joined') + expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Oldest joined') end it 'sorts by name ascending' do @@ -59,7 +59,7 @@ describe 'Groups > Members > Sort members' do expect(first_member).to include(owner.name) expect(second_member).to include(developer.name) - expect(page).to have_css('.qa-member-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending') + expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending') end it 'sorts by name descending' do @@ -67,7 +67,7 @@ describe 'Groups > Members > Sort members' do expect(first_member).to include(developer.name) expect(second_member).to include(owner.name) - expect(page).to have_css('.qa-member-sort-dropdown .dropdown-toggle-text', text: 'Name, descending') + expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Name, descending') end it 'sorts by recent sign in', :clean_gitlab_redis_shared_state do @@ -75,7 +75,7 @@ describe 'Groups > Members > Sort members' do expect(first_member).to include(owner.name) expect(second_member).to include(developer.name) - expect(page).to have_css('.qa-member-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in') + expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in') end it 'sorts by oldest sign in', :clean_gitlab_redis_shared_state do @@ -83,7 +83,7 @@ describe 'Groups > Members > Sort members' do expect(first_member).to include(developer.name) expect(second_member).to include(owner.name) - expect(page).to have_css('.qa-member-sort-dropdown .dropdown-toggle-text', text: 'Oldest sign in') + expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Oldest sign in') end def visit_members_list(sort:) diff --git a/spec/features/project_variables_spec.rb b/spec/features/project_variables_spec.rb index 95685a3c7ff..9e3f8a843a1 100644 --- a/spec/features/project_variables_spec.rb +++ b/spec/features/project_variables_spec.rb @@ -17,4 +17,27 @@ describe 'Project variables', :js do end it_behaves_like 'variable list' + + it 'adds new variable with a special environment scope' do + page.within('.js-ci-variable-list-section .js-row:last-child') do + find('.js-ci-variable-input-key').set('somekey') + find('.js-ci-variable-input-value').set('somevalue') + + find('.js-variable-environment-toggle').click + find('.js-variable-environment-dropdown-wrapper .dropdown-input-field').set('review/*') + find('.js-variable-environment-dropdown-wrapper .js-dropdown-create-new-item').click + + expect(find('input[name="variables[variables_attributes][][environment_scope]"]', visible: false).value).to eq('review/*') + end + + click_button('Save variables') + wait_for_requests + + visit page_path + + page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do + expect(find('.js-ci-variable-input-key').value).to eq('somekey') + expect(page).to have_content('review/*') + end + end end diff --git a/spec/features/projects/jobs/user_browses_job_spec.rb b/spec/features/projects/jobs/user_browses_job_spec.rb index 6d0269dd96b..1b277e17b0c 100644 --- a/spec/features/projects/jobs/user_browses_job_spec.rb +++ b/spec/features/projects/jobs/user_browses_job_spec.rb @@ -50,6 +50,20 @@ describe 'User browses a job', :js do expect(page).not_to have_content(text_to_hide) expect(page).to have_content(text_to_show) end + + it 'collapses the section header clicked' do + wait_for_requests + text_to_hide = "Cloning into '/nolith/ci-tests'" + text_to_show = 'Waiting for pod' + + expect(page).to have_content(text_to_hide) + expect(page).to have_content(text_to_show) + + first('.js-section-header.js-s-get-sources').click + + expect(page).not_to have_content(text_to_hide) + expect(page).to have_content(text_to_show) + end end context 'when job trace contains sections' do diff --git a/spec/features/projects/members/groups_with_access_list_spec.rb b/spec/features/projects/members/groups_with_access_list_spec.rb index 7b1fded1834..6e8d1a945e1 100644 --- a/spec/features/projects/members/groups_with_access_list_spec.rb +++ b/spec/features/projects/members/groups_with_access_list_spec.rb @@ -52,18 +52,18 @@ describe 'Projects > Members > Groups with access list', :js do context 'search in existing members (yes, this filters the groups list as well)' do it 'finds no results' do - page.within '.member-search-form' do + page.within '.user-search-form' do fill_in 'search', with: 'testing 123' - find('.member-search-btn').click + find('.user-search-btn').click end expect(page).not_to have_selector('.group_member') end it 'finds results' do - page.within '.member-search-form' do + page.within '.user-search-form' do fill_in 'search', with: group.name - find('.member-search-btn').click + find('.user-search-btn').click end expect(page).to have_selector('.group_member', count: 1) diff --git a/spec/features/projects/members/sorting_spec.rb b/spec/features/projects/members/sorting_spec.rb index 3d756ee1b42..5a14d334088 100644 --- a/spec/features/projects/members/sorting_spec.rb +++ b/spec/features/projects/members/sorting_spec.rb @@ -18,7 +18,7 @@ describe 'Projects > Members > Sorting' do expect(first_member).to include(maintainer.name) expect(second_member).to include(developer.name) - expect(page).to have_css('.qa-member-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending') + expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending') end it 'sorts by access level ascending' do @@ -26,7 +26,7 @@ describe 'Projects > Members > Sorting' do expect(first_member).to include(developer.name) expect(second_member).to include(maintainer.name) - expect(page).to have_css('.qa-member-sort-dropdown .dropdown-toggle-text', text: 'Access level, ascending') + expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Access level, ascending') end it 'sorts by access level descending' do @@ -34,7 +34,7 @@ describe 'Projects > Members > Sorting' do expect(first_member).to include(maintainer.name) expect(second_member).to include(developer.name) - expect(page).to have_css('.qa-member-sort-dropdown .dropdown-toggle-text', text: 'Access level, descending') + expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Access level, descending') end it 'sorts by last joined' do @@ -42,7 +42,7 @@ describe 'Projects > Members > Sorting' do expect(first_member).to include(maintainer.name) expect(second_member).to include(developer.name) - expect(page).to have_css('.qa-member-sort-dropdown .dropdown-toggle-text', text: 'Last joined') + expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Last joined') end it 'sorts by oldest joined' do @@ -50,7 +50,7 @@ describe 'Projects > Members > Sorting' do expect(first_member).to include(developer.name) expect(second_member).to include(maintainer.name) - expect(page).to have_css('.qa-member-sort-dropdown .dropdown-toggle-text', text: 'Oldest joined') + expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Oldest joined') end it 'sorts by name ascending' do @@ -58,7 +58,7 @@ describe 'Projects > Members > Sorting' do expect(first_member).to include(maintainer.name) expect(second_member).to include(developer.name) - expect(page).to have_css('.qa-member-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending') + expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending') end it 'sorts by name descending' do @@ -66,7 +66,7 @@ describe 'Projects > Members > Sorting' do expect(first_member).to include(developer.name) expect(second_member).to include(maintainer.name) - expect(page).to have_css('.qa-member-sort-dropdown .dropdown-toggle-text', text: 'Name, descending') + expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Name, descending') end it 'sorts by recent sign in', :clean_gitlab_redis_shared_state do @@ -74,7 +74,7 @@ describe 'Projects > Members > Sorting' do expect(first_member).to include(maintainer.name) expect(second_member).to include(developer.name) - expect(page).to have_css('.qa-member-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in') + expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in') end it 'sorts by oldest sign in', :clean_gitlab_redis_shared_state do @@ -82,7 +82,7 @@ describe 'Projects > Members > Sorting' do expect(first_member).to include(developer.name) expect(second_member).to include(maintainer.name) - expect(page).to have_css('.qa-member-sort-dropdown .dropdown-toggle-text', text: 'Oldest sign in') + expect(page).to have_css('.qa-user-sort-dropdowns .dropdown-toggle-text', text: 'Oldest sign in') end def visit_members_list(sort:) diff --git a/spec/features/search/user_searches_for_code_spec.rb b/spec/features/search/user_searches_for_code_spec.rb index 71af75640de..5a60991c1bf 100644 --- a/spec/features/search/user_searches_for_code_spec.rb +++ b/spec/features/search/user_searches_for_code_spec.rb @@ -6,6 +6,21 @@ describe 'User searches for code' do let(:user) { create(:user) } let(:project) { create(:project, :repository, namespace: user.namespace) } + def submit_search(search, with_send_keys: false) + page.within('.search') do + field = find_field('search') + field.fill_in(with: search) + + if with_send_keys + field.send_keys(:enter) + else + click_button("Go") + end + end + + click_link('Code') + end + context 'when signed in' do before do project.add_maintainer(user) @@ -15,12 +30,7 @@ describe 'User searches for code' do it 'finds a file' do visit(project_path(project)) - page.within('.search') do - fill_in('search', with: 'application.js') - click_button('Go') - end - - click_link('Code') + submit_search('application.js') expect(page).to have_selector('.file-content .code') expect(page).to have_selector("span.line[lang='javascript']") @@ -48,6 +58,50 @@ describe 'User searches for code' do end end end + + context 'search code within refs', :js do + let(:ref_name) { 'v1.0.0' } + + before do + visit(project_tree_path(project, ref_name)) + submit_search('gitlab-grack', with_send_keys: true) + end + + it 'shows ref switcher in code result summary' do + expect(find('.js-project-refs-dropdown')).to have_text(ref_name) + end + it 'persists branch name across search' do + find('.btn-search').click + expect(find('.js-project-refs-dropdown')).to have_text(ref_name) + end + + # this example is use to test the desgine that the refs is not + # only repersent the branch as well as the tags. + it 'ref swither list all the branchs and tags' do + find('.js-project-refs-dropdown').click + expect(find('.dropdown-page-one .dropdown-content')).to have_link('sha-starting-with-large-number') + expect(find('.dropdown-page-one .dropdown-content')).to have_link('v1.0.0') + end + + it 'search result changes when refs switched' do + expect(find('.search-results')).not_to have_content('path = gitlab-grack') + find('.js-project-refs-dropdown').click + find('.dropdown-page-one .dropdown-content').click_link('master') + expect(find('.search-results')).to have_content('path = gitlab-grack') + end + end + + it 'no ref switcher shown in issue result summary', :js do + issue = create(:issue, title: 'test', project: project) + visit(project_tree_path(project)) + submit_search('test', with_send_keys: true) + expect(page).to have_selector('.js-project-refs-dropdown') + page.within('.search-filter') do + click_link('Issues') + end + expect(find(:css, '.search-results')).to have_link(issue.title) + expect(page).not_to have_selector('.js-project-refs-dropdown') + end end context 'when signed out' do @@ -58,8 +112,7 @@ describe 'User searches for code' do end it 'finds code' do - fill_in('search', with: 'rspec') - click_button('Go') + submit_search('rspec') page.within('.results') do expect(find(:css, '.search-results')).to have_content('Update capybara, rspec-rails, poltergeist to recent versions') diff --git a/spec/features/search/user_uses_header_search_field_spec.rb b/spec/features/search/user_uses_header_search_field_spec.rb index 29ce5425323..c781048d06d 100644 --- a/spec/features/search/user_uses_header_search_field_spec.rb +++ b/spec/features/search/user_uses_header_search_field_spec.rb @@ -22,7 +22,7 @@ describe 'User uses header search field', :js do fill_in('search', with: 'gitlab') find('#search').native.send_keys(:enter) - page.within('.breadcrumbs-sub-title') do + page.within('.page-title') do expect(page).to have_content('Search') end end diff --git a/spec/features/search/user_uses_search_filters_spec.rb b/spec/features/search/user_uses_search_filters_spec.rb index f5cda15b38a..fbd7da3c643 100644 --- a/spec/features/search/user_uses_search_filters_spec.rb +++ b/spec/features/search/user_uses_search_filters_spec.rb @@ -22,7 +22,7 @@ describe 'User uses search filters', :js do wait_for_requests - page.within('.search-holder') do + page.within('.search-page-form') do click_link(group.name) end diff --git a/spec/features/user_opens_link_to_comment_spec.rb b/spec/features/user_opens_link_to_comment_spec.rb index f1e07e55799..9533a4fe40d 100644 --- a/spec/features/user_opens_link_to_comment_spec.rb +++ b/spec/features/user_opens_link_to_comment_spec.rb @@ -18,8 +18,13 @@ describe 'User opens link to comment', :js do visit Gitlab::UrlBuilder.build(note) + wait_for_requests + expect(page.find('#discussion-filter-dropdown')).to have_content('Show all activity') expect(page).not_to have_content('Something went wrong while fetching comments') + + # Auto-switching to show all notes shouldn't be persisted + expect(user.reload.notes_filter_for(note.noteable)).to eq(UserPreference::NOTES_FILTERS[:only_activity]) end end diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index bcde730c40b..879ff01f294 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -113,13 +113,13 @@ describe IssuesFinder do let(:params) { { milestone_title: 'Any' } } it 'returns issues with any assigned milestone' do - expect(issues).to contain_exactly(issue1, issue2, issue3, issue4) + expect(issues).to contain_exactly(issue1) end it 'returns issues with any assigned milestone (deprecated)' do params[:milestone_title] = Milestone::Any.title - expect(issues).to contain_exactly(issue1, issue2, issue3, issue4) + expect(issues).to contain_exactly(issue1) end end diff --git a/spec/finders/starred_projects_finder_spec.rb b/spec/finders/starred_projects_finder_spec.rb new file mode 100644 index 00000000000..7aa8251c3ab --- /dev/null +++ b/spec/finders/starred_projects_finder_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe StarredProjectsFinder do + let(:project1) { create(:project, :public, :empty_repo) } + let(:project2) { create(:project, :public, :empty_repo) } + let(:other_project) { create(:project, :public, :empty_repo) } + + let(:user) { create(:user) } + let(:other_user) { create(:user) } + + before do + user.toggle_star(project1) + user.toggle_star(project2) + end + + describe '#execute' do + let(:finder) { described_class.new(user, params: {}, current_user: current_user) } + + subject { finder.execute } + + describe 'as same user' do + let(:current_user) { user } + + it { is_expected.to contain_exactly(project1, project2) } + end + + describe 'as other user' do + let(:current_user) { other_user } + + it { is_expected.to contain_exactly(project1, project2) } + end + + describe 'as no user' do + let(:current_user) { nil } + + it { is_expected.to contain_exactly(project1, project2) } + end + end +end diff --git a/spec/finders/users_star_projects_finder_spec.rb b/spec/finders/users_star_projects_finder_spec.rb new file mode 100644 index 00000000000..fb1d8088f44 --- /dev/null +++ b/spec/finders/users_star_projects_finder_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe UsersStarProjectsFinder do + let(:project) { create(:project, :public, :empty_repo) } + + let(:user) { create(:user) } + let(:private_user) { create(:user, private_profile: true) } + let(:other_user) { create(:user) } + + before do + user.toggle_star(project) + private_user.toggle_star(project) + end + + describe '#execute' do + let(:finder) { described_class.new(project, {}, current_user: current_user) } + let(:public_stars) { user.users_star_projects } + let(:private_stars) { private_user.users_star_projects } + + subject { finder.execute } + + describe 'as same user' do + let(:current_user) { private_user } + + it { is_expected.to match_array(private_stars + public_stars) } + end + + describe 'as other user' do + let(:current_user) { other_user } + + it { is_expected.to match_array(public_stars) } + end + + describe 'as no user' do + let(:current_user) { nil } + + it { is_expected.to match_array(public_stars) } + end + end +end diff --git a/spec/fixtures/api/schemas/public_api/v4/group_labels.json b/spec/fixtures/api/schemas/public_api/v4/group_labels.json deleted file mode 100644 index fbde45f2904..00000000000 --- a/spec/fixtures/api/schemas/public_api/v4/group_labels.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "type": "array", - "items": { - "type": "object", - "properties" : { - "id" : { "type": "integer" }, - "name" : { "type": "string "}, - "color" : { "type": "string "}, - "text_color" : { "type": "string "}, - "description" : { "type": "string "}, - "open_issues_count" : { "type": "integer "}, - "closed_issues_count" : { "type": "integer "}, - "open_merge_requests_count" : { "type": "integer "}, - "subscribed" : { "type": "boolean" }, - "priority" : { "type": "null" } - }, - "additionalProperties": false - } -} diff --git a/spec/fixtures/api/schemas/public_api/v4/labels/label.json b/spec/fixtures/api/schemas/public_api/v4/labels/label.json new file mode 100644 index 00000000000..a116b33572d --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/labels/label.json @@ -0,0 +1,11 @@ +{ + "type": "object", + "properties": { + "id": { "type": "integer" }, + "name": { "type": "string" }, + "color": { "type": "string" }, + "text_color": { "type": "string" }, + "description": { "type": ["string", "null"] }, + "subscribed": { "type": "boolean" } + } +} diff --git a/spec/fixtures/api/schemas/public_api/v4/labels/label_with_counts.json b/spec/fixtures/api/schemas/public_api/v4/labels/label_with_counts.json new file mode 100644 index 00000000000..2331932e07d --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/labels/label_with_counts.json @@ -0,0 +1,16 @@ +{ + "type": "object", + "properties": { + "allOf": [ + { "$ref": "label.json" }, + { + "type": "object", + "properties": { + "open_issues_count": { "type": "integer" }, + "closed_issues_count": { "type": "integer" }, + "open_merge_requests_count": { "type": "integer" } + } + } + ] + } +} diff --git a/spec/fixtures/api/schemas/public_api/v4/labels/project_label.json b/spec/fixtures/api/schemas/public_api/v4/labels/project_label.json new file mode 100644 index 00000000000..a9a065ee71f --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/labels/project_label.json @@ -0,0 +1,15 @@ +{ + "type": "object", + "properties": { + "allOf": [ + { "$ref": "label.json" }, + { + "type": "object", + "properties": { + "priority": { "type": ["integer", "null"] }, + "is_project_label": { "type": "boolean" } + } + } + ] + } +} diff --git a/spec/fixtures/api/schemas/public_api/v4/labels/project_label_with_counts.json b/spec/fixtures/api/schemas/public_api/v4/labels/project_label_with_counts.json new file mode 100644 index 00000000000..87b90b2b3b5 --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/labels/project_label_with_counts.json @@ -0,0 +1,9 @@ +{ + "type": "object", + "properties": { + "allOf": [ + { "$ref": "project_label.json" }, + { "$ref": "label_with_counts.json" } + ] + } +} diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metrics.json b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metrics.json index 33393805464..9c1be32645a 100644 --- a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metrics.json +++ b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metrics.json @@ -16,7 +16,8 @@ "unit": { "type": "string" }, "label": { "type": "string" }, "track": { "type": "string" }, - "prometheus_endpoint_path": { "type": "string" } + "prometheus_endpoint_path": { "type": "string" }, + "metric_id": { "type": "number" } }, "additionalProperties": false } diff --git a/spec/frontend/fixtures/projects.rb b/spec/frontend/fixtures/projects.rb index b6c29003e57..91e3b65215a 100644 --- a/spec/frontend/fixtures/projects.rb +++ b/spec/frontend/fixtures/projects.rb @@ -18,8 +18,6 @@ describe 'Projects (JavaScript fixtures)', type: :controller do end before do - stub_licensed_features(variable_environment_scope: true) - project.add_maintainer(admin) sign_in(admin) allow(SecureRandom).to receive(:hex).and_return('securerandomhex:thereisnospoon') diff --git a/spec/frontend/lib/utils/forms_spec.js b/spec/frontend/lib/utils/forms_spec.js new file mode 100644 index 00000000000..cac17235f0d --- /dev/null +++ b/spec/frontend/lib/utils/forms_spec.js @@ -0,0 +1,74 @@ +import { serializeForm } from '~/lib/utils/forms'; + +describe('lib/utils/forms', () => { + const createDummyForm = inputs => { + const form = document.createElement('form'); + + form.innerHTML = inputs + .map(({ type, name, value }) => { + let str = ``; + if (type === 'select') { + str = `<select name="${name}">`; + value.forEach(v => { + if (v.length > 0) { + str += `<option value="${v}"></option> `; + } + }); + str += `</select>`; + } else { + str = `<input type="${type}" name="${name}" value="${value}" checked/>`; + } + return str; + }) + .join(''); + + return form; + }; + + describe('serializeForm', () => { + it('returns an object of key values from inputs', () => { + const form = createDummyForm([ + { type: 'text', name: 'foo', value: 'foo-value' }, + { type: 'text', name: 'bar', value: 'bar-value' }, + ]); + + const data = serializeForm(form); + + expect(data).toEqual({ + foo: 'foo-value', + bar: 'bar-value', + }); + }); + + it('works with select', () => { + const form = createDummyForm([ + { type: 'select', name: 'foo', value: ['foo-value1', 'foo-value2'] }, + { type: 'text', name: 'bar', value: 'bar-value1' }, + ]); + + const data = serializeForm(form); + + expect(data).toEqual({ + foo: 'foo-value1', + bar: 'bar-value1', + }); + }); + + it('works with multiple inputs of the same name', () => { + const form = createDummyForm([ + { type: 'checkbox', name: 'foo', value: 'foo-value3' }, + { type: 'checkbox', name: 'foo', value: 'foo-value2' }, + { type: 'checkbox', name: 'foo', value: 'foo-value1' }, + { type: 'text', name: 'bar', value: 'bar-value2' }, + { type: 'text', name: 'bar', value: 'bar-value1' }, + ]); + + const data = serializeForm(form); + + expect(data).toEqual({ + foo: ['foo-value3', 'foo-value2', 'foo-value1'], + bar: ['bar-value2', 'bar-value1'], + }); + }); + }); +}); diff --git a/spec/frontend/lib/utils/url_utility_spec.js b/spec/frontend/lib/utils/url_utility_spec.js index c771984a137..a986bc49f28 100644 --- a/spec/frontend/lib/utils/url_utility_spec.js +++ b/spec/frontend/lib/utils/url_utility_spec.js @@ -34,6 +34,41 @@ describe('URL utility', () => { }); }); + describe('getParameterValues', () => { + beforeEach(() => { + setWindowLocation({ + href: 'https://gitlab.com?test=passing&multiple=1&multiple=2', + // make our fake location act like real window.location.toString + // URL() (used in getParameterValues) does this if passed an object + toString() { + return this.href; + }, + }); + }); + + it('returns empty array for no params', () => { + expect(urlUtils.getParameterValues()).toEqual([]); + }); + + it('returns empty array for non-matching params', () => { + expect(urlUtils.getParameterValues('notFound')).toEqual([]); + }); + + it('returns single match', () => { + expect(urlUtils.getParameterValues('test')).toEqual(['passing']); + }); + + it('returns multiple matches', () => { + expect(urlUtils.getParameterValues('multiple')).toEqual(['1', '2']); + }); + + it('accepts url as second arg', () => { + const url = 'https://gitlab.com?everything=works'; + expect(urlUtils.getParameterValues('everything', url)).toEqual(['works']); + expect(urlUtils.getParameterValues('test', url)).toEqual([]); + }); + }); + describe('mergeUrlParams', () => { it('adds w', () => { expect(urlUtils.mergeUrlParams({ w: 1 }, '#frag')).toBe('?w=1#frag'); diff --git a/spec/frontend/tracking_spec.js b/spec/frontend/tracking_spec.js index 7e462e9a6ce..cd0bf50f8e9 100644 --- a/spec/frontend/tracking_spec.js +++ b/spec/frontend/tracking_spec.js @@ -31,13 +31,6 @@ describe('Tracking', () => { expect(snowplowSpy).not.toHaveBeenCalled(); }); - - it('skips tracking if ', () => { - window.snowplow = false; - Tracking.event('_category_', '_eventName_'); - - expect(snowplowSpy).not.toHaveBeenCalled(); - }); }); describe('tracking interface events', () => { diff --git a/spec/helpers/users_helper_spec.rb b/spec/helpers/users_helper_spec.rb index f3649495493..a6623bc7941 100644 --- a/spec/helpers/users_helper_spec.rb +++ b/spec/helpers/users_helper_spec.rb @@ -27,7 +27,7 @@ describe UsersHelper do context 'with public profile' do it 'includes all the expected tabs' do - expect(tabs).to include(:activity, :groups, :contributed, :projects, :snippets) + expect(tabs).to include(:activity, :groups, :contributed, :projects, :starred, :snippets) end end diff --git a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js index e6a969bd855..b2fe315f6c6 100644 --- a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js +++ b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js @@ -224,7 +224,7 @@ describe('AjaxFormVariableList', () => { describe('maskableRegex', () => { it('takes in the regex provided by the data attribute', () => { - expect(container.dataset.maskableRegex).toBe('^[a-zA-Z0-9_+=/-]{8,}$'); + expect(container.dataset.maskableRegex).toBe('^[a-zA-Z0-9_+=/@:-]{8,}$'); expect(ajaxVariableList.maskableRegex).toBe(container.dataset.maskableRegex); }); }); diff --git a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js index 064113e879a..c8d6f789ed0 100644 --- a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js +++ b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js @@ -162,7 +162,7 @@ describe('VariableList', () => { }); it('has a regex provided via a data attribute', () => { - expect($wrapper.attr('data-maskable-regex')).toBe('^[a-zA-Z0-9_+=/-]{8,}$'); + expect($wrapper.attr('data-maskable-regex')).toBe('^[a-zA-Z0-9_+=/@:-]{8,}$'); }); it('allows values that are 8 characters long', done => { diff --git a/spec/frontend/jobs/components/empty_state_spec.js b/spec/javascripts/jobs/components/empty_state_spec.js index dfba5a936ee..c6eac4e27b3 100644 --- a/spec/frontend/jobs/components/empty_state_spec.js +++ b/spec/javascripts/jobs/components/empty_state_spec.js @@ -105,7 +105,7 @@ describe('Empty State', () => { }); describe('with playbale action and not scheduled job', () => { - it('renders manual variables form', () => { + beforeEach(() => { vm = mountComponent(Component, { ...props, content, @@ -117,9 +117,15 @@ describe('Empty State', () => { method: 'post', }, }); + }); + it('renders manual variables form', () => { expect(vm.$el.querySelector('.js-manual-vars-form')).not.toBeNull(); }); + + it('does not render the empty state action', () => { + expect(vm.$el.querySelector('.js-job-empty-state-action')).toBeNull(); + }); }); describe('with playbale action and scheduled job', () => { diff --git a/spec/javascripts/jobs/components/job_log_spec.js b/spec/javascripts/jobs/components/job_log_spec.js index 7e2ec2ec3f7..3485eec7763 100644 --- a/spec/javascripts/jobs/components/job_log_spec.js +++ b/spec/javascripts/jobs/components/job_log_spec.js @@ -98,5 +98,25 @@ describe('Job Log', () => { .then(done) .catch(done.fail); }); + + it('toggles hidden class to the sibilings rows when header section is clicked', done => { + vm.$nextTick() + .then(() => { + const { section } = vm.$el.querySelector('.js-section-header').dataset; + vm.$el.querySelector('.js-section-header').click(); + + vm.$el.querySelectorAll(`.js-s-${section}:not(.js-section-header)`).forEach(el => { + expect(el.classList.contains('hidden')).toEqual(true); + }); + + vm.$el.querySelector('.js-section-header').click(); + + vm.$el.querySelectorAll(`.js-s-${section}:not(.js-section-header)`).forEach(el => { + expect(el.classList.contains('hidden')).toEqual(false); + }); + }) + .then(done) + .catch(done.fail); + }); }); }); diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index d3e10194d92..36f650d5933 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -307,7 +307,7 @@ describe('Dashboard', () => { }); spyOn(component.$store, 'dispatch').and.stub(); - const getTimeDiffSpy = spyOnDependency(Dashboard, 'getTimeDiff'); + const getTimeDiffSpy = spyOnDependency(Dashboard, 'getTimeDiff').and.callThrough(); component.$store.commit( `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`, @@ -319,7 +319,7 @@ describe('Dashboard', () => { Vue.nextTick() .then(() => { expect(component.$store.dispatch).toHaveBeenCalled(); - expect(getTimeDiffSpy).toHaveBeenCalledWith(component.selectedTimeWindow); + expect(getTimeDiffSpy).toHaveBeenCalled(); done(); }) @@ -327,7 +327,17 @@ describe('Dashboard', () => { }); it('shows a specific time window selected from the url params', done => { - spyOnDependency(Dashboard, 'getParameterValues').and.returnValue(['thirtyMinutes']); + const start = 1564439536; + const end = 1564441336; + spyOnDependency(Dashboard, 'getTimeDiff').and.returnValue({ + start, + end, + }); + spyOnDependency(Dashboard, 'getParameterValues').and.callFake(param => { + if (param === 'start') return [start]; + if (param === 'end') return [end]; + return []; + }); component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), diff --git a/spec/javascripts/monitoring/store/actions_spec.js b/spec/javascripts/monitoring/store/actions_spec.js index 677455275de..955a39e03a5 100644 --- a/spec/javascripts/monitoring/store/actions_spec.js +++ b/spec/javascripts/monitoring/store/actions_spec.js @@ -313,8 +313,8 @@ describe('Monitoring store actions', () => { it('commits prometheus query result', done => { const commit = jasmine.createSpy(); const params = { - start: '1557216349.469', - end: '1557218149.469', + start: '2019-08-06T12:40:02.184Z', + end: '2019-08-06T20:40:02.184Z', }; const metric = metricsDashboardResponse.dashboard.panel_groups[0].panels[0].metrics[0]; const state = storeState(); diff --git a/spec/javascripts/monitoring/utils_spec.js b/spec/javascripts/monitoring/utils_spec.js index 5570d57b8b2..e22e8cdc03d 100644 --- a/spec/javascripts/monitoring/utils_spec.js +++ b/spec/javascripts/monitoring/utils_spec.js @@ -3,28 +3,38 @@ import { timeWindows } from '~/monitoring/constants'; import { graphDataPrometheusQuery, graphDataPrometheusQueryRange } from './mock_data'; describe('getTimeDiff', () => { + function secondsBetween({ start, end }) { + return (new Date(end) - new Date(start)) / 1000; + } + + function minutesBetween(timeRange) { + return secondsBetween(timeRange) / 60; + } + + function hoursBetween(timeRange) { + return minutesBetween(timeRange) / 60; + } + it('defaults to an 8 hour (28800s) difference', () => { const params = getTimeDiff(); - expect(params.end - params.start).toEqual(28800); + expect(hoursBetween(params)).toEqual(8); }); it('accepts time window as an argument', () => { - const params = getTimeDiff(timeWindows.thirtyMinutes); + const params = getTimeDiff('thirtyMinutes'); - expect(params.end - params.start).not.toEqual(28800); + expect(minutesBetween(params)).toEqual(30); }); it('returns a value for every defined time window', () => { const nonDefaultWindows = Object.keys(timeWindows).filter(window => window !== 'eightHours'); - nonDefaultWindows.forEach(window => { - const params = getTimeDiff(timeWindows[window]); - const diff = params.end - params.start; + nonDefaultWindows.forEach(timeWindow => { + const params = getTimeDiff(timeWindow); - // Ensure we're not returning the default, 28800 (the # of seconds in 8 hrs) - expect(diff).not.toEqual(28800); - expect(typeof diff).toEqual('number'); + // Ensure we're not returning the default + expect(hoursBetween(params)).not.toEqual(8); }); }); }); diff --git a/spec/javascripts/notes/stores/actions_spec.js b/spec/javascripts/notes/stores/actions_spec.js index c461c28a37b..e55aa0e965a 100644 --- a/spec/javascripts/notes/stores/actions_spec.js +++ b/spec/javascripts/notes/stores/actions_spec.js @@ -892,4 +892,31 @@ describe('Actions Notes Store', () => { }); }); }); + + describe('filterDiscussion', () => { + const path = 'some-discussion-path'; + const filter = 0; + + beforeEach(() => { + dispatch.and.returnValue(new Promise(() => {})); + }); + + it('fetches discussions with filter and persistFilter false', () => { + actions.filterDiscussion({ dispatch }, { path, filter, persistFilter: false }); + + expect(dispatch.calls.allArgs()).toEqual([ + ['setLoadingState', true], + ['fetchDiscussions', { path, filter, persistFilter: false }], + ]); + }); + + it('fetches discussions with filter and persistFilter true', () => { + actions.filterDiscussion({ dispatch }, { path, filter, persistFilter: true }); + + expect(dispatch.calls.allArgs()).toEqual([ + ['setLoadingState', true], + ['fetchDiscussions', { path, filter, persistFilter: true }], + ]); + }); + }); }); diff --git a/spec/lib/banzai/filter/commit_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_reference_filter_spec.rb index 1bc0335cfc0..326703eea05 100644 --- a/spec/lib/banzai/filter/commit_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/commit_reference_filter_spec.rb @@ -105,6 +105,17 @@ describe Banzai::Filter::CommitReferenceFilter do expect(doc.css('a').first[:href]).to eq(url) end + + context "a doc with many (29) strings that could be SHAs" do + let!(:oids) { noteable.commits.collect(&:id) } + + it 'makes only a single request to Gitaly' do + expect(Gitlab::GitalyClient).to receive(:allow_n_plus_1_calls).exactly(0).times + expect(Gitlab::Git::Commit).to receive(:batch_by_oid).once.and_call_original + + reference_filter("A big list of SHAs #{oids.join(", ")}", noteable: noteable) + end + end end end diff --git a/spec/lib/gitlab/ci/ansi2html_spec.rb b/spec/lib/gitlab/ci/ansi2html_spec.rb index eaf06ed8992..b6b3de4bc4a 100644 --- a/spec/lib/gitlab/ci/ansi2html_spec.rb +++ b/spec/lib/gitlab/ci/ansi2html_spec.rb @@ -209,7 +209,7 @@ describe Gitlab::Ci::Ansi2html do let(:section_start) { "section_start:#{section_start_time.to_i}:#{section_name}\r\033[0K"} let(:section_end) { "section_end:#{section_end_time.to_i}:#{section_name}\r\033[0K"} let(:section_start_html) do - '<div class="js-section-start fa fa-caret-down append-right-8 cursor-pointer"' \ + '<div class="js-section-start fa fa-caret-down pr-2 cursor-pointer"' \ " data-timestamp=\"#{section_start_time.to_i}\" data-section=\"#{class_name(section_name)}\"" \ ' role="button"></div>' end @@ -233,8 +233,8 @@ describe Gitlab::Ci::Ansi2html do it 'prints light red' do text = "#{section_start}\e[91mHello\e[0m\nLine 1\nLine 2\nLine 3\n#{section_end}" - header = %{<span class="term-fg-l-red section js-section-header section-header js-s-#{class_name(section_name)}">Hello</span>} - line_break = %{<span class="section js-section-header section-header js-s-#{class_name(section_name)}"><br/></span>} + header = %{<span class="term-fg-l-red section js-section-header section-header cursor-pointer js-s-#{class_name(section_name)}">Hello</span>} + line_break = %{<span class="section js-section-header section-header cursor-pointer js-s-#{class_name(section_name)}"><br/></span>} output_line = %{<span class="section line js-s-#{class_name(section_name)}">Line 1<br/>Line 2<br/>Line 3<br/></span>} html = "#{section_start_html}#{header}#{line_break}#{output_line}#{section_end_html}" diff --git a/spec/lib/gitlab/ci/build/policy/variables_spec.rb b/spec/lib/gitlab/ci/build/policy/variables_spec.rb index 42a2a9fda2e..f712f47a558 100644 --- a/spec/lib/gitlab/ci/build/policy/variables_spec.rb +++ b/spec/lib/gitlab/ci/build/policy/variables_spec.rb @@ -91,5 +91,38 @@ describe Gitlab::Ci::Build::Policy::Variables do expect(policy).to be_satisfied_by(pipeline, seed) end end + + context 'when using project ci variables in environment scope' do + let(:ci_build) do + build(:ci_build, pipeline: pipeline, + project: project, + ref: 'master', + stage: 'review', + environment: 'test/$CI_JOB_STAGE/1') + end + + before do + create(:ci_variable, project: project, + key: 'SCOPED_VARIABLE', + value: 'my-value-1') + + create(:ci_variable, project: project, + key: 'SCOPED_VARIABLE', + value: 'my-value-2', + environment_scope: 'test/review/*') + end + + it 'is satisfied by scoped variable match' do + policy = described_class.new(['$SCOPED_VARIABLE == "my-value-2"']) + + expect(policy).to be_satisfied_by(pipeline, seed) + end + + it 'is not satisfied when matching against overridden variable' do + policy = described_class.new(['$SCOPED_VARIABLE == "my-value-1"']) + + expect(policy).not_to be_satisfied_by(pipeline, seed) + end + end end end diff --git a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb index e7670c9d523..1d404915617 100644 --- a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb +++ b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb @@ -13,7 +13,8 @@ describe Gitlab::ContentSecurityPolicy::ConfigLoader do child_src: "'self' https://child.example.com", default_src: "'self' https://other.example.com", script_src: "'self' https://script.exammple.com ", - worker_src: "data: https://worker.example.com" + worker_src: "data: https://worker.example.com", + report_uri: "http://example.com" } } end @@ -46,6 +47,7 @@ describe Gitlab::ContentSecurityPolicy::ConfigLoader do expect(policy.directives['default-src']).to eq(expected_config(:default_src)) expect(policy.directives['child-src']).to eq(expected_config(:child_src)) expect(policy.directives['worker-src']).to eq(expected_config(:worker_src)) + expect(policy.directives['report-uri']).to eq(expected_config(:report_uri)) end it 'ignores malformed policy statements' do diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb index 2759412add8..eced96a4c77 100644 --- a/spec/lib/gitlab/current_settings_spec.rb +++ b/spec/lib/gitlab/current_settings_spec.rb @@ -14,6 +14,16 @@ describe Gitlab::CurrentSettings do end end + describe '.expire_current_application_settings', :use_clean_rails_memory_store_caching, :request_store do + include_context 'with settings in cache' + + it 'expires the cache' do + described_class.expire_current_application_settings + + expect(ActiveRecord::QueryRecorder.new { described_class.current_application_settings }.count).not_to eq(0) + end + end + describe '#current_application_settings', :use_clean_rails_memory_store_caching do it 'allows keys to be called directly' do db_settings = create(:application_setting, diff --git a/spec/lib/gitlab/github_import/importer/releases_importer_spec.rb b/spec/lib/gitlab/github_import/importer/releases_importer_spec.rb index 23ae026fb14..0e5419e6c5e 100644 --- a/spec/lib/gitlab/github_import/importer/releases_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/releases_importer_spec.rb @@ -6,6 +6,7 @@ describe Gitlab::GithubImport::Importer::ReleasesImporter do let(:importer) { described_class.new(project, client) } let(:created_at) { Time.new(2017, 1, 1, 12, 00) } let(:updated_at) { Time.new(2017, 1, 1, 12, 15) } + let(:released_at) { Time.new(2017, 1, 1, 12, 00) } let(:release) do double( @@ -13,7 +14,8 @@ describe Gitlab::GithubImport::Importer::ReleasesImporter do tag_name: '1.0', body: 'This is my release', created_at: created_at, - updated_at: updated_at + updated_at: updated_at, + published_at: released_at ) end @@ -23,7 +25,8 @@ describe Gitlab::GithubImport::Importer::ReleasesImporter do tag_name: '1.0', description: 'This is my release', created_at: created_at, - updated_at: updated_at + updated_at: updated_at, + released_at: released_at } expect(importer).to receive(:build_releases).and_return([release_hash]) diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb index a410e4eab45..4676db6b8d8 100644 --- a/spec/lib/gitlab/highlight_spec.rb +++ b/spec/lib/gitlab/highlight_spec.rb @@ -62,14 +62,6 @@ describe Gitlab::Highlight do expect(lines[2].text).to eq(' """') end - context 'since param is present' do - it 'highlights with the LC starting from "since" param' do - lines = described_class.highlight(file_name, content, since: 2).lines - - expect(lines[0]).to include('LC2') - end - end - context 'diff highlighting' do let(:file_name) { 'test.diff' } let(:content) { "+aaa\n+bbb\n- ccc\n ddd\n"} diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index ada8c649ff6..fddb5066d6f 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -277,7 +277,6 @@ project: - bugzilla_service - gitlab_issue_tracker_service - external_wiki_service -- kubernetes_service - mock_ci_service - mock_deployment_service - mock_monitoring_service diff --git a/spec/lib/gitlab/metrics/dashboard/defaults_spec.rb b/spec/lib/gitlab/metrics/dashboard/defaults_spec.rb new file mode 100644 index 00000000000..420b246b3f5 --- /dev/null +++ b/spec/lib/gitlab/metrics/dashboard/defaults_spec.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Metrics::Dashboard::Defaults do + it { is_expected.to be_const_defined(:DEFAULT_PANEL_TYPE) } + it { is_expected.to be_const_defined(:DEFAULT_PANEL_WEIGHT) } +end diff --git a/spec/lib/gitlab/metrics/dashboard/finder_spec.rb b/spec/lib/gitlab/metrics/dashboard/finder_spec.rb index e57c7326320..ce1bb49f5c9 100644 --- a/spec/lib/gitlab/metrics/dashboard/finder_spec.rb +++ b/spec/lib/gitlab/metrics/dashboard/finder_spec.rb @@ -5,10 +5,9 @@ require 'spec_helper' describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_caching do include MetricsDashboardHelpers - set(:project) { build(:project) } + set(:project) { create(:project) } set(:user) { create(:user) } set(:environment) { create(:environment, project: project) } - let(:system_dashboard_path) { ::Metrics::Dashboard::SystemDashboardService::SYSTEM_DASHBOARD_PATH} before do project.add_maintainer(user) @@ -52,9 +51,80 @@ describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_cachi end context 'when the dashboard is expected to be embedded' do - let(:service_call) { described_class.find(project, user, environment, dashboard_path: nil, embedded: true) } + let(:service_call) { described_class.find(project, user, environment, **params) } + let(:params) { { embedded: true } } it_behaves_like 'valid embedded dashboard service response' + + context 'when params are incomplete' do + let(:params) { { embedded: true, dashboard_path: system_dashboard_path } } + + it_behaves_like 'valid embedded dashboard service response' + end + + context 'when the panel is specified' do + context 'as a custom metric' do + let(:params) do + { embedded: true, + dashboard_path: system_dashboard_path, + group: business_metric_title, + title: 'title', + y_label: 'y_label' } + end + + it_behaves_like 'misconfigured dashboard service response', :not_found + + context 'when the metric exists' do + before do + create(:prometheus_metric, project: project) + end + + it_behaves_like 'valid embedded dashboard service response' + end + end + + context 'as a project-defined panel' do + let(:dashboard_path) { '.gitlab/dashboard/test.yml' } + let(:params) do + { embedded: true, + dashboard_path: dashboard_path, + group: 'Group A', + title: 'Super Chart A1', + y_label: 'y_label' } + end + + it_behaves_like 'misconfigured dashboard service response', :not_found + + context 'when the metric exists' do + let(:project) { project_with_dashboard(dashboard_path) } + + it_behaves_like 'valid embedded dashboard service response' + end + end + end + end + end + + describe '.find_raw' do + let(:dashboard) { YAML.load_file(Rails.root.join('config', 'prometheus', 'common_metrics.yml')) } + let(:params) { {} } + + subject { described_class.find_raw(project, **params) } + + it { is_expected.to eq dashboard } + + context 'when the system dashboard is specified' do + let(:params) { { dashboard_path: system_dashboard_path } } + + it { is_expected.to eq dashboard } + end + + context 'when an existing project dashboard is specified' do + let(:dashboard) { YAML.safe_load(fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml')) } + let(:params) { { dashboard_path: '.gitlab/dashboards/test.yml' } } + let(:project) { project_with_dashboard(params[:dashboard_path]) } + + it { is_expected.to eq dashboard } end end diff --git a/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb b/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb new file mode 100644 index 00000000000..095d0a2df78 --- /dev/null +++ b/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Metrics::Dashboard::ServiceSelector do + include MetricsDashboardHelpers + + describe '#call' do + let(:arguments) { {} } + + subject { described_class.call(arguments) } + + it { is_expected.to be Metrics::Dashboard::SystemDashboardService } + + context 'when just the dashboard path is provided' do + let(:arguments) { { dashboard_path: '.gitlab/dashboards/test.yml' } } + + it { is_expected.to be Metrics::Dashboard::ProjectDashboardService } + + context 'when the path is for the system dashboard' do + let(:arguments) { { dashboard_path: system_dashboard_path } } + + it { is_expected.to be Metrics::Dashboard::SystemDashboardService } + end + end + + context 'when the embedded flag is provided' do + let(:arguments) { { embedded: true } } + + it { is_expected.to be Metrics::Dashboard::DefaultEmbedService } + + context 'when an incomplete set of dashboard identifiers are provided' do + let(:arguments) { { embedded: true, dashboard_path: '.gitlab/dashboards/test.yml' } } + + it { is_expected.to be Metrics::Dashboard::DefaultEmbedService } + end + + context 'when all the chart identifiers are provided' do + let(:arguments) do + { + embedded: true, + dashboard_path: '.gitlab/dashboards/test.yml', + group: 'Important Metrics', + title: 'Total Requests', + y_label: 'req/sec' + } + end + + it { is_expected.to be Metrics::Dashboard::DynamicEmbedService } + end + + context 'when all chart params expect dashboard_path are provided' do + let(:arguments) do + { + embedded: true, + group: 'Important Metrics', + title: 'Total Requests', + y_label: 'req/sec' + } + end + + it { is_expected.to be Metrics::Dashboard::DynamicEmbedService } + end + + context 'with a system dashboard and "custom" group' do + let(:arguments) do + { + embedded: true, + dashboard_path: system_dashboard_path, + group: business_metric_title, + title: 'Total Requests', + y_label: 'req/sec' + } + end + + it { is_expected.to be Metrics::Dashboard::CustomMetricEmbedService } + end + end + end +end diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb index a1079e54975..ba295386a55 100644 --- a/spec/lib/gitlab/regex_spec.rb +++ b/spec/lib/gitlab/regex_spec.rb @@ -32,6 +32,14 @@ describe Gitlab::Regex do it { is_expected.not_to match('/') } end + describe '.environment_scope_regex' do + subject { described_class.environment_scope_regex } + + it { is_expected.to match('foo') } + it { is_expected.to match('foo*Z') } + it { is_expected.not_to match('!!()()') } + end + describe '.environment_slug_regex' do subject { described_class.environment_slug_regex } diff --git a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb index 98286eb432d..5621d3d17d1 100644 --- a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb +++ b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' describe Gitlab::SidekiqLogging::StructuredLogger do describe '#call' do let(:timestamp) { Time.iso8601('2018-01-01T12:00:00Z') } - let(:created_at) { timestamp } - let(:scheduling_latency_s) { 0.0 } + let(:created_at) { timestamp - 1.second } + let(:scheduling_latency_s) { 1.0 } let(:job) do { @@ -153,5 +153,29 @@ describe Gitlab::SidekiqLogging::StructuredLogger do end end end + + context 'with Gitaly and Rugged calls' do + let(:timing_data) do + { + gitaly_calls: 10, + gitaly_duration: 10000, + rugged_calls: 1, + rugged_duration_ms: 5000 + } + end + + before do + job.merge!(timing_data) + end + + it 'logs with Gitaly and Rugged timing data' do + Timecop.freeze(timestamp) do + expect(logger).to receive(:info).with(start_payload.except('args')).ordered + expect(logger).to receive(:info).with(end_payload.except('args')).ordered + + subject.call(job, 'test_queue') { } + end + end + end end end diff --git a/spec/lib/gitlab/usage_data_counters/note_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/note_counter_spec.rb new file mode 100644 index 00000000000..1669a22879f --- /dev/null +++ b/spec/lib/gitlab/usage_data_counters/note_counter_spec.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::UsageDataCounters::NoteCounter, :clean_gitlab_redis_shared_state do + shared_examples 'a note usage counter' do |event, noteable_type| + describe ".count(#{event})" do + it "increments the Note #{event} counter by 1" do + expect do + described_class.count(event, noteable_type) + end.to change { described_class.read(event, noteable_type) }.by 1 + end + end + + describe ".read(#{event})" do + event_count = 5 + + it "returns the total number of #{event} events" do + event_count.times do + described_class.count(event, noteable_type) + end + + expect(described_class.read(event, noteable_type)).to eq(event_count) + end + end + end + + it_behaves_like 'a note usage counter', :create, 'Snippet' + + describe '.totals' do + let(:combinations) do + [ + [:create, 'Snippet', 3] + ] + end + + let(:expected_totals) do + { snippet_comment: 3 } + end + + before do + combinations.each do |event, noteable_type, n| + n.times do + described_class.count(event, noteable_type) + end + end + end + + it 'can report all totals' do + expect(described_class.totals).to include(expected_totals) + end + end + + describe 'unknown events or noteable_type' do + using RSpec::Parameterized::TableSyntax + + let(:unknown_event_error) { Gitlab::UsageDataCounters::BaseCounter::UnknownEvent } + + where(:event, :noteable_type, :expected_count, :should_raise) do + :create | 'Snippet' | 1 | false + :wibble | 'Snippet' | 0 | true + :create | 'Issue' | 0 | false + :wibble | 'Issue' | 0 | false + end + + with_them do + it "handles event" do + if should_raise + expect { described_class.count(event, noteable_type) }.to raise_error(unknown_event_error) + else + described_class.count(event, noteable_type) + + expect(described_class.read(event, noteable_type)).to eq(expected_count) + end + end + end + end +end diff --git a/spec/lib/gitlab/usage_data_counters/snippet_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/snippet_counter_spec.rb new file mode 100644 index 00000000000..65381ed36d1 --- /dev/null +++ b/spec/lib/gitlab/usage_data_counters/snippet_counter_spec.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::UsageDataCounters::SnippetCounter do + it_behaves_like 'a redis usage counter', 'Snippet', :create + it_behaves_like 'a redis usage counter', 'Snippet', :update + + it_behaves_like 'a redis usage counter with totals', :snippet, + create: 3, + update: 2 +end diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index 297c4f0b683..bf36273251b 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -62,6 +62,9 @@ describe Gitlab::UsageData do )) expect(subject).to include( + snippet_create: a_kind_of(Integer), + snippet_update: a_kind_of(Integer), + snippet_comment: a_kind_of(Integer), wiki_pages_create: a_kind_of(Integer), wiki_pages_update: a_kind_of(Integer), wiki_pages_delete: a_kind_of(Integer), diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index b7e005e3883..4aac4b640f4 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -2340,6 +2340,32 @@ describe Ci::Build do it_behaves_like 'containing environment variables' end end + + context 'when project has an environment specific variable' do + let(:environment_specific_variable) do + { key: 'MY_STAGING_ONLY_VARIABLE', value: 'environment_specific_variable', public: false, masked: false } + end + + before do + create(:ci_variable, environment_specific_variable.slice(:key, :value) + .merge(project: project, environment_scope: 'stag*')) + end + + it_behaves_like 'containing environment variables' + + context 'when environment scope does not match build environment' do + it { is_expected.not_to include(environment_specific_variable) } + end + + context 'when environment scope matches build environment' do + before do + create(:environment, name: 'staging', project: project) + build.update!(environment: 'staging') + end + + it { is_expected.to include(environment_specific_variable) } + end + end end context 'when build started manually' do diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb index a231c7eaed8..3ff547456c6 100644 --- a/spec/models/ci/variable_spec.rb +++ b/spec/models/ci/variable_spec.rb @@ -10,6 +10,7 @@ describe Ci::Variable do describe 'validations' do it { is_expected.to include_module(Presentable) } it { is_expected.to include_module(Maskable) } + it { is_expected.to include_module(HasEnvironmentScope) } it { is_expected.to validate_uniqueness_of(:key).scoped_to(:project_id, :environment_scope).with_message(/\(\w+\) has already been taken/) } end diff --git a/spec/models/concerns/cacheable_attributes_spec.rb b/spec/models/concerns/cacheable_attributes_spec.rb index eeacdadab9c..da46effe411 100644 --- a/spec/models/concerns/cacheable_attributes_spec.rb +++ b/spec/models/concerns/cacheable_attributes_spec.rb @@ -7,6 +7,7 @@ describe CacheableAttributes do Class.new do include ActiveModel::Model extend ActiveModel::Callbacks + include ActiveModel::AttributeMethods define_model_callbacks :commit include CacheableAttributes @@ -34,44 +35,60 @@ describe CacheableAttributes do end end + before do + stub_const("MinimalTestClass", minimal_test_class) + end + shared_context 'with defaults' do before do - minimal_test_class.define_singleton_method(:defaults) do + MinimalTestClass.define_singleton_method(:defaults) do { foo: 'a', bar: 'b', baz: 'c' } end end end + describe '.expire', :use_clean_rails_memory_store_caching, :request_store do + it 'wipes the cache' do + obj = MinimalTestClass.new + obj.cache! + expect(MinimalTestClass.cached).not_to eq(nil) + + MinimalTestClass.expire + + expect(MinimalTestClass.cached).to eq(nil) + end + end + describe '.current_without_cache' do it 'defaults to last' do - expect(minimal_test_class.current_without_cache).to eq(minimal_test_class.last) + expect(MinimalTestClass.current_without_cache).to eq(MinimalTestClass.last) end it 'can be overridden' do - minimal_test_class.define_singleton_method(:current_without_cache) do + MinimalTestClass.define_singleton_method(:current_without_cache) do first end - expect(minimal_test_class.current_without_cache).to eq(minimal_test_class.first) + expect(MinimalTestClass.current_without_cache).to eq(MinimalTestClass.first) end end describe '.cache_key' do it 'excludes cache attributes' do - expect(minimal_test_class.cache_key).to eq("TestClass:#{Gitlab::VERSION}:#{Rails.version}") + expect(MinimalTestClass.cache_key).to eq("TestClass:#{Gitlab::VERSION}:#{Rails.version}") end end describe '.defaults' do it 'defaults to {}' do - expect(minimal_test_class.defaults).to eq({}) + expect(MinimalTestClass.defaults).to eq({}) end context 'with defaults defined' do include_context 'with defaults' it 'can be overridden' do - expect(minimal_test_class.defaults).to eq({ foo: 'a', bar: 'b', baz: 'c' }) + expect(MinimalTestClass.defaults).to eq({ foo: 'a', bar: 'b', baz: 'c' }) end end end @@ -81,13 +98,13 @@ describe CacheableAttributes do context 'without any attributes given' do it 'intializes a new object with the defaults' do - expect(minimal_test_class.build_from_defaults.attributes).to eq(minimal_test_class.defaults.stringify_keys) + expect(MinimalTestClass.build_from_defaults.attributes).to eq(MinimalTestClass.defaults.stringify_keys) end end context 'with attributes given' do it 'intializes a new object with the given attributes merged into the defaults' do - expect(minimal_test_class.build_from_defaults(foo: 'd').attributes['foo']).to eq('d') + expect(MinimalTestClass.build_from_defaults(foo: 'd').attributes['foo']).to eq('d') end end @@ -108,8 +125,8 @@ describe CacheableAttributes do describe '.current', :use_clean_rails_memory_store_caching do context 'redis unavailable' do before do - allow(minimal_test_class).to receive(:last).and_return(:last) - expect(Rails.cache).to receive(:read).with(minimal_test_class.cache_key).and_raise(Redis::BaseError) + allow(MinimalTestClass).to receive(:last).and_return(:last) + expect(Rails.cache).to receive(:read).with(MinimalTestClass.cache_key).and_raise(Redis::BaseError) end context 'in production environment' do @@ -120,7 +137,7 @@ describe CacheableAttributes do it 'returns an uncached record and logs a warning' do expect(Rails.logger).to receive(:warn).with("Cached record for TestClass couldn't be loaded, falling back to uncached record: Redis::BaseError") - expect(minimal_test_class.current).to eq(:last) + expect(MinimalTestClass.current).to eq(:last) end end @@ -132,7 +149,7 @@ describe CacheableAttributes do it 'returns an uncached record and logs a warning' do expect(Rails.logger).not_to receive(:warn) - expect { minimal_test_class.current }.to raise_error(Redis::BaseError) + expect { MinimalTestClass.current }.to raise_error(Redis::BaseError) end end end @@ -202,7 +219,7 @@ describe CacheableAttributes do describe '.cached', :use_clean_rails_memory_store_caching do context 'when cache is cold' do it 'returns nil' do - expect(minimal_test_class.cached).to be_nil + expect(MinimalTestClass.cached).to be_nil end end diff --git a/spec/models/concerns/has_environment_scope_spec.rb b/spec/models/concerns/has_environment_scope_spec.rb new file mode 100644 index 00000000000..a6e1ba59263 --- /dev/null +++ b/spec/models/concerns/has_environment_scope_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe HasEnvironmentScope do + subject { build(:ci_variable) } + + it { is_expected.to allow_value('*').for(:environment_scope) } + it { is_expected.to allow_value('review/*').for(:environment_scope) } + it { is_expected.not_to allow_value('').for(:environment_scope) } + it { is_expected.not_to allow_value('!!()()').for(:environment_scope) } + + it do + is_expected.to validate_uniqueness_of(:key) + .scoped_to(:project_id, :environment_scope) + .with_message(/\(\w+\) has already been taken/) + end + + describe '.on_environment' do + let(:project) { create(:project) } + + it 'returns scoped objects' do + variable1 = create(:ci_variable, project: project, environment_scope: '*') + variable2 = create(:ci_variable, project: project, environment_scope: 'product/*') + create(:ci_variable, project: project, environment_scope: 'staging/*') + + expect(project.variables.on_environment('product/canary-1')).to eq([variable1, variable2]) + end + + it 'returns only the most relevant object if relevant_only is true' do + create(:ci_variable, project: project, environment_scope: '*') + variable2 = create(:ci_variable, project: project, environment_scope: 'product/*') + create(:ci_variable, project: project, environment_scope: 'staging/*') + + expect(project.variables.on_environment('product/canary-1', relevant_only: true)).to eq([variable2]) + end + + it 'returns scopes ordered by lowest precedence first' do + create(:ci_variable, project: project, environment_scope: '*') + create(:ci_variable, project: project, environment_scope: 'production*') + create(:ci_variable, project: project, environment_scope: 'production') + + result = project.variables.on_environment('production').map(&:environment_scope) + + expect(result).to eq(['*', 'production*', 'production']) + end + end + + describe '#environment_scope=' do + context 'when the new environment_scope is nil' do + it 'strips leading and trailing whitespaces' do + subject.environment_scope = nil + + expect(subject.environment_scope).to eq('') + end + end + + context 'when the new environment_scope has leadind and trailing whitespaces' do + it 'strips leading and trailing whitespaces' do + subject.environment_scope = ' * ' + + expect(subject.environment_scope).to eq('*') + end + end + end +end diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb deleted file mode 100644 index d33bbb0470f..00000000000 --- a/spec/models/project_services/kubernetes_service_spec.rb +++ /dev/null @@ -1,167 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe KubernetesService, :use_clean_rails_memory_store_caching do - include KubernetesHelpers - include ReactiveCachingHelpers - - let(:project) { create(:kubernetes_project) } - let(:service) { create(:kubernetes_service, project: project) } - - describe 'Associations' do - it { is_expected.to belong_to :project } - end - - describe 'Validations' do - context 'when service is active' do - before do - subject.active = true - subject.skip_deprecation_validation = true - end - - it { is_expected.not_to validate_presence_of(:namespace) } - it { is_expected.to validate_presence_of(:api_url) } - it { is_expected.to validate_presence_of(:token) } - - context 'namespace format' do - before do - subject.project = project - subject.api_url = "http://example.com" - subject.token = "test" - end - - { - 'foo' => true, - '1foo' => true, - 'foo1' => true, - 'foo-bar' => true, - '-foo' => false, - 'foo-' => false, - 'a' * 63 => true, - 'a' * 64 => false, - 'a.b' => false, - 'a*b' => false, - 'FOO' => true - }.each do |namespace, validity| - it "validates #{namespace} as #{validity ? 'valid' : 'invalid'}" do - subject.namespace = namespace - - expect(subject.valid?).to eq(validity) - end - end - end - end - - context 'when service is inactive' do - before do - subject.project = project - subject.active = false - end - - it { is_expected.not_to validate_presence_of(:api_url) } - it { is_expected.not_to validate_presence_of(:token) } - end - - context 'with a deprecated service' do - let(:kubernetes_service) { create(:kubernetes_service) } - - before do - kubernetes_service.update_attribute(:active, false) - kubernetes_service.skip_deprecation_validation = false - kubernetes_service.properties['namespace'] = "foo" - end - - it 'does not update attributes' do - expect(kubernetes_service.save).to be_falsy - end - - it 'includes an error with a deprecation message' do - kubernetes_service.valid? - expect(kubernetes_service.errors[:base].first).to match(/Kubernetes service integration has been disabled/) - end - end - - context 'with an active and deprecated service' do - let(:kubernetes_service) { create(:kubernetes_service) } - - before do - kubernetes_service.skip_deprecation_validation = false - kubernetes_service.active = false - kubernetes_service.properties['namespace'] = 'foo' - kubernetes_service.save - end - - it 'deactivates the service' do - expect(kubernetes_service.active?).to be_falsy - end - - it 'does not include a deprecation message as error' do - expect(kubernetes_service.errors.messages.count).to eq(0) - end - - it 'updates attributes' do - expect(kubernetes_service.properties['namespace']).to eq("foo") - end - end - end - - describe '#initialize_properties' do - context 'without a project' do - it 'leaves the namespace unset' do - expect(described_class.new.namespace).to be_nil - end - end - end - - describe '#fields' do - let(:kube_namespace) do - subject.fields.find { |h| h[:name] == 'namespace' } - end - - context 'as template' do - before do - subject.template = true - end - - it 'sets the namespace to the default' do - expect(kube_namespace).not_to be_nil - expect(kube_namespace[:placeholder]).to eq(subject.class::TEMPLATE_PLACEHOLDER) - end - end - - context 'with associated project' do - before do - subject.project = project - end - - it 'sets the namespace to the default' do - expect(kube_namespace).not_to be_nil - expect(kube_namespace[:placeholder]).to match(/\A#{Gitlab::PathRegex::PATH_REGEX_STR}-\d+\z/) - end - end - end - - describe "#deprecated?" do - let(:kubernetes_service) { create(:kubernetes_service) } - - it 'returns true' do - expect(kubernetes_service.deprecated?).to be_truthy - end - end - - describe "#deprecation_message" do - let(:kubernetes_service) { create(:kubernetes_service) } - - it 'indicates the service is deprecated' do - expect(kubernetes_service.deprecation_message).to match(/Kubernetes service integration has been disabled/) - end - - context 'if the service is not active' do - it 'returns a message' do - kubernetes_service.update_attribute(:active, false) - expect(kubernetes_service.deprecation_message).to match(/Fields on this page are not used by GitLab/) - end - end - end -end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index dde766c3813..29a589eba20 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -2648,9 +2648,10 @@ describe Project do describe '#ci_variables_for' do let(:project) { create(:project) } + let(:environment_scope) { '*' } let!(:ci_variable) do - create(:ci_variable, value: 'secret', project: project) + create(:ci_variable, value: 'secret', project: project, environment_scope: environment_scope) end let!(:protected_variable) do @@ -2695,6 +2696,96 @@ describe Project do it_behaves_like 'ref is protected' end + + context 'when environment name is specified' do + let(:environment) { 'review/name' } + + subject do + project.ci_variables_for(ref: 'ref', environment: environment) + end + + context 'when environment scope is exactly matched' do + let(:environment_scope) { 'review/name' } + + it { is_expected.to contain_exactly(ci_variable) } + end + + context 'when environment scope is matched by wildcard' do + let(:environment_scope) { 'review/*' } + + it { is_expected.to contain_exactly(ci_variable) } + end + + context 'when environment scope does not match' do + let(:environment_scope) { 'review/*/special' } + + it { is_expected.not_to contain_exactly(ci_variable) } + end + + context 'when environment scope has _' do + let(:environment_scope) { '*_*' } + + it 'does not treat it as wildcard' do + is_expected.not_to contain_exactly(ci_variable) + end + + context 'when environment name contains underscore' do + let(:environment) { 'foo_bar/test' } + let(:environment_scope) { 'foo_bar/*' } + + it 'matches literally for _' do + is_expected.to contain_exactly(ci_variable) + end + end + end + + # The environment name and scope cannot have % at the moment, + # but we're considering relaxing it and we should also make sure + # it doesn't break in case some data sneaked in somehow as we're + # not checking this integrity in database level. + context 'when environment scope has %' do + it 'does not treat it as wildcard' do + ci_variable.update_attribute(:environment_scope, '*%*') + + is_expected.not_to contain_exactly(ci_variable) + end + + context 'when environment name contains a percent' do + let(:environment) { 'foo%bar/test' } + + it 'matches literally for _' do + ci_variable.update(environment_scope: 'foo%bar/*') + + is_expected.to contain_exactly(ci_variable) + end + end + end + + context 'when variables with the same name have different environment scopes' do + let!(:partially_matched_variable) do + create(:ci_variable, + key: ci_variable.key, + value: 'partial', + environment_scope: 'review/*', + project: project) + end + + let!(:perfectly_matched_variable) do + create(:ci_variable, + key: ci_variable.key, + value: 'prefect', + environment_scope: 'review/name', + project: project) + end + + it 'puts variables matching environment scope more in the end' do + is_expected.to eq( + [ci_variable, + partially_matched_variable, + perfectly_matched_variable]) + end + end + end end describe '#any_lfs_file_locks?', :request_store do diff --git a/spec/models/prometheus_metric_spec.rb b/spec/models/prometheus_metric_spec.rb index 3610408c138..a123ff5a2a6 100644 --- a/spec/models/prometheus_metric_spec.rb +++ b/spec/models/prometheus_metric_spec.rb @@ -150,4 +150,17 @@ describe PrometheusMetric do expect(subject.to_query_metric.queries).to eq(queries) end end + + describe '#to_metric_hash' do + it 'returns a hash suitable for inclusion on a metrics dashboard' do + expected_output = { + query_range: subject.query, + unit: subject.unit, + label: subject.legend, + metric_id: subject.id + } + + expect(subject.to_metric_hash).to eq(expected_output) + end + end end diff --git a/spec/presenters/blob_presenter_spec.rb b/spec/presenters/blob_presenter_spec.rb index 95db2ba6a0d..eacf383be7d 100644 --- a/spec/presenters/blob_presenter_spec.rb +++ b/spec/presenters/blob_presenter_spec.rb @@ -28,70 +28,24 @@ describe BlobPresenter, :seed_helper do subject { described_class.new(blob) } it 'returns highlighted content' do - expect(Gitlab::Highlight) - .to receive(:highlight) - .with( - 'files/ruby/regex.rb', - git_blob.data, - since: nil, - plain: nil, - language: nil - ) + expect(Gitlab::Highlight).to receive(:highlight).with('files/ruby/regex.rb', git_blob.data, plain: nil, language: nil) subject.highlight end it 'returns plain content when :plain is true' do - expect(Gitlab::Highlight) - .to receive(:highlight) - .with( - 'files/ruby/regex.rb', - git_blob.data, - since: nil, - plain: true, - language: nil - ) + expect(Gitlab::Highlight).to receive(:highlight).with('files/ruby/regex.rb', git_blob.data, plain: true, language: nil) subject.highlight(plain: true) end - context '"since" and "to" are present' do - before do - allow(git_blob) - .to receive(:data) - .and_return("line one\nline two\nline 3\nline 4") - end - - it 'returns limited highlighted content' do - expect(Gitlab::Highlight) - .to receive(:highlight) - .with( - 'files/ruby/regex.rb', - "line two\nline 3\n", - since: 2, - language: nil, - plain: nil - ) - - subject.highlight(since: 2, to: 3) - end - end - context 'gitlab-language contains a match' do before do allow(blob).to receive(:language_from_gitattributes).and_return('ruby') end it 'passes language to inner call' do - expect(Gitlab::Highlight) - .to receive(:highlight) - .with( - 'files/ruby/regex.rb', - git_blob.data, - since: nil, - plain: nil, - language: 'ruby' - ) + expect(Gitlab::Highlight).to receive(:highlight).with('files/ruby/regex.rb', git_blob.data, plain: nil, language: 'ruby') subject.highlight end diff --git a/spec/requests/api/group_labels_spec.rb b/spec/requests/api/group_labels_spec.rb index fcea57d9df7..0be4e2e9121 100644 --- a/spec/requests/api/group_labels_spec.rb +++ b/spec/requests/api/group_labels_spec.rb @@ -14,12 +14,25 @@ describe API::GroupLabels do get api("/groups/#{group.id}/labels", user) expect(response).to have_gitlab_http_status(200) - expect(response).to match_response_schema('public_api/v4/group_labels') expect(response).to include_pagination_headers expect(json_response).to be_an Array + expect(json_response).to all(match_schema('public_api/v4/labels/label')) expect(json_response.size).to eq(2) expect(json_response.map {|r| r['name'] }).to contain_exactly('feature', 'bug') end + + context 'when the with_counts parameter is set' do + it 'includes counts in the response' do + get api("/groups/#{group.id}/labels", user), params: { with_counts: true } + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response).to all(match_schema('public_api/v4/labels/label_with_counts')) + expect(json_response.size).to eq(2) + expect(json_response.map { |r| r['open_issues_count'] }).to contain_exactly(0, 0) + end + end end describe 'POST /groups/:id/labels' do diff --git a/spec/requests/api/issues/get_project_issues_spec.rb b/spec/requests/api/issues/get_project_issues_spec.rb index f11d8259d4a..f7ca6fd1e0a 100644 --- a/spec/requests/api/issues/get_project_issues_spec.rb +++ b/spec/requests/api/issues/get_project_issues_spec.rb @@ -389,7 +389,7 @@ describe API::Issues do it 'returns an array of issues with any milestone' do get api("#{base_url}/issues", user), params: { milestone: any_milestone_title } - expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id]) + expect_paginated_array_response([issue.id, closed_issue.id]) end context 'without sort params' do diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb index 518181e4d93..ad0974f55a3 100644 --- a/spec/requests/api/labels_spec.rb +++ b/spec/requests/api/labels_spec.rb @@ -11,65 +11,76 @@ describe API::Labels do end describe 'GET /projects/:id/labels' do - it 'returns all available labels to the project' do - group = create(:group) - group_label = create(:group_label, title: 'feature', group: group) - project.update(group: group) - create(:labeled_issue, project: project, labels: [group_label], author: user) - create(:labeled_issue, project: project, labels: [label1], author: user, state: :closed) - create(:labeled_merge_request, labels: [priority_label], author: user, source_project: project ) + let(:group) { create(:group) } + let!(:group_label) { create(:group_label, title: 'feature', group: group) } - expected_keys = %w( - id name color text_color description - open_issues_count closed_issues_count open_merge_requests_count - subscribed priority is_project_label - ) + before do + project.update!(group: group) + end + it 'returns all available labels to the project' do get api("/projects/#{project.id}/labels", user) expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers - expect(json_response).to be_an Array + expect(json_response).to all(match_schema('public_api/v4/labels/project_label')) expect(json_response.size).to eq(3) - expect(json_response.first.keys).to match_array expected_keys expect(json_response.map { |l| l['name'] }).to match_array([group_label.name, priority_label.name, label1.name]) + end - label1_response = json_response.find { |l| l['name'] == label1.title } - group_label_response = json_response.find { |l| l['name'] == group_label.title } - priority_label_response = json_response.find { |l| l['name'] == priority_label.title } - - expect(label1_response['open_issues_count']).to eq(0) - expect(label1_response['closed_issues_count']).to eq(1) - expect(label1_response['open_merge_requests_count']).to eq(0) - expect(label1_response['name']).to eq(label1.name) - expect(label1_response['color']).to be_present - expect(label1_response['text_color']).to be_present - expect(label1_response['description']).to be_nil - expect(label1_response['priority']).to be_nil - expect(label1_response['subscribed']).to be_falsey - expect(label1_response['is_project_label']).to be_truthy - - expect(group_label_response['open_issues_count']).to eq(1) - expect(group_label_response['closed_issues_count']).to eq(0) - expect(group_label_response['open_merge_requests_count']).to eq(0) - expect(group_label_response['name']).to eq(group_label.name) - expect(group_label_response['color']).to be_present - expect(group_label_response['text_color']).to be_present - expect(group_label_response['description']).to be_nil - expect(group_label_response['priority']).to be_nil - expect(group_label_response['subscribed']).to be_falsey - expect(group_label_response['is_project_label']).to be_falsey - - expect(priority_label_response['open_issues_count']).to eq(0) - expect(priority_label_response['closed_issues_count']).to eq(0) - expect(priority_label_response['open_merge_requests_count']).to eq(1) - expect(priority_label_response['name']).to eq(priority_label.name) - expect(priority_label_response['color']).to be_present - expect(priority_label_response['text_color']).to be_present - expect(priority_label_response['description']).to be_nil - expect(priority_label_response['priority']).to eq(3) - expect(priority_label_response['subscribed']).to be_falsey - expect(priority_label_response['is_project_label']).to be_truthy + context 'when the with_counts parameter is set' do + before do + create(:labeled_issue, project: project, labels: [group_label], author: user) + create(:labeled_issue, project: project, labels: [label1], author: user, state: :closed) + create(:labeled_merge_request, labels: [priority_label], author: user, source_project: project ) + end + + it 'includes counts in the response' do + get api("/projects/#{project.id}/labels", user), params: { with_counts: true } + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to all(match_schema('public_api/v4/labels/project_label_with_counts')) + expect(json_response.size).to eq(3) + expect(json_response.map { |l| l['name'] }).to match_array([group_label.name, priority_label.name, label1.name]) + + label1_response = json_response.find { |l| l['name'] == label1.title } + group_label_response = json_response.find { |l| l['name'] == group_label.title } + priority_label_response = json_response.find { |l| l['name'] == priority_label.title } + + expect(label1_response).to include('open_issues_count' => 0, + 'closed_issues_count' => 1, + 'open_merge_requests_count' => 0, + 'name' => label1.name, + 'description' => nil, + 'color' => a_string_matching(/^#\h{6}$/), + 'text_color' => a_string_matching(/^#\h{6}$/), + 'priority' => nil, + 'subscribed' => false, + 'is_project_label' => true) + + expect(group_label_response).to include('open_issues_count' => 1, + 'closed_issues_count' => 0, + 'open_merge_requests_count' => 0, + 'name' => group_label.name, + 'description' => nil, + 'color' => a_string_matching(/^#\h{6}$/), + 'text_color' => a_string_matching(/^#\h{6}$/), + 'priority' => nil, + 'subscribed' => false, + 'is_project_label' => false) + + expect(priority_label_response).to include('open_issues_count' => 0, + 'closed_issues_count' => 0, + 'open_merge_requests_count' => 1, + 'name' => priority_label.name, + 'description' => nil, + 'color' => a_string_matching(/^#\h{6}$/), + 'text_color' => a_string_matching(/^#\h{6}$/), + 'priority' => 3, + 'subscribed' => false, + 'is_project_label' => true) + end end end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 5b3a2412aff..1d7ca85cdd2 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -838,6 +838,28 @@ describe API::Projects do end end + describe 'GET /users/:user_id/starred_projects/' do + before do + user3.update(starred_projects: [project, project2, project3]) + end + + it 'returns error when user not found' do + get api('/users/9999/starred_projects/') + + expect(response).to have_gitlab_http_status(404) + expect(json_response['message']).to eq('404 User Not Found') + end + + it 'returns projects filtered by user' do + get api("/users/#{user3.id}/starred_projects/", user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.map { |project| project['id'] }).to contain_exactly(project.id, project2.id, project3.id) + end + end + describe 'POST /projects/user/:id' do it 'creates new project without path but with name and return 201' do expect { post api("/projects/user/#{user.id}", admin), params: { name: 'Foo Project' } }.to change { Project.count }.by(1) @@ -2148,6 +2170,85 @@ describe API::Projects do end end + describe 'GET /projects/:id/starrers' do + shared_examples_for 'project starrers response' do + it 'returns an array of starrers' do + get api("/projects/#{public_project.id}/starrers", current_user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response[0]['starred_since']).to be_present + expect(json_response[0]['user']).to be_present + end + + it 'returns the proper security headers' do + get api('/projects/1/starrers', current_user) + + expect(response).to include_security_headers + end + end + + let(:public_project) { create(:project, :public) } + let(:private_user) { create(:user, private_profile: true) } + + before do + user.update(starred_projects: [public_project]) + private_user.update(starred_projects: [public_project]) + end + + it 'returns not_found(404) for not existing project' do + get api("/projects/9999999999/starrers", user) + + expect(response).to have_gitlab_http_status(:not_found) + end + + context 'public project without user' do + it_behaves_like 'project starrers response' do + let(:current_user) { nil } + end + + it 'returns only starrers with a public profile' do + get api("/projects/#{public_project.id}/starrers", nil) + + user_ids = json_response.map { |s| s['user']['id'] } + expect(user_ids).to include(user.id) + expect(user_ids).not_to include(private_user.id) + end + end + + context 'public project with user with private profile' do + it_behaves_like 'project starrers response' do + let(:current_user) { private_user } + end + + it 'returns current user with a private profile' do + get api("/projects/#{public_project.id}/starrers", private_user) + + user_ids = json_response.map { |s| s['user']['id'] } + expect(user_ids).to include(user.id, private_user.id) + end + end + + context 'private project' do + context 'with unauthorized user' do + it 'returns not_found for existing but unauthorized project' do + get api("/projects/#{project3.id}/starrers", user3) + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'without user' do + it 'returns not_found for existing but unauthorized project' do + get api("/projects/#{project3.id}/starrers", nil) + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + end + describe 'GET /projects/:id/languages' do context 'with an authorized user' do it_behaves_like 'languages and percentages JSON response' do diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb index 91cb8760a04..76a70ab6e9e 100644 --- a/spec/requests/api/services_spec.rb +++ b/spec/requests/api/services_spec.rb @@ -10,10 +10,7 @@ describe API::Services do end Service.available_services_names.each do |service| - # TODO: Remove below `if: (service != "kubernetes")` in the next release - # KubernetesService was deprecated and it can't be updated. Right now it's - # only readable. It should be completely removed in the next iteration. - describe "PUT /projects/:id/services/#{service.dasherize}", if: (service != "kubernetes") do + describe "PUT /projects/:id/services/#{service.dasherize}" do include_context service it "updates #{service} settings" do @@ -62,10 +59,7 @@ describe API::Services do end end - # TODO: Remove below `if: (service != "kubernetes")` in the next release - # KubernetesService was deprecated and it can't be updated. Right now it's - # only readable. It should be completely removed in the next iteration. - describe "DELETE /projects/:id/services/#{service.dasherize}", if: (service != "kubernetes") do + describe "DELETE /projects/:id/services/#{service.dasherize}" do include_context service before do diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb index 55b1419a004..69f105b71a8 100644 --- a/spec/requests/api/variables_spec.rb +++ b/spec/requests/api/variables_spec.rb @@ -106,6 +106,30 @@ describe API::Variables do expect(response).to have_gitlab_http_status(400) end + + it 'creates variable with a specific environment scope' do + expect do + post api("/projects/#{project.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'VALUE_2', environment_scope: 'review/*' } + end.to change { project.variables.reload.count }.by(1) + + expect(response).to have_gitlab_http_status(201) + expect(json_response['key']).to eq('TEST_VARIABLE_2') + expect(json_response['value']).to eq('VALUE_2') + expect(json_response['environment_scope']).to eq('review/*') + end + + it 'allows duplicated variable key given different environment scopes' do + variable = create(:ci_variable, project: project) + + expect do + post api("/projects/#{project.id}/variables", user), params: { key: variable.key, value: 'VALUE_2', environment_scope: 'review/*' } + end.to change { project.variables.reload.count }.by(1) + + expect(response).to have_gitlab_http_status(201) + expect(json_response['key']).to eq(variable.key) + expect(json_response['value']).to eq('VALUE_2') + expect(json_response['environment_scope']).to eq('review/*') + end end context 'authorized user with invalid permissions' do diff --git a/spec/serializers/variable_entity_spec.rb b/spec/serializers/variable_entity_spec.rb index effc0022633..10664ff66ec 100644 --- a/spec/serializers/variable_entity_spec.rb +++ b/spec/serializers/variable_entity_spec.rb @@ -8,7 +8,7 @@ describe VariableEntity do subject { entity.as_json } it 'contains required fields' do - expect(subject).to include(:id, :key, :value, :protected) + expect(subject).to include(:id, :key, :value, :protected, :environment_scope) end end end diff --git a/spec/services/create_snippet_service_spec.rb b/spec/services/create_snippet_service_spec.rb index f6b6989b955..9b83f65a17e 100644 --- a/spec/services/create_snippet_service_spec.rb +++ b/spec/services/create_snippet_service_spec.rb @@ -36,6 +36,22 @@ describe CreateSnippetService do end end + describe 'usage counter' do + let(:counter) { Gitlab::UsageDataCounters::SnippetCounter } + + it 'increments count' do + expect do + create_snippet(nil, @admin, @opts) + end.to change { counter.read(:create) }.by 1 + end + + it 'does not increment count if create fails' do + expect do + create_snippet(nil, @admin, {}) + end.not_to change { counter.read(:create) } + end + end + def create_snippet(project, user, opts) CreateSnippetService.new(project, user, opts).execute end diff --git a/spec/services/issuable/bulk_update_service_spec.rb b/spec/services/issuable/bulk_update_service_spec.rb index fb12877fa05..e3a728f2566 100644 --- a/spec/services/issuable/bulk_update_service_spec.rb +++ b/spec/services/issuable/bulk_update_service_spec.rb @@ -31,7 +31,159 @@ describe Issuable::BulkUpdateService do end end - context 'with project issuables' do + shared_examples 'updating labels' do + def create_issue_with_labels(labels) + create(:labeled_issue, project: project, labels: labels) + end + + let(:issue_all_labels) { create_issue_with_labels([bug, regression, merge_requests]) } + let(:issue_bug_and_regression) { create_issue_with_labels([bug, regression]) } + let(:issue_bug_and_merge_requests) { create_issue_with_labels([bug, merge_requests]) } + let(:issue_no_labels) { create(:issue, project: project) } + let(:issues) { [issue_all_labels, issue_bug_and_regression, issue_bug_and_merge_requests, issue_no_labels] } + + let(:labels) { [] } + let(:add_labels) { [] } + let(:remove_labels) { [] } + + let(:bulk_update_params) do + { + label_ids: labels.map(&:id), + add_label_ids: add_labels.map(&:id), + remove_label_ids: remove_labels.map(&:id) + } + end + + before do + bulk_update(issues, bulk_update_params) + end + + context 'when label_ids are passed' do + let(:issues) { [issue_all_labels, issue_no_labels] } + let(:labels) { [bug, regression] } + + it 'updates the labels of all issues passed to the labels passed' do + expect(issues.map(&:reload).map(&:label_ids)).to all(match_array(labels.map(&:id))) + end + + it 'does not update issues not passed in' do + expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id) + end + + context 'when those label IDs are empty' do + let(:labels) { [] } + + it 'updates the issues passed to have no labels' do + expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty) + end + end + end + + context 'when add_label_ids are passed' do + let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] } + let(:add_labels) { [bug, regression, merge_requests] } + + it 'adds those label IDs to all issues passed' do + expect(issues.map(&:reload).map(&:label_ids)).to all(include(*add_labels.map(&:id))) + end + + it 'does not update issues not passed in' do + expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id) + end + end + + context 'when remove_label_ids are passed' do + let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] } + let(:remove_labels) { [bug, regression, merge_requests] } + + it 'removes those label IDs from all issues passed' do + expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty) + end + + it 'does not update issues not passed in' do + expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id) + end + end + + context 'when add_label_ids and remove_label_ids are passed' do + let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] } + let(:add_labels) { [bug] } + let(:remove_labels) { [merge_requests] } + + it 'adds the label IDs to all issues passed' do + expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id)) + end + + it 'removes the label IDs from all issues passed' do + expect(issues.map(&:reload).flat_map(&:label_ids)).not_to include(merge_requests.id) + end + + it 'does not update issues not passed in' do + expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id) + end + end + + context 'when add_label_ids and label_ids are passed' do + let(:issues) { [issue_all_labels, issue_bug_and_regression, issue_bug_and_merge_requests] } + let(:labels) { [merge_requests] } + let(:add_labels) { [regression] } + + it 'adds the label IDs to all issues passed' do + expect(issues.map(&:reload).map(&:label_ids)).to all(include(regression.id)) + end + + it 'ignores the label IDs parameter' do + expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id)) + end + + it 'does not update issues not passed in' do + expect(issue_no_labels.label_ids).to be_empty + end + end + + context 'when remove_label_ids and label_ids are passed' do + let(:issues) { [issue_no_labels, issue_bug_and_regression] } + let(:labels) { [merge_requests] } + let(:remove_labels) { [regression] } + + it 'removes the label IDs from all issues passed' do + expect(issues.map(&:reload).flat_map(&:label_ids)).not_to include(regression.id) + end + + it 'ignores the label IDs parameter' do + expect(issues.map(&:reload).flat_map(&:label_ids)).not_to include(merge_requests.id) + end + + it 'does not update issues not passed in' do + expect(issue_all_labels.label_ids).to contain_exactly(bug.id, regression.id, merge_requests.id) + end + end + + context 'when add_label_ids, remove_label_ids, and label_ids are passed' do + let(:issues) { [issue_bug_and_merge_requests, issue_no_labels] } + let(:labels) { [regression] } + let(:add_labels) { [bug] } + let(:remove_labels) { [merge_requests] } + + it 'adds the label IDs to all issues passed' do + expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id)) + end + + it 'removes the label IDs from all issues passed' do + expect(issues.map(&:reload).flat_map(&:label_ids)).not_to include(merge_requests.id) + end + + it 'ignores the label IDs parameter' do + expect(issues.map(&:reload).flat_map(&:label_ids)).not_to include(regression.id) + end + + it 'does not update issues not passed in' do + expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id) + end + end + end + + context 'with issuables at a project level' do describe 'close issues' do let(:issues) { create_list(:issue, 2, project: project) } @@ -178,159 +330,11 @@ describe Issuable::BulkUpdateService do end describe 'updating labels' do - def create_issue_with_labels(labels) - create(:labeled_issue, project: project, labels: labels) - end - let(:bug) { create(:label, project: project) } let(:regression) { create(:label, project: project) } let(:merge_requests) { create(:label, project: project) } - let(:issue_all_labels) { create_issue_with_labels([bug, regression, merge_requests]) } - let(:issue_bug_and_regression) { create_issue_with_labels([bug, regression]) } - let(:issue_bug_and_merge_requests) { create_issue_with_labels([bug, merge_requests]) } - let(:issue_no_labels) { create(:issue, project: project) } - let(:issues) { [issue_all_labels, issue_bug_and_regression, issue_bug_and_merge_requests, issue_no_labels] } - - let(:labels) { [] } - let(:add_labels) { [] } - let(:remove_labels) { [] } - - let(:bulk_update_params) do - { - label_ids: labels.map(&:id), - add_label_ids: add_labels.map(&:id), - remove_label_ids: remove_labels.map(&:id) - } - end - - before do - bulk_update(issues, bulk_update_params) - end - - context 'when label_ids are passed' do - let(:issues) { [issue_all_labels, issue_no_labels] } - let(:labels) { [bug, regression] } - - it 'updates the labels of all issues passed to the labels passed' do - expect(issues.map(&:reload).map(&:label_ids)).to all(match_array(labels.map(&:id))) - end - - it 'does not update issues not passed in' do - expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id) - end - - context 'when those label IDs are empty' do - let(:labels) { [] } - - it 'updates the issues passed to have no labels' do - expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty) - end - end - end - - context 'when add_label_ids are passed' do - let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] } - let(:add_labels) { [bug, regression, merge_requests] } - - it 'adds those label IDs to all issues passed' do - expect(issues.map(&:reload).map(&:label_ids)).to all(include(*add_labels.map(&:id))) - end - - it 'does not update issues not passed in' do - expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id) - end - end - - context 'when remove_label_ids are passed' do - let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] } - let(:remove_labels) { [bug, regression, merge_requests] } - - it 'removes those label IDs from all issues passed' do - expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty) - end - - it 'does not update issues not passed in' do - expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id) - end - end - - context 'when add_label_ids and remove_label_ids are passed' do - let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] } - let(:add_labels) { [bug] } - let(:remove_labels) { [merge_requests] } - - it 'adds the label IDs to all issues passed' do - expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id)) - end - - it 'removes the label IDs from all issues passed' do - expect(issues.map(&:reload).flat_map(&:label_ids)).not_to include(merge_requests.id) - end - - it 'does not update issues not passed in' do - expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id) - end - end - - context 'when add_label_ids and label_ids are passed' do - let(:issues) { [issue_all_labels, issue_bug_and_regression, issue_bug_and_merge_requests] } - let(:labels) { [merge_requests] } - let(:add_labels) { [regression] } - - it 'adds the label IDs to all issues passed' do - expect(issues.map(&:reload).map(&:label_ids)).to all(include(regression.id)) - end - - it 'ignores the label IDs parameter' do - expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id)) - end - - it 'does not update issues not passed in' do - expect(issue_no_labels.label_ids).to be_empty - end - end - - context 'when remove_label_ids and label_ids are passed' do - let(:issues) { [issue_no_labels, issue_bug_and_regression] } - let(:labels) { [merge_requests] } - let(:remove_labels) { [regression] } - - it 'removes the label IDs from all issues passed' do - expect(issues.map(&:reload).flat_map(&:label_ids)).not_to include(regression.id) - end - - it 'ignores the label IDs parameter' do - expect(issues.map(&:reload).flat_map(&:label_ids)).not_to include(merge_requests.id) - end - - it 'does not update issues not passed in' do - expect(issue_all_labels.label_ids).to contain_exactly(bug.id, regression.id, merge_requests.id) - end - end - - context 'when add_label_ids, remove_label_ids, and label_ids are passed' do - let(:issues) { [issue_bug_and_merge_requests, issue_no_labels] } - let(:labels) { [regression] } - let(:add_labels) { [bug] } - let(:remove_labels) { [merge_requests] } - - it 'adds the label IDs to all issues passed' do - expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id)) - end - - it 'removes the label IDs from all issues passed' do - expect(issues.map(&:reload).flat_map(&:label_ids)).not_to include(merge_requests.id) - end - - it 'ignores the label IDs parameter' do - expect(issues.map(&:reload).flat_map(&:label_ids)).not_to include(regression.id) - end - - it 'does not update issues not passed in' do - expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id) - end - end + it_behaves_like 'updating labels' end describe 'subscribe to issues' do @@ -360,7 +364,7 @@ describe Issuable::BulkUpdateService do end end - context 'with group issuables ' do + context 'with issuables at a group level' do let(:group) { create(:group) } describe 'updating milestones' do @@ -387,5 +391,18 @@ describe Issuable::BulkUpdateService do it_behaves_like 'updates milestones' end end + + describe 'updating labels' do + let(:project) { create(:project, :repository, group: group) } + let(:bug) { create(:group_label, group: group) } + let(:regression) { create(:group_label, group: group) } + let(:merge_requests) { create(:group_label, group: group) } + + before do + group.add_reporter(user) + end + + it_behaves_like 'updating labels' + end end end diff --git a/spec/services/metrics/dashboard/custom_metric_embed_service_spec.rb b/spec/services/metrics/dashboard/custom_metric_embed_service_spec.rb new file mode 100644 index 00000000000..53b7497ae21 --- /dev/null +++ b/spec/services/metrics/dashboard/custom_metric_embed_service_spec.rb @@ -0,0 +1,145 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Metrics::Dashboard::CustomMetricEmbedService do + include MetricsDashboardHelpers + + set(:project) { build(:project) } + set(:user) { create(:user) } + set(:environment) { create(:environment, project: project) } + + before do + project.add_maintainer(user) + end + + let(:dashboard_path) { system_dashboard_path } + let(:group) { business_metric_title } + let(:title) { 'title' } + let(:y_label) { 'y_label' } + + describe '.valid_params?' do + let(:valid_params) do + { + embedded: true, + dashboard_path: dashboard_path, + group: group, + title: title, + y_label: y_label + } + end + + subject { described_class.valid_params?(params) } + + let(:params) { valid_params } + + it { is_expected.to be_truthy } + + context 'not embedded' do + let(:params) { valid_params.except(:embedded) } + + it { is_expected.to be_falsey } + end + + context 'non-system dashboard' do + let(:dashboard_path) { '.gitlab/dashboards/test.yml' } + + it { is_expected.to be_falsey } + end + + context 'undefined dashboard' do + let(:params) { valid_params.except(:dashboard_path) } + + it { is_expected.to be_truthy } + end + + context 'non-custom metric group' do + let(:group) { 'Different Group' } + + it { is_expected.to be_falsey } + end + + context 'missing group' do + let(:group) { nil } + + it { is_expected.to be_falsey } + end + + context 'missing title' do + let(:title) { nil } + + it { is_expected.to be_falsey } + end + + context 'undefined y-axis label' do + let(:params) { valid_params.except(:y_label) } + + it { is_expected.to be_falsey } + end + end + + describe '#get_dashboard' do + let(:service_params) do + [ + project, + user, + { + embedded: true, + environment: environment, + dashboard_path: dashboard_path, + group: group, + title: title, + y_label: y_label + } + ] + end + + let(:service_call) { described_class.new(*service_params).get_dashboard } + + it_behaves_like 'misconfigured dashboard service response', :not_found + it_behaves_like 'raises error for users with insufficient permissions' + + context 'the custom metric exists' do + let!(:metric) { create(:prometheus_metric, project: project) } + + it_behaves_like 'valid embedded dashboard service response' + + it 'does not cache the unprocessed dashboard' do + expect(Gitlab::Metrics::Dashboard::Cache).not_to receive(:fetch) + + described_class.new(*service_params).get_dashboard + end + + context 'multiple metrics meet criteria' do + let!(:metric_2) { create(:prometheus_metric, project: project, query: 'avg(metric_2)') } + + it_behaves_like 'valid embedded dashboard service response' + + it 'includes both metrics' do + result = service_call + included_queries = all_queries(result[:dashboard]) + + expect(included_queries).to include('avg(metric_2)', 'avg(metric)') + end + end + end + + context 'when the metric exists in another project' do + let!(:metric) { create(:prometheus_metric, project: create(:project)) } + + it_behaves_like 'misconfigured dashboard service response', :not_found + end + end + + private + + def all_queries(dashboard) + dashboard[:panel_groups].flat_map do |group| + group[:panels].flat_map do |panel| + panel[:metrics].map do |metric| + metric[:query_range] + end + end + end + end +end diff --git a/spec/services/metrics/dashboard/dynamic_embed_service_spec.rb b/spec/services/metrics/dashboard/dynamic_embed_service_spec.rb new file mode 100644 index 00000000000..a0f7315f750 --- /dev/null +++ b/spec/services/metrics/dashboard/dynamic_embed_service_spec.rb @@ -0,0 +1,151 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Metrics::Dashboard::DynamicEmbedService, :use_clean_rails_memory_store_caching do + include MetricsDashboardHelpers + + set(:project) { build(:project) } + set(:user) { create(:user) } + set(:environment) { create(:environment, project: project) } + + before do + project.add_maintainer(user) + end + + let(:dashboard_path) { '.gitlab/dashboards/test.yml' } + let(:group) { 'Group A' } + let(:title) { 'Super Chart A1' } + let(:y_label) { 'y_label' } + + describe '.valid_params?' do + let(:valid_params) do + { + embedded: true, + dashboard_path: dashboard_path, + group: group, + title: title, + y_label: y_label + } + end + + subject { described_class.valid_params?(params) } + + let(:params) { valid_params } + + it { is_expected.to be_truthy } + + context 'not embedded' do + let(:params) { valid_params.except(:embedded) } + + it { is_expected.to be_falsey } + end + + context 'undefined dashboard' do + let(:params) { valid_params.except(:dashboard_path) } + + it { is_expected.to be_truthy } + end + + context 'missing dashboard' do + let(:dashboard) { '' } + + it { is_expected.to be_truthy } + end + + context 'missing group' do + let(:group) { '' } + + it { is_expected.to be_falsey } + end + + context 'missing title' do + let(:title) { '' } + + it { is_expected.to be_falsey } + end + + context 'undefined y-axis label' do + let(:params) { valid_params.except(:y_label) } + + it { is_expected.to be_falsey } + end + end + + describe '#get_dashboard' do + let(:service_params) do + [ + project, + user, + { + environment: environment, + dashboard_path: dashboard_path, + group: group, + title: title, + y_label: y_label + } + ] + end + + let(:service_call) { described_class.new(*service_params).get_dashboard } + + context 'when the dashboard does not exist' do + it_behaves_like 'misconfigured dashboard service response', :not_found + end + + context 'when the dashboard is exists' do + let(:project) { project_with_dashboard(dashboard_path) } + + it_behaves_like 'valid embedded dashboard service response' + it_behaves_like 'raises error for users with insufficient permissions' + + it 'caches the unprocessed dashboard for subsequent calls' do + expect(YAML).to receive(:safe_load).once.and_call_original + + described_class.new(*service_params).get_dashboard + described_class.new(*service_params).get_dashboard + end + + context 'when the specified group is not present on the dashboard' do + let(:group) { 'Group Not Found' } + + it_behaves_like 'misconfigured dashboard service response', :not_found + end + + context 'when the specified title is not present on the dashboard' do + let(:title) { 'Title Not Found' } + + it_behaves_like 'misconfigured dashboard service response', :not_found + end + + context 'when the specified y-axis label is not present on the dashboard' do + let(:y_label) { 'Y-Axis Not Found' } + + it_behaves_like 'misconfigured dashboard service response', :not_found + end + end + + shared_examples 'uses system dashboard' do + it 'uses the default dashboard' do + expect(Gitlab::Metrics::Dashboard::Finder) + .to receive(:find_raw) + .with(project, dashboard_path: system_dashboard_path) + .once + + service_call + end + end + + context 'when the dashboard is nil' do + let(:dashboard_path) { nil } + + it_behaves_like 'uses system dashboard' + end + + context 'when the dashboard is not present' do + let(:dashboard_path) { '' } + + it_behaves_like 'uses system dashboard' + end + end +end diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb index 46abd8f356a..cd4ea9c401d 100644 --- a/spec/services/notes/create_service_spec.rb +++ b/spec/services/notes/create_service_spec.rb @@ -365,5 +365,43 @@ describe Notes::CreateService do .and change { existing_note.updated_at } end end + + describe "usage counter" do + let(:counter) { Gitlab::UsageDataCounters::NoteCounter } + + context 'snippet note' do + let(:snippet) { create(:project_snippet, project: project) } + let(:opts) { { note: 'reply', noteable_type: 'Snippet', noteable_id: snippet.id, project: project } } + + it 'increments usage counter' do + expect do + note = described_class.new(project, user, opts).execute + + expect(note).to be_valid + end.to change { counter.read(:create, opts[:noteable_type]) }.by 1 + end + + it 'does not increment usage counter when creation fails' do + expect do + note = described_class.new(project, user, { note: '' }).execute + + expect(note).to be_invalid + end.not_to change { counter.read(:create, opts[:noteable_type]) } + end + end + + context 'issue note' do + let(:issue) { create(:issue, project: project) } + let(:opts) { { note: 'reply', noteable_type: 'Issue', noteable_id: issue.id, project: project } } + + it 'does not increment usage counter' do + expect do + note = described_class.new(project, user, opts).execute + + expect(note).to be_valid + end.not_to change { counter.read(:create, opts[:noteable_type]) } + end + end + end end end diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb index e436af77ed4..9a6f64b825a 100644 --- a/spec/services/projects/destroy_service_spec.rb +++ b/spec/services/projects/destroy_service_spec.rb @@ -241,6 +241,18 @@ describe Projects::DestroyService do expect(destroy_project(project, user)).to be false end end + + context 'when registry is disabled' do + before do + stub_container_registry_config(enabled: false) + end + + it 'does not attempting to remove any tags' do + expect(Projects::ContainerRepository::DestroyService).not_to receive(:new) + + destroy_project(project, user) + end + end end context 'when there are tags for legacy root repository' do diff --git a/spec/services/self_monitoring/project/create_service_spec.rb b/spec/services/self_monitoring/project/create_service_spec.rb index 7d4faba526b..def20448bd9 100644 --- a/spec/services/self_monitoring/project/create_service_spec.rb +++ b/spec/services/self_monitoring/project/create_service_spec.rb @@ -30,15 +30,13 @@ describe SelfMonitoring::Project::CreateService do context 'with admin users' do let(:project) { result[:project] } + let(:group) { result[:group] } + let(:application_setting) { Gitlab::CurrentSettings.current_application_settings } let!(:user) { create(:user, :admin) } before do - allow(ApplicationSetting) - .to receive(:current) - .and_return( - ApplicationSetting.build_from_defaults(allow_local_requests_from_web_hooks_and_services: true) - ) + application_setting.allow_local_requests_from_web_hooks_and_services = true end shared_examples 'has prometheus service' do |listen_address| @@ -55,6 +53,15 @@ describe SelfMonitoring::Project::CreateService do it_behaves_like 'has prometheus service', 'http://localhost:9090' + it 'creates group' do + expect(result[:status]).to eq(:success) + expect(group).to be_persisted + expect(group.name).to eq(described_class::GROUP_NAME) + expect(group.path).to start_with(described_class::GROUP_PATH) + expect(group.path.split('-').last.length).to eq(8) + expect(group.visibility_level).to eq(described_class::VISIBILITY_LEVEL) + end + it 'creates project with internal visibility' do expect(result[:status]).to eq(:success) expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL) @@ -62,7 +69,7 @@ describe SelfMonitoring::Project::CreateService do end it 'creates project with internal visibility even when internal visibility is restricted' do - stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::INTERNAL]) + application_setting.restricted_visibility_levels = [Gitlab::VisibilityLevel::INTERNAL] expect(result[:status]).to eq(:success) expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL) @@ -71,8 +78,8 @@ describe SelfMonitoring::Project::CreateService do it 'creates project with correct name and description' do expect(result[:status]).to eq(:success) - expect(project.name).to eq(described_class::DEFAULT_NAME) - expect(project.description).to eq(described_class::DEFAULT_DESCRIPTION) + expect(project.name).to eq(described_class::PROJECT_NAME) + expect(project.description).to eq(described_class::PROJECT_DESCRIPTION) end it 'adds all admins as maintainers' do @@ -81,25 +88,53 @@ describe SelfMonitoring::Project::CreateService do create(:user) expect(result[:status]).to eq(:success) - expect(project.owner).to eq(user) - expect(project.members.collect(&:user)).to contain_exactly(user, admin1, admin2) - expect(project.members.collect(&:access_level)).to contain_exactly( - Gitlab::Access::MAINTAINER, + expect(project.owner).to eq(group) + expect(group.members.collect(&:user)).to contain_exactly(user, admin1, admin2) + expect(group.members.collect(&:access_level)).to contain_exactly( + Gitlab::Access::OWNER, Gitlab::Access::MAINTAINER, Gitlab::Access::MAINTAINER ) end + it 'saves the project id' do + expect(result[:status]).to eq(:success) + expect(application_setting.instance_administration_project_id).to eq(project.id) + end + + it 'returns error when saving project ID fails' do + allow(application_setting).to receive(:update) { false } + + expect(result[:status]).to eq(:error) + expect(result[:failed_step]).to eq(:save_project_id) + expect(result[:message]).to eq('Could not save project ID') + end + + it 'does not fail when a project already exists' do + expect(result[:status]).to eq(:success) + + second_result = subject.execute + + expect(second_result[:status]).to eq(:success) + expect(second_result[:project]).to eq(project) + expect(second_result[:group]).to eq(group) + end + context 'when local requests from hooks and services are not allowed' do before do - allow(ApplicationSetting) - .to receive(:current) - .and_return( - ApplicationSetting.build_from_defaults(allow_local_requests_from_web_hooks_and_services: false) - ) + application_setting.allow_local_requests_from_web_hooks_and_services = false end it_behaves_like 'has prometheus service', 'http://localhost:9090' + + it 'does not overwrite the existing whitelist' do + application_setting.outbound_local_requests_whitelist = ['example.com'] + + expect(result[:status]).to eq(:success) + expect(application_setting.outbound_local_requests_whitelist).to contain_exactly( + 'example.com', 'localhost' + ) + end end context 'with non default prometheus address' do @@ -175,7 +210,7 @@ describe SelfMonitoring::Project::CreateService do expect(result).to eq({ status: :error, message: 'Could not add admins as members', - failed_step: :add_project_members + failed_step: :add_group_members }) end end diff --git a/spec/services/update_snippet_service_spec.rb b/spec/services/update_snippet_service_spec.rb index 23ea4e003f8..0678f54c195 100644 --- a/spec/services/update_snippet_service_spec.rb +++ b/spec/services/update_snippet_service_spec.rb @@ -40,6 +40,23 @@ describe UpdateSnippetService do end end + describe 'usage counter' do + let(:counter) { Gitlab::UsageDataCounters::SnippetCounter } + let(:snippet) { create_snippet(nil, @user, @opts) } + + it 'increments count' do + expect do + update_snippet(nil, @admin, snippet, @opts) + end.to change { counter.read(:update) }.by 1 + end + + it 'does not increment count if create fails' do + expect do + update_snippet(nil, @admin, snippet, { title: '' }) + end.not_to change { counter.read(:update) } + end + end + def create_snippet(project, user, opts) CreateSnippetService.new(project, user, opts).execute end diff --git a/spec/support/helpers/metrics_dashboard_helpers.rb b/spec/support/helpers/metrics_dashboard_helpers.rb index 1511a2f6b49..0e86b6dfda7 100644 --- a/spec/support/helpers/metrics_dashboard_helpers.rb +++ b/spec/support/helpers/metrics_dashboard_helpers.rb @@ -18,6 +18,14 @@ module MetricsDashboardHelpers project.repository.refresh_method_caches([:metrics_dashboard]) end + def system_dashboard_path + Metrics::Dashboard::SystemDashboardService::SYSTEM_DASHBOARD_PATH + end + + def business_metric_title + PrometheusMetricEnums.group_details[:business][:group_title] + end + shared_examples_for 'misconfigured dashboard service response' do |status_code| it 'returns an appropriate message and status code' do result = service_call diff --git a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb index fb22498f84f..26ed86bfe26 100644 --- a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb +++ b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb @@ -41,7 +41,15 @@ shared_examples 'issuable notes filter' do get :discussions, params: params.merge(notes_filter: notes_filter) - expect(user.reload.notes_filter_for(issuable)).to eq(0) + expect(user.reload.notes_filter_for(issuable)).to eq(UserPreference::NOTES_FILTERS[:all_notes]) + end + + it 'does not set notes filter when persist_filter param is false' do + notes_filter = UserPreference::NOTES_FILTERS[:only_comments] + + get :discussions, params: params.merge(notes_filter: notes_filter, persist_filter: false) + + expect(user.reload.notes_filter_for(issuable)).to eq(UserPreference::NOTES_FILTERS[:all_notes]) end it 'returns only user comments' do diff --git a/spec/support/shared_examples/relative_positioning_shared_examples.rb b/spec/support/shared_examples/relative_positioning_shared_examples.rb index 9837ba806db..b7382cea93c 100644 --- a/spec/support/shared_examples/relative_positioning_shared_examples.rb +++ b/spec/support/shared_examples/relative_positioning_shared_examples.rb @@ -17,8 +17,8 @@ RSpec.shared_examples 'a class that supports relative positioning' do describe '.move_nulls_to_end' do it 'moves items with null relative_position to the end' do - skip("#{item1} has a default relative position") if item1.relative_position - skip("#{item2} has a default relative position") if item2.relative_position + item1.update!(relative_position: nil) + item2.update!(relative_position: nil) described_class.move_nulls_to_end([item1, item2]) @@ -28,7 +28,7 @@ RSpec.shared_examples 'a class that supports relative positioning' do end it 'moves the item near the start position when there are no existing positions' do - skip("#{item1} has a default relative position") if item1.relative_position + item1.update!(relative_position: nil) described_class.move_nulls_to_end([item1]) diff --git a/spec/views/search/_filter.html.haml_spec.rb b/spec/views/search/_filter.html.haml_spec.rb new file mode 100644 index 00000000000..d2cd636f8c6 --- /dev/null +++ b/spec/views/search/_filter.html.haml_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'search/_filter' do + context 'when the search page is opened' do + it 'displays the correct elements' do + render + + expect(rendered).to have_selector('label[for="dashboard_search_group"]') + expect(rendered).to have_selector('button#dashboard_search_group') + + expect(rendered).to have_selector('label[for="dashboard_search_project"]') + expect(rendered).to have_selector('button#dashboard_search_project') + end + end +end diff --git a/spec/views/search/_form.html.haml_spec.rb b/spec/views/search/_form.html.haml_spec.rb new file mode 100644 index 00000000000..69f40895d86 --- /dev/null +++ b/spec/views/search/_form.html.haml_spec.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'search/_form' do + context 'when the search page is opened' do + it 'displays the correct elements' do + render + + expect(rendered).to have_selector('.search-field-holder.form-group') + expect(rendered).to have_selector('label[for="dashboard_search"]') + end + end +end diff --git a/spec/views/search/show.html.haml_spec.rb b/spec/views/search/show.html.haml_spec.rb new file mode 100644 index 00000000000..483b913f2cc --- /dev/null +++ b/spec/views/search/show.html.haml_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'search/show' do + let(:search_term) { nil } + + before do + stub_template "search/_category.html.haml" => 'Category Partial' + stub_template "search/_results.html.haml" => 'Results Partial' + + @search_term = search_term + + render + end + + context 'when the search page is opened' do + it 'displays the title' do + expect(rendered).to have_selector('h1.page-title', text: 'Search') + expect(rendered).not_to have_selector('h1.page-title code') + end + + it 'does not render partials' do + expect(rendered).not_to render_template('search/_category') + expect(rendered).not_to render_template('search/_results') + end + end + + context 'when search term is supplied' do + let(:search_term) { 'Search Foo' } + + it 'renders partials' do + expect(rendered).to render_template('search/_category') + expect(rendered).to render_template('search/_results') + end + end +end diff --git a/vendor/project_templates/rails.tar.gz b/vendor/project_templates/rails.tar.gz Binary files differindex c1f90c3f8f7..6bb51bcd7a1 100644 --- a/vendor/project_templates/rails.tar.gz +++ b/vendor/project_templates/rails.tar.gz diff --git a/yarn.lock b/yarn.lock index b35f23c2791..2a406b92c5f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -991,6 +991,13 @@ dependencies: requireindex "~1.1.0" +"@gitlab/eslint-plugin-vue-i18n@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@gitlab/eslint-plugin-vue-i18n/-/eslint-plugin-vue-i18n-1.2.0.tgz#6dcd8bf6bdd7a31c1c4c2c4114762508af435836" + integrity sha512-lROk643/C3tBORIJeXNQlPutjAdSkjA43rpNHVxugU5OcpIOfc7oVXDZ23GwiHMZp8bE3QpP/91PEvEY9ywAVQ== + dependencies: + vue-eslint-parser "^6.0.4" + "@gitlab/svgs@^1.67.0": version "1.67.0" resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.67.0.tgz#c7b94eca13b99fd3aaa737fb6dcc0abc41d3c579" @@ -4745,6 +4752,15 @@ espree@^4.0.0, espree@^4.1.0: acorn-jsx "^5.0.0" eslint-visitor-keys "^1.0.0" +espree@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-5.0.1.tgz#5d6526fa4fc7f0788a5cf75b15f30323e2f81f7a" + integrity sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A== + dependencies: + acorn "^6.0.7" + acorn-jsx "^5.0.0" + eslint-visitor-keys "^1.0.0" + esprima@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" @@ -12646,6 +12662,18 @@ vue-eslint-parser@^4.0.2: esquery "^1.0.1" lodash "^4.17.11" +vue-eslint-parser@^6.0.4: + version "6.0.4" + resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-6.0.4.tgz#56ff47e2c2644bff39951d5a284982c7ecd6f7fa" + integrity sha512-GYsDsDWwKaGtnkW4nGUxr01wqIO2FB9/QHQTW1Gl5SUr5OyQvpnR90/D+Gq2cIxURX7aJ7+VyD+37Yx9eFwTgw== + dependencies: + debug "^4.1.1" + eslint-scope "^4.0.0" + eslint-visitor-keys "^1.0.0" + espree "^5.0.0" + esquery "^1.0.1" + lodash "^4.17.11" + vue-functional-data-merge@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/vue-functional-data-merge/-/vue-functional-data-merge-3.1.0.tgz#08a7797583b7f35680587f8a1d51d729aa1dc657" |