Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/api.js7
-rw-r--r--app/assets/javascripts/awards_handler.js33
-rw-r--r--app/assets/javascripts/boards/components/board_list.vue2
-rw-r--r--app/assets/javascripts/boards/components/board_sidebar.js8
-rw-r--r--app/assets/javascripts/boards/models/list.js3
-rw-r--r--app/assets/javascripts/commons/polyfills.js1
-rw-r--r--app/assets/javascripts/diffs/components/app.vue12
-rw-r--r--app/assets/javascripts/diffs/components/diff_file.vue16
-rw-r--r--app/assets/javascripts/diffs/constants.js3
-rw-r--r--app/assets/javascripts/diffs/store/actions.js21
-rw-r--r--app/assets/javascripts/diffs/store/mutation_types.js1
-rw-r--r--app/assets/javascripts/diffs/store/mutations.js43
-rw-r--r--app/assets/javascripts/emoji/support/unicode_support_map.js2
-rw-r--r--app/assets/javascripts/ide/components/ide_tree_list.vue2
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/button.vue11
-rw-r--r--app/assets/javascripts/ide/components/preview/clientside.vue31
-rw-r--r--app/assets/javascripts/ide/components/repo_editor.vue1
-rw-r--r--app/assets/javascripts/ide/ide_router.js100
-rw-r--r--app/assets/javascripts/ide/stores/actions/file.js10
-rw-r--r--app/assets/javascripts/ide/stores/actions/merge_request.js68
-rw-r--r--app/assets/javascripts/ide/stores/actions/project.js32
-rw-r--r--app/assets/javascripts/ide/stores/modules/branches/actions.js3
-rw-r--r--app/assets/javascripts/ide/stores/modules/file_templates/actions.js82
-rw-r--r--app/assets/javascripts/ide/stores/modules/file_templates/getters.js23
-rw-r--r--app/assets/javascripts/ide/stores/modules/file_templates/index.js12
-rw-r--r--app/assets/javascripts/ide/stores/modules/file_templates/mutation_types.js7
-rw-r--r--app/assets/javascripts/ide/stores/modules/file_templates/mutations.js21
-rw-r--r--app/assets/javascripts/ide/stores/modules/file_templates/state.js6
-rw-r--r--app/assets/javascripts/ide/stores/mutations.js7
-rw-r--r--app/assets/javascripts/importer_status.js2
-rw-r--r--app/assets/javascripts/jobs/components/artifacts_block.vue98
-rw-r--r--app/assets/javascripts/jobs/components/commit_block.vue64
-rw-r--r--app/assets/javascripts/jobs/components/empty_state.vue76
-rw-r--r--app/assets/javascripts/jobs/components/erased_block.vue48
-rw-r--r--app/assets/javascripts/jobs/components/job_log.vue33
-rw-r--r--app/assets/javascripts/jobs/components/job_log_controllers.vue139
-rw-r--r--app/assets/javascripts/jobs/components/jobs_container.vue60
-rw-r--r--app/assets/javascripts/jobs/components/sidebar_details_block.vue38
-rw-r--r--app/assets/javascripts/jobs/components/stages_dropdown.vue97
-rw-r--r--app/assets/javascripts/jobs/components/stuck_block.vue63
-rw-r--r--app/assets/javascripts/jobs/components/trigger_block.vue84
-rw-r--r--app/assets/javascripts/jobs/job_details_bundle.js2
-rw-r--r--app/assets/javascripts/labels_select.js9
-rw-r--r--app/assets/javascripts/locale/ensure_single_line.js25
-rw-r--r--app/assets/javascripts/locale/index.js13
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue2
-rw-r--r--app/assets/javascripts/notes/components/note_awards_list.vue24
-rw-r--r--app/assets/javascripts/pages/admin/application_settings/index.js2
-rw-r--r--app/assets/javascripts/pages/instance_statistics/cohorts/index.js (renamed from app/assets/javascripts/pages/admin/cohorts/index.js)0
-rw-r--r--app/assets/javascripts/pages/instance_statistics/cohorts/usage_ping.js (renamed from app/assets/javascripts/pages/admin/cohorts/usage_ping.js)0
-rw-r--r--app/assets/javascripts/pages/instance_statistics/conversational_development_index/index.js (renamed from app/assets/javascripts/pages/admin/conversational_development_index/show/index.js)0
-rw-r--r--app/assets/javascripts/pages/projects/settings/repository/show/index.js8
-rw-r--r--app/assets/javascripts/pages/projects/settings/repository/show/mirror_repos.js94
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_table_row.vue2
-rw-r--r--app/assets/javascripts/project_select.js8
-rw-r--r--app/assets/javascripts/reports/components/grouped_test_reports_app.vue8
-rw-r--r--app/assets/javascripts/reports/components/issue_body.js (renamed from app/assets/javascripts/vue_shared/components/reports/issue_body.js)2
-rw-r--r--app/assets/javascripts/reports/components/issue_status_icon.vue (renamed from app/assets/javascripts/vue_shared/components/reports/issue_status_icon.vue)3
-rw-r--r--app/assets/javascripts/reports/components/issues_list.vue (renamed from app/assets/javascripts/vue_shared/components/reports/issues_list.vue)4
-rw-r--r--app/assets/javascripts/reports/components/modal_open_name.vue (renamed from app/assets/javascripts/vue_shared/components/reports/modal_open_name.vue)0
-rw-r--r--app/assets/javascripts/reports/components/report_issues.vue (renamed from app/assets/javascripts/vue_shared/components/reports/report_issues.vue)4
-rw-r--r--app/assets/javascripts/reports/components/report_link.vue (renamed from app/assets/javascripts/vue_shared/components/reports/report_link.vue)0
-rw-r--r--app/assets/javascripts/reports/components/report_section.vue (renamed from app/assets/javascripts/vue_shared/components/reports/report_section.vue)2
-rw-r--r--app/assets/javascripts/reports/components/summary_row.vue (renamed from app/assets/javascripts/vue_shared/components/reports/summary_row.vue)2
-rw-r--r--app/assets/javascripts/reports/constants.js2
-rw-r--r--app/assets/javascripts/sidebar/components/lock/edit_form_buttons.vue3
-rw-r--r--app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue10
-rw-r--r--app/assets/javascripts/vue_shared/components/reports/constants.js3
-rw-r--r--app/assets/stylesheets/bootstrap_migration.scss13
-rw-r--r--app/assets/stylesheets/framework.scss1
-rw-r--r--app/assets/stylesheets/framework/avatar.scss13
-rw-r--r--app/assets/stylesheets/framework/awards.scss4
-rw-r--r--app/assets/stylesheets/framework/badges.scss2
-rw-r--r--app/assets/stylesheets/framework/buttons.scss10
-rw-r--r--app/assets/stylesheets/framework/callout.scss24
-rw-r--r--app/assets/stylesheets/framework/common.scss37
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss10
-rw-r--r--app/assets/stylesheets/framework/emojis.scss2
-rw-r--r--app/assets/stylesheets/framework/feature_highlight.scss4
-rw-r--r--app/assets/stylesheets/framework/files.scss20
-rw-r--r--app/assets/stylesheets/framework/filters.scss6
-rw-r--r--app/assets/stylesheets/framework/flash.scss4
-rw-r--r--app/assets/stylesheets/framework/forms.scss6
-rw-r--r--app/assets/stylesheets/framework/header.scss2
-rw-r--r--app/assets/stylesheets/framework/icons.scss2
-rw-r--r--app/assets/stylesheets/framework/images.scss2
-rw-r--r--app/assets/stylesheets/framework/issue_box.scss6
-rw-r--r--app/assets/stylesheets/framework/lists.scss18
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss2
-rw-r--r--app/assets/stylesheets/framework/mixins.scss2
-rw-r--r--app/assets/stylesheets/framework/modal.scss1
-rw-r--r--app/assets/stylesheets/framework/selects.scss8
-rw-r--r--app/assets/stylesheets/framework/typography.scss12
-rw-r--r--app/assets/stylesheets/framework/variables.scss158
-rw-r--r--app/assets/stylesheets/framework/variables_overrides.scss20
-rw-r--r--app/assets/stylesheets/framework/zen.scss2
-rw-r--r--app/assets/stylesheets/page_bundles/ide.scss3
-rw-r--r--app/assets/stylesheets/pages/boards.scss4
-rw-r--r--app/assets/stylesheets/pages/builds.scss4
-rw-r--r--app/assets/stylesheets/pages/commits.scss4
-rw-r--r--app/assets/stylesheets/pages/convdev_index.scss12
-rw-r--r--app/assets/stylesheets/pages/cycle_analytics.scss6
-rw-r--r--app/assets/stylesheets/pages/diff.scss6
-rw-r--r--app/assets/stylesheets/pages/editor.scss29
-rw-r--r--app/assets/stylesheets/pages/environments.scss6
-rw-r--r--app/assets/stylesheets/pages/events.scss4
-rw-r--r--app/assets/stylesheets/pages/graph.scss4
-rw-r--r--app/assets/stylesheets/pages/groups.scss7
-rw-r--r--app/assets/stylesheets/pages/issuable.scss26
-rw-r--r--app/assets/stylesheets/pages/issues.scss4
-rw-r--r--app/assets/stylesheets/pages/labels.scss13
-rw-r--r--app/assets/stylesheets/pages/login.scss2
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss8
-rw-r--r--app/assets/stylesheets/pages/milestone.scss2
-rw-r--r--app/assets/stylesheets/pages/note_form.scss16
-rw-r--r--app/assets/stylesheets/pages/notes.scss33
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss12
-rw-r--r--app/assets/stylesheets/pages/profile.scss6
-rw-r--r--app/assets/stylesheets/pages/projects.scss47
-rw-r--r--app/assets/stylesheets/pages/runners.scss10
-rw-r--r--app/assets/stylesheets/pages/search.scss14
-rw-r--r--app/assets/stylesheets/pages/settings.scss24
-rw-r--r--app/assets/stylesheets/pages/stat_graph.scss4
-rw-r--r--app/assets/stylesheets/pages/todos.scss6
-rw-r--r--app/assets/stylesheets/pages/tree.scss8
-rw-r--r--app/assets/stylesheets/performance_bar.scss2
-rw-r--r--app/controllers/application_controller.rb2
-rw-r--r--app/controllers/autocomplete_controller.rb72
-rw-r--r--app/controllers/concerns/issuable_collections.rb22
-rw-r--r--app/controllers/concerns/renders_commits.rb20
-rw-r--r--app/controllers/concerns/toggle_award_emoji.rb2
-rw-r--r--app/controllers/notification_settings_controller.rb8
-rw-r--r--app/controllers/projects/autocomplete_sources_controller.rb11
-rw-r--r--app/controllers/projects/commit_controller.rb2
-rw-r--r--app/controllers/projects/commits_controller.rb2
-rw-r--r--app/controllers/projects/compare_controller.rb2
-rw-r--r--app/controllers/projects/issues_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests/creations_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb2
-rw-r--r--app/controllers/projects/pages_controller.rb2
-rw-r--r--app/finders/autocomplete/group_finder.rb37
-rw-r--r--app/finders/autocomplete/move_to_project_finder.rb35
-rw-r--r--app/finders/autocomplete/project_finder.rb35
-rw-r--r--app/finders/autocomplete/users_finder.rb85
-rw-r--r--app/finders/autocomplete_users_finder.rb68
-rw-r--r--app/finders/awarded_emoji_finder.rb21
-rw-r--r--app/finders/license_template_finder.rb36
-rw-r--r--app/finders/move_to_project_finder.rb21
-rw-r--r--app/finders/user_finder.rb26
-rw-r--r--app/helpers/application_settings_helper.rb1
-rw-r--r--app/helpers/avatars_helper.rb52
-rw-r--r--app/helpers/blob_helper.rb12
-rw-r--r--app/helpers/button_helper.rb6
-rw-r--r--app/helpers/commits_helper.rb11
-rw-r--r--app/helpers/groups_helper.rb5
-rw-r--r--app/helpers/icons_helper.rb2
-rw-r--r--app/helpers/issues_helper.rb8
-rw-r--r--app/helpers/mirror_helper.rb5
-rw-r--r--app/helpers/notifications_helper.rb12
-rw-r--r--app/helpers/projects_helper.rb5
-rw-r--r--app/mailers/abuse_report_mailer.rb2
-rw-r--r--app/mailers/base_mailer.rb2
-rw-r--r--app/mailers/devise_mailer.rb9
-rw-r--r--app/mailers/email_rejection_mailer.rb2
-rw-r--r--app/mailers/emails/issues.rb2
-rw-r--r--app/mailers/emails/members.rb2
-rw-r--r--app/mailers/emails/merge_requests.rb2
-rw-r--r--app/mailers/emails/notes.rb2
-rw-r--r--app/mailers/emails/pages_domains.rb2
-rw-r--r--app/mailers/emails/pipelines.rb8
-rw-r--r--app/mailers/emails/profile.rb2
-rw-r--r--app/mailers/emails/projects.rb2
-rw-r--r--app/mailers/notify.rb16
-rw-r--r--app/mailers/previews/devise_mailer_preview.rb2
-rw-r--r--app/mailers/previews/email_rejection_mailer_preview.rb2
-rw-r--r--app/mailers/previews/notify_preview.rb2
-rw-r--r--app/mailers/previews/repository_check_mailer_preview.rb2
-rw-r--r--app/mailers/repository_check_mailer.rb2
-rw-r--r--app/models/application_setting.rb3
-rw-r--r--app/models/award_emoji.rb17
-rw-r--r--app/models/ci/build.rb7
-rw-r--r--app/models/ci/job_artifact.rb2
-rw-r--r--app/models/commit.rb5
-rw-r--r--app/models/concerns/awardable.rb18
-rw-r--r--app/models/concerns/fast_destroy_all.rb2
-rw-r--r--app/models/concerns/optionally_search.rb19
-rw-r--r--app/models/internal_id.rb6
-rw-r--r--app/models/issue.rb47
-rw-r--r--app/models/lfs_object.rb2
-rw-r--r--app/models/license_template.rb53
-rw-r--r--app/models/member.rb2
-rw-r--r--app/models/namespace.rb2
-rw-r--r--app/models/notification_setting.rb9
-rw-r--r--app/models/programming_language.rb2
-rw-r--r--app/models/project.rb52
-rw-r--r--app/models/project_auto_devops.rb6
-rw-r--r--app/models/project_services/asana_service.rb2
-rw-r--r--app/models/project_services/assembla_service.rb2
-rw-r--r--app/models/project_services/bamboo_service.rb2
-rw-r--r--app/models/project_services/bugzilla_service.rb2
-rw-r--r--app/models/project_services/buildkite_service.rb2
-rw-r--r--app/models/project_services/builds_email_service.rb2
-rw-r--r--app/models/project_services/campfire_service.rb6
-rw-r--r--app/models/project_services/chat_message/base_message.rb2
-rw-r--r--app/models/project_services/chat_message/issue_message.rb2
-rw-r--r--app/models/project_services/chat_message/merge_message.rb2
-rw-r--r--app/models/project_services/chat_message/note_message.rb2
-rw-r--r--app/models/project_services/chat_message/pipeline_message.rb2
-rw-r--r--app/models/project_services/chat_message/push_message.rb2
-rw-r--r--app/models/project_services/chat_message/wiki_page_message.rb2
-rw-r--r--app/models/project_services/chat_notification_service.rb2
-rw-r--r--app/models/project_services/ci_service.rb2
-rw-r--r--app/models/project_services/custom_issue_tracker_service.rb2
-rw-r--r--app/models/project_services/deployment_service.rb2
-rw-r--r--app/models/project_services/drone_ci_service.rb2
-rw-r--r--app/models/project_services/emails_on_push_service.rb2
-rw-r--r--app/models/project_services/external_wiki_service.rb2
-rw-r--r--app/models/project_services/flowdock_service.rb2
-rw-r--r--app/models/project_services/gemnasium_service.rb60
-rw-r--r--app/models/project_services/gitlab_issue_tracker_service.rb2
-rw-r--r--app/models/project_services/hangouts_chat_service.rb2
-rw-r--r--app/models/project_services/hipchat_service.rb22
-rw-r--r--app/models/project_services/irker_service.rb2
-rw-r--r--app/models/project_services/issue_tracker_service.rb2
-rw-r--r--app/models/project_services/jira_service.rb2
-rw-r--r--app/models/project_services/kubernetes_service.rb2
-rw-r--r--app/models/project_services/mattermost_service.rb2
-rw-r--r--app/models/project_services/mattermost_slash_commands_service.rb2
-rw-r--r--app/models/project_services/microsoft_teams_service.rb2
-rw-r--r--app/models/project_services/mock_ci_service.rb2
-rw-r--r--app/models/project_services/mock_deployment_service.rb2
-rw-r--r--app/models/project_services/mock_monitoring_service.rb2
-rw-r--r--app/models/project_services/monitoring_service.rb2
-rw-r--r--app/models/project_services/packagist_service.rb2
-rw-r--r--app/models/project_services/pipelines_email_service.rb2
-rw-r--r--app/models/project_services/pivotaltracker_service.rb2
-rw-r--r--app/models/project_services/prometheus_service.rb2
-rw-r--r--app/models/project_services/pushover_service.rb4
-rw-r--r--app/models/project_services/redmine_service.rb2
-rw-r--r--app/models/project_services/slack_service.rb2
-rw-r--r--app/models/project_services/slack_slash_commands_service.rb2
-rw-r--r--app/models/project_services/slash_commands_service.rb2
-rw-r--r--app/models/project_services/teamcity_service.rb2
-rw-r--r--app/models/protected_branch/merge_access_level.rb2
-rw-r--r--app/models/protected_branch/push_access_level.rb2
-rw-r--r--app/models/protected_tag/create_access_level.rb2
-rw-r--r--app/models/remote_mirror.rb9
-rw-r--r--app/models/repository_language.rb2
-rw-r--r--app/models/site_statistic.rb4
-rw-r--r--app/models/storage/hashed_project.rb2
-rw-r--r--app/models/storage/legacy_project.rb2
-rw-r--r--app/models/system_note_metadata.rb2
-rw-r--r--app/models/user.rb77
-rw-r--r--app/policies/commit_policy.rb5
-rw-r--r--app/policies/project_policy.rb1
-rw-r--r--app/presenters/ci/build_runner_presenter.rb2
-rw-r--r--app/serializers/environment_entity.rb11
-rw-r--r--app/serializers/move_to_project_entity.rb6
-rw-r--r--app/serializers/move_to_project_serializer.rb5
-rw-r--r--app/serializers/project_mirror_serializer.rb5
-rw-r--r--app/serializers/test_case_entity.rb2
-rw-r--r--app/serializers/test_reports_comparer_entity.rb2
-rw-r--r--app/serializers/test_reports_comparer_serializer.rb2
-rw-r--r--app/serializers/test_suite_comparer_entity.rb2
-rw-r--r--app/services/ci/enqueue_build_service.rb8
-rw-r--r--app/services/ci/process_pipeline_service.rb6
-rw-r--r--app/services/commits/tag_service.rb26
-rw-r--r--app/services/git_push_service.rb15
-rw-r--r--app/services/groups/destroy_service.rb7
-rw-r--r--app/services/issues/fetch_referenced_merge_requests_service.rb14
-rw-r--r--app/services/issues/referenced_merge_requests_service.rb66
-rw-r--r--app/services/labels/promote_service.rb2
-rw-r--r--app/services/milestones/promote_service.rb2
-rw-r--r--app/services/notes/quick_actions_service.rb3
-rw-r--r--app/services/notification_recipient_service.rb12
-rw-r--r--app/services/preview_markdown_service.rb12
-rw-r--r--app/services/projects/autocomplete_service.rb10
-rw-r--r--app/services/projects/destroy_service.rb3
-rw-r--r--app/services/projects/detect_repository_languages_service.rb2
-rw-r--r--app/services/projects/fork_service.rb8
-rw-r--r--app/services/projects/move_deploy_keys_projects_service.rb2
-rw-r--r--app/services/projects/move_lfs_objects_projects_service.rb2
-rw-r--r--app/services/projects/move_notification_settings_service.rb2
-rw-r--r--app/services/projects/move_project_group_links_service.rb2
-rw-r--r--app/services/projects/move_project_members_service.rb2
-rw-r--r--app/services/projects/update_remote_mirror_service.rb1
-rw-r--r--app/services/projects/update_service.rb2
-rw-r--r--app/services/protected_branches/legacy_api_update_service.rb4
-rw-r--r--app/services/quick_actions/interpret_service.rb49
-rw-r--r--app/services/quick_actions/target_service.rb30
-rw-r--r--app/services/system_note_service.rb15
-rw-r--r--app/services/tags/create_service.rb2
-rw-r--r--app/services/todos/destroy/base_service.rb2
-rw-r--r--app/services/todos/destroy/confidential_issue_service.rb2
-rw-r--r--app/services/todos/destroy/entity_leave_service.rb2
-rw-r--r--app/services/todos/destroy/group_private_service.rb2
-rw-r--r--app/services/todos/destroy/private_features_service.rb2
-rw-r--r--app/services/todos/destroy/project_private_service.rb2
-rw-r--r--app/services/users/destroy_service.rb7
-rw-r--r--app/views/admin/application_settings/_account_and_limit.html.haml6
-rw-r--r--app/views/admin/application_settings/_ci_cd.html.haml2
-rw-r--r--app/views/admin/application_settings/show.html.haml2
-rw-r--r--app/views/admin/hook_logs/show.html.haml3
-rw-r--r--app/views/admin/runners/index.html.haml2
-rw-r--r--app/views/admin/spam_logs/index.html.haml2
-rw-r--r--app/views/admin/users/_access_levels.html.haml12
-rw-r--r--app/views/admin/users/_form.html.haml33
-rw-r--r--app/views/admin/users/show.html.haml12
-rw-r--r--app/views/award_emoji/_awards_block.html.haml3
-rw-r--r--app/views/instance_statistics/cohorts/_cohorts_table.html.haml2
-rw-r--r--app/views/instance_statistics/conversational_development_index/_no_data.html.haml2
-rw-r--r--app/views/instance_statistics/conversational_development_index/index.html.haml2
-rw-r--r--app/views/projects/blob/_editor.html.haml8
-rw-r--r--app/views/projects/commits/_commit_list.html.haml5
-rw-r--r--app/views/projects/commits/_commits.html.haml3
-rw-r--r--app/views/projects/environments/metrics.html.haml7
-rw-r--r--app/views/projects/forks/_fork_button.html.haml4
-rw-r--r--app/views/projects/hook_logs/show.html.haml2
-rw-r--r--app/views/projects/jobs/_sidebar.html.haml7
-rw-r--r--app/views/projects/merge_requests/creations/_new_submit.html.haml2
-rw-r--r--app/views/projects/mirrors/_instructions.html.haml15
-rw-r--r--app/views/projects/mirrors/_mirror_repos.html.haml63
-rw-r--r--app/views/projects/mirrors/_mirror_repos_form.html.haml18
-rw-r--r--app/views/projects/mirrors/_push.html.haml50
-rw-r--r--app/views/projects/mirrors/_show.html.haml4
-rw-r--r--app/views/projects/notes/_actions.html.haml2
-rw-r--r--app/views/projects/pages/_use.html.haml4
-rw-r--r--app/views/projects/runners/_specific_runners.html.haml2
-rw-r--r--app/views/projects/settings/ci_cd/_form.html.haml4
-rw-r--r--app/views/projects/show.html.haml3
-rw-r--r--app/views/search/results/_blob.html.haml2
-rw-r--r--app/views/shared/_remote_mirror_update_button.html.haml19
-rw-r--r--app/views/shared/milestones/_issuables.html.haml4
-rw-r--r--app/views/shared/notifications/_custom_notifications.html.haml2
-rw-r--r--app/views/shared/plugins/_index.html.haml4
-rw-r--r--app/views/shared/runners/_form.html.haml30
-rw-r--r--app/views/snippets/notes/_actions.html.haml2
-rw-r--r--app/workers/detect_repository_languages_worker.rb4
-rw-r--r--app/workers/remove_expired_group_links_worker.rb2
-rw-r--r--app/workers/remove_old_web_hook_logs_worker.rb2
-rw-r--r--app/workers/todos_destroyer/confidential_issue_worker.rb2
-rw-r--r--app/workers/todos_destroyer/entity_leave_worker.rb2
-rw-r--r--app/workers/todos_destroyer/group_private_worker.rb2
-rw-r--r--app/workers/todos_destroyer/private_features_worker.rb2
-rw-r--r--app/workers/todos_destroyer/project_private_worker.rb2
345 files changed, 3013 insertions, 1277 deletions
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index 25fe2ae553e..cd800d75f7a 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -15,6 +15,7 @@ const Api = {
mergeRequestChangesPath: '/api/:version/projects/:id/merge_requests/:mrid/changes',
mergeRequestVersionsPath: '/api/:version/projects/:id/merge_requests/:mrid/versions',
groupLabelsPath: '/groups/:namespace_path/-/labels',
+ templatesPath: '/api/:version/templates/:key',
licensePath: '/api/:version/templates/licenses/:key',
gitignorePath: '/api/:version/templates/gitignores/:key',
gitlabCiYmlPath: '/api/:version/templates/gitlab_ci_ymls/:key',
@@ -265,6 +266,12 @@ const Api = {
});
},
+ templates(key, params = {}) {
+ const url = Api.buildUrl(this.templatesPath).replace(':key', key);
+
+ return axios.get(url, { params });
+ },
+
buildUrl(url) {
let urlRoot = '';
if (gon.relative_url_root != null) {
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
index e34db893989..5b0c4285339 100644
--- a/app/assets/javascripts/awards_handler.js
+++ b/app/assets/javascripts/awards_handler.js
@@ -109,8 +109,6 @@ export class AwardsHandler {
}
const $menu = $(`.${this.menuClass}`);
- const $thumbsBtn = $menu.find('[data-name="thumbsup"], [data-name="thumbsdown"]').parent();
- const $userAuthored = this.isUserAuthored($addBtn);
if ($menu.length) {
if ($menu.is('.is-visible')) {
$addBtn.removeClass('is-active');
@@ -134,9 +132,6 @@ export class AwardsHandler {
}, 200);
});
}
-
- $thumbsBtn.toggleClass('disabled', $userAuthored);
- $thumbsBtn.prop('disabled', $userAuthored);
}
// Create the emoji menu with the first category of emojis.
@@ -364,10 +359,6 @@ export class AwardsHandler {
return $emojiButton.hasClass('active');
}
- isUserAuthored($button) {
- return $button.hasClass('js-user-authored');
- }
-
decrementCounter($emojiButton, emoji) {
const counter = $('.js-counter', $emojiButton);
const counterNumber = parseInt(counter.text(), 10);
@@ -474,20 +465,16 @@ export class AwardsHandler {
}
postEmoji($emojiButton, awardUrl, emoji, callback) {
- if (this.isUserAuthored($emojiButton)) {
- this.userAuthored($emojiButton);
- } else {
- axios
- .post(awardUrl, {
- name: emoji,
- })
- .then(({ data }) => {
- if (data.ok) {
- callback();
- }
- })
- .catch(() => flash(__('Something went wrong on our end.')));
- }
+ axios
+ .post(awardUrl, {
+ name: emoji,
+ })
+ .then(({ data }) => {
+ if (data.ok) {
+ callback();
+ }
+ })
+ .catch(() => flash(__('Something went wrong on our end.')));
}
findEmojiIcon(votesBlock, emoji) {
diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue
index 3e610a4088c..bfc8d9b03ad 100644
--- a/app/assets/javascripts/boards/components/board_list.vue
+++ b/app/assets/javascripts/boards/components/board_list.vue
@@ -203,7 +203,7 @@ export default {
this.showIssueForm = !this.showIssueForm;
},
onScroll() {
- if (!this.loadingMore && (this.scrollTop() > this.scrollHeight() - this.scrollOffset)) {
+ if (!this.list.loadingMore && (this.scrollTop() > this.scrollHeight() - this.scrollOffset)) {
this.loadNextPage();
}
},
diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js
index a9102743bf9..109e60cbde2 100644
--- a/app/assets/javascripts/boards/components/board_sidebar.js
+++ b/app/assets/javascripts/boards/components/board_sidebar.js
@@ -3,7 +3,7 @@
import $ from 'jquery';
import Vue from 'vue';
import Flash from '../../flash';
-import { __ } from '../../locale';
+import { sprintf, __ } from '../../locale';
import Sidebar from '../../right_sidebar';
import eventHub from '../../sidebar/event_hub';
import AssigneeTitle from '../../sidebar/components/assignees/assignee_title.vue';
@@ -55,8 +55,10 @@ gl.issueBoards.BoardSidebar = Vue.extend({
return this.issue.labels && this.issue.labels.length;
},
labelDropdownTitle() {
- return this.hasLabels ?
- `${this.issue.labels[0].title} ${this.issue.labels.length - 1}+ more` : 'Label';
+ return this.hasLabels ? sprintf(__('%{firstLabel} +%{labelCount} more'), {
+ firstLabel: this.issue.labels[0].title,
+ labelCount: this.issue.labels.length - 1
+ }) : __('Label');
},
selectedLabels() {
return this.hasLabels ? this.issue.labels.map(l => l.title).join(',') : '';
diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js
index 050cbd8db48..ad473404c29 100644
--- a/app/assets/javascripts/boards/models/list.js
+++ b/app/assets/javascripts/boards/models/list.js
@@ -1,6 +1,7 @@
/* eslint-disable no-underscore-dangle, class-methods-use-this, consistent-return, no-shadow, no-param-reassign, max-len */
/* global ListIssue */
+import { __ } from '~/locale';
import ListLabel from '~/vue_shared/models/label';
import ListAssignee from '~/vue_shared/models/assignee';
import queryData from '../utils/query_data';
@@ -30,7 +31,7 @@ class List {
this.id = obj.id;
this._uid = this.guid();
this.position = obj.position;
- this.title = obj.title;
+ this.title = obj.list_type === 'backlog' ? __('Open') : obj.title;
this.type = obj.list_type;
const typeInfo = this.getTypeInfo(this.type);
diff --git a/app/assets/javascripts/commons/polyfills.js b/app/assets/javascripts/commons/polyfills.js
index 589eeee9695..742cf490ad2 100644
--- a/app/assets/javascripts/commons/polyfills.js
+++ b/app/assets/javascripts/commons/polyfills.js
@@ -8,6 +8,7 @@ import 'core-js/fn/object/assign';
import 'core-js/fn/promise';
import 'core-js/fn/string/code-point-at';
import 'core-js/fn/string/from-code-point';
+import 'core-js/fn/string/includes';
import 'core-js/fn/symbol';
import 'core-js/es6/map';
import 'core-js/es6/weak-map';
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue
index 7cc4e6a2c3a..b5b05df4d34 100644
--- a/app/assets/javascripts/diffs/components/app.vue
+++ b/app/assets/javascripts/diffs/components/app.vue
@@ -114,11 +114,15 @@ export default {
this.adjustView();
},
methods: {
- ...mapActions('diffs', ['setBaseConfig', 'fetchDiffFiles']),
+ ...mapActions('diffs', ['setBaseConfig', 'fetchDiffFiles', 'startRenderDiffsQueue']),
fetchData() {
- this.fetchDiffFiles().catch(() => {
- createFlash(__('Something went wrong on our end. Please try again!'));
- });
+ this.fetchDiffFiles()
+ .then(() => {
+ requestIdleCallback(this.startRenderDiffsQueue, { timeout: 1000 });
+ })
+ .catch(() => {
+ createFlash(__('Something went wrong on our end. Please try again!'));
+ });
if (!this.isNotesFetched) {
eventHub.$emit('fetchNotesData');
diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue
index 7e7058d8d08..59e9ba08b8b 100644
--- a/app/assets/javascripts/diffs/components/diff_file.vue
+++ b/app/assets/javascripts/diffs/components/diff_file.vue
@@ -46,16 +46,25 @@ export default {
showExpandMessage() {
return this.isCollapsed && !this.isLoadingCollapsedDiff && !this.file.tooLarge;
},
+ showLoadingIcon() {
+ return this.isLoadingCollapsedDiff || (!this.file.renderIt && !this.isCollapsed);
+ },
},
methods: {
...mapActions('diffs', ['loadCollapsedDiff']),
handleToggle() {
const { collapsed, highlightedDiffLines, parallelDiffLines } = this.file;
- if (collapsed && !highlightedDiffLines && !parallelDiffLines.length) {
+ if (
+ collapsed &&
+ !highlightedDiffLines &&
+ parallelDiffLines !== undefined &&
+ !parallelDiffLines.length
+ ) {
this.handleLoadCollapsedDiff();
} else {
this.file.collapsed = !this.file.collapsed;
+ this.file.renderIt = true;
}
},
handleLoadCollapsedDiff() {
@@ -65,6 +74,7 @@ export default {
.then(() => {
this.isLoadingCollapsedDiff = false;
this.file.collapsed = false;
+ this.file.renderIt = true;
})
.catch(() => {
this.isLoadingCollapsedDiff = false;
@@ -121,12 +131,12 @@ export default {
</div>
<diff-content
- v-if="!isCollapsed"
+ v-if="!isCollapsed && file.renderIt"
:class="{ hidden: isCollapsed || file.tooLarge }"
:diff-file="file"
/>
<loading-icon
- v-if="isLoadingCollapsedDiff"
+ v-else-if="showLoadingIcon"
class="diff-content loading"
/>
<div
diff --git a/app/assets/javascripts/diffs/constants.js b/app/assets/javascripts/diffs/constants.js
index 2fa8367f528..f68afa44837 100644
--- a/app/assets/javascripts/diffs/constants.js
+++ b/app/assets/javascripts/diffs/constants.js
@@ -25,3 +25,6 @@ export const CONTEXT_LINE_CLASS_NAME = 'diff-expanded';
export const UNFOLD_COUNT = 20;
export const COUNT_OF_AVATARS_IN_GUTTER = 3;
export const LENGTH_OF_AVATAR_TOOLTIP = 17;
+
+export const LINES_TO_BE_RENDERED_DIRECTLY = 100;
+export const MAX_LINES_TO_BE_RENDERED = 2000;
diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js
index 27001142257..4ab6ceb249a 100644
--- a/app/assets/javascripts/diffs/store/actions.js
+++ b/app/assets/javascripts/diffs/store/actions.js
@@ -29,6 +29,27 @@ export const fetchDiffFiles = ({ state, commit }) => {
.then(handleLocationHash);
};
+export const startRenderDiffsQueue = ({ state, commit }) => {
+ const checkItem = () => {
+ const nextFile = state.diffFiles.find(
+ file => !file.renderIt && (!file.collapsed || !file.text),
+ );
+ if (nextFile) {
+ requestAnimationFrame(() => {
+ commit(types.RENDER_FILE, nextFile);
+ });
+ requestIdleCallback(
+ () => {
+ checkItem();
+ },
+ { timeout: 1000 },
+ );
+ }
+ };
+
+ checkItem();
+};
+
export const setInlineDiffViewType = ({ commit }) => {
commit(types.SET_DIFF_VIEW_TYPE, INLINE_DIFF_VIEW_TYPE);
diff --git a/app/assets/javascripts/diffs/store/mutation_types.js b/app/assets/javascripts/diffs/store/mutation_types.js
index 2c8e1a1466f..c999d637d50 100644
--- a/app/assets/javascripts/diffs/store/mutation_types.js
+++ b/app/assets/javascripts/diffs/store/mutation_types.js
@@ -8,3 +8,4 @@ export const REMOVE_COMMENT_FORM_LINE = 'REMOVE_COMMENT_FORM_LINE';
export const ADD_CONTEXT_LINES = 'ADD_CONTEXT_LINES';
export const ADD_COLLAPSED_DIFFS = 'ADD_COLLAPSED_DIFFS';
export const EXPAND_ALL_FILES = 'EXPAND_ALL_FILES';
+export const RENDER_FILE = 'RENDER_FILE';
diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js
index a98b2be89a3..0522e32c410 100644
--- a/app/assets/javascripts/diffs/store/mutations.js
+++ b/app/assets/javascripts/diffs/store/mutations.js
@@ -2,6 +2,7 @@ import Vue from 'vue';
import _ from 'underscore';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { findDiffFile, addLineReferences, removeMatchLine, addContextLines } from './utils';
+import { LINES_TO_BE_RENDERED_DIRECTLY, MAX_LINES_TO_BE_RENDERED } from '../constants';
import * as types from './mutation_types';
export default {
@@ -15,8 +16,48 @@ export default {
},
[types.SET_DIFF_DATA](state, data) {
+ const diffData = convertObjectPropsToCamelCase(data, { deep: true });
+ let showingLines = 0;
+ const filesLength = diffData.diffFiles.length;
+ let i;
+ for (i = 0; i < filesLength; i += 1) {
+ const file = diffData.diffFiles[i];
+ if (file.parallelDiffLines) {
+ const linesLength = file.parallelDiffLines.length;
+ let u = 0;
+ for (u = 0; u < linesLength; u += 1) {
+ const line = file.parallelDiffLines[u];
+ if (line.left) delete line.left.text;
+ if (line.right) delete line.right.text;
+ }
+ }
+
+ if (file.highlightedDiffLines) {
+ const linesLength = file.highlightedDiffLines.length;
+ let u;
+ for (u = 0; u < linesLength; u += 1) {
+ const line = file.highlightedDiffLines[u];
+ delete line.text;
+ }
+ }
+
+ if (file.highlightedDiffLines) {
+ showingLines += file.parallelDiffLines.length;
+ }
+ Object.assign(file, {
+ renderIt: showingLines < LINES_TO_BE_RENDERED_DIRECTLY,
+ collapsed: file.text && showingLines > MAX_LINES_TO_BE_RENDERED,
+ });
+ }
+
Object.assign(state, {
- ...convertObjectPropsToCamelCase(data, { deep: true }),
+ ...diffData,
+ });
+ },
+
+ [types.RENDER_FILE](state, file) {
+ Object.assign(file, {
+ renderIt: true,
});
},
diff --git a/app/assets/javascripts/emoji/support/unicode_support_map.js b/app/assets/javascripts/emoji/support/unicode_support_map.js
index 8c1861c56db..651169391fe 100644
--- a/app/assets/javascripts/emoji/support/unicode_support_map.js
+++ b/app/assets/javascripts/emoji/support/unicode_support_map.js
@@ -86,7 +86,7 @@ function generateUnicodeSupportMap(testMap) {
canvas.height = numTestEntries * fontSize;
ctx.fillStyle = '#000000';
ctx.textBaseline = 'middle';
- ctx.font = `${fontSize}px "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"`;
+ ctx.font = `${fontSize}px "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"`;
// Write each emoji to the canvas vertically
let writeIndex = 0;
testMapKeys.forEach(testKey => {
diff --git a/app/assets/javascripts/ide/components/ide_tree_list.vue b/app/assets/javascripts/ide/components/ide_tree_list.vue
index 5611b37be7c..00ae5ea2c15 100644
--- a/app/assets/javascripts/ide/components/ide_tree_list.vue
+++ b/app/assets/javascripts/ide/components/ide_tree_list.vue
@@ -61,7 +61,7 @@ export default {
<slot name="header"></slot>
</header>
<div
- class="ide-tree-body"
+ class="ide-tree-body h-100"
>
<repo-file
v-for="file in currentTree.tree"
diff --git a/app/assets/javascripts/ide/components/new_dropdown/button.vue b/app/assets/javascripts/ide/components/new_dropdown/button.vue
index ff114e47741..aa5fce59dbf 100644
--- a/app/assets/javascripts/ide/components/new_dropdown/button.vue
+++ b/app/assets/javascripts/ide/components/new_dropdown/button.vue
@@ -1,7 +1,11 @@
<script>
import Icon from '~/vue_shared/components/icon.vue';
+import tooltip from '~/vue_shared/directives/tooltip';
export default {
+ directives: {
+ tooltip,
+ },
components: {
Icon,
},
@@ -26,6 +30,11 @@ export default {
default: true,
},
},
+ computed: {
+ tooltipTitle() {
+ return this.showLabel ? '' : this.label;
+ },
+ },
methods: {
clicked() {
this.$emit('click');
@@ -36,7 +45,9 @@ export default {
<template>
<button
+ v-tooltip
:aria-label="label"
+ :title="tooltipTitle"
type="button"
class="btn-blank"
@click.stop.prevent="clicked"
diff --git a/app/assets/javascripts/ide/components/preview/clientside.vue b/app/assets/javascripts/ide/components/preview/clientside.vue
index fef36eae7b1..39a1bd1f61b 100644
--- a/app/assets/javascripts/ide/components/preview/clientside.vue
+++ b/app/assets/javascripts/ide/components/preview/clientside.vue
@@ -2,6 +2,7 @@
import { mapActions, mapGetters, mapState } from 'vuex';
import _ from 'underscore';
import { Manager } from 'smooshpack';
+import { listen } from 'codesandbox-api';
import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
import Navigator from './navigator.vue';
import { packageJsonPath } from '../../constants';
@@ -16,6 +17,7 @@ export default {
return {
manager: {},
loading: false,
+ sandpackReady: false,
};
},
computed: {
@@ -81,6 +83,10 @@ export default {
}
this.manager = {};
+ if (this.listener) {
+ this.listener();
+ }
+
clearTimeout(this.timeout);
this.timeout = null;
},
@@ -96,17 +102,29 @@ export default {
return this.loadFileContent(this.mainEntry)
.then(() => this.$nextTick())
- .then(() =>
+ .then(() => {
this.initManager('#ide-preview', this.sandboxOpts, {
fileResolver: {
isFile: p => Promise.resolve(!!this.entries[createPathWithExt(p)]),
readFile: p => this.loadFileContent(createPathWithExt(p)).then(content => content),
},
- }),
- );
+ });
+
+ this.listener = listen(e => {
+ switch (e.type) {
+ case 'done':
+ this.sandpackReady = true;
+ break;
+ default:
+ break;
+ }
+ });
+ });
},
update() {
- if (this.timeout) return;
+ if (!this.sandpackReady) return;
+
+ clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
if (_.isEmpty(this.manager)) {
@@ -116,10 +134,7 @@ export default {
}
this.manager.updatePreview(this.sandboxOpts);
-
- clearTimeout(this.timeout);
- this.timeout = null;
- }, 500);
+ }, 250);
},
initManager(el, opts, resolver) {
this.manager = new Manager(el, opts, resolver);
diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue
index f9badb01535..f55aa843444 100644
--- a/app/assets/javascripts/ide/components/repo_editor.vue
+++ b/app/assets/javascripts/ide/components/repo_editor.vue
@@ -133,7 +133,6 @@ export default {
.then(() =>
this.getRawFileData({
path: this.file.path,
- baseSha: this.currentMergeRequest ? this.currentMergeRequest.baseCommitSha : '',
}),
)
.then(() => {
diff --git a/app/assets/javascripts/ide/ide_router.js b/app/assets/javascripts/ide/ide_router.js
index c6d7d218e81..3f6101e58f4 100644
--- a/app/assets/javascripts/ide/ide_router.js
+++ b/app/assets/javascripts/ide/ide_router.js
@@ -3,7 +3,6 @@ import VueRouter from 'vue-router';
import { join as joinPath } from 'path';
import flash from '~/flash';
import store from './stores';
-import { activityBarViews } from './constants';
Vue.use(VueRouter);
@@ -74,98 +73,23 @@ router.beforeEach((to, from, next) => {
projectId: to.params.project,
})
.then(() => {
- const fullProjectId = `${to.params.namespace}/${to.params.project}`;
-
+ const basePath = to.params[0] || '';
+ const projectId = `${to.params.namespace}/${to.params.project}`;
const branchId = to.params.branchid;
+ const mergeRequestId = to.params.mrid;
if (branchId) {
- const basePath = to.params[0] || '';
-
- store.dispatch('setCurrentBranchId', branchId);
-
- store.dispatch('getBranchData', {
- projectId: fullProjectId,
+ store.dispatch('openBranch', {
+ projectId,
branchId,
+ basePath,
+ });
+ } else if (mergeRequestId) {
+ store.dispatch('openMergeRequest', {
+ projectId,
+ mergeRequestId,
+ targetProjectId: to.query.target_project,
});
-
- store
- .dispatch('getFiles', {
- projectId: fullProjectId,
- branchId,
- })
- .then(() => {
- if (basePath) {
- const path = basePath.slice(-1) === '/' ? basePath.slice(0, -1) : basePath;
- const treeEntryKey = Object.keys(store.state.entries).find(
- key => key === path && !store.state.entries[key].pending,
- );
- const treeEntry = store.state.entries[treeEntryKey];
-
- if (treeEntry) {
- store.dispatch('handleTreeEntryAction', treeEntry);
- }
- }
- })
- .catch(e => {
- throw e;
- });
- } else if (to.params.mrid) {
- store
- .dispatch('getMergeRequestData', {
- projectId: fullProjectId,
- targetProjectId: to.query.target_project,
- mergeRequestId: to.params.mrid,
- })
- .then(mr => {
- store.dispatch('updateActivityBarView', activityBarViews.review);
-
- store.dispatch('getBranchData', {
- projectId: fullProjectId,
- branchId: mr.source_branch,
- });
-
- return store.dispatch('getFiles', {
- projectId: fullProjectId,
- branchId: mr.source_branch,
- });
- })
- .then(() =>
- store.dispatch('getMergeRequestVersions', {
- projectId: fullProjectId,
- targetProjectId: to.query.target_project,
- mergeRequestId: to.params.mrid,
- }),
- )
- .then(() =>
- store.dispatch('getMergeRequestChanges', {
- projectId: fullProjectId,
- targetProjectId: to.query.target_project,
- mergeRequestId: to.params.mrid,
- }),
- )
- .then(mrChanges => {
- mrChanges.changes.forEach((change, ind) => {
- const changeTreeEntry = store.state.entries[change.new_path];
-
- if (changeTreeEntry) {
- store.dispatch('setFileMrChange', {
- file: changeTreeEntry,
- mrChange: change,
- });
-
- if (ind < 10) {
- store.dispatch('getFileData', {
- path: change.new_path,
- makeFileActive: ind === 0,
- });
- }
- }
- });
- })
- .catch(e => {
- flash('Error while loading the merge request. Please try again.');
- throw e;
- });
}
})
.catch(e => {
diff --git a/app/assets/javascripts/ide/stores/actions/file.js b/app/assets/javascripts/ide/stores/actions/file.js
index 9e3f5da4676..28b9d0df201 100644
--- a/app/assets/javascripts/ide/stores/actions/file.js
+++ b/app/assets/javascripts/ide/stores/actions/file.js
@@ -54,9 +54,6 @@ export const setFileActive = ({ commit, state, getters, dispatch }, path) => {
commit(types.SET_FILE_ACTIVE, { path, active: true });
dispatch('scrollToTab');
-
- commit(types.SET_CURRENT_PROJECT, file.projectId);
- commit(types.SET_CURRENT_BRANCH, file.branchId);
};
export const getFileData = ({ state, commit, dispatch }, { path, makeFileActive = true }) => {
@@ -95,7 +92,7 @@ export const setFileMrChange = ({ commit }, { file, mrChange }) => {
commit(types.SET_FILE_MERGE_REQUEST_CHANGE, { file, mrChange });
};
-export const getRawFileData = ({ state, commit, dispatch }, { path, baseSha }) => {
+export const getRawFileData = ({ state, commit, dispatch, getters }, { path }) => {
const file = state.entries[path];
return new Promise((resolve, reject) => {
service
@@ -103,6 +100,9 @@ export const getRawFileData = ({ state, commit, dispatch }, { path, baseSha }) =
.then(raw => {
if (!(file.tempFile && !file.prevPath)) commit(types.SET_FILE_RAW_DATA, { file, raw });
if (file.mrChange && file.mrChange.new_file === false) {
+ const baseSha =
+ (getters.currentMergeRequest && getters.currentMergeRequest.baseCommitSha) || '';
+
service
.getBaseRawFileData(file, baseSha)
.then(baseRaw => {
@@ -125,7 +125,7 @@ export const getRawFileData = ({ state, commit, dispatch }, { path, baseSha }) =
action: payload =>
dispatch('getRawFileData', payload).then(() => dispatch('setErrorMessage', null)),
actionText: __('Please try again'),
- actionPayload: { path, baseSha },
+ actionPayload: { path },
});
reject();
});
diff --git a/app/assets/javascripts/ide/stores/actions/merge_request.js b/app/assets/javascripts/ide/stores/actions/merge_request.js
index 1887b77b00b..187f8c75d07 100644
--- a/app/assets/javascripts/ide/stores/actions/merge_request.js
+++ b/app/assets/javascripts/ide/stores/actions/merge_request.js
@@ -1,6 +1,8 @@
-import { __ } from '../../../locale';
+import flash from '~/flash';
+import { __ } from '~/locale';
import service from '../../services';
import * as types from '../mutation_types';
+import { activityBarViews } from '../../constants';
export const getMergeRequestData = (
{ commit, dispatch, state },
@@ -104,3 +106,67 @@ export const getMergeRequestVersions = (
resolve(state.projects[projectId].mergeRequests[mergeRequestId].versions);
}
});
+
+export const openMergeRequest = (
+ { dispatch, state },
+ { projectId, targetProjectId, mergeRequestId } = {},
+) =>
+ dispatch('getMergeRequestData', {
+ projectId,
+ targetProjectId,
+ mergeRequestId,
+ })
+ .then(mr => {
+ dispatch('setCurrentBranchId', mr.source_branch);
+
+ dispatch('getBranchData', {
+ projectId,
+ branchId: mr.source_branch,
+ });
+
+ return dispatch('getFiles', {
+ projectId,
+ branchId: mr.source_branch,
+ });
+ })
+ .then(() =>
+ dispatch('getMergeRequestVersions', {
+ projectId,
+ targetProjectId,
+ mergeRequestId,
+ }),
+ )
+ .then(() =>
+ dispatch('getMergeRequestChanges', {
+ projectId,
+ targetProjectId,
+ mergeRequestId,
+ }),
+ )
+ .then(mrChanges => {
+ if (mrChanges.changes.length) {
+ dispatch('updateActivityBarView', activityBarViews.review);
+ }
+
+ mrChanges.changes.forEach((change, ind) => {
+ const changeTreeEntry = state.entries[change.new_path];
+
+ if (changeTreeEntry) {
+ dispatch('setFileMrChange', {
+ file: changeTreeEntry,
+ mrChange: change,
+ });
+
+ if (ind < 10) {
+ dispatch('getFileData', {
+ path: change.new_path,
+ makeFileActive: ind === 0,
+ });
+ }
+ }
+ });
+ })
+ .catch(e => {
+ flash(__('Error while loading the merge request. Please try again.'));
+ throw e;
+ });
diff --git a/app/assets/javascripts/ide/stores/actions/project.js b/app/assets/javascripts/ide/stores/actions/project.js
index 501e25d452b..543dc6c0461 100644
--- a/app/assets/javascripts/ide/stores/actions/project.js
+++ b/app/assets/javascripts/ide/stores/actions/project.js
@@ -124,3 +124,35 @@ export const showBranchNotFoundError = ({ dispatch }, branchId) => {
actionPayload: branchId,
});
};
+
+export const openBranch = (
+ { dispatch, state },
+ { projectId, branchId, basePath },
+) => {
+ dispatch('setCurrentBranchId', branchId);
+
+ dispatch('getBranchData', {
+ projectId,
+ branchId,
+ });
+
+ return (
+ dispatch('getFiles', {
+ projectId,
+ branchId,
+ })
+ .then(() => {
+ if (basePath) {
+ const path = basePath.slice(-1) === '/' ? basePath.slice(0, -1) : basePath;
+ const treeEntryKey = Object.keys(state.entries).find(
+ key => key === path && !state.entries[key].pending,
+ );
+ const treeEntry = state.entries[treeEntryKey];
+
+ if (treeEntry) {
+ dispatch('handleTreeEntryAction', treeEntry);
+ }
+ }
+ })
+ );
+};
diff --git a/app/assets/javascripts/ide/stores/modules/branches/actions.js b/app/assets/javascripts/ide/stores/modules/branches/actions.js
index 74aa98ef9f9..f90c2d77f2b 100644
--- a/app/assets/javascripts/ide/stores/modules/branches/actions.js
+++ b/app/assets/javascripts/ide/stores/modules/branches/actions.js
@@ -33,7 +33,4 @@ export const fetchBranches = ({ dispatch, rootGetters }, { search = '' }) => {
export const resetBranches = ({ commit }) => commit(types.RESET_BRANCHES);
-export const openBranch = ({ rootState, dispatch }, id) =>
- dispatch('goToRoute', `/project/${rootState.currentProjectId}/edit/${id}`, { root: true });
-
export default () => {};
diff --git a/app/assets/javascripts/ide/stores/modules/file_templates/actions.js b/app/assets/javascripts/ide/stores/modules/file_templates/actions.js
new file mode 100644
index 00000000000..43237a29466
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/file_templates/actions.js
@@ -0,0 +1,82 @@
+import Api from '~/api';
+import { __ } from '~/locale';
+import * as types from './mutation_types';
+
+export const requestTemplateTypes = ({ commit }) => commit(types.REQUEST_TEMPLATE_TYPES);
+export const receiveTemplateTypesError = ({ commit, dispatch }) => {
+ commit(types.RECEIVE_TEMPLATE_TYPES_ERROR);
+ dispatch(
+ 'setErrorMessage',
+ {
+ text: __('Error loading template types.'),
+ action: () =>
+ dispatch('fetchTemplateTypes').then(() =>
+ dispatch('setErrorMessage', null, { root: true }),
+ ),
+ actionText: __('Please try again'),
+ },
+ { root: true },
+ );
+};
+export const receiveTemplateTypesSuccess = ({ commit }, templates) =>
+ commit(types.RECEIVE_TEMPLATE_TYPES_SUCCESS, templates);
+
+export const fetchTemplateTypes = ({ dispatch, state }) => {
+ if (!Object.keys(state.selectedTemplateType).length) return Promise.reject();
+
+ dispatch('requestTemplateTypes');
+
+ return Api.templates(state.selectedTemplateType.key)
+ .then(({ data }) => dispatch('receiveTemplateTypesSuccess', data))
+ .catch(() => dispatch('receiveTemplateTypesError'));
+};
+
+export const setSelectedTemplateType = ({ commit }, type) =>
+ commit(types.SET_SELECTED_TEMPLATE_TYPE, type);
+
+export const receiveTemplateError = ({ dispatch }, template) => {
+ dispatch(
+ 'setErrorMessage',
+ {
+ text: __('Error loading template.'),
+ action: payload =>
+ dispatch('fetchTemplateTypes', payload).then(() =>
+ dispatch('setErrorMessage', null, { root: true }),
+ ),
+ actionText: __('Please try again'),
+ actionPayload: template,
+ },
+ { root: true },
+ );
+};
+
+export const fetchTemplate = ({ dispatch, state }, template) => {
+ if (template.content) {
+ return dispatch('setFileTemplate', template);
+ }
+
+ return Api.templates(`${state.selectedTemplateType.key}/${template.key || template.name}`)
+ .then(({ data }) => {
+ dispatch('setFileTemplate', data);
+ })
+ .catch(() => dispatch('receiveTemplateError', template));
+};
+
+export const setFileTemplate = ({ dispatch, commit, rootGetters }, template) => {
+ dispatch(
+ 'changeFileContent',
+ { path: rootGetters.activeFile.path, content: template.content },
+ { root: true },
+ );
+ commit(types.SET_UPDATE_SUCCESS, true);
+};
+
+export const undoFileTemplate = ({ dispatch, commit, rootGetters }) => {
+ const file = rootGetters.activeFile;
+
+ dispatch('changeFileContent', { path: file.path, content: file.raw }, { root: true });
+ commit(types.SET_UPDATE_SUCCESS, false);
+};
+
+// prevent babel-plugin-rewire from generating an invalid default during karma tests
+export default () => {};
diff --git a/app/assets/javascripts/ide/stores/modules/file_templates/getters.js b/app/assets/javascripts/ide/stores/modules/file_templates/getters.js
new file mode 100644
index 00000000000..38318fd49bf
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/file_templates/getters.js
@@ -0,0 +1,23 @@
+export const templateTypes = () => [
+ {
+ name: '.gitlab-ci.yml',
+ key: 'gitlab_ci_ymls',
+ },
+ {
+ name: '.gitignore',
+ key: 'gitignores',
+ },
+ {
+ name: 'LICENSE',
+ key: 'licenses',
+ },
+ {
+ name: 'Dockerfile',
+ key: 'dockerfiles',
+ },
+];
+
+export const showFileTemplatesBar = (_, getters) => name =>
+ getters.templateTypes.find(t => t.name === name);
+
+export default () => {};
diff --git a/app/assets/javascripts/ide/stores/modules/file_templates/index.js b/app/assets/javascripts/ide/stores/modules/file_templates/index.js
new file mode 100644
index 00000000000..dfa5ef54413
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/file_templates/index.js
@@ -0,0 +1,12 @@
+import createState from './state';
+import * as actions from './actions';
+import * as getters from './getters';
+import mutations from './mutations';
+
+export default {
+ namespaced: true,
+ actions,
+ state: createState(),
+ getters,
+ mutations,
+};
diff --git a/app/assets/javascripts/ide/stores/modules/file_templates/mutation_types.js b/app/assets/javascripts/ide/stores/modules/file_templates/mutation_types.js
new file mode 100644
index 00000000000..cf4499c0264
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/file_templates/mutation_types.js
@@ -0,0 +1,7 @@
+export const REQUEST_TEMPLATE_TYPES = 'REQUEST_TEMPLATE_TYPES';
+export const RECEIVE_TEMPLATE_TYPES_ERROR = 'RECEIVE_TEMPLATE_TYPES_ERROR';
+export const RECEIVE_TEMPLATE_TYPES_SUCCESS = 'RECEIVE_TEMPLATE_TYPES_SUCCESS';
+
+export const SET_SELECTED_TEMPLATE_TYPE = 'SET_SELECTED_TEMPLATE_TYPE';
+
+export const SET_UPDATE_SUCCESS = 'SET_UPDATE_SUCCESS';
diff --git a/app/assets/javascripts/ide/stores/modules/file_templates/mutations.js b/app/assets/javascripts/ide/stores/modules/file_templates/mutations.js
new file mode 100644
index 00000000000..e413e61eaaa
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/file_templates/mutations.js
@@ -0,0 +1,21 @@
+/* eslint-disable no-param-reassign */
+import * as types from './mutation_types';
+
+export default {
+ [types.REQUEST_TEMPLATE_TYPES](state) {
+ state.isLoading = true;
+ },
+ [types.RECEIVE_TEMPLATE_TYPES_ERROR](state) {
+ state.isLoading = false;
+ },
+ [types.RECEIVE_TEMPLATE_TYPES_SUCCESS](state, templates) {
+ state.isLoading = false;
+ state.templates = templates;
+ },
+ [types.SET_SELECTED_TEMPLATE_TYPE](state, type) {
+ state.selectedTemplateType = type;
+ },
+ [types.SET_UPDATE_SUCCESS](state, success) {
+ state.updateSuccess = success;
+ },
+};
diff --git a/app/assets/javascripts/ide/stores/modules/file_templates/state.js b/app/assets/javascripts/ide/stores/modules/file_templates/state.js
new file mode 100644
index 00000000000..bd4b7d7bc52
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/file_templates/state.js
@@ -0,0 +1,6 @@
+export default () => ({
+ isLoading: false,
+ templates: [],
+ selectedTemplateType: {},
+ updateSuccess: false,
+});
diff --git a/app/assets/javascripts/ide/stores/mutations.js b/app/assets/javascripts/ide/stores/mutations.js
index 1eda5768709..56a8d9430c7 100644
--- a/app/assets/javascripts/ide/stores/mutations.js
+++ b/app/assets/javascripts/ide/stores/mutations.js
@@ -200,6 +200,7 @@ export default {
},
[types.DELETE_ENTRY](state, path) {
const entry = state.entries[path];
+ const { tempFile = false } = entry;
const parent = entry.parentPath
? state.entries[entry.parentPath]
: state.trees[`${state.currentProjectId}/${state.currentBranchId}`];
@@ -209,7 +210,11 @@ export default {
parent.tree = parent.tree.filter(f => f.path !== entry.path);
if (entry.type === 'blob') {
- state.changedFiles = state.changedFiles.concat(entry);
+ if (tempFile) {
+ state.changedFiles = state.changedFiles.filter(f => f.path !== path);
+ } else {
+ state.changedFiles = state.changedFiles.concat(entry);
+ }
}
},
[types.RENAME_ENTRY](state, { path, name, entryPath = null }) {
diff --git a/app/assets/javascripts/importer_status.js b/app/assets/javascripts/importer_status.js
index 0035d809062..eda8cdad908 100644
--- a/app/assets/javascripts/importer_status.js
+++ b/app/assets/javascripts/importer_status.js
@@ -87,7 +87,7 @@ class ImporterStatus {
details = error.response.data.errors;
}
- flash(__(`An error occurred while importing project: ${details}`));
+ flash(sprintf(__('An error occurred while importing project: %{details}'), { details }));
});
}
diff --git a/app/assets/javascripts/jobs/components/artifacts_block.vue b/app/assets/javascripts/jobs/components/artifacts_block.vue
new file mode 100644
index 00000000000..525c5eec91a
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/artifacts_block.vue
@@ -0,0 +1,98 @@
+<script>
+ import TimeagoTooltiop from '~/vue_shared/components/time_ago_tooltip.vue';
+
+ export default {
+ components: {
+ TimeagoTooltiop,
+ },
+ props: {
+ // @build.artifacts_expired?
+ haveArtifactsExpired: {
+ type: Boolean,
+ required: true,
+ },
+ // @build.has_expiring_artifacts?
+ willArtifactsExpire: {
+ type: Boolean,
+ required: true,
+ },
+ expireAt: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ keepArtifactsPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ downloadArtifactsPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ browseArtifactsPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ },
+ };
+</script>
+<template>
+ <div class="block">
+ <div class="title">
+ {{ s__('Job|Job artifacts') }}
+ </div>
+
+ <p
+ v-if="haveArtifactsExpired"
+ class="js-artifacts-removed build-detail-row"
+ >
+ {{ s__('Job|The artifacts were removed') }}
+ </p>
+ <p
+ v-else-if="willArtifactsExpire"
+ class="js-artifacts-will-be-removed build-detail-row"
+ >
+ {{ s__('Job|The artifacts will be removed') }}
+ </p>
+
+ <timeago-tooltiop
+ v-if="expireAt"
+ :time="expireAt"
+ />
+
+ <div
+ class="btn-group d-flex"
+ role="group"
+ >
+ <a
+ v-if="keepArtifactsPath"
+ :href="keepArtifactsPath"
+ class="js-keep-artifacts btn btn-sm btn-default"
+ data-method="post"
+ >
+ {{ s__('Job|Keep') }}
+ </a>
+
+ <a
+ v-if="downloadArtifactsPath"
+ :href="downloadArtifactsPath"
+ class="js-download-artifacts btn btn-sm btn-default"
+ download
+ rel="nofollow"
+ >
+ {{ s__('Job|Download') }}
+ </a>
+
+ <a
+ v-if="browseArtifactsPath"
+ :href="browseArtifactsPath"
+ class="js-browse-artifacts btn btn-sm btn-default"
+ >
+ {{ s__('Job|Browse') }}
+ </a>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/jobs/components/commit_block.vue b/app/assets/javascripts/jobs/components/commit_block.vue
new file mode 100644
index 00000000000..7f485295513
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/commit_block.vue
@@ -0,0 +1,64 @@
+<script>
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+
+export default {
+ components: {
+ ClipboardButton,
+ },
+ props: {
+ pipelineShortSha: {
+ type: String,
+ required: true,
+ },
+ pipelineShaPath: {
+ type: String,
+ required: true,
+ },
+ mergeRequestReference: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ mergeRequestPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ gitCommitTitlte: {
+ type: String,
+ required: true,
+ },
+ },
+};
+</script>
+<template>
+ <div class="block">
+ <p>
+ {{ __('Commit') }}
+
+ <a
+ :href="pipelineShaPath"
+ class="js-commit-sha commit-sha link-commit"
+ >
+ {{ pipelineShortSha }}
+ </a>
+
+ <clipboard-button
+ :text="pipelineShortSha"
+ :title="__('Copy commit SHA to clipboard')"
+ />
+
+ <a
+ v-if="mergeRequestPath && mergeRequestReference"
+ :href="mergeRequestPath"
+ class="js-link-commit link-commit"
+ >
+ {{ mergeRequestReference }}
+ </a>
+ </p>
+
+ <p class="build-light-text append-bottom-0">
+ {{ gitCommitTitlte }}
+ </p>
+ </div>
+</template>
diff --git a/app/assets/javascripts/jobs/components/empty_state.vue b/app/assets/javascripts/jobs/components/empty_state.vue
new file mode 100644
index 00000000000..4faf08387fb
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/empty_state.vue
@@ -0,0 +1,76 @@
+<script>
+ export default {
+ props: {
+ illustrationPath: {
+ type: String,
+ required: true,
+ },
+ illustrationSizeClass: {
+ type: String,
+ required: true,
+ },
+ title: {
+ type: String,
+ required: true,
+ },
+ content: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ action: {
+ type: Object,
+ required: false,
+ default: null,
+ validator(value) {
+ return (
+ value === null ||
+ (Object.prototype.hasOwnProperty.call(value, 'link') &&
+ Object.prototype.hasOwnProperty.call(value, 'method') &&
+ Object.prototype.hasOwnProperty.call(value, 'title'))
+ );
+ },
+ },
+ },
+ };
+</script>
+<template>
+ <div class="row empty-state">
+ <div class="col-12">
+ <div
+ :class="illustrationSizeClass"
+ class="svg-content"
+ >
+ <img :src="illustrationPath" />
+ </div>
+ </div>
+
+ <div class="col-12">
+ <div class="text-content">
+ <h4 class="js-job-empty-state-title text-center">
+ {{ title }}
+ </h4>
+
+ <p
+ v-if="content"
+ class="js-job-empty-state-content"
+ >
+ {{ content }}
+ </p>
+
+ <div
+ v-if="action"
+ class="text-center"
+ >
+ <a
+ :href="action.link"
+ :data-method="action.method"
+ class="js-job-empty-state-action btn btn-primary"
+ >
+ {{ action.title }}
+ </a>
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/jobs/components/erased_block.vue b/app/assets/javascripts/jobs/components/erased_block.vue
new file mode 100644
index 00000000000..d688eebfa95
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/erased_block.vue
@@ -0,0 +1,48 @@
+<script>
+import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+
+export default {
+ components: {
+ TimeagoTooltip,
+ },
+ props: {
+ erasedByUser: {
+ type: Boolean,
+ required: true,
+ },
+ username: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ linkToUser: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ erasedAt: {
+ type: String,
+ required: true,
+ },
+ },
+};
+</script>
+<template>
+ <div class="prepend-top-default js-build-erased">
+ <div class="erased alert alert-warning">
+ <template v-if="erasedByUser">
+ {{ s__("Job|Job has been erased by") }}
+ <a :href="linkToUser">
+ {{ username }}
+ </a>
+ </template>
+ <template v-else>
+ {{ s__("Job|Job has been erased") }}
+ </template>
+
+ <timeago-tooltip
+ :time="erasedAt"
+ />
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/jobs/components/job_log.vue b/app/assets/javascripts/jobs/components/job_log.vue
new file mode 100644
index 00000000000..3c4749d996b
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/job_log.vue
@@ -0,0 +1,33 @@
+<script>
+ export default {
+ name: 'JobLog',
+ props: {
+ trace: {
+ type: String,
+ required: true,
+ },
+ isReceivingBuildTrace: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ };
+</script>
+<template>
+ <pre class="build-trace">
+ <code
+ class="bash"
+ v-html="trace"
+ >
+ </code>
+
+ <div
+ v-if="isReceivingBuildTrace"
+ class="js-log-animation build-loader-animation"
+ >
+ <div class="dot"></div>
+ <div class="dot"></div>
+ <div class="dot"></div>
+ </div>
+ </pre>
+</template>
diff --git a/app/assets/javascripts/jobs/components/job_log_controllers.vue b/app/assets/javascripts/jobs/components/job_log_controllers.vue
new file mode 100644
index 00000000000..513851e376f
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/job_log_controllers.vue
@@ -0,0 +1,139 @@
+<script>
+ import Icon from '~/vue_shared/components/icon.vue';
+ import tooltip from '~/vue_shared/directives/tooltip';
+ import { numberToHumanSize } from '~/lib/utils/number_utils';
+ import { s__, sprintf } from '~/locale';
+
+ export default {
+ components: {
+ Icon,
+ },
+ directives: {
+ tooltip,
+ },
+ props: {
+ canEraseJob: {
+ type: Boolean,
+ required: true,
+ },
+ size: {
+ type: Number,
+ required: true,
+ },
+ rawTracePath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ canScrollToTop: {
+ type: Boolean,
+ required: true,
+ },
+ canScrollToBottom: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ computed: {
+ jobLogSize() {
+ return sprintf('Showing last %{startSpanTag} %{size} %{endSpanTag} of log -', {
+ startSpanTag: '<span class="s-truncated-info-size truncated-info-size">',
+ endSpanTag: '</span>',
+ size: numberToHumanSize(this.size),
+ });
+ },
+ },
+ methods: {
+ handleEraseJobClick() {
+ // eslint-disable-next-line no-alert
+ if (window.confirm(s__('Job|Are you sure you want to erase this job?'))) {
+ this.$emit('eraseJob');
+ }
+ },
+ handleScrollToTop() {
+ this.$emit('scrollJobLogTop');
+ },
+ handleScrollToBottom() {
+ this.$emit('scrollJobLogBottom');
+ },
+ },
+ };
+</script>
+<template>
+ <div class="top-bar">
+ <!-- truncate information -->
+ <div class="js-truncated-info truncated-info d-none d-sm-block float-left">
+ <p v-html="jobLogSize"></p>
+
+ <a
+ v-if="rawTracePath"
+ :href="rawTracePath"
+ class="js-raw-link raw-link"
+ >
+ {{ s__("Job|Complete Raw") }}
+ </a>
+ </div>
+ <!-- eo truncate information -->
+
+ <div class="controllers float-right">
+ <!-- links -->
+ <a
+ v-tooltip
+ v-if="rawTracePath"
+ :title="s__('Job|Show complete raw')"
+ :href="rawTracePath"
+ class="js-raw-link-controller controllers-buttons"
+ data-container="body"
+ >
+ <icon name="doc-text" />
+ </a>
+
+ <button
+ v-tooltip
+ v-if="canEraseJob"
+ :title="s__('Job|Erase job log')"
+ type="button"
+ class="js-erase-link controllers-buttons"
+ data-container="body"
+ @click="handleEraseJobClick"
+ >
+ <icon name="remove" />
+ </button>
+ <!-- eo links -->
+
+ <!-- scroll buttons -->
+ <div
+ v-tooltip
+ :title="s__('Job|Scroll to top')"
+ class="controllers-buttons"
+ data-container="body"
+ >
+ <button
+ :disabled="!canScrollToTop"
+ type="button"
+ class="js-scroll-top btn-scroll btn-transparent btn-blank"
+ @click="handleScrollToTop"
+ >
+ <icon name="scroll_up"/>
+ </button>
+ </div>
+
+ <div
+ v-tooltip
+ :title="s__('Job|Scroll to bottom')"
+ class="controllers-buttons"
+ data-container="body"
+ >
+ <button
+ :disabled="!canScrollToBottom"
+ type="button"
+ class="js-scroll-bottom btn-scroll btn-transparent btn-blank"
+ @click="handleScrollToBottom"
+ >
+ <icon name="scroll_down"/>
+ </button>
+ </div>
+ <!-- eo scroll buttons -->
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/jobs/components/jobs_container.vue b/app/assets/javascripts/jobs/components/jobs_container.vue
new file mode 100644
index 00000000000..b81109bdd06
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/jobs_container.vue
@@ -0,0 +1,60 @@
+<script>
+ import CiIcon from '~/vue_shared/components/ci_icon.vue';
+ import Icon from '~/vue_shared/components/icon.vue';
+ import tooltip from '~/vue_shared/directives/tooltip';
+
+ export default {
+ components: {
+ CiIcon,
+ Icon,
+ },
+ directives: {
+ tooltip,
+ },
+ props: {
+ jobs: {
+ type: Array,
+ required: true,
+ },
+ },
+ };
+</script>
+<template>
+ <div class="builds-container">
+ <div
+ class="build-job"
+ >
+ <a
+ v-tooltip
+ v-for="job in jobs"
+ :key="job.id"
+ :href="job.path"
+ :title="job.tooltip"
+ :class="{ active: job.active, retried: job.retried }"
+ >
+ <icon
+ v-if="job.active"
+ name="arrow-right"
+ class="js-arrow-right"
+ />
+
+ <ci-icon :status="job.status" />
+
+ <span>
+ <template v-if="job.name">
+ {{ job.name }}
+ </template>
+ <template v-else>
+ {{ job.id }}
+ </template>
+ </span>
+
+ <icon
+ v-if="job.retried"
+ name="retry"
+ class="js-retry-icon"
+ />
+ </a>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/jobs/components/sidebar_details_block.vue b/app/assets/javascripts/jobs/components/sidebar_details_block.vue
index d2adf628050..36d4a3e2bc9 100644
--- a/app/assets/javascripts/jobs/components/sidebar_details_block.vue
+++ b/app/assets/javascripts/jobs/components/sidebar_details_block.vue
@@ -1,14 +1,16 @@
<script>
-import detailRow from './sidebar_detail_row.vue';
-import loadingIcon from '../../vue_shared/components/loading_icon.vue';
-import timeagoMixin from '../../vue_shared/mixins/timeago';
-import { timeIntervalInWords } from '../../lib/utils/datetime_utility';
+import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
+import timeagoMixin from '~/vue_shared/mixins/timeago';
+import { timeIntervalInWords } from '~/lib/utils/datetime_utility';
+import Icon from '~/vue_shared/components/icon.vue';
+import DetailRow from './sidebar_detail_row.vue';
export default {
name: 'SidebarDetailsBlock',
components: {
- detailRow,
- loadingIcon,
+ DetailRow,
+ LoadingIcon,
+ Icon,
},
mixins: [timeagoMixin],
props: {
@@ -20,16 +22,16 @@ export default {
type: Boolean,
required: true,
},
- canUserRetry: {
- type: Boolean,
- required: false,
- default: false,
- },
runnerHelpUrl: {
type: String,
required: false,
default: '',
},
+ terminalPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
},
computed: {
shouldRenderContent() {
@@ -92,7 +94,7 @@ export default {
{{ job.name }}
</strong>
<a
- v-if="canUserRetry"
+ v-if="job.retry_path"
:class="retryButtonClass"
:href="job.retry_path"
data-method="post"
@@ -100,6 +102,16 @@ export default {
>
{{ __('Retry') }}
</a>
+ <a
+ v-if="terminalPath"
+ :href="terminalPath"
+ class="js-terminal-link pull-right btn btn-primary
+ btn-inverted visible-md-block visible-lg-block"
+ target="_blank"
+ >
+ {{ __('Debug') }}
+ <icon name="external-link" />
+ </a>
<button
:aria-label="__('Toggle Sidebar')"
type="button"
@@ -125,7 +137,7 @@ export default {
{{ __('New issue') }}
</a>
<a
- v-if="canUserRetry"
+ v-if="job.retry_path"
:href="job.retry_path"
class="js-retry-job btn btn-inverted-secondary"
data-method="post"
diff --git a/app/assets/javascripts/jobs/components/stages_dropdown.vue b/app/assets/javascripts/jobs/components/stages_dropdown.vue
new file mode 100644
index 00000000000..d6d64fa32f7
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/stages_dropdown.vue
@@ -0,0 +1,97 @@
+<script>
+ import CiIcon from '~/vue_shared/components/ci_icon.vue';
+ import Icon from '~/vue_shared/components/icon.vue';
+
+ import { sprintf, __ } from '~/locale';
+
+ export default {
+ components: {
+ CiIcon,
+ Icon,
+ },
+ props: {
+ pipelineId: {
+ type: Number,
+ required: true,
+ },
+ pipelinePath: {
+ type: String,
+ required: true,
+ },
+ pipelineRef: {
+ type: String,
+ required: true,
+ },
+ pipelineRefPath: {
+ type: String,
+ required: true,
+ },
+ stages: {
+ type: Array,
+ required: true,
+ },
+ pipelineStatus: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ selectedStage: this.stages.length > 0 ? this.stages[0].name : __('More'),
+ };
+ },
+ computed: {
+ pipelineLink() {
+ return sprintf(__('Pipeline %{pipelineLinkStart} #%{pipelineId} %{pipelineLinkEnd} from %{pipelineLinkRefStart} %{pipelineRef} %{pipelineLinkRefEnd}'), {
+ pipelineLinkStart: `<a href=${this.pipelinePath} class="js-pipeline-path link-commit">`,
+ pipelineId: this.pipelineId,
+ pipelineLinkEnd: '</a>',
+ pipelineLinkRefStart: `<a href=${this.pipelineRefPath} class="link-commit ref-name">`,
+ pipelineRef: this.pipelineRef,
+ pipelineLinkRefEnd: '</a>',
+ }, false);
+ },
+ },
+ methods: {
+ onStageClick(stage) {
+ // todo: consider moving into store
+ this.selectedStage = stage.name;
+
+ // update dropdown with jobs
+ // jobs container is a new component.
+ this.$emit('requestSidebarStageDropdown', stage);
+ },
+ },
+ };
+</script>
+<template>
+ <div class="block-last">
+ <ci-icon :status="pipelineStatus" />
+
+ <p v-html="pipelineLink"></p>
+
+ <div class="dropdown">
+ <button
+ type="button"
+ data-toggle="dropdown"
+ >
+ {{ selectedStage }}
+ <icon name="chevron-down" />
+ </button>
+ <ul class="dropdown-menu">
+ <li
+ v-for="(stage, index) in stages"
+ :key="index"
+ >
+ <button
+ type="button"
+ class="stage-item"
+ @click="onStageClick(stage)"
+ >
+ {{ stage.name }}
+ </button>
+ </li>
+ </ul>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/jobs/components/stuck_block.vue b/app/assets/javascripts/jobs/components/stuck_block.vue
new file mode 100644
index 00000000000..18883fea950
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/stuck_block.vue
@@ -0,0 +1,63 @@
+<script>
+/**
+ * Renders Stuck Runners block for job's view.
+ */
+export default {
+ props: {
+ hasNoRunnersForProject: {
+ type: Boolean,
+ required: true,
+ },
+ tags: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ runnersPath: {
+ type: String,
+ required: true,
+ },
+ },
+};
+</script>
+<template>
+ <div class="bs-callout bs-callout-warning">
+ <p
+ v-if="hasNoRunnersForProject"
+ class="js-stuck-no-runners"
+ >
+ {{ s__(`Job|This job is stuck, because the project
+ doesn't have any runners online assigned to it.`) }}
+ </p>
+ <p
+ v-else-if="tags.length"
+ class="js-stuck-with-tags"
+ >
+ {{ s__(`This job is stuck, because you don't have
+ any active runners online with any of these tags assigned to them:`) }}
+ <span
+ v-for="(tag, index) in tags"
+ :key="index"
+ class="badge badge-primary"
+ >
+ {{ tag }}
+ </span>
+ </p>
+ <p
+ v-else
+ class="js-stuck-no-active-runner"
+ >
+ {{ s__(`This job is stuck, because you don't
+ have any active runners that can run this job.`) }}
+ </p>
+
+ {{ __("Go to") }}
+ <a
+ v-if="runnersPath"
+ :href="runnersPath"
+ class="js-runners-path"
+ >
+ {{ __("Runners page") }}
+ </a>
+ </div>
+</template>
diff --git a/app/assets/javascripts/jobs/components/trigger_block.vue b/app/assets/javascripts/jobs/components/trigger_block.vue
new file mode 100644
index 00000000000..8a88e5da6aa
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/trigger_block.vue
@@ -0,0 +1,84 @@
+<script>
+ export default {
+ props: {
+ shortToken: {
+ type: String,
+ required: false,
+ default: null,
+ },
+
+ variables: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ },
+ data() {
+ return {
+ areVariablesVisible: false,
+ };
+ },
+ computed: {
+ hasVariables() {
+ return Object.keys(this.variables).length > 0;
+ },
+ },
+ methods: {
+ revealVariables() {
+ this.areVariablesVisible = true;
+ },
+ },
+ };
+</script>
+
+<template>
+ <div class="build-widget block">
+ <h4 class="title">
+ {{ __('Trigger') }}
+ </h4>
+
+ <p
+ v-if="shortToken"
+ class="js-short-token"
+ >
+ <span class="build-light-text">
+ {{ __('Token') }}
+ </span>
+ {{ shortToken }}
+ </p>
+
+ <p v-if="hasVariables">
+ <button
+ type="button"
+ class="btn btn-default group js-reveal-variables"
+ @click="revealVariables"
+ >
+ {{ __('Reveal Variables') }}
+ </button>
+
+ </p>
+
+ <dl
+ v-if="areVariablesVisible"
+ class="js-build-variables trigger-build-variables"
+ >
+ <template
+ v-for="(value, key) in variables"
+ >
+ <dt
+ :key="`${key}-variable`"
+ class="js-build-variable trigger-build-variable"
+ >
+ {{ key }}
+ </dt>
+
+ <dd
+ :key="`${key}-value`"
+ class="js-build-value trigger-build-value"
+ >
+ {{ value }}
+ </dd>
+ </template>
+ </dl>
+ </div>
+</template>
diff --git a/app/assets/javascripts/jobs/job_details_bundle.js b/app/assets/javascripts/jobs/job_details_bundle.js
index 0db7b95636c..a84324f14b2 100644
--- a/app/assets/javascripts/jobs/job_details_bundle.js
+++ b/app/assets/javascripts/jobs/job_details_bundle.js
@@ -52,9 +52,9 @@ export default () => {
return createElement('details-block', {
props: {
isLoading: this.mediator.state.isLoading,
- canUserRetry: !!('canUserRetry' in detailsBlockDataset),
job: this.mediator.store.state.job,
runnerHelpUrl: dataset.runnerHelpUrl,
+ terminalPath: detailsBlockDataset.terminalPath,
},
});
},
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index cb851ff6745..6499b919787 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -4,7 +4,7 @@
import $ from 'jquery';
import _ from 'underscore';
-import { __ } from './locale';
+import { sprintf, __ } from './locale';
import axios from './lib/utils/axios_utils';
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
import DropdownUtils from './filtered_search/dropdown_utils';
@@ -39,7 +39,7 @@ export default class LabelsSelect {
showNo = $dropdown.data('showNo');
showAny = $dropdown.data('showAny');
showMenuAbove = $dropdown.data('showMenuAbove');
- defaultLabel = $dropdown.data('defaultLabel') || 'Label';
+ defaultLabel = $dropdown.data('defaultLabel') || __('Label');
abilityName = $dropdown.data('abilityName');
$selectbox = $dropdown.closest('.selectbox');
$block = $selectbox.closest('.block');
@@ -267,7 +267,10 @@ export default class LabelsSelect {
return selectedLabels;
}
else if (selectedLabels.length) {
- return selectedLabels[0] + " +" + (selectedLabels.length - 1) + " more";
+ return sprintf(__('%{firstLabel} +%{labelCount} more'), {
+ firstLabel: selectedLabels[0],
+ labelCount: selectedLabels.length - 1
+ });
}
else {
return defaultLabel;
diff --git a/app/assets/javascripts/locale/ensure_single_line.js b/app/assets/javascripts/locale/ensure_single_line.js
new file mode 100644
index 00000000000..47c52fe6c50
--- /dev/null
+++ b/app/assets/javascripts/locale/ensure_single_line.js
@@ -0,0 +1,25 @@
+/* eslint-disable import/no-commonjs */
+
+const SPLIT_REGEX = /\s*[\r\n]+\s*/;
+
+/**
+ *
+ * strips newlines from strings and replaces them with a single space
+ *
+ * @example
+ *
+ * ensureSingleLine('foo \n bar') === 'foo bar'
+ *
+ * @param {String} str
+ * @returns {String}
+ */
+module.exports = function ensureSingleLine(str) {
+ // This guard makes the function significantly faster
+ if (str.includes('\n') || str.includes('\r')) {
+ return str
+ .split(SPLIT_REGEX)
+ .filter(s => s !== '')
+ .join(' ');
+ }
+ return str;
+};
diff --git a/app/assets/javascripts/locale/index.js b/app/assets/javascripts/locale/index.js
index 2cc5fb10027..1ae3362c4bc 100644
--- a/app/assets/javascripts/locale/index.js
+++ b/app/assets/javascripts/locale/index.js
@@ -1,4 +1,5 @@
import Jed from 'jed';
+import ensureSingleLine from './ensure_single_line';
import sprintf from './sprintf';
const languageCode = () => document.querySelector('html').getAttribute('lang') || 'en';
@@ -10,7 +11,7 @@ delete window.translations;
@param text The text to be translated
@returns {String} The translated text
*/
-const gettext = locale.gettext.bind(locale);
+const gettext = text => locale.gettext.bind(locale)(ensureSingleLine(text));
/**
Translate the text with a number
@@ -23,7 +24,10 @@ const gettext = locale.gettext.bind(locale);
@returns {String} Translated text with the number replaced (eg. '2 days')
*/
const ngettext = (text, pluralText, count) => {
- const translated = locale.ngettext(text, pluralText, count).replace(/%d/g, count).split('|');
+ const translated = locale
+ .ngettext(ensureSingleLine(text), ensureSingleLine(pluralText), count)
+ .replace(/%d/g, count)
+ .split('|');
return translated[translated.length - 1];
};
@@ -40,7 +44,7 @@ const ngettext = (text, pluralText, count) => {
@returns {String} Translated context based text
*/
const pgettext = (keyOrContext, key) => {
- const normalizedKey = key ? `${keyOrContext}|${key}` : keyOrContext;
+ const normalizedKey = ensureSingleLine(key ? `${keyOrContext}|${key}` : keyOrContext);
const translated = gettext(normalizedKey).split('|');
return translated[translated.length - 1];
@@ -52,8 +56,7 @@ const pgettext = (keyOrContext, key) => {
@param formatOptions for available options, please see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat
@returns {Intl.DateTimeFormat}
*/
-const createDateTimeFormat =
- formatOptions => Intl.DateTimeFormat(languageCode(), formatOptions);
+const createDateTimeFormat = formatOptions => Intl.DateTimeFormat(languageCode(), formatOptions);
export { languageCode };
export { gettext as __ };
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index 6afaefc56f8..ae96ac3b80c 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -172,7 +172,7 @@ export default {
<template>
<div
v-if="!showEmptyState"
- class="prometheus-graphs prepend-top-10"
+ class="prometheus-graphs prepend-top-default"
>
<div class="environments d-flex align-items-center">
{{ s__('Metrics|Environment') }}
diff --git a/app/assets/javascripts/notes/components/note_awards_list.vue b/app/assets/javascripts/notes/components/note_awards_list.vue
index 225d9f18612..e111d3b9ac2 100644
--- a/app/assets/javascripts/notes/components/note_awards_list.vue
+++ b/app/assets/javascripts/notes/components/note_awards_list.vue
@@ -82,29 +82,17 @@ export default {
getAwardHTML(name) {
return glEmojiTag(name);
},
- getAwardClassBindings(awardList, awardName) {
+ getAwardClassBindings(awardList) {
return {
active: this.hasReactionByCurrentUser(awardList),
- disabled: !this.canInteractWithEmoji(awardList, awardName),
+ disabled: !this.canInteractWithEmoji(),
};
},
- canInteractWithEmoji(awardList, awardName) {
- let isAllowed = true;
- const restrictedEmojis = ['thumbsup', 'thumbsdown'];
-
- // Users can not add :+1: and :-1: to their own notes
- if (
- this.getUserData.id === this.noteAuthorId &&
- restrictedEmojis.indexOf(awardName) > -1
- ) {
- isAllowed = false;
- }
-
- return this.getUserData.id && isAllowed;
+ canInteractWithEmoji() {
+ return this.getUserData.id;
},
hasReactionByCurrentUser(awardList) {
- return awardList.filter(award => award.user.id === this.getUserData.id)
- .length;
+ return awardList.filter(award => award.user.id === this.getUserData.id).length;
},
awardTitle(awardsList) {
const hasReactionByCurrentUser = this.hasReactionByCurrentUser(
@@ -197,7 +185,7 @@ export default {
v-tooltip
v-for="(awardList, awardName, index) in groupedAwards"
:key="index"
- :class="getAwardClassBindings(awardList, awardName)"
+ :class="getAwardClassBindings(awardList)"
:title="awardTitle(awardList)"
class="btn award-control"
data-boundary="viewport"
diff --git a/app/assets/javascripts/pages/admin/application_settings/index.js b/app/assets/javascripts/pages/admin/application_settings/index.js
index 48d75f5443b..47bd70537f1 100644
--- a/app/assets/javascripts/pages/admin/application_settings/index.js
+++ b/app/assets/javascripts/pages/admin/application_settings/index.js
@@ -1,6 +1,8 @@
import initSettingsPanels from '~/settings_panels';
+import projectSelect from '~/project_select';
document.addEventListener('DOMContentLoaded', () => {
// Initialize expandable settings panels
initSettingsPanels();
+ projectSelect();
});
diff --git a/app/assets/javascripts/pages/admin/cohorts/index.js b/app/assets/javascripts/pages/instance_statistics/cohorts/index.js
index 2d5020dbef4..2d5020dbef4 100644
--- a/app/assets/javascripts/pages/admin/cohorts/index.js
+++ b/app/assets/javascripts/pages/instance_statistics/cohorts/index.js
diff --git a/app/assets/javascripts/pages/admin/cohorts/usage_ping.js b/app/assets/javascripts/pages/instance_statistics/cohorts/usage_ping.js
index 914a9661c27..914a9661c27 100644
--- a/app/assets/javascripts/pages/admin/cohorts/usage_ping.js
+++ b/app/assets/javascripts/pages/instance_statistics/cohorts/usage_ping.js
diff --git a/app/assets/javascripts/pages/admin/conversational_development_index/show/index.js b/app/assets/javascripts/pages/instance_statistics/conversational_development_index/index.js
index c1056537f90..c1056537f90 100644
--- a/app/assets/javascripts/pages/admin/conversational_development_index/show/index.js
+++ b/app/assets/javascripts/pages/instance_statistics/conversational_development_index/index.js
diff --git a/app/assets/javascripts/pages/projects/settings/repository/show/index.js b/app/assets/javascripts/pages/projects/settings/repository/show/index.js
index ffc84dc106b..78cf5406e43 100644
--- a/app/assets/javascripts/pages/projects/settings/repository/show/index.js
+++ b/app/assets/javascripts/pages/projects/settings/repository/show/index.js
@@ -1,3 +1,9 @@
import initForm from '../form';
+import MirrorRepos from './mirror_repos';
-document.addEventListener('DOMContentLoaded', initForm);
+document.addEventListener('DOMContentLoaded', () => {
+ initForm();
+
+ const mirrorReposContainer = document.querySelector('.js-mirror-settings');
+ if (mirrorReposContainer) new MirrorRepos(mirrorReposContainer).init();
+});
diff --git a/app/assets/javascripts/pages/projects/settings/repository/show/mirror_repos.js b/app/assets/javascripts/pages/projects/settings/repository/show/mirror_repos.js
new file mode 100644
index 00000000000..4c56af20cc3
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/settings/repository/show/mirror_repos.js
@@ -0,0 +1,94 @@
+import $ from 'jquery';
+import _ from 'underscore';
+import { __ } from '~/locale';
+import Flash from '~/flash';
+import axios from '~/lib/utils/axios_utils';
+
+export default class MirrorRepos {
+ constructor(container) {
+ this.$container = $(container);
+ this.$form = $('.js-mirror-form', this.$container);
+ this.$urlInput = $('.js-mirror-url', this.$form);
+ this.$protectedBranchesInput = $('.js-mirror-protected', this.$form);
+ this.$table = $('.js-mirrors-table-body', this.$container);
+ this.mirrorEndpoint = this.$form.data('projectMirrorEndpoint');
+ }
+
+ init() {
+ this.initMirrorPush();
+ this.registerUpdateListeners();
+ }
+
+ initMirrorPush() {
+ this.$passwordGroup = $('.js-password-group', this.$container);
+ this.$password = $('.js-password', this.$passwordGroup);
+ this.$authMethod = $('.js-auth-method', this.$form);
+
+ this.$authMethod.on('change', () => this.togglePassword());
+ this.$password.on('input.updateUrl', () => this.debouncedUpdateUrl());
+ }
+
+ updateUrl() {
+ let val = this.$urlInput.val();
+
+ if (this.$password) {
+ const password = this.$password.val();
+ if (password) val = val.replace('@', `:${password}@`);
+ }
+
+ $('.js-mirror-url-hidden', this.$form).val(val);
+ }
+
+ updateProtectedBranches() {
+ const val = this.$protectedBranchesInput.get(0).checked
+ ? this.$protectedBranchesInput.val()
+ : '0';
+ $('.js-mirror-protected-hidden', this.$form).val(val);
+ }
+
+ registerUpdateListeners() {
+ this.debouncedUpdateUrl = _.debounce(() => this.updateUrl(), 200);
+ this.$urlInput.on('input', () => this.debouncedUpdateUrl());
+ this.$protectedBranchesInput.on('change', () => this.updateProtectedBranches());
+ this.$table.on('click', '.js-delete-mirror', event => this.deleteMirror(event));
+ }
+
+ togglePassword() {
+ const isPassword = this.$authMethod.val() === 'password';
+
+ if (!isPassword) {
+ this.$password.val('');
+ this.updateUrl();
+ }
+ this.$passwordGroup.collapse(isPassword ? 'show' : 'hide');
+ }
+
+ deleteMirror(event, existingPayload) {
+ const $target = $(event.currentTarget);
+ let payload = existingPayload;
+
+ if (!payload) {
+ payload = {
+ project: {
+ remote_mirrors_attributes: {
+ id: $target.data('mirrorId'),
+ enabled: 0,
+ },
+ },
+ };
+ }
+
+ return axios
+ .put(this.mirrorEndpoint, payload)
+ .then(() => this.removeRow($target))
+ .catch(() => Flash(__('Failed to remove mirror.')));
+ }
+
+ /* eslint-disable class-methods-use-this */
+ removeRow($target) {
+ const row = $target.closest('tr');
+ $('.js-delete-mirror', row).tooltip('hide');
+ row.remove();
+ }
+ /* eslint-enable class-methods-use-this */
+}
diff --git a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
index 804822a3ea8..29b347824de 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
@@ -330,7 +330,7 @@ export default {
<pipelines-artifacts-component
v-if="pipeline.details.artifacts.length"
:artifacts="pipeline.details.artifacts"
- class="d-none d-sm-none d-md-block"
+ class="d-md-block"
/>
<loading-button
diff --git a/app/assets/javascripts/project_select.js b/app/assets/javascripts/project_select.js
index bce7556bd40..6f3b32f8eea 100644
--- a/app/assets/javascripts/project_select.js
+++ b/app/assets/javascripts/project_select.js
@@ -14,6 +14,7 @@ export default function projectSelect() {
this.orderBy = $(select).data('orderBy') || 'id';
this.withIssuesEnabled = $(select).data('withIssuesEnabled');
this.withMergeRequestsEnabled = $(select).data('withMergeRequestsEnabled');
+ this.allowClear = $(select).data('allowClear') || false;
placeholder = "Search for project";
if (this.includeGroups) {
@@ -71,6 +72,13 @@ export default function projectSelect() {
text: function (project) {
return project.name_with_namespace || project.name;
},
+
+ initSelection: function(el, callback) {
+ return Api.project(el.val()).then(({ data }) => callback(data));
+ },
+
+ allowClear: this.allowClear,
+
dropdownCssClass: "ajax-project-dropdown"
});
if (simpleFilter) return select;
diff --git a/app/assets/javascripts/reports/components/grouped_test_reports_app.vue b/app/assets/javascripts/reports/components/grouped_test_reports_app.vue
index 140475b4dfa..7b37f4e9a97 100644
--- a/app/assets/javascripts/reports/components/grouped_test_reports_app.vue
+++ b/app/assets/javascripts/reports/components/grouped_test_reports_app.vue
@@ -1,10 +1,10 @@
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
import { s__ } from '~/locale';
- import { componentNames } from '~/vue_shared/components/reports/issue_body';
- import ReportSection from '~/vue_shared/components/reports/report_section.vue';
- import SummaryRow from '~/vue_shared/components/reports/summary_row.vue';
- import IssuesList from '~/vue_shared/components/reports/issues_list.vue';
+ import { componentNames } from './issue_body';
+ import ReportSection from './report_section.vue';
+ import SummaryRow from './summary_row.vue';
+ import IssuesList from './issues_list.vue';
import Modal from './modal.vue';
import createStore from '../store';
import { summaryTextBuilder, reportTextBuilder, statusIcon } from '../store/utils';
diff --git a/app/assets/javascripts/vue_shared/components/reports/issue_body.js b/app/assets/javascripts/reports/components/issue_body.js
index 54dfb7b16bf..8b5af263d50 100644
--- a/app/assets/javascripts/vue_shared/components/reports/issue_body.js
+++ b/app/assets/javascripts/reports/components/issue_body.js
@@ -1,4 +1,4 @@
-import TestIssueBody from '~/reports/components/test_issue_body.vue';
+import TestIssueBody from './test_issue_body.vue';
export const components = {
TestIssueBody,
diff --git a/app/assets/javascripts/vue_shared/components/reports/issue_status_icon.vue b/app/assets/javascripts/reports/components/issue_status_icon.vue
index f8189117ac3..85811698a37 100644
--- a/app/assets/javascripts/vue_shared/components/reports/issue_status_icon.vue
+++ b/app/assets/javascripts/reports/components/issue_status_icon.vue
@@ -1,11 +1,10 @@
<script>
import Icon from '~/vue_shared/components/icon.vue';
-
import {
STATUS_FAILED,
STATUS_NEUTRAL,
STATUS_SUCCESS,
-} from '~/vue_shared/components/reports/constants';
+} from '../constants';
export default {
name: 'IssueStatusIcon',
diff --git a/app/assets/javascripts/vue_shared/components/reports/issues_list.vue b/app/assets/javascripts/reports/components/issues_list.vue
index 2545e84f932..df42201b5de 100644
--- a/app/assets/javascripts/vue_shared/components/reports/issues_list.vue
+++ b/app/assets/javascripts/reports/components/issues_list.vue
@@ -1,10 +1,10 @@
<script>
-import IssuesBlock from '~/vue_shared/components/reports/report_issues.vue';
+import IssuesBlock from '~/reports/components/report_issues.vue';
import {
STATUS_SUCCESS,
STATUS_FAILED,
STATUS_NEUTRAL,
-} from '~/vue_shared/components/reports/constants';
+} from '~/reports/constants';
/**
* Renders block of issues
diff --git a/app/assets/javascripts/vue_shared/components/reports/modal_open_name.vue b/app/assets/javascripts/reports/components/modal_open_name.vue
index 4f81cee2a38..4f81cee2a38 100644
--- a/app/assets/javascripts/vue_shared/components/reports/modal_open_name.vue
+++ b/app/assets/javascripts/reports/components/modal_open_name.vue
diff --git a/app/assets/javascripts/vue_shared/components/reports/report_issues.vue b/app/assets/javascripts/reports/components/report_issues.vue
index 1f13e555b31..c553a374f66 100644
--- a/app/assets/javascripts/vue_shared/components/reports/report_issues.vue
+++ b/app/assets/javascripts/reports/components/report_issues.vue
@@ -1,6 +1,6 @@
<script>
-import IssueStatusIcon from '~/vue_shared/components/reports/issue_status_icon.vue';
-import { components, componentNames } from '~/vue_shared/components/reports/issue_body';
+import IssueStatusIcon from '~/reports/components/issue_status_icon.vue';
+import { components, componentNames } from '~/reports/components/issue_body';
export default {
name: 'ReportIssues',
diff --git a/app/assets/javascripts/vue_shared/components/reports/report_link.vue b/app/assets/javascripts/reports/components/report_link.vue
index 74d68f9f439..74d68f9f439 100644
--- a/app/assets/javascripts/vue_shared/components/reports/report_link.vue
+++ b/app/assets/javascripts/reports/components/report_link.vue
diff --git a/app/assets/javascripts/vue_shared/components/reports/report_section.vue b/app/assets/javascripts/reports/components/report_section.vue
index a6dbf21092b..dc609d6f90e 100644
--- a/app/assets/javascripts/vue_shared/components/reports/report_section.vue
+++ b/app/assets/javascripts/reports/components/report_section.vue
@@ -1,8 +1,8 @@
<script>
import { __ } from '~/locale';
import StatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue';
+import Popover from '~/vue_shared/components/help_popover.vue';
import IssuesList from './issues_list.vue';
-import Popover from '../help_popover.vue';
const LOADING = 'LOADING';
const ERROR = 'ERROR';
diff --git a/app/assets/javascripts/vue_shared/components/reports/summary_row.vue b/app/assets/javascripts/reports/components/summary_row.vue
index 063beab58fc..4456d84c968 100644
--- a/app/assets/javascripts/vue_shared/components/reports/summary_row.vue
+++ b/app/assets/javascripts/reports/components/summary_row.vue
@@ -1,7 +1,7 @@
<script>
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
-import Popover from '../help_popover.vue';
+import Popover from '~/vue_shared/components/help_popover.vue';
/**
* Renders the summary row for each report
diff --git a/app/assets/javascripts/reports/constants.js b/app/assets/javascripts/reports/constants.js
index 807ecb1039e..c323dc543f3 100644
--- a/app/assets/javascripts/reports/constants.js
+++ b/app/assets/javascripts/reports/constants.js
@@ -11,6 +11,8 @@ export const SUCCESS = 'SUCCESS';
export const STATUS_FAILED = 'failed';
export const STATUS_SUCCESS = 'success';
+export const STATUS_NEUTRAL = 'neutral';
+
export const ICON_WARNING = 'warning';
export const ICON_SUCCESS = 'success';
export const ICON_NOTFOUND = 'notfound';
diff --git a/app/assets/javascripts/sidebar/components/lock/edit_form_buttons.vue b/app/assets/javascripts/sidebar/components/lock/edit_form_buttons.vue
index 5e7b8f9698f..63082654101 100644
--- a/app/assets/javascripts/sidebar/components/lock/edit_form_buttons.vue
+++ b/app/assets/javascripts/sidebar/components/lock/edit_form_buttons.vue
@@ -1,4 +1,5 @@
<script>
+import { __ } from '~/locale';
import $ from 'jquery';
import eventHub from '../../event_hub';
@@ -17,7 +18,7 @@ export default {
computed: {
buttonText() {
- return this.isLocked ? this.__('Unlock') : this.__('Lock');
+ return this.isLocked ? __('Unlock') : __('Lock');
},
toggleLock() {
diff --git a/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue b/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue
index 8bbc59f623a..ab7fab7e5ca 100644
--- a/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue
+++ b/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue
@@ -1,5 +1,5 @@
<script>
-import { __ } from '~/locale';
+import { __, sprintf } from '~/locale';
import Flash from '~/flash';
import tooltip from '~/vue_shared/directives/tooltip';
import issuableMixin from '~/vue_shared/mixins/issuable';
@@ -79,11 +79,9 @@ export default {
.then(() => window.location.reload())
.catch(() =>
Flash(
- this.__(
- `Something went wrong trying to change the locked state of this ${
- this.issuableDisplayName
- }`,
- ),
+ sprintf(__('Something went wrong trying to change the locked state of this %{issuableDisplayName}'), {
+ issuableDisplayName: this.issuableDisplayName,
+ }),
),
);
},
diff --git a/app/assets/javascripts/vue_shared/components/reports/constants.js b/app/assets/javascripts/vue_shared/components/reports/constants.js
deleted file mode 100644
index dbde648bfdb..00000000000
--- a/app/assets/javascripts/vue_shared/components/reports/constants.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export const STATUS_FAILED = 'failed';
-export const STATUS_SUCCESS = 'success';
-export const STATUS_NEUTRAL = 'neutral';
diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss
index c20738a20c3..c91f5e279ea 100644
--- a/app/assets/stylesheets/bootstrap_migration.scss
+++ b/app/assets/stylesheets/bootstrap_migration.scss
@@ -4,17 +4,16 @@
$text-color: $gl-text-color;
-$brand-primary: $gl-primary;
-$brand-success: $gl-success;
-$brand-info: $gl-info;
-$brand-warning: $gl-warning;
-$brand-danger: $gl-danger;
+$brand-primary: $blue-500;
+$brand-success: $green-500;
+$brand-info: $blue-500;
+$brand-warning: $orange-500;
+$brand-danger: $red-500;
$border-radius-base: 3px !default;
$modal-body-bg: $white-light;
$input-border: $border-color;
-$input-border-focus: $focus-border-color;
$padding-base-vertical: $gl-vert-padding;
$padding-base-horizontal: $gl-padding;
@@ -86,7 +85,7 @@ strong {
}
a {
- color: $gl-link-color;
+ color: $blue-600;
}
hr {
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index c46b0b5db09..b1a20c06910 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -1,4 +1,5 @@
@import 'framework/variables';
+@import 'framework/variables_overrides';
@import 'framework/mixins';
@import 'bootstrap';
diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss
index 369556dc24e..9dd0384a228 100644
--- a/app/assets/stylesheets/framework/avatar.scss
+++ b/app/assets/stylesheets/framework/avatar.scss
@@ -8,7 +8,7 @@
float: left;
margin-right: 15px;
border-radius: $avatar-radius;
- border: 1px solid $avatar-border;
+ border: 1px solid $gray-normal;
&.s16 { @include avatar-size(16px, 6px); }
&.s18 { @include avatar-size(18px, 6px); }
&.s19 { @include avatar-size(19px, 6px); }
@@ -36,7 +36,7 @@
width: 40px;
height: 40px;
padding: 0;
- background: $avatar-background;
+ background: $gray-lightest;
overflow: hidden;
&.avatar-inline {
@@ -62,7 +62,7 @@
}
&:not([href]):hover {
- border-color: darken($avatar-border, 10%);
+ border-color: darken($gray-normal, 10%);
}
}
@@ -70,7 +70,7 @@
text-align: center;
vertical-align: top;
color: $identicon-fg-color;
- background-color: $identicon-gray;
+ background-color: $gray-darker;
// Sizes
&.s16 { font-size: 12px; line-height: 1.33; }
@@ -94,7 +94,7 @@
&.bg4 { background-color: $identicon-blue; }
&.bg5 { background-color: $identicon-teal; }
&.bg6 { background-color: $identicon-orange; }
- &.bg7 { background-color: $identicon-gray; }
+ &.bg7 { background-color: $gray-darker; }
}
.avatar-container {
@@ -103,6 +103,7 @@
display: flex;
a {
+ width: 100%;
display: flex;
}
@@ -121,7 +122,7 @@
.avatar-counter {
background-color: $gray-darkest;
color: $white-light;
- border: 1px solid $avatar-border;
+ border: 1px solid $gray-normal;
border-radius: 1em;
font-family: $regular-font;
font-size: 9px;
diff --git a/app/assets/stylesheets/framework/awards.scss b/app/assets/stylesheets/framework/awards.scss
index 8d11b92cf88..a265e4206f1 100644
--- a/app/assets/stylesheets/framework/awards.scss
+++ b/app/assets/stylesheets/framework/awards.scss
@@ -141,8 +141,8 @@
&:hover,
&:active,
&.is-active {
- background-color: $row-hover;
- border-color: $row-hover-border;
+ background-color: $blue-50;
+ border-color: $blue-200;
box-shadow: none;
outline: 0;
diff --git a/app/assets/stylesheets/framework/badges.scss b/app/assets/stylesheets/framework/badges.scss
index 57df9b969c3..c6060161dec 100644
--- a/app/assets/stylesheets/framework/badges.scss
+++ b/app/assets/stylesheets/framework/badges.scss
@@ -1,6 +1,6 @@
.badge.badge-pill {
font-weight: $gl-font-weight-normal;
background-color: $badge-bg;
- color: $badge-color;
+ color: $gl-text-color-secondary;
vertical-align: baseline;
}
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index 646cedd79ed..72b4ed0ac33 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -350,7 +350,7 @@
&:focus {
cursor: text;
box-shadow: none;
- border-color: lighten($dropdown-input-focus-border, 20%);
+ border-color: lighten($blue-300, 20%);
color: $gray-darkest;
background-color: $gray-light;
}
@@ -434,7 +434,7 @@
&:hover,
&:active,
&:focus {
- color: $gl-link-color;
+ color: $blue-600;
text-decoration: none;
}
}
@@ -445,21 +445,21 @@
&:hover,
&:active,
&:focus {
- color: $gl-link-color;
+ color: $blue-600;
text-decoration: none;
}
}
}
.btn-missing {
- color: $notes-light-color;
+ color: $gl-text-color-secondary;
border: 1px dashed $border-gray-normal-dashed;
border-radius: $border-radius-default;
&:hover,
&:active,
&:focus {
- color: $notes-light-color;
+ color: $gl-text-color-secondary;
background-color: $white-normal;
}
}
diff --git a/app/assets/stylesheets/framework/callout.scss b/app/assets/stylesheets/framework/callout.scss
index 1bd94c0acba..bdd7f09d926 100644
--- a/app/assets/stylesheets/framework/callout.scss
+++ b/app/assets/stylesheets/framework/callout.scss
@@ -25,25 +25,25 @@
/* Variations */
.bs-callout-danger {
- background-color: $callout-danger-bg;
- border-color: $callout-danger-border;
- color: $callout-danger-color;
+ background-color: $red-100;
+ border-color: $red-200;
+ color: $red-700;
}
.bs-callout-warning {
- background-color: $callout-warning-bg;
- border-color: $callout-warning-border;
- color: $callout-warning-color;
+ background-color: $orange-100;
+ border-color: $orange-200;
+ color: $orange-700;
}
.bs-callout-info {
- background-color: $callout-info-bg;
- border-color: $callout-info-border;
- color: $callout-info-color;
+ background-color: $blue-100;
+ border-color: $blue-200;
+ color: $blue-700;
}
.bs-callout-success {
- background-color: $callout-success-bg;
- border-color: $callout-success-border;
- color: $callout-success-color;
+ background-color: $green-100;
+ border-color: $green-200;
+ color: $green-700;
}
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index af17210f341..72e27f9ad16 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -1,8 +1,8 @@
/** COLORS **/
-.cgray { color: $common-gray; }
+.cgray { color: $gl-text-color; }
.clgray { color: $common-gray-light; }
-.cred { color: $common-red; }
-.cgreen { color: $common-green; }
+.cred { color: $red-500; }
+.cgreen { color: $green-600; }
.cdark { color: $common-gray-dark; }
.text-plain,
@@ -44,10 +44,10 @@
}
.hint { font-style: italic; color: $hint-color; }
-.light { color: $common-gray; }
+.light { color: $gl-text-color; }
.slead {
- color: $common-gray;
+ color: $gl-text-color;
font-size: 14px;
margin-bottom: 12px;
font-weight: $gl-font-weight-normal;
@@ -71,7 +71,7 @@ pre {
}
&.card.card-body-pre {
- border: 1px solid $well-pre-bg;
+ border: 1px solid $gray-darker;
background: $gray-light;
border-radius: 0;
color: $well-pre-color;
@@ -114,7 +114,11 @@ hr {
.item-title { font-weight: $gl-font-weight-bold; }
.author-link {
- color: $gl-link-color;
+ color: $blue-600;
+}
+
+.author-link:hover {
+ text-decoration: none;
}
.back-link {
@@ -229,7 +233,7 @@ li.note {
.error-message {
padding: 10px;
- background: $error-bg;
+ background: $red-400;
margin: 0;
color: $white-light;
@@ -240,11 +244,11 @@ li.note {
}
.warning_message {
- border-left: 4px solid $warning-message-border;
- color: $warning-message-color;
+ border-left: 4px solid $orange-200;
+ color: $orange-700;
padding: 10px;
margin-bottom: 10px;
- background: $warning-message-bg;
+ background: $orange-100;
padding-left: 20px;
&.centered {
@@ -344,20 +348,11 @@ img.emoji {
}
}
-.profiler-results {
- top: 73px !important;
-
- .profiler-button,
- .profiler-controls {
- border-color: $profiler-border !important;
- }
-}
-
.dropzone .dz-preview .dz-progress {
border-color: $border-color !important;
.dz-upload {
- background: $gl-success !important;
+ background: $green-500 !important;
}
}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index eebce8b9011..8a224dc517e 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -147,7 +147,7 @@
}
@mixin dropdown-item-hover {
- background-color: $dropdown-item-hover-bg;
+ background-color: $gray-darker;
color: $gl-text-color;
outline: 0;
@@ -195,7 +195,7 @@
text-decoration: none;
.badge.badge-pill {
- background-color: darken($dropdown-link-hover-bg, 5%);
+ background-color: darken($blue-50, 5%);
}
}
@@ -233,7 +233,7 @@
font-weight: $gl-font-weight-normal;
padding: 8px 0;
background-color: $white-light;
- border: 1px solid $dropdown-border-color;
+ border: 1px solid $border-color;
border-radius: $border-radius-base;
box-shadow: 0 2px 4px $dropdown-shadow-color;
@@ -615,7 +615,7 @@
&:focus {
color: $dropdown-link-color;
- border-color: $dropdown-input-focus-border;
+ border-color: $blue-300;
box-shadow: 0 0 4px $dropdown-input-focus-shadow;
~ .fa {
@@ -874,7 +874,7 @@ header.header-content .dropdown-menu.frequent-items-dropdown-menu {
overflow-y: auto;
li.section-empty.section-failure {
- color: $callout-danger-color;
+ color: $red-700;
}
.frequent-items-list-item-container a {
diff --git a/app/assets/stylesheets/framework/emojis.scss b/app/assets/stylesheets/framework/emojis.scss
index 3cde0490371..a8ec1e1145a 100644
--- a/app/assets/stylesheets/framework/emojis.scss
+++ b/app/assets/stylesheets/framework/emojis.scss
@@ -2,7 +2,7 @@ gl-emoji {
font-style: normal;
display: inline-flex;
vertical-align: middle;
- font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
+ font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: 1.5em;
line-height: 0.9;
}
diff --git a/app/assets/stylesheets/framework/feature_highlight.scss b/app/assets/stylesheets/framework/feature_highlight.scss
index cad915bc86f..85cabf43e9e 100644
--- a/app/assets/stylesheets/framework/feature_highlight.scss
+++ b/app/assets/stylesheets/framework/feature_highlight.scss
@@ -72,11 +72,11 @@
.feature-highlight-popover {
width: 240px;
padding: 0;
- border: 1px solid $dropdown-border-color;
+ border: 1px solid $border-color;
box-shadow: 0 2px 4px $dropdown-shadow-color;
&.right > .arrow {
- border-right-color: $dropdown-border-color;
+ border-right-color: $border-color;
}
.popover-body {
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 00eac1688f2..1d3512bbb4c 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -112,7 +112,7 @@
&.image_file,
&.video {
- background: $file-image-bg;
+ background: $gray-darker;
text-align: center;
padding: 30px;
@@ -131,7 +131,7 @@
}
&.blob-no-preview {
- background: $blob-bg;
+ background: $gray-darker;
text-shadow: 0 1px 2px $white-light;
padding: 100px 0;
}
@@ -146,7 +146,7 @@
}
tr {
- border-bottom: 1px solid $blame-border;
+ border-bottom: 1px solid $gray-darker;
&:last-child {
border-bottom: 0;
@@ -211,7 +211,7 @@
}
&.logs {
- background: $logs-bg;
+ background: $gray-darker;
max-height: 700px;
overflow-y: auto;
@@ -233,7 +233,7 @@
}
&:hover {
- background: $row-hover;
+ background: $blue-50;
}
}
}
@@ -286,19 +286,19 @@ span.idiff {
.new-file {
a {
- color: $gl-text-green;
+ color: $green-600;
}
}
.renamed-file {
a {
- color: $gl-text-orange;
+ color: $orange-600;
}
}
.deleted-file {
a {
- color: $gl-text-red;
+ color: $red-500;
}
}
@@ -312,11 +312,11 @@ span.idiff {
text-decoration: none;
.new-file {
- color: $notify-new-file;
+ color: $green-600;
}
.deleted-file {
- color: $notify-deleted-file;
+ color: $red-700;
}
}
}
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index 5d79610b21e..abfe350677e 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -205,8 +205,8 @@
&.focus,
&.focus:hover {
- border-color: $dropdown-input-focus-border;
- box-shadow: 0 0 4px $search-input-focus-shadow-color;
+ border-color: $blue-300;
+ box-shadow: 0 0 4px $dropdown-input-focus-shadow;
}
gl-emoji {
@@ -294,7 +294,7 @@
&:hover,
&:focus {
color: $gl-text-color;
- border-color: $dropdown-input-focus-border;
+ border-color: $blue-300;
outline: none;
}
diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss
index e4bcb92876d..7a4c3914fb0 100644
--- a/app/assets/stylesheets/framework/flash.scss
+++ b/app/assets/stylesheets/framework/flash.scss
@@ -16,10 +16,10 @@
color: $gl-text-color;
a {
- color: $gl-link-color;
+ color: $blue-600;
&:hover {
- color: $gl-link-hover-color;
+ color: $blue-800;
text-decoration: none;
}
}
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index d7149d93622..afd888af672 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -8,7 +8,7 @@ input {
input[type='text'].danger {
background: $input-danger-bg !important;
- border-color: $input-danger-border;
+ border-color: $red-400;
text-shadow: 0 1px 1px $white-light;
}
@@ -170,7 +170,7 @@ label {
}
.form-control::-webkit-input-placeholder {
- color: $placeholder-text-color;
+ color: $gl-text-color-tertiary;
}
.input-group {
@@ -201,7 +201,7 @@ label {
}
.gl-show-field-errors {
- .form-control {
+ .form-control:not(textarea) {
height: 34px;
}
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index e7e13d35d8e..11a30d83f03 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -554,7 +554,7 @@
float: left;
margin-right: 5px;
border-radius: 50%;
- border: 1px solid $avatar-border;
+ border: 1px solid $gray-normal;
}
.with-performance-bar .navbar-gitlab {
diff --git a/app/assets/stylesheets/framework/icons.scss b/app/assets/stylesheets/framework/icons.scss
index d1f7ff4438b..f002edced8a 100644
--- a/app/assets/stylesheets/framework/icons.scss
+++ b/app/assets/stylesheets/framework/icons.scss
@@ -11,7 +11,7 @@
.ci-status-icon-failed {
svg {
- fill: $gl-danger;
+ fill: $red-500;
}
&.add-border {
diff --git a/app/assets/stylesheets/framework/images.scss b/app/assets/stylesheets/framework/images.scss
index f878ec1ca91..1e93bf2b751 100644
--- a/app/assets/stylesheets/framework/images.scss
+++ b/app/assets/stylesheets/framework/images.scss
@@ -25,7 +25,7 @@
&.svg-#{$width} {
img,
svg {
- width: #{$width + 'px'};
+ max-width: #{$width + 'px'};
}
}
}
diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss
index 86de88729ee..2d672e62e08 100644
--- a/app/assets/stylesheets/framework/issue_box.scss
+++ b/app/assets/stylesheets/framework/issue_box.scss
@@ -26,12 +26,12 @@
&.status-box-closed,
&.status-box-mr-closed {
- background-color: $gl-danger;
+ background-color: $red-500;
}
&.status-box-issue-closed,
&.status-box-mr-merged {
- background-color: $gl-primary;
+ background-color: $blue-500;
}
&.status-box-open {
@@ -39,7 +39,7 @@
}
&.status-box-expired {
- background-color: $issue-status-expired;
+ background-color: $orange-500;
}
&.status-box-upcoming {
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index 4b67eab05b3..fdc0454d837 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -21,11 +21,11 @@
}
&.disabled {
- color: $list-text-disabled-color;
+ color: $gl-text-color-tertiary;
}
&:not(.ui-sort-disabled):hover {
- background: $row-hover;
+ background: $blue-50;
}
&.unstyled {
@@ -35,12 +35,12 @@
}
&.warning-row {
- background-color: $list-warning-row-bg;
- border-color: $list-warning-row-border;
- color: $list-warning-row-color;
+ background-color: $orange-100;
+ border-color: $orange-200;
+ color: $orange-700;
&:hover {
- background: $list-warning-row-bg;
+ background: $orange-100;
}
}
@@ -73,7 +73,7 @@
}
.card.card-body-title {
- font-size: $list-font-size;
+ font-size: $gl-font-size;
line-height: 18px;
}
}
@@ -109,8 +109,8 @@ ul.content-list {
li {
border-color: $white-normal;
- font-size: $list-font-size;
- color: $list-text-color;
+ font-size: $gl-font-size;
+ color: $gl-text-color;
&.no-description {
.title {
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index 7290a174668..d8391b59a8c 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -179,7 +179,7 @@
&:hover,
&:focus {
svg {
- fill: $gl-link-color;
+ fill: $blue-600;
}
}
}
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index 98bf26a5222..7edb89ce6f3 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -50,7 +50,7 @@
@include clearfix;
padding: 10px 0;
- border-bottom: 1px solid $list-border-light;
+ border-bottom: 1px solid $gray-darker;
display: block;
margin: 0;
diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss
index ffb40166c15..7d53a631cdf 100644
--- a/app/assets/stylesheets/framework/modal.scss
+++ b/app/assets/stylesheets/framework/modal.scss
@@ -79,7 +79,6 @@ body.modal-open {
.modal {
background-color: $black-transparent;
- z-index: 2100;
@include media-breakpoint-up(md) {
.modal-dialog {
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index b40dcf93969..3ae2c7078d6 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -58,7 +58,7 @@
.select2-drop.select2-drop-above {
box-shadow: 0 2px 4px $dropdown-shadow-color;
border-radius: $border-radius-base;
- border: 1px solid $dropdown-border-color;
+ border: 1px solid $border-color;
min-width: 175px;
color: $gl-text-color;
z-index: 999;
@@ -69,7 +69,7 @@
}
.select2-drop.select2-drop-above.select2-drop-active {
- border-top: 1px solid $dropdown-border-color;
+ border-top: 1px solid $border-color;
margin-top: -6px;
}
@@ -153,7 +153,7 @@
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
&:focus {
- border-color: $input-border-focus;
+ border-color: $blue-300;
}
&.select2-active {
@@ -193,7 +193,7 @@
color: $gl-text-color;
.select2-result-label {
- background: $dropdown-item-hover-bg;
+ background: $gray-darker;
}
}
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 473ca408c04..5c6110737a4 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -7,7 +7,7 @@
}
a {
- color: $md-link-color;
+ color: $blue-600;
}
img:not(.emoji) {
@@ -180,7 +180,7 @@
}
a > code {
- color: $gl-link-color;
+ color: $blue-600;
}
dd {
@@ -423,25 +423,25 @@ h4 {
input,
textarea {
&::-webkit-input-placeholder {
- color: $placeholder-text-color;
+ color: $gl-text-color-tertiary;
}
// support firefox 19+ vendor prefix
&::-moz-placeholder {
- color: $placeholder-text-color;
+ color: $gl-text-color-tertiary;
opacity: 1; // FF defaults to 0.54
}
// scss-lint:disable PseudoElement
// support Edge vendor prefix
&::-ms-input-placeholder {
- color: $placeholder-text-color;
+ color: $gl-text-color-tertiary;
}
// scss-lint:disable PseudoElement
// support IE vendor prefix
&:-ms-input-placeholder {
- color: $placeholder-text-color;
+ color: $gl-text-color-tertiary;
}
}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 4db9efff6ee..2781d910b8d 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -177,7 +177,6 @@ $border-gray-dark: darken($white-normal, $darken-border-factor);
* UI elements
*/
$border-color: #e5e5e5;
-$focus-border-color: $blue-300;
$well-expand-item: #e8f2f7;
$well-inner-border: #eef0f2;
$well-light-border: #f1f1f1;
@@ -196,38 +195,20 @@ $gl-text-color-quaternary: #d6d6d6;
$gl-text-color-inverted: rgba(255, 255, 255, 1);
$gl-text-color-secondary-inverted: rgba(255, 255, 255, 0.85);
$gl-text-color-disabled: #919191;
-$gl-text-green: $green-600;
-$gl-text-green-hover: $green-700;
-$gl-text-red: $red-500;
-$gl-text-orange: $orange-600;
-$gl-link-color: $blue-600;
-$gl-link-hover-color: $blue-800;
$gl-grayish-blue: #7f8fa4;
-$gl-gray: $gl-text-color;
$gl-gray-dark: #313236;
$gl-gray-light: #5c5c5c;
$gl-header-color: #4c4e54;
-$gl-header-nav-hover-color: #434343;
-$placeholder-text-color: $gl-text-color-tertiary;
/*
* Lists
*/
-$list-font-size: $gl-font-size;
-$list-title-color: $gl-text-color;
-$list-text-color: $gl-text-color;
-$list-text-disabled-color: $gl-text-color-tertiary;
-$list-border-light: #eee;
$list-border: rgba(0, 0, 0, 0.05);
$list-text-height: 42px;
-$list-warning-row-bg: $orange-100;
-$list-warning-row-border: $orange-200;
-$list-warning-row-color: $orange-700;
/*
* Markdown
*/
-$md-link-color: $gl-link-color;
$md-area-border: #ddd;
/*
@@ -259,8 +240,6 @@ $gl-bar-padding: 3px;
/*
* Misc
*/
-$row-hover: $blue-50;
-$row-hover-border: $blue-200;
$progress-color: #c0392b;
$header-height: 40px;
$ide-statusbar-height: 25px;
@@ -268,19 +247,13 @@ $fixed-layout-width: 1280px;
$limited-layout-width: 990px;
$container-text-max-width: 540px;
$gl-avatar-size: 40px;
-$error-exclamation-point: $red-500;
$border-radius-default: 4px;
$border-radius-small: 2px;
$settings-icon-size: 18px;
-$provider-btn-not-active-color: $blue-500;
-$link-underline-blue: $blue-500;
-$active-item-blue: $blue-500;
$layout-link-gray: #7e7c7c;
$btn-side-margin: 10px;
$btn-sm-side-margin: 7px;
$btn-margin-5: 5px;
-$issue-status-expired: $orange-500;
-$issuable-sidebar-color: $gl-text-color-secondary;
$sidebar-block-hover-color: #ebebeb;
$group-path-color: #999;
$namespace-kind-color: #aaa;
@@ -302,7 +275,6 @@ $breadcrumb-min-height: 48px;
* Common component specific colors
*/
$hint-color: #999;
-$well-pre-bg: #eee;
$well-pre-color: #555;
$loading-color: #555;
$update-author-color: #999;
@@ -311,10 +283,6 @@ $user-mention-bg-hover: rgba($blue-500, 0.15);
$time-color: #999;
$project-member-show-color: #aaa;
$gl-promo-color: #aaa;
-$error-bg: $red-400;
-$warning-message-bg: $orange-100;
-$warning-message-border: $orange-200;
-$warning-message-color: $orange-700;
$control-group-descr-color: #666;
$table-permission-x-bg: #d9edf7;
$username-color: #666;
@@ -329,19 +297,9 @@ $tanuki-yellow: #fca326;
/*
* State colors:
*/
-$gl-primary: $blue-500;
-$gl-success: $green-500;
-$gl-success-focus: rgba($gl-success, 0.4);
-$gl-info: $blue-500;
-$gl-warning: $orange-500;
-$gl-danger: $red-500;
+$green-500-focus: rgba($green-500, 0.4);
$gl-btn-active-background: rgba(0, 0, 0, 0.16);
$gl-btn-active-gradient: inset 0 2px 3px $gl-btn-active-background;
-// Bootstrap override states
-$success: $gl-success;
-$info: $gl-info;
-$warning: $gl-warning;
-$danger: $gl-danger;
/*
* Commit Diff Colors
@@ -361,10 +319,9 @@ $line-select-yellow-dark: #f0e2bd;
$dark-diff-match-bg: rgba(255, 255, 255, 0.3);
$dark-diff-match-color: rgba(255, 255, 255, 0.1);
$file-mode-changed: #777;
-$file-mode-changed: #777;
-$diff-image-info-color: grey;
+$diff-image-info-color: gray;
$diff-swipe-border: #999;
-$diff-view-modes-color: grey;
+$diff-view-modes-color: gray;
$diff-view-modes-border: #c1c1c1;
$diff-jagged-border-gradient-color: darken($white-normal, 8%);
@@ -374,7 +331,7 @@ $diff-jagged-border-gradient-color: darken($white-normal, 8%);
$monospace-font: 'Menlo', 'DejaVu Sans Mono', 'Liberation Mono', 'Consolas', 'Ubuntu Mono',
'Courier New', 'andale mono', 'lucida console', monospace;
$regular-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell,
- 'Helvetica Neue', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
+ 'Helvetica Neue', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
/*
* Dropdowns
@@ -384,20 +341,16 @@ $dropdown-min-height: 40px;
$dropdown-max-height: 312px;
$dropdown-vertical-offset: 4px;
$dropdown-link-color: #555;
-$dropdown-link-hover-bg: $row-hover;
$dropdown-empty-row-bg: rgba(#000, 0.04);
-$dropdown-border-color: $border-color;
$dropdown-shadow-color: rgba(#000, 0.1);
$dropdown-divider-color: rgba(#000, 0.1);
$dropdown-title-btn-color: #bfbfbf;
$dropdown-input-color: #555;
$dropdown-input-fa-color: #c7c7c7;
-$dropdown-input-focus-border: $focus-border-color;
-$dropdown-input-focus-shadow: rgba($dropdown-input-focus-border, 0.4);
+$dropdown-input-focus-shadow: rgba($blue-300, 0.4);
$dropdown-loading-bg: rgba(#fff, 0.6);
$dropdown-chevron-size: 10px;
$dropdown-toggle-active-border-color: darken($border-color, 14%);
-$dropdown-item-hover-bg: $gray-darker;
$dropdown-fade-mask-height: 32px;
$dropdown-member-form-control-width: 163px;
@@ -405,7 +358,6 @@ $dropdown-member-form-control-width: 163px;
* Filtered Search
*/
$filtered-search-term-shadow-color: rgba(0, 0, 0, 0.09);
-$dropdown-hover-color: $blue-400;
/*
* Contextual Sidebar
@@ -420,7 +372,7 @@ $sidebar-milestone-toggle-bottom-margin: 10px;
* Buttons
*/
$btn-active-gray: #ececec;
-$btn-active-gray-light: e4e7ed;
+$btn-active-gray-light: #e4e7ed;
$btn-white-active: #848484;
$gl-btn-padding: 10px;
$gl-btn-line-height: 16px;
@@ -431,7 +383,6 @@ $gl-btn-horz-padding: 12px;
* Badges
*/
$badge-bg: rgba(0, 0, 0, 0.07);
-$badge-color: $gl-text-color-secondary;
/*
* Pagination
@@ -439,21 +390,12 @@ $badge-color: $gl-text-color-secondary;
$pagination-padding-y: 6px;
$pagination-padding-x: 16px;
$pagination-line-height: 20px;
-$pagination-border-color: $border-color;
-$pagination-active-bg: $blue-600;
-$pagination-active-border-color: $blue-600;
-$pagination-hover-bg: $blue-50;
-$pagination-hover-border-color: $border-color;
-$pagination-hover-color: $gl-text-color;
$pagination-disabled-color: #cdcdcd;
-$pagination-disabled-bg: $gray-light;
-$pagination-disabled-border-color: $border-color;
/*
* Status icons
*/
$status-icon-size: 22px;
-$status-icon-margin: $gl-btn-padding;
/*
* Award emoji
@@ -466,16 +408,13 @@ $award-emoji-positive-add-lines: #bb9c13;
* Search Box
*/
$search-input-border-color: rgba($blue-400, 0.8);
-$search-input-focus-shadow-color: $dropdown-input-focus-shadow;
$search-input-width: 240px;
$search-input-active-width: 320px;
-$location-badge-active-bg: $blue-500;
$location-icon-color: #e7e9ed;
/*
* Notes
*/
-$notes-light-color: $gl-text-color-secondary;
$note-disabled-comment-color: #b2b2b2;
$note-targe3-outside: #fffff0;
$note-targe3-inside: #ffffd3;
@@ -496,7 +435,6 @@ $identicon-indigo: #e8eaf6;
$identicon-blue: #e3f2fd;
$identicon-teal: #e0f2f1;
$identicon-orange: #fbe9e7;
-$identicon-gray: $gray-darker;
$identicon-fg-color: #555555;
/*
@@ -512,7 +450,6 @@ $calendar-user-contrib-text: #959494;
$cycle-analytics-box-padding: 30px;
$cycle-analytics-box-text-color: #8c8c8c;
$cycle-analytics-big-font: 19px;
-$cycle-analytics-dark-text: $gl-text-color;
$cycle-analytics-light-gray: #bfbfbf;
$cycle-analytics-dismiss-icon-color: #b2b2b2;
@@ -540,9 +477,6 @@ $issue-board-list-difference-md: $issue-board-list-difference-sm + $issue-boards
* Avatar
*/
$avatar-radius: 50%;
-$avatar-border: $gray-normal;
-$avatar-border-hover: $gray-darker;
-$avatar-background: $gray-lightest;
$gl-avatar-size: 40px;
/*
@@ -558,22 +492,6 @@ $blame-blue: #254e77;
$builds-trace-bg: #111;
/*
-* Callout
-*/
-$callout-danger-bg: $red-100;
-$callout-danger-border: $red-200;
-$callout-danger-color: $red-700;
-$callout-warning-bg: $orange-100;
-$callout-warning-border: $orange-200;
-$callout-warning-color: $orange-700;
-$callout-info-bg: $blue-100;
-$callout-info-border: $blue-200;
-$callout-info-color: $blue-700;
-$callout-success-bg: $green-100;
-$callout-success-border: $green-200;
-$callout-success-color: $green-700;
-
-/*
* Commit Page
*/
$commit-max-width-marker-color: rgba(0, 0, 0, 0);
@@ -582,16 +500,8 @@ $commit-message-text-area-bg: rgba(0, 0, 0, 0);
/*
* Common
*/
-$common-gray: $gl-text-color;
$common-gray-light: #bbb;
$common-gray-dark: #444;
-$common-red: $gl-text-red;
-$common-green: $gl-text-green;
-
-/*
-* Editor
-*/
-$editor-cancel-color: $red-600;
/*
* Events
@@ -603,11 +513,7 @@ $events-body-border: #ddd;
/*
* Files
*/
-$file-image-bg: #eee;
-$blob-bg: #eee;
-$blame-border: #eee;
$blame-line-numbers-border: #ddd;
-$logs-bg: #eee;
$logs-li-color: #888;
$logs-p-color: #333;
@@ -616,7 +522,6 @@ $logs-p-color: #333;
*/
$input-height: 34px;
$input-danger-bg: #f2dede;
-$input-danger-border: $red-400;
$input-group-addon-bg: #f7f8fa;
$gl-field-focus-shadow: rgba(0, 0, 0, 0.075);
$gl-field-focus-shadow-error: rgba($red-500, 0.6);
@@ -663,22 +568,14 @@ $fade-mask-transition-duration: 0.1s;
$fade-mask-transition-curve: ease-in-out;
/*
-* Lint
-*/
-$lint-incorrect-color: $red-500;
-$lint-correct-color: $green-500;
-
-/*
* Login
*/
$login-brand-holder-color: #888;
-$login-devise-error-color: $red-700;
/*
* Nav
*/
$nav-link-gray: #959494;
-$nav-badge-bg: #eee;
$nav-toggle-gray: #666;
/*
@@ -686,15 +583,12 @@ $nav-toggle-gray: #666;
*/
$notify-details: #777;
$notify-footer: #777;
-$notify-new-file: $green-600;
-$notify-deleted-file: $red-700;
/*
* Projects
*/
$project-option-descr-color: #54565b;
$project-breadcrumb-color: #999;
-$project-private-forks-notice-odd: $green-600;
$project-network-controls-color: #888;
$feature-toggle-color: #fff;
@@ -703,21 +597,10 @@ $feature-toggle-color-disabled: #999;
$feature-toggle-color-enabled: #4a8bee;
/*
-* Runners
-*/
-$runner-state-shared-bg: $green-400;
-$runner-state-specific-bg: $blue-400;
-$runner-status-online-color: $green-600;
-$runner-status-offline-color: $gray-darkest;
-$runner-status-paused-color: $red-500;
-
-/*
Stat Graph
*/
$stat-graph-common-bg: #f3f3f3;
-$stat-graph-area-fill: $green-500;
$stat-graph-axis-fill: #aaa;
-$stat-graph-orange-fill: $orange-500;
$stat-graph-selection-fill: #333;
$stat-graph-selection-stroke: #333;
@@ -730,7 +613,6 @@ $select2-drop-shadow2: rgba(31, 37, 50, 0.317647);
/*
* Todo
*/
-$todo-alert-blue: $blue-500;
$todo-body-pre-color: #777;
$todo-body-border: #ddd;
@@ -753,7 +635,6 @@ $ui-dev-kit-example-border: #ddd;
/*
Pipeline Graph
*/
-$stage-hover-bg: $gray-darker;
$ci-action-icon-size: 22px;
$ci-action-icon-size-lg: 24px;
$pipeline-dropdown-line-height: 20px;
@@ -781,13 +662,6 @@ Animation Functions
$dropdown-animation-timing: cubic-bezier(0.23, 1, 0.32, 1);
/*
-Convdev Index
-*/
-$color-high-score: $green-400;
-$color-average-score: $orange-400;
-$color-low-score: $red-400;
-
-/*
Performance Bar
*/
$perf-bar-text: #999;
@@ -828,25 +702,5 @@ Modals
*/
$modal-body-height: 134px;
-/*
-Prometheus
-*/
-$prometheus-table-row-highlight-color: $theme-gray-100;
$priority-label-empty-state-width: 114px;
-
-/*
- * Override Bootstrap 4 variables
- */
-
-$secondary: $gray-light;
-$input-disabled-bg: $gray-light;
-$input-border-color: $theme-gray-200;
-$input-color: $gl-text-color;
-$font-family-sans-serif: $regular-font;
-$font-family-monospace: $monospace-font;
-$input-line-height: 20px;
-$btn-line-height: 20px;
-$table-accent-bg: $gray-light;
-$card-border-color: $border-color;
-$card-cap-bg: $gray-light;
diff --git a/app/assets/stylesheets/framework/variables_overrides.scss b/app/assets/stylesheets/framework/variables_overrides.scss
new file mode 100644
index 00000000000..7d90452e1f4
--- /dev/null
+++ b/app/assets/stylesheets/framework/variables_overrides.scss
@@ -0,0 +1,20 @@
+/*
+ * This file is only for overriding Bootstrap 4 variables.
+ * Please add any new variables to variables.scss
+ */
+
+$secondary: $gray-light;
+$input-disabled-bg: $gray-light;
+$input-border-color: $theme-gray-200;
+$input-color: $gl-text-color;
+$font-family-sans-serif: $regular-font;
+$font-family-monospace: $monospace-font;
+$input-line-height: 20px;
+$btn-line-height: 20px;
+$table-accent-bg: $gray-light;
+$card-border-color: $border-color;
+$card-cap-bg: $gray-light;
+$success: $green-500;
+$info: $blue-500;
+$warning: $orange-500;
+$danger: $red-500;
diff --git a/app/assets/stylesheets/framework/zen.scss b/app/assets/stylesheets/framework/zen.scss
index dbd3144b9b4..f2d296fb875 100644
--- a/app/assets/stylesheets/framework/zen.scss
+++ b/app/assets/stylesheets/framework/zen.scss
@@ -44,7 +44,7 @@
color: $gl-text-color-secondary;
&:hover {
- color: $gl-link-color;
+ color: $blue-600;
text-decoration: none;
}
}
diff --git a/app/assets/stylesheets/page_bundles/ide.scss b/app/assets/stylesheets/page_bundles/ide.scss
index 2b8163b8c68..eac1345742d 100644
--- a/app/assets/stylesheets/page_bundles/ide.scss
+++ b/app/assets/stylesheets/page_bundles/ide.scss
@@ -1158,7 +1158,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
}
a {
- color: $gl-link-color;
+ color: $blue-600;
}
}
@@ -1293,6 +1293,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
&.build-page .top-bar {
top: 0;
+ height: auto;
font-size: 12px;
border-top-right-radius: $border-radius-default;
}
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index a68b47b1d02..69d7de886b4 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -225,7 +225,7 @@
outline: 0;
&:hover {
- color: $gl-link-color;
+ color: $blue-600;
}
}
@@ -288,7 +288,7 @@
&.is-active,
&.is-active .board-card-assignee:hover a {
- background-color: $row-hover;
+ background-color: $blue-50;
}
.badge {
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index e8158cd7f6b..14ba8b1df83 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -221,7 +221,7 @@
color: $gl-text-color;
&:hover {
- color: $gl-link-color;
+ color: $blue-600;
text-decoration: none;
}
}
@@ -397,7 +397,7 @@
}
&:hover {
- background-color: $dropdown-item-hover-bg;
+ background-color: $gray-darker;
}
.icon-retry {
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index bce83bf0dd0..10764e0f3df 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -279,7 +279,7 @@
}
&.autodevops-link {
- color: $gl-link-color;
+ color: $blue-600;
}
}
@@ -321,7 +321,7 @@
}
.commit-sha {
- color: $gl-link-color;
+ color: $blue-600;
}
.commit-row-message {
diff --git a/app/assets/stylesheets/pages/convdev_index.scss b/app/assets/stylesheets/pages/convdev_index.scss
index bd338326154..52fcdf4a405 100644
--- a/app/assets/stylesheets/pages/convdev_index.scss
+++ b/app/assets/stylesheets/pages/convdev_index.scss
@@ -80,7 +80,7 @@ $space-between-cards: 8px;
}
.convdev-card-low {
- border-top-color: $color-low-score;
+ border-top-color: $red-400;
.board-card-score-big {
background-color: $red-50;
@@ -88,7 +88,7 @@ $space-between-cards: 8px;
}
.convdev-card-average {
- border-top-color: $color-average-score;
+ border-top-color: $orange-400;
.board-card-score-big {
background-color: $orange-50;
@@ -96,7 +96,7 @@ $space-between-cards: 8px;
}
.convdev-card-high {
- border-top-color: $color-high-score;
+ border-top-color: $green-400;
.board-card-score-big {
background-color: $green-50;
@@ -243,13 +243,13 @@ $space-between-cards: 8px;
}
.convdev-high-score {
- color: $color-high-score;
+ color: $green-400;
}
.convdev-average-score {
- color: $color-average-score;
+ color: $orange-400;
}
.convdev-low-score {
- color: $color-low-score;
+ color: $red-400;
}
diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss
index e2c0a7a6225..f0228768b5a 100644
--- a/app/assets/stylesheets/pages/cycle_analytics.scss
+++ b/app/assets/stylesheets/pages/cycle_analytics.scss
@@ -165,7 +165,7 @@
border-right-color: transparent;
border-top-color: $border-color;
border-bottom-color: $border-color;
- box-shadow: inset 2px 0 0 0 $active-item-blue;
+ box-shadow: inset 2px 0 0 0 $blue-500;
.stage-name {
font-weight: $gl-font-weight-bold;
@@ -285,7 +285,7 @@
.total-time {
font-size: $cycle-analytics-big-font;
- color: $cycle-analytics-dark-text;
+ color: $gl-text-color;
span {
color: $gl-text-color;
@@ -360,7 +360,7 @@
}
.commit-sha {
- color: $gl-link-color;
+ color: $blue-600;
line-height: 1.3;
vertical-align: top;
font-weight: $gl-font-weight-normal;
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 591e21243ed..a999a70693e 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -186,7 +186,7 @@
}
.image {
- background: $file-image-bg;
+ background: $gray-darker;
text-align: center;
padding: 30px;
@@ -511,13 +511,13 @@
padding: 0;
background-color: transparent;
border: 0;
- color: $gl-link-color;
+ color: $blue-600;
font-weight: $gl-font-weight-bold;
&:hover,
&:focus {
outline: none;
- color: $gl-link-hover-color;
+ color: $blue-800;
}
.caret-icon {
diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss
index ddd1f8cc98a..04570c057d1 100644
--- a/app/assets/stylesheets/pages/editor.scss
+++ b/app/assets/stylesheets/pages/editor.scss
@@ -23,10 +23,10 @@
}
.cancel-btn {
- color: $editor-cancel-color;
+ color: $red-600;
&:hover {
- color: $editor-cancel-color;
+ color: $red-600;
}
}
@@ -36,10 +36,7 @@
line-height: 35px;
padding-top: 7px;
padding-bottom: 7px;
-
- .float-right {
- height: 20px;
- }
+ display: flex;
}
.editor-ref {
@@ -60,20 +57,18 @@
.new-file-name {
display: inline-block;
- max-width: 450px;
+ max-width: 420px;
float: left;
@media(max-width: map-get($grid-breakpoints, lg)-1) {
- width: 280px;
- }
-
- @media(max-width: map-get($grid-breakpoints, md)-1) {
width: 180px;
}
}
.file-buttons {
- font-size: 0;
+ display: flex;
+ flex: 1;
+ justify-content: flex-end;
}
.select2 {
@@ -111,12 +106,10 @@
}
-@include media-breakpoint-down(xs) {
+@include media-breakpoint-down(sm) {
.file-editor {
.file-title {
- .float-right {
- height: auto;
- }
+ display: block;
}
.new-file-name {
@@ -144,6 +137,10 @@
}
}
}
+
+ .editor-ref {
+ max-width: 250px;
+ }
}
}
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index 8a074017344..196f6ae6d8c 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -479,10 +479,10 @@
.deploy-info-text-link {
font-family: $monospace-font;
- fill: $gl-link-color;
+ fill: $blue-600;
&:hover {
- fill: $gl-link-hover-color;
+ fill: $blue-800;
}
}
@@ -501,5 +501,5 @@
}
.prometheus-table-row-highlight {
- background-color: $prometheus-table-row-highlight-color;
+ background-color: $theme-gray-100;
}
diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss
index f79586b68b9..da0c9b44498 100644
--- a/app/assets/stylesheets/pages/events.scss
+++ b/app/assets/stylesheets/pages/events.scss
@@ -6,7 +6,7 @@
font-size: $gl-font-size;
padding: $gl-padding-top 0 $gl-padding-top 40px;
border-bottom: 1px solid $white-normal;
- color: $list-text-color;
+ color: $gl-text-color;
position: relative;
&.event-inline {
@@ -58,7 +58,7 @@
.event-title {
@include str-truncated(calc(100% - 174px));
font-weight: $gl-font-weight-bold;
- color: $list-text-color;
+ color: $gl-text-color;
}
.event-body {
diff --git a/app/assets/stylesheets/pages/graph.scss b/app/assets/stylesheets/pages/graph.scss
index 49d8a5d959b..22fce893fd7 100644
--- a/app/assets/stylesheets/pages/graph.scss
+++ b/app/assets/stylesheets/pages/graph.scss
@@ -24,11 +24,11 @@
}
.graph-additions {
- color: $gl-text-green;
+ color: $green-600;
}
.graph-deletions {
- color: $gl-text-red;
+ color: $red-500;
}
}
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index 1587aebfe1d..60b4d39bb1a 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -385,8 +385,8 @@
padding: $gl-padding-top;
&:hover {
- border-color: $row-hover-border;
- background-color: $row-hover;
+ border-color: $blue-200;
+ background-color: $blue-50;
cursor: pointer;
}
@@ -419,6 +419,7 @@
.stats {
position: relative;
line-height: normal;
+ text-align: right;
flex-shrink: 0;
> span {
@@ -464,7 +465,7 @@
}
.last-updated {
- position: absolute;
+ position: relative;
right: 12px;
min-width: 250px;
text-align: right;
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 8e78d9f65eb..9ac47a771a5 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -141,10 +141,10 @@
color: inherit;
&:hover {
- color: $gl-link-hover-color;
+ color: $blue-800;
.avatar {
- border-color: rgba($avatar-border, .2);
+ border-color: rgba($gray-normal, .2);
}
}
@@ -231,7 +231,7 @@
}
a.edit-link:not([href]):hover {
- color: rgba($avatar-border, .2);
+ color: rgba($gray-normal, .2);
}
.lock-edit, // uses same style, different js behaviour
@@ -241,7 +241,7 @@
&:hover {
text-decoration: underline;
- color: $gl-link-hover-color;
+ color: $blue-800;
}
}
}
@@ -329,7 +329,7 @@
}
.btn-secondary-hover-link:hover {
- color: $gl-link-color;
+ color: $blue-600;
}
.sidebar-collapsed-icon {
@@ -423,10 +423,10 @@
width: 100%;
text-align: center;
margin-bottom: 10px;
- color: $issuable-sidebar-color;
+ color: $gl-text-color-secondary;
svg {
- fill: $issuable-sidebar-color;
+ fill: $gl-text-color-secondary;
}
&:hover:not(.disabled),
@@ -448,8 +448,8 @@
}
.todo-undone {
- color: $gl-link-color;
- fill: $gl-link-color;
+ color: $blue-600;
+ fill: $blue-600;
}
.author {
@@ -457,14 +457,14 @@
}
.avatar-counter:hover {
- color: $issuable-sidebar-color;
- border-color: $issuable-sidebar-color;
+ color: $gl-text-color-secondary;
+ border-color: $gl-text-color-secondary;
}
.btn-clipboard {
border: 0;
background: transparent;
- color: $issuable-sidebar-color;
+ color: $gl-text-color-secondary;
&:hover {
color: $gl-text-color;
@@ -821,7 +821,7 @@
svg {
width: 16px;
height: 16px;
- fill: $issuable-sidebar-color;
+ fill: $gl-text-color-secondary;
}
&:hover svg {
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 212e5979273..0f95fb911e1 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -157,7 +157,7 @@ ul.related-merge-requests > li {
.issuable-email-modal-btn {
padding: 0;
- color: $gl-link-color;
+ color: $blue-600;
background-color: transparent;
border: 0;
outline: 0;
@@ -190,7 +190,7 @@ ul.related-merge-requests > li {
.create-mr-dropdown-wrap {
.ref::selection {
- color: $placeholder-text-color;
+ color: $gl-text-color-tertiary;
}
.dropdown {
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index 2b40404971c..d2b9470be69 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -67,7 +67,7 @@
.dropdown-labels-error {
padding: 5px 10px;
margin-bottom: 10px;
- background-color: $gl-danger;
+ background-color: $red-500;
color: $white-light;
}
@@ -114,10 +114,10 @@
}
&:hover {
- color: $gl-link-color;
+ color: $blue-600;
&.remove-row {
- color: $gl-danger;
+ color: $red-500;
}
}
}
@@ -253,7 +253,7 @@
text-align: right;
padding: 0;
position: relative;
- top: -3px;
+ margin: 0;
}
.label-badge {
@@ -274,6 +274,7 @@
.label-links {
list-style: none;
+ margin: 0;
padding: 0;
white-space: nowrap;
}
@@ -342,10 +343,10 @@
&.remove-row {
&:hover {
- color: $gl-text-red;
+ color: $red-500;
svg {
- fill: $gl-text-red;
+ fill: $red-500;
}
}
}
diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss
index 8a4a2caa6c9..c9e5fb9c579 100644
--- a/app/assets/stylesheets/pages/login.scss
+++ b/app/assets/stylesheets/pages/login.scss
@@ -186,7 +186,7 @@
h2 {
margin-top: 0;
font-size: 14px;
- color: $login-devise-error-color;
+ color: $red-700;
}
}
}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 621321101cd..5631d943984 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -200,7 +200,7 @@
.mr-widget-icon {
font-size: 22px;
- margin-right: $status-icon-margin;
+ margin-right: $gl-btn-padding;
}
.ci-status-icon svg {
@@ -281,7 +281,7 @@
margin-bottom: 0;
&.has-conflicts .fa-exclamation-triangle {
- color: $gl-warning;
+ color: $orange-500;
}
time {
@@ -313,7 +313,7 @@
}
.danger {
- color: $gl-danger;
+ color: $red-500;
}
.spacing,
@@ -514,7 +514,7 @@
}
.mr-links {
- padding-left: $status-icon-size + $status-icon-margin;
+ padding-left: $status-icon-size + $gl-btn-padding;
}
.mr-info-list {
diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss
index 46437ce5841..1e92582d6d9 100644
--- a/app/assets/stylesheets/pages/milestone.scss
+++ b/app/assets/stylesheets/pages/milestone.scss
@@ -30,7 +30,7 @@
.milestone-progress {
a {
- color: $gl-link-color;
+ color: $blue-600;
}
}
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index dcf590e7331..ac7b701c2e2 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -69,18 +69,18 @@
.comment-toolbar,
.nav-links {
- border-color: $focus-border-color;
+ border-color: $blue-300;
}
}
&.is-dropzone-hover {
- border-color: $gl-success;
+ border-color: $green-500;
box-shadow: 0 0 2px $black-transparent,
- 0 0 4px $gl-success-focus;
+ 0 0 4px $green-500-focus;
.comment-toolbar,
.nav-links {
- border-color: $gl-success;
+ border-color: $green-500;
}
}
}
@@ -306,7 +306,7 @@
&:hover,
&:focus {
- color: $gl-link-color;
+ color: $blue-600;
outline: 0;
}
@@ -424,7 +424,7 @@
.uploading-error-icon,
.uploading-error-message {
- color: $gl-text-red;
+ color: $red-500;
}
.uploading-error-message {
@@ -443,7 +443,7 @@
.attach-new-file,
.button-attach-file,
.retry-uploading-link {
- color: $gl-link-color;
+ color: $blue-600;
padding: 0;
background: none;
border: 0;
@@ -452,5 +452,5 @@
}
.markdown-selector {
- color: $gl-link-color;
+ color: $blue-600;
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index c369d89d63c..fce04c58c24 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -141,9 +141,6 @@ ul.notes {
}
.note-body {
- overflow-x: auto;
- overflow-y: hidden;
-
.note-text {
@include md-typography;
// Reset ul style types since we're nested inside a ul already
@@ -210,7 +207,7 @@ ul.notes {
}
a {
- color: $gl-link-color;
+ color: $blue-600;
}
p {
@@ -253,14 +250,14 @@ ul.notes {
overflow: hidden;
.system-note-commit-list-toggler {
- color: $gl-link-color;
+ color: $blue-600;
padding: 10px 0 0;
cursor: pointer;
position: relative;
z-index: 2;
&:hover {
- color: $gl-link-color;
+ color: $blue-600;
text-decoration: underline;
}
}
@@ -390,7 +387,7 @@ ul.notes {
color: inherit;
&:hover {
- color: $gl-link-color;
+ color: $blue-600;
}
&:focus,
@@ -446,12 +443,12 @@ ul.notes {
.note-headline-light,
.discussion-headline-light {
- color: $notes-light-color;
+ color: $gl-text-color-secondary;
}
.discussion-headline-light {
a {
- color: $gl-link-color;
+ color: $blue-600;
}
}
@@ -560,12 +557,12 @@ ul.notes {
&:hover,
&.is-active {
.danger-highlight {
- color: $gl-text-red;
+ color: $red-500;
}
.link-highlight {
- color: $gl-link-color;
- fill: $gl-link-color;
+ color: $blue-600;
+ fill: $blue-600;
}
.award-control-icon-neutral {
@@ -597,13 +594,13 @@ ul.notes {
transition: color 0.1s linear;
&:hover {
- color: $gl-link-color;
+ color: $blue-600;
}
&:focus {
text-decoration: underline;
outline: none;
- color: $gl-link-color;
+ color: $blue-600;
}
.fa {
@@ -673,7 +670,7 @@ ul.notes {
}
a {
- color: $gl-link-color;
+ color: $blue-600;
}
}
@@ -759,16 +756,16 @@ ul.notes {
&:not(.is-disabled) {
&:hover,
&:focus {
- color: $gl-text-green;
+ color: $green-600;
}
}
&.is-active {
- color: $gl-text-green;
+ color: $green-600;
&:hover,
&:focus {
- color: $gl-text-green-hover;
+ color: $green-700;
}
}
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index b68c89c25d8..8bb8b83dc5e 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -19,7 +19,7 @@
background-color: $white-light;
&:hover {
- background-color: $stage-hover-bg;
+ background-color: $gray-darker;
border: 1px solid $dropdown-toggle-active-border-color;
color: $gl-text-color;
}
@@ -175,7 +175,7 @@
}
.commit-sha {
- color: $gl-link-color;
+ color: $blue-600;
}
.badge {
@@ -595,7 +595,7 @@
a.build-content:hover,
button.build-content:hover {
- background-color: $stage-hover-bg;
+ background-color: $gray-darker;
border: 1px solid $dropdown-toggle-active-border-color;
}
@@ -668,7 +668,7 @@
display: block;
&:hover {
- background-color: $stage-hover-bg;
+ background-color: $gray-darker;
border: 1px solid $dropdown-toggle-active-border-color;
svg {
@@ -835,7 +835,7 @@ button.mini-pipeline-graph-dropdown-toggle {
display: block;
&:hover {
- background-color: $stage-hover-bg;
+ background-color: $gray-darker;
border: 1px solid $dropdown-toggle-active-border-color;
svg {
@@ -934,7 +934,7 @@ button.mini-pipeline-graph-dropdown-toggle {
&:focus {
outline: none;
text-decoration: none;
- background-color: $stage-hover-bg;
+ background-color: $gray-darker;
}
}
}
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index b45e305897c..17f34319050 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -20,7 +20,7 @@
.account-btn-link,
.profile-settings-sidebar a,
.settings-sidebar a {
- color: $md-link-color;
+ color: $blue-600;
}
.private-tokens-reset div.reset-action:not(:first-child) {
@@ -137,7 +137,7 @@
.profile-settings-content {
a {
- color: $md-link-color;
+ color: $blue-600;
}
}
@@ -170,7 +170,7 @@
background-color: $gray-light;
&.not-active {
- color: $provider-btn-not-active-color;
+ color: $blue-500;
}
}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 944421604fe..a95e78931b1 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -388,29 +388,29 @@
line-height: $gl-btn-line-height;
&:hover {
- color: $gl-link-color;
+ color: $blue-600;
}
}
}
.vs-public {
- color: $gl-primary;
+ color: $blue-500;
}
.vs-internal {
- color: $gl-warning;
+ color: $orange-500;
}
.vs-private {
- color: $gl-success;
+ color: $green-500;
}
.lfs-enabled {
- color: $gl-success;
+ color: $green-500;
}
.lfs-disabled {
- color: $gl-warning;
+ color: $orange-500;
}
.breadcrumb.repo-breadcrumb {
@@ -449,8 +449,8 @@
&:hover:not(.disabled),
&.forked {
- background-color: $row-hover;
- border-color: $row-hover-border;
+ background-color: $blue-50;
+ border-color: $blue-200;
}
.avatar-container,
@@ -731,7 +731,7 @@
background-color: transparent;
font-size: $gl-font-size;
line-height: $gl-btn-line-height;
- color: $notes-light-color;
+ color: $gl-text-color-secondary;
}
.stat-link {
@@ -754,8 +754,19 @@
}
.repository-languages-bar {
- height: 6px;
- margin-bottom: 8px;
+ height: 8px;
+ margin-bottom: $gl-padding-8;
+ background-color: $white-light;
+ border-radius: $border-radius-default;
+
+ .progress-bar {
+ margin-right: 2px;
+ padding: 0 $gl-padding-4;
+
+ &:last-child {
+ margin-right: 0;
+ }
+ }
}
pre.light-well {
@@ -823,10 +834,6 @@ pre.light-well {
.avatar-container {
align-self: flex-start;
-
- > a {
- width: 100%;
- }
}
.project-details {
@@ -887,13 +894,13 @@ pre.light-well {
.cannot-be-merged,
.cannot-be-merged:hover {
- color: $error-exclamation-point;
+ color: $red-500;
margin-top: 2px;
}
.private-forks-notice .private-fork-icon {
i:nth-child(1) {
- color: $project-private-forks-notice-odd;
+ color: $green-600;
}
i:nth-child(2) {
@@ -954,7 +961,7 @@ pre.light-well {
margin-left: 5px;
&.is-done {
- color: $gl-text-green;
+ color: $green-600;
}
}
@@ -1121,12 +1128,12 @@ pre.light-well {
.project-ci-body {
.incorrect-syntax {
font-size: 18px;
- color: $lint-incorrect-color;
+ color: $red-500;
}
.correct-syntax {
font-size: 18px;
- color: $lint-correct-color;
+ color: $green-500;
}
}
diff --git a/app/assets/stylesheets/pages/runners.scss b/app/assets/stylesheets/pages/runners.scss
index 2734faec558..59f01f3e958 100644
--- a/app/assets/stylesheets/pages/runners.scss
+++ b/app/assets/stylesheets/pages/runners.scss
@@ -4,24 +4,24 @@
color: $white-light;
&.runner-state-shared {
- background: $runner-state-shared-bg;
+ background: $green-400;
}
&.runner-state-specific {
- background: $runner-state-specific-bg;
+ background: $blue-400;
}
}
.runner-status-online {
- color: $runner-status-online-color;
+ color: $green-600;
}
.runner-status-offline {
- color: $runner-status-offline-color;
+ color: $gray-darkest;
}
.runner-status-paused {
- color: $runner-status-paused-color;
+ color: $red-500;
}
.runner {
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index 60b280fd12e..77119aea9e2 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -23,13 +23,13 @@ $search-avatar-size: 16px;
.search-text-input:hover,
.form-control:hover,
:not[readonly] {
- border-color: lighten($dropdown-input-focus-border, 20%);
- box-shadow: 0 0 4px lighten($search-input-focus-shadow-color, 20%);
+ border-color: lighten($blue-300, 20%);
+ box-shadow: 0 0 4px lighten($dropdown-input-focus-shadow, 20%);
}
input[type='checkbox']:hover {
- box-shadow: 0 0 2px 2px lighten($search-input-focus-shadow-color, 20%),
- 0 0 0 1px lighten($search-input-focus-shadow-color, 20%);
+ box-shadow: 0 0 2px 2px lighten($dropdown-input-focus-shadow, 20%),
+ 0 0 0 1px lighten($dropdown-input-focus-shadow, 20%);
}
.search {
@@ -127,7 +127,7 @@ input[type='checkbox']:hover {
&.search-active {
form {
@extend .form-control:focus;
- border-color: $dropdown-input-focus-border;
+ border-color: $blue-300;
box-shadow: none;
@include media-breakpoint-up(xl) {
@@ -181,7 +181,7 @@ input[type='checkbox']:hover {
width: $search-avatar-size;
height: $search-avatar-size;
border-radius: 50%;
- border: 1px solid $avatar-border;
+ border: 1px solid $gray-normal;
}
}
@@ -259,6 +259,6 @@ input[type='checkbox']:hover {
&:hover,
&:focus {
- color: $gl-link-color;
+ color: $blue-600;
}
}
diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss
index 839ac5ba59b..e351dd7c0bb 100644
--- a/app/assets/stylesheets/pages/settings.scss
+++ b/app/assets/stylesheets/pages/settings.scss
@@ -113,18 +113,18 @@
.settings-message {
padding: 5px;
line-height: 1.3;
- color: $warning-message-color;
- background-color: $warning-message-bg;
- border: 1px solid $warning-message-border;
+ color: $orange-700;
+ background-color: $orange-100;
+ border: 1px solid $orange-200;
border-radius: $border-radius-base;
}
.warning-title {
- color: $gl-warning;
+ color: $orange-500;
}
.danger-title {
- color: $gl-danger;
+ color: $red-500;
}
.integration-settings-form {
@@ -301,3 +301,17 @@
margin-bottom: 0;
}
}
+
+.mirror-error-badge {
+ background-color: $red-400;
+ border-radius: $border-radius-default;
+ color: $white-light;
+}
+
+.push-pull-table {
+ margin-top: 1em;
+
+ .mirror-action-buttons {
+ padding-right: 0;
+ }
+}
diff --git a/app/assets/stylesheets/pages/stat_graph.scss b/app/assets/stylesheets/pages/stat_graph.scss
index 3f6f5f06075..d331edaa302 100644
--- a/app/assets/stylesheets/pages/stat_graph.scss
+++ b/app/assets/stylesheets/pages/stat_graph.scss
@@ -5,7 +5,7 @@
}
.area {
- fill: $stat-graph-area-fill;
+ fill: $green-500;
fill-opacity: 0.5;
}
@@ -54,7 +54,7 @@
}
.area-contributor {
- fill: $stat-graph-orange-fill;
+ fill: $orange-500;
}
}
}
diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss
index 010a2c05a1c..5d3b7b21ce4 100644
--- a/app/assets/stylesheets/pages/todos.scss
+++ b/app/assets/stylesheets/pages/todos.scss
@@ -12,8 +12,8 @@
flex-direction: row;
&:hover {
- background-color: $row-hover;
- border-color: $row-hover-border;
+ background-color: $blue-50;
+ border-color: $blue-200;
cursor: pointer;
}
@@ -22,7 +22,7 @@
border-bottom: 1px solid transparent;
&:hover {
- border-color: $row-hover-border;
+ border-color: $blue-200;
}
}
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index 1cc26d40ba9..dc5ca78ff58 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -111,9 +111,9 @@
&:hover:not(.tree-truncated-warning) {
td {
- background-color: $row-hover;
- border-top: 1px solid $row-hover-border;
- border-bottom: 1px solid $row-hover-border;
+ background-color: $blue-50;
+ border-top: 1px solid $blue-200;
+ border-bottom: 1px solid $blue-200;
cursor: pointer;
}
}
@@ -229,7 +229,7 @@
.upload-link {
font-weight: $gl-font-weight-normal;
- color: $md-link-color;
+ color: $blue-600;
}
.repo-charts {
diff --git a/app/assets/stylesheets/performance_bar.scss b/app/assets/stylesheets/performance_bar.scss
index 7a93c4dfa28..57d43beaf21 100644
--- a/app/assets/stylesheets/performance_bar.scss
+++ b/app/assets/stylesheets/performance_bar.scss
@@ -6,7 +6,7 @@
left: 0;
top: 0;
width: 100%;
- z-index: 2000;
+ z-index: 1039;
height: $performance-bar-height;
background: $black;
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 05ed3669a41..e5b38898a67 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -11,7 +11,6 @@ class ApplicationController < ActionController::Base
include EnforcesTwoFactorAuthentication
include WithPerformanceBar
- before_action :limit_unauthenticated_session_times
before_action :authenticate_sessionless_user!
before_action :authenticate_user!
before_action :enforce_terms!, if: :should_enforce_terms?
@@ -27,6 +26,7 @@ class ApplicationController < ActionController::Base
around_action :set_locale
after_action :set_page_title_header, if: :json_request?
+ after_action :limit_unauthenticated_session_times
protect_from_forgery with: :exception, prepend: true
diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb
index 86bade49ec9..9e30b982b06 100644
--- a/app/controllers/autocomplete_controller.rb
+++ b/app/controllers/autocomplete_controller.rb
@@ -1,67 +1,39 @@
class AutocompleteController < ApplicationController
- AWARD_EMOJI_MAX = 100
-
skip_before_action :authenticate_user!, only: [:users, :award_emojis]
- before_action :load_project, only: [:users]
- before_action :load_group, only: [:users]
def users
- @users = AutocompleteUsersFinder.new(params: params, current_user: current_user, project: @project, group: @group).execute
-
- render json: UserSerializer.new.represent(@users)
- end
-
- def user
- @user = User.find(params[:id])
- render json: UserSerializer.new.represent(@user)
- end
-
- def projects
- project = Project.find_by_id(params[:project_id])
- projects = projects_finder.execute(project, search: params[:search], offset_id: params[:offset_id])
+ project = Autocomplete::ProjectFinder
+ .new(current_user, params)
+ .execute
- render json: projects.to_json(only: [:id, :name_with_namespace], methods: :name_with_namespace)
- end
+ group = Autocomplete::GroupFinder
+ .new(current_user, project, params)
+ .execute
- def award_emojis
- emoji_with_count = AwardEmoji
- .limit(AWARD_EMOJI_MAX)
- .where(user: current_user)
- .group(:name)
- .order('count_all DESC, name ASC')
- .count
+ users = Autocomplete::UsersFinder
+ .new(params: params, current_user: current_user, project: project, group: group)
+ .execute
- # Transform from hash to array to guarantee json order
- # e.g. { 'thumbsup' => 2, 'thumbsdown' = 1 }
- # => [{ name: 'thumbsup' }, { name: 'thumbsdown' }]
- render json: emoji_with_count.map { |k, v| { name: k } }
+ render json: UserSerializer.new.represent(users)
end
- private
-
- def load_group
- @group ||= begin
- if @project.blank? && params[:group_id].present?
- group = Group.find(params[:group_id])
- return render_404 unless can?(current_user, :read_group, group)
+ def user
+ user = UserFinder.new(params).execute!
- group
- end
- end
+ render json: UserSerializer.new.represent(user)
end
- def load_project
- @project ||= begin
- if params[:project_id].present?
- project = Project.find(params[:project_id])
- return render_404 unless can?(current_user, :read_project, project)
+ # Displays projects to use for the dropdown when moving a resource from one
+ # project to another.
+ def projects
+ projects = Autocomplete::MoveToProjectFinder
+ .new(current_user, params)
+ .execute
- project
- end
- end
+ render json: MoveToProjectSerializer.new.represent(projects)
end
- def projects_finder
- MoveToProjectFinder.new(current_user)
+ def award_emojis
+ render json: AwardedEmojiFinder.new(current_user).execute
end
end
diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb
index 2ef2ee76855..22b39f47bf0 100644
--- a/app/controllers/concerns/issuable_collections.rb
+++ b/app/controllers/concerns/issuable_collections.rb
@@ -107,11 +107,15 @@ module IssuableCollections
end
def set_sort_order_from_cookie
- key = 'issuable_sort'
+ cookies[remember_sorting_key] = params[:sort] if params[:sort].present?
+ # fallback to legacy cookie value for backward compatibility
+ cookies[remember_sorting_key] ||= cookies['issuable_sort']
+ cookies[remember_sorting_key] = update_cookie_value(cookies[remember_sorting_key])
+ params[:sort] = cookies[remember_sorting_key]
+ end
- cookies[key] = params[:sort] if params[:sort].present?
- cookies[key] = update_cookie_value(cookies[key])
- params[:sort] = cookies[key]
+ def remember_sorting_key
+ @remember_sorting_key ||= "#{collection_type.downcase}_sort"
end
def default_sort_order
@@ -140,16 +144,14 @@ module IssuableCollections
end
def finder
- strong_memoize(:finder) do
- issuable_finder_for(finder_type)
- end
+ @finder ||= issuable_finder_for(finder_type)
end
def collection_type
- @collection_type ||= case finder
- when IssuesFinder
+ @collection_type ||= case finder_type.name
+ when 'IssuesFinder'
'Issue'
- when MergeRequestsFinder
+ when 'MergeRequestsFinder'
'MergeRequest'
end
end
diff --git a/app/controllers/concerns/renders_commits.rb b/app/controllers/concerns/renders_commits.rb
index fb41dc1e8a8..b1c9b1e532f 100644
--- a/app/controllers/concerns/renders_commits.rb
+++ b/app/controllers/concerns/renders_commits.rb
@@ -1,4 +1,24 @@
module RendersCommits
+ def limited_commits(commits)
+ if commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
+ [
+ commits.first(MergeRequestDiff::COMMITS_SAFE_SIZE),
+ commits.size - MergeRequestDiff::COMMITS_SAFE_SIZE
+ ]
+ else
+ [commits, 0]
+ end
+ end
+
+ # This is used as a helper method in a controller.
+ # rubocop: disable Gitlab/ModuleWithInstanceVariables
+ def set_commits_for_rendering(commits)
+ @total_commit_count = commits.size
+ limited, @hidden_commit_count = limited_commits(commits)
+ prepare_commits_for_rendering(limited)
+ end
+ # rubocop: enable Gitlab/ModuleWithInstanceVariables
+
def prepare_commits_for_rendering(commits)
Banzai::CommitRenderer.render(commits, @project, current_user) # rubocop:disable Gitlab/ModuleWithInstanceVariables
diff --git a/app/controllers/concerns/toggle_award_emoji.rb b/app/controllers/concerns/toggle_award_emoji.rb
index ba5b7d33f87..ae0b815f85e 100644
--- a/app/controllers/concerns/toggle_award_emoji.rb
+++ b/app/controllers/concerns/toggle_award_emoji.rb
@@ -5,7 +5,7 @@ module ToggleAwardEmoji
authenticate_user!
name = params.require(:name)
- if awardable.user_can_award?(current_user, name)
+ if awardable.user_can_award?(current_user)
awardable.toggle_award_emoji(name, current_user)
todoable = to_todoable(awardable)
diff --git a/app/controllers/notification_settings_controller.rb b/app/controllers/notification_settings_controller.rb
index ed20302487c..461f26561f1 100644
--- a/app/controllers/notification_settings_controller.rb
+++ b/app/controllers/notification_settings_controller.rb
@@ -5,14 +5,14 @@ class NotificationSettingsController < ApplicationController
return render_404 unless can_read?(resource)
@notification_setting = current_user.notification_settings_for(resource)
- @saved = @notification_setting.update(notification_setting_params)
+ @saved = @notification_setting.update(notification_setting_params_for(resource))
render_response
end
def update
@notification_setting = current_user.notification_settings.find(params[:id])
- @saved = @notification_setting.update(notification_setting_params)
+ @saved = @notification_setting.update(notification_setting_params_for(@notification_setting.source))
render_response
end
@@ -42,8 +42,8 @@ class NotificationSettingsController < ApplicationController
}
end
- def notification_setting_params
- allowed_fields = NotificationSetting::EMAIL_EVENTS.dup
+ def notification_setting_params_for(source)
+ allowed_fields = NotificationSetting.email_events(source).dup
allowed_fields << :level
params.require(:notification_setting).permit(allowed_fields)
end
diff --git a/app/controllers/projects/autocomplete_sources_controller.rb b/app/controllers/projects/autocomplete_sources_controller.rb
index 07627ffb69f..a8f73ed5cb0 100644
--- a/app/controllers/projects/autocomplete_sources_controller.rb
+++ b/app/controllers/projects/autocomplete_sources_controller.rb
@@ -32,13 +32,8 @@ class Projects::AutocompleteSourcesController < Projects::ApplicationController
end
def target
- case params[:type]&.downcase
- when 'issue'
- IssuesFinder.new(current_user, project_id: @project.id).find_by(iid: params[:type_id])
- when 'mergerequest'
- MergeRequestsFinder.new(current_user, project_id: @project.id).find_by(iid: params[:type_id])
- when 'commit'
- @project.commit(params[:type_id])
- end
+ QuickActions::TargetService
+ .new(project, current_user)
+ .execute(params[:type], params[:type_id])
end
end
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 44b176d304e..53637780a07 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -101,7 +101,7 @@ class Projects::CommitController < Projects::ApplicationController
@branch_name = create_new_branch? ? @commit.cherry_pick_branch_name : @start_branch
- create_commit(Commits::CherryPickService, success_notice: "The #{@commit.change_type_title(current_user)} has been successfully cherry-picked.",
+ create_commit(Commits::CherryPickService, success_notice: "The #{@commit.change_type_title(current_user)} has been successfully cherry-picked into #{@branch_name}.",
success_path: -> { successful_change_path }, failure_path: failed_change_path)
end
diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb
index 36faea8056e..5546bef850b 100644
--- a/app/controllers/projects/commits_controller.rb
+++ b/app/controllers/projects/commits_controller.rb
@@ -63,7 +63,7 @@ class Projects::CommitsController < Projects::ApplicationController
end
@commits = @commits.with_pipeline_status
- @commits = prepare_commits_for_rendering(@commits)
+ @commits = set_commits_for_rendering(@commits)
end
# Rails 5 sets request.format from the extension.
diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb
index f93e500a07a..a1e12821caf 100644
--- a/app/controllers/projects/compare_controller.rb
+++ b/app/controllers/projects/compare_controller.rb
@@ -78,7 +78,7 @@ class Projects::CompareController < Projects::ApplicationController
end
def define_commits
- @commits = compare.present? ? prepare_commits_for_rendering(compare.commits) : []
+ @commits = compare.present? ? set_commits_for_rendering(@compare.commits) : []
end
def define_diffs
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index ef8159aa553..c3ac8e107fb 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -113,7 +113,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
def referenced_merge_requests
- @merge_requests, @closed_by_merge_requests = ::Issues::FetchReferencedMergeRequestsService.new(project, current_user).execute(issue)
+ @merge_requests, @closed_by_merge_requests = ::Issues::ReferencedMergeRequestsService.new(project, current_user).execute(issue)
respond_to do |format|
format.json do
diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb
index 81129456ad8..03d0290ac1d 100644
--- a/app/controllers/projects/merge_requests/creations_controller.rb
+++ b/app/controllers/projects/merge_requests/creations_controller.rb
@@ -101,7 +101,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
@target_project = @merge_request.target_project
@source_project = @merge_request.source_project
- @commits = prepare_commits_for_rendering(@merge_request.commits)
+ @commits = set_commits_for_rendering(@merge_request.commits)
@commit = @merge_request.diff_head_commit
@labels = LabelsFinder.new(current_user, project_id: @project.id).execute
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 1b069fe507b..d31b58972ca 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -79,7 +79,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
# Get commits from repository
# or from cache if already merged
@commits =
- prepare_commits_for_rendering(@merge_request.commits.with_pipeline_status)
+ set_commits_for_rendering(@merge_request.commits.with_pipeline_status)
render json: { html: view_to_html_string('projects/merge_requests/_commits') }
end
diff --git a/app/controllers/projects/pages_controller.rb b/app/controllers/projects/pages_controller.rb
index cae6e2c40b8..ff49911d892 100644
--- a/app/controllers/projects/pages_controller.rb
+++ b/app/controllers/projects/pages_controller.rb
@@ -11,7 +11,7 @@ class Projects::PagesController < Projects::ApplicationController
def destroy
project.remove_pages
- project.pages_domains.destroy_all
+ project.pages_domains.destroy_all # rubocop: disable DestroyAll
respond_to do |format|
format.html do
diff --git a/app/finders/autocomplete/group_finder.rb b/app/finders/autocomplete/group_finder.rb
new file mode 100644
index 00000000000..dd97ac4c817
--- /dev/null
+++ b/app/finders/autocomplete/group_finder.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module Autocomplete
+ # Finder for retrieving a group to use for autocomplete data sources.
+ class GroupFinder
+ attr_reader :current_user, :project, :group_id
+
+ # current_user - The currently logged in user, if any.
+ # project - The Project (if any) to use for the autocomplete data sources.
+ # params - A Hash containing parameters to use for finding the project.
+ #
+ # The following parameters are supported:
+ #
+ # * group_id: The ID of the group to find.
+ def initialize(current_user = nil, project = nil, params = {})
+ @current_user = current_user
+ @project = project
+ @group_id = params[:group_id]
+ end
+
+ # Attempts to find a Group based on the current group ID.
+ def execute
+ return unless project.blank? && group_id.present?
+
+ group = Group.find(group_id)
+
+ # This removes the need for using `return render_404` and similar patterns
+ # in controllers that use this finder.
+ unless Ability.allowed?(current_user, :read_group, group)
+ raise ActiveRecord::RecordNotFound
+ .new("Could not find a Group with ID #{group_id}")
+ end
+
+ group
+ end
+ end
+end
diff --git a/app/finders/autocomplete/move_to_project_finder.rb b/app/finders/autocomplete/move_to_project_finder.rb
new file mode 100644
index 00000000000..edaf74c5f92
--- /dev/null
+++ b/app/finders/autocomplete/move_to_project_finder.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Autocomplete
+ # Finder that retrieves a list of projects that an issue can be moved to.
+ class MoveToProjectFinder
+ attr_reader :current_user, :search, :project_id, :offset_id
+
+ # current_user - The User object of the user that wants to view the list of
+ # projects.
+ #
+ # params - A Hash containing additional parameters to set.
+ #
+ # The following parameters can be set (as Symbols):
+ #
+ # * search: An optional search query to apply to the list of projects.
+ # * project_id: The ID of a project to exclude from the returned relation.
+ # * offset_id: The ID of a project to use for pagination. When given, only
+ # projects with a lower ID are included in the list.
+ def initialize(current_user, params = {})
+ @current_user = current_user
+ @search = params[:search]
+ @project_id = params[:project_id]
+ @offset_id = params[:offset_id]
+ end
+
+ def execute
+ current_user
+ .projects_where_can_admin_issues
+ .optionally_search(search)
+ .excluding_project(project_id)
+ .paginate_in_descending_order_using_id(before: offset_id)
+ .eager_load_namespace_and_owner
+ end
+ end
+end
diff --git a/app/finders/autocomplete/project_finder.rb b/app/finders/autocomplete/project_finder.rb
new file mode 100644
index 00000000000..3a4696f4c2e
--- /dev/null
+++ b/app/finders/autocomplete/project_finder.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Autocomplete
+ # Finder for retrieving a project to use for autocomplete data sources.
+ class ProjectFinder
+ attr_reader :current_user, :project_id
+
+ # current_user - The currently logged in user, if any.
+ # params - A Hash containing parameters to use for finding the project.
+ #
+ # The following parameters are supported:
+ #
+ # * project_id: The ID of the project to find.
+ def initialize(current_user = nil, params = {})
+ @current_user = current_user
+ @project_id = params[:project_id]
+ end
+
+ # Attempts to find a Project based on the current project ID.
+ def execute
+ return if project_id.blank?
+
+ project = Project.find(project_id)
+
+ # This removes the need for using `return render_404` and similar patterns
+ # in controllers that use this finder.
+ unless Ability.allowed?(current_user, :read_project, project)
+ raise ActiveRecord::RecordNotFound
+ .new("Could not find a Project with ID #{project_id}")
+ end
+
+ project
+ end
+ end
+end
diff --git a/app/finders/autocomplete/users_finder.rb b/app/finders/autocomplete/users_finder.rb
new file mode 100644
index 00000000000..b2557469079
--- /dev/null
+++ b/app/finders/autocomplete/users_finder.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+module Autocomplete
+ class UsersFinder
+ # The number of users to display in the results is hardcoded to 20, and
+ # pagination is not supported. This ensures that performance remains
+ # consistent and removes the need for implementing keyset pagination to
+ # ensure good performance.
+ LIMIT = 20
+
+ attr_reader :current_user, :project, :group, :search, :skip_users,
+ :author_id, :todo_filter, :todo_state_filter,
+ :filter_by_current_user
+
+ def initialize(params:, current_user:, project:, group:)
+ @current_user = current_user
+ @project = project
+ @group = group
+ @search = params[:search]
+ @skip_users = params[:skip_users]
+ @author_id = params[:author_id]
+ @todo_filter = params[:todo_filter]
+ @todo_state_filter = params[:todo_state_filter]
+ @filter_by_current_user = params[:current_user]
+ end
+
+ def execute
+ items = limited_users
+
+ if search.blank?
+ # Include current user if available to filter by "Me"
+ items.unshift(current_user) if prepend_current_user?
+
+ if prepend_author? && (author = User.find_by_id(author_id))
+ items.unshift(author)
+ end
+ end
+
+ items.uniq
+ end
+
+ private
+
+ # Returns the users based on the input parameters, as an Array.
+ #
+ # This method is separate so it is easier to extend in EE.
+ def limited_users
+ # When changing the order of these method calls, make sure that
+ # reorder_by_name() is called _before_ optionally_search(), otherwise
+ # reorder_by_name will break the ORDER BY applied in optionally_search().
+ find_users
+ .active
+ .reorder_by_name
+ .optionally_search(search)
+ .where_not_in(skip_users)
+ .limit_to_todo_authors(
+ user: current_user,
+ with_todos: todo_filter,
+ todo_state: todo_state_filter
+ )
+ .limit(LIMIT)
+ .to_a
+ end
+
+ def prepend_current_user?
+ filter_by_current_user.present? && current_user
+ end
+
+ def prepend_author?
+ author_id.present? && current_user
+ end
+
+ def find_users
+ if project
+ project.authorized_users.union_with_user(author_id)
+ elsif group
+ group.users_with_parents
+ elsif current_user
+ User.all
+ else
+ User.none
+ end
+ end
+ end
+end
diff --git a/app/finders/autocomplete_users_finder.rb b/app/finders/autocomplete_users_finder.rb
deleted file mode 100644
index e8a03947f59..00000000000
--- a/app/finders/autocomplete_users_finder.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-class AutocompleteUsersFinder
- # The number of users to display in the results is hardcoded to 20, and
- # pagination is not supported. This ensures that performance remains
- # consistent and removes the need for implementing keyset pagination to ensure
- # good performance.
- LIMIT = 20
-
- attr_reader :current_user, :project, :group, :search, :skip_users,
- :author_id, :params
-
- def initialize(params:, current_user:, project:, group:)
- @current_user = current_user
- @project = project
- @group = group
- @search = params[:search]
- @skip_users = params[:skip_users]
- @author_id = params[:author_id]
- @params = params
- end
-
- def execute
- items = find_users
- items = items.active
- items = items.reorder(:name)
- items = items.search(search) if search.present?
- items = items.where.not(id: skip_users) if skip_users.present?
- items = items.limit(LIMIT)
-
- if params[:todo_filter].present? && current_user
- items = items.todo_authors(current_user.id, params[:todo_state_filter])
- end
-
- if search.blank?
- # Include current user if available to filter by "Me"
- if params[:current_user].present? && current_user
- items = [current_user, *items].uniq
- end
-
- if author_id.present? && current_user
- author = User.find_by_id(author_id)
- items = [author, *items].uniq if author
- end
- end
-
- items
- end
-
- private
-
- def find_users
- return users_from_project if project
- return group.users_with_parents if group
- return User.all if current_user
-
- User.none
- end
-
- def users_from_project
- if author_id.present?
- union = Gitlab::SQL::Union
- .new([project.authorized_users, User.where(id: author_id)])
-
- User.from("(#{union.to_sql}) #{User.table_name}")
- else
- project.authorized_users
- end
- end
-end
diff --git a/app/finders/awarded_emoji_finder.rb b/app/finders/awarded_emoji_finder.rb
new file mode 100644
index 00000000000..f0cc17f3b26
--- /dev/null
+++ b/app/finders/awarded_emoji_finder.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+# Class for retrieving information about emoji awarded _by_ a particular user.
+class AwardedEmojiFinder
+ attr_reader :current_user
+
+ # current_user - The User to generate the data for.
+ def initialize(current_user = nil)
+ @current_user = current_user
+ end
+
+ def execute
+ return [] unless current_user
+
+ # We want the resulting data set to be an Array containing the emoji names
+ # in descending order, based on how often they were awarded.
+ AwardEmoji
+ .award_counts_for_user(current_user)
+ .map { |name, _| { name: name } }
+ end
+end
diff --git a/app/finders/license_template_finder.rb b/app/finders/license_template_finder.rb
new file mode 100644
index 00000000000..fad33f0eca2
--- /dev/null
+++ b/app/finders/license_template_finder.rb
@@ -0,0 +1,36 @@
+# LicenseTemplateFinder
+#
+# Used to find license templates, which may come from a variety of external
+# sources
+#
+# Arguments:
+# popular: boolean. When set to true, only "popular" licenses are shown. When
+# false, all licenses except popular ones are shown. When nil (the
+# default), *all* licenses will be shown.
+class LicenseTemplateFinder
+ attr_reader :params
+
+ def initialize(params = {})
+ @params = params
+ end
+
+ def execute
+ Licensee::License.all(featured: popular_only?).map do |license|
+ LicenseTemplate.new(
+ id: license.key,
+ name: license.name,
+ nickname: license.nickname,
+ category: (license.featured? ? :Popular : :Other),
+ content: license.content,
+ url: license.url,
+ meta: license.meta
+ )
+ end
+ end
+
+ private
+
+ def popular_only?
+ params.fetch(:popular, nil)
+ end
+end
diff --git a/app/finders/move_to_project_finder.rb b/app/finders/move_to_project_finder.rb
deleted file mode 100644
index 038d5565a1e..00000000000
--- a/app/finders/move_to_project_finder.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-class MoveToProjectFinder
- PAGE_SIZE = 50
-
- def initialize(user)
- @user = user
- end
-
- def execute(from_project, search: nil, offset_id: nil)
- projects = @user.projects_where_can_admin_issues
- projects = projects.search(search) if search.present?
- projects = projects.excluding_project(from_project)
- projects = projects.order_id_desc
-
- # infinite scroll using offset
- projects = projects.where('projects.id < ?', offset_id) if offset_id.present?
- projects = projects.limit(PAGE_SIZE)
-
- # to ask for Project#name_with_namespace
- projects.includes(namespace: :owner)
- end
-end
diff --git a/app/finders/user_finder.rb b/app/finders/user_finder.rb
new file mode 100644
index 00000000000..484a93c9873
--- /dev/null
+++ b/app/finders/user_finder.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+# A simple finding for obtaining a single User.
+#
+# While using `User.find_by` directly is straightforward, it can lead to a lot
+# of code duplication. Sometimes we just want to find a user by an ID, other
+# times we may want to exclude blocked user. By using this finder (and extending
+# it whenever necessary) we can keep this logic in one place.
+class UserFinder
+ attr_reader :params
+
+ def initialize(params)
+ @params = params
+ end
+
+ # Tries to find a User, returning nil if none could be found.
+ def execute
+ User.find_by(id: params[:id])
+ end
+
+ # Tries to find a User, raising a `ActiveRecord::RecordNotFound` if it could
+ # not be found.
+ def execute!
+ User.find(params[:id])
+ end
+end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 2bdf2c2c120..1e05f07e676 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -254,6 +254,7 @@ module ApplicationSettingsHelper
:usage_ping_enabled,
:instance_statistics_visibility_private,
:user_default_external,
+ :user_show_add_ssh_key_message,
:user_oauth_applications,
:version_check_enabled,
:web_ide_clientside_preview_enabled
diff --git a/app/helpers/avatars_helper.rb b/app/helpers/avatars_helper.rb
index d48dae8f06d..494f785e305 100644
--- a/app/helpers/avatars_helper.rb
+++ b/app/helpers/avatars_helper.rb
@@ -1,28 +1,10 @@
module AvatarsHelper
def project_icon(project_id, options = {})
- project =
- if project_id.respond_to?(:avatar_url)
- project_id
- else
- Project.find_by_full_path(project_id)
- end
-
- if project.avatar_url
- image_tag project.avatar_url, options
- else # generated icon
- project_identicon(project, options)
- end
+ source_icon(Project, project_id, options)
end
- def project_identicon(project, options = {})
- bg_key = (project.id % 7) + 1
- options[:class] ||= ''
- options[:class] << ' identicon'
- options[:class] << " bg#{bg_key}"
-
- content_tag(:div, class: options[:class]) do
- project.name[0, 1].upcase
- end
+ def group_icon(group_id, options = {})
+ source_icon(Group, group_id, options)
end
# Takes both user and email and returns the avatar_icon by
@@ -123,4 +105,32 @@ module AvatarsHelper
mail_to(options[:user_email], avatar)
end
end
+
+ private
+
+ def source_icon(klass, source_id, options = {})
+ source =
+ if source_id.respond_to?(:avatar_url)
+ source_id
+ else
+ klass.find_by_full_path(source_id)
+ end
+
+ if source.avatar_url
+ image_tag source.avatar_url, options
+ else
+ source_identicon(source, options)
+ end
+ end
+
+ def source_identicon(source, options = {})
+ bg_key = (source.id % 7) + 1
+ options[:class] ||= ''
+ options[:class] << ' identicon'
+ options[:class] << " bg#{bg_key}"
+
+ content_tag(:div, class: options[:class].strip) do
+ source.name[0, 1].upcase
+ end
+ end
end
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 7eb45ddd117..b61cbd5418a 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -182,12 +182,14 @@ module BlobHelper
def licenses_for_select
return @licenses_for_select if defined?(@licenses_for_select)
- licenses = Licensee::License.all
+ grouped_licenses = LicenseTemplateFinder.new.execute.group_by(&:category)
+ categories = grouped_licenses.keys
- @licenses_for_select = {
- Popular: licenses.select(&:featured).map { |license| { name: license.name, id: license.key } },
- Other: licenses.reject(&:featured).map { |license| { name: license.name, id: license.key } }
- }
+ @licenses_for_select = categories.each_with_object({}) do |category, hash|
+ hash[category] = grouped_licenses[category].map do |license|
+ { name: license.name, id: license.id }
+ end
+ end
end
def ref_project
diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb
index 0171a880164..7adc882bc47 100644
--- a/app/helpers/button_helper.rb
+++ b/app/helpers/button_helper.rb
@@ -73,7 +73,11 @@ module ButtonHelper
end
def ssh_clone_button(project, append_link: true)
- dropdown_description = _("You won't be able to pull or push project code via SSH until you add an SSH key to your profile") if current_user.try(:require_ssh_key?)
+ if Gitlab::CurrentSettings.user_show_add_ssh_key_message? &&
+ current_user.try(:require_ssh_key?)
+ dropdown_description = _("You won't be able to pull or push project code via SSH until you add an SSH key to your profile")
+ end
+
append_url = project.ssh_url_to_repo if append_link
dropdown_item_with_description('SSH', dropdown_description, href: append_url)
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index 89fe90fd801..7a942c44ac4 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -210,17 +210,6 @@ module CommitsHelper
Sanitize.clean(string, remove_contents: true)
end
- def limited_commits(commits)
- if commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
- [
- commits.first(MergeRequestDiff::COMMITS_SAFE_SIZE),
- commits.size - MergeRequestDiff::COMMITS_SAFE_SIZE
- ]
- else
- [commits, 0]
- end
- end
-
def commit_path(project, commit, merge_request: nil)
if merge_request&.persisted?
diffs_project_merge_request_path(project, merge_request, commit_id: commit.id)
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index 3c5c8bbd71b..5b51d2f2425 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -33,11 +33,6 @@ module GroupsHelper
.count
end
- def group_icon(group, options = {})
- img_path = group_icon_url(group, options)
- image_tag img_path, options
- end
-
def group_icon_url(group, options = {})
if group.is_a?(String)
group = Group.find_by_full_path(group)
diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb
index 41084ec686f..a8a10c98d69 100644
--- a/app/helpers/icons_helper.rb
+++ b/app/helpers/icons_helper.rb
@@ -62,6 +62,8 @@ module IconsHelper
names = "key"
when "two-factor"
names = "key"
+ when "google_oauth2"
+ names = "google"
end
options.include?(:base) ? fa_stacked_icon(names, options) : fa_icon(names, options)
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 96dc7ae1185..5b27d1d9404 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -92,14 +92,6 @@ module IssuesHelper
end
end
- def award_user_authored_class(award)
- if award == 'thumbsdown' || award == 'thumbsup'
- 'user-authored js-user-authored'
- else
- ''
- end
- end
-
def awards_sort(awards)
awards.sort_by do |award, award_emojis|
if award == "thumbsup"
diff --git a/app/helpers/mirror_helper.rb b/app/helpers/mirror_helper.rb
new file mode 100644
index 00000000000..93ed22513ac
--- /dev/null
+++ b/app/helpers/mirror_helper.rb
@@ -0,0 +1,5 @@
+module MirrorHelper
+ def mirrors_form_data_attributes
+ { project_mirror_endpoint: project_mirror_path(@project) }
+ end
+end
diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb
index 3e42063224e..a185f2916d4 100644
--- a/app/helpers/notifications_helper.rb
+++ b/app/helpers/notifications_helper.rb
@@ -83,21 +83,11 @@ module NotificationsHelper
end
def notification_event_name(event)
- # All values from NotificationSetting::EMAIL_EVENTS
+ # All values from NotificationSetting.email_events
case event
when :success_pipeline
s_('NotificationEvent|Successful pipeline')
else
- N_('NotificationEvent|New note')
- N_('NotificationEvent|New issue')
- N_('NotificationEvent|Reopen issue')
- N_('NotificationEvent|Close issue')
- N_('NotificationEvent|Reassign issue')
- N_('NotificationEvent|New merge request')
- N_('NotificationEvent|Close merge request')
- N_('NotificationEvent|Reassign merge request')
- N_('NotificationEvent|Merge merge request')
- N_('NotificationEvent|Failed pipeline')
s_(event.to_s.humanize)
end
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index aaf9dff43ee..6b4079b4113 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -192,7 +192,10 @@ module ProjectsHelper
end
def show_no_ssh_key_message?
- cookies[:hide_no_ssh_message].blank? && !current_user.hide_no_ssh_key && current_user.require_ssh_key?
+ Gitlab::CurrentSettings.user_show_add_ssh_key_message? &&
+ cookies[:hide_no_ssh_message].blank? &&
+ !current_user.hide_no_ssh_key &&
+ current_user.require_ssh_key?
end
def show_no_password_message?
diff --git a/app/mailers/abuse_report_mailer.rb b/app/mailers/abuse_report_mailer.rb
index fe5f68ba3d5..e032f568913 100644
--- a/app/mailers/abuse_report_mailer.rb
+++ b/app/mailers/abuse_report_mailer.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class AbuseReportMailer < BaseMailer
def notify(abuse_report_id)
return unless deliverable?
diff --git a/app/mailers/base_mailer.rb b/app/mailers/base_mailer.rb
index 654468bc7fe..5fd209c4761 100644
--- a/app/mailers/base_mailer.rb
+++ b/app/mailers/base_mailer.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class BaseMailer < ActionMailer::Base
around_action :render_with_default_locale
diff --git a/app/mailers/devise_mailer.rb b/app/mailers/devise_mailer.rb
index 962570a0efd..7aa75ee30e6 100644
--- a/app/mailers/devise_mailer.rb
+++ b/app/mailers/devise_mailer.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class DeviseMailer < Devise::Mailer
default from: "#{Gitlab.config.gitlab.email_display_name} <#{Gitlab.config.gitlab.email_from}>"
default reply_to: Gitlab.config.gitlab.email_reply_to
@@ -9,8 +11,9 @@ class DeviseMailer < Devise::Mailer
protected
def subject_for(key)
- subject = super
- subject << " | #{Gitlab.config.gitlab.email_subject_suffix}" if Gitlab.config.gitlab.email_subject_suffix.present?
- subject
+ subject = [super]
+ subject << Gitlab.config.gitlab.email_subject_suffix if Gitlab.config.gitlab.email_subject_suffix.present?
+
+ subject.join(' | ')
end
end
diff --git a/app/mailers/email_rejection_mailer.rb b/app/mailers/email_rejection_mailer.rb
index 76db31a4c45..45fc5a6c383 100644
--- a/app/mailers/email_rejection_mailer.rb
+++ b/app/mailers/email_rejection_mailer.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class EmailRejectionMailer < BaseMailer
def rejection(reason, original_raw, can_retry = false)
@reason = reason
diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb
index 392cc0bee03..c8b1ab5033a 100644
--- a/app/mailers/emails/issues.rb
+++ b/app/mailers/emails/issues.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Emails
module Issues
def new_issue_email(recipient_id, issue_id, reason = nil)
diff --git a/app/mailers/emails/members.rb b/app/mailers/emails/members.rb
index 75cf56a51f2..91dfdf58982 100644
--- a/app/mailers/emails/members.rb
+++ b/app/mailers/emails/members.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Emails
module Members
extend ActiveSupport::Concern
diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb
index 70509e9066d..70f65d4e58d 100644
--- a/app/mailers/emails/merge_requests.rb
+++ b/app/mailers/emails/merge_requests.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Emails
module MergeRequests
def new_merge_request_email(recipient_id, merge_request_id, reason = nil)
diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb
index d9a6fe2a41e..d3284e90568 100644
--- a/app/mailers/emails/notes.rb
+++ b/app/mailers/emails/notes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Emails
module Notes
def note_commit_email(recipient_id, note_id)
diff --git a/app/mailers/emails/pages_domains.rb b/app/mailers/emails/pages_domains.rb
index 0027dfdc36b..ce449237ef6 100644
--- a/app/mailers/emails/pages_domains.rb
+++ b/app/mailers/emails/pages_domains.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Emails
module PagesDomains
def pages_domain_enabled_email(domain, recipient)
diff --git a/app/mailers/emails/pipelines.rb b/app/mailers/emails/pipelines.rb
index f9f45ab987b..31e183640ad 100644
--- a/app/mailers/emails/pipelines.rb
+++ b/app/mailers/emails/pipelines.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Emails
module Pipelines
def pipeline_success_email(pipeline, recipients)
@@ -39,10 +41,10 @@ module Emails
end
def pipeline_subject(status)
- commit = @pipeline.short_sha
- commit << " in #{@merge_request.to_reference}" if @merge_request
+ commit = [@pipeline.short_sha]
+ commit << "in #{@merge_request.to_reference}" if @merge_request
- subject("Pipeline ##{@pipeline.id} has #{status} for #{@pipeline.ref}", commit)
+ subject("Pipeline ##{@pipeline.id} has #{status} for #{@pipeline.ref}", commit.join(' '))
end
end
end
diff --git a/app/mailers/emails/profile.rb b/app/mailers/emails/profile.rb
index 4f5edeb9bda..40d7b9ccd7a 100644
--- a/app/mailers/emails/profile.rb
+++ b/app/mailers/emails/profile.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Emails
module Profile
def new_user_email(user_id, token = nil)
diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb
index 761d873c01c..d7e6c2ba7b2 100644
--- a/app/mailers/emails/projects.rb
+++ b/app/mailers/emails/projects.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Emails
module Projects
def project_was_moved_email(project_id, user_id, old_path_with_namespace)
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 0e1e39501f5..f4eeb85270e 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Notify < BaseMailer
include ActionDispatch::Routing::PolymorphicRoutes
include GitlabRoutingHelper
@@ -92,12 +94,14 @@ class Notify < BaseMailer
# >> subject('Lorem ipsum', 'Dolor sit amet')
# => "Lorem ipsum | Dolor sit amet"
def subject(*extra)
- subject = ""
- subject << "#{@project.name} | " if @project
- subject << "#{@group.name} | " if @group
- subject << extra.join(' | ') if extra.present?
- subject << " | #{Gitlab.config.gitlab.email_subject_suffix}" if Gitlab.config.gitlab.email_subject_suffix.present?
- subject
+ subject = []
+
+ subject << @project.name if @project
+ subject << @group.name if @group
+ subject.concat(extra) if extra.present?
+ subject << Gitlab.config.gitlab.email_subject_suffix if Gitlab.config.gitlab.email_subject_suffix.present?
+
+ subject.join(' | ')
end
# Return a string suitable for inclusion in the 'Message-Id' mail header.
diff --git a/app/mailers/previews/devise_mailer_preview.rb b/app/mailers/previews/devise_mailer_preview.rb
index d6588efc486..3b9ef0d3ac0 100644
--- a/app/mailers/previews/devise_mailer_preview.rb
+++ b/app/mailers/previews/devise_mailer_preview.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class DeviseMailerPreview < ActionMailer::Preview
def confirmation_instructions_for_signup
DeviseMailer.confirmation_instructions(unsaved_user, 'faketoken', {})
diff --git a/app/mailers/previews/email_rejection_mailer_preview.rb b/app/mailers/previews/email_rejection_mailer_preview.rb
index 639e8471232..402066151ef 100644
--- a/app/mailers/previews/email_rejection_mailer_preview.rb
+++ b/app/mailers/previews/email_rejection_mailer_preview.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class EmailRejectionMailerPreview < ActionMailer::Preview
def rejection
EmailRejectionMailer.rejection("some rejection reason", "From: someone@example.com\nraw email here").message
diff --git a/app/mailers/previews/notify_preview.rb b/app/mailers/previews/notify_preview.rb
index 3615cde8026..df470930e9e 100644
--- a/app/mailers/previews/notify_preview.rb
+++ b/app/mailers/previews/notify_preview.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class NotifyPreview < ActionMailer::Preview
def note_merge_request_email_for_individual_note
note_email(:note_merge_request_email) do
diff --git a/app/mailers/previews/repository_check_mailer_preview.rb b/app/mailers/previews/repository_check_mailer_preview.rb
index 19d4eab1805..834d7594719 100644
--- a/app/mailers/previews/repository_check_mailer_preview.rb
+++ b/app/mailers/previews/repository_check_mailer_preview.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class RepositoryCheckMailerPreview < ActionMailer::Preview
def notify
RepositoryCheckMailer.notify(3).message
diff --git a/app/mailers/repository_check_mailer.rb b/app/mailers/repository_check_mailer.rb
index 22a9f5da646..4bcf371cfc0 100644
--- a/app/mailers/repository_check_mailer.rb
+++ b/app/mailers/repository_check_mailer.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class RepositoryCheckMailer < BaseMailer
def notify(failed_count)
@message =
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index bbe7811841a..c77faa4b71d 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -298,7 +298,8 @@ class ApplicationSetting < ActiveRecord::Base
unique_ips_limit_time_window: 3600,
usage_ping_enabled: Settings.gitlab['usage_ping_enabled'],
instance_statistics_visibility_private: false,
- user_default_external: false
+ user_default_external: false,
+ user_show_add_ssh_key_message: true
}
end
diff --git a/app/models/award_emoji.rb b/app/models/award_emoji.rb
index 99c7866d636..ddc516ccb60 100644
--- a/app/models/award_emoji.rb
+++ b/app/models/award_emoji.rb
@@ -28,6 +28,23 @@ class AwardEmoji < ActiveRecord::Base
.where('name IN (?) AND awardable_type = ? AND awardable_id IN (?)', [DOWNVOTE_NAME, UPVOTE_NAME], type, ids)
.group('name', 'awardable_id')
end
+
+ # Returns the top 100 emoji awarded by the given user.
+ #
+ # The returned value is a Hash mapping emoji names to the number of times
+ # they were awarded:
+ #
+ # { 'thumbsup' => 2, 'thumbsdown' => 1 }
+ #
+ # user - The User to get the awards for.
+ # limt - The maximum number of emoji to return.
+ def award_counts_for_user(user, limit = 100)
+ limit(limit)
+ .where(user: user)
+ .group(:name)
+ .order('count_all DESC, name ASC')
+ .count
+ end
end
def downvote?
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 9292929be98..faa160ad6ba 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -67,6 +67,10 @@ module Ci
'', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').archive)
end
+ scope :with_archived_trace, ->() do
+ where('EXISTS (?)', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').trace)
+ end
+
scope :without_archived_trace, ->() do
where('NOT EXISTS (?)', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').trace)
end
@@ -77,6 +81,7 @@ module Ci
end
scope :with_artifacts_stored_locally, -> { with_artifacts_archive.where(artifacts_file_store: [nil, LegacyArtifactUploader::Store::LOCAL]) }
+ scope :with_archived_trace_stored_locally, -> { with_archived_trace.where(artifacts_file_store: [nil, LegacyArtifactUploader::Store::LOCAL]) }
scope :with_artifacts_not_expired, ->() { with_artifacts_archive.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) }
scope :with_expired_artifacts, ->() { with_artifacts_archive.where('artifacts_expire_at < ?', Time.now) }
scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) }
@@ -226,7 +231,7 @@ module Ci
end
def cancelable?
- active?
+ active? || created?
end
def retryable?
diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb
index d7c5f29be96..17b7ee4f07e 100644
--- a/app/models/ci/job_artifact.rb
+++ b/app/models/ci/job_artifact.rb
@@ -33,7 +33,7 @@ module Ci
where(file_type: types)
end
- delegate :exists?, :open, to: :file
+ delegate :filename, :exists?, :open, to: :file
enum file_type: {
archive: 1,
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 8b9f4490ffa..594972ad344 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -193,6 +193,7 @@ class Commit
# otherwise returns commit message without first line
def description
return safe_message if full_title.length >= 100
+ return no_commit_message if safe_message.blank?
safe_message.split("\n", 2)[1].try(:chomp)
end
@@ -448,6 +449,10 @@ class Commit
true
end
+ def to_ability_name
+ model_name.singular
+ end
+
def touch
# no-op but needs to be defined since #persisted? is defined
end
diff --git a/app/models/concerns/awardable.rb b/app/models/concerns/awardable.rb
index dd07f389fa5..4200253053a 100644
--- a/app/models/concerns/awardable.rb
+++ b/app/models/concerns/awardable.rb
@@ -76,12 +76,8 @@ module Awardable
true
end
- def awardable_votes?(name)
- AwardEmoji::UPVOTE_NAME == name || AwardEmoji::DOWNVOTE_NAME == name
- end
-
- def user_can_award?(current_user, name)
- awardable_by_user?(current_user, name) && Ability.allowed?(current_user, :award_emoji, self)
+ def user_can_award?(current_user)
+ Ability.allowed?(current_user, :award_emoji, self)
end
def user_authored?(current_user)
@@ -101,7 +97,7 @@ module Awardable
end
def remove_award_emoji(name, current_user)
- award_emoji.where(name: name, user: current_user).destroy_all
+ award_emoji.where(name: name, user: current_user).destroy_all # rubocop: disable DestroyAll
end
def toggle_award_emoji(emoji_name, current_user)
@@ -117,12 +113,4 @@ module Awardable
def normalize_name(name)
Gitlab::Emoji.normalize_emoji_name(name)
end
-
- def awardable_by_user?(current_user, name)
- if user_authored?(current_user)
- !awardable_votes?(normalize_name(name))
- else
- true
- end
- end
end
diff --git a/app/models/concerns/fast_destroy_all.rb b/app/models/concerns/fast_destroy_all.rb
index 65ed46ea202..c342d01243e 100644
--- a/app/models/concerns/fast_destroy_all.rb
+++ b/app/models/concerns/fast_destroy_all.rb
@@ -34,7 +34,7 @@ module FastDestroyAll
included do
before_destroy do
- raise ForbiddenActionError, '`destroy` and `destroy_all` are forbbiden. Please use `fast_destroy_all`'
+ raise ForbiddenActionError, '`destroy` and `destroy_all` are forbidden. Please use `fast_destroy_all`'
end
end
diff --git a/app/models/concerns/optionally_search.rb b/app/models/concerns/optionally_search.rb
new file mode 100644
index 00000000000..dec97b7dee8
--- /dev/null
+++ b/app/models/concerns/optionally_search.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module OptionallySearch
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ def search(*)
+ raise(
+ NotImplementedError,
+ 'Your model must implement the "search" class method'
+ )
+ end
+
+ # Optionally limits a result set to those matching the given search query.
+ def optionally_search(query = nil)
+ query.present? ? search(query) : all
+ end
+ end
+end
diff --git a/app/models/internal_id.rb b/app/models/internal_id.rb
index 4eb211eff61..e7168d49db9 100644
--- a/app/models/internal_id.rb
+++ b/app/models/internal_id.rb
@@ -111,7 +111,7 @@ class InternalId < ActiveRecord::Base
# Generates next internal id and returns it
def generate
- subject.transaction do
+ InternalId.transaction do
# Create a record in internal_ids if one does not yet exist
# and increment its last value
#
@@ -125,7 +125,7 @@ class InternalId < ActiveRecord::Base
#
# Note this will acquire a ROW SHARE lock on the InternalId record
def track_greatest(new_value)
- subject.transaction do
+ InternalId.transaction do
(lookup || create_record).track_greatest_and_save!(new_value)
end
end
@@ -148,7 +148,7 @@ class InternalId < ActiveRecord::Base
# violation. We can safely roll-back the nested transaction and perform
# a lookup instead to retrieve the record.
def create_record
- subject.transaction(requires_new: true) do
+ InternalId.transaction(requires_new: true) do
InternalId.create!(
**scope,
usage: usage_value,
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 94cf12f3c2b..d0cd7461daa 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -170,27 +170,6 @@ class Issue < ActiveRecord::Base
"#{project.to_reference(from, full: full)}#{reference}"
end
- def referenced_merge_requests(current_user = nil)
- ext = all_references(current_user)
-
- notes_with_associations.each do |object|
- object.all_references(current_user, extractor: ext)
- end
-
- merge_requests = ext.merge_requests.sort_by(&:iid)
-
- cross_project_filter = -> (merge_requests) do
- merge_requests.select { |mr| mr.target_project == project }
- end
-
- Ability.merge_requests_readable_by_user(
- merge_requests, current_user,
- filters: {
- read_cross_project: cross_project_filter
- }
- )
- end
-
# All branches containing the current issue's ID, except for
# those with a merge request open referencing the current issue.
def related_branches(current_user)
@@ -198,7 +177,11 @@ class Issue < ActiveRecord::Base
branch =~ /\A#{iid}-(?!\d+-stable)/i
end
- branches_with_merge_request = self.referenced_merge_requests(current_user).map(&:source_branch)
+ branches_with_merge_request =
+ Issues::ReferencedMergeRequestsService
+ .new(project, current_user)
+ .referenced_merge_requests(self)
+ .map(&:source_branch)
branches_with_iid - branches_with_merge_request
end
@@ -225,26 +208,6 @@ class Issue < ActiveRecord::Base
project
end
- # From all notes on this issue, we'll select the system notes about linked
- # merge requests. Of those, the MRs closing `self` are returned.
- def closed_by_merge_requests(current_user = nil)
- return [] unless open?
-
- ext = all_references(current_user)
-
- notes.system.each do |note|
- note.all_references(current_user, extractor: ext)
- end
-
- merge_requests = ext.merge_requests.select(&:open?)
- if merge_requests.any?
- ids = MergeRequestsClosingIssues.where(merge_request_id: merge_requests.map(&:id), issue_id: id).pluck(:merge_request_id)
- merge_requests.select { |mr| mr.id.in?(ids) }
- else
- []
- end
- end
-
def moved?
!moved_to.nil?
end
diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb
index 2a1a4ef48b7..97bf5d611c2 100644
--- a/app/models/lfs_object.rb
+++ b/app/models/lfs_object.rb
@@ -29,11 +29,13 @@ class LfsObject < ActiveRecord::Base
[nil, LfsObjectUploader::Store::LOCAL].include?(self.file_store)
end
+ # rubocop: disable DestroyAll
def self.destroy_unreferenced
joins("LEFT JOIN lfs_objects_projects ON lfs_objects_projects.lfs_object_id = #{table_name}.id")
.where(lfs_objects_projects: { id: nil })
.destroy_all
end
+ # rubocop: enable DestroyAll
def self.calculate_oid(path)
Digest::SHA256.file(path).hexdigest
diff --git a/app/models/license_template.rb b/app/models/license_template.rb
new file mode 100644
index 00000000000..0ad75b27827
--- /dev/null
+++ b/app/models/license_template.rb
@@ -0,0 +1,53 @@
+class LicenseTemplate
+ PROJECT_TEMPLATE_REGEX =
+ %r{[\<\{\[]
+ (project|description|
+ one\sline\s.+\swhat\sit\sdoes\.) # matching the start and end is enough here
+ [\>\}\]]}xi.freeze
+ YEAR_TEMPLATE_REGEX = /[<{\[](year|yyyy)[>}\]]/i.freeze
+ FULLNAME_TEMPLATE_REGEX =
+ %r{[\<\{\[]
+ (fullname|name\sof\s(author|copyright\sowner))
+ [\>\}\]]}xi.freeze
+
+ attr_reader :id, :name, :category, :nickname, :url, :meta
+
+ alias_method :key, :id
+
+ def initialize(id:, name:, category:, content:, nickname: nil, url: nil, meta: {})
+ @id = id
+ @name = name
+ @category = category
+ @content = content
+ @nickname = nickname
+ @url = url
+ @meta = meta
+ end
+
+ def popular?
+ category == :Popular
+ end
+ alias_method :featured?, :popular?
+
+ # Returns the text of the license
+ def content
+ if @content.respond_to?(:call)
+ @content = @content.call
+ else
+ @content
+ end
+ end
+
+ # Populate placeholders in the LicenseTemplate content
+ def resolve!(project_name: nil, fullname: nil, year: Time.now.year.to_s)
+ # Ensure the string isn't shared with any other instance of LicenseTemplate
+ new_content = content.dup
+ new_content.gsub!(YEAR_TEMPLATE_REGEX, year) if year.present?
+ new_content.gsub!(PROJECT_TEMPLATE_REGEX, project_name) if project_name.present?
+ new_content.gsub!(FULLNAME_TEMPLATE_REGEX, fullname) if fullname.present?
+
+ @content = new_content
+
+ self
+ end
+end
diff --git a/app/models/member.rb b/app/models/member.rb
index 05c0bc8cb97..d9b4e8d2ac6 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -103,7 +103,7 @@ class Member < ActiveRecord::Base
def filter_by_2fa(value)
case value
when 'enabled'
- left_join_users.merge(User.with_two_factor_indistinct)
+ left_join_users.merge(User.with_two_factor)
when 'disabled'
left_join_users.merge(User.without_two_factor)
else
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index b974309aeb6..0deb44d7916 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -10,6 +10,7 @@ class Namespace < ActiveRecord::Base
include Storage::LegacyNamespace
include Gitlab::SQL::Pattern
include IgnorableColumn
+ include FeatureGate
ignore_column :deleted_at
@@ -124,7 +125,6 @@ class Namespace < ActiveRecord::Base
def to_param
full_path
end
- alias_method :flipper_id, :to_param
def human_name
owner_name
diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb
index 1df3a51a7fc..1600acfc575 100644
--- a/app/models/notification_setting.rb
+++ b/app/models/notification_setting.rb
@@ -45,6 +45,15 @@ class NotificationSetting < ActiveRecord::Base
:success_pipeline
].freeze
+ # Update unfound_translations.rb when events are changed
+ def self.email_events(source = nil)
+ EMAIL_EVENTS
+ end
+
+ def email_events
+ self.class.email_events(source)
+ end
+
EXCLUDED_PARTICIPATING_EVENTS = [
:success_pipeline
].freeze
diff --git a/app/models/programming_language.rb b/app/models/programming_language.rb
index 400d6c407a7..0e667dac21e 100644
--- a/app/models/programming_language.rb
+++ b/app/models/programming_language.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ProgrammingLanguage < ActiveRecord::Base
validates :name, presence: true
validates :color, allow_blank: false, color: true
diff --git a/app/models/project.rb b/app/models/project.rb
index 36089995ed3..8f631d7f0ed 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -27,6 +27,8 @@ class Project < ActiveRecord::Base
include FastDestroyAll::Helpers
include WithUploads
include BatchDestroyDependentAssociations
+ include FeatureGate
+ include OptionallySearch
extend Gitlab::Cache::RequestCache
extend Gitlab::ConfigHelper
@@ -139,7 +141,6 @@ class Project < ActiveRecord::Base
has_one :flowdock_service
has_one :assembla_service
has_one :asana_service
- has_one :gemnasium_service
has_one :mattermost_slash_commands_service
has_one :mattermost_service
has_one :slack_slash_commands_service
@@ -383,6 +384,26 @@ class Project < ActiveRecord::Base
only_integer: true,
message: 'needs to be beetween 10 minutes and 1 month' }
+ # Paginates a collection using a `WHERE id < ?` condition.
+ #
+ # before - A project ID to use for filtering out projects with an equal or
+ # greater ID. If no ID is given, all projects are included.
+ #
+ # limit - The maximum number of rows to include.
+ def self.paginate_in_descending_order_using_id(
+ before: nil,
+ limit: Kaminari.config.default_per_page
+ )
+ relation = order_id_desc.limit(limit)
+ relation = relation.where('projects.id < ?', before) if before
+
+ relation
+ end
+
+ def self.eager_load_namespace_and_owner
+ includes(namespace: :owner)
+ end
+
# Returns a collection of projects that is either public or visible to the
# logged in user.
def self.public_or_visible_to_user(user = nil)
@@ -470,6 +491,24 @@ class Project < ActiveRecord::Base
}x
end
+ def reference_postfix
+ '>'
+ end
+
+ def reference_postfix_escaped
+ '&gt;'
+ end
+
+ # Pattern used to extract `namespace/project>` project references from text.
+ # '>' or its escaped form ('&gt;') are checked for because '>' is sometimes escaped
+ # when the reference comes from an external source.
+ def markdown_reference_pattern
+ %r{
+ #{reference_pattern}
+ (#{reference_postfix}|#{reference_postfix_escaped})
+ }x
+ end
+
def trending
joins('INNER JOIN trending_projects ON projects.id = trending_projects.project_id')
.reorder('trending_projects.id ASC')
@@ -501,18 +540,19 @@ class Project < ActiveRecord::Base
def auto_devops_enabled?
if auto_devops&.enabled.nil?
- Gitlab::CurrentSettings.auto_devops_enabled?
+ has_auto_devops_implicitly_enabled?
else
auto_devops.enabled?
end
end
def has_auto_devops_implicitly_enabled?
- auto_devops&.enabled.nil? && Gitlab::CurrentSettings.auto_devops_enabled?
+ auto_devops&.enabled.nil? &&
+ (Gitlab::CurrentSettings.auto_devops_enabled? || Feature.enabled?(:force_autodevops_on_by_default, self))
end
def has_auto_devops_implicitly_disabled?
- auto_devops&.enabled.nil? && !Gitlab::CurrentSettings.auto_devops_enabled?
+ auto_devops&.enabled.nil? && !(Gitlab::CurrentSettings.auto_devops_enabled? || Feature.enabled?(:force_autodevops_on_by_default, self))
end
def empty_repo?
@@ -908,6 +948,10 @@ class Project < ActiveRecord::Base
end
end
+ def to_reference_with_postfix
+ "#{to_reference(full: true)}#{self.class.reference_postfix}"
+ end
+
# `from` argument can be a Namespace or Project.
def to_reference(from = nil, full: false)
if full || cross_namespace_reference?(from)
diff --git a/app/models/project_auto_devops.rb b/app/models/project_auto_devops.rb
index 155400d1a43..dc6736dd9cd 100644
--- a/app/models/project_auto_devops.rb
+++ b/app/models/project_auto_devops.rb
@@ -47,12 +47,8 @@ class ProjectAutoDevops < ActiveRecord::Base
end
def needs_to_create_deploy_token?
- auto_devops_enabled? &&
+ project.auto_devops_enabled? &&
!project.public? &&
!project.deploy_tokens.find_by(name: DeployToken::GITLAB_DEPLOY_TOKEN_NAME).present?
end
-
- def auto_devops_enabled?
- Gitlab::CurrentSettings.auto_devops_enabled? || enabled?
- end
end
diff --git a/app/models/project_services/asana_service.rb b/app/models/project_services/asana_service.rb
index 4f289e6e215..35c19049c04 100644
--- a/app/models/project_services/asana_service.rb
+++ b/app/models/project_services/asana_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'asana'
class AsanaService < Service
diff --git a/app/models/project_services/assembla_service.rb b/app/models/project_services/assembla_service.rb
index 4234b8044e5..60575e45a90 100644
--- a/app/models/project_services/assembla_service.rb
+++ b/app/models/project_services/assembla_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class AssemblaService < Service
prop_accessor :token, :subdomain
validates :token, presence: true, if: :activated?
diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb
index edc5c00d9c4..d502423726c 100644
--- a/app/models/project_services/bamboo_service.rb
+++ b/app/models/project_services/bamboo_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class BambooService < CiService
include ReactiveService
diff --git a/app/models/project_services/bugzilla_service.rb b/app/models/project_services/bugzilla_service.rb
index e4e3a80976b..1a2bb6a171b 100644
--- a/app/models/project_services/bugzilla_service.rb
+++ b/app/models/project_services/bugzilla_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class BugzillaService < IssueTrackerService
validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated?
diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb
index 35884c4560c..43edfde851c 100644
--- a/app/models/project_services/buildkite_service.rb
+++ b/app/models/project_services/buildkite_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "addressable/uri"
class BuildkiteService < CiService
diff --git a/app/models/project_services/builds_email_service.rb b/app/models/project_services/builds_email_service.rb
index 0c526b53d72..f2295a95b60 100644
--- a/app/models/project_services/builds_email_service.rb
+++ b/app/models/project_services/builds_email_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# This class is to be removed with 9.1
# We should also by then remove BuildsEmailService from database
class BuildsEmailService < Service
diff --git a/app/models/project_services/campfire_service.rb b/app/models/project_services/campfire_service.rb
index cb4af73807b..1d7877a1fb5 100644
--- a/app/models/project_services/campfire_service.rb
+++ b/app/models/project_services/campfire_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class CampfireService < Service
prop_accessor :token, :subdomain, :room
validates :token, presence: true, if: :activated?
@@ -82,7 +84,7 @@ class CampfireService < Service
before = push[:before]
after = push[:after]
- message = ""
+ message = []
message << "[#{project.full_name}] "
message << "#{push[:user_name]} "
@@ -95,6 +97,6 @@ class CampfireService < Service
message << "#{project.web_url}/compare/#{before}...#{after}"
end
- message
+ message.join
end
end
diff --git a/app/models/project_services/chat_message/base_message.rb b/app/models/project_services/chat_message/base_message.rb
index f710fa85b5d..8c68ddc40f2 100644
--- a/app/models/project_services/chat_message/base_message.rb
+++ b/app/models/project_services/chat_message/base_message.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'slack-notifier'
module ChatMessage
diff --git a/app/models/project_services/chat_message/issue_message.rb b/app/models/project_services/chat_message/issue_message.rb
index 3273f41dbd2..0cdcfcf0237 100644
--- a/app/models/project_services/chat_message/issue_message.rb
+++ b/app/models/project_services/chat_message/issue_message.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ChatMessage
class IssueMessage < BaseMessage
attr_reader :title
diff --git a/app/models/project_services/chat_message/merge_message.rb b/app/models/project_services/chat_message/merge_message.rb
index f412b6833d9..58631e09538 100644
--- a/app/models/project_services/chat_message/merge_message.rb
+++ b/app/models/project_services/chat_message/merge_message.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ChatMessage
class MergeMessage < BaseMessage
attr_reader :merge_request_iid
diff --git a/app/models/project_services/chat_message/note_message.rb b/app/models/project_services/chat_message/note_message.rb
index 7f9486132e6..741474fb27b 100644
--- a/app/models/project_services/chat_message/note_message.rb
+++ b/app/models/project_services/chat_message/note_message.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ChatMessage
class NoteMessage < BaseMessage
attr_reader :note
diff --git a/app/models/project_services/chat_message/pipeline_message.rb b/app/models/project_services/chat_message/pipeline_message.rb
index 96fd23aede3..62aec4351db 100644
--- a/app/models/project_services/chat_message/pipeline_message.rb
+++ b/app/models/project_services/chat_message/pipeline_message.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ChatMessage
class PipelineMessage < BaseMessage
attr_reader :ref_type
diff --git a/app/models/project_services/chat_message/push_message.rb b/app/models/project_services/chat_message/push_message.rb
index 8d599c5f116..82be33a12a1 100644
--- a/app/models/project_services/chat_message/push_message.rb
+++ b/app/models/project_services/chat_message/push_message.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ChatMessage
class PushMessage < BaseMessage
attr_reader :after
diff --git a/app/models/project_services/chat_message/wiki_page_message.rb b/app/models/project_services/chat_message/wiki_page_message.rb
index d84b80f2de2..b605d289278 100644
--- a/app/models/project_services/chat_message/wiki_page_message.rb
+++ b/app/models/project_services/chat_message/wiki_page_message.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ChatMessage
class WikiPageMessage < BaseMessage
attr_reader :title
diff --git a/app/models/project_services/chat_notification_service.rb b/app/models/project_services/chat_notification_service.rb
index a60b4c7fd0d..c10ee07ccf4 100644
--- a/app/models/project_services/chat_notification_service.rb
+++ b/app/models/project_services/chat_notification_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Base class for Chat notifications services
# This class is not meant to be used directly, but only to inherit from.
class ChatNotificationService < Service
diff --git a/app/models/project_services/ci_service.rb b/app/models/project_services/ci_service.rb
index 82979c8bd34..f0ef2d925ab 100644
--- a/app/models/project_services/ci_service.rb
+++ b/app/models/project_services/ci_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Base class for CI services
# List methods you need to implement to get your CI service
# working with GitLab Merge Requests
diff --git a/app/models/project_services/custom_issue_tracker_service.rb b/app/models/project_services/custom_issue_tracker_service.rb
index 456c7f5cee2..b8f8072869c 100644
--- a/app/models/project_services/custom_issue_tracker_service.rb
+++ b/app/models/project_services/custom_issue_tracker_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class CustomIssueTrackerService < IssueTrackerService
validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated?
diff --git a/app/models/project_services/deployment_service.rb b/app/models/project_services/deployment_service.rb
index 5b8320158fc..6dae4f3a4a6 100644
--- a/app/models/project_services/deployment_service.rb
+++ b/app/models/project_services/deployment_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Base class for deployment services
#
# These services integrate with a deployment solution like Kubernetes/OpenShift,
diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb
index ab4e46da89f..158ae0bf255 100644
--- a/app/models/project_services/drone_ci_service.rb
+++ b/app/models/project_services/drone_ci_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class DroneCiService < CiService
include ReactiveService
diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb
index b604d860a87..fb73d430fb1 100644
--- a/app/models/project_services/emails_on_push_service.rb
+++ b/app/models/project_services/emails_on_push_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class EmailsOnPushService < Service
boolean_accessor :send_from_committer_email
boolean_accessor :disable_diffs
diff --git a/app/models/project_services/external_wiki_service.rb b/app/models/project_services/external_wiki_service.rb
index a4b1ef09e93..d2835c6ac82 100644
--- a/app/models/project_services/external_wiki_service.rb
+++ b/app/models/project_services/external_wiki_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ExternalWikiService < Service
prop_accessor :external_wiki_url
diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb
index da01ac1b7cf..2545df06f6b 100644
--- a/app/models/project_services/flowdock_service.rb
+++ b/app/models/project_services/flowdock_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "flowdock-git-hook"
# Flow dock depends on Grit to compute the number of commits between two given
diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb
deleted file mode 100644
index 8a6b0ed1a5f..00000000000
--- a/app/models/project_services/gemnasium_service.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-require "gemnasium/gitlab_service"
-
-class GemnasiumService < Service
- prop_accessor :token, :api_key
- validates :token, :api_key, presence: true, if: :activated?
- validate :deprecation_validation
-
- def title
- 'Gemnasium'
- end
-
- def description
- 'Gemnasium monitors your project dependencies and alerts you about updates and security vulnerabilities.'
- end
-
- def self.to_param
- 'gemnasium'
- end
-
- def fields
- [
- { type: 'text', name: 'api_key', placeholder: 'Your personal API KEY on gemnasium.com ', required: true },
- { type: 'text', name: 'token', placeholder: 'The project\'s slug on gemnasium.com', required: true }
- ]
- end
-
- def self.supported_events
- %w(push)
- end
-
- def deprecated?
- true
- end
-
- def deprecation_message
- "Gemnasium has been acquired by GitLab in January 2018. Since May 15, 2018, the service provided by Gemnasium is no longer available."
- end
-
- def deprecation_validation
- errors[:base] << deprecation_message
- end
-
- def execute(data)
- return unless supported_events.include?(data[:object_kind])
-
- # Gitaly: this class will be removed https://gitlab.com/gitlab-org/gitlab-ee/issues/6010
- repo_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- project.repository.path_to_repo
- end
-
- Gemnasium::GitlabService.execute(
- ref: data[:ref],
- before: data[:before],
- after: data[:after],
- token: token,
- api_key: api_key,
- repo: repo_path
- )
- end
-end
diff --git a/app/models/project_services/gitlab_issue_tracker_service.rb b/app/models/project_services/gitlab_issue_tracker_service.rb
index 16e32a4139e..fa9abf58e62 100644
--- a/app/models/project_services/gitlab_issue_tracker_service.rb
+++ b/app/models/project_services/gitlab_issue_tracker_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class GitlabIssueTrackerService < IssueTrackerService
include Gitlab::Routing
diff --git a/app/models/project_services/hangouts_chat_service.rb b/app/models/project_services/hangouts_chat_service.rb
index a8512c5f57c..272cd0f4e47 100644
--- a/app/models/project_services/hangouts_chat_service.rb
+++ b/app/models/project_services/hangouts_chat_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'hangouts_chat'
class HangoutsChatService < ChatNotificationService
diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
index dce878e485f..66012f0da99 100644
--- a/app/models/project_services/hipchat_service.rb
+++ b/app/models/project_services/hipchat_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class HipchatService < Service
include ActionView::Helpers::SanitizeHelper
@@ -108,7 +110,7 @@ class HipchatService < Service
before = push[:before]
after = push[:after]
- message = ""
+ message = []
message << "#{push[:user_name]} "
if Gitlab::Git.blank_ref?(before)
@@ -132,7 +134,7 @@ class HipchatService < Service
end
end
- message
+ message.join
end
def markdown(text, options = {})
@@ -165,11 +167,11 @@ class HipchatService < Service
description = obj_attr[:description]
issue_link = "<a href=\"#{issue_url}\">issue ##{issue_iid}</a>"
- message = "#{user_name} #{state} #{issue_link} in #{project_link}: <b>#{title}</b>"
+ message = ["#{user_name} #{state} #{issue_link} in #{project_link}: <b>#{title}</b>"]
message << "<pre>#{markdown(description)}</pre>"
- message
+ message.join
end
def create_merge_request_message(data)
@@ -184,12 +186,11 @@ class HipchatService < Service
merge_request_url = "#{project_url}/merge_requests/#{merge_request_id}"
merge_request_link = "<a href=\"#{merge_request_url}\">merge request !#{merge_request_id}</a>"
- message = "#{user_name} #{state} #{merge_request_link} in " \
- "#{project_link}: <b>#{title}</b>"
+ message = ["#{user_name} #{state} #{merge_request_link} in " \
+ "#{project_link}: <b>#{title}</b>"]
message << "<pre>#{markdown(description)}</pre>"
-
- message
+ message.join
end
def format_title(title)
@@ -235,12 +236,11 @@ class HipchatService < Service
end
subject_html = "<a href=\"#{note_url}\">#{subject_type} #{subject_desc}</a>"
- message = "#{user_name} commented on #{subject_html} in #{project_link}: "
+ message = ["#{user_name} commented on #{subject_html} in #{project_link}: "]
message << title
message << "<pre>#{markdown(note, ref: commit_id)}</pre>"
-
- message
+ message.join
end
def create_pipeline_message(data)
diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb
index 27bdf708c80..a783a314071 100644
--- a/app/models/project_services/irker_service.rb
+++ b/app/models/project_services/irker_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'uri'
class IrkerService < Service
diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb
index df6dcd90985..c7520d766a8 100644
--- a/app/models/project_services/issue_tracker_service.rb
+++ b/app/models/project_services/issue_tracker_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class IssueTrackerService < Service
validate :one_issue_tracker, if: :activated?, on: :manual_change
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 82d438d5378..cc98b3f5a41 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class JiraService < IssueTrackerService
include Gitlab::Routing
include ApplicationHelper
diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb
index 722642f6da7..bda1f67b8ff 100644
--- a/app/models/project_services/kubernetes_service.rb
+++ b/app/models/project_services/kubernetes_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
##
# NOTE:
# We'll move this class to Clusters::Platforms::Kubernetes, which contains exactly the same logic.
diff --git a/app/models/project_services/mattermost_service.rb b/app/models/project_services/mattermost_service.rb
index 0362ed172c7..b8bc83b870e 100644
--- a/app/models/project_services/mattermost_service.rb
+++ b/app/models/project_services/mattermost_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class MattermostService < ChatNotificationService
def title
'Mattermost notifications'
diff --git a/app/models/project_services/mattermost_slash_commands_service.rb b/app/models/project_services/mattermost_slash_commands_service.rb
index 227d430083d..ca324f68d2d 100644
--- a/app/models/project_services/mattermost_slash_commands_service.rb
+++ b/app/models/project_services/mattermost_slash_commands_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class MattermostSlashCommandsService < SlashCommandsService
include TriggersHelper
diff --git a/app/models/project_services/microsoft_teams_service.rb b/app/models/project_services/microsoft_teams_service.rb
index 99500caec0e..5b0e5fed092 100644
--- a/app/models/project_services/microsoft_teams_service.rb
+++ b/app/models/project_services/microsoft_teams_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class MicrosoftTeamsService < ChatNotificationService
def title
'Microsoft Teams Notification'
diff --git a/app/models/project_services/mock_ci_service.rb b/app/models/project_services/mock_ci_service.rb
index b89dc07a73e..6883976f0c8 100644
--- a/app/models/project_services/mock_ci_service.rb
+++ b/app/models/project_services/mock_ci_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# For an example companion mocking service, see https://gitlab.com/gitlab-org/gitlab-mock-ci-service
class MockCiService < CiService
ALLOWED_STATES = %w[failed canceled running pending success success_with_warnings skipped not_found].freeze
diff --git a/app/models/project_services/mock_deployment_service.rb b/app/models/project_services/mock_deployment_service.rb
index 59a3811ce5d..7ab1687f8ba 100644
--- a/app/models/project_services/mock_deployment_service.rb
+++ b/app/models/project_services/mock_deployment_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class MockDeploymentService < DeploymentService
def title
'Mock deployment'
diff --git a/app/models/project_services/mock_monitoring_service.rb b/app/models/project_services/mock_monitoring_service.rb
index ed0318c6b27..bcf8f1df5da 100644
--- a/app/models/project_services/mock_monitoring_service.rb
+++ b/app/models/project_services/mock_monitoring_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class MockMonitoringService < MonitoringService
def title
'Mock monitoring'
diff --git a/app/models/project_services/monitoring_service.rb b/app/models/project_services/monitoring_service.rb
index 9af68b4e821..1b530a8247b 100644
--- a/app/models/project_services/monitoring_service.rb
+++ b/app/models/project_services/monitoring_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Base class for monitoring services
#
# These services integrate with a deployment solution like Prometheus
diff --git a/app/models/project_services/packagist_service.rb b/app/models/project_services/packagist_service.rb
index ba62a5b7ac0..003884bb7ac 100644
--- a/app/models/project_services/packagist_service.rb
+++ b/app/models/project_services/packagist_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class PackagistService < Service
prop_accessor :username, :token, :server
diff --git a/app/models/project_services/pipelines_email_service.rb b/app/models/project_services/pipelines_email_service.rb
index 4cf149ac044..6f39a5e6e83 100644
--- a/app/models/project_services/pipelines_email_service.rb
+++ b/app/models/project_services/pipelines_email_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class PipelinesEmailService < Service
prop_accessor :recipients
boolean_accessor :notify_only_broken_pipelines
diff --git a/app/models/project_services/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb
index 3476e7d2283..617e502b639 100644
--- a/app/models/project_services/pivotaltracker_service.rb
+++ b/app/models/project_services/pivotaltracker_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class PivotaltrackerService < Service
API_ENDPOINT = 'https://www.pivotaltracker.com/services/v5/source_commits'.freeze
diff --git a/app/models/project_services/prometheus_service.rb b/app/models/project_services/prometheus_service.rb
index df4254e0523..509e5b6089b 100644
--- a/app/models/project_services/prometheus_service.rb
+++ b/app/models/project_services/prometheus_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class PrometheusService < MonitoringService
include PrometheusAdapter
diff --git a/app/models/project_services/pushover_service.rb b/app/models/project_services/pushover_service.rb
index 8777a44b72f..4e48c348b45 100644
--- a/app/models/project_services/pushover_service.rb
+++ b/app/models/project_services/pushover_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class PushoverService < Service
BASE_URI = 'https://api.pushover.net/1'.freeze
@@ -79,7 +81,7 @@ class PushoverService < Service
end
if data[:total_commits_count] > 0
- message << "\nTotal commits count: #{data[:total_commits_count]}"
+ message = [message, "Total commits count: #{data[:total_commits_count]}"].join("\n")
end
pushover_data = {
diff --git a/app/models/project_services/redmine_service.rb b/app/models/project_services/redmine_service.rb
index 3721093a6d1..a80be4b06da 100644
--- a/app/models/project_services/redmine_service.rb
+++ b/app/models/project_services/redmine_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class RedmineService < IssueTrackerService
validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated?
diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb
index 71da0af75f6..482808255f9 100644
--- a/app/models/project_services/slack_service.rb
+++ b/app/models/project_services/slack_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class SlackService < ChatNotificationService
def title
'Slack notifications'
diff --git a/app/models/project_services/slack_slash_commands_service.rb b/app/models/project_services/slack_slash_commands_service.rb
index 1c3892a3f75..6c82e088231 100644
--- a/app/models/project_services/slack_slash_commands_service.rb
+++ b/app/models/project_services/slack_slash_commands_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class SlackSlashCommandsService < SlashCommandsService
include TriggersHelper
diff --git a/app/models/project_services/slash_commands_service.rb b/app/models/project_services/slash_commands_service.rb
index 37ea45109ae..e3ab60adefd 100644
--- a/app/models/project_services/slash_commands_service.rb
+++ b/app/models/project_services/slash_commands_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Base class for Chat services
# This class is not meant to be used directly, but only to inherrit from.
class SlashCommandsService < Service
diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb
index 802678147cf..eeeff5e802a 100644
--- a/app/models/project_services/teamcity_service.rb
+++ b/app/models/project_services/teamcity_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class TeamcityService < CiService
include ReactiveService
diff --git a/app/models/protected_branch/merge_access_level.rb b/app/models/protected_branch/merge_access_level.rb
index e8d35ac326f..b0d5c64e931 100644
--- a/app/models/protected_branch/merge_access_level.rb
+++ b/app/models/protected_branch/merge_access_level.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base
include ProtectedBranchAccess
end
diff --git a/app/models/protected_branch/push_access_level.rb b/app/models/protected_branch/push_access_level.rb
index 7a2e9e5ec5d..b2a88229853 100644
--- a/app/models/protected_branch/push_access_level.rb
+++ b/app/models/protected_branch/push_access_level.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ProtectedBranch::PushAccessLevel < ActiveRecord::Base
include ProtectedBranchAccess
end
diff --git a/app/models/protected_tag/create_access_level.rb b/app/models/protected_tag/create_access_level.rb
index 6b6ab3d8279..b06e55fb5dd 100644
--- a/app/models/protected_tag/create_access_level.rb
+++ b/app/models/protected_tag/create_access_level.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ProtectedTag::CreateAccessLevel < ActiveRecord::Base
include ProtectedTagAccess
diff --git a/app/models/remote_mirror.rb b/app/models/remote_mirror.rb
index 833faf3bc82..c1f53b5da4f 100644
--- a/app/models/remote_mirror.rb
+++ b/app/models/remote_mirror.rb
@@ -150,6 +150,15 @@ class RemoteMirror < ActiveRecord::Base
result.to_s
end
+ def ensure_remote!
+ return unless project
+ return unless remote_name && url
+
+ # If this fails or the remote already exists, we won't know due to
+ # https://gitlab.com/gitlab-org/gitaly/issues/1317
+ project.repository.add_remote(remote_name, url)
+ end
+
private
def raw
diff --git a/app/models/repository_language.rb b/app/models/repository_language.rb
index f467d4eafa3..b18142a2ac4 100644
--- a/app/models/repository_language.rb
+++ b/app/models/repository_language.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class RepositoryLanguage < ActiveRecord::Base
belongs_to :project
belongs_to :programming_language
diff --git a/app/models/site_statistic.rb b/app/models/site_statistic.rb
index 9c9c3172fe6..48324570f0b 100644
--- a/app/models/site_statistic.rb
+++ b/app/models/site_statistic.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class SiteStatistic < ActiveRecord::Base
# prevents the creation of multiple rows
default_value_for :id, 1
@@ -47,7 +49,7 @@ class SiteStatistic < ActiveRecord::Base
#
# @return [SiteStatistic] record with tracked information
def self.fetch
- SiteStatistic.transaction(requires_new: true) do
+ transaction(requires_new: true) do
SiteStatistic.first_or_create!
end
rescue ActiveRecord::RecordNotUnique
diff --git a/app/models/storage/hashed_project.rb b/app/models/storage/hashed_project.rb
index 26b4b78ac64..90710f73fd3 100644
--- a/app/models/storage/hashed_project.rb
+++ b/app/models/storage/hashed_project.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Storage
class HashedProject
attr_accessor :project
diff --git a/app/models/storage/legacy_project.rb b/app/models/storage/legacy_project.rb
index 27cb388c702..9f6f19acb41 100644
--- a/app/models/storage/legacy_project.rb
+++ b/app/models/storage/legacy_project.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Storage
class LegacyProject
attr_accessor :project
diff --git a/app/models/system_note_metadata.rb b/app/models/system_note_metadata.rb
index c5c77bc8333..376ef673ca8 100644
--- a/app/models/system_note_metadata.rb
+++ b/app/models/system_note_metadata.rb
@@ -15,7 +15,7 @@ class SystemNoteMetadata < ActiveRecord::Base
commit description merge confidential visible label assignee cross_reference
title time_tracking branch milestone discussion task moved
opened closed merged duplicate locked unlocked
- outdated
+ outdated tag
].freeze
validates :note, presence: true
diff --git a/app/models/user.rb b/app/models/user.rb
index 37f2e8b680e..f21ca1c569f 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -19,6 +19,7 @@ class User < ActiveRecord::Base
include BulkMemberAccessLoad
include BlocksJsonSerialization
include WithUploads
+ include OptionallySearch
DEFAULT_NOTIFICATION_LEVEL = :participating
@@ -101,6 +102,10 @@ class User < ActiveRecord::Base
has_many :groups, through: :group_members
has_many :owned_groups, -> { where(members: { access_level: Gitlab::Access::OWNER }) }, through: :group_members, source: :group
has_many :maintainers_groups, -> { where(members: { access_level: Gitlab::Access::MAINTAINER }) }, through: :group_members, source: :group
+ has_many :owned_or_maintainers_groups,
+ -> { where(members: { access_level: [Gitlab::Access::MAINTAINER, Gitlab::Access::OWNER] }) },
+ through: :group_members,
+ source: :group
alias_attribute :masters_groups, :maintainers_groups
# Projects
@@ -249,18 +254,51 @@ class User < ActiveRecord::Base
scope :external, -> { where(external: true) }
scope :active, -> { with_state(:active).non_internal }
scope :without_projects, -> { joins('LEFT JOIN project_authorizations ON users.id = project_authorizations.user_id').where(project_authorizations: { user_id: nil }) }
- scope :todo_authors, ->(user_id, state) { where(id: Todo.where(user_id: user_id, state: state).select(:author_id)) }
scope :order_recent_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'DESC')) }
scope :order_oldest_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'ASC')) }
scope :confirmed, -> { where.not(confirmed_at: nil) }
- def self.with_two_factor_indistinct
- joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id")
- .where("u2f.id IS NOT NULL OR users.otp_required_for_login = ?", true)
+ # Limits the users to those that have TODOs, optionally in the given state.
+ #
+ # user - The user to get the todos for.
+ #
+ # with_todos - If we should limit the result set to users that are the
+ # authors of todos.
+ #
+ # todo_state - An optional state to require the todos to be in.
+ def self.limit_to_todo_authors(user: nil, with_todos: false, todo_state: nil)
+ if user && with_todos
+ where(id: Todo.where(user: user, state: todo_state).select(:author_id))
+ else
+ all
+ end
+ end
+
+ # Returns a relation that optionally includes the given user.
+ #
+ # user_id - The ID of the user to include.
+ def self.union_with_user(user_id = nil)
+ if user_id.present?
+ union = Gitlab::SQL::Union.new([all, User.unscoped.where(id: user_id)])
+
+ # We use "unscoped" here so that any inner conditions are not repeated for
+ # the outer query, which would be redundant.
+ User.unscoped.from("(#{union.to_sql}) #{User.table_name}")
+ else
+ all
+ end
end
def self.with_two_factor
- with_two_factor_indistinct.distinct(arel_table[:id])
+ with_u2f_registrations = <<-SQL
+ EXISTS (
+ SELECT *
+ FROM u2f_registrations AS u2f
+ WHERE u2f.user_id = users.id
+ ) OR users.otp_required_for_login = ?
+ SQL
+
+ where(with_u2f_registrations, true)
end
def self.without_two_factor
@@ -361,6 +399,18 @@ class User < ActiveRecord::Base
).reorder(order % { query: ActiveRecord::Base.connection.quote(query) }, :name)
end
+ # Limits the result set to users _not_ in the given query/list of IDs.
+ #
+ # users - The list of users to ignore. This can be an
+ # `ActiveRecord::Relation`, or an Array.
+ def where_not_in(users = nil)
+ users ? where.not(id: users) : all
+ end
+
+ def reorder_by_name
+ reorder(:name)
+ end
+
# searches user by given pattern
# it compares name, email, username fields and user's secondary emails with given pattern
# This method uses ILIKE on PostgreSQL and LIKE on MySQL.
@@ -512,7 +562,7 @@ class User < ActiveRecord::Base
otp_grace_period_started_at: nil,
otp_backup_codes: nil
)
- self.u2f_registrations.destroy_all
+ self.u2f_registrations.destroy_all # rubocop: disable DestroyAll
end
end
@@ -982,15 +1032,7 @@ class User < ActiveRecord::Base
end
def manageable_groups
- union_sql = Gitlab::SQL::Union.new([owned_groups.select(:id), maintainers_groups.select(:id)]).to_sql
-
- # Update this line to not use raw SQL when migrated to Rails 5.2.
- # Either ActiveRecord or Arel constructions are fine.
- # This was replaced with the raw SQL construction because of bugs in the arel gem.
- # Bugs were fixed in arel 9.0.0 (Rails 5.2).
- owned_and_maintainer_groups = Group.where("namespaces.id IN (#{union_sql})") # rubocop:disable GitlabSecurity/SqlInjection
-
- Gitlab::GroupHierarchy.new(owned_and_maintainer_groups).base_and_descendants
+ Gitlab::GroupHierarchy.new(owned_or_maintainers_groups).base_and_descendants
end
def namespaces
@@ -1244,11 +1286,6 @@ class User < ActiveRecord::Base
!terms_accepted?
end
- def owned_or_maintainers_groups
- union = Gitlab::SQL::Union.new([owned_groups, maintainers_groups])
- Group.from("(#{union.to_sql}) namespaces")
- end
-
# @deprecated
alias_method :owned_or_masters_groups, :owned_or_maintainers_groups
diff --git a/app/policies/commit_policy.rb b/app/policies/commit_policy.rb
new file mode 100644
index 00000000000..67e9bc12804
--- /dev/null
+++ b/app/policies/commit_policy.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class CommitPolicy < BasePolicy
+ delegate { @subject.project }
+end
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index f52a3bad77d..00c58f15013 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -251,6 +251,7 @@ class ProjectPolicy < BasePolicy
enable :update_pages
enable :read_cluster
enable :create_cluster
+ enable :create_environment_terminal
end
rule { (mirror_available & can?(:admin_project)) | admin }.enable :admin_remote_mirror
diff --git a/app/presenters/ci/build_runner_presenter.rb b/app/presenters/ci/build_runner_presenter.rb
index 02f6c5bdf81..880218e2727 100644
--- a/app/presenters/ci/build_runner_presenter.rb
+++ b/app/presenters/ci/build_runner_presenter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Ci
class BuildRunnerPresenter < SimpleDelegator
def artifacts
diff --git a/app/serializers/environment_entity.rb b/app/serializers/environment_entity.rb
index b18e9706db6..07a13c33b89 100644
--- a/app/serializers/environment_entity.rb
+++ b/app/serializers/environment_entity.rb
@@ -23,9 +23,8 @@ class EnvironmentEntity < Grape::Entity
stop_project_environment_path(environment.project, environment)
end
- expose :terminal_path, if: ->(environment, _) { environment.has_terminals? } do |environment|
- can?(request.current_user, :admin_environment, environment.project) &&
- terminal_project_environment_path(environment.project, environment)
+ expose :terminal_path, if: ->(*) { environment.has_terminals? && can_access_terminal? } do |environment|
+ terminal_project_environment_path(environment.project, environment)
end
expose :folder_path do |environment|
@@ -40,7 +39,13 @@ class EnvironmentEntity < Grape::Entity
private
+ alias_method :environment, :object
+
def current_user
request.current_user
end
+
+ def can_access_terminal?
+ can?(request.current_user, :create_environment_terminal, environment)
+ end
end
diff --git a/app/serializers/move_to_project_entity.rb b/app/serializers/move_to_project_entity.rb
new file mode 100644
index 00000000000..dac1124b0b3
--- /dev/null
+++ b/app/serializers/move_to_project_entity.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+class MoveToProjectEntity < Grape::Entity
+ expose :id
+ expose :name_with_namespace
+end
diff --git a/app/serializers/move_to_project_serializer.rb b/app/serializers/move_to_project_serializer.rb
new file mode 100644
index 00000000000..6a59317505c
--- /dev/null
+++ b/app/serializers/move_to_project_serializer.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class MoveToProjectSerializer < BaseSerializer
+ entity MoveToProjectEntity
+end
diff --git a/app/serializers/project_mirror_serializer.rb b/app/serializers/project_mirror_serializer.rb
new file mode 100644
index 00000000000..6a9462aa7cb
--- /dev/null
+++ b/app/serializers/project_mirror_serializer.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class ProjectMirrorSerializer < BaseSerializer
+ entity ProjectMirrorEntity
+end
diff --git a/app/serializers/test_case_entity.rb b/app/serializers/test_case_entity.rb
index 5c1cbf37182..ec60055ba5b 100644
--- a/app/serializers/test_case_entity.rb
+++ b/app/serializers/test_case_entity.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class TestCaseEntity < Grape::Entity
expose :status
expose :name
diff --git a/app/serializers/test_reports_comparer_entity.rb b/app/serializers/test_reports_comparer_entity.rb
index b95d820d093..d7a3dd34fdc 100644
--- a/app/serializers/test_reports_comparer_entity.rb
+++ b/app/serializers/test_reports_comparer_entity.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class TestReportsComparerEntity < Grape::Entity
expose :total_status, as: :status
diff --git a/app/serializers/test_reports_comparer_serializer.rb b/app/serializers/test_reports_comparer_serializer.rb
index a739858efb2..7fb8d28b09a 100644
--- a/app/serializers/test_reports_comparer_serializer.rb
+++ b/app/serializers/test_reports_comparer_serializer.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class TestReportsComparerSerializer < BaseSerializer
entity TestReportsComparerEntity
end
diff --git a/app/serializers/test_suite_comparer_entity.rb b/app/serializers/test_suite_comparer_entity.rb
index a3965ba3930..9fa3a897ebe 100644
--- a/app/serializers/test_suite_comparer_entity.rb
+++ b/app/serializers/test_suite_comparer_entity.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class TestSuiteComparerEntity < Grape::Entity
expose :name
expose :total_status, as: :status
diff --git a/app/services/ci/enqueue_build_service.rb b/app/services/ci/enqueue_build_service.rb
new file mode 100644
index 00000000000..8140651d980
--- /dev/null
+++ b/app/services/ci/enqueue_build_service.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+module Ci
+ class EnqueueBuildService < BaseService
+ def execute(build)
+ build.enqueue
+ end
+ end
+end
diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb
index cda9bbff3b4..cafee76a33c 100644
--- a/app/services/ci/process_pipeline_service.rb
+++ b/app/services/ci/process_pipeline_service.rb
@@ -37,7 +37,7 @@ module Ci
def process_build(build, current_status)
if valid_statuses_for_when(build.when).include?(current_status)
- build.action? ? build.actionize : build.enqueue
+ build.action? ? build.actionize : enqueue_build(build)
true
else
build.skip
@@ -93,5 +93,9 @@ module Ci
.where.not(id: latest_statuses.map(&:first))
.update_all(retried: true) if latest_statuses.any?
end
+
+ def enqueue_build(build)
+ Ci::EnqueueBuildService.new(project, @user).execute(build)
+ end
end
end
diff --git a/app/services/commits/tag_service.rb b/app/services/commits/tag_service.rb
new file mode 100644
index 00000000000..7961ba4d3c4
--- /dev/null
+++ b/app/services/commits/tag_service.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Commits
+ class TagService < BaseService
+ def execute(commit)
+ unless params[:tag_name]
+ return error('Missing parameter tag_name')
+ end
+
+ tag_name = params[:tag_name]
+ message = params[:tag_message]
+ release_description = nil
+
+ result = Tags::CreateService
+ .new(commit.project, current_user)
+ .execute(tag_name, commit.sha, message, release_description)
+
+ if result[:status] == :success
+ tag = result[:tag]
+ SystemNoteService.tag_commit(commit, commit.project, current_user, tag.name)
+ end
+
+ result
+ end
+ end
+end
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index 637c1df4ad9..26e90e8cf8c 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -140,7 +140,6 @@ class GitPushService < BaseService
EventCreateService.new.push(project, current_user, build_push_data)
Ci::CreatePipelineService.new(project, current_user, build_push_data).execute(:push)
- SystemHookPushWorker.perform_async(build_push_data.dup, :push_hooks)
project.execute_hooks(build_push_data.dup, :push_hooks)
project.execute_services(build_push_data.dup, :push_hooks)
@@ -159,7 +158,7 @@ class GitPushService < BaseService
end
def process_default_branch
- offset = [push_commits_count - PROCESS_COMMIT_LIMIT, 0].max
+ offset = [push_commits_count_for_ref - PROCESS_COMMIT_LIMIT, 0].max
@push_commits = project.repository.commits(params[:newrev], offset: offset, limit: PROCESS_COMMIT_LIMIT)
project.after_create_default_branch
@@ -173,7 +172,7 @@ class GitPushService < BaseService
params[:newrev],
params[:ref],
@push_commits,
- commits_count: push_commits_count)
+ commits_count: commits_count)
end
def push_to_existing_branch?
@@ -214,8 +213,14 @@ class GitPushService < BaseService
end
end
- def push_commits_count
- strong_memoize(:push_commits_count) do
+ def commits_count
+ return push_commits_count_for_ref if default_branch? && push_to_new_branch?
+
+ Array(@push_commits).size
+ end
+
+ def push_commits_count_for_ref
+ strong_memoize(:push_commits_count_for_ref) do
project.repository.commit_count_for_ref(params[:ref])
end
end
diff --git a/app/services/groups/destroy_service.rb b/app/services/groups/destroy_service.rb
index c4554ce45fb..12aeba4af71 100644
--- a/app/services/groups/destroy_service.rb
+++ b/app/services/groups/destroy_service.rb
@@ -2,6 +2,8 @@
module Groups
class DestroyService < Groups::BaseService
+ DestroyError = Class.new(StandardError)
+
def async_execute
job_id = GroupDestroyWorker.perform_async(group.id, current_user.id)
Rails.logger.info("User #{current_user.id} scheduled a deletion of group ID #{group.id} with job ID #{job_id}")
@@ -12,9 +14,8 @@ module Groups
group.projects.each do |project|
# Execute the destruction of the models immediately to ensure atomic cleanup.
- # Skip repository removal because we remove directory with namespace
- # that contain all these repositories
- ::Projects::DestroyService.new(project, current_user, skip_repo: project.legacy_storage?).execute
+ success = ::Projects::DestroyService.new(project, current_user).execute
+ raise DestroyError, "Project #{project.id} can't be deleted" unless success
end
group.children.each do |group|
diff --git a/app/services/issues/fetch_referenced_merge_requests_service.rb b/app/services/issues/fetch_referenced_merge_requests_service.rb
deleted file mode 100644
index 5e84f3c81c9..00000000000
--- a/app/services/issues/fetch_referenced_merge_requests_service.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-# frozen_string_literal: true
-
-module Issues
- class FetchReferencedMergeRequestsService < Issues::BaseService
- def execute(issue)
- referenced_merge_requests = issue.referenced_merge_requests(current_user)
- referenced_merge_requests = Gitlab::IssuableSorter.sort(project, referenced_merge_requests) { |i| i.iid.to_s }
- closed_by_merge_requests = issue.closed_by_merge_requests(current_user)
- closed_by_merge_requests = Gitlab::IssuableSorter.sort(project, closed_by_merge_requests) { |i| i.iid.to_s }
-
- [referenced_merge_requests, closed_by_merge_requests]
- end
- end
-end
diff --git a/app/services/issues/referenced_merge_requests_service.rb b/app/services/issues/referenced_merge_requests_service.rb
new file mode 100644
index 00000000000..40d78502697
--- /dev/null
+++ b/app/services/issues/referenced_merge_requests_service.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+module Issues
+ class ReferencedMergeRequestsService < Issues::BaseService
+ def execute(issue)
+ referenced = referenced_merge_requests(issue)
+ closed_by = closed_by_merge_requests(issue)
+ preloader = ActiveRecord::Associations::Preloader.new
+
+ preloader.preload(referenced + closed_by,
+ head_pipeline: { project: [:route, { namespace: :route }] })
+
+ [sort_by_iid(referenced), sort_by_iid(closed_by)]
+ end
+
+ def referenced_merge_requests(issue)
+ merge_requests = extract_merge_requests(issue)
+
+ cross_project_filter = -> (merge_requests) do
+ merge_requests.select { |mr| mr.target_project == project }
+ end
+
+ Ability.merge_requests_readable_by_user(
+ merge_requests,
+ current_user,
+ filters: {
+ read_cross_project: cross_project_filter
+ }
+ )
+ end
+
+ def closed_by_merge_requests(issue)
+ return [] unless issue.open?
+
+ merge_requests = extract_merge_requests(issue, filter: :system).select(&:open?)
+
+ return [] if merge_requests.empty?
+
+ ids = MergeRequestsClosingIssues.where(merge_request_id: merge_requests.map(&:id), issue_id: issue.id).pluck(:merge_request_id)
+ merge_requests.select { |mr| mr.id.in?(ids) }
+ end
+
+ private
+
+ def extract_merge_requests(issue, filter: nil)
+ ext = issue.all_references(current_user)
+ notes = issue_notes(issue)
+ notes = notes.select(&filter) if filter
+
+ notes.each do |note|
+ note.all_references(current_user, extractor: ext)
+ end
+
+ ext.merge_requests
+ end
+
+ def issue_notes(issue)
+ @issue_notes ||= {}
+ @issue_notes[issue] ||= issue.notes.includes(:author)
+ end
+
+ def sort_by_iid(merge_requests)
+ Gitlab::IssuableSorter.sort(project, merge_requests) { |mr| mr.iid.to_s }
+ end
+ end
+end
diff --git a/app/services/labels/promote_service.rb b/app/services/labels/promote_service.rb
index c0463052821..623a5f0950e 100644
--- a/app/services/labels/promote_service.rb
+++ b/app/services/labels/promote_service.rb
@@ -65,7 +65,7 @@ module Labels
end
def update_project_labels(label_ids)
- Label.where(id: label_ids).destroy_all
+ Label.where(id: label_ids).destroy_all # rubocop: disable DestroyAll
end
def clone_label_to_group_label(label)
diff --git a/app/services/milestones/promote_service.rb b/app/services/milestones/promote_service.rb
index 37aa6d3a9bc..660b4faaec0 100644
--- a/app/services/milestones/promote_service.rb
+++ b/app/services/milestones/promote_service.rb
@@ -73,7 +73,7 @@ module Milestones
end
def destroy_old_milestones(milestone)
- Milestone.where(id: milestone_ids_for_merge(milestone)).destroy_all
+ Milestone.where(id: milestone_ids_for_merge(milestone)).destroy_all # rubocop: disable DestroyAll
end
def group_project_ids
diff --git a/app/services/notes/quick_actions_service.rb b/app/services/notes/quick_actions_service.rb
index 7280449bb1c..4c14d834949 100644
--- a/app/services/notes/quick_actions_service.rb
+++ b/app/services/notes/quick_actions_service.rb
@@ -4,7 +4,8 @@ module Notes
class QuickActionsService < BaseService
UPDATE_SERVICES = {
'Issue' => Issues::UpdateService,
- 'MergeRequest' => MergeRequests::UpdateService
+ 'MergeRequest' => MergeRequests::UpdateService,
+ 'Commit' => Commits::TagService
}.freeze
def self.noteable_update_service(note)
diff --git a/app/services/notification_recipient_service.rb b/app/services/notification_recipient_service.rb
index 4389fd89538..5c0e8a35cb0 100644
--- a/app/services/notification_recipient_service.rb
+++ b/app/services/notification_recipient_service.rb
@@ -130,7 +130,7 @@ module NotificationRecipientService
end
def add_project_watchers
- add_recipients(project_watchers, :watch, nil)
+ add_recipients(project_watchers, :watch, nil) if project
end
def add_group_watchers
@@ -220,6 +220,8 @@ module NotificationRecipientService
end
class Default < Base
+ MENTION_TYPE_ACTIONS = [:new_issue, :new_merge_request].freeze
+
attr_reader :target
attr_reader :current_user
attr_reader :action
@@ -252,7 +254,7 @@ module NotificationRecipientService
add_subscribed_users
- if [:new_issue, :new_merge_request].include?(custom_action)
+ if self.class.mention_type_actions.include?(custom_action)
# These will all be participants as well, but adding with the :mention
# type ensures that users with the mention notification level will
# receive them, too.
@@ -279,10 +281,14 @@ module NotificationRecipientService
end
# Build event key to search on custom notification level
- # Check NotificationSetting::EMAIL_EVENTS
+ # Check NotificationSetting.email_events
def custom_action
@custom_action ||= "#{action}_#{target.class.model_name.name.underscore}".to_sym
end
+
+ def self.mention_type_actions
+ MENTION_TYPE_ACTIONS.dup
+ end
end
class NewNote < Base
diff --git a/app/services/preview_markdown_service.rb b/app/services/preview_markdown_service.rb
index a15ee4911ef..11b996ed4b6 100644
--- a/app/services/preview_markdown_service.rb
+++ b/app/services/preview_markdown_service.rb
@@ -16,7 +16,7 @@ class PreviewMarkdownService < BaseService
private
def explain_quick_actions(text)
- return text, [] unless %w(Issue MergeRequest).include?(commands_target_type)
+ return text, [] unless %w(Issue MergeRequest Commit).include?(commands_target_type)
quick_actions_service = QuickActions::InterpretService.new(project, current_user)
quick_actions_service.explain(text, find_commands_target)
@@ -29,13 +29,9 @@ class PreviewMarkdownService < BaseService
end
def find_commands_target
- if commands_target_id.present?
- finder = commands_target_type == 'Issue' ? IssuesFinder : MergeRequestsFinder
- finder.new(current_user, project_id: project.id).find(commands_target_id)
- else
- collection = commands_target_type == 'Issue' ? project.issues : project.merge_requests
- collection.build
- end
+ QuickActions::TargetService
+ .new(project, current_user)
+ .execute(commands_target_type, commands_target_id)
end
def commands_target_type
diff --git a/app/services/projects/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb
index 10eb2cea4a2..5286b92ab6b 100644
--- a/app/services/projects/autocomplete_service.rb
+++ b/app/services/projects/autocomplete_service.rb
@@ -47,15 +47,7 @@ module Projects
end
def commands(noteable, type)
- noteable ||=
- case type
- when 'Issue'
- @project.issues.build
- when 'MergeRequest'
- @project.merge_requests.build
- end
-
- return [] unless noteable&.is_a?(Issuable)
+ return [] unless noteable
QuickActions::InterpretService.new(project, current_user).available_commands(noteable)
end
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index 46a8a5e4d98..76e22507698 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -83,9 +83,6 @@ module Projects
end
def remove_repository(path)
- # Skip repository removal. We use this flag when remove user or group
- return true if params[:skip_repo] == true
-
# There is a possibility project does not have repository or wiki
return true unless repo_exists?(path)
diff --git a/app/services/projects/detect_repository_languages_service.rb b/app/services/projects/detect_repository_languages_service.rb
index 4b4108de231..3488b9ce47e 100644
--- a/app/services/projects/detect_repository_languages_service.rb
+++ b/app/services/projects/detect_repository_languages_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Projects
class DetectRepositoryLanguagesService < BaseService
attr_reader :detected_repository_languages, :programming_languages
diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb
index 33ad2120a75..cbbb88a9410 100644
--- a/app/services/projects/fork_service.rb
+++ b/app/services/projects/fork_service.rb
@@ -17,6 +17,14 @@ module Projects
link_fork_network(fork_to_project)
+ # A forked project stores its LFS objects in the `forked_from_project`.
+ # So the LFS objects become inaccessible, and therefore delete them from
+ # the database so they'll get cleaned up.
+ #
+ # TODO: refactor this to get the correct lfs objects when implementing
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/39769
+ fork_to_project.lfs_objects_projects.delete_all
+
fork_to_project
end
diff --git a/app/services/projects/move_deploy_keys_projects_service.rb b/app/services/projects/move_deploy_keys_projects_service.rb
index 40a22837eaf..9f3f44f30ea 100644
--- a/app/services/projects/move_deploy_keys_projects_service.rb
+++ b/app/services/projects/move_deploy_keys_projects_service.rb
@@ -27,7 +27,7 @@ module Projects
end
def remove_remaining_deploy_keys_projects
- source_project.deploy_keys_projects.destroy_all
+ source_project.deploy_keys_projects.destroy_all # rubocop: disable DestroyAll
end
end
end
diff --git a/app/services/projects/move_lfs_objects_projects_service.rb b/app/services/projects/move_lfs_objects_projects_service.rb
index a5099519594..f78546a1e9c 100644
--- a/app/services/projects/move_lfs_objects_projects_service.rb
+++ b/app/services/projects/move_lfs_objects_projects_service.rb
@@ -21,7 +21,7 @@ module Projects
end
def remove_remaining_lfs_objects_project
- source_project.lfs_objects_projects.destroy_all
+ source_project.lfs_objects_projects.destroy_all # rubocop: disable DestroyAll
end
def non_existent_lfs_objects_projects
diff --git a/app/services/projects/move_notification_settings_service.rb b/app/services/projects/move_notification_settings_service.rb
index 746605d56f1..109a00dd6d9 100644
--- a/app/services/projects/move_notification_settings_service.rb
+++ b/app/services/projects/move_notification_settings_service.rb
@@ -22,7 +22,7 @@ module Projects
# Remove remaining notification settings from source_project
def remove_remaining_notification_settings
- source_project.notification_settings.destroy_all
+ source_project.notification_settings.destroy_all # rubocop: disable DestroyAll
end
# Get users of current notification_settings
diff --git a/app/services/projects/move_project_group_links_service.rb b/app/services/projects/move_project_group_links_service.rb
index d9038030f7e..1efafdce36d 100644
--- a/app/services/projects/move_project_group_links_service.rb
+++ b/app/services/projects/move_project_group_links_service.rb
@@ -26,7 +26,7 @@ module Projects
# Remove remaining project group links from source_project
def remove_remaining_project_group_links
- source_project.reload.project_group_links.destroy_all
+ source_project.reload.project_group_links.destroy_all # rubocop: disable DestroyAll
end
def group_links_in_target_project
diff --git a/app/services/projects/move_project_members_service.rb b/app/services/projects/move_project_members_service.rb
index bb0c0d10242..ec983582d94 100644
--- a/app/services/projects/move_project_members_service.rb
+++ b/app/services/projects/move_project_members_service.rb
@@ -25,7 +25,7 @@ module Projects
def remove_remaining_members
# Remove remaining members and authorizations from source_project
- source_project.project_members.destroy_all
+ source_project.project_members.destroy_all # rubocop: disable DestroyAll
end
def project_members_in_target_project
diff --git a/app/services/projects/update_remote_mirror_service.rb b/app/services/projects/update_remote_mirror_service.rb
index 4651f7c4f8f..591b38b8151 100644
--- a/app/services/projects/update_remote_mirror_service.rb
+++ b/app/services/projects/update_remote_mirror_service.rb
@@ -10,6 +10,7 @@ module Projects
return success unless remote_mirror.enabled?
begin
+ remote_mirror.ensure_remote!
repository.fetch_remote(remote_mirror.remote_name, no_tags: true)
opts = {}
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index 97f181ccea8..e390d7a04c3 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -30,7 +30,7 @@ module Projects
def run_auto_devops_pipeline?
return false if project.repository.gitlab_ci_yml || !project.auto_devops&.previous_changes&.include?('enabled')
- project.auto_devops.enabled? || (project.auto_devops.enabled.nil? && Gitlab::CurrentSettings.auto_devops_enabled?)
+ project.auto_devops_enabled?
end
private
diff --git a/app/services/protected_branches/legacy_api_update_service.rb b/app/services/protected_branches/legacy_api_update_service.rb
index 1f6bbe72f85..da8bf2ce02a 100644
--- a/app/services/protected_branches/legacy_api_update_service.rb
+++ b/app/services/protected_branches/legacy_api_update_service.rb
@@ -38,11 +38,11 @@ module ProtectedBranches
def delete_redundant_access_levels
unless @developers_can_merge.nil?
- @protected_branch.merge_access_levels.destroy_all
+ @protected_branch.merge_access_levels.destroy_all # rubocop: disable DestroyAll
end
unless @developers_can_push.nil?
- @protected_branch.push_access_levels.destroy_all
+ @protected_branch.push_access_levels.destroy_all # rubocop: disable DestroyAll
end
end
end
diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb
index cdc8514c47c..a4c4c9e4812 100644
--- a/app/services/quick_actions/interpret_service.rb
+++ b/app/services/quick_actions/interpret_service.rb
@@ -60,7 +60,8 @@ module QuickActions
"Closes this #{issuable.to_ability_name.humanize(capitalize: false)}."
end
condition do
- issuable.persisted? &&
+ issuable.is_a?(Issuable) &&
+ issuable.persisted? &&
issuable.open? &&
current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
end
@@ -75,7 +76,8 @@ module QuickActions
"Reopens this #{issuable.to_ability_name.humanize(capitalize: false)}."
end
condition do
- issuable.persisted? &&
+ issuable.is_a?(Issuable) &&
+ issuable.persisted? &&
issuable.closed? &&
current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
end
@@ -149,7 +151,8 @@ module QuickActions
issuable.allows_multiple_assignees? ? '@user1 @user2' : ''
end
condition do
- issuable.persisted? &&
+ issuable.is_a?(Issuable) &&
+ issuable.persisted? &&
issuable.assignees.any? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end
@@ -188,7 +191,8 @@ module QuickActions
"Removes #{issuable.milestone.to_reference(format: :name)} milestone."
end
condition do
- issuable.persisted? &&
+ issuable.is_a?(Issuable) &&
+ issuable.persisted? &&
issuable.milestone_id? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end
@@ -231,7 +235,8 @@ module QuickActions
end
params '~label1 ~"label 2"'
condition do
- issuable.persisted? &&
+ issuable.is_a?(Issuable) &&
+ issuable.persisted? &&
issuable.labels.any? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end
@@ -257,7 +262,8 @@ module QuickActions
end
params '~label1 ~"label 2"'
condition do
- issuable.persisted? &&
+ issuable.is_a?(Issuable) &&
+ issuable.persisted? &&
issuable.labels.any? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end
@@ -295,7 +301,8 @@ module QuickActions
desc 'Add a todo'
explanation 'Adds a todo.'
condition do
- issuable.persisted? &&
+ issuable.is_a?(Issuable) &&
+ issuable.persisted? &&
!TodoService.new.todo_exist?(issuable, current_user)
end
command :todo do
@@ -317,7 +324,8 @@ module QuickActions
"Subscribes to this #{issuable.to_ability_name.humanize(capitalize: false)}."
end
condition do
- issuable.persisted? &&
+ issuable.is_a?(Issuable) &&
+ issuable.persisted? &&
!issuable.subscribed?(current_user, project)
end
command :subscribe do
@@ -329,7 +337,8 @@ module QuickActions
"Unsubscribes from this #{issuable.to_ability_name.humanize(capitalize: false)}."
end
condition do
- issuable.persisted? &&
+ issuable.is_a?(Issuable) &&
+ issuable.persisted? &&
issuable.subscribed?(current_user, project)
end
command :unsubscribe do
@@ -385,14 +394,15 @@ module QuickActions
end
params ':emoji:'
condition do
- issuable.persisted?
+ issuable.is_a?(Issuable) &&
+ issuable.persisted?
end
parse_params do |emoji_param|
match = emoji_param.match(Banzai::Filter::EmojiFilter.emoji_pattern)
match[1] if match
end
command :award do |name|
- if name && issuable.user_can_award?(current_user, name)
+ if name && issuable.user_can_award?(current_user)
@updates[:emoji_award] = name
end
end
@@ -574,6 +584,23 @@ module QuickActions
@updates[:confidential] = true
end
+ desc 'Tag this commit.'
+ explanation do |tag_name, message|
+ with_message = %{ with "#{message}"} if message.present?
+ "Tags this commit to #{tag_name}#{with_message}."
+ end
+ params 'v1.2.3 <message>'
+ parse_params do |tag_name_and_message|
+ tag_name_and_message.split(' ', 2)
+ end
+ condition do
+ issuable.is_a?(Commit) && current_user.can?(:push_code, project)
+ end
+ command :tag do |tag_name, message|
+ @updates[:tag_name] = tag_name
+ @updates[:tag_message] = message
+ end
+
def extract_users(params)
return [] if params.nil?
diff --git a/app/services/quick_actions/target_service.rb b/app/services/quick_actions/target_service.rb
new file mode 100644
index 00000000000..d8ba52c6e50
--- /dev/null
+++ b/app/services/quick_actions/target_service.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module QuickActions
+ class TargetService < BaseService
+ def execute(type, type_id)
+ case type&.downcase
+ when 'issue'
+ issue(type_id)
+ when 'mergerequest'
+ merge_request(type_id)
+ when 'commit'
+ commit(type_id)
+ end
+ end
+
+ private
+
+ def issue(type_id)
+ IssuesFinder.new(current_user, project_id: project.id).find_by(iid: type_id) || project.issues.build
+ end
+
+ def merge_request(type_id)
+ MergeRequestsFinder.new(current_user, project_id: project.id).find_by(iid: type_id) || project.merge_requests.build
+ end
+
+ def commit(type_id)
+ project.commit(type_id)
+ end
+ end
+end
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 77494295f14..dda89830179 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -32,6 +32,21 @@ module SystemNoteService
create_note(NoteSummary.new(noteable, project, author, body, action: 'commit', commit_count: total_count))
end
+ # Called when a commit was tagged
+ #
+ # noteable - Noteable object
+ # project - Project owning noteable
+ # author - User performing the tag
+ # tag_name - The created tag name
+ #
+ # Returns the created Note object
+ def tag_commit(noteable, project, author, tag_name)
+ link = url_helpers.project_tag_url(project, id: tag_name)
+ body = "tagged commit #{noteable.sha} to [`#{tag_name}`](#{link})"
+
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'tag'))
+ end
+
# Called when the assignee of a Noteable is changed or removed
#
# noteable - Noteable object
diff --git a/app/services/tags/create_service.rb b/app/services/tags/create_service.rb
index 329722df747..35390f5082c 100644
--- a/app/services/tags/create_service.rb
+++ b/app/services/tags/create_service.rb
@@ -7,7 +7,7 @@ module Tags
return error('Tag name invalid') unless valid_tag
repository = project.repository
- message&.strip!
+ message = message&.strip
new_tag = nil
diff --git a/app/services/todos/destroy/base_service.rb b/app/services/todos/destroy/base_service.rb
index dff5e1f30e5..aeb60e50c64 100644
--- a/app/services/todos/destroy/base_service.rb
+++ b/app/services/todos/destroy/base_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Todos
module Destroy
class BaseService
diff --git a/app/services/todos/destroy/confidential_issue_service.rb b/app/services/todos/destroy/confidential_issue_service.rb
index c5b66df057a..efec0f22da5 100644
--- a/app/services/todos/destroy/confidential_issue_service.rb
+++ b/app/services/todos/destroy/confidential_issue_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Todos
module Destroy
class ConfidentialIssueService < ::Todos::Destroy::BaseService
diff --git a/app/services/todos/destroy/entity_leave_service.rb b/app/services/todos/destroy/entity_leave_service.rb
index 045f5ecaae7..4cb9d08713d 100644
--- a/app/services/todos/destroy/entity_leave_service.rb
+++ b/app/services/todos/destroy/entity_leave_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Todos
module Destroy
class EntityLeaveService < ::Todos::Destroy::BaseService
diff --git a/app/services/todos/destroy/group_private_service.rb b/app/services/todos/destroy/group_private_service.rb
index d13fa7a6516..f67f1d40597 100644
--- a/app/services/todos/destroy/group_private_service.rb
+++ b/app/services/todos/destroy/group_private_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Todos
module Destroy
class GroupPrivateService < ::Todos::Destroy::BaseService
diff --git a/app/services/todos/destroy/private_features_service.rb b/app/services/todos/destroy/private_features_service.rb
index 4d8e2877bfb..7e204885b31 100644
--- a/app/services/todos/destroy/private_features_service.rb
+++ b/app/services/todos/destroy/private_features_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Todos
module Destroy
class PrivateFeaturesService < ::Todos::Destroy::BaseService
diff --git a/app/services/todos/destroy/project_private_service.rb b/app/services/todos/destroy/project_private_service.rb
index 315a0c33398..ae8fab3ffca 100644
--- a/app/services/todos/destroy/project_private_service.rb
+++ b/app/services/todos/destroy/project_private_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Todos
module Destroy
class ProjectPrivateService < ::Todos::Destroy::BaseService
diff --git a/app/services/users/destroy_service.rb b/app/services/users/destroy_service.rb
index 4bc78b5b64e..73fa6089945 100644
--- a/app/services/users/destroy_service.rb
+++ b/app/services/users/destroy_service.rb
@@ -2,6 +2,8 @@
module Users
class DestroyService
+ DestroyError = Class.new(StandardError)
+
attr_accessor :current_user
def initialize(current_user)
@@ -46,9 +48,8 @@ module Users
namespace.prepare_for_destroy
user.personal_projects.each do |project|
- # Skip repository removal because we remove directory with namespace
- # that contain all this repositories
- ::Projects::DestroyService.new(project, current_user, skip_repo: project.legacy_storage?).execute
+ success = ::Projects::DestroyService.new(project, current_user).execute
+ raise DestroyError, "Project #{project.id} can't be deleted" unless success
end
yield(user) if block_given?
diff --git a/app/views/admin/application_settings/_account_and_limit.html.haml b/app/views/admin/application_settings/_account_and_limit.html.haml
index 7c8243a7a90..622cb11010e 100644
--- a/app/views/admin/application_settings/_account_and_limit.html.haml
+++ b/app/views/admin/application_settings/_account_and_limit.html.haml
@@ -29,5 +29,11 @@
= f.check_box :user_default_external, class: 'form-check-input'
= f.label :user_default_external, class: 'form-check-label' do
Newly registered users will by default be external
+ .form-group
+ = f.label :user_show_add_ssh_key_message, 'Prompt users to upload SSH keys', class: 'label-bold'
+ .form-check
+ = f.check_box :user_show_add_ssh_key_message, class: 'form-check-input'
+ = f.label :user_show_add_ssh_key_message, class: 'form-check-label' do
+ Inform users without uploaded SSH keys that they can't push over SSH until one is added
= f.submit 'Save changes', class: 'btn btn-success'
diff --git a/app/views/admin/application_settings/_ci_cd.html.haml b/app/views/admin/application_settings/_ci_cd.html.haml
index 5037017e38a..97be658cd34 100644
--- a/app/views/admin/application_settings/_ci_cd.html.haml
+++ b/app/views/admin/application_settings/_ci_cd.html.haml
@@ -38,6 +38,8 @@
.form-text.text-muted
Set the default expiration time for each job's artifacts.
0 for unlimited.
+ The default unit is in seconds, but you can define an alternative. For example:
+ <code>4 mins 2 sec</code>, <code>2h42min</code>.
= link_to icon('question-circle'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'default-artifacts-expiration')
= f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/show.html.haml b/app/views/admin/application_settings/show.html.haml
index 258d50ad676..6133a7646f4 100644
--- a/app/views/admin/application_settings/show.html.haml
+++ b/app/views/admin/application_settings/show.html.haml
@@ -325,6 +325,8 @@
.settings-content
= render partial: 'repository_mirrors_form'
+= render_if_exists 'admin/application_settings/templates', expanded: expanded
+
%section.settings.as-third-party-offers.no-animate#js-third-party-offers-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
diff --git a/app/views/admin/hook_logs/show.html.haml b/app/views/admin/hook_logs/show.html.haml
index 2eb3ac85722..86729dbe7bc 100644
--- a/app/views/admin/hook_logs/show.html.haml
+++ b/app/views/admin/hook_logs/show.html.haml
@@ -4,7 +4,6 @@
%hr
-= link_to 'Resend Request', retry_admin_hook_hook_log_path(@hook, @hook_log), class: "btn btn-default float-right prepend-left-10"
+= link_to 'Resend Request', retry_admin_hook_hook_log_path(@hook, @hook_log), method: :post, class: "btn btn-default float-right prepend-left-10"
= render partial: 'shared/hook_logs/content', locals: { hook_log: @hook_log }
-
diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml
index 8dfd176f1b7..9280ff4d478 100644
--- a/app/views/admin/runners/index.html.haml
+++ b/app/views/admin/runners/index.html.haml
@@ -49,7 +49,7 @@
= submit_tag 'Search', class: 'btn'
.float-right.light
- Runners with last contact more than a minute ago: #{@active_runners_cnt}
+ Runners currently online: #{@active_runners_cnt}
%br
diff --git a/app/views/admin/spam_logs/index.html.haml b/app/views/admin/spam_logs/index.html.haml
index 8aaa6379730..b45d3e4823b 100644
--- a/app/views/admin/spam_logs/index.html.haml
+++ b/app/views/admin/spam_logs/index.html.haml
@@ -17,6 +17,6 @@
%th Primary Action
%th
= render @spam_logs
- = paginate @spam_logs
+ = paginate @spam_logs, theme: 'gitlab'
- else
%h4 There are no Spam Logs
diff --git a/app/views/admin/users/_access_levels.html.haml b/app/views/admin/users/_access_levels.html.haml
index 04acc5f8423..5f68163163e 100644
--- a/app/views/admin/users/_access_levels.html.haml
+++ b/app/views/admin/users/_access_levels.html.haml
@@ -1,15 +1,18 @@
%fieldset
%legend Access
.form-group.row
- = f.label :projects_limit, class: 'col-form-label col-sm-2'
+ .col-sm-2.text-right
+ = f.label :projects_limit, class: 'col-form-label'
.col-sm-10= f.number_field :projects_limit, min: 0, max: Gitlab::Database::MAX_INT_VALUE, class: 'form-control'
.form-group.row
- = f.label :can_create_group, class: 'col-form-label col-sm-2'
+ .col-sm-2.text-right
+ = f.label :can_create_group, class: 'col-form-label'
.col-sm-10= f.check_box :can_create_group
.form-group.row
- = f.label :access_level, class: 'col-form-label col-sm-2'
+ .col-sm-2.text-right
+ = f.label :access_level, class: 'col-form-label'
.col-sm-10
- editing_current_user = (current_user == @user)
@@ -29,7 +32,8 @@
You cannot remove your own admin rights.
.form-group.row
- = f.label :external, class: 'col-form-label col-sm-2'
+ .col-sm-2.text-right
+ = f.label :external, class: 'col-form-label'
.col-sm-10
= f.check_box :external do
External
diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml
index 58be07fc83e..7f21bdb91c8 100644
--- a/app/views/admin/users/_form.html.haml
+++ b/app/views/admin/users/_form.html.haml
@@ -5,17 +5,20 @@
%fieldset
%legend Account
.form-group.row
- = f.label :name, class: 'col-form-label col-sm-2'
+ .col-sm-2.text-right
+ = f.label :name, class: 'col-form-label'
.col-sm-10
= f.text_field :name, required: true, autocomplete: 'off', class: 'form-control'
%span.help-inline * required
.form-group.row
- = f.label :username, class: 'col-form-label col-sm-2'
+ .col-sm-2.text-right
+ = f.label :username, class: 'col-form-label'
.col-sm-10
= f.text_field :username, required: true, autocomplete: 'off', autocorrect: 'off', autocapitalize: 'off', spellcheck: false, class: 'form-control'
%span.help-inline * required
.form-group.row
- = f.label :email, class: 'col-form-label col-sm-2'
+ .col-sm-2.text-right
+ = f.label :email, class: 'col-form-label'
.col-sm-10
= f.text_field :email, required: true, autocomplete: 'off', class: 'form-control'
%span.help-inline * required
@@ -24,7 +27,8 @@
%fieldset
%legend Password
.form-group.row
- = f.label :password, class: 'col-form-label col-sm-2'
+ .col-sm-2.text-right
+ = f.label :password, class: 'col-form-label'
.col-sm-10
%strong
Reset link will be generated and sent to the user.
@@ -34,10 +38,12 @@
%fieldset
%legend Password
.form-group.row
- = f.label :password, class: 'col-form-label col-sm-2'
+ .col-sm-2.text-right
+ = f.label :password, class: 'col-form-label'
.col-sm-10= f.password_field :password, disabled: f.object.force_random_password, class: 'form-control'
.form-group.row
- = f.label :password_confirmation, class: 'col-form-label col-sm-2'
+ .col-sm-2.text-right
+ = f.label :password_confirmation, class: 'col-form-label'
.col-sm-10= f.password_field :password_confirmation, disabled: f.object.force_random_password, class: 'form-control'
= render partial: 'access_levels', locals: { f: f }
@@ -45,21 +51,26 @@
%fieldset
%legend Profile
.form-group.row
- = f.label :avatar, class: 'col-form-label col-sm-2'
+ .col-sm-2.text-right
+ = f.label :avatar, class: 'col-form-label'
.col-sm-10
= f.file_field :avatar
.form-group.row
- = f.label :skype, class: 'col-form-label col-sm-2'
+ .col-sm-2.text-right
+ = f.label :skype, class: 'col-form-label'
.col-sm-10= f.text_field :skype, class: 'form-control'
.form-group.row
- = f.label :linkedin, class: 'col-form-label col-sm-2'
+ .col-sm-2.text-right
+ = f.label :linkedin, class: 'col-form-label'
.col-sm-10= f.text_field :linkedin, class: 'form-control'
.form-group.row
- = f.label :twitter, class: 'col-form-label col-sm-2'
+ .col-sm-2.text-right
+ = f.label :twitter, class: 'col-form-label'
.col-sm-10= f.text_field :twitter, class: 'form-control'
.form-group.row
- = f.label :website_url, 'Website', class: 'col-form-label col-sm-2'
+ .col-sm-2.text-right
+ = f.label :website_url, 'Website', class: 'col-form-label'
.col-sm-10= f.text_field :website_url, class: 'form-control'
.form-actions
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index f730fd05176..029efadd75d 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -128,8 +128,8 @@
.col-md-6
- unless @user == current_user
- unless @user.confirmed?
- .card.bg-info
- .card-header
+ .card.border-info
+ .card-header.bg-info.text-white
Confirm user
.card-body
- if @user.unconfirmed_email.present?
@@ -138,8 +138,8 @@
%br
= link_to 'Confirm user', confirm_admin_user_path(@user), method: :put, class: "btn btn-info", data: { confirm: 'Are you sure?' }
- if @user.blocked?
- .card.bg-info
- .card-header
+ .card.border-info
+ .card-header.bg-info.text-white
This user is blocked
.card-body
%p A blocked user cannot:
@@ -162,8 +162,8 @@
%br
= link_to 'Block user', block_admin_user_path(@user), data: { confirm: 'USER WILL BE BLOCKED! Are you sure?' }, method: :put, class: "btn btn-warning"
- if @user.access_locked?
- .card.bg-info
- .card-header
+ .card.border-info
+ .card-header.bg-info.text-white
This account has been locked
.card-body
%p This user has been temporarily locked due to excessive number of failed logins. You may manually unlock the account.
diff --git a/app/views/award_emoji/_awards_block.html.haml b/app/views/award_emoji/_awards_block.html.haml
index 8ca9fb4512e..30d7b21b1b8 100644
--- a/app/views/award_emoji/_awards_block.html.haml
+++ b/app/views/award_emoji/_awards_block.html.haml
@@ -3,7 +3,7 @@
.awards.js-awards-block{ class: ("hidden" if !inline && grouped_emojis.empty?), data: { award_url: toggle_award_url(awardable) } }
- awards_sort(grouped_emojis).each do |emoji, awards|
%button.btn.award-control.js-emoji-btn.has-tooltip{ type: "button",
- class: [(award_state_class(awardable, awards, current_user)), (award_user_authored_class(emoji) if user_authored)],
+ class: [(award_state_class(awardable, awards, current_user))],
data: { placement: "bottom", title: award_user_list(awards, current_user) } }
= emoji_icon(emoji)
%span.award-control-text.js-counter
@@ -13,7 +13,6 @@
.award-menu-holder.js-award-holder
%button.btn.award-control.has-tooltip.js-add-award{ type: 'button',
'aria-label': _('Add reaction'),
- class: ("js-user-authored" if user_authored),
data: { title: _('Add reaction'), placement: "bottom" } }
%span{ class: "award-control-icon award-control-icon-neutral" }= custom_icon('emoji_slightly_smiling_face')
%span{ class: "award-control-icon award-control-icon-positive" }= custom_icon('emoji_smiley')
diff --git a/app/views/instance_statistics/cohorts/_cohorts_table.html.haml b/app/views/instance_statistics/cohorts/_cohorts_table.html.haml
index 701a4e62b39..6a7c999bff3 100644
--- a/app/views/instance_statistics/cohorts/_cohorts_table.html.haml
+++ b/app/views/instance_statistics/cohorts/_cohorts_table.html.haml
@@ -3,7 +3,7 @@
User cohorts are shown for the last #{@cohorts[:months_included]}
months. Only users with activity are counted in the cohort total; inactive
users are counted separately.
- = link_to icon('question-circle'), help_page_path('user/admin_area/user_cohorts', anchor: 'cohorts'), title: 'About this feature', target: '_blank'
+ = link_to icon('question-circle'), help_page_path('user/instance_statistics/user_cohorts', anchor: 'cohorts'), title: 'About this feature', target: '_blank'
.table-holder
%table.table
diff --git a/app/views/instance_statistics/conversational_development_index/_no_data.html.haml b/app/views/instance_statistics/conversational_development_index/_no_data.html.haml
index d69c46194b4..dd795aee135 100644
--- a/app/views/instance_statistics/conversational_development_index/_no_data.html.haml
+++ b/app/views/instance_statistics/conversational_development_index/_no_data.html.haml
@@ -4,4 +4,4 @@
%h4 Data is still calculating...
%p
In order to gather accurate feature usage data, it can take 1 to 2 weeks to see your index.
- = link_to 'Learn more', help_page_path('user/admin_area/monitoring/convdev'), target: '_blank'
+ = link_to 'Learn more', help_page_path('user/instance_statistics/convdev'), target: '_blank'
diff --git a/app/views/instance_statistics/conversational_development_index/index.html.haml b/app/views/instance_statistics/conversational_development_index/index.html.haml
index e3d1aa31dc2..dd63b98376f 100644
--- a/app/views/instance_statistics/conversational_development_index/index.html.haml
+++ b/app/views/instance_statistics/conversational_development_index/index.html.haml
@@ -19,7 +19,7 @@
index
%br
score
- = link_to icon('question-circle', 'aria-hidden' => 'true'), help_page_path('user/admin_area/monitoring/convdev')
+ = link_to icon('question-circle', 'aria-hidden' => 'true'), help_page_path('user/instance_statistics/convdev')
.convdev-cards.board-card-container
- @metric.cards.each do |card|
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index 8560b72fe85..24f256d083b 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -2,11 +2,11 @@
.file-holder-bottom-radius.file-holder.file.append-bottom-default
.js-file-title.file-title.clearfix{ data: { current_action: action } }
- .editor-ref
+ .editor-ref.block-truncated
= sprite_icon('fork', size: 12)
= ref
- %span.editor-file-name
- - if current_action?(:edit) || current_action?(:update)
+ - if current_action?(:edit) || current_action?(:update)
+ %span.editor-file-name
= text_field_tag 'file_path', (params[:file_path] || @path),
class: 'form-control new-file-path js-file-path-name-input'
@@ -16,7 +16,7 @@
= text_field_tag 'file_name', params[:file_name], placeholder: "File name",
required: true, class: 'form-control new-file-name js-file-path-name-input'
- .float-right.file-buttons
+ .file-buttons
= button_tag class: 'soft-wrap-toggle btn', type: 'button', tabindex: '-1' do
%span.no-wrap
= custom_icon('icon_no_wrap')
diff --git a/app/views/projects/commits/_commit_list.html.haml b/app/views/projects/commits/_commit_list.html.haml
index 8f8eb2c3d5a..6ed65d07202 100644
--- a/app/views/projects/commits/_commit_list.html.haml
+++ b/app/views/projects/commits/_commit_list.html.haml
@@ -1,9 +1,10 @@
-- commits, hidden = limited_commits(@commits)
+- commits = @commits
+- hidden = @hidden_commit_count
- commits = Commit.decorate(commits, @project)
.card
.card-header
- Commits (#{@commits.count})
+ Commits (#{@total_commit_count})
- if hidden > 0
%ul.content-list
- commits.each do |commit|
diff --git a/app/views/projects/commits/_commits.html.haml b/app/views/projects/commits/_commits.html.haml
index ac6852751be..ec05ff50f25 100644
--- a/app/views/projects/commits/_commits.html.haml
+++ b/app/views/projects/commits/_commits.html.haml
@@ -2,7 +2,8 @@
- project = local_assigns.fetch(:project) { merge_request&.project }
- ref = local_assigns.fetch(:ref) { merge_request&.source_branch }
-- commits, hidden = limited_commits(@commits)
+- commits = @commits
+- hidden = @hidden_commit_count
- commits.chunk { |c| c.committed_date.in_time_zone.to_date }.each do |day, commits|
%li.commit-header.js-commit-header{ data: { day: day } }
diff --git a/app/views/projects/environments/metrics.html.haml b/app/views/projects/environments/metrics.html.haml
index af86b8e8e67..4222963a754 100644
--- a/app/views/projects/environments/metrics.html.haml
+++ b/app/views/projects/environments/metrics.html.haml
@@ -2,11 +2,4 @@
- page_title "Metrics for environment", @environment.name
.prometheus-container{ class: container_class }
- .top-area
- .row
- .col-sm-6
- %h3
- Environment:
- = link_to @environment.name, environment_path(@environment)
-
#prometheus-graphs{ data: metrics_data(@project, @environment) }
diff --git a/app/views/projects/forks/_fork_button.html.haml b/app/views/projects/forks/_fork_button.html.haml
index 8a549d431ee..12cf40bb65f 100644
--- a/app/views/projects/forks/_fork_button.html.haml
+++ b/app/views/projects/forks/_fork_button.html.haml
@@ -5,7 +5,7 @@
.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)
- = project_identicon(namespace, class: "avatar s100 identicon")
+ = project_icon(namespace, class: "avatar s100 identicon")
- else
.avatar-container.s100
= image_tag(avatar, class: "avatar s100")
@@ -18,7 +18,7 @@
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)
- = project_identicon(namespace, class: "avatar s100 identicon")
+ = project_icon(namespace, class: "avatar s100 identicon")
- else
.avatar-container.s100
= image_tag(avatar, class: "avatar s100")
diff --git a/app/views/projects/hook_logs/show.html.haml b/app/views/projects/hook_logs/show.html.haml
index e51efa85df0..bd8ca5e7d70 100644
--- a/app/views/projects/hook_logs/show.html.haml
+++ b/app/views/projects/hook_logs/show.html.haml
@@ -4,6 +4,6 @@
Request details
.col-lg-9
- = link_to 'Resend Request', retry_project_hook_hook_log_path(@project, @hook, @hook_log), class: "btn btn-default float-right prepend-left-10"
+ = link_to 'Resend Request', retry_project_hook_hook_log_path(@project, @hook, @hook_log), method: :post, class: "btn btn-default float-right prepend-left-10"
= render partial: 'shared/hook_logs/content', locals: { hook_log: @hook_log }
diff --git a/app/views/projects/jobs/_sidebar.html.haml b/app/views/projects/jobs/_sidebar.html.haml
index 74f88486738..acc1e17b811 100644
--- a/app/views/projects/jobs/_sidebar.html.haml
+++ b/app/views/projects/jobs/_sidebar.html.haml
@@ -1,12 +1,7 @@
%aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar.js-right-sidebar{ data: { "offset-top" => "101", "spy" => "affix" } }
.sidebar-container
.blocks-container
- - if can?(current_user, :create_build_terminal, @build)
- .block
- = link_to terminal_project_job_path(@project, @build), class: 'terminal-button pull-right btn visible-md-block visible-lg-block', title: 'Terminal' do
- Terminal
-
- #js-details-block-vue{ data: { can_user_retry: can?(current_user, :update_build, @build) && @build.retryable? } }
+ #js-details-block-vue{ data: { terminal_path: can?(current_user, :create_build_terminal, @build) && @build.has_terminal? ? terminal_project_job_path(@project, @build) : nil } }
- if can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?)
.block
diff --git a/app/views/projects/merge_requests/creations/_new_submit.html.haml b/app/views/projects/merge_requests/creations/_new_submit.html.haml
index f7a5d85500f..d5c4134dee2 100644
--- a/app/views/projects/merge_requests/creations/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/creations/_new_submit.html.haml
@@ -33,7 +33,7 @@
%li.commits-tab.new-tab
= link_to url_for(safe_params), data: {target: 'div#commits', action: 'new', toggle: 'tabvue'} do
Commits
- %span.badge.badge-pill= @commits.size
+ %span.badge.badge-pill= @total_commit_count
- if @pipelines.any?
%li.builds-tab
= link_to url_for(safe_params.merge(action: 'pipelines')), data: {target: 'div#pipelines', action: 'pipelines', toggle: 'tabvue'} do
diff --git a/app/views/projects/mirrors/_instructions.html.haml b/app/views/projects/mirrors/_instructions.html.haml
index 64f0fde30cf..e051f9e6331 100644
--- a/app/views/projects/mirrors/_instructions.html.haml
+++ b/app/views/projects/mirrors/_instructions.html.haml
@@ -1,10 +1,11 @@
.account-well.prepend-top-default.append-bottom-default
%ul
%li
- The repository must be accessible over <code>http://</code>, <code>https://</code>, <code>ssh://</code> or <code>git://</code>.
- %li
- Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>.
- %li
- The update action will time out after 10 minutes. For big repositories, use a clone/push combination.
- %li
- The Git LFS objects will <strong>not</strong> be synced.
+ = _('The repository must be accessible over <code>http://</code>,
+ <code>https://</code>, <code>ssh://</code> and <code>git://</code>.').html_safe
+ %li= _('Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>.').html_safe
+ %li= _("The update action will time out after #{import_will_timeout_message(Gitlab.config.gitlab_shell.git_timeout)} minutes. For big repositories, use a clone/push combination.")
+ %li= _('The Git LFS objects will <strong>not</strong> be synced.').html_safe
+ %li
+ = _('This user will be the author of all events in the activity feed that are the result of an update,
+ like new branches being created or new commits being pushed to existing branches.')
diff --git a/app/views/projects/mirrors/_mirror_repos.html.haml b/app/views/projects/mirrors/_mirror_repos.html.haml
new file mode 100644
index 00000000000..c6764c7607a
--- /dev/null
+++ b/app/views/projects/mirrors/_mirror_repos.html.haml
@@ -0,0 +1,63 @@
+- expanded = Rails.env.test?
+- protocols = Gitlab::UrlSanitizer::ALLOWED_SCHEMES.join('|')
+
+%section.settings.project-mirror-settings.js-mirror-settings.no-animate#js-push-remote-settings{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4= _('Mirroring repositories')
+ %button.btn.js-settings-toggle
+ = expanded ? _('Collapse') : _('Expand')
+ %p
+ = _('Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically.')
+ = link_to _('Read more'), help_page_path('workflow/repository_mirroring'), target: '_blank'
+
+ .settings-content
+ = form_for @project, url: project_mirror_path(@project), html: { class: 'gl-show-field-errors js-mirror-form', autocomplete: 'false', data: mirrors_form_data_attributes } do |f|
+ .panel.panel-default
+ .panel-heading
+ %h3.panel-title= _('Mirror a repository')
+ .panel-body
+ %div= form_errors(@project)
+
+ .form-group.has-feedback
+ = label_tag :url, _('Git repository URL'), class: 'label-light'
+ = text_field_tag :url, nil, class: 'form-control js-mirror-url js-repo-url', placeholder: _('Input your repository URL'), required: true, pattern: "(#{protocols}):\/\/.+"
+
+ = render 'projects/mirrors/instructions'
+
+ = render 'projects/mirrors/mirror_repos_form', f: f
+
+ .form-check.append-bottom-10
+ = check_box_tag :only_protected_branches, '1', false, class: 'js-mirror-protected form-check-input'
+ = label_tag :only_protected_branches, _('Only mirror protected branches'), class: 'form-check-label'
+ = link_to icon('question-circle'), help_page_path('user/project/protected_branches')
+
+ .panel-footer
+ = f.submit _('Mirror repository'), class: 'btn btn-create', name: :update_remote_mirror
+
+ .panel.panel-default
+ .table-responsive
+ %table.table.push-pull-table
+ %thead
+ %tr
+ %th
+ = _('Mirrored repositories')
+ = render_if_exists 'projects/mirrors/mirrored_repositories_count'
+ %th= _('Direction')
+ %th= _('Last update')
+ %th
+ %th
+ %tbody.js-mirrors-table-body
+ = render_if_exists 'projects/mirrors/table_pull_row'
+ - @project.remote_mirrors.each_with_index do |mirror, index|
+ - if mirror.enabled
+ %tr
+ %td= mirror.safe_url
+ %td= _('Push')
+ %td= mirror.last_update_at.present? ? time_ago_with_tooltip(mirror.last_update_at) : _('Never')
+ %td
+ - if mirror.last_error.present?
+ .badge.mirror-error-badge{ data: { toggle: 'tooltip', html: 'true' }, title: html_escape(mirror.last_error.try(:strip)) }= _('Error')
+ %td.mirror-action-buttons
+ .btn-group.mirror-actions-group.pull-right{ role: 'group' }
+ = render 'shared/remote_mirror_update_button', remote_mirror: mirror
+ %button.js-delete-mirror.btn.btn-danger{ type: 'button', data: { mirror_id: mirror.id, toggle: 'tooltip', container: 'body' }, title: _('Remove') }= icon('trash-o')
diff --git a/app/views/projects/mirrors/_mirror_repos_form.html.haml b/app/views/projects/mirrors/_mirror_repos_form.html.haml
new file mode 100644
index 00000000000..93994cb30ac
--- /dev/null
+++ b/app/views/projects/mirrors/_mirror_repos_form.html.haml
@@ -0,0 +1,18 @@
+- protocols = Gitlab::UrlSanitizer::ALLOWED_SCHEMES.join('|')
+
+.form-group
+ = label_tag :mirror_direction, _('Mirror direction'), class: 'label-light'
+ = select_tag :mirror_direction, options_for_select([[_('Push'), 'push']]), class: 'form-control js-mirror-direction', disabled: true
+
+= f.fields_for :remote_mirrors, @project.remote_mirrors.build do |rm_f|
+ = rm_f.hidden_field :enabled, value: '1'
+ = rm_f.hidden_field :url, class: 'js-mirror-url-hidden', required: true, pattern: "(#{protocols}):\/\/.+"
+ = rm_f.hidden_field :only_protected_branches, class: 'js-mirror-protected-hidden'
+
+.form-group
+ = label_tag :auth_method, _('Authentication method'), class: 'label-bold'
+ = select_tag :auth_method, options_for_select([[_('None'), 'none'], [_('Password'), 'password']], 'none'), { class: "form-control js-auth-method" }
+
+.form-group.js-password-group.collapse
+ = label_tag :password, _('Password'), class: 'label-bold'
+ = text_field_tag :password, '', class: 'form-control js-password'
diff --git a/app/views/projects/mirrors/_push.html.haml b/app/views/projects/mirrors/_push.html.haml
deleted file mode 100644
index 08375e09816..00000000000
--- a/app/views/projects/mirrors/_push.html.haml
+++ /dev/null
@@ -1,50 +0,0 @@
-- expanded = Rails.env.test?
-%section.settings.no-animate#js-push-remote-settings{ class: ('expanded' if expanded) }
- .settings-header
- %h4
- Push to a remote repository
- %button.btn.js-settings-toggle
- = expanded ? 'Collapse' : 'Expand'
- %p
- Set up the remote repository that you want to update with the content of the current repository
- every time someone pushes to it.
- = link_to 'Read more', help_page_path('workflow/repository_mirroring', anchor: 'pushing-to-a-remote-repository'), target: '_blank'
- .settings-content
- = form_for @project, url: project_mirror_path(@project) do |f|
- %div
- = form_errors(@project)
- = render "shared/remote_mirror_update_button", remote_mirror: @remote_mirror
- - if @remote_mirror.last_error.present?
- .panel.panel-danger
- .panel-heading
- - if @remote_mirror.last_update_at
- The remote repository failed to update #{time_ago_with_tooltip(@remote_mirror.last_update_at)}.
- - else
- The remote repository failed to update.
-
- - if @remote_mirror.last_successful_update_at
- Last successful update #{time_ago_with_tooltip(@remote_mirror.last_successful_update_at)}.
- .panel-body
- %pre
- :preserve
- #{h(@remote_mirror.last_error.strip)}
- = f.fields_for :remote_mirrors, @remote_mirror do |rm_form|
- .form-group
- = rm_form.check_box :enabled, class: "float-left"
- .prepend-left-20
- = rm_form.label :enabled, "Remote mirror repository", class: "label-bold append-bottom-0"
- %p.light.append-bottom-0
- Automatically update the remote mirror's branches, tags, and commits from this repository every time someone pushes to it.
- .form-group.has-feedback
- = rm_form.label :url, "Git repository URL", class: "label-bold"
- = rm_form.text_field :url, class: "form-control", placeholder: 'https://username:password@gitlab.company.com/group/project.git'
-
- = render "projects/mirrors/instructions"
-
- .form-group
- = rm_form.check_box :only_protected_branches, class: 'float-left'
- .prepend-left-20
- = rm_form.label :only_protected_branches, class: 'label-bold'
- = link_to icon('question-circle'), help_page_path('user/project/protected_branches')
-
- = f.submit 'Save changes', class: 'btn btn-create', name: 'update_remote_mirror'
diff --git a/app/views/projects/mirrors/_show.html.haml b/app/views/projects/mirrors/_show.html.haml
index de77701a373..8318d5898a1 100644
--- a/app/views/projects/mirrors/_show.html.haml
+++ b/app/views/projects/mirrors/_show.html.haml
@@ -1,3 +1 @@
-- if can?(current_user, :admin_remote_mirror, @project)
- = render 'projects/mirrors/push'
-
+= render 'projects/mirrors/mirror_repos'
diff --git a/app/views/projects/notes/_actions.html.haml b/app/views/projects/notes/_actions.html.haml
index b4fe1cabdfd..e9008d60098 100644
--- a/app/views/projects/notes/_actions.html.haml
+++ b/app/views/projects/notes/_actions.html.haml
@@ -40,7 +40,7 @@
- if note.emoji_awardable?
- user_authored = note.user_authored?(current_user)
.note-actions-item
- = button_tag title: 'Add reaction', class: "note-action-button note-emoji-button js-add-award js-note-emoji #{'js-user-authored' if user_authored} has-tooltip btn btn-transparent", data: { position: 'right', container: 'body' } do
+ = button_tag title: 'Add reaction', class: "note-action-button note-emoji-button js-add-award js-note-emoji} has-tooltip btn btn-transparent", data: { position: 'right', container: 'body' } do
= icon('spinner spin')
%span{ class: 'link-highlight award-control-icon-neutral' }= custom_icon('emoji_slightly_smiling_face')
%span{ class: 'link-highlight award-control-icon-positive' }= custom_icon('emoji_smiley')
diff --git a/app/views/projects/pages/_use.html.haml b/app/views/projects/pages/_use.html.haml
index cd9177c0f9e..988dabef3a0 100644
--- a/app/views/projects/pages/_use.html.haml
+++ b/app/views/projects/pages/_use.html.haml
@@ -1,6 +1,6 @@
- unless @project.pages_deployed?
- .card.bg-info
- .card-header
+ .card.border-info
+ .card-header.bg-info.text-white
Configure pages
.card-body
%p
diff --git a/app/views/projects/runners/_specific_runners.html.haml b/app/views/projects/runners/_specific_runners.html.haml
index 6c11ce3b394..314af44490e 100644
--- a/app/views/projects/runners/_specific_runners.html.haml
+++ b/app/views/projects/runners/_specific_runners.html.haml
@@ -13,4 +13,4 @@
%h4.underlined-title Available specific runners
%ul.bordered-list.available-specific-runners
= render partial: 'projects/runners/runner', collection: @assignable_runners, as: :runner
- = paginate @assignable_runners, theme: "gitlab"
+ = paginate @assignable_runners, theme: "gitlab", :params => { :anchor => '#js-runners-settings' }
diff --git a/app/views/projects/settings/ci_cd/_form.html.haml b/app/views/projects/settings/ci_cd/_form.html.haml
index 434aed2f603..9134257b631 100644
--- a/app/views/projects/settings/ci_cd/_form.html.haml
+++ b/app/views/projects/settings/ci_cd/_form.html.haml
@@ -17,7 +17,7 @@
%h5.prepend-top-0
= _("Git strategy for pipelines")
%p
- = _("Choose between <code>clone</code> or <code>fetch</code> to get the recent application code")
+ = _("Choose between <code>clone</code> or <code>fetch</code> to get the recent application code").html_safe
= link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'git-strategy'), target: '_blank'
.form-check
= f.radio_button :build_allow_git_fetch, 'false', { class: 'form-check-input' }
@@ -47,7 +47,7 @@
= f.label :ci_config_path, _('Custom CI config path'), class: 'label-bold'
= f.text_field :ci_config_path, class: 'form-control', placeholder: '.gitlab-ci.yml'
%p.form-text.text-muted
- = _("The path to CI config file. Defaults to <code>.gitlab-ci.yml</code>")
+ = _("The path to CI config file. Defaults to <code>.gitlab-ci.yml</code>").html_safe
= link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'custom-ci-config-path'), target: '_blank'
%hr
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 8a5abb64515..df8a5742450 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -21,8 +21,7 @@
%nav.project-stats{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
= render 'stat_anchor_list', anchors: @project.statistics_anchors(show_auto_devops_callout: show_auto_devops_callout)
= render 'stat_anchor_list', anchors: @project.statistics_buttons(show_auto_devops_callout: show_auto_devops_callout)
- - if Feature.enabled?(:repository_languages, @project.namespace.becomes(Namespace))
- = repository_languages_bar(@project.repository_languages)
+ = repository_languages_bar(@project.repository_languages)
%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
- if @project.archived?
diff --git a/app/views/search/results/_blob.html.haml b/app/views/search/results/_blob.html.haml
index fdcd126e7a3..a8d4d4af93a 100644
--- a/app/views/search/results/_blob.html.haml
+++ b/app/views/search/results/_blob.html.haml
@@ -1,4 +1,6 @@
- project = find_project_for_result_blob(blob)
+- return unless project
+
- file_name, blob = parse_search_result(blob)
- blob_link = project_blob_path(project, tree_join(blob.ref, file_name))
diff --git a/app/views/shared/_remote_mirror_update_button.html.haml b/app/views/shared/_remote_mirror_update_button.html.haml
index 34de1c0695f..f32cff18fa8 100644
--- a/app/views/shared/_remote_mirror_update_button.html.haml
+++ b/app/views/shared/_remote_mirror_update_button.html.haml
@@ -1,13 +1,6 @@
-- if @project.has_remote_mirror?
- .append-bottom-default
- - if remote_mirror.update_in_progress?
- %span.btn.disabled
- = icon("refresh spin")
- Updating&hellip;
- - else
- = link_to update_now_project_mirror_path(@project, sync_remote: true), method: :post, class: "btn" do
- = icon("refresh")
- Update Now
- - if @remote_mirror.last_successful_update_at
- %p.inline.prepend-left-10
- Successfully updated #{time_ago_with_tooltip(@remote_mirror.last_successful_update_at)}.
+- if remote_mirror.update_in_progress?
+ %button.btn.disabled{ type: 'button', data: { toggle: 'tooltip', container: 'body' }, title: _('Updating') }
+ = icon("refresh spin")
+- else
+ = link_to update_now_project_mirror_path(@project, sync_remote: true), method: :post, class: "btn", data: { toggle: 'tooltip', container: 'body' }, title: _('Update now') do
+ = icon("refresh")
diff --git a/app/views/shared/milestones/_issuables.html.haml b/app/views/shared/milestones/_issuables.html.haml
index ee6354b1c28..ee97f0172da 100644
--- a/app/views/shared/milestones/_issuables.html.haml
+++ b/app/views/shared/milestones/_issuables.html.haml
@@ -2,8 +2,8 @@
- primary = local_assigns.fetch(:primary, false)
- panel_class = primary ? 'bg-primary text-white' : ''
-.card{ class: panel_class }
- .card-header
+.card
+ .card-header{ class: panel_class }
.title
= title
- if show_counter
diff --git a/app/views/shared/notifications/_custom_notifications.html.haml b/app/views/shared/notifications/_custom_notifications.html.haml
index 1f6e8f98bbb..43a87fd8397 100644
--- a/app/views/shared/notifications/_custom_notifications.html.haml
+++ b/app/views/shared/notifications/_custom_notifications.html.haml
@@ -19,7 +19,7 @@
- paragraph = _('Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}.') % { notification_link: notification_link.html_safe }
#{ paragraph.html_safe }
.col-lg-8
- - NotificationSetting::EMAIL_EVENTS.each_with_index do |event, index|
+ - notification_setting.email_events.each_with_index do |event, index|
- field_id = "#{notifications_menu_identifier("modal", notification_setting)}_notification_setting[#{event}]"
.form-group
.form-check{ class: ("prepend-top-0" if index == 0) }
diff --git a/app/views/shared/plugins/_index.html.haml b/app/views/shared/plugins/_index.html.haml
index 7bcc54e7459..9d230d12be2 100644
--- a/app/views/shared/plugins/_index.html.haml
+++ b/app/views/shared/plugins/_index.html.haml
@@ -19,5 +19,5 @@
.monospace
= File.basename(file)
- else
- %p.card.bg-light.text-center
- No plugins found.
+ .card.bg-light.text-center
+ .nothing-here-block No plugins found.
diff --git a/app/views/shared/runners/_form.html.haml b/app/views/shared/runners/_form.html.haml
index 0337680d79b..fa93307be31 100644
--- a/app/views/shared/runners/_form.html.haml
+++ b/app/views/shared/runners/_form.html.haml
@@ -1,56 +1,56 @@
= form_for runner, url: runner_form_url do |f|
= form_errors(runner)
.form-group.row
- = label :active, "Active", class: 'col-form-label col-sm-2'
+ = label :active, _("Active"), class: 'col-form-label col-sm-2'
.col-sm-10
.form-check
= f.check_box :active, { class: 'form-check-input' }
- %span.light Paused Runners don't accept new jobs
+ %label.light{ for: :runner_active }= _("Paused Runners don't accept new jobs")
.form-group.row
- = label :protected, "Protected", class: 'col-form-label col-sm-2'
+ = label :protected, _("Protected"), class: 'col-form-label col-sm-2'
.col-sm-10
.form-check
= f.check_box :access_level, { class: 'form-check-input' }, 'ref_protected', 'not_protected'
- %span.light This runner will only run on pipelines triggered on protected branches
+ %label.light{ for: :runner_access_level }= _('This runner will only run on pipelines triggered on protected branches')
.form-group.row
- = label :run_untagged, 'Run untagged jobs', class: 'col-form-label col-sm-2'
+ = label :run_untagged, _('Run untagged jobs'), class: 'col-form-label col-sm-2'
.col-sm-10
.form-check
= f.check_box :run_untagged, { class: 'form-check-input' }
- %span.light Indicates whether this runner can pick jobs without tags
+ %label.light{ for: :runner_run_untagged }= _('Indicates whether this runner can pick jobs without tags')
- unless runner.group_type?
.form-group.row
= label :locked, _('Lock to current projects'), class: 'col-form-label col-sm-2'
.col-sm-10
.form-check
= f.check_box :locked, { class: 'form-check-input' }
- %span.light= _('When a runner is locked, it cannot be assigned to other projects')
+ %label.light{ for: :runner_locked }= _('When a runner is locked, it cannot be assigned to other projects')
.form-group.row
= label_tag :token, class: 'col-form-label col-sm-2' do
- Token
+ = _('Token')
.col-sm-10
= f.text_field :token, class: 'form-control', readonly: true
.form-group.row
= label_tag :ip_address, class: 'col-form-label col-sm-2' do
- IP Address
+ = _('IP Address')
.col-sm-10
= f.text_field :ip_address, class: 'form-control', readonly: true
.form-group.row
= label_tag :description, class: 'col-form-label col-sm-2' do
- Description
+ = _('Description')
.col-sm-10
= f.text_field :description, class: 'form-control'
.form-group.row
= label_tag :maximum_timeout_human_readable, class: 'col-form-label col-sm-2' do
- Maximum job timeout
+ = _('Maximum job timeout')
.col-sm-10
= f.text_field :maximum_timeout_human_readable, class: 'form-control'
- .form-text.text-muted This timeout will take precedence when lower than Project-defined timeout
+ .form-text.text-muted= _('This timeout will take precedence when lower than Project-defined timeout')
.form-group.row
= label_tag :tag_list, class: 'col-form-label col-sm-2' do
- Tags
+ = _('Tags')
.col-sm-10
= f.text_field :tag_list, value: runner.tag_list.sort.join(', '), class: 'form-control'
- .form-text.text-muted You can setup jobs to only use Runners with specific tags. Separate tags with commas.
+ .form-text.text-muted= _('You can setup jobs to only use Runners with specific tags. Separate tags with commas.')
.form-actions
- = f.submit 'Save changes', class: 'btn btn-save'
+ = f.submit _('Save changes'), class: 'btn btn-success'
diff --git a/app/views/snippets/notes/_actions.html.haml b/app/views/snippets/notes/_actions.html.haml
index 3a50324770d..e1f7ee80ebb 100644
--- a/app/views/snippets/notes/_actions.html.haml
+++ b/app/views/snippets/notes/_actions.html.haml
@@ -2,7 +2,7 @@
- if note.emoji_awardable?
- user_authored = note.user_authored?(current_user)
.note-actions-item
- = link_to '#', title: 'Add reaction', class: "note-action-button note-emoji-button js-add-award js-note-emoji #{'js-user-authored' if user_authored} has-tooltip", data: { position: 'right' } do
+ = link_to '#', title: 'Add reaction', class: "note-action-button note-emoji-button js-add-award js-note-emoji has-tooltip", data: { position: 'right' } do
= icon('spinner spin')
%span{ class: 'link-highlight award-control-icon-neutral' }= custom_icon('emoji_slightly_smiling_face')
%span{ class: 'link-highlight award-control-icon-positive' }= custom_icon('emoji_smiley')
diff --git a/app/workers/detect_repository_languages_worker.rb b/app/workers/detect_repository_languages_worker.rb
index 537b8fd5963..854b74b884a 100644
--- a/app/workers/detect_repository_languages_worker.rb
+++ b/app/workers/detect_repository_languages_worker.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class DetectRepositoryLanguagesWorker
include ApplicationWorker
include ExceptionBacktrace
@@ -14,8 +16,6 @@ class DetectRepositoryLanguagesWorker
user = User.find_by(id: user_id)
return unless project && user
- return if Feature.disabled?(:repository_languages, project.namespace)
-
try_obtain_lease do
::Projects::DetectRepositoryLanguagesService.new(project, user).execute
end
diff --git a/app/workers/remove_expired_group_links_worker.rb b/app/workers/remove_expired_group_links_worker.rb
index 6b8b972a440..25128caf72f 100644
--- a/app/workers/remove_expired_group_links_worker.rb
+++ b/app/workers/remove_expired_group_links_worker.rb
@@ -5,6 +5,6 @@ class RemoveExpiredGroupLinksWorker
include CronjobQueue
def perform
- ProjectGroupLink.expired.destroy_all
+ ProjectGroupLink.expired.destroy_all # rubocop: disable DestroyAll
end
end
diff --git a/app/workers/remove_old_web_hook_logs_worker.rb b/app/workers/remove_old_web_hook_logs_worker.rb
index 17140ac4450..0f486f8991d 100644
--- a/app/workers/remove_old_web_hook_logs_worker.rb
+++ b/app/workers/remove_old_web_hook_logs_worker.rb
@@ -6,7 +6,9 @@ class RemoveOldWebHookLogsWorker
WEB_HOOK_LOG_LIFETIME = 2.days
+ # rubocop: disable DestroyAll
def perform
WebHookLog.destroy_all(['created_at < ?', Time.now - WEB_HOOK_LOG_LIFETIME])
end
+ # rubocop: enable DestroyAll
end
diff --git a/app/workers/todos_destroyer/confidential_issue_worker.rb b/app/workers/todos_destroyer/confidential_issue_worker.rb
index 9d640c14963..481fde8c83d 100644
--- a/app/workers/todos_destroyer/confidential_issue_worker.rb
+++ b/app/workers/todos_destroyer/confidential_issue_worker.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module TodosDestroyer
class ConfidentialIssueWorker
include ApplicationWorker
diff --git a/app/workers/todos_destroyer/entity_leave_worker.rb b/app/workers/todos_destroyer/entity_leave_worker.rb
index e62d9876f4a..7db3f6c84b4 100644
--- a/app/workers/todos_destroyer/entity_leave_worker.rb
+++ b/app/workers/todos_destroyer/entity_leave_worker.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module TodosDestroyer
class EntityLeaveWorker
include ApplicationWorker
diff --git a/app/workers/todos_destroyer/group_private_worker.rb b/app/workers/todos_destroyer/group_private_worker.rb
index 3e47eec7461..21ec4abe478 100644
--- a/app/workers/todos_destroyer/group_private_worker.rb
+++ b/app/workers/todos_destroyer/group_private_worker.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module TodosDestroyer
class GroupPrivateWorker
include ApplicationWorker
diff --git a/app/workers/todos_destroyer/private_features_worker.rb b/app/workers/todos_destroyer/private_features_worker.rb
index f457d5e0471..1e68f0fd5ae 100644
--- a/app/workers/todos_destroyer/private_features_worker.rb
+++ b/app/workers/todos_destroyer/private_features_worker.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module TodosDestroyer
class PrivateFeaturesWorker
include ApplicationWorker
diff --git a/app/workers/todos_destroyer/project_private_worker.rb b/app/workers/todos_destroyer/project_private_worker.rb
index 7a853c36370..064e001530c 100644
--- a/app/workers/todos_destroyer/project_private_worker.rb
+++ b/app/workers/todos_destroyer/project_private_worker.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module TodosDestroyer
class ProjectPrivateWorker
include ApplicationWorker