summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorLuke Bennett <lbennett@gitlab.com>2018-09-24 14:39:00 +0100
committerLuke Bennett <lbennett@gitlab.com>2018-09-24 14:39:00 +0100
commit05afd11e16aecd43adfb869ae90aa6cae13916ec (patch)
tree63b31431981d48ac970853778dd01a5301bb9564 /app
parent086549d986a453e1b2dd0d09ffbd19d0487d9c51 (diff)
parent28086b203ae397e01d5e9870dfbddd66466450c2 (diff)
downloadgitlab-ce-05afd11e16aecd43adfb869ae90aa6cae13916ec.tar.gz
Merge remote-tracking branch 'origin/master' into ce-6983-promote-starting-a-gitlab-com-trial
Diffstat (limited to 'app')
-rw-r--r--app/assets/images/auth_buttons/auth0_64.pngbin0 -> 1815 bytes
-rw-r--r--app/assets/images/auth_buttons/azure_64.pngbin695 -> 199 bytes
-rw-r--r--app/assets/images/auth_buttons/bitbucket_64.pngbin2161 -> 1299 bytes
-rw-r--r--app/assets/images/auth_buttons/google_64.pngbin4366 -> 1625 bytes
-rw-r--r--app/assets/images/auth_buttons/jwt_64.pngbin0 -> 2457 bytes
-rw-r--r--app/assets/images/auth_buttons/shibboleth_64.pngbin0 -> 2993 bytes
-rw-r--r--app/assets/images/cluster_app_logos/elasticsearch.pngbin0 -> 796 bytes
-rw-r--r--app/assets/images/cluster_app_logos/gitlab.pngbin0 -> 1757 bytes
-rw-r--r--app/assets/images/cluster_app_logos/helm.pngbin0 -> 1438 bytes
-rw-r--r--app/assets/images/cluster_app_logos/jeager.pngbin0 -> 2619 bytes
-rw-r--r--app/assets/images/cluster_app_logos/jupyterhub.pngbin0 -> 895 bytes
-rw-r--r--app/assets/images/cluster_app_logos/kubernetes.pngbin0 -> 1437 bytes
-rw-r--r--app/assets/images/cluster_app_logos/meltano.pngbin0 -> 580 bytes
-rw-r--r--app/assets/images/cluster_app_logos/prometheus.pngbin0 -> 923 bytes
-rw-r--r--app/assets/javascripts/badges/components/badge.vue6
-rw-r--r--app/assets/javascripts/badges/components/badge_form.vue4
-rw-r--r--app/assets/javascripts/badges/components/badge_list.vue6
-rw-r--r--app/assets/javascripts/badges/components/badge_list_row.vue4
-rw-r--r--app/assets/javascripts/behaviors/index.js6
-rw-r--r--app/assets/javascripts/behaviors/markdown/gfm_auto_complete.js19
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_gfm.js2
-rw-r--r--app/assets/javascripts/behaviors/preview_markdown.js (renamed from app/assets/javascripts/preview_markdown.js)0
-rw-r--r--app/assets/javascripts/behaviors/shortcuts.js35
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts.js (renamed from app/assets/javascripts/shortcuts.js)6
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts_blob.js (renamed from app/assets/javascripts/shortcuts_blob.js)2
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts_find_file.js (renamed from app/assets/javascripts/shortcuts_find_file.js)0
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js (renamed from app/assets/javascripts/shortcuts_issuable.js)4
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js (renamed from app/assets/javascripts/shortcuts_navigation.js)2
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts_network.js (renamed from app/assets/javascripts/shortcuts_network.js)0
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts_wiki.js (renamed from app/assets/javascripts/shortcuts_wiki.js)2
-rw-r--r--app/assets/javascripts/blob/3d_viewer/index.js4
-rw-r--r--app/assets/javascripts/boards/components/board_blank_state.vue2
-rw-r--r--app/assets/javascripts/boards/components/board_list.vue10
-rw-r--r--app/assets/javascripts/boards/components/board_new_issue.vue2
-rw-r--r--app/assets/javascripts/boards/components/issue_card_inner.vue4
-rw-r--r--app/assets/javascripts/boards/components/modal/index.vue18
-rw-r--r--app/assets/javascripts/boards/components/project_select.vue6
-rw-r--r--app/assets/javascripts/boards/index.js6
-rw-r--r--app/assets/javascripts/boards/models/list.js7
-rw-r--r--app/assets/javascripts/boards/utils/query_data.js21
-rw-r--r--app/assets/javascripts/clusters/clusters_bundle.js39
-rw-r--r--app/assets/javascripts/clusters/clusters_index.js4
-rw-r--r--app/assets/javascripts/clusters/components/application_row.vue137
-rw-r--r--app/assets/javascripts/clusters/components/applications.vue439
-rw-r--r--app/assets/javascripts/clusters/components/gcp_signup_offer.js8
-rw-r--r--app/assets/javascripts/commit/pipelines/pipelines_table.vue4
-rw-r--r--app/assets/javascripts/commons/gitlab_ui.js14
-rw-r--r--app/assets/javascripts/commons/polyfills.js2
-rw-r--r--app/assets/javascripts/commons/polyfills/element.js23
-rw-r--r--app/assets/javascripts/commons/polyfills/svg.js5
-rw-r--r--app/assets/javascripts/deploy_keys/components/action_btn.vue6
-rw-r--r--app/assets/javascripts/deploy_keys/components/app.vue6
-rw-r--r--app/assets/javascripts/deploy_keys/components/key.vue10
-rw-r--r--app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js22
-rw-r--r--app/assets/javascripts/diff_notes/services/resolve.js9
-rw-r--r--app/assets/javascripts/diffs/components/app.vue33
-rw-r--r--app/assets/javascripts/diffs/components/changed_files_dropdown.vue2
-rw-r--r--app/assets/javascripts/diffs/components/diff_discussions.vue10
-rw-r--r--app/assets/javascripts/diffs/components/diff_file.vue39
-rw-r--r--app/assets/javascripts/diffs/components/diff_file_header.vue4
-rw-r--r--app/assets/javascripts/diffs/components/diff_gutter_avatars.vue8
-rw-r--r--app/assets/javascripts/diffs/components/diff_line_gutter_content.vue39
-rw-r--r--app/assets/javascripts/diffs/components/diff_line_note_form.vue46
-rw-r--r--app/assets/javascripts/diffs/components/diff_table_cell.vue37
-rw-r--r--app/assets/javascripts/diffs/components/inline_diff_comment_row.vue18
-rw-r--r--app/assets/javascripts/diffs/components/inline_diff_table_row.vue13
-rw-r--r--app/assets/javascripts/diffs/components/inline_diff_view.vue25
-rw-r--r--app/assets/javascripts/diffs/components/no_changes.vue2
-rw-r--r--app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue54
-rw-r--r--app/assets/javascripts/diffs/components/parallel_diff_table_row.vue124
-rw-r--r--app/assets/javascripts/diffs/components/parallel_diff_view.vue43
-rw-r--r--app/assets/javascripts/diffs/constants.js1
-rw-r--r--app/assets/javascripts/diffs/store/actions.js99
-rw-r--r--app/assets/javascripts/diffs/store/getters.js55
-rw-r--r--app/assets/javascripts/diffs/store/modules/diff_state.js2
-rw-r--r--app/assets/javascripts/diffs/store/modules/index.js4
-rw-r--r--app/assets/javascripts/diffs/store/mutation_types.js2
-rw-r--r--app/assets/javascripts/diffs/store/mutations.js132
-rw-r--r--app/assets/javascripts/diffs/store/utils.js79
-rw-r--r--app/assets/javascripts/dismissable_callout.js4
-rw-r--r--app/assets/javascripts/dispatcher.js89
-rw-r--r--app/assets/javascripts/dropzone_input.js25
-rw-r--r--app/assets/javascripts/environments/components/container.vue6
-rw-r--r--app/assets/javascripts/environments/components/empty_state.vue2
-rw-r--r--app/assets/javascripts/environments/components/environment_actions.vue4
-rw-r--r--app/assets/javascripts/environments/components/environment_rollback.vue4
-rw-r--r--app/assets/javascripts/environments/components/environments_app.vue2
-rw-r--r--app/assets/javascripts/environments/components/environments_table.vue8
-rw-r--r--app/assets/javascripts/environments/mixins/environments_mixin.js2
-rw-r--r--app/assets/javascripts/feature_highlight/feature_highlight.js2
-rw-r--r--app/assets/javascripts/filtered_search/admin_runners_filtered_search_token_keys.js14
-rw-r--r--app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.vue4
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_hint.js2
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js6
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js4
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_token_keys.js115
-rw-r--r--app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js77
-rw-r--r--app/assets/javascripts/filtered_search/null_dropdown.js9
-rw-r--r--app/assets/javascripts/fly_out_nav.js4
-rw-r--r--app/assets/javascripts/frequent_items/components/app.vue6
-rw-r--r--app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue2
-rw-r--r--app/assets/javascripts/gl_form.js2
-rw-r--r--app/assets/javascripts/groups/components/app.vue84
-rw-r--r--app/assets/javascripts/groups/components/group_folder.vue7
-rw-r--r--app/assets/javascripts/groups/components/group_item.vue16
-rw-r--r--app/assets/javascripts/groups/components/groups.vue89
-rw-r--r--app/assets/javascripts/groups/components/item_actions.vue11
-rw-r--r--app/assets/javascripts/groups/constants.js24
-rw-r--r--app/assets/javascripts/groups/groups_filterable_list.js64
-rw-r--r--app/assets/javascripts/groups/index.js31
-rw-r--r--app/assets/javascripts/ide/components/branches/search_list.vue8
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/editor_header.vue78
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/list.vue77
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/list_item.vue29
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue61
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/unstage_button.vue11
-rw-r--r--app/assets/javascripts/ide/components/error_message.vue6
-rw-r--r--app/assets/javascripts/ide/components/file_finder/index.vue2
-rw-r--r--app/assets/javascripts/ide/components/file_finder/item.vue12
-rw-r--r--app/assets/javascripts/ide/components/file_row_extra.vue104
-rw-r--r--app/assets/javascripts/ide/components/file_templates/bar.vue80
-rw-r--r--app/assets/javascripts/ide/components/file_templates/dropdown.vue123
-rw-r--r--app/assets/javascripts/ide/components/ide.vue9
-rw-r--r--app/assets/javascripts/ide/components/ide_tree_list.vue12
-rw-r--r--app/assets/javascripts/ide/components/jobs/list.vue6
-rw-r--r--app/assets/javascripts/ide/components/jobs/stage.vue6
-rw-r--r--app/assets/javascripts/ide/components/merge_requests/list.vue6
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/modal.vue47
-rw-r--r--app/assets/javascripts/ide/components/pipelines/list.vue6
-rw-r--r--app/assets/javascripts/ide/components/preview/clientside.vue6
-rw-r--r--app/assets/javascripts/ide/components/preview/navigator.vue4
-rw-r--r--app/assets/javascripts/ide/components/repo_commit_section.vue6
-rw-r--r--app/assets/javascripts/ide/components/repo_editor.vue8
-rw-r--r--app/assets/javascripts/ide/components/repo_file.vue227
-rw-r--r--app/assets/javascripts/ide/components/repo_file_status_icon.vue2
-rw-r--r--app/assets/javascripts/ide/stores/actions.js22
-rw-r--r--app/assets/javascripts/ide/stores/actions/file.js31
-rw-r--r--app/assets/javascripts/ide/stores/index.js2
-rw-r--r--app/assets/javascripts/ide/stores/modules/branches/mutations.js1
-rw-r--r--app/assets/javascripts/ide/stores/modules/file_templates/actions.js24
-rw-r--r--app/assets/javascripts/ide/stores/modules/file_templates/getters.js7
-rw-r--r--app/assets/javascripts/ide/stores/modules/file_templates/index.js4
-rw-r--r--app/assets/javascripts/ide/stores/modules/file_templates/mutations.js1
-rw-r--r--app/assets/javascripts/ide/stores/modules/merge_requests/mutations.js1
-rw-r--r--app/assets/javascripts/ide/stores/modules/pipelines/mutations.js1
-rw-r--r--app/assets/javascripts/ide/stores/mutations.js18
-rw-r--r--app/assets/javascripts/ide/stores/mutations/file.js3
-rw-r--r--app/assets/javascripts/issuable_bulk_update_actions.js8
-rw-r--r--app/assets/javascripts/issuable_bulk_update_sidebar.js6
-rw-r--r--app/assets/javascripts/issue_show/components/edit_actions.vue2
-rw-r--r--app/assets/javascripts/issue_show/components/title.vue2
-rw-r--r--app/assets/javascripts/jobs/components/header.vue8
-rw-r--r--app/assets/javascripts/jobs/components/job_log_controllers.vue4
-rw-r--r--app/assets/javascripts/jobs/components/jobs_container.vue2
-rw-r--r--app/assets/javascripts/jobs/components/sidebar_details_block.vue8
-rw-r--r--app/assets/javascripts/jobs/store/mutations.js2
-rw-r--r--app/assets/javascripts/labels_select.js4
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js84
-rw-r--r--app/assets/javascripts/lib/utils/navigation_utility.js (renamed from app/assets/javascripts/shortcuts_dashboard_navigation.js)2
-rw-r--r--app/assets/javascripts/lib/utils/text_utility.js10
-rw-r--r--app/assets/javascripts/lib/utils/url_utility.js4
-rw-r--r--app/assets/javascripts/main.js16
-rw-r--r--app/assets/javascripts/merge_request_tabs.js3
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue4
-rw-r--r--app/assets/javascripts/monitoring/components/graph.vue43
-rw-r--r--app/assets/javascripts/monitoring/components/graph/flag.vue6
-rw-r--r--app/assets/javascripts/monitoring/components/graph/legend.vue4
-rw-r--r--app/assets/javascripts/monitoring/mixins/monitoring_mixins.js15
-rw-r--r--app/assets/javascripts/monitoring/utils/multiple_time_series.js30
-rw-r--r--app/assets/javascripts/mr_notes/stores/index.js4
-rw-r--r--app/assets/javascripts/notebook/index.vue4
-rw-r--r--app/assets/javascripts/notes.js61
-rw-r--r--app/assets/javascripts/notes/components/comment_form.vue2
-rw-r--r--app/assets/javascripts/notes/components/diff_file_header.vue2
-rw-r--r--app/assets/javascripts/notes/components/diff_with_note.vue7
-rw-r--r--app/assets/javascripts/notes/components/note_actions.vue19
-rw-r--r--app/assets/javascripts/notes/components/note_awards_list.vue4
-rw-r--r--app/assets/javascripts/notes/components/note_form.vue13
-rw-r--r--app/assets/javascripts/notes/components/note_header.vue13
-rw-r--r--app/assets/javascripts/notes/components/noteable_discussion.vue21
-rw-r--r--app/assets/javascripts/notes/components/noteable_note.vue1
-rw-r--r--app/assets/javascripts/notes/components/notes_app.vue7
-rw-r--r--app/assets/javascripts/notes/stores/actions.js68
-rw-r--r--app/assets/javascripts/notes/stores/getters.js14
-rw-r--r--app/assets/javascripts/notes/stores/index.js12
-rw-r--r--app/assets/javascripts/notes/stores/modules/index.js4
-rw-r--r--app/assets/javascripts/notes/stores/mutations.js40
-rw-r--r--app/assets/javascripts/notes/stores/utils.js21
-rw-r--r--app/assets/javascripts/pages/admin/application_settings/metrics_and_profiling/index.js8
-rw-r--r--app/assets/javascripts/pages/admin/application_settings/usage_ping_payload.js62
-rw-r--r--app/assets/javascripts/pages/admin/runners/index.js10
-rw-r--r--app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue5
-rw-r--r--app/assets/javascripts/pages/constants.js1
-rw-r--r--app/assets/javascripts/pages/dashboard/groups/index/index.js4
-rw-r--r--app/assets/javascripts/pages/groups/boards/index.js2
-rw-r--r--app/assets/javascripts/pages/groups/issues/index.js2
-rw-r--r--app/assets/javascripts/pages/groups/merge_requests/index.js2
-rw-r--r--app/assets/javascripts/pages/groups/show/group_tabs.js136
-rw-r--r--app/assets/javascripts/pages/groups/show/index.js14
-rw-r--r--app/assets/javascripts/pages/instance_statistics/cohorts/index.js3
-rw-r--r--app/assets/javascripts/pages/instance_statistics/cohorts/usage_ping.js13
-rw-r--r--app/assets/javascripts/pages/projects/activity/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/artifacts/browse/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/artifacts/file/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/boards/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/commit/show/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/commits/show/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/find_file/show/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/index.js6
-rw-r--r--app/assets/javascripts/pages/projects/init_blob.js4
-rw-r--r--app/assets/javascripts/pages/projects/issues/form.js2
-rw-r--r--app/assets/javascripts/pages/projects/issues/index/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/issues/show.js2
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/index/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js2
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js2
-rw-r--r--app/assets/javascripts/pages/projects/network/show/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue2
-rw-r--r--app/assets/javascripts/pages/projects/project.js64
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue12
-rw-r--r--app/assets/javascripts/pages/projects/show/index.js7
-rw-r--r--app/assets/javascripts/pages/projects/tree/show/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/wikis/components/delete_wiki_modal.vue69
-rw-r--r--app/assets/javascripts/pages/projects/wikis/index.js14
-rw-r--r--app/assets/javascripts/performance_bar/components/performance_bar_app.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/graph/graph_component.vue8
-rw-r--r--app/assets/javascripts/pipelines/components/graph/job_component.vue4
-rw-r--r--app/assets/javascripts/pipelines/components/graph/stage_column_component.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/header_component.vue6
-rw-r--r--app/assets/javascripts/pipelines/components/nav_controls.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_url.vue8
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines.vue4
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_actions.vue4
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_table_row.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/stage.vue6
-rw-r--r--app/assets/javascripts/pipelines/mixins/pipelines.js2
-rw-r--r--app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_dropdown_mixin.js2
-rw-r--r--app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown.vue2
-rw-r--r--app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue2
-rw-r--r--app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue2
-rw-r--r--app/assets/javascripts/projects/project_import_gitlab_project.js16
-rw-r--r--app/assets/javascripts/projects/project_new.js29
-rw-r--r--app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue6
-rw-r--r--app/assets/javascripts/read_more.js41
-rw-r--r--app/assets/javascripts/registry/components/app.vue6
-rw-r--r--app/assets/javascripts/registry/components/collapsible_container.vue14
-rw-r--r--app/assets/javascripts/registry/components/table_registry.vue2
-rw-r--r--app/assets/javascripts/reports/components/grouped_test_reports_app.vue4
-rw-r--r--app/assets/javascripts/reports/components/report_issues.vue4
-rw-r--r--app/assets/javascripts/reports/components/summary_row.vue4
-rw-r--r--app/assets/javascripts/reports/store/mutations.js1
-rw-r--r--app/assets/javascripts/search_autocomplete.js6
-rw-r--r--app/assets/javascripts/sidebar/components/participants/participants.vue6
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue4
-rw-r--r--app/assets/javascripts/sidebar/components/todo_toggle/todo.vue4
-rw-r--r--app/assets/javascripts/usage_ping_consent.js30
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/deployment.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue12
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_squash_before_merge.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue6
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/index.js5
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/bar_chart.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/diff_viewer/diff_viewer.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/dropdown/dropdown_button.vue6
-rw-r--r--app/assets/javascripts/vue_shared/components/file_icon.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/file_row.vue210
-rw-r--r--app/assets/javascripts/vue_shared/components/header_ci_component.vue8
-rw-r--r--app/assets/javascripts/vue_shared/components/loading_button.vue7
-rw-r--r--app/assets/javascripts/vue_shared/components/loading_icon.vue45
-rw-r--r--app/assets/javascripts/vue_shared/components/pagination_links.vue34
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/stacked_progress_bar.vue6
-rw-r--r--app/assets/javascripts/vue_shared/components/toggle_button.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/tooltip_on_truncate.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue2
-rw-r--r--app/assets/javascripts/vue_shared/vue_resource_interceptor.js2
-rw-r--r--app/assets/stylesheets/bootstrap_migration.scss1
-rw-r--r--app/assets/stylesheets/framework.scss2
-rw-r--r--app/assets/stylesheets/framework/avatar.scss3
-rw-r--r--app/assets/stylesheets/framework/awards.scss4
-rw-r--r--app/assets/stylesheets/framework/buttons.scss20
-rw-r--r--app/assets/stylesheets/framework/common.scss117
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss12
-rw-r--r--app/assets/stylesheets/framework/emojis.scss10
-rw-r--r--app/assets/stylesheets/framework/files.scss2
-rw-r--r--app/assets/stylesheets/framework/filters.scss13
-rw-r--r--app/assets/stylesheets/framework/jquery.scss15
-rw-r--r--app/assets/stylesheets/framework/layout.scss19
-rw-r--r--app/assets/stylesheets/framework/lists.scss1
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss2
-rw-r--r--app/assets/stylesheets/framework/mixins.scss6
-rw-r--r--app/assets/stylesheets/framework/mobile.scss8
-rw-r--r--app/assets/stylesheets/framework/read_more.scss13
-rw-r--r--app/assets/stylesheets/framework/responsive_tables.scss2
-rw-r--r--app/assets/stylesheets/framework/selects.scss4
-rw-r--r--app/assets/stylesheets/framework/snippets.scss2
-rw-r--r--app/assets/stylesheets/framework/toggle.scss8
-rw-r--r--app/assets/stylesheets/framework/typography.scss4
-rw-r--r--app/assets/stylesheets/framework/variables.scss89
-rw-r--r--app/assets/stylesheets/framework/zen.scss2
-rw-r--r--app/assets/stylesheets/notify.scss4
-rw-r--r--app/assets/stylesheets/page_bundles/ide.scss197
-rw-r--r--app/assets/stylesheets/page_bundles/xterm.scss (renamed from app/assets/stylesheets/pages/xterm.scss)2
-rw-r--r--app/assets/stylesheets/pages/admin.scss4
-rw-r--r--app/assets/stylesheets/pages/branches.scss4
-rw-r--r--app/assets/stylesheets/pages/clusters.scss57
-rw-r--r--app/assets/stylesheets/pages/diff.scss9
-rw-r--r--app/assets/stylesheets/pages/environments.scss4
-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.scss60
-rw-r--r--app/assets/stylesheets/pages/help.scss4
-rw-r--r--app/assets/stylesheets/pages/issuable.scss13
-rw-r--r--app/assets/stylesheets/pages/login.scss29
-rw-r--r--app/assets/stylesheets/pages/members.scss2
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss4
-rw-r--r--app/assets/stylesheets/pages/note_form.scss2
-rw-r--r--app/assets/stylesheets/pages/notes.scss18
-rw-r--r--app/assets/stylesheets/pages/profile.scss4
-rw-r--r--app/assets/stylesheets/pages/projects.scss239
-rw-r--r--app/assets/stylesheets/pages/search.scss2
-rw-r--r--app/assets/stylesheets/pages/settings.scss4
-rw-r--r--app/assets/stylesheets/pages/todos.scss4
-rw-r--r--app/assets/stylesheets/pages/ui_dev_kit.scss2
-rw-r--r--app/assets/stylesheets/performance_bar.scss6
-rw-r--r--app/controllers/abuse_reports_controller.rb4
-rw-r--r--app/controllers/admin/abuse_reports_controller.rb4
-rw-r--r--app/controllers/admin/appearances_controller.rb2
-rw-r--r--app/controllers/admin/application_controller.rb2
-rw-r--r--app/controllers/admin/application_settings_controller.rb56
-rw-r--r--app/controllers/admin/applications_controller.rb6
-rw-r--r--app/controllers/admin/background_jobs_controller.rb2
-rw-r--r--app/controllers/admin/broadcast_messages_controller.rb4
-rw-r--r--app/controllers/admin/dashboard_controller.rb4
-rw-r--r--app/controllers/admin/deploy_keys_controller.rb2
-rw-r--r--app/controllers/admin/gitaly_servers_controller.rb2
-rw-r--r--app/controllers/admin/groups_controller.rb4
-rw-r--r--app/controllers/admin/health_check_controller.rb2
-rw-r--r--app/controllers/admin/hook_logs_controller.rb2
-rw-r--r--app/controllers/admin/hooks_controller.rb2
-rw-r--r--app/controllers/admin/identities_controller.rb4
-rw-r--r--app/controllers/admin/impersonation_tokens_controller.rb6
-rw-r--r--app/controllers/admin/impersonations_controller.rb2
-rw-r--r--app/controllers/admin/jobs_controller.rb4
-rw-r--r--app/controllers/admin/keys_controller.rb4
-rw-r--r--app/controllers/admin/labels_controller.rb2
-rw-r--r--app/controllers/admin/logs_controller.rb5
-rw-r--r--app/controllers/admin/projects_controller.rb6
-rw-r--r--app/controllers/admin/requests_profiles_controller.rb2
-rw-r--r--app/controllers/admin/runner_projects_controller.rb2
-rw-r--r--app/controllers/admin/runners_controller.rb13
-rw-r--r--app/controllers/admin/services_controller.rb4
-rw-r--r--app/controllers/admin/spam_logs_controller.rb4
-rw-r--r--app/controllers/admin/system_info_controller.rb2
-rw-r--r--app/controllers/admin/users_controller.rb4
-rw-r--r--app/controllers/application_controller.rb29
-rw-r--r--app/controllers/autocomplete_controller.rb2
-rw-r--r--app/controllers/boards/application_controller.rb2
-rw-r--r--app/controllers/boards/issues_controller.rb4
-rw-r--r--app/controllers/boards/lists_controller.rb2
-rw-r--r--app/controllers/ci/lints_controller.rb2
-rw-r--r--app/controllers/concerns/accepts_pending_invitations.rb2
-rw-r--r--app/controllers/concerns/authenticates_with_two_factor.rb4
-rw-r--r--app/controllers/concerns/boards_responses.rb2
-rw-r--r--app/controllers/concerns/checks_collaboration.rb2
-rw-r--r--app/controllers/concerns/continue_params.rb2
-rw-r--r--app/controllers/concerns/controller_with_cross_project_access_check.rb2
-rw-r--r--app/controllers/concerns/creates_commit.rb6
-rw-r--r--app/controllers/concerns/cycle_analytics_params.rb2
-rw-r--r--app/controllers/concerns/diff_for_path.rb2
-rw-r--r--app/controllers/concerns/enforces_two_factor_authentication.rb4
-rw-r--r--app/controllers/concerns/group_tree.rb6
-rw-r--r--app/controllers/concerns/hooks_execution.rb2
-rw-r--r--app/controllers/concerns/internal_redirect.rb8
-rw-r--r--app/controllers/concerns/issuable_actions.rb5
-rw-r--r--app/controllers/concerns/issuable_collections.rb6
-rw-r--r--app/controllers/concerns/issues_action.rb2
-rw-r--r--app/controllers/concerns/issues_calendar.rb4
-rw-r--r--app/controllers/concerns/lfs_request.rb2
-rw-r--r--app/controllers/concerns/members_presentation.rb4
-rw-r--r--app/controllers/concerns/membership_actions.rb4
-rw-r--r--app/controllers/concerns/merge_requests_action.rb2
-rw-r--r--app/controllers/concerns/milestone_actions.rb2
-rw-r--r--app/controllers/concerns/notes_actions.rb51
-rw-r--r--app/controllers/concerns/oauth_applications.rb2
-rw-r--r--app/controllers/concerns/params_backward_compatibility.rb2
-rw-r--r--app/controllers/concerns/preview_markdown.rb2
-rw-r--r--app/controllers/concerns/renders_blob.rb2
-rw-r--r--app/controllers/concerns/renders_commits.rb2
-rw-r--r--app/controllers/concerns/renders_member_access.rb4
-rw-r--r--app/controllers/concerns/renders_notes.rb6
-rw-r--r--app/controllers/concerns/repository_settings_redirect.rb2
-rw-r--r--app/controllers/concerns/requires_whitelisted_monitoring_client.rb2
-rw-r--r--app/controllers/concerns/routable_actions.rb2
-rw-r--r--app/controllers/concerns/send_file_upload.rb18
-rw-r--r--app/controllers/concerns/service_params.rb2
-rw-r--r--app/controllers/concerns/snippets_actions.rb2
-rw-r--r--app/controllers/concerns/spammable_actions.rb2
-rw-r--r--app/controllers/concerns/todos_actions.rb2
-rw-r--r--app/controllers/concerns/toggle_award_emoji.rb2
-rw-r--r--app/controllers/concerns/toggle_subscription_action.rb2
-rw-r--r--app/controllers/concerns/uploads_actions.rb6
-rw-r--r--app/controllers/concerns/with_performance_bar.rb2
-rw-r--r--app/controllers/concerns/workhorse_request.rb2
-rw-r--r--app/controllers/confirmations_controller.rb4
-rw-r--r--app/controllers/dashboard/milestones_controller.rb2
-rw-r--r--app/controllers/dashboard/projects_controller.rb4
-rw-r--r--app/controllers/dashboard/todos_controller.rb2
-rw-r--r--app/controllers/dashboard_controller.rb2
-rw-r--r--app/controllers/explore/projects_controller.rb4
-rw-r--r--app/controllers/graphql_controller.rb2
-rw-r--r--app/controllers/groups/labels_controller.rb9
-rw-r--r--app/controllers/groups_controller.rb16
-rw-r--r--app/controllers/health_check_controller.rb2
-rw-r--r--app/controllers/health_controller.rb2
-rw-r--r--app/controllers/help_controller.rb2
-rw-r--r--app/controllers/ide_controller.rb2
-rw-r--r--app/controllers/import/base_controller.rb4
-rw-r--r--app/controllers/import/bitbucket_controller.rb2
-rw-r--r--app/controllers/import/bitbucket_server_controller.rb2
-rw-r--r--app/controllers/import/fogbugz_controller.rb2
-rw-r--r--app/controllers/import/github_controller.rb4
-rw-r--r--app/controllers/import/gitlab_controller.rb2
-rw-r--r--app/controllers/import/gitlab_projects_controller.rb8
-rw-r--r--app/controllers/import/google_code_controller.rb2
-rw-r--r--app/controllers/import/manifest_controller.rb6
-rw-r--r--app/controllers/instance_statistics/cohorts_controller.rb6
-rw-r--r--app/controllers/instance_statistics/conversational_development_index_controller.rb2
-rw-r--r--app/controllers/invites_controller.rb8
-rw-r--r--app/controllers/jwt_controller.rb2
-rw-r--r--app/controllers/koding_controller.rb2
-rw-r--r--app/controllers/metrics_controller.rb2
-rw-r--r--app/controllers/notification_settings_controller.rb2
-rw-r--r--app/controllers/oauth/applications_controller.rb2
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb9
-rw-r--r--app/controllers/passwords_controller.rb4
-rw-r--r--app/controllers/profiles/accounts_controller.rb2
-rw-r--r--app/controllers/profiles/notifications_controller.rb2
-rw-r--r--app/controllers/profiles/personal_access_tokens_controller.rb2
-rw-r--r--app/controllers/profiles_controller.rb6
-rw-r--r--app/controllers/projects/artifacts_controller.rb4
-rw-r--r--app/controllers/projects/blob_controller.rb2
-rw-r--r--app/controllers/projects/branches_controller.rb2
-rw-r--r--app/controllers/projects/build_artifacts_controller.rb4
-rw-r--r--app/controllers/projects/clusters/applications_controller.rb2
-rw-r--r--app/controllers/projects/clusters_controller.rb6
-rw-r--r--app/controllers/projects/commit_controller.rb4
-rw-r--r--app/controllers/projects/commits_controller.rb2
-rw-r--r--app/controllers/projects/compare_controller.rb2
-rw-r--r--app/controllers/projects/deploy_keys_controller.rb2
-rw-r--r--app/controllers/projects/deployments_controller.rb4
-rw-r--r--app/controllers/projects/discussions_controller.rb2
-rw-r--r--app/controllers/projects/environments_controller.rb4
-rw-r--r--app/controllers/projects/forks_controller.rb4
-rw-r--r--app/controllers/projects/issues_controller.rb2
-rw-r--r--app/controllers/projects/jobs_controller.rb4
-rw-r--r--app/controllers/projects/labels_controller.rb9
-rw-r--r--app/controllers/projects/lfs_api_controller.rb2
-rw-r--r--app/controllers/projects/lfs_storage_controller.rb4
-rw-r--r--app/controllers/projects/merge_requests/application_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests/creations_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests/diffs_controller.rb6
-rw-r--r--app/controllers/projects/merge_requests_controller.rb1
-rw-r--r--app/controllers/projects/milestones_controller.rb2
-rw-r--r--app/controllers/projects/pages_controller.rb2
-rw-r--r--app/controllers/projects/pages_domains_controller.rb2
-rw-r--r--app/controllers/projects/pipeline_schedules_controller.rb2
-rw-r--r--app/controllers/projects/pipelines_controller.rb4
-rw-r--r--app/controllers/projects/project_members_controller.rb2
-rw-r--r--app/controllers/projects/refs_controller.rb65
-rw-r--r--app/controllers/projects/registry/repositories_controller.rb16
-rw-r--r--app/controllers/projects/releases_controller.rb2
-rw-r--r--app/controllers/projects/settings/repository_controller.rb2
-rw-r--r--app/controllers/projects/tags_controller.rb4
-rw-r--r--app/controllers/projects_controller.rb24
-rw-r--r--app/controllers/registrations_controller.rb2
-rw-r--r--app/controllers/root_controller.rb2
-rw-r--r--app/controllers/search_controller.rb4
-rw-r--r--app/controllers/sent_notifications_controller.rb2
-rw-r--r--app/controllers/sessions_controller.rb4
-rw-r--r--app/controllers/snippets/notes_controller.rb2
-rw-r--r--app/controllers/snippets_controller.rb6
-rw-r--r--app/controllers/uploads_controller.rb2
-rw-r--r--app/controllers/user_callouts_controller.rb4
-rw-r--r--app/controllers/users_controller.rb2
-rw-r--r--app/finders/access_requests_finder.rb2
-rw-r--r--app/finders/admin/projects_finder.rb8
-rw-r--r--app/finders/admin/runners_finder.rb52
-rw-r--r--app/finders/autocomplete/users_finder.rb4
-rw-r--r--app/finders/branches_finder.rb2
-rw-r--r--app/finders/clusters_finder.rb2
-rw-r--r--app/finders/concerns/created_at_filter.rb2
-rw-r--r--app/finders/concerns/custom_attributes_filter.rb4
-rw-r--r--app/finders/concerns/finder_methods.rb6
-rw-r--r--app/finders/concerns/finder_with_cross_project_access.rb4
-rw-r--r--app/finders/contributed_projects_finder.rb4
-rw-r--r--app/finders/environments_finder.rb4
-rw-r--r--app/finders/events_finder.rb14
-rw-r--r--app/finders/fork_projects_finder.rb4
-rw-r--r--app/finders/group_descendants_finder.rb24
-rw-r--r--app/finders/group_finder.rb4
-rw-r--r--app/finders/group_labels_finder.rb17
-rw-r--r--app/finders/group_members_finder.rb4
-rw-r--r--app/finders/group_projects_finder.rb4
-rw-r--r--app/finders/groups_finder.rb10
-rw-r--r--app/finders/issuable_finder.rb40
-rw-r--r--app/finders/issues_finder.rb8
-rw-r--r--app/finders/joined_groups_finder.rb2
-rw-r--r--app/finders/labels_finder.rb20
-rw-r--r--app/finders/license_template_finder.rb2
-rw-r--r--app/finders/members_finder.rb8
-rw-r--r--app/finders/merge_request_target_project_finder.rb4
-rw-r--r--app/finders/merge_requests_finder.rb6
-rw-r--r--app/finders/milestones_finder.rb8
-rw-r--r--app/finders/notes_finder.rb14
-rw-r--r--app/finders/personal_access_tokens_finder.rb4
-rw-r--r--app/finders/personal_projects_finder.rb4
-rw-r--r--app/finders/pipeline_schedules_finder.rb4
-rw-r--r--app/finders/pipelines_finder.rb22
-rw-r--r--app/finders/projects_finder.rb17
-rw-r--r--app/finders/runner_jobs_finder.rb4
-rw-r--r--app/finders/snippets_finder.rb16
-rw-r--r--app/finders/tags_finder.rb2
-rw-r--r--app/finders/template_finder.rb4
-rw-r--r--app/finders/todos_finder.rb27
-rw-r--r--app/finders/union_finder.rb10
-rw-r--r--app/finders/user_finder.rb2
-rw-r--r--app/finders/user_recent_events_finder.rb24
-rw-r--r--app/finders/users_finder.rb8
-rw-r--r--app/graphql/functions/base_function.rb2
-rw-r--r--app/graphql/functions/echo.rb2
-rw-r--r--app/graphql/gitlab_schema.rb2
-rw-r--r--app/graphql/mutations/concerns/mutations/resolves_project.rb2
-rw-r--r--app/graphql/mutations/merge_requests/base.rb2
-rw-r--r--app/graphql/resolvers/base_resolver.rb2
-rw-r--r--app/graphql/resolvers/concerns/resolves_pipelines.rb2
-rw-r--r--app/graphql/resolvers/full_path_resolver.rb2
-rw-r--r--app/graphql/resolvers/merge_request_pipelines_resolver.rb2
-rw-r--r--app/graphql/resolvers/merge_request_resolver.rb4
-rw-r--r--app/graphql/resolvers/project_pipelines_resolver.rb2
-rw-r--r--app/graphql/resolvers/project_resolver.rb2
-rw-r--r--app/graphql/types/base_enum.rb2
-rw-r--r--app/graphql/types/base_field.rb2
-rw-r--r--app/graphql/types/base_input_object.rb2
-rw-r--r--app/graphql/types/base_interface.rb2
-rw-r--r--app/graphql/types/base_object.rb2
-rw-r--r--app/graphql/types/base_scalar.rb2
-rw-r--r--app/graphql/types/base_union.rb2
-rw-r--r--app/graphql/types/ci/pipeline_status_enum.rb2
-rw-r--r--app/graphql/types/ci/pipeline_type.rb2
-rw-r--r--app/graphql/types/merge_request_type.rb2
-rw-r--r--app/graphql/types/permission_types/base_permission_type.rb2
-rw-r--r--app/graphql/types/permission_types/ci/pipeline.rb2
-rw-r--r--app/graphql/types/permission_types/merge_request.rb2
-rw-r--r--app/graphql/types/permission_types/project.rb2
-rw-r--r--app/graphql/types/project_type.rb2
-rw-r--r--app/graphql/types/query_type.rb2
-rw-r--r--app/graphql/types/time_type.rb2
-rw-r--r--app/helpers/accounts_helper.rb2
-rw-r--r--app/helpers/active_sessions_helper.rb2
-rw-r--r--app/helpers/appearances_helper.rb2
-rw-r--r--app/helpers/application_helper.rb29
-rw-r--r--app/helpers/application_settings_helper.rb13
-rw-r--r--app/helpers/auth_helper.rb4
-rw-r--r--app/helpers/auto_devops_helper.rb4
-rw-r--r--app/helpers/avatars_helper.rb8
-rw-r--r--app/helpers/award_emoji_helper.rb2
-rw-r--r--app/helpers/blame_helper.rb2
-rw-r--r--app/helpers/blob_helper.rb2
-rw-r--r--app/helpers/boards_helper.rb2
-rw-r--r--app/helpers/branches_helper.rb2
-rw-r--r--app/helpers/breadcrumbs_helper.rb2
-rw-r--r--app/helpers/broadcast_messages_helper.rb7
-rw-r--r--app/helpers/builds_helper.rb10
-rw-r--r--app/helpers/button_helper.rb11
-rw-r--r--app/helpers/calendar_helper.rb2
-rw-r--r--app/helpers/ci_status_helper.rb7
-rw-r--r--app/helpers/clusters_helper.rb6
-rw-r--r--app/helpers/commits_helper.rb2
-rw-r--r--app/helpers/compare_helper.rb2
-rw-r--r--app/helpers/components_helper.rb2
-rw-r--r--app/helpers/conversational_development_index_helper.rb2
-rw-r--r--app/helpers/count_helper.rb2
-rw-r--r--app/helpers/dashboard_helper.rb2
-rw-r--r--app/helpers/defer_script_tag_helper.rb2
-rw-r--r--app/helpers/deploy_tokens_helper.rb2
-rw-r--r--app/helpers/diff_helper.rb13
-rw-r--r--app/helpers/dropdowns_helper.rb11
-rw-r--r--app/helpers/emails_helper.rb6
-rw-r--r--app/helpers/emoji_helper.rb2
-rw-r--r--app/helpers/environment_helper.rb4
-rw-r--r--app/helpers/environments_helper.rb2
-rw-r--r--app/helpers/events_helper.rb4
-rw-r--r--app/helpers/explore_helper.rb2
-rw-r--r--app/helpers/external_wiki_helper.rb2
-rw-r--r--app/helpers/favicon_helper.rb2
-rw-r--r--app/helpers/form_helper.rb2
-rw-r--r--app/helpers/git_helper.rb2
-rw-r--r--app/helpers/gitlab_routing_helper.rb2
-rw-r--r--app/helpers/graph_helper.rb6
-rw-r--r--app/helpers/groups_helper.rb25
-rw-r--r--app/helpers/hooks_helper.rb2
-rw-r--r--app/helpers/icons_helper.rb22
-rw-r--r--app/helpers/import_helper.rb2
-rw-r--r--app/helpers/instance_configuration_helper.rb2
-rw-r--r--app/helpers/issuables_helper.rb20
-rw-r--r--app/helpers/issues_helper.rb16
-rw-r--r--app/helpers/javascript_helper.rb2
-rw-r--r--app/helpers/kerberos_spnego_helper.rb2
-rw-r--r--app/helpers/labels_helper.rb2
-rw-r--r--app/helpers/lazy_image_tag_helper.rb8
-rw-r--r--app/helpers/markup_helper.rb41
-rw-r--r--app/helpers/mattermost_helper.rb2
-rw-r--r--app/helpers/members_helper.rb16
-rw-r--r--app/helpers/merge_requests_helper.rb10
-rw-r--r--app/helpers/milestones_helper.rb20
-rw-r--r--app/helpers/milestones_routing_helper.rb2
-rw-r--r--app/helpers/mirror_helper.rb2
-rw-r--r--app/helpers/namespaces_helper.rb6
-rw-r--r--app/helpers/nav_helper.rb2
-rw-r--r--app/helpers/notes_helper.rb4
-rw-r--r--app/helpers/notifications_helper.rb2
-rw-r--r--app/helpers/numbers_helper.rb4
-rw-r--r--app/helpers/page_layout_helper.rb14
-rw-r--r--app/helpers/pagination_helper.rb2
-rw-r--r--app/helpers/performance_bar_helper.rb2
-rw-r--r--app/helpers/pipeline_schedules_helper.rb2
-rw-r--r--app/helpers/preferences_helper.rb2
-rw-r--r--app/helpers/profiles_helper.rb2
-rw-r--r--app/helpers/projects_helper.rb35
-rw-r--r--app/helpers/repository_languages_helper.rb2
-rw-r--r--app/helpers/rss_helper.rb2
-rw-r--r--app/helpers/runners_helper.rb2
-rw-r--r--app/helpers/safe_params_helper.rb4
-rw-r--r--app/helpers/search_helper.rb6
-rw-r--r--app/helpers/selects_helper.rb28
-rw-r--r--app/helpers/sentry_helper.rb2
-rw-r--r--app/helpers/services_helper.rb4
-rw-r--r--app/helpers/sidekiq_helper.rb2
-rw-r--r--app/helpers/snippets_helper.rb2
-rw-r--r--app/helpers/sorting_helper.rb35
-rw-r--r--app/helpers/storage_health_helper.rb2
-rw-r--r--app/helpers/storage_helper.rb2
-rw-r--r--app/helpers/submodule_helper.rb2
-rw-r--r--app/helpers/system_note_helper.rb5
-rw-r--r--app/helpers/tab_helper.rb22
-rw-r--r--app/helpers/tags_helper.rb7
-rw-r--r--app/helpers/time_helper.rb2
-rw-r--r--app/helpers/todos_helper.rb11
-rw-r--r--app/helpers/tree_helper.rb10
-rw-r--r--app/helpers/triggers_helper.rb2
-rw-r--r--app/helpers/user_callouts_helper.rb7
-rw-r--r--app/helpers/users_helper.rb2
-rw-r--r--app/helpers/version_check_helper.rb12
-rw-r--r--app/helpers/visibility_level_helper.rb12
-rw-r--r--app/helpers/webpack_helper.rb2
-rw-r--r--app/helpers/wiki_helper.rb2
-rw-r--r--app/helpers/workhorse_helper.rb2
-rw-r--r--app/mailers/emails/auto_devops.rb24
-rw-r--r--app/mailers/emails/issues.rb2
-rw-r--r--app/mailers/emails/merge_requests.rb2
-rw-r--r--app/mailers/emails/profile.rb4
-rw-r--r--app/mailers/notify.rb1
-rw-r--r--app/mailers/previews/notify_preview.rb10
-rw-r--r--app/mailers/repository_check_mailer.rb2
-rw-r--r--app/models/application_setting.rb9
-rw-r--r--app/models/badge.rb2
-rw-r--r--app/models/blob_viewer/gitlab_ci_yml.rb8
-rw-r--r--app/models/ci/build.rb30
-rw-r--r--app/models/ci/job_artifact.rb4
-rw-r--r--app/models/ci/pipeline.rb12
-rw-r--r--app/models/ci/runner.rb39
-rw-r--r--app/models/ci/trigger_request.rb2
-rw-r--r--app/models/clusters/applications/helm.rb3
-rw-r--r--app/models/clusters/applications/ingress.rb1
-rw-r--r--app/models/clusters/applications/jupyter.rb1
-rw-r--r--app/models/clusters/applications/prometheus.rb3
-rw-r--r--app/models/clusters/applications/runner.rb1
-rw-r--r--app/models/clusters/cluster.rb1
-rw-r--r--app/models/clusters/platforms/kubernetes.rb27
-rw-r--r--app/models/commit.rb1
-rw-r--r--app/models/commit_status.rb4
-rw-r--r--app/models/concerns/case_sensitivity.rb45
-rw-r--r--app/models/concerns/from_union.rb51
-rw-r--r--app/models/concerns/issuable.rb6
-rw-r--r--app/models/concerns/mentionable.rb13
-rw-r--r--app/models/concerns/mentionable/reference_regexes.rb12
-rw-r--r--app/models/concerns/noteable.rb19
-rw-r--r--app/models/concerns/project_services_loggable.rb28
-rw-r--r--app/models/concerns/protected_branch_access.rb11
-rw-r--r--app/models/concerns/protected_ref_access.rb18
-rw-r--r--app/models/concerns/protected_tag_access.rb3
-rw-r--r--app/models/concerns/storage/legacy_namespace.rb4
-rw-r--r--app/models/container_repository.rb4
-rw-r--r--app/models/dashboard_group_milestone.rb6
-rw-r--r--app/models/deploy_key.rb2
-rw-r--r--app/models/diff_note.rb2
-rw-r--r--app/models/environment.rb2
-rw-r--r--app/models/epic.rb4
-rw-r--r--app/models/event.rb9
-rw-r--r--app/models/global_milestone.rb6
-rw-r--r--app/models/group.rb8
-rw-r--r--app/models/hooks/active_hook_filter.rb2
-rw-r--r--app/models/hooks/service_hook.rb2
-rw-r--r--app/models/hooks/web_hook.rb4
-rw-r--r--app/models/issue.rb4
-rw-r--r--app/models/key.rb8
-rw-r--r--app/models/label.rb5
-rw-r--r--app/models/label_link.rb2
-rw-r--r--app/models/label_note.rb97
-rw-r--r--app/models/license_template.rb2
-rw-r--r--app/models/member.rb10
-rw-r--r--app/models/members/project_member.rb2
-rw-r--r--app/models/merge_request.rb27
-rw-r--r--app/models/merge_request_diff.rb2
-rw-r--r--app/models/milestone.rb5
-rw-r--r--app/models/namespace.rb13
-rw-r--r--app/models/network/commit.rb2
-rw-r--r--app/models/network/graph.rb4
-rw-r--r--app/models/note.rb16
-rw-r--r--app/models/pages_domain.rb2
-rw-r--r--app/models/project.rb67
-rw-r--r--app/models/project_authorization.rb8
-rw-r--r--app/models/project_import_state.rb2
-rw-r--r--app/models/project_services/asana_service.rb4
-rw-r--r--app/models/project_services/chat_message/merge_message.rb9
-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.rb9
-rw-r--r--app/models/project_services/kubernetes_service.rb26
-rw-r--r--app/models/project_services/slash_commands_service.rb4
-rw-r--r--app/models/project_wiki.rb9
-rw-r--r--app/models/prometheus_metric.rb89
-rw-r--r--app/models/repository.rb27
-rw-r--r--app/models/resource_label_event.rb101
-rw-r--r--app/models/service.rb1
-rw-r--r--app/models/snippet.rb1
-rw-r--r--app/models/system_note_metadata.rb2
-rw-r--r--app/models/todo.rb1
-rw-r--r--app/models/user.rb139
-rw-r--r--app/models/wiki_page.rb4
-rw-r--r--app/policies/application_setting/term_policy.rb2
-rw-r--r--app/policies/ci/runner_policy.rb2
-rw-r--r--app/policies/deploy_key_policy.rb2
-rw-r--r--app/policies/project_policy.rb2
-rw-r--r--app/presenters/commit_status_presenter.rb8
-rw-r--r--app/presenters/conversational_development_index/metric_presenter.rb2
-rw-r--r--app/presenters/merge_request_presenter.rb4
-rw-r--r--app/presenters/project_presenter.rb158
-rw-r--r--app/presenters/projects/settings/deploy_keys_presenter.rb8
-rw-r--r--app/serializers/build_details_entity.rb46
-rw-r--r--app/serializers/commit_entity.rb2
-rw-r--r--app/serializers/detailed_status_entity.rb (renamed from app/serializers/status_entity.rb)10
-rw-r--r--app/serializers/diffs_entity.rb4
-rw-r--r--app/serializers/discussion_entity.rb1
-rw-r--r--app/serializers/environment_serializer.rb2
-rw-r--r--app/serializers/group_child_entity.rb2
-rw-r--r--app/serializers/group_entity.rb2
-rw-r--r--app/serializers/job_entity.rb2
-rw-r--r--app/serializers/job_group_entity.rb2
-rw-r--r--app/serializers/merge_request_widget_entity.rb2
-rw-r--r--app/serializers/note_entity.rb10
-rw-r--r--app/serializers/pipeline_entity.rb2
-rw-r--r--app/serializers/pipeline_serializer.rb2
-rw-r--r--app/serializers/project_note_entity.rb4
-rw-r--r--app/serializers/runner_entity.rb7
-rw-r--r--app/serializers/stage_entity.rb18
-rw-r--r--app/serializers/trigger_variable_entity.rb7
-rw-r--r--app/services/application_settings/update_service.rb8
-rw-r--r--app/services/applications/create_service.rb2
-rw-r--r--app/services/boards/create_service.rb2
-rw-r--r--app/services/boards/issues/list_service.rb17
-rw-r--r--app/services/boards/issues/move_service.rb6
-rw-r--r--app/services/boards/lists/destroy_service.rb2
-rw-r--r--app/services/boards/lists/move_service.rb4
-rw-r--r--app/services/chat_names/find_user_service.rb2
-rw-r--r--app/services/ci/compare_test_reports_service.rb2
-rw-r--r--app/services/ci/create_pipeline_service.rb4
-rw-r--r--app/services/ci/ensure_stage_service.rb2
-rw-r--r--app/services/ci/extract_sections_from_build_trace_service.rb2
-rw-r--r--app/services/ci/fetch_kubernetes_token_service.rb75
-rw-r--r--app/services/ci/process_pipeline_service.rb8
-rw-r--r--app/services/ci/register_job_service.rb12
-rw-r--r--app/services/ci/retry_build_service.rb2
-rw-r--r--app/services/clusters/gcp/finalize_creation_service.rb50
-rw-r--r--app/services/clusters/gcp/kubernetes.rb13
-rw-r--r--app/services/clusters/gcp/kubernetes/create_service_account_service.rb51
-rw-r--r--app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb30
-rw-r--r--app/services/clusters/gcp/provision_service.rb4
-rw-r--r--app/services/cohorts_service.rb2
-rw-r--r--app/services/concerns/issues/resolve_discussions.rb2
-rw-r--r--app/services/create_release_service.rb2
-rw-r--r--app/services/delete_merged_branches_service.rb2
-rw-r--r--app/services/emails/base_service.rb2
-rw-r--r--app/services/emails/create_service.rb7
-rw-r--r--app/services/files/base_service.rb6
-rw-r--r--app/services/git_push_service.rb2
-rw-r--r--app/services/groups/destroy_service.rb2
-rw-r--r--app/services/groups/transfer_service.rb4
-rw-r--r--app/services/import_export_clean_up_service.rb2
-rw-r--r--app/services/issuable/bulk_update_service.rb2
-rw-r--r--app/services/issuable/common_system_notes_service.rb9
-rw-r--r--app/services/issuable_base_service.rb17
-rw-r--r--app/services/issues/base_service.rb2
-rw-r--r--app/services/issues/move_service.rb17
-rw-r--r--app/services/issues/referenced_merge_requests_service.rb6
-rw-r--r--app/services/issues/update_service.rb4
-rw-r--r--app/services/labels/find_or_create_service.rb2
-rw-r--r--app/services/labels/promote_service.rb23
-rw-r--r--app/services/labels/transfer_service.rb24
-rw-r--r--app/services/lfs/file_transformer.rb2
-rw-r--r--app/services/lfs/lock_file_service.rb2
-rw-r--r--app/services/lfs/locks_finder_service.rb2
-rw-r--r--app/services/lfs/unlock_file_service.rb2
-rw-r--r--app/services/merge_requests/base_service.rb2
-rw-r--r--app/services/merge_requests/build_service.rb20
-rw-r--r--app/services/merge_requests/create_from_issue_service.rb7
-rw-r--r--app/services/merge_requests/create_service.rb2
-rw-r--r--app/services/merge_requests/delete_non_latest_diffs_service.rb2
-rw-r--r--app/services/merge_requests/refresh_service.rb6
-rw-r--r--app/services/merge_requests/reload_diffs_service.rb12
-rw-r--r--app/services/milestones/promote_service.rb8
-rw-r--r--app/services/milestones/update_service.rb2
-rw-r--r--app/services/notification_recipient_service.rb18
-rw-r--r--app/services/notification_service.rb6
-rw-r--r--app/services/preview_markdown_service.rb6
-rw-r--r--app/services/projects/auto_devops/disable_service.rb41
-rw-r--r--app/services/projects/base_move_relations_service.rb2
-rw-r--r--app/services/projects/batch_forks_count_service.rb2
-rw-r--r--app/services/projects/batch_open_issues_count_service.rb2
-rw-r--r--app/services/projects/container_repository/destroy_service.rb15
-rw-r--r--app/services/projects/create_service.rb6
-rw-r--r--app/services/projects/destroy_service.rb25
-rw-r--r--app/services/projects/detect_repository_languages_service.rb6
-rw-r--r--app/services/projects/enable_deploy_key_service.rb2
-rw-r--r--app/services/projects/forks_count_service.rb2
-rw-r--r--app/services/projects/gitlab_projects_import_service.rb2
-rw-r--r--app/services/projects/hashed_storage/migrate_repository_service.rb4
-rw-r--r--app/services/projects/lfs_pointers/lfs_download_service.rb2
-rw-r--r--app/services/projects/lfs_pointers/lfs_import_service.rb2
-rw-r--r--app/services/projects/lfs_pointers/lfs_link_service.rb2
-rw-r--r--app/services/projects/move_deploy_keys_projects_service.rb2
-rw-r--r--app/services/projects/move_forks_service.rb6
-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_authorizations_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/open_issues_count_service.rb4
-rw-r--r--app/services/projects/propagate_service_template.rb4
-rw-r--r--app/services/projects/transfer_service.rb2
-rw-r--r--app/services/projects/unlink_fork_service.rb2
-rw-r--r--app/services/projects/update_remote_mirror_service.rb4
-rw-r--r--app/services/projects/update_service.rb2
-rw-r--r--app/services/quick_actions/interpret_service.rb36
-rw-r--r--app/services/quick_actions/target_service.rb4
-rw-r--r--app/services/resource_events/change_labels_service.rb3
-rw-r--r--app/services/resource_events/merge_into_notes_service.rb57
-rw-r--r--app/services/search/group_service.rb2
-rw-r--r--app/services/search_service.rb4
-rw-r--r--app/services/spam_check_service.rb2
-rw-r--r--app/services/submit_usage_ping_service.rb1
-rw-r--r--app/services/system_note_service.rb59
-rw-r--r--app/services/tags/destroy_service.rb2
-rw-r--r--app/services/todo_service.rb10
-rw-r--r--app/services/todos/destroy/base_service.rb4
-rw-r--r--app/services/todos/destroy/confidential_issue_service.rb6
-rw-r--r--app/services/todos/destroy/entity_leave_service.rb18
-rw-r--r--app/services/todos/destroy/group_private_service.rb4
-rw-r--r--app/services/todos/destroy/private_features_service.rb4
-rw-r--r--app/services/todos/destroy/project_private_service.rb4
-rw-r--r--app/services/update_release_service.rb2
-rw-r--r--app/services/users/last_push_event_service.rb2
-rw-r--r--app/services/users/migrate_to_ghost_user_service.rb4
-rw-r--r--app/services/users/respond_to_terms_service.rb2
-rw-r--r--app/services/wikis/create_attachment_service.rb12
-rw-r--r--app/uploaders/avatar_uploader.rb4
-rw-r--r--app/uploaders/namespace_file_uploader.rb22
-rw-r--r--app/uploaders/records_uploads.rb6
-rw-r--r--app/validators/branch_filter_validator.rb2
-rw-r--r--app/validators/js_regex_validator.rb2
-rw-r--r--app/validators/url_validator.rb14
-rw-r--r--app/validators/variable_duplicates_validator.rb2
-rw-r--r--app/views/abuse_reports/new.html.haml2
-rw-r--r--app/views/admin/appearances/_form.html.haml8
-rw-r--r--app/views/admin/appearances/preview_sign_in.html.haml2
-rw-r--r--app/views/admin/application_settings/_account_and_limit.html.haml5
-rw-r--r--app/views/admin/application_settings/_influx.html.haml2
-rw-r--r--app/views/admin/application_settings/_repository_mirrors_form.html.haml4
-rw-r--r--app/views/admin/application_settings/_signin.html.haml2
-rw-r--r--app/views/admin/application_settings/_usage.html.haml31
-rw-r--r--app/views/admin/application_settings/ci_cd.html.haml26
-rw-r--r--app/views/admin/application_settings/integrations.html.haml31
-rw-r--r--app/views/admin/application_settings/metrics_and_profiling.html.haml50
-rw-r--r--app/views/admin/application_settings/network.html.haml36
-rw-r--r--app/views/admin/application_settings/preferences.html.haml69
-rw-r--r--app/views/admin/application_settings/reporting.html.haml36
-rw-r--r--app/views/admin/application_settings/repository.html.haml36
-rw-r--r--app/views/admin/application_settings/show.html.haml306
-rw-r--r--app/views/admin/applications/_form.html.haml6
-rw-r--r--app/views/admin/applications/index.html.haml2
-rw-r--r--app/views/admin/broadcast_messages/_form.html.haml4
-rw-r--r--app/views/admin/dashboard/index.html.haml6
-rw-r--r--app/views/admin/deploy_keys/edit.html.haml2
-rw-r--r--app/views/admin/deploy_keys/index.html.haml2
-rw-r--r--app/views/admin/deploy_keys/new.html.haml2
-rw-r--r--app/views/admin/groups/_form.html.haml4
-rw-r--r--app/views/admin/groups/index.html.haml2
-rw-r--r--app/views/admin/groups/show.html.haml2
-rw-r--r--app/views/admin/hooks/edit.html.haml2
-rw-r--r--app/views/admin/hooks/index.html.haml2
-rw-r--r--app/views/admin/identities/_form.html.haml2
-rw-r--r--app/views/admin/identities/index.html.haml2
-rw-r--r--app/views/admin/labels/_form.html.haml2
-rw-r--r--app/views/admin/labels/index.html.haml2
-rw-r--r--app/views/admin/projects/index.html.haml2
-rw-r--r--app/views/admin/runners/_runner.html.haml127
-rw-r--r--app/views/admin/runners/_sort_dropdown.html.haml11
-rw-r--r--app/views/admin/runners/index.html.haml121
-rw-r--r--app/views/admin/services/_form.html.haml2
-rw-r--r--app/views/admin/users/_form.html.haml4
-rw-r--r--app/views/admin/users/index.html.haml2
-rw-r--r--app/views/admin/users/projects.html.haml2
-rw-r--r--app/views/ci/runner/_how_to_setup_runner.html.haml2
-rw-r--r--app/views/ci/runner/_how_to_setup_specific_runner.html.haml2
-rw-r--r--app/views/dashboard/_groups_head.html.haml2
-rw-r--r--app/views/dashboard/_projects_head.html.haml2
-rw-r--r--app/views/dashboard/_snippets_head.html.haml2
-rw-r--r--app/views/dashboard/issues.atom.builder2
-rw-r--r--app/views/dashboard/snippets/index.html.haml2
-rw-r--r--app/views/devise/passwords/edit.html.haml6
-rw-r--r--app/views/devise/sessions/_new_base.html.haml10
-rw-r--r--app/views/devise/sessions/_new_crowd.html.haml2
-rw-r--r--app/views/devise/sessions/_new_ldap.html.haml6
-rw-r--r--app/views/devise/sessions/two_factor.html.haml2
-rw-r--r--app/views/devise/shared/_omniauth_box.html.haml27
-rw-r--r--app/views/devise/shared/_signup_box.html.haml17
-rw-r--r--app/views/devise/shared/_tabs_ldap.html.haml4
-rw-r--r--app/views/devise/shared/_tabs_normal.html.haml4
-rw-r--r--app/views/discussions/_diff_discussion.html.haml3
-rw-r--r--app/views/discussions/_parallel_diff_discussion.html.haml12
-rw-r--r--app/views/doorkeeper/applications/_form.html.haml2
-rw-r--r--app/views/doorkeeper/applications/index.html.haml5
-rw-r--r--app/views/events/_event.html.haml2
-rw-r--r--app/views/events/_event_scope.html.haml2
-rw-r--r--app/views/events/event/_common.html.haml2
-rw-r--r--app/views/events/event/_created_project.html.haml4
-rw-r--r--app/views/events/event/_note.html.haml2
-rw-r--r--app/views/events/event/_private.html.haml10
-rw-r--r--app/views/events/event/_push.html.haml2
-rw-r--r--app/views/groups/_archived_projects.html.haml8
-rw-r--r--app/views/groups/_children.html.haml4
-rw-r--r--app/views/groups/_group_admin_settings.html.haml6
-rw-r--r--app/views/groups/_shared_projects.html.haml8
-rw-r--r--app/views/groups/_subgroups_and_projects.html.haml8
-rw-r--r--app/views/groups/group_members/_new_group_member.html.haml2
-rw-r--r--app/views/groups/issues.atom.builder2
-rw-r--r--app/views/groups/labels/index.html.haml3
-rw-r--r--app/views/groups/milestones/_form.html.haml4
-rw-r--r--app/views/groups/milestones/index.html.haml2
-rw-r--r--app/views/groups/new.html.haml2
-rw-r--r--app/views/groups/settings/_permissions.html.haml2
-rw-r--r--app/views/groups/show.html.haml37
-rw-r--r--app/views/help/instance_configuration/_gitlab_pages.html.haml2
-rw-r--r--app/views/help/ui.html.haml2
-rw-r--r--app/views/import/fogbugz/new.html.haml2
-rw-r--r--app/views/import/fogbugz/new_user_map.html.haml2
-rw-r--r--app/views/import/gitea/new.html.haml2
-rw-r--r--app/views/import/gitlab_projects/new.html.haml11
-rw-r--r--app/views/import/google_code/new.html.haml2
-rw-r--r--app/views/import/google_code/new_user_map.html.haml2
-rw-r--r--app/views/instance_statistics/cohorts/index.html.haml16
-rw-r--r--app/views/instance_statistics/conversational_development_index/_disabled.html.haml17
-rw-r--r--app/views/instance_statistics/conversational_development_index/index.html.haml7
-rw-r--r--app/views/issues/_issues_calendar.ics.ruby2
-rw-r--r--app/views/layouts/_page.html.haml1
-rw-r--r--app/views/layouts/nav/sidebar/_admin.html.haml52
-rw-r--r--app/views/layouts/nav/sidebar/_instance_statistics.html.haml23
-rw-r--r--app/views/layouts/nav/sidebar/_profile.html.haml23
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml4
-rw-r--r--app/views/notify/_failed_builds.html.haml32
-rw-r--r--app/views/notify/autodevops_disabled_email.html.haml49
-rw-r--r--app/views/notify/autodevops_disabled_email.text.erb20
-rw-r--r--app/views/notify/pipeline_failed_email.html.haml35
-rw-r--r--app/views/profiles/emails/index.html.haml10
-rw-r--r--app/views/profiles/gpg_keys/_form.html.haml2
-rw-r--r--app/views/profiles/keys/_form.html.haml8
-rw-r--r--app/views/profiles/keys/_key_details.html.haml2
-rw-r--r--app/views/profiles/passwords/edit.html.haml2
-rw-r--r--app/views/profiles/passwords/new.html.haml4
-rw-r--r--app/views/profiles/preferences/show.html.haml2
-rw-r--r--app/views/profiles/show.html.haml72
-rw-r--r--app/views/profiles/two_factor_auths/_codes.html.haml4
-rw-r--r--app/views/projects/_commit_button.html.haml2
-rw-r--r--app/views/projects/_flash_messages.html.haml1
-rw-r--r--app/views/projects/_fork_suggestion.html.haml2
-rw-r--r--app/views/projects/_home_panel.html.haml83
-rw-r--r--app/views/projects/_import_project_pane.html.haml2
-rw-r--r--app/views/projects/_new_project_fields.html.haml36
-rw-r--r--app/views/projects/_project_templates.html.haml2
-rw-r--r--app/views/projects/_readme.html.haml2
-rw-r--r--app/views/projects/_stat_anchor_list.html.haml2
-rw-r--r--app/views/projects/_wiki.html.haml2
-rw-r--r--app/views/projects/blob/_blob.html.haml1
-rw-r--r--app/views/projects/blob/_new_dir.html.haml2
-rw-r--r--app/views/projects/blob/_upload.html.haml2
-rw-r--r--app/views/projects/blob/edit.html.haml2
-rw-r--r--app/views/projects/blob/preview.html.haml2
-rw-r--r--app/views/projects/blob/viewers/_gitlab_ci_yml.html.haml4
-rw-r--r--app/views/projects/blob/viewers/_markup.html.haml6
-rw-r--r--app/views/projects/branches/index.html.haml2
-rw-r--r--app/views/projects/branches/new.html.haml2
-rw-r--r--app/views/projects/buttons/_fork.html.haml30
-rw-r--r--app/views/projects/buttons/_star.html.haml30
-rw-r--r--app/views/projects/clusters/_banner.html.haml14
-rw-r--r--app/views/projects/clusters/_integration_form.html.haml20
-rw-r--r--app/views/projects/clusters/gcp/_form.html.haml10
-rw-r--r--app/views/projects/clusters/gcp/_show.html.haml9
-rw-r--r--app/views/projects/clusters/show.html.haml3
-rw-r--r--app/views/projects/clusters/user/_form.html.haml10
-rw-r--r--app/views/projects/clusters/user/_show.html.haml9
-rw-r--r--app/views/projects/commit/_change.html.haml2
-rw-r--r--app/views/projects/compare/_form.html.haml2
-rw-r--r--app/views/projects/deploy_keys/_form.html.haml2
-rw-r--r--app/views/projects/deploy_keys/edit.html.haml2
-rw-r--r--app/views/projects/diffs/_stats.html.haml4
-rw-r--r--app/views/projects/edit.html.haml6
-rw-r--r--app/views/projects/empty.html.haml18
-rw-r--r--app/views/projects/environments/_form.html.haml2
-rw-r--r--app/views/projects/environments/terminal.html.haml2
-rw-r--r--app/views/projects/find_file/show.html.haml2
-rw-r--r--app/views/projects/forks/index.html.haml4
-rw-r--r--app/views/projects/hooks/_index.html.haml2
-rw-r--r--app/views/projects/hooks/edit.html.haml2
-rw-r--r--app/views/projects/imports/new.html.haml2
-rw-r--r--app/views/projects/issues/_issue.html.haml6
-rw-r--r--app/views/projects/issues/_nav_btns.html.haml2
-rw-r--r--app/views/projects/issues/index.atom.builder2
-rw-r--r--app/views/projects/issues/show.html.haml2
-rw-r--r--app/views/projects/jobs/_header.html.haml2
-rw-r--r--app/views/projects/jobs/index.html.haml2
-rw-r--r--app/views/projects/jobs/show.html.haml5
-rw-r--r--app/views/projects/labels/index.html.haml3
-rw-r--r--app/views/projects/mattermosts/_team_selection.html.haml2
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml6
-rw-r--r--app/views/projects/merge_requests/_nav_btns.html.haml2
-rw-r--r--app/views/projects/merge_requests/creations/_new_compare.html.haml2
-rw-r--r--app/views/projects/merge_requests/diffs/_diffs.html.haml2
-rw-r--r--app/views/projects/milestones/_form.html.haml4
-rw-r--r--app/views/projects/milestones/index.html.haml2
-rw-r--r--app/views/projects/mirrors/_mirror_repos.html.haml2
-rw-r--r--app/views/projects/pages/show.html.haml2
-rw-r--r--app/views/projects/pages_domains/edit.html.haml2
-rw-r--r--app/views/projects/pages_domains/new.html.haml2
-rw-r--r--app/views/projects/pipeline_schedules/_form.html.haml2
-rw-r--r--app/views/projects/pipeline_schedules/index.html.haml2
-rw-r--r--app/views/projects/project_members/_new_project_group.html.haml (renamed from app/views/projects/project_members/_new_shared_group.html.haml)12
-rw-r--r--app/views/projects/project_members/_new_project_member.html.haml2
-rw-r--r--app/views/projects/project_members/import.html.haml2
-rw-r--r--app/views/projects/project_members/index.html.haml16
-rw-r--r--app/views/projects/protected_branches/shared/_create_protected_branch.html.haml2
-rw-r--r--app/views/projects/protected_tags/shared/_create_protected_tag.html.haml2
-rw-r--r--app/views/projects/releases/edit.html.haml2
-rw-r--r--app/views/projects/runners/_group_runners.html.haml2
-rw-r--r--app/views/projects/runners/_runner.html.haml2
-rw-r--r--app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml2
-rw-r--r--app/views/projects/services/slack_slash_commands/_help.html.haml2
-rw-r--r--app/views/projects/settings/ci_cd/_form.html.haml2
-rw-r--r--app/views/projects/show.html.haml9
-rw-r--r--app/views/projects/snippets/_actions.html.haml2
-rw-r--r--app/views/projects/snippets/index.html.haml2
-rw-r--r--app/views/projects/tags/index.html.haml2
-rw-r--r--app/views/projects/tags/new.html.haml2
-rw-r--r--app/views/projects/tree/_tree_commit_column.html.haml2
-rw-r--r--app/views/projects/triggers/_form.html.haml2
-rw-r--r--app/views/projects/update.js.haml2
-rw-r--r--app/views/projects/wikis/_form.html.haml10
-rw-r--r--app/views/projects/wikis/_main_links.html.haml2
-rw-r--r--app/views/projects/wikis/_new.html.haml2
-rw-r--r--app/views/projects/wikis/_sidebar.html.haml2
-rw-r--r--app/views/projects/wikis/edit.html.haml19
-rw-r--r--app/views/projects/wikis/show.html.haml2
-rw-r--r--app/views/search/results/_snippet_blob.html.haml2
-rw-r--r--app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml9
-rw-r--r--app/views/shared/_clone_panel.html.haml6
-rw-r--r--app/views/shared/_mobile_clone_panel.html.haml13
-rw-r--r--app/views/shared/_new_project_item_select.html.haml4
-rw-r--r--app/views/shared/_personal_access_tokens_form.html.haml2
-rw-r--r--app/views/shared/_ping_consent.html.haml12
-rw-r--r--app/views/shared/_recaptcha_form.html.haml2
-rw-r--r--app/views/shared/_visibility_level.html.haml2
-rw-r--r--app/views/shared/empty_states/_labels.html.haml2
-rw-r--r--app/views/shared/empty_states/_merge_requests.html.haml2
-rw-r--r--app/views/shared/empty_states/_wikis.html.haml4
-rw-r--r--app/views/shared/groups/_empty_state.html.haml7
-rw-r--r--app/views/shared/groups/_search_form.html.haml4
-rw-r--r--app/views/shared/issuable/_assignees.html.haml2
-rw-r--r--app/views/shared/issuable/_board_create_list_dropdown.html.haml2
-rw-r--r--app/views/shared/issuable/_form.html.haml4
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml5
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml2
-rw-r--r--app/views/shared/issuable/form/_metadata.html.haml2
-rw-r--r--app/views/shared/issuable/form/_title.html.haml2
-rw-r--r--app/views/shared/labels/_form.html.haml4
-rw-r--r--app/views/shared/labels/_sort_dropdown.html.haml9
-rw-r--r--app/views/shared/members/_access_request_buttons.html.haml4
-rw-r--r--app/views/shared/notes/_comment_button.html.haml2
-rw-r--r--app/views/shared/notes/_edit_form.html.haml2
-rw-r--r--app/views/shared/runners/_form.html.haml2
-rw-r--r--app/views/shared/runners/_runner_description.html.haml2
-rw-r--r--app/views/shared/snippets/_form.html.haml4
-rw-r--r--app/views/snippets/_actions.html.haml2
-rw-r--r--app/views/u2f/_register.html.haml4
-rw-r--r--app/views/users/calendar_activities.html.haml42
-rw-r--r--app/workers/admin_email_worker.rb2
-rw-r--r--app/workers/all_queues.yml3
-rw-r--r--app/workers/archive_trace_worker.rb2
-rw-r--r--app/workers/authorized_projects_worker.rb2
-rw-r--r--app/workers/auto_devops/disable_worker.rb35
-rw-r--r--app/workers/build_coverage_worker.rb2
-rw-r--r--app/workers/build_finished_worker.rb2
-rw-r--r--app/workers/build_hooks_worker.rb2
-rw-r--r--app/workers/build_queue_worker.rb2
-rw-r--r--app/workers/build_success_worker.rb2
-rw-r--r--app/workers/build_trace_sections_worker.rb2
-rw-r--r--app/workers/ci/archive_traces_cron_worker.rb2
-rw-r--r--app/workers/ci/build_trace_chunk_flush_worker.rb2
-rw-r--r--app/workers/concerns/auto_devops_queue.rb9
-rw-r--r--app/workers/concerns/gitlab/github_import/rescheduling_methods.rb2
-rw-r--r--app/workers/concerns/gitlab/github_import/stage_methods.rb2
-rw-r--r--app/workers/concerns/new_issuable.rb4
-rw-r--r--app/workers/create_gpg_signature_worker.rb2
-rw-r--r--app/workers/delete_container_repository_worker.rb36
-rw-r--r--app/workers/delete_diff_files_worker.rb2
-rw-r--r--app/workers/detect_repository_languages_worker.rb2
-rw-r--r--app/workers/expire_build_artifacts_worker.rb2
-rw-r--r--app/workers/expire_build_instance_artifacts_worker.rb2
-rw-r--r--app/workers/expire_job_cache_worker.rb2
-rw-r--r--app/workers/expire_pipeline_cache_worker.rb2
-rw-r--r--app/workers/gitlab/github_import/advance_stage_worker.rb2
-rw-r--r--app/workers/gitlab/github_import/refresh_import_jid_worker.rb2
-rw-r--r--app/workers/invalid_gpg_signature_update_worker.rb2
-rw-r--r--app/workers/issue_due_scheduler_worker.rb2
-rw-r--r--app/workers/mail_scheduler/issue_due_worker.rb2
-rw-r--r--app/workers/new_merge_request_worker.rb2
-rw-r--r--app/workers/new_note_worker.rb2
-rw-r--r--app/workers/object_storage/migrate_uploads_worker.rb4
-rw-r--r--app/workers/pages_domain_verification_worker.rb2
-rw-r--r--app/workers/pages_worker.rb2
-rw-r--r--app/workers/pipeline_hooks_worker.rb2
-rw-r--r--app/workers/pipeline_metrics_worker.rb4
-rw-r--r--app/workers/pipeline_notification_worker.rb2
-rw-r--r--app/workers/pipeline_process_worker.rb2
-rw-r--r--app/workers/pipeline_schedule_worker.rb2
-rw-r--r--app/workers/pipeline_success_worker.rb2
-rw-r--r--app/workers/pipeline_update_worker.rb2
-rw-r--r--app/workers/process_commit_worker.rb4
-rw-r--r--app/workers/project_cache_worker.rb2
-rw-r--r--app/workers/project_migrate_hashed_storage_worker.rb2
-rw-r--r--app/workers/project_service_worker.rb6
-rw-r--r--app/workers/propagate_service_template_worker.rb2
-rw-r--r--app/workers/prune_old_events_worker.rb2
-rw-r--r--app/workers/prune_web_hook_logs_worker.rb2
-rw-r--r--app/workers/reactive_caching_worker.rb2
-rw-r--r--app/workers/repository_check/batch_worker.rb6
-rw-r--r--app/workers/repository_check/clear_worker.rb2
-rw-r--r--app/workers/repository_check/single_repository_worker.rb2
-rw-r--r--app/workers/run_pipeline_schedule_worker.rb2
-rw-r--r--app/workers/stage_update_worker.rb2
-rw-r--r--app/workers/stuck_ci_jobs_worker.rb2
-rw-r--r--app/workers/stuck_import_jobs_worker.rb8
-rw-r--r--app/workers/stuck_merge_jobs_worker.rb6
-rw-r--r--app/workers/update_head_pipeline_for_merge_request_worker.rb2
-rw-r--r--app/workers/update_merge_requests_worker.rb2
1180 files changed, 8846 insertions, 4293 deletions
diff --git a/app/assets/images/auth_buttons/auth0_64.png b/app/assets/images/auth_buttons/auth0_64.png
new file mode 100644
index 00000000000..5ad59659380
--- /dev/null
+++ b/app/assets/images/auth_buttons/auth0_64.png
Binary files differ
diff --git a/app/assets/images/auth_buttons/azure_64.png b/app/assets/images/auth_buttons/azure_64.png
index 85de7793440..168a9c81395 100644
--- a/app/assets/images/auth_buttons/azure_64.png
+++ b/app/assets/images/auth_buttons/azure_64.png
Binary files differ
diff --git a/app/assets/images/auth_buttons/bitbucket_64.png b/app/assets/images/auth_buttons/bitbucket_64.png
index b3d022a5a70..0edf7f52a11 100644
--- a/app/assets/images/auth_buttons/bitbucket_64.png
+++ b/app/assets/images/auth_buttons/bitbucket_64.png
Binary files differ
diff --git a/app/assets/images/auth_buttons/google_64.png b/app/assets/images/auth_buttons/google_64.png
index 720824230a5..389c1cd54ca 100644
--- a/app/assets/images/auth_buttons/google_64.png
+++ b/app/assets/images/auth_buttons/google_64.png
Binary files differ
diff --git a/app/assets/images/auth_buttons/jwt_64.png b/app/assets/images/auth_buttons/jwt_64.png
new file mode 100644
index 00000000000..ca97ae47002
--- /dev/null
+++ b/app/assets/images/auth_buttons/jwt_64.png
Binary files differ
diff --git a/app/assets/images/auth_buttons/shibboleth_64.png b/app/assets/images/auth_buttons/shibboleth_64.png
new file mode 100644
index 00000000000..d4c752f9400
--- /dev/null
+++ b/app/assets/images/auth_buttons/shibboleth_64.png
Binary files differ
diff --git a/app/assets/images/cluster_app_logos/elasticsearch.png b/app/assets/images/cluster_app_logos/elasticsearch.png
new file mode 100644
index 00000000000..96e9e0ff934
--- /dev/null
+++ b/app/assets/images/cluster_app_logos/elasticsearch.png
Binary files differ
diff --git a/app/assets/images/cluster_app_logos/gitlab.png b/app/assets/images/cluster_app_logos/gitlab.png
new file mode 100644
index 00000000000..cb2195fc6a2
--- /dev/null
+++ b/app/assets/images/cluster_app_logos/gitlab.png
Binary files differ
diff --git a/app/assets/images/cluster_app_logos/helm.png b/app/assets/images/cluster_app_logos/helm.png
new file mode 100644
index 00000000000..2989cae7b93
--- /dev/null
+++ b/app/assets/images/cluster_app_logos/helm.png
Binary files differ
diff --git a/app/assets/images/cluster_app_logos/jeager.png b/app/assets/images/cluster_app_logos/jeager.png
new file mode 100644
index 00000000000..be5bf2a0c9c
--- /dev/null
+++ b/app/assets/images/cluster_app_logos/jeager.png
Binary files differ
diff --git a/app/assets/images/cluster_app_logos/jupyterhub.png b/app/assets/images/cluster_app_logos/jupyterhub.png
new file mode 100644
index 00000000000..80c7343067f
--- /dev/null
+++ b/app/assets/images/cluster_app_logos/jupyterhub.png
Binary files differ
diff --git a/app/assets/images/cluster_app_logos/kubernetes.png b/app/assets/images/cluster_app_logos/kubernetes.png
new file mode 100644
index 00000000000..4d774909c10
--- /dev/null
+++ b/app/assets/images/cluster_app_logos/kubernetes.png
Binary files differ
diff --git a/app/assets/images/cluster_app_logos/meltano.png b/app/assets/images/cluster_app_logos/meltano.png
new file mode 100644
index 00000000000..7a2d82fbe27
--- /dev/null
+++ b/app/assets/images/cluster_app_logos/meltano.png
Binary files differ
diff --git a/app/assets/images/cluster_app_logos/prometheus.png b/app/assets/images/cluster_app_logos/prometheus.png
new file mode 100644
index 00000000000..a8663449b88
--- /dev/null
+++ b/app/assets/images/cluster_app_logos/prometheus.png
Binary files differ
diff --git a/app/assets/javascripts/badges/components/badge.vue b/app/assets/javascripts/badges/components/badge.vue
index 155c348286c..97232d7f783 100644
--- a/app/assets/javascripts/badges/components/badge.vue
+++ b/app/assets/javascripts/badges/components/badge.vue
@@ -1,13 +1,11 @@
<script>
import Icon from '~/vue_shared/components/icon.vue';
-import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
import Tooltip from '~/vue_shared/directives/tooltip';
export default {
name: 'Badge',
components: {
Icon,
- LoadingIcon,
Tooltip,
},
directives: {
@@ -80,7 +78,7 @@ export default {
/>
</a>
- <loading-icon
+ <gl-loading-icon
v-show="isLoading"
:inline="true"
/>
@@ -105,8 +103,8 @@ export default {
</div>
<button
- v-tooltip
v-show="hasError"
+ v-tooltip
:title="s__('Badges|Reload badge image')"
class="btn btn-transparent btn-sm text-primary"
type="button"
diff --git a/app/assets/javascripts/badges/components/badge_form.vue b/app/assets/javascripts/badges/components/badge_form.vue
index b3f25da87ce..aff7c4180e3 100644
--- a/app/assets/javascripts/badges/components/badge_form.vue
+++ b/app/assets/javascripts/badges/components/badge_form.vue
@@ -4,7 +4,6 @@ import { mapActions, mapState } from 'vuex';
import createFlash from '~/flash';
import { s__, sprintf } from '~/locale';
import LoadingButton from '~/vue_shared/components/loading_button.vue';
-import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
import createEmptyBadge from '../empty_badge';
import Badge from './badge.vue';
@@ -15,7 +14,6 @@ export default {
components: {
Badge,
LoadingButton,
- LoadingIcon,
},
props: {
isEditing: {
@@ -207,7 +205,7 @@ export default {
:link-url="renderedLinkUrl"
/>
<p v-show="isRendering">
- <loading-icon
+ <gl-loading-icon
:inline="true"
/>
</p>
diff --git a/app/assets/javascripts/badges/components/badge_list.vue b/app/assets/javascripts/badges/components/badge_list.vue
index d2ec0fbb2c0..359d3e10380 100644
--- a/app/assets/javascripts/badges/components/badge_list.vue
+++ b/app/assets/javascripts/badges/components/badge_list.vue
@@ -1,6 +1,5 @@
<script>
import { mapState } from 'vuex';
-import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
import BadgeListRow from './badge_list_row.vue';
import { GROUP_BADGE } from '../constants';
@@ -8,7 +7,6 @@ export default {
name: 'BadgeList',
components: {
BadgeListRow,
- LoadingIcon,
},
computed: {
...mapState(['badges', 'isLoading', 'kind']),
@@ -31,10 +29,10 @@ export default {
class="badge badge-pill"
>{{ badges.length }}</span>
</div>
- <loading-icon
+ <gl-loading-icon
v-show="isLoading"
+ :size="2"
class="card-body"
- size="2"
/>
<div
v-if="hasNoBadges"
diff --git a/app/assets/javascripts/badges/components/badge_list_row.vue b/app/assets/javascripts/badges/components/badge_list_row.vue
index 712d81d0430..5d16ba3ce6d 100644
--- a/app/assets/javascripts/badges/components/badge_list_row.vue
+++ b/app/assets/javascripts/badges/components/badge_list_row.vue
@@ -2,7 +2,6 @@
import { mapActions, mapState } from 'vuex';
import { s__ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
-import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
import { PROJECT_BADGE } from '../constants';
import Badge from './badge.vue';
@@ -11,7 +10,6 @@ export default {
components: {
Badge,
Icon,
- LoadingIcon,
},
props: {
badge: {
@@ -79,7 +77,7 @@ export default {
name="remove"
/>
</button>
- <loading-icon
+ <gl-loading-icon
v-show="badge.isDeleting"
:inline="true"
/>
diff --git a/app/assets/javascripts/behaviors/index.js b/app/assets/javascripts/behaviors/index.js
index 84fef4d8b4f..8c4eccc34a3 100644
--- a/app/assets/javascripts/behaviors/index.js
+++ b/app/assets/javascripts/behaviors/index.js
@@ -1,15 +1,19 @@
import './autosize';
import './bind_in_out';
import './markdown/render_gfm';
+import initGFMInput from './markdown/gfm_auto_complete';
import initCopyAsGFM from './markdown/copy_as_gfm';
import initCopyToClipboard from './copy_to_clipboard';
import './details_behavior';
import installGlEmojiElement from './gl_emoji';
import './quick_submit';
import './requires_input';
+import initPageShortcuts from './shortcuts';
import './toggler_behavior';
-import '../preview_markdown';
+import './preview_markdown';
installGlEmojiElement();
+initGFMInput();
initCopyAsGFM();
initCopyToClipboard();
+initPageShortcuts();
diff --git a/app/assets/javascripts/behaviors/markdown/gfm_auto_complete.js b/app/assets/javascripts/behaviors/markdown/gfm_auto_complete.js
new file mode 100644
index 00000000000..a303e504cc7
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/gfm_auto_complete.js
@@ -0,0 +1,19 @@
+import $ from 'jquery';
+import { convertPermissionToBoolean } from '~/lib/utils/common_utils';
+import GfmAutoComplete from '~/gfm_auto_complete';
+
+export default function initGFMInput() {
+ $('.js-gfm-input:not(.js-vue-textarea)').each((i, el) => {
+ const gfm = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources);
+ const enableGFM = convertPermissionToBoolean(el.dataset.supportsAutocomplete);
+
+ gfm.setup($(el), {
+ emojis: true,
+ members: enableGFM,
+ issues: enableGFM,
+ milestones: enableGFM,
+ mergeRequests: enableGFM,
+ labels: enableGFM,
+ });
+ });
+}
diff --git a/app/assets/javascripts/behaviors/markdown/render_gfm.js b/app/assets/javascripts/behaviors/markdown/render_gfm.js
index dbff2bd4b10..429455f97ec 100644
--- a/app/assets/javascripts/behaviors/markdown/render_gfm.js
+++ b/app/assets/javascripts/behaviors/markdown/render_gfm.js
@@ -3,7 +3,7 @@ import syntaxHighlight from '~/syntax_highlight';
import renderMath from './render_math';
import renderMermaid from './render_mermaid';
-// Render Gitlab flavoured Markdown
+// Render GitLab flavoured Markdown
//
// Delegates to syntax highlight and render math & mermaid diagrams.
//
diff --git a/app/assets/javascripts/preview_markdown.js b/app/assets/javascripts/behaviors/preview_markdown.js
index 0964baf8954..0964baf8954 100644
--- a/app/assets/javascripts/preview_markdown.js
+++ b/app/assets/javascripts/behaviors/preview_markdown.js
diff --git a/app/assets/javascripts/behaviors/shortcuts.js b/app/assets/javascripts/behaviors/shortcuts.js
new file mode 100644
index 00000000000..7987a533ae5
--- /dev/null
+++ b/app/assets/javascripts/behaviors/shortcuts.js
@@ -0,0 +1,35 @@
+import Shortcuts from './shortcuts/shortcuts';
+
+export default function initPageShortcuts() {
+ const { page } = document.body.dataset;
+ const pagesWithCustomShortcuts = [
+ 'projects:activity',
+ 'projects:artifacts:browse',
+ 'projects:artifacts:file',
+ 'projects:blame:show',
+ 'projects:blob:show',
+ 'projects:commit:show',
+ 'projects:commits:show',
+ 'projects:find_file:show',
+ 'projects:issues:edit',
+ 'projects:issues:index',
+ 'projects:issues:new',
+ 'projects:issues:show',
+ 'projects:merge_requests:creations:diffs',
+ 'projects:merge_requests:creations:new',
+ 'projects:merge_requests:edit',
+ 'projects:merge_requests:index',
+ 'projects:merge_requests:show',
+ 'projects:network:show',
+ 'projects:show',
+ 'projects:tree:show',
+ 'groups:show',
+ ];
+
+ // the pages above have their own shortcuts sub-classes instantiated elsewhere
+ // TODO: replace this whitelist with something more automated/maintainable
+ if (page && !pagesWithCustomShortcuts.includes(page)) {
+ return new Shortcuts();
+ }
+ return false;
+}
diff --git a/app/assets/javascripts/shortcuts.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js
index 99c71d6524a..6719bfd6d22 100644
--- a/app/assets/javascripts/shortcuts.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js
@@ -1,9 +1,9 @@
import $ from 'jquery';
import Cookies from 'js-cookie';
import Mousetrap from 'mousetrap';
-import axios from './lib/utils/axios_utils';
-import { refreshCurrentPage, visitUrl } from './lib/utils/url_utility';
-import findAndFollowLink from './shortcuts_dashboard_navigation';
+import axios from '../../lib/utils/axios_utils';
+import { refreshCurrentPage, visitUrl } from '../../lib/utils/url_utility';
+import findAndFollowLink from '../../lib/utils/navigation_utility';
const defaultStopCallback = Mousetrap.stopCallback;
Mousetrap.stopCallback = (e, element, combo) => {
diff --git a/app/assets/javascripts/shortcuts_blob.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_blob.js
index 908b9cab93d..052e33b4a2b 100644
--- a/app/assets/javascripts/shortcuts_blob.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_blob.js
@@ -1,5 +1,5 @@
import Mousetrap from 'mousetrap';
-import { getLocationHash, visitUrl } from './lib/utils/url_utility';
+import { getLocationHash, visitUrl } from '../../lib/utils/url_utility';
import Shortcuts from './shortcuts';
const defaults = {
diff --git a/app/assets/javascripts/shortcuts_find_file.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_find_file.js
index 8658081c6c2..8658081c6c2 100644
--- a/app/assets/javascripts/shortcuts_find_file.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_find_file.js
diff --git a/app/assets/javascripts/shortcuts_issuable.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js
index e9451be31fd..5e48bf5a35c 100644
--- a/app/assets/javascripts/shortcuts_issuable.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js
@@ -1,9 +1,9 @@
import $ from 'jquery';
import Mousetrap from 'mousetrap';
import _ from 'underscore';
-import Sidebar from './right_sidebar';
+import Sidebar from '../../right_sidebar';
import Shortcuts from './shortcuts';
-import { CopyAsGFM } from './behaviors/markdown/copy_as_gfm';
+import { CopyAsGFM } from '../markdown/copy_as_gfm';
export default class ShortcutsIssuable extends Shortcuts {
constructor(isMergeRequest) {
diff --git a/app/assets/javascripts/shortcuts_navigation.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js
index 6b595764bc5..fa9b2c9f755 100644
--- a/app/assets/javascripts/shortcuts_navigation.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js
@@ -1,5 +1,5 @@
import Mousetrap from 'mousetrap';
-import findAndFollowLink from './shortcuts_dashboard_navigation';
+import findAndFollowLink from '../../lib/utils/navigation_utility';
import Shortcuts from './shortcuts';
export default class ShortcutsNavigation extends Shortcuts {
diff --git a/app/assets/javascripts/shortcuts_network.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_network.js
index a88c280fa3b..a88c280fa3b 100644
--- a/app/assets/javascripts/shortcuts_network.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_network.js
diff --git a/app/assets/javascripts/shortcuts_wiki.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_wiki.js
index 41865dcf4ba..8b7e6a56d25 100644
--- a/app/assets/javascripts/shortcuts_wiki.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_wiki.js
@@ -1,6 +1,6 @@
import Mousetrap from 'mousetrap';
import ShortcutsNavigation from './shortcuts_navigation';
-import findAndFollowLink from './shortcuts_dashboard_navigation';
+import findAndFollowLink from '../../lib/utils/navigation_utility';
export default class ShortcutsWiki extends ShortcutsNavigation {
constructor() {
diff --git a/app/assets/javascripts/blob/3d_viewer/index.js b/app/assets/javascripts/blob/3d_viewer/index.js
index 68d4ddad551..1bdf1aeb76c 100644
--- a/app/assets/javascripts/blob/3d_viewer/index.js
+++ b/app/assets/javascripts/blob/3d_viewer/index.js
@@ -29,12 +29,12 @@ export default class Renderer {
this.scene.add(this.camera);
- // Setup the viewer
+ // Set up the viewer
this.setupRenderer();
this.setupGrid();
this.setupLight();
- // Setup OrbitControls
+ // Set up OrbitControls
this.controls = new OrbitControls(
this.camera,
this.renderer.domElement,
diff --git a/app/assets/javascripts/boards/components/board_blank_state.vue b/app/assets/javascripts/boards/components/board_blank_state.vue
index 286529b4d13..cde22725a89 100644
--- a/app/assets/javascripts/boards/components/board_blank_state.vue
+++ b/app/assets/javascripts/boards/components/board_blank_state.vue
@@ -83,7 +83,7 @@ export default {
right on the way to making the most of your board.
</p>
<button
- class="btn btn-create btn-inverted btn-block"
+ class="btn btn-success btn-inverted btn-block"
type="button"
@click.stop="addDefaultLists">
Add default lists
diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue
index bfc8d9b03ad..7ddb22ad824 100644
--- a/app/assets/javascripts/boards/components/board_list.vue
+++ b/app/assets/javascripts/boards/components/board_list.vue
@@ -3,7 +3,6 @@ import Sortable from 'sortablejs';
import boardNewIssue from './board_new_issue.vue';
import boardCard from './board_card.vue';
import eventHub from '../eventhub';
-import loadingIcon from '../../vue_shared/components/loading_icon.vue';
const Store = gl.issueBoards.BoardsStore;
@@ -12,7 +11,6 @@ export default {
components: {
boardCard,
boardNewIssue,
- loadingIcon,
},
props: {
groupId: {
@@ -217,7 +215,7 @@ export default {
v-if="loading"
class="board-list-loading text-center"
aria-label="Loading issues">
- <loading-icon />
+ <gl-loading-icon />
</div>
<board-new-issue
v-if="list.type !== 'closed' && showIssueForm"
@@ -233,19 +231,19 @@ export default {
<board-card
v-for="(issue, index) in issues"
ref="issue"
+ :key="issue.id"
:index="index"
:list="list"
:issue="issue"
:issue-link-base="issueLinkBase"
:group-id="groupId"
:root-path="rootPath"
- :disabled="disabled"
- :key="issue.id" />
+ :disabled="disabled" />
<li
v-if="showCount"
class="board-list-count text-center"
data-issue-id="-1">
- <loading-icon
+ <gl-loading-icon
v-show="list.loadingMore"
label="Loading more issues"
/>
diff --git a/app/assets/javascripts/boards/components/board_new_issue.vue b/app/assets/javascripts/boards/components/board_new_issue.vue
index 1e3cd43d1f0..f248f53fa51 100644
--- a/app/assets/javascripts/boards/components/board_new_issue.vue
+++ b/app/assets/javascripts/boards/components/board_new_issue.vue
@@ -110,9 +110,9 @@ export default {
Title
</label>
<input
+ :id="list.id + '-title'"
ref="input"
v-model="title"
- :id="list.id + '-title'"
class="form-control"
type="text"
name="issue_title"
diff --git a/app/assets/javascripts/boards/components/issue_card_inner.vue b/app/assets/javascripts/boards/components/issue_card_inner.vue
index d50641dc3a9..f56d3fe000c 100644
--- a/app/assets/javascripts/boards/components/issue_card_inner.vue
+++ b/app/assets/javascripts/boards/components/issue_card_inner.vue
@@ -170,8 +170,8 @@
tooltip-placement="bottom"
/>
<span
- v-tooltip
v-if="shouldRenderCounter"
+ v-tooltip
:title="assigneeCounterTooltip"
class="avatar-counter"
>
@@ -184,10 +184,10 @@
class="board-card-footer"
>
<button
- v-tooltip
v-for="label in issue.labels"
v-if="showLabel(label)"
:key="label.id"
+ v-tooltip
:style="labelStyle(label)"
:title="label.description"
class="badge color-label"
diff --git a/app/assets/javascripts/boards/components/modal/index.vue b/app/assets/javascripts/boards/components/modal/index.vue
index 33e72a6782e..0c4c709324d 100644
--- a/app/assets/javascripts/boards/components/modal/index.vue
+++ b/app/assets/javascripts/boards/components/modal/index.vue
@@ -1,7 +1,6 @@
<script>
/* global ListIssue */
- import queryData from '~/boards/utils/query_data';
- import loadingIcon from '~/vue_shared/components/loading_icon.vue';
+ import { urlParamsToObject } from '~/lib/utils/common_utils';
import ModalHeader from './header.vue';
import ModalList from './list.vue';
import ModalFooter from './footer.vue';
@@ -14,7 +13,6 @@
ModalHeader,
ModalList,
ModalFooter,
- loadingIcon,
},
props: {
newIssuePath: {
@@ -109,13 +107,11 @@
loadIssues(clearIssues = false) {
if (!this.showAddIssuesModal) return false;
- return gl.boardService
- .getBacklog(
- queryData(this.filter.path, {
- page: this.page,
- per: this.perPage,
- }),
- )
+ return gl.boardService.getBacklog({
+ ...urlParamsToObject(this.filter.path),
+ page: this.page,
+ per: this.perPage,
+ })
.then(res => res.data)
.then(data => {
if (clearIssues) {
@@ -169,7 +165,7 @@
class="add-issues-list text-center"
>
<div class="add-issues-list-loading">
- <loading-icon />
+ <gl-loading-icon />
</div>
</section>
<modal-footer/>
diff --git a/app/assets/javascripts/boards/components/project_select.vue b/app/assets/javascripts/boards/components/project_select.vue
index ef9844d5562..d4676914e02 100644
--- a/app/assets/javascripts/boards/components/project_select.vue
+++ b/app/assets/javascripts/boards/components/project_select.vue
@@ -2,14 +2,10 @@
import $ from 'jquery';
import _ from 'underscore';
import eventHub from '../eventhub';
-import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import Api from '../../api';
export default {
name: 'BoardProjectSelect',
- components: {
- loadingIcon,
- },
props: {
groupId: {
type: Number,
@@ -119,7 +115,7 @@ export default {
</div>
<div class="dropdown-content"></div>
<div class="dropdown-loading">
- <loading-icon />
+ <gl-loading-icon />
</div>
</div>
</div>
diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js
index bc263cbbfea..662363a6f26 100644
--- a/app/assets/javascripts/boards/index.js
+++ b/app/assets/javascripts/boards/index.js
@@ -9,7 +9,7 @@ import '~/vue_shared/models/assignee';
import FilteredSearchBoards from './filtered_search_boards';
import eventHub from './eventhub';
-import sidebarEventHub from '~/sidebar/event_hub'; // eslint-disable-line import/first
+import sidebarEventHub from '~/sidebar/event_hub';
import './models/issue';
import './models/list';
import './models/milestone';
@@ -24,7 +24,7 @@ import './components/board';
import './components/board_sidebar';
import './components/new_list_dropdown';
import BoardAddIssuesModal from './components/modal/index.vue';
-import '~/vue_shared/vue_resource_interceptor'; // eslint-disable-line import/first
+import '~/vue_shared/vue_resource_interceptor';
export default () => {
const $boardApp = document.getElementById('board-app');
@@ -229,7 +229,7 @@ export default () => {
template: `
<div class="board-extra-actions">
<button
- class="btn btn-create prepend-left-10"
+ class="btn btn-success prepend-left-10"
type="button"
data-placement="bottom"
ref="addIssuesButton"
diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js
index ad473404c29..d416b76f0f4 100644
--- a/app/assets/javascripts/boards/models/list.js
+++ b/app/assets/javascripts/boards/models/list.js
@@ -4,7 +4,7 @@
import { __ } from '~/locale';
import ListLabel from '~/vue_shared/models/label';
import ListAssignee from '~/vue_shared/models/assignee';
-import queryData from '../utils/query_data';
+import { urlParamsToObject } from '~/lib/utils/common_utils';
const PER_PAGE = 20;
@@ -115,7 +115,10 @@ class List {
}
getIssues(emptyIssues = true) {
- const data = queryData(gl.issueBoards.BoardsStore.filter.path, { page: this.page });
+ const data = {
+ ...urlParamsToObject(gl.issueBoards.BoardsStore.filter.path),
+ page: this.page,
+ };
if (this.label && data.label_name) {
data.label_name = data.label_name.filter(label => label !== this.label.title);
diff --git a/app/assets/javascripts/boards/utils/query_data.js b/app/assets/javascripts/boards/utils/query_data.js
deleted file mode 100644
index 65315979df7..00000000000
--- a/app/assets/javascripts/boards/utils/query_data.js
+++ /dev/null
@@ -1,21 +0,0 @@
-export default (path, extraData) => path.split('&').reduce((dataParam, filterParam) => {
- if (filterParam === '') return dataParam;
-
- const data = dataParam;
- const paramSplit = filterParam.split('=');
- const paramKeyNormalized = paramSplit[0].replace('[]', '');
- const isArray = paramSplit[0].indexOf('[]');
- const value = decodeURIComponent(paramSplit[1].replace(/\+/g, ' '));
-
- if (isArray !== -1) {
- if (!data[paramKeyNormalized]) {
- data[paramKeyNormalized] = [];
- }
-
- data[paramKeyNormalized].push(value);
- } else {
- data[paramKeyNormalized] = value;
- }
-
- return data;
-}, extraData);
diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js
index 0fdf0c7a389..ebf76af5966 100644
--- a/app/assets/javascripts/clusters/clusters_bundle.js
+++ b/app/assets/javascripts/clusters/clusters_bundle.js
@@ -1,16 +1,12 @@
import Visibility from 'visibilityjs';
import Vue from 'vue';
+import initDismissableCallout from '~/dismissable_callout';
import { s__, sprintf } from '../locale';
import Flash from '../flash';
import Poll from '../lib/utils/poll';
import initSettingsPanels from '../settings_panels';
import eventHub from './event_hub';
-import {
- APPLICATION_STATUS,
- REQUEST_LOADING,
- REQUEST_SUCCESS,
- REQUEST_FAILURE,
-} from './constants';
+import { APPLICATION_STATUS, REQUEST_LOADING, REQUEST_SUCCESS, REQUEST_FAILURE } from './constants';
import ClustersService from './services/clusters_service';
import ClustersStore from './stores/clusters_store';
import applications from './components/applications.vue';
@@ -66,6 +62,7 @@ export default class Clusters {
this.showTokenButton = document.querySelector('.js-show-cluster-token');
this.tokenField = document.querySelector('.js-cluster-token');
+ initDismissableCallout('.js-cluster-security-warning');
initSettingsPanels();
setupToggleButtons(document.querySelector('.js-cluster-enable-toggle-area'));
this.initApplications();
@@ -129,7 +126,8 @@ export default class Clusters {
if (!Visibility.hidden()) {
this.poll.makeRequest();
} else {
- this.service.fetchData()
+ this.service
+ .fetchData()
.then(data => this.handleSuccess(data))
.catch(() => Clusters.handleError());
}
@@ -177,15 +175,21 @@ export default class Clusters {
checkForNewInstalls(prevApplicationMap, newApplicationMap) {
const appTitles = Object.keys(newApplicationMap)
- .filter(appId => newApplicationMap[appId].status === APPLICATION_STATUS.INSTALLED &&
- prevApplicationMap[appId].status !== APPLICATION_STATUS.INSTALLED &&
- prevApplicationMap[appId].status !== null)
+ .filter(
+ appId =>
+ newApplicationMap[appId].status === APPLICATION_STATUS.INSTALLED &&
+ prevApplicationMap[appId].status !== APPLICATION_STATUS.INSTALLED &&
+ prevApplicationMap[appId].status !== null,
+ )
.map(appId => newApplicationMap[appId].title);
if (appTitles.length > 0) {
- const text = sprintf(s__('ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster'), {
- appList: appTitles.join(', '),
- });
+ const text = sprintf(
+ s__('ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster'),
+ {
+ appList: appTitles.join(', '),
+ },
+ );
Flash(text, 'notice', this.successApplicationContainer);
}
}
@@ -218,13 +222,18 @@ export default class Clusters {
this.store.updateAppProperty(appId, 'requestStatus', REQUEST_LOADING);
this.store.updateAppProperty(appId, 'requestReason', null);
- this.service.installApplication(appId, data.params)
+ this.service
+ .installApplication(appId, data.params)
.then(() => {
this.store.updateAppProperty(appId, 'requestStatus', REQUEST_SUCCESS);
})
.catch(() => {
this.store.updateAppProperty(appId, 'requestStatus', REQUEST_FAILURE);
- this.store.updateAppProperty(appId, 'requestReason', s__('ClusterIntegration|Request to begin installing failed'));
+ this.store.updateAppProperty(
+ appId,
+ 'requestReason',
+ s__('ClusterIntegration|Request to begin installing failed'),
+ );
});
}
diff --git a/app/assets/javascripts/clusters/clusters_index.js b/app/assets/javascripts/clusters/clusters_index.js
index 1e5c733d151..789c8360124 100644
--- a/app/assets/javascripts/clusters/clusters_index.js
+++ b/app/assets/javascripts/clusters/clusters_index.js
@@ -1,14 +1,14 @@
import createFlash from '~/flash';
import { __ } from '~/locale';
import setupToggleButtons from '~/toggle_buttons';
-import gcpSignupOffer from '~/clusters/components/gcp_signup_offer';
+import initDismissableCallout from '~/dismissable_callout';
import ClustersService from './services/clusters_service';
export default () => {
const clusterList = document.querySelector('.js-clusters-list');
- gcpSignupOffer();
+ initDismissableCallout('.gcp-signup-offer');
// The empty state won't have a clusterList
if (clusterList) {
diff --git a/app/assets/javascripts/clusters/components/application_row.vue b/app/assets/javascripts/clusters/components/application_row.vue
index 651f3b50236..0452729d3ff 100644
--- a/app/assets/javascripts/clusters/components/application_row.vue
+++ b/app/assets/javascripts/clusters/components/application_row.vue
@@ -2,6 +2,7 @@
/* eslint-disable vue/require-default-prop */
import { s__, sprintf } from '../../locale';
import eventHub from '../event_hub';
+ import identicon from '../../vue_shared/components/identicon.vue';
import loadingButton from '../../vue_shared/components/loading_button.vue';
import {
APPLICATION_STATUS,
@@ -13,6 +14,7 @@
export default {
components: {
loadingButton,
+ identicon,
},
props: {
id: {
@@ -31,6 +33,16 @@
type: String,
required: false,
},
+ logoUrl: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ disabled: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
status: {
type: String,
required: false,
@@ -60,6 +72,18 @@
isKnownStatus() {
return Object.values(APPLICATION_STATUS).includes(this.status);
},
+ isInstalled() {
+ return (
+ this.status === APPLICATION_STATUS.INSTALLED || this.status === APPLICATION_STATUS.UPDATED
+ );
+ },
+ hasLogo() {
+ return !!this.logoUrl;
+ },
+ identiconId() {
+ // generate a deterministic integer id for the identicon background
+ return this.id.charCodeAt(0);
+ },
rowJsClass() {
return `js-cluster-application-row-${this.id}`;
},
@@ -128,37 +152,81 @@
<template>
<div
- :class="rowJsClass"
- class="gl-responsive-table-row gl-responsive-table-row-col-span"
+ :class="[
+ rowJsClass,
+ isInstalled && 'cluster-application-installed',
+ disabled && 'cluster-application-disabled'
+ ]"
+ class="cluster-application-row gl-responsive-table-row gl-responsive-table-row-col-span"
>
<div
class="gl-responsive-table-row-layout"
role="row"
>
- <a
- v-if="titleLink"
- :href="titleLink"
- target="blank"
- rel="noopener noreferrer"
+ <div
+ class="table-section append-right-8 section-align-top"
role="gridcell"
- class="table-section section-15 section-align-top js-cluster-application-title"
>
- {{ title }}
- </a>
- <span
- v-else
- class="table-section section-15 section-align-top js-cluster-application-title"
- >
- {{ title }}
- </span>
+ <img
+ v-if="hasLogo"
+ :src="logoUrl"
+ :alt="`${title} logo`"
+ class="cluster-application-logo avatar s40"
+ />
+ <identicon
+ v-else
+ :entity-id="identiconId"
+ :entity-name="title"
+ size-class="s40"
+ />
+ </div>
<div
- class="table-section section-wrap"
+ class="table-section cluster-application-description section-wrap"
role="gridcell"
>
+ <strong>
+ <a
+ v-if="titleLink"
+ :href="titleLink"
+ target="blank"
+ rel="noopener noreferrer"
+ class="js-cluster-application-title"
+ >
+ {{ title }}
+ </a>
+ <span
+ v-else
+ class="js-cluster-application-title"
+ >
+ {{ title }}
+ </span>
+ </strong>
<slot name="description"></slot>
+ <div
+ v-if="hasError || isUnknownStatus"
+ class="cluster-application-error text-danger prepend-top-10"
+ >
+ <p class="js-cluster-application-general-error-message append-bottom-0">
+ {{ generalErrorDescription }}
+ </p>
+ <ul v-if="statusReason || requestReason">
+ <li
+ v-if="statusReason"
+ class="js-cluster-application-status-error-message"
+ >
+ {{ statusReason }}
+ </li>
+ <li
+ v-if="requestReason"
+ class="js-cluster-application-request-error-message"
+ >
+ {{ requestReason }}
+ </li>
+ </ul>
+ </div>
</div>
<div
- :class="{ 'section-20': showManageButton, 'section-15': !showManageButton }"
+ :class="{ 'section-25': showManageButton, 'section-15': !showManageButton }"
class="table-section table-button-footer section-align-top"
role="gridcell"
>
@@ -168,6 +236,7 @@
>
<a
:href="manageLink"
+ :class="{ disabled: disabled }"
class="btn"
>
{{ manageButtonLabel }}
@@ -176,7 +245,7 @@
<div class="btn-group table-action-buttons">
<loading-button
:loading="installButtonLoading"
- :disabled="installButtonDisabled"
+ :disabled="disabled || installButtonDisabled"
:label="installButtonLabel"
class="js-cluster-application-install-button"
@click="installClicked"
@@ -184,35 +253,5 @@
</div>
</div>
</div>
- <div
- v-if="hasError || isUnknownStatus"
- class="gl-responsive-table-row-layout"
- role="row"
- >
- <div
- class="alert alert-danger alert-block append-bottom-0 clusters-error-alert"
- role="gridcell"
- >
- <div>
- <p class="js-cluster-application-general-error-message">
- {{ generalErrorDescription }}
- </p>
- <ul v-if="statusReason || requestReason">
- <li
- v-if="statusReason"
- class="js-cluster-application-status-error-message"
- >
- {{ statusReason }}
- </li>
- <li
- v-if="requestReason"
- class="js-cluster-application-request-error-message"
- >
- {{ requestReason }}
- </li>
- </ul>
- </div>
- </div>
- </div>
</div>
</template>
diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue
index d708a9e595a..a1069985178 100644
--- a/app/assets/javascripts/clusters/components/applications.vue
+++ b/app/assets/javascripts/clusters/components/applications.vue
@@ -1,5 +1,14 @@
<script>
import _ from 'underscore';
+import helmInstallIllustration from '@gitlab-org/gitlab-svgs/illustrations/kubernetes-installation.svg';
+import elasticsearchLogo from 'images/cluster_app_logos/elasticsearch.png';
+import gitlabLogo from 'images/cluster_app_logos/gitlab.png';
+import helmLogo from 'images/cluster_app_logos/helm.png';
+import jeagerLogo from 'images/cluster_app_logos/jeager.png';
+import jupyterhubLogo from 'images/cluster_app_logos/jupyterhub.png';
+import kubernetesLogo from 'images/cluster_app_logos/kubernetes.png';
+import meltanoLogo from 'images/cluster_app_logos/meltano.png';
+import prometheusLogo from 'images/cluster_app_logos/prometheus.png';
import { s__, sprintf } from '../../locale';
import applicationRow from './application_row.vue';
import clipboardButton from '../../vue_shared/components/clipboard_button.vue';
@@ -37,21 +46,21 @@ export default {
default: '',
},
},
+ data: () => ({
+ elasticsearchLogo,
+ gitlabLogo,
+ helmLogo,
+ jeagerLogo,
+ jupyterhubLogo,
+ kubernetesLogo,
+ meltanoLogo,
+ prometheusLogo,
+ }),
computed: {
- generalApplicationDescription() {
- return sprintf(
- _.escape(
- s__(
- `ClusterIntegration|Install applications on your Kubernetes cluster.
- Read more about %{helpLink}`,
- ),
- ),
- {
- helpLink: `<a href="${this.helpPath}">
- ${_.escape(s__('ClusterIntegration|installing applications'))}
- </a>`,
- },
- false,
+ helmInstalled() {
+ return (
+ this.applications.helm.status === APPLICATION_STATUS.INSTALLED ||
+ this.applications.helm.status === APPLICATION_STATUS.UPDATED
);
},
ingressId() {
@@ -128,224 +137,240 @@ export default {
return this.applications.jupyter.hostname;
},
},
+ created() {
+ this.helmInstallIllustration = helmInstallIllustration;
+ },
};
</script>
<template>
- <section
- id="cluster-applications"
- class="settings no-animate expanded"
- >
- <div class="settings-header">
- <h4>
- {{ s__('ClusterIntegration|Applications') }}
- </h4>
- <p
- class="append-bottom-0"
- v-html="generalApplicationDescription"
- >
- </p>
- </div>
+ <section id="cluster-applications">
+ <h4>
+ {{ s__('ClusterIntegration|Applications') }}
+ </h4>
+ <p class="append-bottom-0">
+ {{ s__(`ClusterIntegration|Choose which applications to install on your Kubernetes cluster.
+ Helm Tiller is required to install any of the following applications.`) }}
+ <a :href="helpPath">
+ {{ __('More information') }}
+ </a>
+ </p>
- <div class="settings-content">
- <div class="append-bottom-20">
- <application-row
- id="helm"
- :title="applications.helm.title"
- :status="applications.helm.status"
- :status-reason="applications.helm.statusReason"
- :request-status="applications.helm.requestStatus"
- :request-reason="applications.helm.requestReason"
- title-link="https://docs.helm.sh/"
- >
- <div slot="description">
- {{ s__(`ClusterIntegration|Helm streamlines installing
- and managing Kubernetes applications.
- Tiller runs inside of your Kubernetes Cluster,
- and manages releases of your charts.`) }}
- </div>
- </application-row>
- <application-row
- :id="ingressId"
- :title="applications.ingress.title"
- :status="applications.ingress.status"
- :status-reason="applications.ingress.statusReason"
- :request-status="applications.ingress.requestStatus"
- :request-reason="applications.ingress.requestReason"
- title-link="https://kubernetes.io/docs/concepts/services-networking/ingress/"
+ <div class="cluster-application-list prepend-top-10">
+ <application-row
+ id="helm"
+ :logo-url="helmLogo"
+ :title="applications.helm.title"
+ :status="applications.helm.status"
+ :status-reason="applications.helm.statusReason"
+ :request-status="applications.helm.requestStatus"
+ :request-reason="applications.helm.requestReason"
+ class="rounded-top"
+ title-link="https://docs.helm.sh/"
+ >
+ <div slot="description">
+ {{ s__(`ClusterIntegration|Helm streamlines installing
+ and managing Kubernetes applications.
+ Tiller runs inside of your Kubernetes Cluster,
+ and manages releases of your charts.`) }}
+ </div>
+ </application-row>
+ <div
+ v-show="!helmInstalled"
+ class="cluster-application-warning"
+ >
+ <div
+ class="svg-container"
+ v-html="helmInstallIllustration"
>
- <div slot="description">
- <p>
- {{ s__(`ClusterIntegration|Ingress gives you a way to route
- requests to services based on the request host or path,
- centralizing a number of services into a single entrypoint.`) }}
- </p>
+ </div>
+ {{ s__(`ClusterIntegration|You must first install Helm Tiller before
+ installing the applications below`) }}
+ </div>
+ <application-row
+ :id="ingressId"
+ :logo-url="kubernetesLogo"
+ :title="applications.ingress.title"
+ :status="applications.ingress.status"
+ :status-reason="applications.ingress.statusReason"
+ :request-status="applications.ingress.requestStatus"
+ :request-reason="applications.ingress.requestReason"
+ :disabled="!helmInstalled"
+ title-link="https://kubernetes.io/docs/concepts/services-networking/ingress/"
+ >
+ <div slot="description">
+ <p>
+ {{ s__(`ClusterIntegration|Ingress gives you a way to route
+ requests to services based on the request host or path,
+ centralizing a number of services into a single entrypoint.`) }}
+ </p>
- <template v-if="ingressInstalled">
- <div class="form-group">
- <label for="ingress-ip-address">
- {{ s__('ClusterIntegration|Ingress IP Address') }}
- </label>
- <div
- v-if="ingressExternalIp"
- class="input-group"
- >
- <input
- id="ingress-ip-address"
- :value="ingressExternalIp"
- type="text"
- class="form-control js-ip-address"
- readonly
- />
- <span class="input-group-append">
- <clipboard-button
- :text="ingressExternalIp"
- :title="s__('ClusterIntegration|Copy Ingress IP Address to clipboard')"
- class="input-group-text js-clipboard-btn"
- />
- </span>
- </div>
+ <template v-if="ingressInstalled">
+ <div class="form-group">
+ <label for="ingress-ip-address">
+ {{ s__('ClusterIntegration|Ingress IP Address') }}
+ </label>
+ <div
+ v-if="ingressExternalIp"
+ class="input-group"
+ >
<input
- v-else
+ id="ingress-ip-address"
+ :value="ingressExternalIp"
type="text"
class="form-control js-ip-address"
readonly
- value="?"
/>
+ <span class="input-group-append">
+ <clipboard-button
+ :text="ingressExternalIp"
+ :title="s__('ClusterIntegration|Copy Ingress IP Address to clipboard')"
+ class="input-group-text js-clipboard-btn"
+ />
+ </span>
</div>
+ <input
+ v-else
+ type="text"
+ class="form-control js-ip-address"
+ readonly
+ value="?"
+ />
+ </div>
- <p
- v-if="!ingressExternalIp"
- class="settings-message js-no-ip-message"
- >
- {{ s__(`ClusterIntegration|The IP address is in
- the process of being assigned. Please check your Kubernetes
- cluster or Quotas on Google Kubernetes Engine if it takes a long time.`) }}
+ <p
+ v-if="!ingressExternalIp"
+ class="settings-message js-no-ip-message"
+ >
+ {{ s__(`ClusterIntegration|The IP address is in
+ the process of being assigned. Please check your Kubernetes
+ cluster or Quotas on Google Kubernetes Engine if it takes a long time.`) }}
- <a
- :href="ingressHelpPath"
- target="_blank"
- rel="noopener noreferrer"
- >
- {{ __('More information') }}
- </a>
- </p>
+ <a
+ :href="ingressHelpPath"
+ target="_blank"
+ rel="noopener noreferrer"
+ >
+ {{ __('More information') }}
+ </a>
+ </p>
- <p>
- {{ s__(`ClusterIntegration|Point a wildcard DNS to this
- generated IP address in order to access
- your application after it has been deployed.`) }}
- <a
- :href="ingressDnsHelpPath"
- target="_blank"
- rel="noopener noreferrer"
- >
- {{ __('More information') }}
- </a>
- </p>
+ <p>
+ {{ s__(`ClusterIntegration|Point a wildcard DNS to this
+ generated IP address in order to access
+ your application after it has been deployed.`) }}
+ <a
+ :href="ingressDnsHelpPath"
+ target="_blank"
+ rel="noopener noreferrer"
+ >
+ {{ __('More information') }}
+ </a>
+ </p>
- </template>
- <div
- v-else
- v-html="ingressDescription"
- >
- </div>
- </div>
- </application-row>
- <application-row
- id="prometheus"
- :title="applications.prometheus.title"
- :manage-link="managePrometheusPath"
- :status="applications.prometheus.status"
- :status-reason="applications.prometheus.statusReason"
- :request-status="applications.prometheus.requestStatus"
- :request-reason="applications.prometheus.requestReason"
- title-link="https://prometheus.io/docs/introduction/overview/"
- >
+ </template>
<div
- slot="description"
- v-html="prometheusDescription"
+ v-html="ingressDescription"
>
</div>
- </application-row>
- <application-row
- id="runner"
- :title="applications.runner.title"
- :status="applications.runner.status"
- :status-reason="applications.runner.statusReason"
- :request-status="applications.runner.requestStatus"
- :request-reason="applications.runner.requestReason"
- title-link="https://docs.gitlab.com/runner/"
- >
- <div slot="description">
- {{ s__(`ClusterIntegration|GitLab Runner connects to this
- project's repository and executes CI/CD jobs,
- pushing results back and deploying,
- applications to production.`) }}
- </div>
- </application-row>
- <application-row
- id="jupyter"
- :title="applications.jupyter.title"
- :status="applications.jupyter.status"
- :status-reason="applications.jupyter.statusReason"
- :request-status="applications.jupyter.requestStatus"
- :request-reason="applications.jupyter.requestReason"
- :install-application-request-params="{ hostname: applications.jupyter.hostname }"
- title-link="https://jupyterhub.readthedocs.io/en/stable/"
+ </div>
+ </application-row>
+ <application-row
+ id="prometheus"
+ :logo-url="prometheusLogo"
+ :title="applications.prometheus.title"
+ :manage-link="managePrometheusPath"
+ :status="applications.prometheus.status"
+ :status-reason="applications.prometheus.statusReason"
+ :request-status="applications.prometheus.requestStatus"
+ :request-reason="applications.prometheus.requestReason"
+ :disabled="!helmInstalled"
+ title-link="https://prometheus.io/docs/introduction/overview/"
+ >
+ <div
+ slot="description"
+ v-html="prometheusDescription"
>
- <div slot="description">
- <p>
- {{ s__(`ClusterIntegration|JupyterHub, a multi-user Hub, spawns,
- manages, and proxies multiple instances of the single-user
- Jupyter notebook server. JupyterHub can be used to serve
- notebooks to a class of students, a corporate data science group,
- or a scientific research group.`) }}
- </p>
+ </div>
+ </application-row>
+ <application-row
+ id="runner"
+ :logo-url="gitlabLogo"
+ :title="applications.runner.title"
+ :status="applications.runner.status"
+ :status-reason="applications.runner.statusReason"
+ :request-status="applications.runner.requestStatus"
+ :request-reason="applications.runner.requestReason"
+ :disabled="!helmInstalled"
+ title-link="https://docs.gitlab.com/runner/"
+ >
+ <div slot="description">
+ {{ s__(`ClusterIntegration|GitLab Runner connects to this
+ project's repository and executes CI/CD jobs,
+ pushing results back and deploying,
+ applications to production.`) }}
+ </div>
+ </application-row>
+ <application-row
+ id="jupyter"
+ :logo-url="jupyterhubLogo"
+ :title="applications.jupyter.title"
+ :status="applications.jupyter.status"
+ :status-reason="applications.jupyter.statusReason"
+ :request-status="applications.jupyter.requestStatus"
+ :request-reason="applications.jupyter.requestReason"
+ :install-application-request-params="{ hostname: applications.jupyter.hostname }"
+ :disabled="!helmInstalled"
+ class="hide-bottom-border rounded-bottom"
+ title-link="https://jupyterhub.readthedocs.io/en/stable/"
+ >
+ <div slot="description">
+ <p>
+ {{ s__(`ClusterIntegration|JupyterHub, a multi-user Hub, spawns,
+ manages, and proxies multiple instances of the single-user
+ Jupyter notebook server. JupyterHub can be used to serve
+ notebooks to a class of students, a corporate data science group,
+ or a scientific research group.`) }}
+ </p>
- <template v-if="ingressExternalIp">
- <div class="form-group">
- <label for="jupyter-hostname">
- {{ s__('ClusterIntegration|Jupyter Hostname') }}
- </label>
+ <template v-if="ingressExternalIp">
+ <div class="form-group">
+ <label for="jupyter-hostname">
+ {{ s__('ClusterIntegration|Jupyter Hostname') }}
+ </label>
- <div class="input-group">
- <input
- v-model="applications.jupyter.hostname"
- :readonly="jupyterInstalled"
- type="text"
- class="form-control js-hostname"
+ <div class="input-group">
+ <input
+ v-model="applications.jupyter.hostname"
+ :readonly="jupyterInstalled"
+ type="text"
+ class="form-control js-hostname"
+ />
+ <span
+ class="input-group-btn"
+ >
+ <clipboard-button
+ :text="jupyterHostname"
+ :title="s__('ClusterIntegration|Copy Jupyter Hostname to clipboard')"
+ class="js-clipboard-btn"
/>
- <span
- class="input-group-btn"
- >
- <clipboard-button
- :text="jupyterHostname"
- :title="s__('ClusterIntegration|Copy Jupyter Hostname to clipboard')"
- class="js-clipboard-btn"
- />
- </span>
- </div>
+ </span>
</div>
- <p v-if="ingressInstalled">
- {{ s__(`ClusterIntegration|Replace this with your own hostname if you want.
- If you do so, point hostname to Ingress IP Address from above.`) }}
- <a
- :href="ingressDnsHelpPath"
- target="_blank"
- rel="noopener noreferrer"
- >
- {{ __('More information') }}
- </a>
- </p>
- </template>
- </div>
- </application-row>
- <!--
- NOTE: Don't forget to update `clusters.scss`
- min-height for this block and uncomment `application_spec` tests
- -->
- </div>
+ </div>
+ <p v-if="ingressInstalled">
+ {{ s__(`ClusterIntegration|Replace this with your own hostname if you want.
+ If you do so, point hostname to Ingress IP Address from above.`) }}
+ <a
+ :href="ingressDnsHelpPath"
+ target="_blank"
+ rel="noopener noreferrer"
+ >
+ {{ __('More information') }}
+ </a>
+ </p>
+ </template>
+ </div>
+ </application-row>
</div>
</section>
</template>
diff --git a/app/assets/javascripts/clusters/components/gcp_signup_offer.js b/app/assets/javascripts/clusters/components/gcp_signup_offer.js
deleted file mode 100644
index 04b778c6be9..00000000000
--- a/app/assets/javascripts/clusters/components/gcp_signup_offer.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import PersistentUserCallout from '../../persistent_user_callout';
-
-export default function gcpSignupOffer() {
- const alertEl = document.querySelector('.gcp-signup-offer');
- if (!alertEl) return;
-
- new PersistentUserCallout(alertEl);
-}
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.vue b/app/assets/javascripts/commit/pipelines/pipelines_table.vue
index 95c4be64d35..4849b0fa3db 100644
--- a/app/assets/javascripts/commit/pipelines/pipelines_table.vue
+++ b/app/assets/javascripts/commit/pipelines/pipelines_table.vue
@@ -76,10 +76,10 @@
<template>
<div class="content-list pipelines">
- <loading-icon
+ <gl-loading-icon
v-if="isLoading"
:label="s__('Pipelines|Loading Pipelines')"
- size="3"
+ :size="3"
class="prepend-top-20"
/>
diff --git a/app/assets/javascripts/commons/gitlab_ui.js b/app/assets/javascripts/commons/gitlab_ui.js
index 923c036f5a4..aed26adfa5c 100644
--- a/app/assets/javascripts/commons/gitlab_ui.js
+++ b/app/assets/javascripts/commons/gitlab_ui.js
@@ -1,4 +1,16 @@
import Vue from 'vue';
-import progressBar from '@gitlab-org/gitlab-ui/dist/base/progress_bar';
+import Pagination from '@gitlab-org/gitlab-ui/dist/components/base/pagination';
+import progressBar from '@gitlab-org/gitlab-ui/dist/components/base/progress_bar';
+import modal from '@gitlab-org/gitlab-ui/dist/components/base/modal';
+import loadingIcon from '@gitlab-org/gitlab-ui/dist/components/base/loading_icon';
+import dModal from '@gitlab-org/gitlab-ui/dist/directives/modal';
+import dTooltip from '@gitlab-org/gitlab-ui/dist/directives/tooltip';
+
+Vue.component('gl-pagination', Pagination);
Vue.component('gl-progress-bar', progressBar);
+Vue.component('gl-ui-modal', modal);
+Vue.component('gl-loading-icon', loadingIcon);
+
+Vue.directive('gl-modal', dModal);
+Vue.directive('gl-tooltip', dTooltip);
diff --git a/app/assets/javascripts/commons/polyfills.js b/app/assets/javascripts/commons/polyfills.js
index 742cf490ad2..539d0d29e0d 100644
--- a/app/assets/javascripts/commons/polyfills.js
+++ b/app/assets/javascripts/commons/polyfills.js
@@ -14,10 +14,10 @@ import 'core-js/es6/map';
import 'core-js/es6/weak-map';
// Browser polyfills
-import 'classlist-polyfill';
import 'formdata-polyfill';
import './polyfills/custom_event';
import './polyfills/element';
import './polyfills/event';
import './polyfills/nodelist';
import './polyfills/request_idle_callback';
+import './polyfills/svg';
diff --git a/app/assets/javascripts/commons/polyfills/element.js b/app/assets/javascripts/commons/polyfills/element.js
index b593bde6aa2..dde5e8f54f9 100644
--- a/app/assets/javascripts/commons/polyfills/element.js
+++ b/app/assets/javascripts/commons/polyfills/element.js
@@ -1,12 +1,17 @@
-Element.prototype.closest = Element.prototype.closest ||
+// polyfill Element.classList and DOMTokenList with classList.js
+import 'classlist-polyfill';
+
+Element.prototype.closest =
+ Element.prototype.closest ||
function closest(selector, selectedElement = this) {
if (!selectedElement) return null;
- return selectedElement.matches(selector) ?
- selectedElement :
- Element.prototype.closest(selector, selectedElement.parentElement);
+ return selectedElement.matches(selector)
+ ? selectedElement
+ : Element.prototype.closest(selector, selectedElement.parentElement);
};
-Element.prototype.matches = Element.prototype.matches ||
+Element.prototype.matches =
+ Element.prototype.matches ||
Element.prototype.matchesSelector ||
Element.prototype.mozMatchesSelector ||
Element.prototype.msMatchesSelector ||
@@ -15,13 +20,15 @@ Element.prototype.matches = Element.prototype.matches ||
function matches(selector) {
const elms = (this.document || this.ownerDocument).querySelectorAll(selector);
let i = elms.length - 1;
- while (i >= 0 && elms.item(i) !== this) { i -= 1; }
+ while (i >= 0 && elms.item(i) !== this) {
+ i -= 1;
+ }
return i > -1;
};
// From the polyfill on MDN, https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/remove#Polyfill
-((arr) => {
- arr.forEach((item) => {
+(arr => {
+ arr.forEach(item => {
if (Object.prototype.hasOwnProperty.call(item, 'remove')) {
return;
}
diff --git a/app/assets/javascripts/commons/polyfills/svg.js b/app/assets/javascripts/commons/polyfills/svg.js
new file mode 100644
index 00000000000..8648a568f6f
--- /dev/null
+++ b/app/assets/javascripts/commons/polyfills/svg.js
@@ -0,0 +1,5 @@
+import svg4everybody from 'svg4everybody';
+
+// polyfill support for external SVG file references via <use xlink:href>
+// @see https://css-tricks.com/svg-use-external-source/
+svg4everybody();
diff --git a/app/assets/javascripts/deploy_keys/components/action_btn.vue b/app/assets/javascripts/deploy_keys/components/action_btn.vue
index 7399fc97d45..10548da8ec5 100644
--- a/app/assets/javascripts/deploy_keys/components/action_btn.vue
+++ b/app/assets/javascripts/deploy_keys/components/action_btn.vue
@@ -1,11 +1,7 @@
<script>
-import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import eventHub from '../eventhub';
export default {
- components: {
- loadingIcon,
- },
props: {
deployKey: {
type: Object,
@@ -45,7 +41,7 @@ export default {
class="btn"
@click="doAction">
<slot></slot>
- <loading-icon
+ <gl-loading-icon
v-if="isLoading"
:inline="true"
/>
diff --git a/app/assets/javascripts/deploy_keys/components/app.vue b/app/assets/javascripts/deploy_keys/components/app.vue
index d91e4809126..aa52f120fe7 100644
--- a/app/assets/javascripts/deploy_keys/components/app.vue
+++ b/app/assets/javascripts/deploy_keys/components/app.vue
@@ -1,7 +1,6 @@
<script>
import { s__ } from '~/locale';
import Flash from '~/flash';
-import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
import NavigationTabs from '~/vue_shared/components/navigation_tabs.vue';
import eventHub from '../eventhub';
import DeployKeysService from '../service';
@@ -11,7 +10,6 @@ import KeysPanel from './keys_panel.vue';
export default {
components: {
KeysPanel,
- LoadingIcon,
NavigationTabs,
},
props: {
@@ -114,10 +112,10 @@ export default {
<template>
<div class="append-bottom-default deploy-keys">
- <loading-icon
+ <gl-loading-icon
v-if="isLoading && !hasKeys"
:label="s__('DeployKeys|Loading deploy keys')"
- size="2"
+ :size="2"
/>
<template v-else-if="hasKeys">
<div class="top-area scrolling-tabs-container inner-page-scroll-tabs">
diff --git a/app/assets/javascripts/deploy_keys/components/key.vue b/app/assets/javascripts/deploy_keys/components/key.vue
index f66ca070445..c05b9b1de79 100644
--- a/app/assets/javascripts/deploy_keys/components/key.vue
+++ b/app/assets/javascripts/deploy_keys/components/key.vue
@@ -145,8 +145,8 @@ export default {
<icon :name="firstProject.can_push ? 'lock-open' : 'lock'"/>
</a>
<a
- v-tooltip
v-if="isExpandable"
+ v-tooltip
:title="restProjectsTooltip"
class="label deploy-project-label"
@click="toggleExpanded"
@@ -154,10 +154,10 @@ export default {
<span>{{ restProjectsLabel }}</span>
</a>
<a
- v-tooltip
v-for="deployKeysProject in restProjects"
v-else-if="isExpanded"
:key="deployKeysProject.project.full_path"
+ v-tooltip
:href="deployKeysProject.project.full_path"
:title="projectTooltipTitle(deployKeysProject)"
class="label deploy-project-label"
@@ -198,8 +198,8 @@ export default {
{{ __('Enable') }}
</action-btn>
<a
- v-tooltip
v-if="deployKey.can_edit"
+ v-tooltip
:href="editDeployKeyPath"
:title="__('Edit')"
class="btn btn-default text-secondary"
@@ -208,8 +208,8 @@ export default {
<icon name="pencil"/>
</a>
<action-btn
- v-tooltip
v-if="isRemovable"
+ v-tooltip
:deploy-key="deployKey"
:title="__('Remove')"
btn-css-class="btn-danger"
@@ -219,8 +219,8 @@ export default {
<icon name="remove"/>
</action-btn>
<action-btn
- v-tooltip
v-else-if="isEnabled"
+ v-tooltip
:deploy-key="deployKey"
:title="__('Disable')"
btn-css-class="btn-warning"
diff --git a/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js b/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js
index 5ed13488788..6fcad187b35 100644
--- a/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js
+++ b/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js
@@ -1,4 +1,4 @@
-/* eslint-disable object-shorthand, func-names, comma-dangle, no-else-return, quotes */
+/* eslint-disable object-shorthand, func-names, no-else-return */
/* global CommentsStore */
/* global ResolveService */
@@ -25,44 +25,44 @@ const ResolveDiscussionBtn = Vue.extend({
};
},
computed: {
- showButton: function () {
+ showButton: function() {
if (this.discussion) {
return this.discussion.isResolvable();
} else {
return false;
}
},
- isDiscussionResolved: function () {
+ isDiscussionResolved: function() {
if (this.discussion) {
return this.discussion.isResolved();
} else {
return false;
}
},
- buttonText: function () {
+ buttonText: function() {
if (this.isDiscussionResolved) {
- return "Unresolve discussion";
+ return 'Unresolve discussion';
} else {
- return "Resolve discussion";
+ return 'Resolve discussion';
}
},
- loading: function () {
+ loading: function() {
if (this.discussion) {
return this.discussion.loading;
} else {
return false;
}
- }
+ },
},
- created: function () {
+ created: function() {
CommentsStore.createDiscussion(this.discussionId, this.canResolve);
this.discussion = CommentsStore.state[this.discussionId];
},
methods: {
- resolve: function () {
+ resolve: function() {
ResolveService.toggleResolveForDiscussion(this.mergeRequestId, this.discussionId);
- }
+ },
},
});
diff --git a/app/assets/javascripts/diff_notes/services/resolve.js b/app/assets/javascripts/diff_notes/services/resolve.js
index 0b3568e432d..e69eaad4423 100644
--- a/app/assets/javascripts/diff_notes/services/resolve.js
+++ b/app/assets/javascripts/diff_notes/services/resolve.js
@@ -8,9 +8,7 @@ window.gl = window.gl || {};
class ResolveServiceClass {
constructor(root) {
- this.noteResource = Vue.resource(
- `${root}/notes{/noteId}/resolve?html=true`,
- );
+ this.noteResource = Vue.resource(`${root}/notes{/noteId}/resolve?html=true`);
this.discussionResource = Vue.resource(
`${root}/merge_requests{/mergeRequestId}/discussions{/discussionId}/resolve?html=true`,
);
@@ -51,10 +49,7 @@ class ResolveServiceClass {
discussion.updateHeadline(data);
})
.catch(
- () =>
- new Flash(
- 'An error occurred when trying to resolve a discussion. Please try again.',
- ),
+ () => new Flash('An error occurred when trying to resolve a discussion. Please try again.'),
);
}
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue
index b5b05df4d34..bfb992340bc 100644
--- a/app/assets/javascripts/diffs/components/app.vue
+++ b/app/assets/javascripts/diffs/components/app.vue
@@ -4,7 +4,6 @@ import Icon from '~/vue_shared/components/icon.vue';
import { __ } from '~/locale';
import createFlash from '~/flash';
import eventHub from '../../notes/event_hub';
-import LoadingIcon from '../../vue_shared/components/loading_icon.vue';
import CompareVersions from './compare_versions.vue';
import ChangedFiles from './changed_files.vue';
import DiffFile from './diff_file.vue';
@@ -15,7 +14,6 @@ export default {
name: 'DiffsApp',
components: {
Icon,
- LoadingIcon,
CompareVersions,
ChangedFiles,
DiffFile,
@@ -59,7 +57,7 @@ export default {
emailPatchPath: state => state.diffs.emailPatchPath,
}),
...mapGetters('diffs', ['isParallelView']),
- ...mapGetters(['isNotesFetched']),
+ ...mapGetters(['isNotesFetched', 'discussionsStructuredByLineCode']),
targetBranch() {
return {
branchName: this.targetBranchName,
@@ -112,13 +110,26 @@ export default {
},
created() {
this.adjustView();
+ eventHub.$once('fetchedNotesData', this.setDiscussions);
},
methods: {
- ...mapActions('diffs', ['setBaseConfig', 'fetchDiffFiles', 'startRenderDiffsQueue']),
+ ...mapActions('diffs', [
+ 'setBaseConfig',
+ 'fetchDiffFiles',
+ 'startRenderDiffsQueue',
+ 'assignDiscussionsToDiff',
+ ]),
+
fetchData() {
this.fetchDiffFiles()
.then(() => {
- requestIdleCallback(this.startRenderDiffsQueue, { timeout: 1000 });
+ requestIdleCallback(
+ () => {
+ this.setDiscussions();
+ this.startRenderDiffsQueue();
+ },
+ { timeout: 1000 },
+ );
})
.catch(() => {
createFlash(__('Something went wrong on our end. Please try again!'));
@@ -128,6 +139,16 @@ export default {
eventHub.$emit('fetchNotesData');
}
},
+ setDiscussions() {
+ if (this.isNotesFetched) {
+ requestIdleCallback(
+ () => {
+ this.assignDiscussionsToDiff(this.discussionsStructuredByLineCode);
+ },
+ { timeout: 1000 },
+ );
+ }
+ },
adjustView() {
if (this.shouldShow && this.isParallelView) {
window.mrTabs.expandViewContainer();
@@ -145,7 +166,7 @@ export default {
v-if="isLoading"
class="loading"
>
- <loading-icon />
+ <gl-loading-icon />
</div>
<div
v-else
diff --git a/app/assets/javascripts/diffs/components/changed_files_dropdown.vue b/app/assets/javascripts/diffs/components/changed_files_dropdown.vue
index 045688a32bf..0ec6b8b7f21 100644
--- a/app/assets/javascripts/diffs/components/changed_files_dropdown.vue
+++ b/app/assets/javascripts/diffs/components/changed_files_dropdown.vue
@@ -63,7 +63,7 @@ export default {
v-else
role="button"
class="fa fa-times dropdown-input-search"
- @click="clearSearch"
+ @click.stop.prevent="clearSearch"
></i>
</div>
<div class="dropdown-content">
diff --git a/app/assets/javascripts/diffs/components/diff_discussions.vue b/app/assets/javascripts/diffs/components/diff_discussions.vue
index e64d5511d78..cddbe554fbd 100644
--- a/app/assets/javascripts/diffs/components/diff_discussions.vue
+++ b/app/assets/javascripts/diffs/components/diff_discussions.vue
@@ -1,4 +1,5 @@
<script>
+import { mapActions } from 'vuex';
import noteableDiscussion from '../../notes/components/noteable_discussion.vue';
export default {
@@ -11,6 +12,14 @@ export default {
required: true,
},
},
+ methods: {
+ ...mapActions('diffs', ['removeDiscussionsFromDiff']),
+ deleteNoteHandler(discussion) {
+ if (discussion.notes.length <= 1) {
+ this.removeDiscussionsFromDiff(discussion);
+ }
+ },
+ },
};
</script>
@@ -31,6 +40,7 @@ export default {
:render-diff-file="false"
:always-expanded="true"
:discussions-by-diff-order="true"
+ @noteDeleted="deleteNoteHandler"
/>
</ul>
</div>
diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue
index 59e9ba08b8b..bcbe374a90c 100644
--- a/app/assets/javascripts/diffs/components/diff_file.vue
+++ b/app/assets/javascripts/diffs/components/diff_file.vue
@@ -1,9 +1,8 @@
<script>
-import { mapActions } from 'vuex';
+import { mapActions, mapGetters } from 'vuex';
import _ from 'underscore';
import { __, sprintf } from '~/locale';
import createFlash from '~/flash';
-import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
import DiffFileHeader from './diff_file_header.vue';
import DiffContent from './diff_content.vue';
@@ -11,7 +10,6 @@ export default {
components: {
DiffFileHeader,
DiffContent,
- LoadingIcon,
},
props: {
file: {
@@ -30,6 +28,7 @@ export default {
};
},
computed: {
+ ...mapGetters(['isNotesFetched', 'discussionsStructuredByLineCode']),
isCollapsed() {
return this.file.collapsed || false;
},
@@ -44,23 +43,23 @@ export default {
);
},
showExpandMessage() {
- return this.isCollapsed && !this.isLoadingCollapsedDiff && !this.file.tooLarge;
+ return (
+ this.isCollapsed ||
+ !this.file.highlightedDiffLines &&
+ !this.isLoadingCollapsedDiff &&
+ !this.file.tooLarge &&
+ this.file.text
+ );
},
showLoadingIcon() {
return this.isLoadingCollapsedDiff || (!this.file.renderIt && !this.isCollapsed);
},
},
methods: {
- ...mapActions('diffs', ['loadCollapsedDiff']),
+ ...mapActions('diffs', ['loadCollapsedDiff', 'assignDiscussionsToDiff']),
handleToggle() {
- const { collapsed, highlightedDiffLines, parallelDiffLines } = this.file;
-
- if (
- collapsed &&
- !highlightedDiffLines &&
- parallelDiffLines !== undefined &&
- !parallelDiffLines.length
- ) {
+ const { highlightedDiffLines, parallelDiffLines } = this.file;
+ if (!highlightedDiffLines && parallelDiffLines !== undefined && !parallelDiffLines.length) {
this.handleLoadCollapsedDiff();
} else {
this.file.collapsed = !this.file.collapsed;
@@ -76,6 +75,14 @@ export default {
this.file.collapsed = false;
this.file.renderIt = true;
})
+ .then(() => {
+ requestIdleCallback(
+ () => {
+ this.assignDiscussionsToDiff(this.discussionsStructuredByLineCode);
+ },
+ { timeout: 1000 },
+ );
+ })
.catch(() => {
this.isLoadingCollapsedDiff = false;
createFlash(__('Something went wrong on our end. Please try again!'));
@@ -135,12 +142,12 @@ export default {
:class="{ hidden: isCollapsed || file.tooLarge }"
:diff-file="file"
/>
- <loading-icon
- v-else-if="showLoadingIcon"
+ <gl-loading-icon
+ v-if="showLoadingIcon"
class="diff-content loading"
/>
<div
- v-if="showExpandMessage"
+ v-else-if="showExpandMessage"
class="nothing-here-block diff-collapsed"
>
{{ __('This diff is collapsed.') }}
diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue
index d3ffbe0415a..517fbf400e8 100644
--- a/app/assets/javascripts/diffs/components/diff_file_header.vue
+++ b/app/assets/javascripts/diffs/components/diff_file_header.vue
@@ -181,8 +181,8 @@ export default {
</span>
<strong
- v-tooltip
v-else
+ v-tooltip
:title="filePath"
class="file-title-name"
data-container="body"
@@ -255,8 +255,8 @@ export default {
</a>
<a
- v-tooltip
v-if="diffFile.externalUrl"
+ v-tooltip
:href="diffFile.externalUrl"
:title="`View on ${diffFile.formattedExternalUrl}`"
target="_blank"
diff --git a/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue b/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue
index 7e50a0aed84..1b59777f901 100644
--- a/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue
+++ b/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue
@@ -1,15 +1,11 @@
<script>
import { mapActions } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue';
-import tooltip from '~/vue_shared/directives/tooltip';
import { pluralize, truncate } from '~/lib/utils/text_utility';
import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue';
import { COUNT_OF_AVATARS_IN_GUTTER, LENGTH_OF_AVATAR_TOOLTIP } from '../constants';
export default {
- directives: {
- tooltip,
- },
components: {
Icon,
UserAvatarImage,
@@ -91,10 +87,10 @@ export default {
@click.native="toggleDiscussions"
/>
<span
- v-tooltip
v-if="moreText"
+ v-gl-tooltip
:title="moreText"
- class="diff-comments-more-count has-tooltip js-diff-comment-avatar js-diff-comment-plus"
+ class="diff-comments-more-count js-diff-comment-avatar js-diff-comment-plus"
data-container="body"
data-placement="top"
role="button"
diff --git a/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue b/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue
index 8ad1ea34245..6eff3013dcd 100644
--- a/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue
+++ b/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue
@@ -13,6 +13,10 @@ export default {
Icon,
},
props: {
+ line: {
+ type: Object,
+ required: true,
+ },
fileHash: {
type: String,
required: true,
@@ -21,31 +25,16 @@ export default {
type: String,
required: true,
},
- lineType: {
- type: String,
- required: false,
- default: '',
- },
lineNumber: {
type: Number,
required: false,
default: 0,
},
- lineCode: {
- type: String,
- required: false,
- default: '',
- },
linePosition: {
type: String,
required: false,
default: '',
},
- metaData: {
- type: Object,
- required: false,
- default: () => ({}),
- },
showCommentButton: {
type: Boolean,
required: false,
@@ -76,11 +65,6 @@ export default {
required: false,
default: false,
},
- discussions: {
- type: Array,
- required: false,
- default: () => [],
- },
},
computed: {
...mapState({
@@ -89,7 +73,7 @@ export default {
}),
...mapGetters(['isLoggedIn']),
lineHref() {
- return this.lineCode ? `#${this.lineCode}` : '#';
+ return `#${this.line.lineCode || ''}`;
},
shouldShowCommentButton() {
return (
@@ -103,20 +87,19 @@ export default {
);
},
hasDiscussions() {
- return this.discussions.length > 0;
+ return this.line.discussions && this.line.discussions.length > 0;
},
shouldShowAvatarsOnGutter() {
- if (!this.lineType && this.linePosition === LINE_POSITION_RIGHT) {
+ if (!this.line.type && this.linePosition === LINE_POSITION_RIGHT) {
return false;
}
-
return this.showCommentButton && this.hasDiscussions;
},
},
methods: {
...mapActions('diffs', ['loadMoreLines', 'showCommentForm']),
handleCommentButton() {
- this.showCommentForm({ lineCode: this.lineCode });
+ this.showCommentForm({ lineCode: this.line.lineCode });
},
handleLoadMoreLines() {
if (this.isRequesting) {
@@ -125,8 +108,8 @@ export default {
this.isRequesting = true;
const endpoint = this.contextLinesPath;
- const oldLineNumber = this.metaData.oldPos || 0;
- const newLineNumber = this.metaData.newPos || 0;
+ const oldLineNumber = this.line.metaData.oldPos || 0;
+ const newLineNumber = this.line.metaData.newPos || 0;
const offset = newLineNumber - oldLineNumber;
const bottom = this.isBottom;
const { fileHash } = this;
@@ -201,7 +184,7 @@ export default {
</a>
<diff-gutter-avatars
v-if="shouldShowAvatarsOnGutter"
- :discussions="discussions"
+ :discussions="line.discussions"
/>
</template>
</div>
diff --git a/app/assets/javascripts/diffs/components/diff_line_note_form.vue b/app/assets/javascripts/diffs/components/diff_line_note_form.vue
index cbe4551d06b..bb9bb821de3 100644
--- a/app/assets/javascripts/diffs/components/diff_line_note_form.vue
+++ b/app/assets/javascripts/diffs/components/diff_line_note_form.vue
@@ -1,9 +1,7 @@
<script>
import { mapState, mapGetters, mapActions } from 'vuex';
-import createFlash from '~/flash';
import { s__ } from '~/locale';
import noteForm from '../../notes/components/note_form.vue';
-import { getNoteFormData } from '../store/utils';
import autosave from '../../notes/mixins/autosave';
import { DIFF_NOTE_TYPE } from '../constants';
@@ -21,7 +19,7 @@ export default {
type: Object,
required: true,
},
- position: {
+ linePosition: {
type: String,
required: false,
default: '',
@@ -38,6 +36,16 @@ export default {
}),
...mapGetters('diffs', ['getDiffFileByHash']),
...mapGetters(['isLoggedIn', 'noteableType', 'getNoteableData', 'getNotesDataByProp']),
+ formData() {
+ return {
+ noteableData: this.noteableData,
+ noteableType: this.noteableType,
+ noteTargetLine: this.noteTargetLine,
+ diffViewType: this.diffViewType,
+ diffFile: this.getDiffFileByHash(this.diffFileHash),
+ linePosition: this.linePosition,
+ };
+ },
},
mounted() {
if (this.isLoggedIn) {
@@ -52,8 +60,7 @@ export default {
}
},
methods: {
- ...mapActions('diffs', ['cancelCommentForm']),
- ...mapActions(['saveNote', 'refetchDiscussionById']),
+ ...mapActions('diffs', ['cancelCommentForm', 'assignDiscussionsToDiff', 'saveDiffDiscussion']),
handleCancelCommentForm(shouldConfirm, isDirty) {
if (shouldConfirm && isDirty) {
const msg = s__('Notes|Are you sure you want to cancel creating this comment?');
@@ -72,32 +79,9 @@ export default {
});
},
handleSaveNote(note) {
- const selectedDiffFile = this.getDiffFileByHash(this.diffFileHash);
- const postData = getNoteFormData({
- note,
- noteableData: this.noteableData,
- noteableType: this.noteableType,
- noteTargetLine: this.noteTargetLine,
- diffViewType: this.diffViewType,
- diffFile: selectedDiffFile,
- linePosition: this.position,
- });
-
- this.saveNote(postData)
- .then(result => {
- const endpoint = this.getNotesDataByProp('discussionsPath');
-
- this.refetchDiscussionById({ path: endpoint, discussionId: result.discussion_id })
- .then(() => {
- this.handleCancelCommentForm();
- })
- .catch(() => {
- createFlash(s__('MergeRequests|Updating discussions failed'));
- });
- })
- .catch(() => {
- createFlash(s__('MergeRequests|Saving the comment failed'));
- });
+ return this.saveDiffDiscussion({ note, formData: this.formData }).then(() =>
+ this.handleCancelCommentForm(),
+ );
},
},
};
diff --git a/app/assets/javascripts/diffs/components/diff_table_cell.vue b/app/assets/javascripts/diffs/components/diff_table_cell.vue
index 33bc8d9971e..5d9a0b123fe 100644
--- a/app/assets/javascripts/diffs/components/diff_table_cell.vue
+++ b/app/assets/javascripts/diffs/components/diff_table_cell.vue
@@ -11,8 +11,6 @@ import {
LINE_HOVER_CLASS_NAME,
LINE_UNFOLD_CLASS_NAME,
INLINE_DIFF_VIEW_TYPE,
- LINE_POSITION_LEFT,
- LINE_POSITION_RIGHT,
} from '../constants';
export default {
@@ -67,42 +65,24 @@ export default {
required: false,
default: false,
},
- discussions: {
- type: Array,
- required: false,
- default: () => [],
- },
},
computed: {
...mapGetters(['isLoggedIn']),
- normalizedLine() {
- let normalizedLine;
-
- if (this.diffViewType === INLINE_DIFF_VIEW_TYPE) {
- normalizedLine = this.line;
- } else if (this.linePosition === LINE_POSITION_LEFT) {
- normalizedLine = this.line.left;
- } else if (this.linePosition === LINE_POSITION_RIGHT) {
- normalizedLine = this.line.right;
- }
-
- return normalizedLine;
- },
isMatchLine() {
- return this.normalizedLine.type === MATCH_LINE_TYPE;
+ return this.line.type === MATCH_LINE_TYPE;
},
isContextLine() {
- return this.normalizedLine.type === CONTEXT_LINE_TYPE;
+ return this.line.type === CONTEXT_LINE_TYPE;
},
isMetaLine() {
- const { type } = this.normalizedLine;
+ const { type } = this.line;
return (
type === OLD_NO_NEW_LINE_TYPE || type === NEW_NO_NEW_LINE_TYPE || type === EMPTY_CELL_TYPE
);
},
classNameMap() {
- const { type } = this.normalizedLine;
+ const { type } = this.line;
return {
[type]: type,
@@ -116,9 +96,9 @@ export default {
};
},
lineNumber() {
- const { lineType, normalizedLine } = this;
+ const { lineType } = this;
- return lineType === OLD_LINE_TYPE ? normalizedLine.oldLine : normalizedLine.newLine;
+ return lineType === OLD_LINE_TYPE ? this.line.oldLine : this.line.newLine;
},
},
};
@@ -129,20 +109,17 @@ export default {
:class="classNameMap"
>
<diff-line-gutter-content
+ :line="line"
:file-hash="fileHash"
:context-lines-path="contextLinesPath"
- :line-type="normalizedLine.type"
- :line-code="normalizedLine.lineCode"
:line-position="linePosition"
:line-number="lineNumber"
- :meta-data="normalizedLine.metaData"
:show-comment-button="showCommentButton"
:is-hover="isHover"
:is-bottom="isBottom"
:is-match-line="isMatchLine"
:is-context-line="isContentLine"
:is-meta-line="isMetaLine"
- :discussions="discussions"
/>
</td>
</template>
diff --git a/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue b/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue
index caf84dc9573..46a51859da5 100644
--- a/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue
+++ b/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue
@@ -21,18 +21,13 @@ export default {
type: Number,
required: true,
},
- discussions: {
- type: Array,
- required: false,
- default: () => [],
- },
},
computed: {
...mapState({
diffLineCommentForms: state => state.diffs.diffLineCommentForms,
}),
className() {
- return this.discussions.length ? '' : 'js-temp-notes-holder';
+ return this.line.discussions.length ? '' : 'js-temp-notes-holder';
},
},
};
@@ -44,14 +39,13 @@ export default {
class="notes_holder"
>
<td
- class="notes_line"
- colspan="2"
- ></td>
- <td class="notes_content">
+ class="notes_content"
+ colspan="3"
+ >
<div class="content">
<diff-discussions
- v-if="discussions.length"
- :discussions="discussions"
+ v-if="line.discussions.length"
+ :discussions="line.discussions"
/>
<diff-line-note-form
v-if="diffLineCommentForms[line.lineCode]"
diff --git a/app/assets/javascripts/diffs/components/inline_diff_table_row.vue b/app/assets/javascripts/diffs/components/inline_diff_table_row.vue
index 32d65ff994f..62fa34e835a 100644
--- a/app/assets/javascripts/diffs/components/inline_diff_table_row.vue
+++ b/app/assets/javascripts/diffs/components/inline_diff_table_row.vue
@@ -1,5 +1,5 @@
<script>
-import { mapGetters } from 'vuex';
+import { mapGetters, mapActions } from 'vuex';
import DiffTableCell from './diff_table_cell.vue';
import {
NEW_LINE_TYPE,
@@ -33,11 +33,6 @@ export default {
required: false,
default: false,
},
- discussions: {
- type: Array,
- required: false,
- default: () => [],
- },
},
data() {
return {
@@ -68,7 +63,11 @@ export default {
this.linePositionLeft = LINE_POSITION_LEFT;
this.linePositionRight = LINE_POSITION_RIGHT;
},
+ mounted() {
+ this.scrollToLineIfNeededInline(this.line);
+ },
methods: {
+ ...mapActions('diffs', ['scrollToLineIfNeededInline']),
handleMouseMove(e) {
// To show the comment icon on the gutter we need to know if we hover the line.
// Current table structure doesn't allow us to do this with CSS in both of the diff view types
@@ -94,7 +93,6 @@ export default {
:is-bottom="isBottom"
:is-hover="isHover"
:show-comment-button="true"
- :discussions="discussions"
class="diff-line-num old_line"
/>
<diff-table-cell
@@ -104,7 +102,6 @@ export default {
:line-type="newLineType"
:is-bottom="isBottom"
:is-hover="isHover"
- :discussions="discussions"
class="diff-line-num new_line"
/>
<td
diff --git a/app/assets/javascripts/diffs/components/inline_diff_view.vue b/app/assets/javascripts/diffs/components/inline_diff_view.vue
index e7d789734c3..fbf9e77ac07 100644
--- a/app/assets/javascripts/diffs/components/inline_diff_view.vue
+++ b/app/assets/javascripts/diffs/components/inline_diff_view.vue
@@ -2,7 +2,6 @@
import { mapGetters, mapState } from 'vuex';
import inlineDiffTableRow from './inline_diff_table_row.vue';
import inlineDiffCommentRow from './inline_diff_comment_row.vue';
-import { trimFirstCharOfLineContent } from '../store/utils';
export default {
components: {
@@ -20,29 +19,17 @@ export default {
},
},
computed: {
- ...mapGetters('diffs', [
- 'commitId',
- 'shouldRenderInlineCommentRow',
- 'singleDiscussionByLineCode',
- ]),
+ ...mapGetters('diffs', ['commitId', 'shouldRenderInlineCommentRow']),
...mapState({
diffLineCommentForms: state => state.diffs.diffLineCommentForms,
}),
- normalizedDiffLines() {
- return this.diffLines.map(line => (line.richText ? trimFirstCharOfLineContent(line) : line));
- },
diffLinesLength() {
- return this.normalizedDiffLines.length;
+ return this.diffLines.length;
},
userColorScheme() {
return window.gon.user_color_scheme;
},
},
- methods: {
- discussionsList(line) {
- return line.lineCode !== undefined ? this.singleDiscussionByLineCode(line.lineCode) : [];
- },
- },
};
</script>
@@ -53,23 +40,21 @@ export default {
class="code diff-wrap-lines js-syntax-highlight text-file js-diff-inline-view">
<tbody>
<template
- v-for="(line, index) in normalizedDiffLines"
+ v-for="(line, index) in diffLines"
>
<inline-diff-table-row
+ :key="line.lineCode"
:file-hash="diffFile.fileHash"
:context-lines-path="diffFile.contextLinesPath"
:line="line"
:is-bottom="index + 1 === diffLinesLength"
- :key="line.lineCode"
- :discussions="discussionsList(line)"
/>
<inline-diff-comment-row
v-if="shouldRenderInlineCommentRow(line)"
+ :key="index"
:diff-file-hash="diffFile.fileHash"
:line="line"
:line-index="index"
- :key="index"
- :discussions="discussionsList(line)"
/>
</template>
</tbody>
diff --git a/app/assets/javascripts/diffs/components/no_changes.vue b/app/assets/javascripts/diffs/components/no_changes.vue
index d817157fbcd..6905630ad8c 100644
--- a/app/assets/javascripts/diffs/components/no_changes.vue
+++ b/app/assets/javascripts/diffs/components/no_changes.vue
@@ -38,7 +38,7 @@ export default {
<div class="text-center">
<a
:href="newBlobPath"
- class="btn btn-save"
+ class="btn btn-success"
>
{{ __('Create commit') }}
</a>
diff --git a/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue b/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue
index 48b8feeb0b4..3339c56cbb6 100644
--- a/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue
+++ b/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue
@@ -21,51 +21,49 @@ export default {
type: Number,
required: true,
},
- leftDiscussions: {
- type: Array,
- required: false,
- default: () => [],
- },
- rightDiscussions: {
- type: Array,
- required: false,
- default: () => [],
- },
},
computed: {
...mapState({
diffLineCommentForms: state => state.diffs.diffLineCommentForms,
}),
leftLineCode() {
- return this.line.left.lineCode;
+ return this.line.left && this.line.left.lineCode;
},
rightLineCode() {
- return this.line.right.lineCode;
+ return this.line.right && this.line.right.lineCode;
},
hasExpandedDiscussionOnLeft() {
- const discussions = this.leftDiscussions;
-
- return discussions ? discussions.every(discussion => discussion.expanded) : false;
+ return this.line.left && this.line.left.discussions
+ ? this.line.left.discussions.every(discussion => discussion.expanded)
+ : false;
},
hasExpandedDiscussionOnRight() {
- const discussions = this.rightDiscussions;
-
- return discussions ? discussions.every(discussion => discussion.expanded) : false;
+ return this.line.right && this.line.right.discussions
+ ? this.line.right.discussions.every(discussion => discussion.expanded)
+ : false;
},
hasAnyExpandedDiscussion() {
return this.hasExpandedDiscussionOnLeft || this.hasExpandedDiscussionOnRight;
},
shouldRenderDiscussionsOnLeft() {
- return this.leftDiscussions && this.hasExpandedDiscussionOnLeft;
+ return this.line.left && this.line.left.discussions && this.hasExpandedDiscussionOnLeft;
},
shouldRenderDiscussionsOnRight() {
- return this.rightDiscussions && this.hasExpandedDiscussionOnRight && this.line.right.type;
+ return (
+ this.line.right &&
+ this.line.right.discussions &&
+ this.hasExpandedDiscussionOnRight &&
+ this.line.right.type
+ );
},
showRightSideCommentForm() {
- return this.line.right.type && this.diffLineCommentForms[this.rightLineCode];
+ return (
+ this.line.right && this.line.right.type && this.diffLineCommentForms[this.rightLineCode]
+ );
},
className() {
- return this.leftDiscussions.length > 0 || this.rightDiscussions.length > 0
+ return (this.left && this.line.left.discussions.length > 0) ||
+ (this.right && this.line.right.discussions.length > 0)
? ''
: 'js-temp-notes-holder';
},
@@ -85,8 +83,8 @@ export default {
class="content"
>
<diff-discussions
- v-if="leftDiscussions.length"
- :discussions="leftDiscussions"
+ v-if="line.left.discussions.length"
+ :discussions="line.left.discussions"
/>
</div>
<diff-line-note-form
@@ -94,7 +92,7 @@ export default {
:diff-file-hash="diffFileHash"
:line="line.left"
:note-target-line="line.left"
- position="left"
+ line-position="left"
/>
</td>
<td class="notes_line new"></td>
@@ -104,8 +102,8 @@ export default {
class="content"
>
<diff-discussions
- v-if="rightDiscussions.length"
- :discussions="rightDiscussions"
+ v-if="line.right.discussions.length"
+ :discussions="line.right.discussions"
/>
</div>
<diff-line-note-form
@@ -113,7 +111,7 @@ export default {
:diff-file-hash="diffFileHash"
:line="line.right"
:note-target-line="line.right"
- position="right"
+ line-position="right"
/>
</td>
</tr>
diff --git a/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue b/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue
index d4e54c2bd00..fcc3b3e9117 100644
--- a/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue
+++ b/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue
@@ -1,6 +1,6 @@
<script>
+import { mapActions } from 'vuex';
import $ from 'jquery';
-import { mapGetters } from 'vuex';
import DiffTableCell from './diff_table_cell.vue';
import {
NEW_LINE_TYPE,
@@ -10,8 +10,7 @@ import {
OLD_NO_NEW_LINE_TYPE,
PARALLEL_DIFF_VIEW_TYPE,
NEW_NO_NEW_LINE_TYPE,
- LINE_POSITION_LEFT,
- LINE_POSITION_RIGHT,
+ EMPTY_CELL_TYPE,
} from '../constants';
export default {
@@ -36,16 +35,6 @@ export default {
required: false,
default: false,
},
- leftDiscussions: {
- type: Array,
- required: false,
- default: () => [],
- },
- rightDiscussions: {
- type: Array,
- required: false,
- default: () => [],
- },
},
data() {
return {
@@ -54,32 +43,33 @@ export default {
};
},
computed: {
- ...mapGetters('diffs', ['isParallelView']),
isContextLine() {
- return this.line.left.type === CONTEXT_LINE_TYPE;
+ return this.line.left && this.line.left.type === CONTEXT_LINE_TYPE;
},
classNameMap() {
return {
[CONTEXT_LINE_CLASS_NAME]: this.isContextLine,
- [PARALLEL_DIFF_VIEW_TYPE]: this.isParallelView,
+ [PARALLEL_DIFF_VIEW_TYPE]: true,
};
},
parallelViewLeftLineType() {
- if (this.line.right.type === NEW_NO_NEW_LINE_TYPE) {
+ if (this.line.right && this.line.right.type === NEW_NO_NEW_LINE_TYPE) {
return OLD_NO_NEW_LINE_TYPE;
}
- return this.line.left.type;
+ return this.line.left ? this.line.left.type : EMPTY_CELL_TYPE;
},
},
created() {
this.newLineType = NEW_LINE_TYPE;
this.oldLineType = OLD_LINE_TYPE;
- this.linePositionLeft = LINE_POSITION_LEFT;
- this.linePositionRight = LINE_POSITION_RIGHT;
this.parallelDiffViewType = PARALLEL_DIFF_VIEW_TYPE;
},
+ mounted() {
+ this.scrollToLineIfNeededParallel(this.line);
+ },
methods: {
+ ...mapActions('diffs', ['scrollToLineIfNeededParallel']),
handleMouseMove(e) {
const isHover = e.type === 'mouseover';
const hoveringCell = e.target.closest('td');
@@ -116,47 +106,57 @@ export default {
@mouseover="handleMouseMove"
@mouseout="handleMouseMove"
>
- <diff-table-cell
- :file-hash="fileHash"
- :context-lines-path="contextLinesPath"
- :line="line"
- :line-type="oldLineType"
- :line-position="linePositionLeft"
- :is-bottom="isBottom"
- :is-hover="isLeftHover"
- :show-comment-button="true"
- :diff-view-type="parallelDiffViewType"
- :discussions="leftDiscussions"
- class="diff-line-num old_line"
- />
- <td
- :id="line.left.lineCode"
- :class="parallelViewLeftLineType"
- class="line_content parallel left-side"
- @mousedown.native="handleParallelLineMouseDown"
- v-html="line.left.richText"
- >
- </td>
- <diff-table-cell
- :file-hash="fileHash"
- :context-lines-path="contextLinesPath"
- :line="line"
- :line-type="newLineType"
- :line-position="linePositionRight"
- :is-bottom="isBottom"
- :is-hover="isRightHover"
- :show-comment-button="true"
- :diff-view-type="parallelDiffViewType"
- :discussions="rightDiscussions"
- class="diff-line-num new_line"
- />
- <td
- :id="line.right.lineCode"
- :class="line.right.type"
- class="line_content parallel right-side"
- @mousedown.native="handleParallelLineMouseDown"
- v-html="line.right.richText"
- >
- </td>
+ <template v-if="line.left">
+ <diff-table-cell
+ :file-hash="fileHash"
+ :context-lines-path="contextLinesPath"
+ :line="line.left"
+ :line-type="oldLineType"
+ :is-bottom="isBottom"
+ :is-hover="isLeftHover"
+ :show-comment-button="true"
+ :diff-view-type="parallelDiffViewType"
+ line-position="left"
+ class="diff-line-num old_line"
+ />
+ <td
+ :id="line.left.lineCode"
+ :class="parallelViewLeftLineType"
+ class="line_content parallel left-side"
+ @mousedown.native="handleParallelLineMouseDown"
+ v-html="line.left.richText"
+ >
+ </td>
+ </template>
+ <template v-else>
+ <td class="diff-line-num old_line empty-cell"></td>
+ <td class="line_content parallel left-side empty-cell"></td>
+ </template>
+ <template v-if="line.right">
+ <diff-table-cell
+ :file-hash="fileHash"
+ :context-lines-path="contextLinesPath"
+ :line="line.right"
+ :line-type="newLineType"
+ :is-bottom="isBottom"
+ :is-hover="isRightHover"
+ :show-comment-button="true"
+ :diff-view-type="parallelDiffViewType"
+ line-position="right"
+ class="diff-line-num new_line"
+ />
+ <td
+ :id="line.right.lineCode"
+ :class="line.right.type"
+ class="line_content parallel right-side"
+ @mousedown.native="handleParallelLineMouseDown"
+ v-html="line.right.richText"
+ >
+ </td>
+ </template>
+ <template v-else>
+ <td class="diff-line-num old_line empty-cell"></td>
+ <td class="line_content parallel right-side empty-cell"></td>
+ </template>
</tr>
</template>
diff --git a/app/assets/javascripts/diffs/components/parallel_diff_view.vue b/app/assets/javascripts/diffs/components/parallel_diff_view.vue
index 24ceb52a04a..3452f0d2b00 100644
--- a/app/assets/javascripts/diffs/components/parallel_diff_view.vue
+++ b/app/assets/javascripts/diffs/components/parallel_diff_view.vue
@@ -2,8 +2,6 @@
import { mapState, mapGetters } from 'vuex';
import parallelDiffTableRow from './parallel_diff_table_row.vue';
import parallelDiffCommentRow from './parallel_diff_comment_row.vue';
-import { EMPTY_CELL_TYPE } from '../constants';
-import { trimFirstCharOfLineContent } from '../store/utils';
export default {
components: {
@@ -21,46 +19,17 @@ export default {
},
},
computed: {
- ...mapGetters('diffs', [
- 'commitId',
- 'singleDiscussionByLineCode',
- 'shouldRenderParallelCommentRow',
- ]),
+ ...mapGetters('diffs', ['commitId', 'shouldRenderParallelCommentRow']),
...mapState({
diffLineCommentForms: state => state.diffs.diffLineCommentForms,
}),
- parallelDiffLines() {
- return this.diffLines.map(line => {
- const parallelLine = Object.assign({}, line);
-
- if (line.left) {
- parallelLine.left = trimFirstCharOfLineContent(line.left);
- } else {
- parallelLine.left = { type: EMPTY_CELL_TYPE };
- }
-
- if (line.right) {
- parallelLine.right = trimFirstCharOfLineContent(line.right);
- } else {
- parallelLine.right = { type: EMPTY_CELL_TYPE };
- }
-
- return parallelLine;
- });
- },
diffLinesLength() {
- return this.parallelDiffLines.length;
+ return this.diffLines.length;
},
userColorScheme() {
return window.gon.user_color_scheme;
},
},
- methods: {
- discussionsByLine(line, leftOrRight) {
- return line[leftOrRight] && line[leftOrRight].lineCode !== undefined ?
- this.singleDiscussionByLineCode(line[leftOrRight].lineCode) : [];
- },
- },
};
</script>
@@ -73,16 +42,14 @@ export default {
<table>
<tbody>
<template
- v-for="(line, index) in parallelDiffLines"
+ v-for="(line, index) in diffLines"
>
<parallel-diff-table-row
+ :key="index"
:file-hash="diffFile.fileHash"
:context-lines-path="diffFile.contextLinesPath"
:line="line"
:is-bottom="index + 1 === diffLinesLength"
- :key="index"
- :left-discussions="discussionsByLine(line, 'left')"
- :right-discussions="discussionsByLine(line, 'right')"
/>
<parallel-diff-comment-row
v-if="shouldRenderParallelCommentRow(line)"
@@ -90,8 +57,6 @@ export default {
:line="line"
:diff-file-hash="diffFile.fileHash"
:line-index="index"
- :left-discussions="discussionsByLine(line, 'left')"
- :right-discussions="discussionsByLine(line, 'right')"
/>
</template>
</tbody>
diff --git a/app/assets/javascripts/diffs/constants.js b/app/assets/javascripts/diffs/constants.js
index f68afa44837..2795dddfc48 100644
--- a/app/assets/javascripts/diffs/constants.js
+++ b/app/assets/javascripts/diffs/constants.js
@@ -7,6 +7,7 @@ export const CONTEXT_LINE_TYPE = 'context';
export const EMPTY_CELL_TYPE = 'empty-cell';
export const COMMENT_FORM_TYPE = 'commentForm';
export const DIFF_NOTE_TYPE = 'DiffNote';
+export const LEGACY_DIFF_NOTE_TYPE = 'LegacyDiffNote';
export const NOTE_TYPE = 'Note';
export const NEW_LINE_TYPE = 'new';
export const OLD_LINE_TYPE = 'old';
diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js
index 4ab6ceb249a..98d8d5943f9 100644
--- a/app/assets/javascripts/diffs/store/actions.js
+++ b/app/assets/javascripts/diffs/store/actions.js
@@ -1,8 +1,12 @@
import Vue from 'vue';
import axios from '~/lib/utils/axios_utils';
import Cookies from 'js-cookie';
+import createFlash from '~/flash';
+import { s__ } from '~/locale';
import { handleLocationHash, historyPushState } from '~/lib/utils/common_utils';
-import { mergeUrlParams } from '~/lib/utils/url_utility';
+import { mergeUrlParams, getLocationHash } from '~/lib/utils/url_utility';
+import { reduceDiscussionsToLineCodes } from '../../notes/stores/utils';
+import { getDiffPositionByLineCode, getNoteFormData } from './utils';
import * as types from './mutation_types';
import {
PARALLEL_DIFF_VIEW_TYPE,
@@ -29,25 +33,53 @@ 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);
+// This is adding line discussions to the actual lines in the diff tree
+// once for parallel and once for inline mode
+export const assignDiscussionsToDiff = ({ state, commit }, allLineDiscussions) => {
+ const diffPositionByLineCode = getDiffPositionByLineCode(state.diffFiles);
+
+ Object.values(allLineDiscussions).forEach(discussions => {
+ if (discussions.length > 0) {
+ const { fileHash } = discussions[0];
+ commit(types.SET_LINE_DISCUSSIONS_FOR_FILE, {
+ fileHash,
+ discussions,
+ diffPositionByLineCode,
});
- requestIdleCallback(
- () => {
- checkItem();
- },
- { timeout: 1000 },
- );
}
- };
+ });
+};
+
+export const removeDiscussionsFromDiff = ({ commit }, removeDiscussion) => {
+ const { fileHash, line_code } = removeDiscussion;
+ commit(types.REMOVE_LINE_DISCUSSIONS_FOR_FILE, { fileHash, lineCode: line_code });
+};
+
+export const startRenderDiffsQueue = ({ state, commit }) => {
+ const checkItem = () =>
+ new Promise(resolve => {
+ const nextFile = state.diffFiles.find(
+ file => !file.renderIt && (!file.collapsed || !file.text),
+ );
+
+ if (nextFile) {
+ requestAnimationFrame(() => {
+ commit(types.RENDER_FILE, nextFile);
+ });
+ requestIdleCallback(
+ () => {
+ checkItem()
+ .then(resolve)
+ .catch(() => {});
+ },
+ { timeout: 1000 },
+ );
+ } else {
+ resolve();
+ }
+ });
- checkItem();
+ return checkItem();
};
export const setInlineDiffViewType = ({ commit }) => {
@@ -91,6 +123,25 @@ export const loadMoreLines = ({ commit }, options) => {
});
};
+export const scrollToLineIfNeededInline = (_, line) => {
+ const hash = getLocationHash();
+
+ if (hash && line.lineCode === hash) {
+ handleLocationHash();
+ }
+};
+
+export const scrollToLineIfNeededParallel = (_, line) => {
+ const hash = getLocationHash();
+
+ if (
+ hash &&
+ ((line.left && line.left.lineCode === hash) || (line.right && line.right.lineCode === hash))
+ ) {
+ handleLocationHash();
+ }
+};
+
export const loadCollapsedDiff = ({ commit }, file) =>
axios.get(file.loadCollapsedDiffUrl).then(res => {
commit(types.ADD_COLLAPSED_DIFFS, {
@@ -130,5 +181,19 @@ export const toggleFileDiscussions = ({ getters, dispatch }, diff) => {
});
};
+export const saveDiffDiscussion = ({ dispatch }, { note, formData }) => {
+ const postData = getNoteFormData({
+ note,
+ ...formData,
+ });
+
+ return dispatch('saveNote', postData, { root: true })
+ .then(result => dispatch('updateDiscussion', result.discussion, { root: true }))
+ .then(discussion =>
+ dispatch('assignDiscussionsToDiff', reduceDiscussionsToLineCodes([discussion])),
+ )
+ .catch(() => createFlash(s__('MergeRequests|Saving the comment failed')));
+};
+
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/diffs/store/getters.js b/app/assets/javascripts/diffs/store/getters.js
index 4a47646d7fa..968ba3c5e13 100644
--- a/app/assets/javascripts/diffs/store/getters.js
+++ b/app/assets/javascripts/diffs/store/getters.js
@@ -17,7 +17,10 @@ export const commitId = state => (state.commit && state.commit.id ? state.commit
export const diffHasAllExpandedDiscussions = (state, getters) => diff => {
const discussions = getters.getDiffFileDiscussions(diff);
- return (discussions.length && discussions.every(discussion => discussion.expanded)) || false;
+ return (
+ (discussions && discussions.length && discussions.every(discussion => discussion.expanded)) ||
+ false
+ );
};
/**
@@ -28,7 +31,10 @@ export const diffHasAllExpandedDiscussions = (state, getters) => diff => {
export const diffHasAllCollpasedDiscussions = (state, getters) => diff => {
const discussions = getters.getDiffFileDiscussions(diff);
- return (discussions.length && discussions.every(discussion => !discussion.expanded)) || false;
+ return (
+ (discussions && discussions.length && discussions.every(discussion => !discussion.expanded)) ||
+ false
+ );
};
/**
@@ -40,7 +46,9 @@ export const diffHasExpandedDiscussions = (state, getters) => diff => {
const discussions = getters.getDiffFileDiscussions(diff);
return (
- (discussions.length && discussions.find(discussion => discussion.expanded) !== undefined) ||
+ (discussions &&
+ discussions.length &&
+ discussions.find(discussion => discussion.expanded) !== undefined) ||
false
);
};
@@ -64,45 +72,38 @@ export const getDiffFileDiscussions = (state, getters, rootState, rootGetters) =
discussion.diff_discussion && _.isEqual(discussion.diff_file.file_hash, diff.fileHash),
) || [];
-export const singleDiscussionByLineCode = (state, getters, rootState, rootGetters) => lineCode => {
- if (!lineCode || lineCode === undefined) return [];
- const discussions = rootGetters.discussionsByLineCode;
- return discussions[lineCode] || [];
-};
-
-export const shouldRenderParallelCommentRow = (state, getters) => line => {
- const leftLineCode = line.left.lineCode;
- const rightLineCode = line.right.lineCode;
- const leftDiscussions = getters.singleDiscussionByLineCode(leftLineCode);
- const rightDiscussions = getters.singleDiscussionByLineCode(rightLineCode);
- const hasDiscussion = leftDiscussions.length || rightDiscussions.length;
+export const shouldRenderParallelCommentRow = state => line => {
+ const hasDiscussion =
+ (line.left && line.left.discussions && line.left.discussions.length) ||
+ (line.right && line.right.discussions && line.right.discussions.length);
- const hasExpandedDiscussionOnLeft = leftDiscussions.length
- ? leftDiscussions.every(discussion => discussion.expanded)
- : false;
- const hasExpandedDiscussionOnRight = rightDiscussions.length
- ? rightDiscussions.every(discussion => discussion.expanded)
- : false;
+ const hasExpandedDiscussionOnLeft =
+ line.left && line.left.discussions && line.left.discussions.length
+ ? line.left.discussions.every(discussion => discussion.expanded)
+ : false;
+ const hasExpandedDiscussionOnRight =
+ line.right && line.right.discussions && line.right.discussions.length
+ ? line.right.discussions.every(discussion => discussion.expanded)
+ : false;
if (hasDiscussion && (hasExpandedDiscussionOnLeft || hasExpandedDiscussionOnRight)) {
return true;
}
- const hasCommentFormOnLeft = state.diffLineCommentForms[leftLineCode];
- const hasCommentFormOnRight = state.diffLineCommentForms[rightLineCode];
+ const hasCommentFormOnLeft = line.left && state.diffLineCommentForms[line.left.lineCode];
+ const hasCommentFormOnRight = line.right && state.diffLineCommentForms[line.right.lineCode];
return hasCommentFormOnLeft || hasCommentFormOnRight;
};
-export const shouldRenderInlineCommentRow = (state, getters) => line => {
+export const shouldRenderInlineCommentRow = state => line => {
if (state.diffLineCommentForms[line.lineCode]) return true;
- const lineDiscussions = getters.singleDiscussionByLineCode(line.lineCode);
- if (lineDiscussions.length === 0) {
+ if (!line.discussions || line.discussions.length === 0) {
return false;
}
- return lineDiscussions.every(discussion => discussion.expanded);
+ return line.discussions.every(discussion => discussion.expanded);
};
// prevent babel-plugin-rewire from generating an invalid default during karma∂ tests
diff --git a/app/assets/javascripts/diffs/store/modules/diff_state.js b/app/assets/javascripts/diffs/store/modules/diff_state.js
index 39d90a64aab..eb596b251c1 100644
--- a/app/assets/javascripts/diffs/store/modules/diff_state.js
+++ b/app/assets/javascripts/diffs/store/modules/diff_state.js
@@ -11,8 +11,10 @@ export default () => ({
endpoint: '',
basePath: '',
commit: null,
+ startVersion: null,
diffFiles: [],
mergeRequestDiffs: [],
+ mergeRequestDiff: null,
diffLineCommentForms: {},
diffViewType: viewTypeFromQueryString || viewTypeFromCookie || defaultViewType,
});
diff --git a/app/assets/javascripts/diffs/store/modules/index.js b/app/assets/javascripts/diffs/store/modules/index.js
index 20d1ebbe049..6860e24db6b 100644
--- a/app/assets/javascripts/diffs/store/modules/index.js
+++ b/app/assets/javascripts/diffs/store/modules/index.js
@@ -3,10 +3,10 @@ import * as getters from '../getters';
import mutations from '../mutations';
import createState from './diff_state';
-export default {
+export default () => ({
namespaced: true,
state: createState(),
getters,
actions,
mutations,
-};
+});
diff --git a/app/assets/javascripts/diffs/store/mutation_types.js b/app/assets/javascripts/diffs/store/mutation_types.js
index c999d637d50..f61efbe6e1e 100644
--- a/app/assets/javascripts/diffs/store/mutation_types.js
+++ b/app/assets/javascripts/diffs/store/mutation_types.js
@@ -9,3 +9,5 @@ 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';
+export const SET_LINE_DISCUSSIONS_FOR_FILE = 'SET_LINE_DISCUSSIONS_FOR_FILE';
+export const REMOVE_LINE_DISCUSSIONS_FOR_FILE = 'REMOVE_LINE_DISCUSSIONS_FOR_FILE';
diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js
index 0522e32c410..59a2c09e54f 100644
--- a/app/assets/javascripts/diffs/store/mutations.js
+++ b/app/assets/javascripts/diffs/store/mutations.js
@@ -1,8 +1,13 @@
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 {
+ findDiffFile,
+ addLineReferences,
+ removeMatchLine,
+ addContextLines,
+ prepareDiffData,
+ isDiscussionApplicableToLine,
+} from './utils';
import * as types from './mutation_types';
export default {
@@ -17,38 +22,7 @@ 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,
- });
- }
+ prepareDiffData(diffData);
Object.assign(state, {
...diffData,
@@ -98,19 +72,95 @@ export default {
[types.ADD_COLLAPSED_DIFFS](state, { file, data }) {
const normalizedData = convertObjectPropsToCamelCase(data, { deep: true });
+ prepareDiffData(normalizedData);
const [newFileData] = normalizedData.diffFiles.filter(f => f.fileHash === file.fileHash);
-
- if (newFileData) {
- const index = _.findIndex(state.diffFiles, f => f.fileHash === file.fileHash);
- state.diffFiles.splice(index, 1, newFileData);
- }
+ const selectedFile = state.diffFiles.find(f => f.fileHash === file.fileHash);
+ Object.assign(selectedFile, { ...newFileData });
},
[types.EXPAND_ALL_FILES](state) {
- // eslint-disable-next-line no-param-reassign
state.diffFiles = state.diffFiles.map(file => ({
...file,
collapsed: false,
}));
},
+
+ [types.SET_LINE_DISCUSSIONS_FOR_FILE](state, { fileHash, discussions, diffPositionByLineCode }) {
+ const selectedFile = state.diffFiles.find(f => f.fileHash === fileHash);
+ const firstDiscussion = discussions[0];
+ const isDiffDiscussion = firstDiscussion.diff_discussion;
+ const hasLineCode = firstDiscussion.line_code;
+ const diffPosition = diffPositionByLineCode[firstDiscussion.line_code];
+
+ if (
+ selectedFile &&
+ isDiffDiscussion &&
+ hasLineCode &&
+ diffPosition &&
+ isDiscussionApplicableToLine({
+ discussion: firstDiscussion,
+ diffPosition,
+ latestDiff: state.latestDiff,
+ })
+ ) {
+ const targetLine = selectedFile.parallelDiffLines.find(
+ line =>
+ (line.left && line.left.lineCode === firstDiscussion.line_code) ||
+ (line.right && line.right.lineCode === firstDiscussion.line_code),
+ );
+ if (targetLine) {
+ if (targetLine.left && targetLine.left.lineCode === firstDiscussion.line_code) {
+ Object.assign(targetLine.left, {
+ discussions,
+ });
+ } else {
+ Object.assign(targetLine.right, {
+ discussions,
+ });
+ }
+ }
+
+ if (selectedFile.highlightedDiffLines) {
+ const targetInlineLine = selectedFile.highlightedDiffLines.find(
+ line => line.lineCode === firstDiscussion.line_code,
+ );
+
+ if (targetInlineLine) {
+ Object.assign(targetInlineLine, {
+ discussions,
+ });
+ }
+ }
+ }
+ },
+
+ [types.REMOVE_LINE_DISCUSSIONS_FOR_FILE](state, { fileHash, lineCode }) {
+ const selectedFile = state.diffFiles.find(f => f.fileHash === fileHash);
+ if (selectedFile) {
+ const targetLine = selectedFile.parallelDiffLines.find(
+ line =>
+ (line.left && line.left.lineCode === lineCode) ||
+ (line.right && line.right.lineCode === lineCode),
+ );
+ if (targetLine) {
+ const side = targetLine.left && targetLine.left.lineCode === lineCode ? 'left' : 'right';
+
+ Object.assign(targetLine[side], {
+ discussions: [],
+ });
+ }
+
+ if (selectedFile.highlightedDiffLines) {
+ const targetInlineLine = selectedFile.highlightedDiffLines.find(
+ line => line.lineCode === lineCode,
+ );
+
+ if (targetInlineLine) {
+ Object.assign(targetInlineLine, {
+ discussions: [],
+ });
+ }
+ }
+ }
+ },
};
diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js
index 82082ac508a..631e3de311e 100644
--- a/app/assets/javascripts/diffs/store/utils.js
+++ b/app/assets/javascripts/diffs/store/utils.js
@@ -4,10 +4,13 @@ import {
LINE_POSITION_LEFT,
LINE_POSITION_RIGHT,
TEXT_DIFF_POSITION_TYPE,
+ LEGACY_DIFF_NOTE_TYPE,
DIFF_NOTE_TYPE,
NEW_LINE_TYPE,
OLD_LINE_TYPE,
MATCH_LINE_TYPE,
+ LINES_TO_BE_RENDERED_DIRECTLY,
+ MAX_LINES_TO_BE_RENDERED,
} from '../constants';
export function findDiffFile(files, hash) {
@@ -52,13 +55,17 @@ export function getNoteFormData(params) {
note_project_id: '',
target_type: noteableData.targetType,
target_id: noteableData.id,
+ return_discussion: true,
note: {
note,
position,
noteable_type: noteableType,
noteable_id: noteableData.id,
commit_id: '',
- type: DIFF_NOTE_TYPE,
+ type:
+ diffFile.diffRefs.startSha && diffFile.diffRefs.headSha
+ ? DIFF_NOTE_TYPE
+ : LEGACY_DIFF_NOTE_TYPE,
line_code: noteTargetLine.lineCode,
},
};
@@ -161,6 +168,11 @@ export function addContextLines(options) {
* @returns {Object}
*/
export function trimFirstCharOfLineContent(line = {}) {
+ // eslint-disable-next-line no-param-reassign
+ delete line.text;
+ // eslint-disable-next-line no-param-reassign
+ line.discussions = [];
+
const parsedLine = Object.assign({}, line);
if (line.richText) {
@@ -174,7 +186,44 @@ export function trimFirstCharOfLineContent(line = {}) {
return parsedLine;
}
-export function getDiffRefsByLineCode(diffFiles) {
+// This prepares and optimizes the incoming diff data from the server
+// by setting up incremental rendering and removing unneeded data
+export function prepareDiffData(diffData) {
+ const filesLength = diffData.diffFiles.length;
+ let showingLines = 0;
+ for (let i = 0; i < filesLength; i += 1) {
+ const file = diffData.diffFiles[i];
+
+ if (file.parallelDiffLines) {
+ const linesLength = file.parallelDiffLines.length;
+ for (let u = 0; u < linesLength; u += 1) {
+ const line = file.parallelDiffLines[u];
+ if (line.left) {
+ line.left = trimFirstCharOfLineContent(line.left);
+ }
+ if (line.right) {
+ line.right = trimFirstCharOfLineContent(line.right);
+ }
+ }
+ }
+
+ if (file.highlightedDiffLines) {
+ const linesLength = file.highlightedDiffLines.length;
+ for (let u = 0; u < linesLength; u += 1) {
+ const line = file.highlightedDiffLines[u];
+ Object.assign(line, { ...trimFirstCharOfLineContent(line) });
+ }
+ showingLines += file.parallelDiffLines.length;
+ }
+
+ Object.assign(file, {
+ renderIt: showingLines < LINES_TO_BE_RENDERED_DIRECTLY,
+ collapsed: file.text && showingLines > MAX_LINES_TO_BE_RENDERED,
+ });
+ }
+}
+
+export function getDiffPositionByLineCode(diffFiles) {
return diffFiles.reduce((acc, diffFile) => {
const { baseSha, headSha, startSha } = diffFile.diffRefs;
const { newPath, oldPath } = diffFile;
@@ -186,7 +235,16 @@ export function getDiffRefsByLineCode(diffFiles) {
const { lineCode, oldLine, newLine } = line;
if (lineCode) {
- acc[lineCode] = { baseSha, headSha, startSha, newPath, oldPath, oldLine, newLine };
+ acc[lineCode] = {
+ baseSha,
+ headSha,
+ startSha,
+ newPath,
+ oldPath,
+ oldLine,
+ newLine,
+ lineCode,
+ };
}
});
}
@@ -194,3 +252,18 @@ export function getDiffRefsByLineCode(diffFiles) {
return acc;
}, {});
}
+
+// This method will check whether the discussion is still applicable
+// to the diff line in question regarding different versions of the MR
+export function isDiscussionApplicableToLine({ discussion, diffPosition, latestDiff }) {
+ const { lineCode, ...diffPositionCopy } = diffPosition;
+
+ if (discussion.original_position && discussion.position) {
+ const originalRefs = convertObjectPropsToCamelCase(discussion.original_position.formatter);
+ const refs = convertObjectPropsToCamelCase(discussion.position.formatter);
+
+ return _.isEqual(refs, diffPositionCopy) || _.isEqual(originalRefs, diffPositionCopy);
+ }
+
+ return latestDiff && discussion.active && lineCode === discussion.line_code;
+}
diff --git a/app/assets/javascripts/dismissable_callout.js b/app/assets/javascripts/dismissable_callout.js
index 94f456bb3fc..27a3742f667 100644
--- a/app/assets/javascripts/dismissable_callout.js
+++ b/app/assets/javascripts/dismissable_callout.js
@@ -1,4 +1,4 @@
-import PersistentUserCallout from './persistent_user_callout';
+import PersistentUserCallout from '../../persistent_user_callout';
export default function initDismissableCallout(alertSelector) {
const alertEl = document.querySelector(alertSelector);
@@ -6,5 +6,5 @@ export default function initDismissableCallout(alertSelector) {
return;
}
- new PersistentUserCallout(alertEl); // eslint-disable-line no-new
+ new PersistentUserCallout(alertEl);
}
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
deleted file mode 100644
index a5af37e80b6..00000000000
--- a/app/assets/javascripts/dispatcher.js
+++ /dev/null
@@ -1,89 +0,0 @@
-/* eslint-disable consistent-return, no-new */
-
-import $ from 'jquery';
-import GfmAutoComplete from './gfm_auto_complete';
-import { convertPermissionToBoolean } from './lib/utils/common_utils';
-import GlFieldErrors from './gl_field_errors';
-import Shortcuts from './shortcuts';
-import SearchAutocomplete from './search_autocomplete';
-import performanceBar from './performance_bar';
-
-function initSearch() {
- // Only when search form is present
- if ($('.search').length) {
- return new SearchAutocomplete();
- }
-}
-
-function initFieldErrors() {
- $('.gl-show-field-errors').each((i, form) => {
- new GlFieldErrors(form);
- });
-}
-
-function initPageShortcuts(page) {
- const pagesWithCustomShortcuts = [
- 'projects:activity',
- 'projects:artifacts:browse',
- 'projects:artifacts:file',
- 'projects:blame:show',
- 'projects:blob:show',
- 'projects:commit:show',
- 'projects:commits:show',
- 'projects:find_file:show',
- 'projects:issues:edit',
- 'projects:issues:index',
- 'projects:issues:new',
- 'projects:issues:show',
- 'projects:merge_requests:creations:diffs',
- 'projects:merge_requests:creations:new',
- 'projects:merge_requests:edit',
- 'projects:merge_requests:index',
- 'projects:merge_requests:show',
- 'projects:network:show',
- 'projects:show',
- 'projects:tree:show',
- 'groups:show',
- ];
-
- if (pagesWithCustomShortcuts.indexOf(page) === -1) {
- new Shortcuts();
- }
-}
-
-function initGFMInput() {
- $('.js-gfm-input:not(.js-vue-textarea)').each((i, el) => {
- const gfm = new GfmAutoComplete(
- gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources,
- );
- const enableGFM = convertPermissionToBoolean(
- el.dataset.supportsAutocomplete,
- );
- gfm.setup($(el), {
- emojis: true,
- members: enableGFM,
- issues: enableGFM,
- milestones: enableGFM,
- mergeRequests: enableGFM,
- labels: enableGFM,
- });
- });
-}
-
-function initPerformanceBar() {
- if (document.querySelector('#js-peek')) {
- performanceBar({ container: '#js-peek' });
- }
-}
-
-export default () => {
- initSearch();
- initFieldErrors();
-
- const page = $('body').attr('data-page');
- if (page) {
- initPageShortcuts(page);
- initGFMInput();
- initPerformanceBar();
- }
-};
diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js
index 5528ad9f38d..d2778bcdf1c 100644
--- a/app/assets/javascripts/dropzone_input.js
+++ b/app/assets/javascripts/dropzone_input.js
@@ -1,12 +1,25 @@
import $ from 'jquery';
import Dropzone from 'dropzone';
import _ from 'underscore';
-import './preview_markdown';
+import './behaviors/preview_markdown';
import csrf from './lib/utils/csrf';
import axios from './lib/utils/axios_utils';
Dropzone.autoDiscover = false;
+/**
+ * Return the error message string from the given response.
+ *
+ * @param {String|Object} res
+ */
+function getErrorMessage(res) {
+ if (!res || _.isString(res)) {
+ return res;
+ }
+
+ return res.message;
+}
+
export default function dropzoneInput(form) {
const divHover = '<div class="div-dropzone-hover"></div>';
const iconPaperclip = '<i class="fa fa-paperclip div-dropzone-icon"></i>';
@@ -18,7 +31,7 @@ export default function dropzoneInput(form) {
const $uploadingErrorContainer = form.find('.uploading-error-container');
const $uploadingErrorMessage = form.find('.uploading-error-message');
const $uploadingProgressContainer = form.find('.uploading-progress-container');
- const uploadsPath = window.uploads_path || null;
+ const uploadsPath = form.data('uploads-path') || window.uploads_path || null;
const maxFileSize = gon.max_file_size || 10;
const formTextarea = form.find('.js-gfm-input');
let handlePaste;
@@ -42,7 +55,7 @@ export default function dropzoneInput(form) {
if (!uploadsPath) {
$formDropzone.addClass('js-invalid-dropzone');
- return;
+ return null;
}
const dropzone = $formDropzone.dropzone({
@@ -84,9 +97,7 @@ export default function dropzoneInput(form) {
// xhr object (xhr.responseText is error message).
// On error we hide the 'Attach' and 'Cancel' buttons
// and show an error.
-
- // If there's xhr error message, let's show it instead of dropzone's one.
- const message = xhr ? xhr.responseText : errorMessage;
+ const message = getErrorMessage(errorMessage || xhr.responseText);
$uploadingErrorContainer.removeClass('hide');
$uploadingErrorMessage.html(message);
@@ -274,4 +285,6 @@ export default function dropzoneInput(form) {
$(this).closest('.gfm-form').find('.div-dropzone').click();
formTextarea.focus();
});
+
+ return Dropzone.forElement($formDropzone.get(0));
}
diff --git a/app/assets/javascripts/environments/components/container.vue b/app/assets/javascripts/environments/components/container.vue
index 9aa224fa407..9de851c9409 100644
--- a/app/assets/javascripts/environments/components/container.vue
+++ b/app/assets/javascripts/environments/components/container.vue
@@ -1,12 +1,10 @@
<script>
- import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tablePagination from '../../vue_shared/components/table_pagination.vue';
import environmentTable from '../components/environments_table.vue';
export default {
components: {
environmentTable,
- loadingIcon,
tablePagination,
},
props: {
@@ -42,11 +40,11 @@
<template>
<div class="environments-container">
- <loading-icon
+ <gl-loading-icon
v-if="isLoading"
+ :size="3"
class="prepend-top-default"
label="Loading environments"
- size="3"
/>
<slot name="emptyState"></slot>
diff --git a/app/assets/javascripts/environments/components/empty_state.vue b/app/assets/javascripts/environments/components/empty_state.vue
index 00e63c3467a..cf78f89981e 100644
--- a/app/assets/javascripts/environments/components/empty_state.vue
+++ b/app/assets/javascripts/environments/components/empty_state.vue
@@ -35,7 +35,7 @@ code gets deployed, such as staging or production.`) }}
<a
v-if="canCreateEnvironment"
:href="newPath"
- class="btn btn-create js-new-environment-button"
+ class="btn btn-success js-new-environment-button"
>
{{ s__("Environments|New environment") }}
</a>
diff --git a/app/assets/javascripts/environments/components/environment_actions.vue b/app/assets/javascripts/environments/components/environment_actions.vue
index 63d83e307ee..e1f9248bc4c 100644
--- a/app/assets/javascripts/environments/components/environment_actions.vue
+++ b/app/assets/javascripts/environments/components/environment_actions.vue
@@ -1,7 +1,6 @@
<script>
import Icon from '~/vue_shared/components/icon.vue';
import eventHub from '../event_hub';
-import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tooltip from '../../vue_shared/directives/tooltip';
export default {
@@ -9,7 +8,6 @@ export default {
tooltip,
},
components: {
- loadingIcon,
Icon,
},
props: {
@@ -67,7 +65,7 @@ export default {
aria-hidden="true"
>
</i>
- <loading-icon v-if="isLoading" />
+ <gl-loading-icon v-if="isLoading" />
</span>
</button>
diff --git a/app/assets/javascripts/environments/components/environment_rollback.vue b/app/assets/javascripts/environments/components/environment_rollback.vue
index 4deeef4beb9..efbf88d0f11 100644
--- a/app/assets/javascripts/environments/components/environment_rollback.vue
+++ b/app/assets/javascripts/environments/components/environment_rollback.vue
@@ -9,12 +9,10 @@ import { s__ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
import tooltip from '~/vue_shared/directives/tooltip';
import eventHub from '../event_hub';
-import LoadingIcon from '../../vue_shared/components/loading_icon.vue';
export default {
components: {
Icon,
- LoadingIcon,
},
directives: {
@@ -70,6 +68,6 @@ export default {
v-else
name="redo"/>
- <loading-icon v-if="isLoading" />
+ <gl-loading-icon v-if="isLoading" />
</button>
</template>
diff --git a/app/assets/javascripts/environments/components/environments_app.vue b/app/assets/javascripts/environments/components/environments_app.vue
index 8efdfb8abe0..e2ecf426e64 100644
--- a/app/assets/javascripts/environments/components/environments_app.vue
+++ b/app/assets/javascripts/environments/components/environments_app.vue
@@ -107,7 +107,7 @@
>
<a
:href="newEnvironmentPath"
- class="btn btn-create"
+ class="btn btn-success"
>
{{ s__("Environments|New environment") }}
</a>
diff --git a/app/assets/javascripts/environments/components/environments_table.vue b/app/assets/javascripts/environments/components/environments_table.vue
index 016e9f7c7b3..16abafebbc0 100644
--- a/app/assets/javascripts/environments/components/environments_table.vue
+++ b/app/assets/javascripts/environments/components/environments_table.vue
@@ -2,13 +2,11 @@
/**
* Render environments table.
*/
-import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import environmentItem from './environment_item.vue';
export default {
components: {
environmentItem,
- loadingIcon,
},
props: {
@@ -85,10 +83,10 @@ export default {
:model="model">
<div
is="environment-item"
+ :key="`environment-item-${i}`"
:model="model"
:can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"
- :key="`environment-item-${i}`"
/>
<template
@@ -97,17 +95,17 @@ export default {
<div
v-if="model.isLoadingFolderContent"
:key="`loading-item-${i}`">
- <loading-icon size="2" />
+ <gl-loading-icon :size="2" />
</div>
<template v-else>
<div
is="environment-item"
v-for="(children, index) in model.children"
+ :key="`env-item-${i}-${index}`"
:model="children"
:can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"
- :key="`env-item-${i}-${index}`"
/>
<div :key="`sub-div-${i}`">
diff --git a/app/assets/javascripts/environments/mixins/environments_mixin.js b/app/assets/javascripts/environments/mixins/environments_mixin.js
index d88624f7f8d..d71964612c5 100644
--- a/app/assets/javascripts/environments/mixins/environments_mixin.js
+++ b/app/assets/javascripts/environments/mixins/environments_mixin.js
@@ -13,7 +13,6 @@ import eventHub from '../event_hub';
import EnvironmentsStore from '../stores/environments_store';
import EnvironmentsService from '../services/environments_service';
-import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tablePagination from '../../vue_shared/components/table_pagination.vue';
import environmentTable from '../components/environments_table.vue';
import tabs from '../../vue_shared/components/navigation_tabs.vue';
@@ -24,7 +23,6 @@ export default {
components: {
environmentTable,
container,
- loadingIcon,
tabs,
tablePagination,
},
diff --git a/app/assets/javascripts/feature_highlight/feature_highlight.js b/app/assets/javascripts/feature_highlight/feature_highlight.js
index 2f27c9351bc..03dfa942d69 100644
--- a/app/assets/javascripts/feature_highlight/feature_highlight.js
+++ b/app/assets/javascripts/feature_highlight/feature_highlight.js
@@ -16,7 +16,7 @@ export function setupFeatureHighlightPopover(id, debounceTimeout = 300) {
const hideOnScroll = togglePopover.bind($selector, false);
$selector
- // Setup popover
+ // Set up popover
.data('content', $popoverContent.prop('outerHTML'))
.popover({
html: true,
diff --git a/app/assets/javascripts/filtered_search/admin_runners_filtered_search_token_keys.js b/app/assets/javascripts/filtered_search/admin_runners_filtered_search_token_keys.js
new file mode 100644
index 00000000000..b4588cc1318
--- /dev/null
+++ b/app/assets/javascripts/filtered_search/admin_runners_filtered_search_token_keys.js
@@ -0,0 +1,14 @@
+import FilteredSearchTokenKeys from './filtered_search_token_keys';
+
+const tokenKeys = [{
+ key: 'status',
+ type: 'string',
+ param: 'status',
+ symbol: '',
+ icon: 'messages',
+ tag: 'status',
+}];
+
+const AdminRunnersFilteredSearchTokenKeys = new FilteredSearchTokenKeys(tokenKeys);
+
+export default AdminRunnersFilteredSearchTokenKeys;
diff --git a/app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.vue b/app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.vue
index a8eb8d94be3..21b5ccdb613 100644
--- a/app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.vue
+++ b/app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.vue
@@ -72,8 +72,8 @@ export default {
@click="onItemActivated(item.text)">
<span>
<span
- v-for="(token, index) in item.tokens"
- :key="`dropdown-token-${index}`"
+ v-for="(token, tokenIndex) in item.tokens"
+ :key="`dropdown-token-${tokenIndex}`"
class="filtered-search-history-dropdown-token"
>
<span class="name">{{ token.prefix }}</span>
diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js b/app/assets/javascripts/filtered_search/dropdown_hint.js
index 184b34b7b5e..8aecf9725e6 100644
--- a/app/assets/javascripts/filtered_search/dropdown_hint.js
+++ b/app/assets/javascripts/filtered_search/dropdown_hint.js
@@ -62,7 +62,7 @@ export default class DropdownHint extends FilteredSearchDropdown {
renderContent() {
const dropdownData = this.tokenKeys.get()
.map(tokenKey => ({
- icon: `fa-${tokenKey.icon}`,
+ icon: `${gon.sprite_icons}#${tokenKey.icon}`,
hint: tokenKey.key,
tag: `:${tokenKey.tag}`,
type: tokenKey.type,
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
index 296571606d6..a750647f8be 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
@@ -7,6 +7,7 @@ import DropdownHint from './dropdown_hint';
import DropdownEmoji from './dropdown_emoji';
import DropdownNonUser from './dropdown_non_user';
import DropdownUser from './dropdown_user';
+import NullDropdown from './null_dropdown';
import FilteredSearchVisualTokens from './filtered_search_visual_tokens';
export default class FilteredSearchDropdownManager {
@@ -90,6 +91,11 @@ export default class FilteredSearchDropdownManager {
gl: DropdownEmoji,
element: this.container.querySelector('#js-dropdown-my-reaction'),
},
+ status: {
+ reference: null,
+ gl: NullDropdown,
+ element: this.container.querySelector('#js-dropdown-admin-runner-status'),
+ },
};
supportedTokens.forEach((type) => {
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index 81286c54c4c..d25f6f95b22 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -3,10 +3,10 @@ import {
getParameterByName,
getUrlParamsArray,
} from '~/lib/utils/common_utils';
+import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
import { visitUrl } from '../lib/utils/url_utility';
import Flash from '../flash';
import FilteredSearchContainer from './container';
-import FilteredSearchTokenKeys from './filtered_search_token_keys';
import RecentSearchesRoot from './recent_searches_root';
import RecentSearchesStore from './stores/recent_searches_store';
import RecentSearchesService from './services/recent_searches_service';
@@ -23,7 +23,7 @@ export default class FilteredSearchManager {
isGroup = false,
isGroupAncestor = true,
isGroupDecendent = false,
- filteredSearchTokenKeys = FilteredSearchTokenKeys,
+ filteredSearchTokenKeys = IssuableFilteredSearchTokenKeys,
stateFiltersSelector = '.issues-state-filters',
}) {
this.isGroup = isGroup;
diff --git a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js
index 087ef5cd6f2..5d131b396a0 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js
@@ -1,103 +1,38 @@
-const tokenKeys = [{
- key: 'author',
- type: 'string',
- param: 'username',
- symbol: '@',
- icon: 'pencil',
- tag: '@author',
-}, {
- key: 'assignee',
- type: 'string',
- param: 'username',
- symbol: '@',
- icon: 'user',
- tag: '@assignee',
-}, {
- key: 'milestone',
- type: 'string',
- param: 'title',
- symbol: '%',
- icon: 'clock-o',
- tag: '%milestone',
-}, {
- key: 'label',
- type: 'array',
- param: 'name[]',
- symbol: '~',
- icon: 'tag',
- tag: '~label',
-}];
-
-if (gon.current_user_id) {
- // Appending tokenkeys only logged-in
- tokenKeys.push({
- key: 'my-reaction',
- type: 'string',
- param: 'emoji',
- symbol: '',
- icon: 'thumbs-up',
- tag: 'emoji',
- });
-}
-
-const alternativeTokenKeys = [{
- key: 'label',
- type: 'string',
- param: 'name',
- symbol: '~',
-}];
-
-const tokenKeysWithAlternative = tokenKeys.concat(alternativeTokenKeys);
+export default class FilteredSearchTokenKeys {
+ constructor(tokenKeys = [], alternativeTokenKeys = [], conditions = []) {
+ this.tokenKeys = tokenKeys;
+ this.alternativeTokenKeys = alternativeTokenKeys;
+ this.conditions = conditions;
-const conditions = [{
- url: 'assignee_id=0',
- tokenKey: 'assignee',
- value: 'none',
-}, {
- url: 'milestone_title=No+Milestone',
- tokenKey: 'milestone',
- value: 'none',
-}, {
- url: 'milestone_title=%23upcoming',
- tokenKey: 'milestone',
- value: 'upcoming',
-}, {
- url: 'milestone_title=%23started',
- tokenKey: 'milestone',
- value: 'started',
-}, {
- url: 'label_name[]=No+Label',
- tokenKey: 'label',
- value: 'none',
-}];
+ this.tokenKeysWithAlternative = this.tokenKeys.concat(this.alternativeTokenKeys);
+ }
-export default class FilteredSearchTokenKeys {
- static get() {
- return tokenKeys;
+ get() {
+ return this.tokenKeys;
}
- static getKeys() {
- return tokenKeys.map(i => i.key);
+ getKeys() {
+ return this.tokenKeys.map(i => i.key);
}
- static getAlternatives() {
- return alternativeTokenKeys;
+ getAlternatives() {
+ return this.alternativeTokenKeys;
}
- static getConditions() {
- return conditions;
+ getConditions() {
+ return this.conditions;
}
- static searchByKey(key) {
- return tokenKeys.find(tokenKey => tokenKey.key === key) || null;
+ searchByKey(key) {
+ return this.tokenKeys.find(tokenKey => tokenKey.key === key) || null;
}
- static searchBySymbol(symbol) {
- return tokenKeys.find(tokenKey => tokenKey.symbol === symbol) || null;
+ searchBySymbol(symbol) {
+ return this.tokenKeys.find(tokenKey => tokenKey.symbol === symbol) || null;
}
- static searchByKeyParam(keyParam) {
- return tokenKeysWithAlternative.find((tokenKey) => {
+ searchByKeyParam(keyParam) {
+ return this.tokenKeysWithAlternative.find((tokenKey) => {
let tokenKeyParam = tokenKey.key;
// Replace hyphen with underscore to compare keyParam with tokenKeyParam
@@ -112,12 +47,12 @@ export default class FilteredSearchTokenKeys {
}) || null;
}
- static searchByConditionUrl(url) {
- return conditions.find(condition => condition.url === url) || null;
+ searchByConditionUrl(url) {
+ return this.conditions.find(condition => condition.url === url) || null;
}
- static searchByConditionKeyValue(key, value) {
- return conditions
+ searchByConditionKeyValue(key, value) {
+ return this.conditions
.find(condition => condition.tokenKey === key && condition.value === value) || null;
}
}
diff --git a/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js b/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js
new file mode 100644
index 00000000000..cc7291c9f59
--- /dev/null
+++ b/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js
@@ -0,0 +1,77 @@
+import FilteredSearchTokenKeys from './filtered_search_token_keys';
+
+export const tokenKeys = [{
+ key: 'author',
+ type: 'string',
+ param: 'username',
+ symbol: '@',
+ icon: 'pencil',
+ tag: '@author',
+}, {
+ key: 'assignee',
+ type: 'string',
+ param: 'username',
+ symbol: '@',
+ icon: 'user',
+ tag: '@assignee',
+}, {
+ key: 'milestone',
+ type: 'string',
+ param: 'title',
+ symbol: '%',
+ icon: 'clock',
+ tag: '%milestone',
+}, {
+ key: 'label',
+ type: 'array',
+ param: 'name[]',
+ symbol: '~',
+ icon: 'labels',
+ tag: '~label',
+}];
+
+if (gon.current_user_id) {
+ // Appending tokenkeys only logged-in
+ tokenKeys.push({
+ key: 'my-reaction',
+ type: 'string',
+ param: 'emoji',
+ symbol: '',
+ icon: 'thumb-up',
+ tag: 'emoji',
+ });
+}
+
+export const alternativeTokenKeys = [{
+ key: 'label',
+ type: 'string',
+ param: 'name',
+ symbol: '~',
+}];
+
+export const conditions = [{
+ url: 'assignee_id=0',
+ tokenKey: 'assignee',
+ value: 'none',
+}, {
+ url: 'milestone_title=No+Milestone',
+ tokenKey: 'milestone',
+ value: 'none',
+}, {
+ url: 'milestone_title=%23upcoming',
+ tokenKey: 'milestone',
+ value: 'upcoming',
+}, {
+ url: 'milestone_title=%23started',
+ tokenKey: 'milestone',
+ value: 'started',
+}, {
+ url: 'label_name[]=No+Label',
+ tokenKey: 'label',
+ value: 'none',
+}];
+
+const IssuableFilteredSearchTokenKeys =
+ new FilteredSearchTokenKeys(tokenKeys, alternativeTokenKeys, conditions);
+
+export default IssuableFilteredSearchTokenKeys;
diff --git a/app/assets/javascripts/filtered_search/null_dropdown.js b/app/assets/javascripts/filtered_search/null_dropdown.js
new file mode 100644
index 00000000000..4cfce2a5beb
--- /dev/null
+++ b/app/assets/javascripts/filtered_search/null_dropdown.js
@@ -0,0 +1,9 @@
+import FilteredSearchDropdown from './filtered_search_dropdown';
+
+export default class NullDropdown extends FilteredSearchDropdown {
+ renderContent(forceShowList = false) {
+ this.droplab.changeHookList(this.hookId, this.dropdown, [], this.config);
+
+ super.renderContent(forceShowList);
+ }
+}
diff --git a/app/assets/javascripts/fly_out_nav.js b/app/assets/javascripts/fly_out_nav.js
index 8b4f3b05ee7..f820f0dc3f0 100644
--- a/app/assets/javascripts/fly_out_nav.js
+++ b/app/assets/javascripts/fly_out_nav.js
@@ -65,8 +65,8 @@ export const hideMenu = (el) => {
const parentEl = el.parentNode;
- el.style.display = ''; // eslint-disable-line no-param-reassign
- el.style.transform = ''; // eslint-disable-line no-param-reassign
+ el.style.display = '';
+ el.style.transform = '';
el.classList.remove(IS_ABOVE_CLASS);
parentEl.classList.remove(IS_OVER_CLASS);
parentEl.classList.remove(IS_SHOWING_FLY_OUT_CLASS);
diff --git a/app/assets/javascripts/frequent_items/components/app.vue b/app/assets/javascripts/frequent_items/components/app.vue
index 2f030de8967..70a8838b772 100644
--- a/app/assets/javascripts/frequent_items/components/app.vue
+++ b/app/assets/javascripts/frequent_items/components/app.vue
@@ -1,6 +1,5 @@
<script>
import { mapState, mapActions, mapGetters } from 'vuex';
-import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
import AccessorUtilities from '~/lib/utils/accessor';
import eventHub from '../event_hub';
import store from '../store/';
@@ -13,7 +12,6 @@ import frequentItemsMixin from './frequent_items_mixin';
export default {
store,
components: {
- LoadingIcon,
FrequentItemsSearchInput,
FrequentItemsList,
},
@@ -98,11 +96,11 @@ export default {
<frequent-items-search-input
:namespace="namespace"
/>
- <loading-icon
+ <gl-loading-icon
v-if="isLoadingItems"
:label="translations.loadingMessage"
+ :size="2"
class="loading-animation prepend-top-20"
- size="2"
/>
<div
v-if="!isLoadingItems && !hasSearchQuery"
diff --git a/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue b/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue
index 1f1665ff7fe..2399ee15332 100644
--- a/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue
+++ b/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue
@@ -1,5 +1,5 @@
<script>
-/* eslint-disable vue/require-default-prop, vue/require-prop-types */
+/* eslint-disable vue/require-default-prop */
import Identicon from '../../vue_shared/components/identicon.vue';
export default {
diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js
index c74de7ac34d..e672284a2d0 100644
--- a/app/assets/javascripts/gl_form.js
+++ b/app/assets/javascripts/gl_form.js
@@ -18,7 +18,7 @@ export default class GLForm {
});
// Before we start, we should clean up any previous data for this form
this.destroy();
- // Setup the form
+ // Set up the form
this.setupForm();
this.form.data('glForm', this);
}
diff --git a/app/assets/javascripts/groups/components/app.vue b/app/assets/javascripts/groups/components/app.vue
index b0765747a36..a032f291546 100644
--- a/app/assets/javascripts/groups/components/app.vue
+++ b/app/assets/javascripts/groups/components/app.vue
@@ -2,23 +2,32 @@
/* global Flash */
import $ from 'jquery';
-import { s__ } from '~/locale';
-import loadingIcon from '~/vue_shared/components/loading_icon.vue';
+import { s__, sprintf } from '~/locale';
import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
+import { HIDDEN_CLASS } from '~/lib/utils/constants';
import { getParameterByName } from '~/lib/utils/common_utils';
import { mergeUrlParams } from '~/lib/utils/url_utility';
import eventHub from '../event_hub';
-import { COMMON_STR } from '../constants';
+import { COMMON_STR, CONTENT_LIST_CLASS } from '../constants';
import groupsComponent from './groups.vue';
export default {
components: {
- loadingIcon,
DeprecatedModal,
groupsComponent,
},
props: {
+ action: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ containerId: {
+ type: String,
+ required: false,
+ default: '',
+ },
store: {
type: Object,
required: true,
@@ -56,31 +65,28 @@ export default {
? COMMON_STR.GROUP_SEARCH_EMPTY
: COMMON_STR.GROUP_PROJECT_SEARCH_EMPTY;
- eventHub.$on('fetchPage', this.fetchPage);
- eventHub.$on('toggleChildren', this.toggleChildren);
- eventHub.$on('showLeaveGroupModal', this.showLeaveGroupModal);
- eventHub.$on('updatePagination', this.updatePagination);
- eventHub.$on('updateGroups', this.updateGroups);
+ eventHub.$on(`${this.action}fetchPage`, this.fetchPage);
+ eventHub.$on(`${this.action}toggleChildren`, this.toggleChildren);
+ eventHub.$on(`${this.action}showLeaveGroupModal`, this.showLeaveGroupModal);
+ eventHub.$on(`${this.action}updatePagination`, this.updatePagination);
+ eventHub.$on(`${this.action}updateGroups`, this.updateGroups);
},
mounted() {
this.fetchAllGroups();
+
+ if (this.containerId) {
+ this.containerEl = document.getElementById(this.containerId);
+ }
},
beforeDestroy() {
- eventHub.$off('fetchPage', this.fetchPage);
- eventHub.$off('toggleChildren', this.toggleChildren);
- eventHub.$off('showLeaveGroupModal', this.showLeaveGroupModal);
- eventHub.$off('updatePagination', this.updatePagination);
- eventHub.$off('updateGroups', this.updateGroups);
+ eventHub.$off(`${this.action}fetchPage`, this.fetchPage);
+ eventHub.$off(`${this.action}toggleChildren`, this.toggleChildren);
+ eventHub.$off(`${this.action}showLeaveGroupModal`, this.showLeaveGroupModal);
+ eventHub.$off(`${this.action}updatePagination`, this.updatePagination);
+ eventHub.$off(`${this.action}updateGroups`, this.updateGroups);
},
methods: {
- fetchGroups({
- parentId,
- page,
- filterGroupsBy,
- sortBy,
- archived,
- updatePagination,
- }) {
+ fetchGroups({ parentId, page, filterGroupsBy, sortBy, archived, updatePagination }) {
return this.service
.getGroups(parentId, page, filterGroupsBy, sortBy, archived)
.then(res => {
@@ -165,13 +171,13 @@ export default {
}
},
showLeaveGroupModal(group, parentGroup) {
+ const { fullName } = group;
this.targetGroup = group;
this.targetParentGroup = parentGroup;
this.showModal = true;
- this.groupLeaveConfirmationMessage = s__(
- `GroupsTree|Are you sure you want to leave the "${
- group.fullName
- }" group?`,
+ this.groupLeaveConfirmationMessage = sprintf(
+ s__('GroupsTree|Are you sure you want to leave the "%{fullName}" group?'),
+ { fullName },
);
},
hideLeaveGroupModal() {
@@ -197,16 +203,35 @@ export default {
this.targetGroup.isBeingRemoved = false;
});
},
+ showEmptyState() {
+ const { containerEl } = this;
+ const contentListEl = containerEl.querySelector(CONTENT_LIST_CLASS);
+ const emptyStateEl = containerEl.querySelector('.empty-state');
+
+ if (contentListEl) {
+ contentListEl.remove();
+ }
+
+ if (emptyStateEl) {
+ emptyStateEl.classList.remove(HIDDEN_CLASS);
+ }
+ },
updatePagination(headers) {
this.store.setPaginationInfo(headers);
},
updateGroups(groups, fromSearch) {
- this.isSearchEmpty = groups ? groups.length === 0 : false;
+ const hasGroups = groups && groups.length > 0;
+ this.isSearchEmpty = !hasGroups;
+
if (fromSearch) {
this.store.setSearchedGroups(groups);
} else {
this.store.setGroups(groups);
}
+
+ if (this.action && !hasGroups && !fromSearch) {
+ this.showEmptyState();
+ }
},
},
};
@@ -214,11 +239,11 @@ export default {
<template>
<div>
- <loading-icon
+ <gl-loading-icon
v-if="isLoading"
:label="s__('GroupsTree|Loading groups')"
+ :size="2"
class="loading-animation prepend-top-20"
- size="2"
/>
<groups-component
v-if="!isLoading"
@@ -226,6 +251,7 @@ export default {
:search-empty="isSearchEmpty"
:search-empty-message="searchEmptyMessage"
:page-info="pageInfo"
+ :action="action"
/>
<deprecated-modal
v-show="showModal"
diff --git a/app/assets/javascripts/groups/components/group_folder.vue b/app/assets/javascripts/groups/components/group_folder.vue
index 647c9d0046d..bcc7a638346 100644
--- a/app/assets/javascripts/groups/components/group_folder.vue
+++ b/app/assets/javascripts/groups/components/group_folder.vue
@@ -11,8 +11,12 @@ export default {
},
groups: {
type: Array,
+ required: true,
+ },
+ action: {
+ type: String,
required: false,
- default: () => ([]),
+ default: '',
},
},
computed: {
@@ -37,6 +41,7 @@ export default {
:key="index"
:group="group"
:parent-group="parentGroup"
+ :action="action"
/>
<li
v-if="hasMoreChildren"
diff --git a/app/assets/javascripts/groups/components/group_item.vue b/app/assets/javascripts/groups/components/group_item.vue
index 2b9e2a929fc..44d6fa26914 100644
--- a/app/assets/javascripts/groups/components/group_item.vue
+++ b/app/assets/javascripts/groups/components/group_item.vue
@@ -30,6 +30,11 @@ export default {
type: Object,
required: true,
},
+ action: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
computed: {
groupDomId() {
@@ -56,10 +61,12 @@ export default {
methods: {
onClickRowGroup(e) {
const NO_EXPAND_CLS = 'no-expand';
- if (!(e.target.classList.contains(NO_EXPAND_CLS) ||
- e.target.parentElement.classList.contains(NO_EXPAND_CLS))) {
+ const targetClasses = e.target.classList;
+ const parentElClasses = e.target.parentElement.classList;
+
+ if (!(targetClasses.contains(NO_EXPAND_CLS) || parentElClasses.contains(NO_EXPAND_CLS))) {
if (this.hasChildren) {
- eventHub.$emit('toggleChildren', this.group);
+ eventHub.$emit(`${this.action}toggleChildren`, this.group);
} else {
visitUrl(this.group.relativePath);
}
@@ -93,7 +100,7 @@ export default {
</div>
<div
:class="{ 'content-loading': group.isChildrenLoading }"
- class="avatar-container s24 d-none d-sm-block"
+ class="avatar-container s24 d-none d-sm-flex"
>
<a
:href="group.relativePath"
@@ -158,6 +165,7 @@ export default {
v-if="group.isOpen && hasChildren"
:parent-group="group"
:groups="group.children"
+ :action="action"
/>
</li>
</template>
diff --git a/app/assets/javascripts/groups/components/groups.vue b/app/assets/javascripts/groups/components/groups.vue
index 73ae928b0d9..81b2e5ea37b 100644
--- a/app/assets/javascripts/groups/components/groups.vue
+++ b/app/assets/javascripts/groups/components/groups.vue
@@ -1,39 +1,44 @@
<script>
- import tablePagination from '~/vue_shared/components/table_pagination.vue';
- import eventHub from '../event_hub';
- import { getParameterByName } from '../../lib/utils/common_utils';
+import PaginationLinks from '~/vue_shared/components/pagination_links.vue';
+import eventHub from '../event_hub';
+import { getParameterByName } from '../../lib/utils/common_utils';
- export default {
- components: {
- tablePagination,
+export default {
+ components: {
+ PaginationLinks,
+ },
+ props: {
+ groups: {
+ type: Array,
+ required: true,
},
- props: {
- groups: {
- type: Array,
- required: true,
- },
- pageInfo: {
- type: Object,
- required: true,
- },
- searchEmpty: {
- type: Boolean,
- required: true,
- },
- searchEmptyMessage: {
- type: String,
- required: true,
- },
+ pageInfo: {
+ type: Object,
+ required: true,
},
- methods: {
- change(page) {
- const filterGroupsParam = getParameterByName('filter_groups');
- const sortParam = getParameterByName('sort');
- const archivedParam = getParameterByName('archived');
- eventHub.$emit('fetchPage', page, filterGroupsParam, sortParam, archivedParam);
- },
+ searchEmpty: {
+ type: Boolean,
+ required: true,
},
- };
+ searchEmptyMessage: {
+ type: String,
+ required: true,
+ },
+ action: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ methods: {
+ change(page) {
+ const filterGroupsParam = getParameterByName('filter_groups');
+ const sortParam = getParameterByName('sort');
+ const archivedParam = getParameterByName('archived');
+ eventHub.$emit(`${this.action}fetchPage`, page, filterGroupsParam, sortParam, archivedParam);
+ },
+ },
+};
</script>
<template>
@@ -44,14 +49,18 @@
>
{{ searchEmptyMessage }}
</div>
- <group-folder
- v-if="!searchEmpty"
- :groups="groups"
- />
- <table-pagination
- v-if="!searchEmpty"
- :change="change"
- :page-info="pageInfo"
- />
+ <template
+ v-else
+ >
+ <group-folder
+ :groups="groups"
+ :action="action"
+ />
+ <pagination-links
+ :change="change"
+ :page-info="pageInfo"
+ class="d-flex justify-content-center prepend-top-default"
+ />
+ </template>
</div>
</template>
diff --git a/app/assets/javascripts/groups/components/item_actions.vue b/app/assets/javascripts/groups/components/item_actions.vue
index 24eec4901ec..c1783d5ce25 100644
--- a/app/assets/javascripts/groups/components/item_actions.vue
+++ b/app/assets/javascripts/groups/components/item_actions.vue
@@ -21,6 +21,11 @@ export default {
type: Object,
required: true,
},
+ action: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
computed: {
leaveBtnTitle() {
@@ -32,7 +37,7 @@ export default {
},
methods: {
onLeaveGroup() {
- eventHub.$emit('showLeaveGroupModal', this.group, this.parentGroup);
+ eventHub.$emit(`${this.action}showLeaveGroupModal`, this.group, this.parentGroup);
},
},
};
@@ -41,8 +46,8 @@ export default {
<template>
<div class="controls">
<a
- v-tooltip
v-if="group.canEdit"
+ v-tooltip
:href="group.editPath"
:title="editBtnTitle"
:aria-label="editBtnTitle"
@@ -52,8 +57,8 @@ export default {
<icon name="settings"/>
</a>
<a
- v-tooltip
v-if="group.canLeave"
+ v-tooltip
:href="group.leavePath"
:title="leaveBtnTitle"
:aria-label="leaveBtnTitle"
diff --git a/app/assets/javascripts/groups/constants.js b/app/assets/javascripts/groups/constants.js
index b8baed682f5..9c246cf3ba6 100644
--- a/app/assets/javascripts/groups/constants.js
+++ b/app/assets/javascripts/groups/constants.js
@@ -2,13 +2,23 @@ import { __, s__ } from '../locale';
export const MAX_CHILDREN_COUNT = 20;
+export const ACTIVE_TAB_SUBGROUPS_AND_PROJECTS = 'subgroups_and_projects';
+export const ACTIVE_TAB_SHARED = 'shared';
+export const ACTIVE_TAB_ARCHIVED = 'archived';
+
+export const GROUPS_LIST_HOLDER_CLASS = '.js-groups-list-holder';
+export const GROUPS_FILTER_FORM_CLASS = '.js-group-filter-form';
+export const CONTENT_LIST_CLASS = '.content-list';
+
export const COMMON_STR = {
FAILURE: __('An error occurred. Please try again.'),
- LEAVE_FORBIDDEN: s__('GroupsTree|Failed to leave the group. Please make sure you are not the only owner.'),
+ LEAVE_FORBIDDEN: s__(
+ 'GroupsTree|Failed to leave the group. Please make sure you are not the only owner.',
+ ),
LEAVE_BTN_TITLE: s__('GroupsTree|Leave this group'),
EDIT_BTN_TITLE: s__('GroupsTree|Edit group'),
- GROUP_SEARCH_EMPTY: s__('GroupsTree|Sorry, no groups matched your search'),
- GROUP_PROJECT_SEARCH_EMPTY: s__('GroupsTree|Sorry, no groups or projects matched your search'),
+ GROUP_SEARCH_EMPTY: s__('GroupsTree|No groups matched your search'),
+ GROUP_PROJECT_SEARCH_EMPTY: s__('GroupsTree|No groups or projects matched your search'),
};
export const ITEM_TYPE = {
@@ -17,8 +27,12 @@ export const ITEM_TYPE = {
};
export const GROUP_VISIBILITY_TYPE = {
- public: __('Public - The group and any public projects can be viewed without any authentication.'),
- internal: __('Internal - The group and any internal projects can be viewed by any logged in user.'),
+ public: __(
+ 'Public - The group and any public projects can be viewed without any authentication.',
+ ),
+ internal: __(
+ 'Internal - The group and any internal projects can be viewed by any logged in user.',
+ ),
private: __('Private - The group and its projects can only be viewed by members.'),
};
diff --git a/app/assets/javascripts/groups/groups_filterable_list.js b/app/assets/javascripts/groups/groups_filterable_list.js
index e6db1746487..693519729ac 100644
--- a/app/assets/javascripts/groups/groups_filterable_list.js
+++ b/app/assets/javascripts/groups/groups_filterable_list.js
@@ -4,13 +4,23 @@ import eventHub from './event_hub';
import { normalizeHeaders, getParameterByName } from '../lib/utils/common_utils';
export default class GroupFilterableList extends FilterableList {
- constructor({ form, filter, holder, filterEndpoint, pagePath, dropdownSel, filterInputField }) {
+ constructor({
+ form,
+ filter,
+ holder,
+ filterEndpoint,
+ pagePath,
+ dropdownSel,
+ filterInputField,
+ action,
+ }) {
super(form, filter, holder, filterInputField);
this.form = form;
this.filterEndpoint = filterEndpoint;
this.pagePath = pagePath;
this.filterInputField = filterInputField;
this.$dropdown = $(dropdownSel);
+ this.action = action;
}
getFilterEndpoint() {
@@ -20,15 +30,16 @@ export default class GroupFilterableList extends FilterableList {
getPagePath(queryData) {
const params = queryData ? $.param(queryData) : '';
const queryString = params ? `?${params}` : '';
- return `${this.pagePath}${queryString}`;
+ const path = this.pagePath || window.location.pathname;
+ return `${path}${queryString}`;
}
bindEvents() {
super.bindEvents();
- this.onFilterOptionClikWrapper = this.onOptionClick.bind(this);
+ this.onFilterOptionClickWrapper = this.onOptionClick.bind(this);
- this.$dropdown.on('click', 'a', this.onFilterOptionClikWrapper);
+ this.$dropdown.on('click', 'a', this.onFilterOptionClickWrapper);
}
onFilterInput() {
@@ -53,7 +64,12 @@ export default class GroupFilterableList extends FilterableList {
}
setDefaultFilterOption() {
- const defaultOption = $.trim(this.$dropdown.find('.dropdown-menu li.js-filter-sort-order a').first().text());
+ const defaultOption = $.trim(
+ this.$dropdown
+ .find('.dropdown-menu li.js-filter-sort-order a')
+ .first()
+ .text(),
+ );
this.$dropdown.find('.dropdown-label').text(defaultOption);
}
@@ -65,11 +81,19 @@ export default class GroupFilterableList extends FilterableList {
// Get type of option selected from dropdown
const currentTargetClassList = e.currentTarget.parentElement.classList;
const isOptionFilterBySort = currentTargetClassList.contains('js-filter-sort-order');
- const isOptionFilterByArchivedProjects = currentTargetClassList.contains('js-filter-archived-projects');
+ const isOptionFilterByArchivedProjects = currentTargetClassList.contains(
+ 'js-filter-archived-projects',
+ );
// Get option query param, also preserve currently applied query param
- const sortParam = getParameterByName('sort', isOptionFilterBySort ? e.currentTarget.href : window.location.href);
- const archivedParam = getParameterByName('archived', isOptionFilterByArchivedProjects ? e.currentTarget.href : window.location.href);
+ const sortParam = getParameterByName(
+ 'sort',
+ isOptionFilterBySort ? e.currentTarget.href : window.location.href,
+ );
+ const archivedParam = getParameterByName(
+ 'archived',
+ isOptionFilterByArchivedProjects ? e.currentTarget.href : window.location.href,
+ );
if (sortParam) {
queryData.sort = sortParam;
@@ -86,7 +110,9 @@ export default class GroupFilterableList extends FilterableList {
this.$dropdown.find('.dropdown-label').text($.trim(e.currentTarget.text));
this.$dropdown.find('.dropdown-menu li.js-filter-sort-order a').removeClass('is-active');
} else if (isOptionFilterByArchivedProjects) {
- this.$dropdown.find('.dropdown-menu li.js-filter-archived-projects a').removeClass('is-active');
+ this.$dropdown
+ .find('.dropdown-menu li.js-filter-archived-projects a')
+ .removeClass('is-active');
}
$(e.target).addClass('is-active');
@@ -98,11 +124,19 @@ export default class GroupFilterableList extends FilterableList {
onFilterSuccess(res, queryData) {
const currentPath = this.getPagePath(queryData);
- window.history.replaceState({
- page: currentPath,
- }, document.title, currentPath);
-
- eventHub.$emit('updateGroups', res.data, Object.prototype.hasOwnProperty.call(queryData, this.filterInputField));
- eventHub.$emit('updatePagination', normalizeHeaders(res.headers));
+ window.history.replaceState(
+ {
+ page: currentPath,
+ },
+ document.title,
+ currentPath,
+ );
+
+ eventHub.$emit(
+ `${this.action}updateGroups`,
+ res.data,
+ Object.prototype.hasOwnProperty.call(queryData, this.filterInputField),
+ );
+ eventHub.$emit(`${this.action}updatePagination`, normalizeHeaders(res.headers));
}
}
diff --git a/app/assets/javascripts/groups/index.js b/app/assets/javascripts/groups/index.js
index 83a9008a94b..0f68f05b523 100644
--- a/app/assets/javascripts/groups/index.js
+++ b/app/assets/javascripts/groups/index.js
@@ -7,18 +7,26 @@ import GroupsService from './service/groups_service';
import groupsApp from './components/app.vue';
import groupFolderComponent from './components/group_folder.vue';
import groupItemComponent from './components/group_item.vue';
+import { GROUPS_LIST_HOLDER_CLASS, CONTENT_LIST_CLASS } from './constants';
Vue.use(Translate);
-export default () => {
- const el = document.getElementById('js-groups-tree');
+export default (containerId = 'js-groups-tree', endpoint, action = '') => {
+ const containerEl = document.getElementById(containerId);
+ let dataEl;
// Don't do anything if element doesn't exist (No groups)
// This is for when the user enters directly to the page via URL
- if (!el) {
+ if (!containerEl) {
return;
}
+ const el = action ? containerEl.querySelector(GROUPS_LIST_HOLDER_CLASS) : containerEl;
+
+ if (action) {
+ dataEl = containerEl.querySelector(CONTENT_LIST_CLASS);
+ }
+
Vue.component('group-folder', groupFolderComponent);
Vue.component('group-item', groupItemComponent);
@@ -29,20 +37,26 @@ export default () => {
groupsApp,
},
data() {
- const { dataset } = this.$options.el;
+ const { dataset } = dataEl || this.$options.el;
const hideProjects = dataset.hideProjects === 'true';
+ const service = new GroupsService(endpoint || dataset.endpoint);
const store = new GroupsStore(hideProjects);
- const service = new GroupsService(dataset.endpoint);
return {
+ action,
store,
service,
hideProjects,
loading: true,
+ containerId,
};
},
beforeMount() {
- const { dataset } = this.$options.el;
+ if (this.action) {
+ return;
+ }
+
+ const { dataset } = dataEl || this.$options.el;
let groupFilterList = null;
const form = document.querySelector(dataset.formSel);
const filter = document.querySelector(dataset.filterSel);
@@ -52,10 +66,11 @@ export default () => {
form,
filter,
holder,
- filterEndpoint: dataset.endpoint,
+ filterEndpoint: endpoint || dataset.endpoint,
pagePath: dataset.path,
dropdownSel: dataset.dropdownSel,
filterInputField: 'filter',
+ action: this.action,
};
groupFilterList = new GroupFilterableList(opts);
@@ -64,9 +79,11 @@ export default () => {
render(createElement) {
return createElement('groups-app', {
props: {
+ action: this.action,
store: this.store,
service: this.service,
hideProjects: this.hideProjects,
+ containerId: this.containerId,
},
});
},
diff --git a/app/assets/javascripts/ide/components/branches/search_list.vue b/app/assets/javascripts/ide/components/branches/search_list.vue
index 6db7b9d6b0e..52ccc537c9d 100644
--- a/app/assets/javascripts/ide/components/branches/search_list.vue
+++ b/app/assets/javascripts/ide/components/branches/search_list.vue
@@ -1,13 +1,11 @@
<script>
import { mapActions, mapState } from 'vuex';
import _ from 'underscore';
-import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
import Icon from '~/vue_shared/components/icon.vue';
import Item from './item.vue';
export default {
components: {
- LoadingIcon,
Item,
Icon,
},
@@ -62,8 +60,8 @@ export default {
<div class="position-relative">
<input
ref="searchInput"
- :placeholder="__('Search branches')"
v-model="search"
+ :placeholder="__('Search branches')"
type="search"
class="form-control dropdown-input-field"
@input="searchBranches"
@@ -76,10 +74,10 @@ export default {
</div>
</div>
<div class="dropdown-content ide-merge-requests-dropdown-content d-flex">
- <loading-icon
+ <gl-loading-icon
v-if="isLoading"
+ :size="2"
class="mt-3 mb-3 align-self-center ml-auto mr-auto"
- size="2"
/>
<ul
v-else
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/editor_header.vue b/app/assets/javascripts/ide/components/commit_sidebar/editor_header.vue
new file mode 100644
index 00000000000..c3ca147e850
--- /dev/null
+++ b/app/assets/javascripts/ide/components/commit_sidebar/editor_header.vue
@@ -0,0 +1,78 @@
+<script>
+import $ from 'jquery';
+import { mapActions } from 'vuex';
+import { __ } from '~/locale';
+import FileIcon from '~/vue_shared/components/file_icon.vue';
+import ChangedFileIcon from '../changed_file_icon.vue';
+
+export default {
+ components: {
+ FileIcon,
+ ChangedFileIcon,
+ },
+ props: {
+ activeFile: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ activeButtonText() {
+ return this.activeFile.staged ? __('Unstage') : __('Stage');
+ },
+ isStaged() {
+ return !this.activeFile.changed && this.activeFile.staged;
+ },
+ },
+ methods: {
+ ...mapActions(['stageChange', 'unstageChange']),
+ actionButtonClicked() {
+ if (this.activeFile.staged) {
+ this.unstageChange(this.activeFile.path);
+ } else {
+ this.stageChange(this.activeFile.path);
+ }
+ },
+ showDiscardModal() {
+ $(document.getElementById(`discard-file-${this.activeFile.path}`)).modal('show');
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="d-flex ide-commit-editor-header align-items-center">
+ <file-icon
+ :file-name="activeFile.name"
+ :size="16"
+ class="mr-2"
+ />
+ <strong class="mr-2">
+ {{ activeFile.path }}
+ </strong>
+ <changed-file-icon
+ :file="activeFile"
+ />
+ <div class="ml-auto">
+ <button
+ v-if="!isStaged"
+ type="button"
+ class="btn btn-remove btn-inverted append-right-8"
+ @click="showDiscardModal"
+ >
+ {{ __('Discard') }}
+ </button>
+ <button
+ :class="{
+ 'btn-success': !isStaged,
+ 'btn-warning': isStaged
+ }"
+ type="button"
+ class="btn btn-inverted"
+ @click="actionButtonClicked"
+ >
+ {{ activeButtonText }}
+ </button>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list.vue b/app/assets/javascripts/ide/components/commit_sidebar/list.vue
index d0fb0e3d99e..3e3539e364b 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/list.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/list.vue
@@ -1,7 +1,9 @@
<script>
+import $ from 'jquery';
import { mapActions } from 'vuex';
import { __, sprintf } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
+import GlModal from '~/vue_shared/components/gl_modal.vue';
import tooltip from '~/vue_shared/directives/tooltip';
import ListItem from './list_item.vue';
@@ -9,6 +11,7 @@ export default {
components: {
Icon,
ListItem,
+ GlModal,
},
directives: {
tooltip,
@@ -56,6 +59,11 @@ export default {
type: String,
required: true,
},
+ emptyStateText: {
+ type: String,
+ required: false,
+ default: __('No changes'),
+ },
},
computed: {
titleText() {
@@ -68,11 +76,19 @@ export default {
},
},
methods: {
- ...mapActions(['stageAllChanges', 'unstageAllChanges']),
+ ...mapActions(['stageAllChanges', 'unstageAllChanges', 'discardAllChanges']),
actionBtnClicked() {
this[this.action]();
+
+ $(this.$refs.actionBtn).tooltip('hide');
+ },
+ openDiscardModal() {
+ $('#discard-all-changes').modal('show');
},
},
+ discardModalText: __(
+ "You will loose all the unstaged changes you've made in this project. This action cannot be undone.",
+ ),
};
</script>
@@ -81,27 +97,32 @@ export default {
class="ide-commit-list-container"
>
<header
- class="multi-file-commit-panel-header"
+ class="multi-file-commit-panel-header d-flex mb-0"
>
<div
- class="multi-file-commit-panel-header-title"
+ class="d-flex align-items-center flex-fill"
>
<icon
v-once
:name="iconName"
:size="18"
+ class="append-right-8"
/>
- {{ titleText }}
+ <strong>
+ {{ titleText }}
+ </strong>
<div class="d-flex ml-auto">
<button
+ ref="actionBtn"
v-tooltip
- v-show="filesLength"
+ :title="actionBtnText"
+ :aria-label="actionBtnText"
+ :disabled="!filesLength"
:class="{
- 'd-flex': filesLength
+ 'disabled-content': !filesLength
}"
- :title="actionBtnText"
type="button"
- class="btn btn-default ide-staged-action-btn p-0 order-1 align-items-center"
+ class="d-flex ide-staged-action-btn p-0 border-0 align-items-center"
data-placement="bottom"
data-container="body"
data-boundary="viewport"
@@ -109,18 +130,32 @@ export default {
>
<icon
:name="actionBtnIcon"
- :size="12"
+ :size="16"
class="ml-auto mr-auto"
/>
</button>
- <span
+ <button
+ v-if="!stagedList"
+ v-tooltip
+ :title="__('Discard all changes')"
+ :aria-label="__('Discard all changes')"
+ :disabled="!filesLength"
:class="{
- 'rounded-right': !filesLength
+ 'disabled-content': !filesLength
}"
- class="ide-commit-file-count order-0 rounded-left text-center"
+ type="button"
+ class="d-flex ide-staged-action-btn p-0 border-0 align-items-center"
+ data-placement="bottom"
+ data-container="body"
+ data-boundary="viewport"
+ @click="openDiscardModal"
>
- {{ filesLength }}
- </span>
+ <icon
+ :size="16"
+ name="remove-all"
+ class="ml-auto mr-auto"
+ />
+ </button>
</div>
</div>
</header>
@@ -143,9 +178,19 @@ export default {
</ul>
<p
v-else
- class="multi-file-commit-list form-text text-muted"
+ class="multi-file-commit-list form-text text-muted text-center"
>
- {{ __('No changes') }}
+ {{ emptyStateText }}
</p>
+ <gl-modal
+ v-if="!stagedList"
+ id="discard-all-changes"
+ :footer-primary-button-text="__('Discard all changes')"
+ :header-title-text="__('Discard all unstaged changes?')"
+ footer-primary-button-variant="danger"
+ @submit="discardAllChanges"
+ >
+ {{ $options.discardModalText }}
+ </gl-modal>
</div>
</template>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue b/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue
index 391004dcd3c..10c78a80302 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue
@@ -2,6 +2,7 @@
import { mapActions } from 'vuex';
import tooltip from '~/vue_shared/directives/tooltip';
import Icon from '~/vue_shared/components/icon.vue';
+import FileIcon from '~/vue_shared/components/file_icon.vue';
import StageButton from './stage_button.vue';
import UnstageButton from './unstage_button.vue';
import { viewerTypes } from '../../constants';
@@ -12,6 +13,7 @@ export default {
Icon,
StageButton,
UnstageButton,
+ FileIcon,
},
directives: {
tooltip,
@@ -48,7 +50,7 @@ export default {
return `${getCommitIconMap(this.file).icon}${suffix}`;
},
iconClass() {
- return `${getCommitIconMap(this.file).class} append-right-8`;
+ return `${getCommitIconMap(this.file).class} ml-auto mr-auto`;
},
fullKey() {
return `${this.keyPrefix}-${this.file.key}`;
@@ -105,17 +107,24 @@ export default {
@click="openFileInEditor"
>
<span class="multi-file-commit-list-file-path d-flex align-items-center">
- <icon
- :name="iconName"
- :size="16"
- :css-classes="iconClass"
+ <file-icon
+ :file-name="file.name"
+ class="append-right-8"
/>{{ file.name }}
</span>
+ <div class="ml-auto d-flex align-items-center">
+ <div class="d-flex align-items-center ide-commit-list-changed-icon">
+ <icon
+ :name="iconName"
+ :size="16"
+ :css-classes="iconClass"
+ />
+ </div>
+ <component
+ :is="actionComponent"
+ :path="file.path"
+ />
+ </div>
</div>
- <component
- :is="actionComponent"
- :path="file.path"
- class="d-flex position-absolute"
- />
</div>
</template>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue b/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue
index e6044401c9f..8a1836a5c92 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue
@@ -1,11 +1,15 @@
<script>
+import $ from 'jquery';
import { mapActions } from 'vuex';
+import { sprintf, __ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
import tooltip from '~/vue_shared/directives/tooltip';
+import GlModal from '~/vue_shared/components/gl_modal.vue';
export default {
components: {
Icon,
+ GlModal,
},
directives: {
tooltip,
@@ -16,8 +20,22 @@ export default {
required: true,
},
},
+ computed: {
+ modalId() {
+ return `discard-file-${this.path}`;
+ },
+ modalTitle() {
+ return sprintf(
+ __('Discard changes to %{path}?'),
+ { path: this.path },
+ );
+ },
+ },
methods: {
...mapActions(['stageChange', 'discardFileChanges']),
+ showDiscardModal() {
+ $(document.getElementById(this.modalId)).modal('show');
+ },
},
};
</script>
@@ -25,51 +43,50 @@ export default {
<template>
<div
v-once
- class="multi-file-discard-btn dropdown"
+ class="multi-file-discard-btn d-flex"
>
<button
v-tooltip
:aria-label="__('Stage changes')"
:title="__('Stage changes')"
type="button"
- class="btn btn-blank append-right-5 d-flex align-items-center"
+ class="btn btn-blank align-items-center"
data-container="body"
data-boundary="viewport"
data-placement="bottom"
- @click.stop="stageChange(path)"
+ @click.stop.prevent="stageChange(path)"
>
<icon
- :size="12"
+ :size="16"
name="mobile-issue-close"
+ class="ml-auto mr-auto"
/>
</button>
<button
v-tooltip
- :title="__('More actions')"
+ :aria-label="__('Discard changes')"
+ :title="__('Discard changes')"
type="button"
- class="btn btn-blank d-flex align-items-center"
+ class="btn btn-blank align-items-center"
data-container="body"
data-boundary="viewport"
data-placement="bottom"
- data-toggle="dropdown"
- data-display="static"
+ @click.stop.prevent="showDiscardModal"
>
<icon
- :size="12"
- name="ellipsis_h"
+ :size="16"
+ name="remove"
+ class="ml-auto mr-auto"
/>
</button>
- <div class="dropdown-menu dropdown-menu-right">
- <ul>
- <li>
- <button
- type="button"
- @click.stop="discardFileChanges(path)"
- >
- {{ __('Discard changes') }}
- </button>
- </li>
- </ul>
- </div>
+ <gl-modal
+ :id="modalId"
+ :header-title-text="modalTitle"
+ :footer-primary-button-text="__('Discard changes')"
+ footer-primary-button-variant="danger"
+ @submit="discardFileChanges(path)"
+ >
+ {{ __("You will loose all changes you've made to this file. This action cannot be undone.") }}
+ </gl-modal>
</div>
</template>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/unstage_button.vue b/app/assets/javascripts/ide/components/commit_sidebar/unstage_button.vue
index 9cec73ec00e..86c40602074 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/unstage_button.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/unstage_button.vue
@@ -25,22 +25,23 @@ export default {
<template>
<div
v-once
- class="multi-file-discard-btn"
+ class="multi-file-discard-btn d-flex"
>
<button
v-tooltip
:aria-label="__('Unstage changes')"
:title="__('Unstage changes')"
type="button"
- class="btn btn-blank d-flex align-items-center"
+ class="btn btn-blank align-items-center"
data-container="body"
data-boundary="viewport"
data-placement="bottom"
- @click="unstageChange(path)"
+ @click.stop.prevent="unstageChange(path)"
>
<icon
- :size="12"
- name="history"
+ :size="16"
+ name="redo"
+ class="ml-auto mr-auto"
/>
</button>
</div>
diff --git a/app/assets/javascripts/ide/components/error_message.vue b/app/assets/javascripts/ide/components/error_message.vue
index acbc98b7a7b..a20dc0a7006 100644
--- a/app/assets/javascripts/ide/components/error_message.vue
+++ b/app/assets/javascripts/ide/components/error_message.vue
@@ -1,11 +1,7 @@
<script>
import { mapActions } from 'vuex';
-import LoadingIcon from '../../vue_shared/components/loading_icon.vue';
export default {
- components: {
- LoadingIcon,
- },
props: {
message: {
type: Object,
@@ -59,7 +55,7 @@ export default {
@click.stop.prevent="clickAction"
>
{{ message.actionText }}
- <loading-icon
+ <gl-loading-icon
v-show="isLoading"
inline
/>
diff --git a/app/assets/javascripts/ide/components/file_finder/index.vue b/app/assets/javascripts/ide/components/file_finder/index.vue
index 0ba33053717..760ed8654ee 100644
--- a/app/assets/javascripts/ide/components/file_finder/index.vue
+++ b/app/assets/javascripts/ide/components/file_finder/index.vue
@@ -174,8 +174,8 @@ export default {
<div class="dropdown-input">
<input
ref="searchInput"
- :placeholder="__('Search files')"
v-model="searchText"
+ :placeholder="__('Search files')"
type="search"
class="dropdown-input-field"
autocomplete="off"
diff --git a/app/assets/javascripts/ide/components/file_finder/item.vue b/app/assets/javascripts/ide/components/file_finder/item.vue
index f5252ce7706..a612739d641 100644
--- a/app/assets/javascripts/ide/components/file_finder/item.vue
+++ b/app/assets/javascripts/ide/components/file_finder/item.vue
@@ -78,10 +78,10 @@ export default {
class="diff-changed-file-name"
>
<span
- v-for="(char, index) in file.name.split('')"
- :key="index + char"
+ v-for="(char, charIndex) in file.name.split('')"
+ :key="charIndex + char"
:class="{
- highlighted: nameSearchTextOccurences.indexOf(index) >= 0,
+ highlighted: nameSearchTextOccurences.indexOf(charIndex) >= 0,
}"
v-text="char"
>
@@ -91,10 +91,10 @@ export default {
class="diff-changed-file-path prepend-top-5"
>
<span
- v-for="(char, index) in pathWithEllipsis.split('')"
- :key="index + char"
+ v-for="(char, charIndex) in pathWithEllipsis.split('')"
+ :key="charIndex + char"
:class="{
- highlighted: pathSearchTextOccurences.indexOf(index) >= 0,
+ highlighted: pathSearchTextOccurences.indexOf(charIndex) >= 0,
}"
v-text="char"
>
diff --git a/app/assets/javascripts/ide/components/file_row_extra.vue b/app/assets/javascripts/ide/components/file_row_extra.vue
new file mode 100644
index 00000000000..44a360ab909
--- /dev/null
+++ b/app/assets/javascripts/ide/components/file_row_extra.vue
@@ -0,0 +1,104 @@
+<script>
+import { mapGetters } from 'vuex';
+import { n__, __, sprintf } from '~/locale';
+import tooltip from '~/vue_shared/directives/tooltip';
+import Icon from '~/vue_shared/components/icon.vue';
+import NewDropdown from './new_dropdown/index.vue';
+import ChangedFileIcon from './changed_file_icon.vue';
+import MrFileIcon from './mr_file_icon.vue';
+
+export default {
+ name: 'FileRowExtra',
+ directives: {
+ tooltip,
+ },
+ components: {
+ Icon,
+ NewDropdown,
+ ChangedFileIcon,
+ MrFileIcon,
+ },
+ props: {
+ file: {
+ type: Object,
+ required: true,
+ },
+ mouseOver: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ computed: {
+ ...mapGetters([
+ 'getChangesInFolder',
+ 'getUnstagedFilesCountForPath',
+ 'getStagedFilesCountForPath',
+ ]),
+ folderUnstagedCount() {
+ return this.getUnstagedFilesCountForPath(this.file.path);
+ },
+ folderStagedCount() {
+ return this.getStagedFilesCountForPath(this.file.path);
+ },
+ changesCount() {
+ return this.getChangesInFolder(this.file.path);
+ },
+ folderChangesTooltip() {
+ if (this.changesCount === 0) return undefined;
+
+ if (this.folderUnstagedCount > 0 && this.folderStagedCount === 0) {
+ return n__('%d unstaged change', '%d unstaged changes', this.folderUnstagedCount);
+ } else if (this.folderUnstagedCount === 0 && this.folderStagedCount > 0) {
+ return n__('%d staged change', '%d staged changes', this.folderStagedCount);
+ }
+
+ return sprintf(__('%{unstaged} unstaged and %{staged} staged changes'), {
+ unstaged: this.folderUnstagedCount,
+ staged: this.folderStagedCount,
+ });
+ },
+ showTreeChangesCount() {
+ return this.file.type === 'tree' && this.changesCount > 0 && !this.file.opened;
+ },
+ showChangedFileIcon() {
+ return this.file.changed || this.file.tempFile || this.file.staged;
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="float-right ide-file-icon-holder">
+ <mr-file-icon
+ v-if="file.mrChange"
+ />
+ <span
+ v-if="showTreeChangesCount"
+ class="ide-tree-changes"
+ >
+ {{ changesCount }}
+ <icon
+ v-tooltip
+ :title="folderChangesTooltip"
+ :size="12"
+ data-container="body"
+ data-placement="right"
+ name="file-modified"
+ css-classes="prepend-left-5 ide-file-modified"
+ />
+ </span>
+ <changed-file-icon
+ v-else-if="showChangedFileIcon"
+ :file="file"
+ :show-tooltip="true"
+ :show-staged-icon="true"
+ :force-modified-icon="true"
+ />
+ <new-dropdown
+ :type="file.type"
+ :path="file.path"
+ :mouse-over="mouseOver"
+ class="prepend-left-8"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/ide/components/file_templates/bar.vue b/app/assets/javascripts/ide/components/file_templates/bar.vue
new file mode 100644
index 00000000000..23be5f45f16
--- /dev/null
+++ b/app/assets/javascripts/ide/components/file_templates/bar.vue
@@ -0,0 +1,80 @@
+<script>
+import { mapActions, mapGetters, mapState } from 'vuex';
+import Dropdown from './dropdown.vue';
+
+export default {
+ components: {
+ Dropdown,
+ },
+ computed: {
+ ...mapGetters(['activeFile']),
+ ...mapGetters('fileTemplates', ['templateTypes']),
+ ...mapState('fileTemplates', ['selectedTemplateType', 'updateSuccess']),
+ showTemplatesDropdown() {
+ return Object.keys(this.selectedTemplateType).length > 0;
+ },
+ },
+ watch: {
+ activeFile: 'setInitialType',
+ },
+ mounted() {
+ this.setInitialType();
+ },
+ methods: {
+ ...mapActions('fileTemplates', [
+ 'setSelectedTemplateType',
+ 'fetchTemplate',
+ 'undoFileTemplate',
+ ]),
+ setInitialType() {
+ const initialTemplateType = this.templateTypes.find(t => t.name === this.activeFile.name);
+
+ if (initialTemplateType) {
+ this.setSelectedTemplateType(initialTemplateType);
+ }
+ },
+ selectTemplateType(templateType) {
+ this.setSelectedTemplateType(templateType);
+ },
+ selectTemplate(template) {
+ this.fetchTemplate(template);
+ },
+ undo() {
+ this.undoFileTemplate();
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="d-flex align-items-center ide-file-templates">
+ <strong class="append-right-default">
+ {{ __('File templates') }}
+ </strong>
+ <dropdown
+ :data="templateTypes"
+ :label="selectedTemplateType.name || __('Choose a type...')"
+ class="mr-2"
+ @click="selectTemplateType"
+ />
+ <dropdown
+ v-if="showTemplatesDropdown"
+ :label="__('Choose a template...')"
+ :is-async-data="true"
+ :searchable="true"
+ :title="__('File templates')"
+ class="mr-2"
+ @click="selectTemplate"
+ />
+ <transition name="fade">
+ <button
+ v-show="updateSuccess"
+ type="button"
+ class="btn btn-default"
+ @click="undo"
+ >
+ {{ __('Undo') }}
+ </button>
+ </transition>
+ </div>
+</template>
diff --git a/app/assets/javascripts/ide/components/file_templates/dropdown.vue b/app/assets/javascripts/ide/components/file_templates/dropdown.vue
new file mode 100644
index 00000000000..ef1f6de3a86
--- /dev/null
+++ b/app/assets/javascripts/ide/components/file_templates/dropdown.vue
@@ -0,0 +1,123 @@
+<script>
+import $ from 'jquery';
+import { mapActions, mapState } from 'vuex';
+import DropdownButton from '~/vue_shared/components/dropdown/dropdown_button.vue';
+
+export default {
+ components: {
+ DropdownButton,
+ },
+ props: {
+ data: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ label: {
+ type: String,
+ required: true,
+ },
+ title: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ isAsyncData: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ searchable: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ data() {
+ return {
+ search: '',
+ };
+ },
+ computed: {
+ ...mapState('fileTemplates', ['templates', 'isLoading']),
+ outputData() {
+ return (this.isAsyncData ? this.templates : this.data).filter(t => {
+ if (!this.searchable) return true;
+
+ return t.name.toLowerCase().indexOf(this.search.toLowerCase()) >= 0;
+ });
+ },
+ showLoading() {
+ return this.isAsyncData ? this.isLoading : false;
+ },
+ },
+ mounted() {
+ $(this.$el).on('show.bs.dropdown', this.fetchTemplatesIfAsync);
+ },
+ beforeDestroy() {
+ $(this.$el).off('show.bs.dropdown', this.fetchTemplatesIfAsync);
+ },
+ methods: {
+ ...mapActions('fileTemplates', ['fetchTemplateTypes']),
+ fetchTemplatesIfAsync() {
+ if (this.isAsyncData) {
+ this.fetchTemplateTypes();
+ }
+ },
+ clickItem(item) {
+ this.$emit('click', item);
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="dropdown">
+ <dropdown-button
+ :toggle-text="label"
+ data-display="static"
+ />
+ <div class="dropdown-menu pb-0">
+ <div
+ v-if="title"
+ class="dropdown-title ml-0 mr-0"
+ >
+ {{ title }}
+ </div>
+ <div
+ v-if="!showLoading && searchable"
+ class="dropdown-input"
+ >
+ <input
+ v-model="search"
+ :placeholder="__('Filter...')"
+ type="search"
+ class="dropdown-input-field"
+ />
+ <i
+ aria-hidden="true"
+ class="fa fa-search dropdown-input-search"
+ ></i>
+ </div>
+ <div class="dropdown-content">
+ <gl-loading-icon
+ v-if="showLoading"
+ :size="2"
+ />
+ <ul v-else>
+ <li
+ v-for="(item, index) in outputData"
+ :key="index"
+ >
+ <button
+ type="button"
+ @click="clickItem(item)"
+ >
+ {{ item.name }}
+ </button>
+ </li>
+ </ul>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/ide/components/ide.vue b/app/assets/javascripts/ide/components/ide.vue
index 6a5ab35a16a..a3add3b778f 100644
--- a/app/assets/javascripts/ide/components/ide.vue
+++ b/app/assets/javascripts/ide/components/ide.vue
@@ -10,6 +10,7 @@ import RepoEditor from './repo_editor.vue';
import FindFile from './file_finder/index.vue';
import RightPane from './panes/right.vue';
import ErrorMessage from './error_message.vue';
+import CommitEditorHeader from './commit_sidebar/editor_header.vue';
const originalStopCallback = Mousetrap.stopCallback;
@@ -23,6 +24,7 @@ export default {
FindFile,
RightPane,
ErrorMessage,
+ CommitEditorHeader,
},
computed: {
...mapState([
@@ -34,7 +36,7 @@ export default {
'currentProjectId',
'errorMessage',
]),
- ...mapGetters(['activeFile', 'hasChanges', 'someUncommitedChanges']),
+ ...mapGetters(['activeFile', 'hasChanges', 'someUncommitedChanges', 'isCommitModeActive']),
},
mounted() {
window.onbeforeunload = e => this.onBeforeUnload(e);
@@ -96,7 +98,12 @@ export default {
<template
v-if="activeFile"
>
+ <commit-editor-header
+ v-if="isCommitModeActive"
+ :active-file="activeFile"
+ />
<repo-tabs
+ v-else
:active-file="activeFile"
:files="openFiles"
:viewer="viewer"
diff --git a/app/assets/javascripts/ide/components/ide_tree_list.vue b/app/assets/javascripts/ide/components/ide_tree_list.vue
index 00ae5ea2c15..e658d1bf956 100644
--- a/app/assets/javascripts/ide/components/ide_tree_list.vue
+++ b/app/assets/javascripts/ide/components/ide_tree_list.vue
@@ -2,15 +2,16 @@
import { mapActions, mapGetters, mapState } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue';
import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
-import RepoFile from './repo_file.vue';
+import FileRow from '~/vue_shared/components/file_row.vue';
import NavDropdown from './nav_dropdown.vue';
+import FileRowExtra from './file_row_extra.vue';
export default {
components: {
Icon,
- RepoFile,
SkeletonLoadingContainer,
NavDropdown,
+ FileRow,
},
props: {
viewerType: {
@@ -34,8 +35,9 @@ export default {
this.updateViewer(this.viewerType);
},
methods: {
- ...mapActions(['updateViewer']),
+ ...mapActions(['updateViewer', 'toggleTreeOpen']),
},
+ FileRowExtra,
};
</script>
@@ -63,11 +65,13 @@ export default {
<div
class="ide-tree-body h-100"
>
- <repo-file
+ <file-row
v-for="file in currentTree.tree"
:key="file.key"
:file="file"
:level="0"
+ :extra-component="$options.FileRowExtra"
+ @toggleTreeOpen="toggleTreeOpen"
/>
</div>
</template>
diff --git a/app/assets/javascripts/ide/components/jobs/list.vue b/app/assets/javascripts/ide/components/jobs/list.vue
index 3b16b860ecd..acd37605d16 100644
--- a/app/assets/javascripts/ide/components/jobs/list.vue
+++ b/app/assets/javascripts/ide/components/jobs/list.vue
@@ -1,11 +1,9 @@
<script>
import { mapActions } from 'vuex';
-import LoadingIcon from '../../../vue_shared/components/loading_icon.vue';
import Stage from './stage.vue';
export default {
components: {
- LoadingIcon,
Stage,
},
props: {
@@ -26,10 +24,10 @@ export default {
<template>
<div>
- <loading-icon
+ <gl-loading-icon
v-if="loading && !stages.length"
+ :size="2"
class="prepend-top-default"
- size="2"
/>
<template v-else>
<stage
diff --git a/app/assets/javascripts/ide/components/jobs/stage.vue b/app/assets/javascripts/ide/components/jobs/stage.vue
index 15e881b7bc8..ec168d36b9e 100644
--- a/app/assets/javascripts/ide/components/jobs/stage.vue
+++ b/app/assets/javascripts/ide/components/jobs/stage.vue
@@ -2,7 +2,6 @@
import tooltip from '../../../vue_shared/directives/tooltip';
import Icon from '../../../vue_shared/components/icon.vue';
import CiIcon from '../../../vue_shared/components/ci_icon.vue';
-import LoadingIcon from '../../../vue_shared/components/loading_icon.vue';
import Item from './item.vue';
export default {
@@ -12,7 +11,6 @@ export default {
components: {
Icon,
CiIcon,
- LoadingIcon,
Item,
},
props: {
@@ -71,8 +69,8 @@ export default {
:size="24"
/>
<strong
- v-tooltip="showTooltip"
ref="stageTitle"
+ v-tooltip="showTooltip"
:title="showTooltip ? stage.name : null"
data-container="body"
class="prepend-left-8 ide-stage-title"
@@ -96,7 +94,7 @@ export default {
v-show="!stage.isCollapsed"
class="card-body"
>
- <loading-icon
+ <gl-loading-icon
v-if="showLoadingIcon"
/>
<template v-else>
diff --git a/app/assets/javascripts/ide/components/merge_requests/list.vue b/app/assets/javascripts/ide/components/merge_requests/list.vue
index fc612956688..c8343e77860 100644
--- a/app/assets/javascripts/ide/components/merge_requests/list.vue
+++ b/app/assets/javascripts/ide/components/merge_requests/list.vue
@@ -3,7 +3,6 @@ import { mapActions, mapState } from 'vuex';
import _ from 'underscore';
import { __ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
-import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
import Item from './item.vue';
import TokenedInput from '../shared/tokened_input.vue';
@@ -14,7 +13,6 @@ const SEARCH_TYPES = [
export default {
components: {
- LoadingIcon,
TokenedInput,
Item,
Icon,
@@ -98,10 +96,10 @@ export default {
</div>
</div>
<div class="dropdown-content ide-merge-requests-dropdown-content d-flex">
- <loading-icon
+ <gl-loading-icon
v-if="isLoading"
+ :size="2"
class="mt-3 mb-3 align-self-center ml-auto mr-auto"
- size="2"
/>
<template v-else>
<ul
diff --git a/app/assets/javascripts/ide/components/new_dropdown/modal.vue b/app/assets/javascripts/ide/components/new_dropdown/modal.vue
index e500ef0e1b5..bcd53ac1ba2 100644
--- a/app/assets/javascripts/ide/components/new_dropdown/modal.vue
+++ b/app/assets/javascripts/ide/components/new_dropdown/modal.vue
@@ -1,6 +1,7 @@
<script>
+import $ from 'jquery';
import { __ } from '~/locale';
-import { mapActions, mapState } from 'vuex';
+import { mapActions, mapState, mapGetters } from 'vuex';
import GlModal from '~/vue_shared/components/gl_modal.vue';
import { modalTypes } from '../../constants';
@@ -15,6 +16,7 @@ export default {
},
computed: {
...mapState(['entryModal']),
+ ...mapGetters('fileTemplates', ['templateTypes']),
entryName: {
get() {
if (this.entryModal.type === modalTypes.rename) {
@@ -31,7 +33,9 @@ export default {
if (this.entryModal.type === modalTypes.tree) {
return __('Create new directory');
} else if (this.entryModal.type === modalTypes.rename) {
- return this.entryModal.entry.type === modalTypes.tree ? __('Rename folder') : __('Rename file');
+ return this.entryModal.entry.type === modalTypes.tree
+ ? __('Rename folder')
+ : __('Rename file');
}
return __('Create new file');
@@ -40,11 +44,16 @@ export default {
if (this.entryModal.type === modalTypes.tree) {
return __('Create directory');
} else if (this.entryModal.type === modalTypes.rename) {
- return this.entryModal.entry.type === modalTypes.tree ? __('Rename folder') : __('Rename file');
+ return this.entryModal.entry.type === modalTypes.tree
+ ? __('Rename folder')
+ : __('Rename file');
}
return __('Create file');
},
+ isCreatingNew() {
+ return this.entryModal.type !== modalTypes.rename;
+ },
},
methods: {
...mapActions(['createTempEntry', 'renameEntry']),
@@ -61,6 +70,14 @@ export default {
});
}
},
+ createFromTemplate(template) {
+ this.createTempEntry({
+ name: template.name,
+ type: this.entryModal.type,
+ });
+
+ $('#ide-new-entry').modal('toggle');
+ },
focusInput() {
this.$refs.fieldName.focus();
},
@@ -77,6 +94,7 @@ export default {
:header-title-text="modalTitle"
:footer-primary-button-text="buttonLabel"
footer-primary-button-variant="success"
+ modal-size="lg"
@submit="submitForm"
@open="focusInput"
@closed="closedModal"
@@ -84,16 +102,35 @@ export default {
<div
class="form-group row"
>
- <label class="label-bold col-form-label col-sm-3">
+ <label class="label-bold col-form-label col-sm-2">
{{ __('Name') }}
</label>
- <div class="col-sm-9">
+ <div class="col-sm-10">
<input
ref="fieldName"
v-model="entryName"
type="text"
class="form-control"
+ placeholder="/dir/file_name"
/>
+ <ul
+ v-if="isCreatingNew"
+ class="prepend-top-default list-inline"
+ >
+ <li
+ v-for="(template, index) in templateTypes"
+ :key="index"
+ class="list-inline-item"
+ >
+ <button
+ type="button"
+ class="btn btn-missing p-1 pr-2 pl-2"
+ @click="createFromTemplate(template)"
+ >
+ {{ template.name }}
+ </button>
+ </li>
+ </ul>
</div>
</div>
</gl-modal>
diff --git a/app/assets/javascripts/ide/components/pipelines/list.vue b/app/assets/javascripts/ide/components/pipelines/list.vue
index 5757dfdc925..0a2681b7a1e 100644
--- a/app/assets/javascripts/ide/components/pipelines/list.vue
+++ b/app/assets/javascripts/ide/components/pipelines/list.vue
@@ -2,7 +2,6 @@
import { mapActions, mapGetters, mapState } from 'vuex';
import _ from 'underscore';
import { sprintf, __ } from '../../../locale';
-import LoadingIcon from '../../../vue_shared/components/loading_icon.vue';
import Icon from '../../../vue_shared/components/icon.vue';
import CiIcon from '../../../vue_shared/components/ci_icon.vue';
import Tabs from '../../../vue_shared/components/tabs/tabs';
@@ -12,7 +11,6 @@ import JobsList from '../jobs/list.vue';
export default {
components: {
- LoadingIcon,
Icon,
CiIcon,
Tabs,
@@ -50,10 +48,10 @@ export default {
<template>
<div class="ide-pipeline">
- <loading-icon
+ <gl-loading-icon
v-if="showLoadingIcon"
+ :size="2"
class="prepend-top-default"
- size="2"
/>
<template v-else-if="latestPipeline !== null">
<header
diff --git a/app/assets/javascripts/ide/components/preview/clientside.vue b/app/assets/javascripts/ide/components/preview/clientside.vue
index 39a1bd1f61b..37a8ad36507 100644
--- a/app/assets/javascripts/ide/components/preview/clientside.vue
+++ b/app/assets/javascripts/ide/components/preview/clientside.vue
@@ -3,14 +3,12 @@ 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';
import { createPathWithExt } from '../../utils';
export default {
components: {
- LoadingIcon,
Navigator,
},
data() {
@@ -177,9 +175,9 @@ export default {
{{ s__('IDE|Get started with Live Preview') }}
</a>
</div>
- <loading-icon
+ <gl-loading-icon
v-else
- size="2"
+ :size="2"
class="align-self-center mt-auto mb-auto"
/>
</div>
diff --git a/app/assets/javascripts/ide/components/preview/navigator.vue b/app/assets/javascripts/ide/components/preview/navigator.vue
index 4bf346946b6..42f23801692 100644
--- a/app/assets/javascripts/ide/components/preview/navigator.vue
+++ b/app/assets/javascripts/ide/components/preview/navigator.vue
@@ -1,12 +1,10 @@
<script>
import { listen } from 'codesandbox-api';
import Icon from '~/vue_shared/components/icon.vue';
-import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
export default {
components: {
Icon,
- LoadingIcon,
},
props: {
manager: {
@@ -138,7 +136,7 @@ export default {
class="ide-navigator-location form-control bg-white"
readonly
/>
- <loading-icon
+ <gl-loading-icon
v-if="loading"
class="position-absolute ide-preview-loading-icon"
/>
diff --git a/app/assets/javascripts/ide/components/repo_commit_section.vue b/app/assets/javascripts/ide/components/repo_commit_section.vue
index 6f1a941fbc4..d3b24c5b793 100644
--- a/app/assets/javascripts/ide/components/repo_commit_section.vue
+++ b/app/assets/javascripts/ide/components/repo_commit_section.vue
@@ -95,8 +95,9 @@ export default {
:file-list="changedFiles"
:action-btn-text="__('Stage all changes')"
:active-file-key="activeFileKey"
+ :empty-state-text="__('There are no unstaged changes')"
action="stageAllChanges"
- action-btn-icon="mobile-issue-close"
+ action-btn-icon="stage-all"
item-action-component="stage-button"
class="is-first"
icon-name="unstaged"
@@ -108,8 +109,9 @@ export default {
:action-btn-text="__('Unstage all changes')"
:staged-list="true"
:active-file-key="activeFileKey"
+ :empty-state-text="__('There are no staged changes')"
action="unstageAllChanges"
- action-btn-icon="history"
+ action-btn-icon="unstage-all"
item-action-component="unstage-button"
icon-name="staged"
/>
diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue
index f55aa843444..d3a73e84cc7 100644
--- a/app/assets/javascripts/ide/components/repo_editor.vue
+++ b/app/assets/javascripts/ide/components/repo_editor.vue
@@ -6,12 +6,14 @@ import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue';
import { activityBarViews, viewerTypes } from '../constants';
import Editor from '../lib/editor';
import ExternalLink from './external_link.vue';
+import FileTemplatesBar from './file_templates/bar.vue';
export default {
components: {
ContentViewer,
DiffViewer,
ExternalLink,
+ FileTemplatesBar,
},
props: {
file: {
@@ -34,6 +36,7 @@ export default {
'isCommitModeActive',
'isReviewModeActive',
]),
+ ...mapGetters('fileTemplates', ['showFileTemplatesBar']),
shouldHideEditor() {
return this.file && this.file.binary && !this.file.content;
},
@@ -216,7 +219,7 @@ export default {
id="ide"
class="blob-viewer-container blob-editor-container"
>
- <div class="ide-mode-tabs clearfix" >
+ <div class="ide-mode-tabs clearfix">
<ul
v-if="!shouldHideEditor && isEditModeActive"
class="nav-links float-left"
@@ -249,6 +252,9 @@ export default {
:file="file"
/>
</div>
+ <file-templates-bar
+ v-if="showFileTemplatesBar(file.name)"
+ />
<div
v-show="!shouldHideEditor && file.viewMode ==='editor'"
ref="editor"
diff --git a/app/assets/javascripts/ide/components/repo_file.vue b/app/assets/javascripts/ide/components/repo_file.vue
deleted file mode 100644
index 110eda83bb4..00000000000
--- a/app/assets/javascripts/ide/components/repo_file.vue
+++ /dev/null
@@ -1,227 +0,0 @@
-<script>
-import { mapActions, mapGetters } from 'vuex';
-import { n__, __, sprintf } from '~/locale';
-import tooltip from '~/vue_shared/directives/tooltip';
-import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
-import Icon from '~/vue_shared/components/icon.vue';
-import FileIcon from '~/vue_shared/components/file_icon.vue';
-import router from '../ide_router';
-import NewDropdown from './new_dropdown/index.vue';
-import FileStatusIcon from './repo_file_status_icon.vue';
-import ChangedFileIcon from './changed_file_icon.vue';
-import MrFileIcon from './mr_file_icon.vue';
-
-export default {
- name: 'RepoFile',
- directives: {
- tooltip,
- },
- components: {
- SkeletonLoadingContainer,
- NewDropdown,
- FileStatusIcon,
- FileIcon,
- ChangedFileIcon,
- MrFileIcon,
- Icon,
- },
- props: {
- file: {
- type: Object,
- required: true,
- },
- level: {
- type: Number,
- required: true,
- },
- },
- data() {
- return {
- mouseOver: false,
- };
- },
- computed: {
- ...mapGetters([
- 'getChangesInFolder',
- 'getUnstagedFilesCountForPath',
- 'getStagedFilesCountForPath',
- ]),
- folderUnstagedCount() {
- return this.getUnstagedFilesCountForPath(this.file.path);
- },
- folderStagedCount() {
- return this.getStagedFilesCountForPath(this.file.path);
- },
- changesCount() {
- return this.getChangesInFolder(this.file.path);
- },
- folderChangesTooltip() {
- if (this.changesCount === 0) return undefined;
-
- if (this.folderUnstagedCount > 0 && this.folderStagedCount === 0) {
- return n__('%d unstaged change', '%d unstaged changes', this.folderUnstagedCount);
- } else if (this.folderUnstagedCount === 0 && this.folderStagedCount > 0) {
- return n__('%d staged change', '%d staged changes', this.folderStagedCount);
- }
-
- return sprintf(__('%{unstaged} unstaged and %{staged} staged changes'), {
- unstaged: this.folderUnstagedCount,
- staged: this.folderStagedCount,
- });
- },
- isTree() {
- return this.file.type === 'tree';
- },
- isBlob() {
- return this.file.type === 'blob';
- },
- levelIndentation() {
- return {
- marginLeft: `${this.level * 16}px`,
- };
- },
- fileClass() {
- return {
- 'file-open': this.isBlob && this.file.opened,
- 'file-active': this.isBlob && this.file.active,
- folder: this.isTree,
- 'is-open': this.file.opened,
- };
- },
- showTreeChangesCount() {
- return this.isTree && this.changesCount > 0 && !this.file.opened;
- },
- showChangedFileIcon() {
- return this.file.changed || this.file.tempFile || this.file.staged;
- },
- },
- watch: {
- 'file.active': function fileActiveWatch(active) {
- if (this.file.type === 'blob' && active) {
- this.scrollIntoView();
- }
- },
- },
- mounted() {
- if (this.hasPathAtCurrentRoute()) {
- this.scrollIntoView(true);
- }
- },
- methods: {
- ...mapActions(['toggleTreeOpen']),
- clickFile() {
- // Manual Action if a tree is selected/opened
- if (this.isTree && this.hasUrlAtCurrentRoute()) {
- this.toggleTreeOpen(this.file.path);
- }
-
- router.push(`/project${this.file.url}`);
- },
- scrollIntoView(isInit = false) {
- const block = isInit && this.isTree ? 'center' : 'nearest';
-
- this.$el.scrollIntoView({
- behavior: 'smooth',
- block,
- });
- },
- hasPathAtCurrentRoute() {
- if (!this.$router || !this.$router.currentRoute) {
- return false;
- }
-
- // - strip route up to "/-/" and ending "/"
- const routePath = this.$router.currentRoute.path
- .replace(/^.*?[/]-[/]/g, '')
- .replace(/[/]$/g, '');
-
- // - strip ending "/"
- const filePath = this.file.path.replace(/[/]$/g, '');
-
- return filePath === routePath;
- },
- hasUrlAtCurrentRoute() {
- return this.$router.currentRoute.path === `/project${this.file.url}`;
- },
- toggleHover(over) {
- this.mouseOver = over;
- },
- },
-};
-</script>
-
-<template>
- <div>
- <div
- :class="fileClass"
- class="file"
- role="button"
- @click="clickFile"
- @mouseover="toggleHover(true)"
- @mouseout="toggleHover(false)"
- >
- <div
- class="file-name"
- >
- <span
- :style="levelIndentation"
- class="ide-file-name str-truncated"
- >
- <file-icon
- :file-name="file.name"
- :loading="file.loading"
- :folder="isTree"
- :opened="file.opened"
- :size="16"
- />
- {{ file.name }}
- <file-status-icon
- :file="file"
- />
- </span>
- <span class="float-right ide-file-icon-holder">
- <mr-file-icon
- v-if="file.mrChange"
- />
- <span
- v-if="showTreeChangesCount"
- class="ide-tree-changes"
- >
- {{ changesCount }}
- <icon
- v-tooltip
- :title="folderChangesTooltip"
- :size="12"
- data-container="body"
- data-placement="right"
- name="file-modified"
- css-classes="prepend-left-5 ide-file-modified"
- />
- </span>
- <changed-file-icon
- v-else-if="showChangedFileIcon"
- :file="file"
- :show-tooltip="true"
- :show-staged-icon="true"
- :force-modified-icon="true"
- class="float-right"
- />
- </span>
- <new-dropdown
- :type="file.type"
- :path="file.path"
- :mouse-over="mouseOver"
- class="float-right prepend-left-8"
- />
- </div>
- </div>
- <template v-if="file.opened">
- <repo-file
- v-for="childFile in file.tree"
- :key="childFile.key"
- :file="childFile"
- :level="level + 1"
- />
- </template>
- </div>
-</template>
diff --git a/app/assets/javascripts/ide/components/repo_file_status_icon.vue b/app/assets/javascripts/ide/components/repo_file_status_icon.vue
index 76a3333be50..97589e116c5 100644
--- a/app/assets/javascripts/ide/components/repo_file_status_icon.vue
+++ b/app/assets/javascripts/ide/components/repo_file_status_icon.vue
@@ -26,8 +26,8 @@ export default {
<template>
<span
- v-tooltip
v-if="file.file_lock"
+ v-tooltip
:title="lockTooltip"
data-container="body"
>
diff --git a/app/assets/javascripts/ide/stores/actions.js b/app/assets/javascripts/ide/stores/actions.js
index aa02dfbddc4..b8b64aead30 100644
--- a/app/assets/javascripts/ide/stores/actions.js
+++ b/app/assets/javascripts/ide/stores/actions.js
@@ -4,6 +4,7 @@ import { visitUrl } from '~/lib/utils/url_utility';
import flash from '~/flash';
import * as types from './mutation_types';
import FilesDecoratorWorker from './workers/files_decorator_worker';
+import { stageKeys } from '../constants';
export const redirectToUrl = (_, url) => visitUrl(url);
@@ -122,14 +123,28 @@ export const scrollToTab = () => {
});
};
-export const stageAllChanges = ({ state, commit }) => {
+export const stageAllChanges = ({ state, commit, dispatch }) => {
+ const openFile = state.openFiles[0];
+
commit(types.SET_LAST_COMMIT_MSG, '');
state.changedFiles.forEach(file => commit(types.STAGE_CHANGE, file.path));
+
+ dispatch('openPendingTab', {
+ file: state.stagedFiles.find(f => f.path === openFile.path),
+ keyPrefix: stageKeys.staged,
+ });
};
-export const unstageAllChanges = ({ state, commit }) => {
+export const unstageAllChanges = ({ state, commit, dispatch }) => {
+ const openFile = state.openFiles[0];
+
state.stagedFiles.forEach(file => commit(types.UNSTAGE_CHANGE, file.path));
+
+ dispatch('openPendingTab', {
+ file: state.changedFiles.find(f => f.path === openFile.path),
+ keyPrefix: stageKeys.unstaged,
+ });
};
export const updateViewer = ({ commit }, viewer) => {
@@ -206,6 +221,7 @@ export const resetOpenFiles = ({ commit }) => commit(types.RESET_OPEN_FILES);
export const renameEntry = ({ dispatch, commit, state }, { path, name, entryPath = null }) => {
const entry = state.entries[entryPath || path];
+
commit(types.RENAME_ENTRY, { path, name, entryPath });
if (entry.type === 'tree') {
@@ -214,7 +230,7 @@ export const renameEntry = ({ dispatch, commit, state }, { path, name, entryPath
);
}
- if (!entryPath) {
+ if (!entryPath && !entry.tempFile) {
dispatch('deleteEntry', path);
}
};
diff --git a/app/assets/javascripts/ide/stores/actions/file.js b/app/assets/javascripts/ide/stores/actions/file.js
index 28b9d0df201..30dcf7ef4df 100644
--- a/app/assets/javascripts/ide/stores/actions/file.js
+++ b/app/assets/javascripts/ide/stores/actions/file.js
@@ -5,7 +5,7 @@ import service from '../../services';
import * as types from '../mutation_types';
import router from '../../ide_router';
import { setPageTitle } from '../utils';
-import { viewerTypes } from '../../constants';
+import { viewerTypes, stageKeys } from '../../constants';
export const closeFile = ({ commit, state, dispatch }, file) => {
const { path } = file;
@@ -208,8 +208,9 @@ export const discardFileChanges = ({ dispatch, state, commit, getters }, path) =
eventHub.$emit(`editor.update.model.dispose.unstaged-${file.key}`, file.content);
};
-export const stageChange = ({ commit, state }, path) => {
+export const stageChange = ({ commit, state, dispatch }, path) => {
const stagedFile = state.stagedFiles.find(f => f.path === path);
+ const openFile = state.openFiles.find(f => f.path === path);
commit(types.STAGE_CHANGE, path);
commit(types.SET_LAST_COMMIT_MSG, '');
@@ -217,21 +218,39 @@ export const stageChange = ({ commit, state }, path) => {
if (stagedFile) {
eventHub.$emit(`editor.update.model.new.content.staged-${stagedFile.key}`, stagedFile.content);
}
+
+ if (openFile && openFile.active) {
+ const file = state.stagedFiles.find(f => f.path === path);
+
+ dispatch('openPendingTab', {
+ file,
+ keyPrefix: stageKeys.staged,
+ });
+ }
};
-export const unstageChange = ({ commit }, path) => {
+export const unstageChange = ({ commit, dispatch, state }, path) => {
+ const openFile = state.openFiles.find(f => f.path === path);
+
commit(types.UNSTAGE_CHANGE, path);
+
+ if (openFile && openFile.active) {
+ const file = state.changedFiles.find(f => f.path === path);
+
+ dispatch('openPendingTab', {
+ file,
+ keyPrefix: stageKeys.unstaged,
+ });
+ }
};
-export const openPendingTab = ({ commit, getters, dispatch, state }, { file, keyPrefix }) => {
+export const openPendingTab = ({ commit, getters, state }, { file, keyPrefix }) => {
if (getters.activeFile && getters.activeFile.key === `${keyPrefix}-${file.key}`) return false;
state.openFiles.forEach(f => eventHub.$emit(`editor.update.model.dispose.${f.key}`));
commit(types.ADD_PENDING_TAB, { file, keyPrefix });
- dispatch('scrollToTab');
-
router.push(`/project/${file.projectId}/tree/${state.currentBranchId}/`);
return true;
diff --git a/app/assets/javascripts/ide/stores/index.js b/app/assets/javascripts/ide/stores/index.js
index a601dc8f5a0..877d88bb060 100644
--- a/app/assets/javascripts/ide/stores/index.js
+++ b/app/assets/javascripts/ide/stores/index.js
@@ -8,6 +8,7 @@ import commitModule from './modules/commit';
import pipelines from './modules/pipelines';
import mergeRequests from './modules/merge_requests';
import branches from './modules/branches';
+import fileTemplates from './modules/file_templates';
Vue.use(Vuex);
@@ -22,6 +23,7 @@ export const createStore = () =>
pipelines,
mergeRequests,
branches,
+ fileTemplates: fileTemplates(),
},
});
diff --git a/app/assets/javascripts/ide/stores/modules/branches/mutations.js b/app/assets/javascripts/ide/stores/modules/branches/mutations.js
index 081ec2d4c28..0a455f4500f 100644
--- a/app/assets/javascripts/ide/stores/modules/branches/mutations.js
+++ b/app/assets/javascripts/ide/stores/modules/branches/mutations.js
@@ -1,4 +1,3 @@
-/* eslint-disable no-param-reassign */
import * as types from './mutation_types';
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
index 43237a29466..dd53213ed18 100644
--- a/app/assets/javascripts/ide/stores/modules/file_templates/actions.js
+++ b/app/assets/javascripts/ide/stores/modules/file_templates/actions.js
@@ -1,6 +1,7 @@
import Api from '~/api';
import { __ } from '~/locale';
import * as types from './mutation_types';
+import eventHub from '../../../eventhub';
export const requestTemplateTypes = ({ commit }) => commit(types.REQUEST_TEMPLATE_TYPES);
export const receiveTemplateTypesError = ({ commit, dispatch }) => {
@@ -31,9 +32,23 @@ export const fetchTemplateTypes = ({ dispatch, state }) => {
.catch(() => dispatch('receiveTemplateTypesError'));
};
-export const setSelectedTemplateType = ({ commit }, type) =>
+export const setSelectedTemplateType = ({ commit, dispatch, rootGetters }, type) => {
commit(types.SET_SELECTED_TEMPLATE_TYPE, type);
+ if (rootGetters.activeFile.prevPath === type.name) {
+ dispatch('discardFileChanges', rootGetters.activeFile.path, { root: true });
+ } else if (rootGetters.activeFile.name !== type.name) {
+ dispatch(
+ 'renameEntry',
+ {
+ path: rootGetters.activeFile.path,
+ name: type.name,
+ },
+ { root: true },
+ );
+ }
+};
+
export const receiveTemplateError = ({ dispatch }, template) => {
dispatch(
'setErrorMessage',
@@ -69,6 +84,7 @@ export const setFileTemplate = ({ dispatch, commit, rootGetters }, template) =>
{ root: true },
);
commit(types.SET_UPDATE_SUCCESS, true);
+ eventHub.$emit(`editor.update.model.new.content.${rootGetters.activeFile.key}`, template.content);
};
export const undoFileTemplate = ({ dispatch, commit, rootGetters }) => {
@@ -76,6 +92,12 @@ export const undoFileTemplate = ({ dispatch, commit, rootGetters }) => {
dispatch('changeFileContent', { path: file.path, content: file.raw }, { root: true });
commit(types.SET_UPDATE_SUCCESS, false);
+
+ eventHub.$emit(`editor.update.model.new.content.${file.key}`, file.raw);
+
+ if (file.prevPath) {
+ dispatch('discardFileChanges', file.path, { root: true });
+ }
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
diff --git a/app/assets/javascripts/ide/stores/modules/file_templates/getters.js b/app/assets/javascripts/ide/stores/modules/file_templates/getters.js
index 38318fd49bf..628babe6a01 100644
--- a/app/assets/javascripts/ide/stores/modules/file_templates/getters.js
+++ b/app/assets/javascripts/ide/stores/modules/file_templates/getters.js
@@ -1,3 +1,5 @@
+import { activityBarViews } from '../../../constants';
+
export const templateTypes = () => [
{
name: '.gitlab-ci.yml',
@@ -17,7 +19,8 @@ export const templateTypes = () => [
},
];
-export const showFileTemplatesBar = (_, getters) => name =>
- getters.templateTypes.find(t => t.name === name);
+export const showFileTemplatesBar = (_, getters, rootState) => name =>
+ getters.templateTypes.find(t => t.name === name) &&
+ rootState.currentActivityView === activityBarViews.edit;
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
index dfa5ef54413..383ff5db392 100644
--- a/app/assets/javascripts/ide/stores/modules/file_templates/index.js
+++ b/app/assets/javascripts/ide/stores/modules/file_templates/index.js
@@ -3,10 +3,10 @@ import * as actions from './actions';
import * as getters from './getters';
import mutations from './mutations';
-export default {
+export default () => ({
namespaced: true,
actions,
state: createState(),
getters,
mutations,
-};
+});
diff --git a/app/assets/javascripts/ide/stores/modules/file_templates/mutations.js b/app/assets/javascripts/ide/stores/modules/file_templates/mutations.js
index e413e61eaaa..674782a28ca 100644
--- a/app/assets/javascripts/ide/stores/modules/file_templates/mutations.js
+++ b/app/assets/javascripts/ide/stores/modules/file_templates/mutations.js
@@ -1,4 +1,3 @@
-/* eslint-disable no-param-reassign */
import * as types from './mutation_types';
export default {
diff --git a/app/assets/javascripts/ide/stores/modules/merge_requests/mutations.js b/app/assets/javascripts/ide/stores/modules/merge_requests/mutations.js
index 98102a68e08..0eba9c39817 100644
--- a/app/assets/javascripts/ide/stores/modules/merge_requests/mutations.js
+++ b/app/assets/javascripts/ide/stores/modules/merge_requests/mutations.js
@@ -1,4 +1,3 @@
-/* eslint-disable no-param-reassign */
import * as types from './mutation_types';
export default {
diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/mutations.js b/app/assets/javascripts/ide/stores/modules/pipelines/mutations.js
index 5a2213bbe89..b4be100cb07 100644
--- a/app/assets/javascripts/ide/stores/modules/pipelines/mutations.js
+++ b/app/assets/javascripts/ide/stores/modules/pipelines/mutations.js
@@ -1,4 +1,3 @@
-/* eslint-disable no-param-reassign */
import * as types from './mutation_types';
import { normalizeJob } from './utils';
diff --git a/app/assets/javascripts/ide/stores/mutations.js b/app/assets/javascripts/ide/stores/mutations.js
index 0347f803757..2c8535bda59 100644
--- a/app/assets/javascripts/ide/stores/mutations.js
+++ b/app/assets/javascripts/ide/stores/mutations.js
@@ -1,4 +1,4 @@
-/* eslint-disable no-param-reassign */
+import Vue from 'vue';
import * as types from './mutation_types';
import projectMutations from './mutations/project';
import mergeRequestMutation from './mutations/merge_request';
@@ -227,7 +227,7 @@ export default {
path: newPath,
name: entryPath ? oldEntry.name : name,
tempFile: true,
- prevPath: oldEntry.path,
+ prevPath: oldEntry.tempFile ? null : oldEntry.path,
url: oldEntry.url.replace(new RegExp(`${oldEntry.path}/?$`), newPath),
tree: [],
parentPath,
@@ -246,6 +246,20 @@ export default {
if (newEntry.type === 'blob') {
state.changedFiles = state.changedFiles.concat(newEntry);
}
+
+ if (state.entries[newPath].opened) {
+ state.openFiles.push(state.entries[newPath]);
+ }
+
+ if (oldEntry.tempFile) {
+ const filterMethod = f => f.path !== oldEntry.path;
+
+ state.openFiles = state.openFiles.filter(filterMethod);
+ state.changedFiles = state.changedFiles.filter(filterMethod);
+ parent.tree = parent.tree.filter(filterMethod);
+
+ Vue.delete(state.entries, oldEntry.path);
+ }
},
...projectMutations,
...mergeRequestMutation,
diff --git a/app/assets/javascripts/ide/stores/mutations/file.js b/app/assets/javascripts/ide/stores/mutations/file.js
index a937fb157f8..6ca246c1d63 100644
--- a/app/assets/javascripts/ide/stores/mutations/file.js
+++ b/app/assets/javascripts/ide/stores/mutations/file.js
@@ -1,4 +1,3 @@
-/* eslint-disable no-param-reassign */
import * as types from '../mutation_types';
import { sortTree } from '../utils';
import { diffModes } from '../../constants';
@@ -56,7 +55,7 @@ export default {
f => f.path === file.path && f.pending && !(f.tempFile && !f.prevPath),
);
- if (file.tempFile) {
+ if (file.tempFile && file.content === '') {
Object.assign(state.entries[file.path], {
content: raw,
});
diff --git a/app/assets/javascripts/issuable_bulk_update_actions.js b/app/assets/javascripts/issuable_bulk_update_actions.js
index 35eaf21a836..9e848699163 100644
--- a/app/assets/javascripts/issuable_bulk_update_actions.js
+++ b/app/assets/javascripts/issuable_bulk_update_actions.js
@@ -36,7 +36,7 @@ export default {
},
getSelectedIssues() {
- return this.issues.has('.selected_issue:checked');
+ return this.issues.has('.selected-issuable:checked');
},
getLabelsFromSelection() {
@@ -110,7 +110,7 @@ export default {
getOriginalCommonIds() {
const labelIds = [];
- this.getElement('.selected_issue:checked').each((i, el) => {
+ this.getElement('.selected-issuable:checked').each((i, el) => {
labelIds.push(this.getElement(`#${this.prefixId}${el.dataset.id}`).data('labels'));
});
return _.intersection.apply(this, labelIds);
@@ -119,7 +119,7 @@ export default {
// From issuable's initial bulk selection
getOriginalMarkedIds() {
const labelIds = [];
- this.getElement('.selected_issue:checked').each((i, el) => {
+ this.getElement('.selected-issuable:checked').each((i, el) => {
labelIds.push(this.getElement(`#${this.prefixId}${el.dataset.id}`).data('labels'));
});
return _.intersection.apply(this, labelIds);
@@ -132,7 +132,7 @@ export default {
let issuableLabels = [];
// Collect unique label IDs for all checked issues
- this.getElement('.selected_issue:checked').each((i, el) => {
+ this.getElement('.selected-issuable:checked').each((i, el) => {
issuableLabels = this.getElement(`#${this.prefixId}${el.dataset.id}`).data('labels');
issuableLabels.forEach((labelId) => {
// Store unique IDs
diff --git a/app/assets/javascripts/issuable_bulk_update_sidebar.js b/app/assets/javascripts/issuable_bulk_update_sidebar.js
index 2307c8e0d85..74150ce3a8b 100644
--- a/app/assets/javascripts/issuable_bulk_update_sidebar.js
+++ b/app/assets/javascripts/issuable_bulk_update_sidebar.js
@@ -30,7 +30,7 @@ export default class IssuableBulkUpdateSidebar {
this.$otherFilters = $('.issues-other-filters');
this.$checkAllContainer = $('.check-all-holder');
this.$issueChecks = $('.issue-check');
- this.$issuesList = $('.selected_issue');
+ this.$issuesList = $('.selected-issuable');
this.$issuableIdsInput = $('#update_issuable_ids');
}
@@ -55,7 +55,7 @@ export default class IssuableBulkUpdateSidebar {
}
updateFormState() {
- const noCheckedIssues = !$('.selected_issue:checked').length;
+ const noCheckedIssues = !$('.selected-issuable:checked').length;
this.toggleSubmitButtonDisabled(noCheckedIssues);
this.updateSelectedIssuableIds();
@@ -123,7 +123,7 @@ export default class IssuableBulkUpdateSidebar {
}
static getCheckedIssueIds() {
- const $checkedIssues = $('.selected_issue:checked');
+ const $checkedIssues = $('.selected-issuable:checked');
if ($checkedIssues.length > 0) {
return $.map($checkedIssues, value => $(value).data('id'));
diff --git a/app/assets/javascripts/issue_show/components/edit_actions.vue b/app/assets/javascripts/issue_show/components/edit_actions.vue
index 597c6d69a81..7fd3ea61aa7 100644
--- a/app/assets/javascripts/issue_show/components/edit_actions.vue
+++ b/app/assets/javascripts/issue_show/components/edit_actions.vue
@@ -53,7 +53,7 @@
<button
:class="{ disabled: formState.updateLoading || !isSubmitEnabled }"
:disabled="formState.updateLoading || !isSubmitEnabled"
- class="btn btn-save float-left"
+ class="btn btn-success float-left"
type="submit"
@click.prevent="updateIssuable">
Save changes
diff --git a/app/assets/javascripts/issue_show/components/title.vue b/app/assets/javascripts/issue_show/components/title.vue
index b5e8e0ea44b..cf99e9a9cd8 100644
--- a/app/assets/javascripts/issue_show/components/title.vue
+++ b/app/assets/javascripts/issue_show/components/title.vue
@@ -76,8 +76,8 @@ export default {
>
</h2>
<button
- v-tooltip
v-if="showInlineEditButton && canUpdate"
+ v-tooltip
type="button"
class="btn btn-default btn-edit btn-svg js-issuable-edit"
title="Edit title and description"
diff --git a/app/assets/javascripts/jobs/components/header.vue b/app/assets/javascripts/jobs/components/header.vue
index 1e7f4b2c3f7..63324e68d68 100644
--- a/app/assets/javascripts/jobs/components/header.vue
+++ b/app/assets/javascripts/jobs/components/header.vue
@@ -1,13 +1,11 @@
<script>
import ciHeader from '../../vue_shared/components/header_ci_component.vue';
-import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import callout from '../../vue_shared/components/callout.vue';
export default {
name: 'JobHeaderSection',
components: {
ciHeader,
- loadingIcon,
callout,
},
props: {
@@ -59,7 +57,7 @@ export default {
actions.push({
label: 'New issue',
path: this.job.new_issue_path,
- cssClass: 'js-new-issue btn btn-new btn-inverted d-none d-md-block d-lg-block d-xl-block',
+ cssClass: 'js-new-issue btn btn-success btn-inverted d-none d-md-block d-lg-block d-xl-block',
type: 'link',
});
}
@@ -82,9 +80,9 @@ export default {
:should-render-triggered-label="jobStarted"
item-name="Job"
/>
- <loading-icon
+ <gl-loading-icon
v-if="isLoading"
- size="2"
+ :size="2"
class="prepend-top-default append-bottom-default"
/>
</div>
diff --git a/app/assets/javascripts/jobs/components/job_log_controllers.vue b/app/assets/javascripts/jobs/components/job_log_controllers.vue
index 513851e376f..2cbf0f85266 100644
--- a/app/assets/javascripts/jobs/components/job_log_controllers.vue
+++ b/app/assets/javascripts/jobs/components/job_log_controllers.vue
@@ -78,8 +78,8 @@
<div class="controllers float-right">
<!-- links -->
<a
- v-tooltip
v-if="rawTracePath"
+ v-tooltip
:title="s__('Job|Show complete raw')"
:href="rawTracePath"
class="js-raw-link-controller controllers-buttons"
@@ -89,8 +89,8 @@
</a>
<button
- v-tooltip
v-if="canEraseJob"
+ v-tooltip
:title="s__('Job|Erase job log')"
type="button"
class="js-erase-link controllers-buttons"
diff --git a/app/assets/javascripts/jobs/components/jobs_container.vue b/app/assets/javascripts/jobs/components/jobs_container.vue
index b81109bdd06..93e2292ff84 100644
--- a/app/assets/javascripts/jobs/components/jobs_container.vue
+++ b/app/assets/javascripts/jobs/components/jobs_container.vue
@@ -25,9 +25,9 @@
class="build-job"
>
<a
- v-tooltip
v-for="job in jobs"
:key="job.id"
+ v-tooltip
:href="job.path"
:title="job.tooltip"
:class="{ active: job.active, retried: job.retried }"
diff --git a/app/assets/javascripts/jobs/components/sidebar_details_block.vue b/app/assets/javascripts/jobs/components/sidebar_details_block.vue
index 36d4a3e2bc9..80c2a5fb48b 100644
--- a/app/assets/javascripts/jobs/components/sidebar_details_block.vue
+++ b/app/assets/javascripts/jobs/components/sidebar_details_block.vue
@@ -1,5 +1,4 @@
<script>
-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';
@@ -9,7 +8,6 @@ export default {
name: 'SidebarDetailsBlock',
components: {
DetailRow,
- LoadingIcon,
Icon,
},
mixins: [timeagoMixin],
@@ -132,7 +130,7 @@ export default {
<a
v-if="job.new_issue_path"
:href="job.new_issue_path"
- class="js-new-issue btn btn-new btn-inverted"
+ class="js-new-issue btn btn-success btn-inverted"
>
{{ __('New issue') }}
</a>
@@ -232,10 +230,10 @@ export default {
</div>
</div>
</template>
- <loading-icon
+ <gl-loading-icon
v-if="isLoading"
+ :size="2"
class="prepend-top-10"
- size="2"
/>
</div>
</template>
diff --git a/app/assets/javascripts/jobs/store/mutations.js b/app/assets/javascripts/jobs/store/mutations.js
index 2a451ef0cd1..cd12ef87d40 100644
--- a/app/assets/javascripts/jobs/store/mutations.js
+++ b/app/assets/javascripts/jobs/store/mutations.js
@@ -1,5 +1,3 @@
-/* eslint-disable no-param-reassign */
-
import * as types from './mutation_types';
export default {
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index 6499b919787..1c7bca78df3 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -449,11 +449,11 @@ export default class LabelsSelect {
}
bindEvents() {
- return $('body').on('change', '.selected_issue', this.onSelectCheckboxIssue);
+ return $('body').on('change', '.selected-issuable', this.onSelectCheckboxIssue);
}
// eslint-disable-next-line class-methods-use-this
onSelectCheckboxIssue() {
- if ($('.selected_issue:checked').length) {
+ if ($('.selected-issuable:checked').length) {
return;
}
return $('.issues-bulk-update .labels-filter .dropdown-toggle-text').text('Label');
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index 3e208764b3e..30925940807 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -56,7 +56,8 @@ export const rstrip = val => {
return val;
};
-export const updateTooltipTitle = ($tooltipEl, newTitle) => $tooltipEl.attr('title', newTitle).tooltip('_fixTitle');
+export const updateTooltipTitle = ($tooltipEl, newTitle) =>
+ $tooltipEl.attr('title', newTitle).tooltip('_fixTitle');
export const disableButtonIfEmptyField = (fieldSelector, buttonSelector, eventName = 'input') => {
const field = $(fieldSelector);
@@ -86,6 +87,7 @@ export const handleLocationHash = () => {
const fixedTabs = document.querySelector('.js-tabs-affix');
const fixedDiffStats = document.querySelector('.js-diff-files-changed');
const fixedNav = document.querySelector('.navbar-gitlab');
+ const performanceBar = document.querySelector('#js-peek');
let adjustment = 0;
if (fixedNav) adjustment -= fixedNav.offsetHeight;
@@ -102,6 +104,10 @@ export const handleLocationHash = () => {
adjustment -= fixedDiffStats.offsetHeight;
}
+ if (performanceBar) {
+ adjustment -= performanceBar.offsetHeight;
+ }
+
window.scrollBy(0, adjustment);
};
@@ -131,17 +137,43 @@ export const parseUrlPathname = url => {
return parsedUrl.pathname.charAt(0) === '/' ? parsedUrl.pathname : `/${parsedUrl.pathname}`;
};
-// We can trust that each param has one & since values containing & will be encoded
-// Remove the first character of search as it is always ?
-export const getUrlParamsArray = () =>
- window.location.search
- .slice(1)
- .split('&')
+const splitPath = (path = '') => path.replace(/^\?/, '').split('&');
+
+export const urlParamsToArray = (path = '') =>
+ splitPath(path)
+ .filter(param => param.length > 0)
.map(param => {
const split = param.split('=');
return [decodeURI(split[0]), split[1]].join('=');
});
+export const getUrlParamsArray = () => urlParamsToArray(window.location.search);
+
+export const urlParamsToObject = (path = '') =>
+ splitPath(path).reduce((dataParam, filterParam) => {
+ if (filterParam === '') {
+ return dataParam;
+ }
+
+ const data = dataParam;
+ let [key, value] = filterParam.split('=');
+ const isArray = key.includes('[]');
+ key = key.replace('[]', '');
+ value = decodeURIComponent(value.replace(/\+/g, ' '));
+
+ if (isArray) {
+ if (!data[key]) {
+ data[key] = [];
+ }
+
+ data[key].push(value);
+ } else {
+ data[key] = value;
+ }
+
+ return data;
+ }, {});
+
export const isMetaKey = e => e.metaKey || e.ctrlKey || e.altKey || e.shiftKey;
// Identify following special clicks
@@ -189,7 +221,7 @@ export const getParameterByName = (name, urlToParse) => {
return decodeURIComponent(results[2].replace(/\+/g, ' '));
};
-const handleSelectedRange = (range) => {
+const handleSelectedRange = range => {
const container = range.commonAncestorContainer;
// add context to fragment if needed
if (container.tagName === 'OL') {
@@ -426,7 +458,7 @@ export const backOff = (fn, timeout = 60000) => {
export const createOverlayIcon = (iconPath, overlayPath) => {
const faviconImage = document.createElement('img');
- return new Promise((resolve) => {
+ return new Promise(resolve => {
faviconImage.onload = () => {
const size = 32;
@@ -437,13 +469,29 @@ export const createOverlayIcon = (iconPath, overlayPath) => {
const context = canvas.getContext('2d');
context.clearRect(0, 0, size, size);
context.drawImage(
- faviconImage, 0, 0, faviconImage.width, faviconImage.height, 0, 0, size, size,
+ faviconImage,
+ 0,
+ 0,
+ faviconImage.width,
+ faviconImage.height,
+ 0,
+ 0,
+ size,
+ size,
);
const overlayImage = document.createElement('img');
overlayImage.onload = () => {
context.drawImage(
- overlayImage, 0, 0, overlayImage.width, overlayImage.height, 0, 0, size, size,
+ overlayImage,
+ 0,
+ 0,
+ overlayImage.width,
+ overlayImage.height,
+ 0,
+ 0,
+ size,
+ size,
);
const faviconWithOverlayUrl = canvas.toDataURL();
@@ -456,17 +504,21 @@ export const createOverlayIcon = (iconPath, overlayPath) => {
});
};
-export const setFaviconOverlay = (overlayPath) => {
+export const setFaviconOverlay = overlayPath => {
const faviconEl = document.getElementById('favicon');
- if (!faviconEl) { return null; }
+ if (!faviconEl) {
+ return null;
+ }
const iconPath = faviconEl.getAttribute('data-original-href');
- return createOverlayIcon(iconPath, overlayPath).then(faviconWithOverlayUrl => faviconEl.setAttribute('href', faviconWithOverlayUrl));
+ return createOverlayIcon(iconPath, overlayPath).then(faviconWithOverlayUrl =>
+ faviconEl.setAttribute('href', faviconWithOverlayUrl),
+ );
};
-export const setFavicon = (faviconPath) => {
+export const setFavicon = faviconPath => {
const faviconEl = document.getElementById('favicon');
if (faviconEl && faviconPath) {
faviconEl.setAttribute('href', faviconPath);
@@ -491,7 +543,7 @@ export const setCiStatusFavicon = pageUrl =>
}
return resetFavicon();
})
- .catch((error) => {
+ .catch(error => {
resetFavicon();
throw error;
});
diff --git a/app/assets/javascripts/shortcuts_dashboard_navigation.js b/app/assets/javascripts/lib/utils/navigation_utility.js
index 9f69f110d06..1579b225e44 100644
--- a/app/assets/javascripts/shortcuts_dashboard_navigation.js
+++ b/app/assets/javascripts/lib/utils/navigation_utility.js
@@ -1,4 +1,4 @@
-import { visitUrl } from './lib/utils/url_utility';
+import { visitUrl } from './url_utility';
/**
* Helper function that finds the href of the fiven selector and updates the location.
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js
index 2be3c97bd95..879f94a26ec 100644
--- a/app/assets/javascripts/lib/utils/text_utility.js
+++ b/app/assets/javascripts/lib/utils/text_utility.js
@@ -49,6 +49,16 @@ export const dasherize = str => str.replace(/[_\s]+/g, '-');
export const slugify = str => str.trim().toLowerCase();
/**
+ * Replaces whitespaces with hyphens and converts to lower case
+ * @param {String} str
+ * @returns {String}
+ */
+export const slugifyWithHyphens = str => {
+ const regex = new RegExp(/\s+/, 'g');
+ return str.toLowerCase().replace(regex, '-');
+};
+
+/**
* Truncates given text
*
* @param {String} string
diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js
index 72b72f4247d..a282c2df441 100644
--- a/app/assets/javascripts/lib/utils/url_utility.js
+++ b/app/assets/javascripts/lib/utils/url_utility.js
@@ -47,9 +47,9 @@ export function removeParamQueryString(url, param) {
return urlVariables.filter(variable => variable.indexOf(param) === -1).join('&');
}
-export function removeParams(params) {
+export function removeParams(params, source = window.location.href) {
const url = document.createElement('a');
- url.href = window.location.href;
+ url.href = source;
params.forEach(param => {
url.search = removeParamQueryString(url.search, param);
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index 2718f73a830..e8aac51a299 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -2,7 +2,6 @@
import jQuery from 'jquery';
import Cookies from 'js-cookie';
-import svg4everybody from 'svg4everybody';
// bootstrap webpack, common libs, polyfills, and behaviors
import './webpack';
@@ -25,10 +24,12 @@ import initLayoutNav from './layout_nav';
import './feature_highlight/feature_highlight_options';
import LazyLoader from './lazy_loader';
import initLogoAnimation from './logo';
-import './milestone_select';
import './frequent_items';
import initBreadcrumbs from './breadcrumb';
-import initDispatcher from './dispatcher';
+import initUsagePingConsent from './usage_ping_consent';
+import initPerformanceBar from './performance_bar';
+import initSearchAutocomplete from './search_autocomplete';
+import GlFieldErrors from './gl_field_errors';
// expose jQuery as global (TODO: remove these)
window.jQuery = jQuery;
@@ -40,8 +41,6 @@ if (process.env.NODE_ENV !== 'production' && gon && gon.test_env) {
import(/* webpackMode: "eager" */ './test_utils/');
}
-svg4everybody();
-
document.addEventListener('beforeunload', () => {
// Unbind scroll events
$(document).off('scroll');
@@ -78,6 +77,10 @@ document.addEventListener('DOMContentLoaded', () => {
initImporterStatus();
initTodoToggle();
initLogoAnimation();
+ initUsagePingConsent();
+
+ if (document.querySelector('.search')) initSearchAutocomplete();
+ if (document.querySelector('#js-peek')) initPerformanceBar({ container: '#js-peek' });
// Set the default path for all cookies to GitLab's root directory
Cookies.defaults.path = gon.relative_url_root || '/';
@@ -268,5 +271,6 @@ document.addEventListener('DOMContentLoaded', () => {
});
}
- initDispatcher();
+ // initialize field errors
+ $('.gl-show-field-errors').each((i, form) => new GlFieldErrors(form));
});
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index 53d7504de35..763429d7242 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -115,8 +115,9 @@ export default class MergeRequestTabs {
this.mergeRequestTabs &&
this.mergeRequestTabs.querySelector(`a[data-action='${action}']`) &&
this.mergeRequestTabs.querySelector(`a[data-action='${action}']`).click
- )
+ ) {
this.mergeRequestTabs.querySelector(`a[data-action='${action}']`).click();
+ }
this.initAffix();
}
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index ae96ac3b80c..a07a0ecfc76 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -214,8 +214,8 @@ export default {
:show-panels="showPanels"
>
<graph
- v-for="(graphData, index) in groupData.metrics"
- :key="index"
+ v-for="(graphData, graphIndex) in groupData.metrics"
+ :key="graphIndex"
:graph-data="graphData"
:hover-data="hoverData"
:update-aspect-ratio="updateAspectRatio"
diff --git a/app/assets/javascripts/monitoring/components/graph.vue b/app/assets/javascripts/monitoring/components/graph.vue
index e5680a0499f..a13f30e6079 100644
--- a/app/assets/javascripts/monitoring/components/graph.vue
+++ b/app/assets/javascripts/monitoring/components/graph.vue
@@ -82,11 +82,12 @@ export default {
value: 0,
},
currentXCoordinate: 0,
- currentCoordinates: [],
+ currentCoordinates: {},
showFlag: false,
showFlagContent: false,
timeSeries: [],
realPixelRatio: 1,
+ seriesUnderMouse: [],
};
},
computed: {
@@ -126,6 +127,9 @@ export default {
this.draw();
},
methods: {
+ showDot(path) {
+ return this.showFlagContent && this.seriesUnderMouse.includes(path);
+ },
draw() {
const breakpointSize = bp.getBreakpointSize();
const query = this.graphData.queries[0];
@@ -155,7 +159,24 @@ export default {
point.y = e.clientY;
point = point.matrixTransform(this.$refs.graphData.getScreenCTM().inverse());
point.x += 7;
- const firstTimeSeries = this.timeSeries[0];
+
+ this.seriesUnderMouse = this.timeSeries.filter((series) => {
+ const mouseX = series.timeSeriesScaleX.invert(point.x);
+ let minDistance = Infinity;
+
+ const closestTickMark = Object.keys(this.allXAxisValues).reduce((closest, x) => {
+ const distance = Math.abs(Number(new Date(x)) - Number(mouseX));
+ if (distance < minDistance) {
+ minDistance = distance;
+ return x;
+ }
+ return closest;
+ });
+
+ return series.values.find(v => v.time.toString() === closestTickMark);
+ });
+
+ const firstTimeSeries = this.seriesUnderMouse[0];
const timeValueOverlay = firstTimeSeries.timeSeriesScaleX.invert(point.x);
const overlayIndex = bisectDate(firstTimeSeries.values, timeValueOverlay, 1);
const d0 = firstTimeSeries.values[overlayIndex - 1];
@@ -190,6 +211,17 @@ export default {
axisXScale.domain(d3.extent(allValues, d => d.time));
axisYScale.domain([0, d3.max(allValues.map(d => d.value))]);
+ this.allXAxisValues = this.timeSeries.reduce((obj, series) => {
+ const seriesKeys = {};
+ series.values.forEach(v => {
+ seriesKeys[v.time] = true;
+ });
+ return {
+ ...obj,
+ ...seriesKeys,
+ };
+ }, {});
+
const xAxis = d3
.axisBottom()
.scale(axisXScale)
@@ -277,9 +309,8 @@ export default {
:line-style="path.lineStyle"
:line-color="path.lineColor"
:area-color="path.areaColor"
- :current-coordinates="currentCoordinates[index]"
- :current-time-series-index="index"
- :show-dot="showFlagContent"
+ :current-coordinates="currentCoordinates[path.metricTag]"
+ :show-dot="showDot(path)"
/>
<graph-deployment
:deployment-data="reducedDeploymentData"
@@ -303,7 +334,7 @@ export default {
:graph-height="graphHeight"
:graph-height-offset="graphHeightOffset"
:show-flag-content="showFlagContent"
- :time-series="timeSeries"
+ :time-series="seriesUnderMouse"
:unit-of-display="unitOfDisplay"
:legend-title="legendTitle"
:deployment-flag-data="deploymentFlagData"
diff --git a/app/assets/javascripts/monitoring/components/graph/flag.vue b/app/assets/javascripts/monitoring/components/graph/flag.vue
index 1e6803abf3a..5f00d20ca3f 100644
--- a/app/assets/javascripts/monitoring/components/graph/flag.vue
+++ b/app/assets/javascripts/monitoring/components/graph/flag.vue
@@ -52,7 +52,7 @@ export default {
required: true,
},
currentCoordinates: {
- type: Array,
+ type: Object,
required: true,
},
},
@@ -91,8 +91,8 @@ export default {
},
methods: {
seriesMetricValue(seriesIndex, series) {
- const indexFromCoordinates = this.currentCoordinates[seriesIndex]
- ? this.currentCoordinates[seriesIndex].currentDataIndex : 0;
+ const indexFromCoordinates = this.currentCoordinates[series.metricTag]
+ ? this.currentCoordinates[series.metricTag].currentDataIndex : 0;
const index = this.deploymentFlagData
? this.deploymentFlagData.seriesIndex
: indexFromCoordinates;
diff --git a/app/assets/javascripts/monitoring/components/graph/legend.vue b/app/assets/javascripts/monitoring/components/graph/legend.vue
index 3276f3a1ceb..ef18ae5c2c8 100644
--- a/app/assets/javascripts/monitoring/components/graph/legend.vue
+++ b/app/assets/javascripts/monitoring/components/graph/legend.vue
@@ -58,8 +58,8 @@ export default {
</td>
<template v-for="(track, trackIndex) in series.tracksLegend">
<track-line
- :track="track"
- :key="`track-line-${trackIndex}`"/>
+ :key="`track-line-${trackIndex}`"
+ :track="track"/>
<td :key="`track-info-${trackIndex}`">
<track-info
:track="track"
diff --git a/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js b/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js
index 4f23814ff3e..007451d5c7a 100644
--- a/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js
+++ b/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js
@@ -50,19 +50,24 @@ const mixins = {
},
positionFlag() {
- const timeSeries = this.timeSeries[0];
- const hoveredDataIndex = bisectDate(timeSeries.values, this.hoverData.hoveredDate, 1);
+ const timeSeries = this.seriesUnderMouse[0];
+ if (!timeSeries) {
+ return;
+ }
+ const hoveredDataIndex = bisectDate(timeSeries.values, this.hoverData.hoveredDate);
this.currentData = timeSeries.values[hoveredDataIndex];
this.currentXCoordinate = Math.floor(timeSeries.timeSeriesScaleX(this.currentData.time));
- this.currentCoordinates = this.timeSeries.map((series) => {
- const currentDataIndex = bisectDate(series.values, this.hoverData.hoveredDate, 1);
+ this.currentCoordinates = {};
+
+ this.seriesUnderMouse.forEach((series) => {
+ const currentDataIndex = bisectDate(series.values, this.hoverData.hoveredDate);
const currentData = series.values[currentDataIndex];
const currentX = Math.floor(series.timeSeriesScaleX(currentData.time));
const currentY = Math.floor(series.timeSeriesScaleY(currentData.value));
- return {
+ this.currentCoordinates[series.metricTag] = {
currentX,
currentY,
currentDataIndex,
diff --git a/app/assets/javascripts/monitoring/utils/multiple_time_series.js b/app/assets/javascripts/monitoring/utils/multiple_time_series.js
index cee39fd0559..eff0d7325cd 100644
--- a/app/assets/javascripts/monitoring/utils/multiple_time_series.js
+++ b/app/assets/javascripts/monitoring/utils/multiple_time_series.js
@@ -2,7 +2,7 @@ import _ from 'underscore';
import { scaleLinear, scaleTime } from 'd3-scale';
import { line, area, curveLinear } from 'd3-shape';
import { extent, max, sum } from 'd3-array';
-import { timeMinute } from 'd3-time';
+import { timeMinute, timeSecond } from 'd3-time';
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
const d3 = {
@@ -14,6 +14,7 @@ const d3 = {
extent,
max,
timeMinute,
+ timeSecond,
sum,
};
@@ -51,6 +52,24 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom
return defaultColorPalette[pick];
}
+ function findByDate(series, time) {
+ const val = series.find(v => Math.abs(d3.timeSecond.count(time, v.time)) < 60);
+ if (val) {
+ return val.value;
+ }
+ return NaN;
+ }
+
+ // The timeseries data may have gaps in it
+ // but we need a regularly-spaced set of time/value pairs
+ // this gives us a complete range of one minute intervals
+ // offset the same amount as the original data
+ const [minX, maxX] = xDom;
+ const offset = d3.timeMinute(minX) - Number(minX);
+ const datesWithoutGaps = d3.timeSecond.every(60)
+ .range(d3.timeMinute.offset(minX, -1), maxX)
+ .map(d => d - offset);
+
query.result.forEach((timeSeries, timeSeriesNumber) => {
let metricTag = '';
let lineColor = '';
@@ -119,9 +138,14 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom
});
}
+ const values = datesWithoutGaps.map(time => ({
+ time,
+ value: findByDate(timeSeries.values, time),
+ }));
+
timeSeriesParsed.push({
- linePath: lineFunction(timeSeries.values),
- areaPath: areaFunction(timeSeries.values),
+ linePath: lineFunction(values),
+ areaPath: areaFunction(values),
timeSeriesScaleX,
timeSeriesScaleY,
values: timeSeries.values,
diff --git a/app/assets/javascripts/mr_notes/stores/index.js b/app/assets/javascripts/mr_notes/stores/index.js
index dd2019001db..446eb477efc 100644
--- a/app/assets/javascripts/mr_notes/stores/index.js
+++ b/app/assets/javascripts/mr_notes/stores/index.js
@@ -9,7 +9,7 @@ Vue.use(Vuex);
export default new Vuex.Store({
modules: {
page: mrPageModule,
- notes: notesModule,
- diffs: diffsModule,
+ notes: notesModule(),
+ diffs: diffsModule(),
},
});
diff --git a/app/assets/javascripts/notebook/index.vue b/app/assets/javascripts/notebook/index.vue
index e2e3b08c77f..f241df9620d 100644
--- a/app/assets/javascripts/notebook/index.vue
+++ b/app/assets/javascripts/notebook/index.vue
@@ -51,10 +51,10 @@
<template>
<div v-if="hasNotebook">
<component
- v-for="(cell, index) in cells"
:is="cellType(cell.cell_type)"
- :cell="cell"
+ v-for="(cell, index) in cells"
:key="index"
+ :cell="cell"
:code-css-class="codeCssClass" />
</div>
</template>
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index 8124ae6201f..0c966e0808a 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -154,7 +154,11 @@ export default class Notes {
this.$wrapperEl.on('click', '.system-note-commit-list-toggler', this.toggleCommitList);
this.$wrapperEl.on('click', '.js-toggle-lazy-diff', this.loadLazyDiff);
- this.$wrapperEl.on('click', '.js-toggle-lazy-diff-retry-button', this.onClickRetryLazyLoad.bind(this));
+ this.$wrapperEl.on(
+ 'click',
+ '.js-toggle-lazy-diff-retry-button',
+ this.onClickRetryLazyLoad.bind(this),
+ );
// fetch notes when tab becomes visible
this.$wrapperEl.on('visibilitychange', this.visibilityChange);
@@ -252,9 +256,7 @@ export default class Notes {
discussionNoteForm = $textarea.closest('.js-discussion-note-form');
if (discussionNoteForm.length) {
if ($textarea.val() !== '') {
- if (
- !window.confirm('Are you sure you want to cancel creating this comment?')
- ) {
+ if (!window.confirm('Are you sure you want to cancel creating this comment?')) {
return;
}
}
@@ -266,9 +268,7 @@ export default class Notes {
originalText = $textarea.closest('form').data('originalNote');
newText = $textarea.val();
if (originalText !== newText) {
- if (
- !window.confirm('Are you sure you want to cancel editing this comment?')
- ) {
+ if (!window.confirm('Are you sure you want to cancel editing this comment?')) {
return;
}
}
@@ -631,7 +631,7 @@ export default class Notes {
*
* deactivates the submit button when text is empty
* hides the preview button when text is empty
- * setup GFM auto complete
+ * set up GFM auto complete
* show the form
*/
setupNoteForm(form, enableGFM = defaultAutocompleteConfig) {
@@ -954,7 +954,7 @@ export default class Notes {
* Note: dataHolder must have the "discussionId" and "lineCode" data attributes set.
*/
setupDiscussionNoteForm(dataHolder, form) {
- // setup note target
+ // set up note target
let diffFileData = dataHolder.closest('.text-file');
if (diffFileData.length === 0) {
@@ -1036,7 +1036,7 @@ export default class Notes {
$diffFile[0].dispatchEvent(clickEvent);
- // Setup comment form
+ // Set up comment form
let newForm;
const $noteContainer = $link.closest('.diff-viewer').find('.note-container');
const $form = $noteContainer.find('> .discussion-form');
@@ -1074,7 +1074,7 @@ export default class Notes {
addForm = false;
let lineTypeSelector = '';
rowCssToAdd =
- '<tr class="notes_holder js-temp-notes-holder"><td class="notes_line" colspan="2"></td><td class="notes_content"><div class="content"></div></td></tr>';
+ '<tr class="notes_holder js-temp-notes-holder"><td class="notes_content" colspan="3"><div class="content"></div></td></tr>';
// In parallel view, look inside the correct left/right pane
if (this.isParallelView()) {
lineTypeSelector = `.${lineType}`;
@@ -1316,8 +1316,7 @@ export default class Notes {
$retryButton.prop('disabled', true);
- return this.loadLazyDiff(e)
- .then(() => {
+ return this.loadLazyDiff(e).then(() => {
$retryButton.prop('disabled', false);
});
}
@@ -1343,18 +1342,18 @@ export default class Notes {
*/
if (url) {
return axios
- .get(url)
- .then(({ data }) => {
- // Reset state in case last request returned error
- $successContainer.removeClass('hidden');
- $errorContainer.addClass('hidden');
-
- Notes.renderDiffContent($container, data);
- })
- .catch(() => {
- $successContainer.addClass('hidden');
- $errorContainer.removeClass('hidden');
- });
+ .get(url)
+ .then(({ data }) => {
+ // Reset state in case last request returned error
+ $successContainer.removeClass('hidden');
+ $errorContainer.addClass('hidden');
+
+ Notes.renderDiffContent($container, data);
+ })
+ .catch(() => {
+ $successContainer.addClass('hidden');
+ $errorContainer.removeClass('hidden');
+ });
}
return Promise.resolve();
}
@@ -1545,12 +1544,8 @@ export default class Notes {
<div class="note-header">
<div class="note-header-info">
<a href="/${_.escape(currentUsername)}">
- <span class="d-none d-sm-inline-block">${_.escape(
- currentUsername,
- )}</span>
- <span class="note-headline-light">${_.escape(
- currentUsername,
- )}</span>
+ <span class="d-none d-sm-inline-block">${_.escape(currentUsername)}</span>
+ <span class="note-headline-light">${_.escape(currentUsername)}</span>
</a>
</div>
</div>
@@ -1565,9 +1560,7 @@ export default class Notes {
);
$tempNote.find('.d-none.d-sm-inline-block').text(_.escape(currentUserFullname));
- $tempNote
- .find('.note-headline-light')
- .text(`@${_.escape(currentUsername)}`);
+ $tempNote.find('.note-headline-light').text(`@${_.escape(currentUsername)}`);
return $tempNote;
}
diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue
index 6612bc44e0b..7735133c470 100644
--- a/app/assets/javascripts/notes/components/comment_form.vue
+++ b/app/assets/javascripts/notes/components/comment_form.vue
@@ -374,7 +374,7 @@ js-gfm-input js-autosize markdown-area js-vue-textarea"
append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown">
<button
:disabled="isSubmitButtonDisabled"
- class="btn btn-create comment-btn js-comment-button js-comment-submit-button"
+ class="btn btn-success comment-btn js-comment-button js-comment-submit-button"
type="submit"
@click.prevent="handleSave()">
{{ __(commentButtonTitle) }}
diff --git a/app/assets/javascripts/notes/components/diff_file_header.vue b/app/assets/javascripts/notes/components/diff_file_header.vue
index fc7b52be241..4fd93304a03 100644
--- a/app/assets/javascripts/notes/components/diff_file_header.vue
+++ b/app/assets/javascripts/notes/components/diff_file_header.vue
@@ -41,8 +41,8 @@ export default {
</div>
<template v-else>
<component
- ref="titleWrapper"
:is="titleTag"
+ ref="titleWrapper"
:href="diffFile.discussionPath"
>
<span v-html="diffFile.blobIcon"></span>
diff --git a/app/assets/javascripts/notes/components/diff_with_note.vue b/app/assets/javascripts/notes/components/diff_with_note.vue
index 27ff7dea909..802be022ba6 100644
--- a/app/assets/javascripts/notes/components/diff_with_note.vue
+++ b/app/assets/javascripts/notes/components/diff_with_note.vue
@@ -148,10 +148,9 @@ export default {
</tr>
<tr class="notes_holder">
<td
- class="notes_line"
- colspan="2"
- ></td>
- <td class="notes_content">
+ class="notes_content"
+ colspan="3"
+ >
<slot></slot>
</td>
</tr>
diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue
index cdbbb342331..beb53da0e6d 100644
--- a/app/assets/javascripts/notes/components/note_actions.vue
+++ b/app/assets/javascripts/notes/components/note_actions.vue
@@ -7,7 +7,6 @@ import editSvg from 'icons/_icon_pencil.svg';
import resolveDiscussionSvg from 'icons/_icon_resolve_discussion.svg';
import resolvedDiscussionSvg from 'icons/_icon_status_success_solid.svg';
import ellipsisSvg from 'icons/_ellipsis_v.svg';
-import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import tooltip from '~/vue_shared/directives/tooltip';
export default {
@@ -15,21 +14,19 @@ export default {
directives: {
tooltip,
},
- components: {
- loadingIcon,
- },
props: {
authorId: {
type: Number,
required: true,
},
noteId: {
- type: Number,
+ type: String,
required: true,
},
noteUrl: {
type: String,
- required: true,
+ required: false,
+ default: '',
},
accessLevel: {
type: String,
@@ -152,9 +149,9 @@ export default {
v-else
v-html="resolveDiscussionSvg"></div>
</template>
- <loading-icon
+ <gl-loading-icon
v-else
- :inline="true"
+ inline
/>
</button>
</div>
@@ -171,7 +168,7 @@ export default {
href="#"
title="Add reaction"
>
- <loading-icon :inline="true" />
+ <gl-loading-icon inline/>
<span
class="link-highlight award-control-icon-neutral"
v-html="emojiSmiling">
@@ -225,11 +222,11 @@ export default {
Report as abuse
</a>
</li>
- <li>
+ <li v-if="noteUrl">
<button
:data-clipboard-text="noteUrl"
type="button"
- css-class="btn-default btn-transparent"
+ class="btn-default btn-transparent js-btn-copy-note-link"
>
Copy link
</button>
diff --git a/app/assets/javascripts/notes/components/note_awards_list.vue b/app/assets/javascripts/notes/components/note_awards_list.vue
index e111d3b9ac2..c68860d98ae 100644
--- a/app/assets/javascripts/notes/components/note_awards_list.vue
+++ b/app/assets/javascripts/notes/components/note_awards_list.vue
@@ -25,7 +25,7 @@ export default {
required: true,
},
noteId: {
- type: Number,
+ type: String,
required: true,
},
canAwardEmoji: {
@@ -182,9 +182,9 @@ export default {
<div class="note-awards">
<div class="awards js-awards-block">
<button
- v-tooltip
v-for="(awardList, awardName, index) in groupedAwards"
:key="index"
+ v-tooltip
:class="getAwardClassBindings(awardList)"
:title="awardTitle(awardList)"
class="btn award-control"
diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue
index abcd4422d7c..2d47d55f33c 100644
--- a/app/assets/javascripts/notes/components/note_form.vue
+++ b/app/assets/javascripts/notes/components/note_form.vue
@@ -20,9 +20,9 @@ export default {
default: '',
},
noteId: {
- type: Number,
+ type: String,
required: false,
- default: 0,
+ default: '',
},
markdownVersion: {
type: Number,
@@ -67,7 +67,10 @@ export default {
'getUserDataByProp',
]),
noteHash() {
- return `#note_${this.noteId}`;
+ if (this.noteId) {
+ return `#note_${this.noteId}`;
+ }
+ return '#';
},
markdownPreviewPath() {
return this.getNoteableDataByProp('preview_note_path');
@@ -168,8 +171,8 @@ export default {
id="note_note"
ref="textarea"
slot="textarea"
- :data-supports-quick-actions="!isEditing"
v-model="updatedNoteBody"
+ :data-supports-quick-actions="!isEditing"
name="note[note]"
class="note-textarea js-gfm-input js-note-text
js-autosize markdown-area js-vue-issue-note-form js-vue-textarea"
@@ -185,7 +188,7 @@ js-autosize markdown-area js-vue-issue-note-form js-vue-textarea"
<button
:disabled="isDisabled"
type="button"
- class="js-vue-issue-save btn btn-save js-comment-button "
+ class="js-vue-issue-save btn btn-success js-comment-button "
@click="handleUpdate()">
{{ saveButtonTitle }}
</button>
diff --git a/app/assets/javascripts/notes/components/note_header.vue b/app/assets/javascripts/notes/components/note_header.vue
index a621418cf72..d669d12a39b 100644
--- a/app/assets/javascripts/notes/components/note_header.vue
+++ b/app/assets/javascripts/notes/components/note_header.vue
@@ -9,7 +9,8 @@ export default {
props: {
author: {
type: Object,
- required: true,
+ required: false,
+ default: () => ({}),
},
createdAt: {
type: String,
@@ -21,7 +22,7 @@ export default {
default: '',
},
noteId: {
- type: Number,
+ type: String,
required: true,
},
includeToggle: {
@@ -72,7 +73,10 @@ export default {
{{ __('Toggle discussion') }}
</button>
</div>
- <a :href="author.path">
+ <a
+ v-if="Object.keys(author).length"
+ :href="author.path"
+ >
<span class="note-header-author-name">{{ author.name }}</span>
<span
v-if="author.status_tooltip_html"
@@ -81,6 +85,9 @@ export default {
@{{ author.username }}
</span>
</a>
+ <span v-else>
+ {{ __('A deleted user') }}
+ </span>
<span class="note-headline-light">
<span class="note-headline-meta">
<template v-if="actionText">
diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue
index 0fe1c16854a..6ede7562edf 100644
--- a/app/assets/javascripts/notes/components/noteable_discussion.vue
+++ b/app/assets/javascripts/notes/components/noteable_discussion.vue
@@ -137,8 +137,10 @@ export default {
return this.unresolvedDiscussions.length > 1;
},
showJumpToNextDiscussion() {
- return this.hasMultipleUnresolvedDiscussions &&
- !this.isLastUnresolvedDiscussion(this.discussion.id, this.discussionsByDiffOrder);
+ return (
+ this.hasMultipleUnresolvedDiscussions &&
+ !this.isLastUnresolvedDiscussion(this.discussion.id, this.discussionsByDiffOrder)
+ );
},
shouldRenderDiffs() {
const { diffDiscussion, diffFile } = this.transformedDiscussion;
@@ -256,11 +258,16 @@ Please check your network connection and try again.`;
});
},
jumpToNextDiscussion() {
- const nextId =
- this.nextUnresolvedDiscussionId(this.discussion.id, this.discussionsByDiffOrder);
+ const nextId = this.nextUnresolvedDiscussionId(
+ this.discussion.id,
+ this.discussionsByDiffOrder,
+ );
this.jumpToDiscussion(nextId);
},
+ deleteNoteHandler(note) {
+ this.$emit('noteDeleted', this.discussion, note);
+ },
},
};
</script>
@@ -270,6 +277,7 @@ Please check your network connection and try again.`;
<div class="timeline-entry-inner">
<div class="timeline-icon">
<user-avatar-link
+ v-if="author"
:link-href="author.path"
:img-src="author.avatar_url"
:img-alt="author.name"
@@ -340,10 +348,11 @@ Please check your network connection and try again.`;
<div class="discussion-notes">
<ul class="notes">
<component
- v-for="note in discussion.notes"
:is="componentName(note)"
- :note="componentData(note)"
+ v-for="note in discussion.notes"
:key="note.id"
+ :note="componentData(note)"
+ @handleDeleteNote="deleteNoteHandler"
/>
</ul>
<div
diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue
index 4ebeb5599f2..7579fc852c6 100644
--- a/app/assets/javascripts/notes/components/noteable_note.vue
+++ b/app/assets/javascripts/notes/components/noteable_note.vue
@@ -86,6 +86,7 @@ export default {
// eslint-disable-next-line no-alert
if (window.confirm('Are you sure you want to delete this comment?')) {
this.isDeleting = true;
+ this.$emit('handleDeleteNote', this.note);
this.deleteNote(this.note)
.then(() => {
diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue
index 9b8713b40fb..d8e8efb982a 100644
--- a/app/assets/javascripts/notes/components/notes_app.vue
+++ b/app/assets/javascripts/notes/components/notes_app.vue
@@ -10,7 +10,6 @@ import systemNote from '../../vue_shared/components/notes/system_note.vue';
import commentForm from './comment_form.vue';
import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue';
import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue';
-import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import skeletonLoadingContainer from '../../vue_shared/components/notes/skeleton_note.vue';
export default {
@@ -20,7 +19,6 @@ export default {
noteableDiscussion,
systemNote,
commentForm,
- loadingIcon,
placeholderNote,
placeholderSystemNote,
},
@@ -138,6 +136,7 @@ export default {
.then(() => {
this.isLoading = false;
this.setNotesFetchedState(true);
+ eventHub.$emit('fetchedNotesData');
})
.then(() => this.$nextTick())
.then(() => this.checkLocationHash())
@@ -188,10 +187,10 @@ export default {
class="notes main-notes-list timeline"
>
<component
- v-for="discussion in allDiscussions"
:is="getComponentName(discussion)"
- v-bind="getComponentData(discussion)"
+ v-for="discussion in allDiscussions"
:key="discussion.id"
+ v-bind="getComponentData(discussion)"
/>
</ul>
diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js
index 3eefbe11c37..320dfa47d5a 100644
--- a/app/assets/javascripts/notes/stores/actions.js
+++ b/app/assets/javascripts/notes/stores/actions.js
@@ -10,6 +10,7 @@ import service from '../services/notes_service';
import loadAwardsHandler from '../../awards_handler';
import sidebarTimeTrackingEventHub from '../../sidebar/event_hub';
import { isInViewport, scrollToElement } from '../../lib/utils/common_utils';
+import mrWidgetEventHub from '../../vue_merge_request_widget/event_hub';
let eTagPoll;
@@ -43,18 +44,17 @@ export const fetchDiscussions = ({ commit }, path) =>
commit(types.SET_INITIAL_DISCUSSIONS, discussions);
});
-export const refetchDiscussionById = ({ commit }, { path, discussionId }) =>
- service
- .fetchDiscussions(path)
- .then(res => res.json())
- .then(discussions => {
- const selectedDiscussion = discussions.find(discussion => discussion.id === discussionId);
- if (selectedDiscussion) commit(types.UPDATE_DISCUSSION, selectedDiscussion);
- });
+export const updateDiscussion = ({ commit, state }, discussion) => {
+ commit(types.UPDATE_DISCUSSION, discussion);
-export const deleteNote = ({ commit }, note) =>
+ return utils.findNoteObjectById(state.discussions, discussion.id);
+};
+
+export const deleteNote = ({ commit, dispatch }, note) =>
service.deleteNote(note.path).then(() => {
commit(types.DELETE_NOTE, note);
+
+ dispatch('updateMergeRequestWidget');
});
export const updateNote = ({ commit }, { endpoint, note }) =>
@@ -75,20 +75,22 @@ export const replyToDiscussion = ({ commit }, { endpoint, data }) =>
return res;
});
-export const createNewNote = ({ commit }, { endpoint, data }) =>
+export const createNewNote = ({ commit, dispatch }, { endpoint, data }) =>
service
.createNewNote(endpoint, data)
.then(res => res.json())
.then(res => {
if (!res.errors) {
commit(types.ADD_NEW_NOTE, res);
+
+ dispatch('updateMergeRequestWidget');
}
return res;
});
export const removePlaceholderNotes = ({ commit }) => commit(types.REMOVE_PLACEHOLDER_NOTES);
-export const toggleResolveNote = ({ commit }, { endpoint, isResolved, discussion }) =>
+export const toggleResolveNote = ({ commit, dispatch }, { endpoint, isResolved, discussion }) =>
service
.toggleResolveNote(endpoint, isResolved)
.then(res => res.json())
@@ -96,6 +98,8 @@ export const toggleResolveNote = ({ commit }, { endpoint, isResolved, discussion
const mutationType = discussion ? types.UPDATE_DISCUSSION : types.UPDATE_NOTE;
commit(mutationType, res);
+
+ dispatch('updateMergeRequestWidget');
});
export const closeIssue = ({ commit, dispatch, state }) => {
@@ -152,26 +156,28 @@ export const saveNote = ({ commit, dispatch }, noteData) => {
const replyId = noteData.data.in_reply_to_discussion_id;
const methodToDispatch = replyId ? 'replyToDiscussion' : 'createNewNote';
- commit(types.REMOVE_PLACEHOLDER_NOTES); // remove previous placeholders
$('.notes-form .flash-container').hide(); // hide previous flash notification
+ commit(types.REMOVE_PLACEHOLDER_NOTES); // remove previous placeholders
- if (hasQuickActions) {
- placeholderText = utils.stripQuickActions(placeholderText);
- }
+ if (replyId) {
+ if (hasQuickActions) {
+ placeholderText = utils.stripQuickActions(placeholderText);
+ }
- if (placeholderText.length) {
- commit(types.SHOW_PLACEHOLDER_NOTE, {
- noteBody: placeholderText,
- replyId,
- });
- }
+ if (placeholderText.length) {
+ commit(types.SHOW_PLACEHOLDER_NOTE, {
+ noteBody: placeholderText,
+ replyId,
+ });
+ }
- if (hasQuickActions) {
- commit(types.SHOW_PLACEHOLDER_NOTE, {
- isSystemNote: true,
- noteBody: utils.getQuickActionText(note),
- replyId,
- });
+ if (hasQuickActions) {
+ commit(types.SHOW_PLACEHOLDER_NOTE, {
+ isSystemNote: true,
+ noteBody: utils.getQuickActionText(note),
+ replyId,
+ });
+ }
}
return dispatch(methodToDispatch, noteData).then(res => {
@@ -211,7 +217,9 @@ export const saveNote = ({ commit, dispatch }, noteData) => {
if (errors && errors.commands_only) {
Flash(errors.commands_only, 'notice', noteData.flashContainer);
}
- commit(types.REMOVE_PLACEHOLDER_NOTES);
+ if (replyId) {
+ commit(types.REMOVE_PLACEHOLDER_NOTES);
+ }
return res;
});
@@ -320,5 +328,9 @@ export const fetchDiscussionDiffLines = ({ commit }, discussion) =>
});
});
+export const updateMergeRequestWidget = () => {
+ mrWidgetEventHub.$emit('mr.discussion.updated');
+};
+
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js
index 5b3b9f8776f..d4babf1fab2 100644
--- a/app/assets/javascripts/notes/stores/getters.js
+++ b/app/assets/javascripts/notes/stores/getters.js
@@ -1,5 +1,6 @@
import _ from 'underscore';
import * as constants from '../constants';
+import { reduceDiscussionsToLineCodes } from './utils';
import { collapseSystemNotes } from './collapse_utils';
export const discussions = state => collapseSystemNotes(state.discussions);
@@ -28,17 +29,8 @@ export const notesById = state =>
return acc;
}, {});
-export const discussionsByLineCode = state =>
- state.discussions.reduce((acc, note) => {
- if (note.diff_discussion && note.line_code && note.resolvable) {
- // For context about line notes: there might be multiple notes with the same line code
- const items = acc[note.line_code] || [];
- items.push(note);
-
- Object.assign(acc, { [note.line_code]: items });
- }
- return acc;
- }, {});
+export const discussionsStructuredByLineCode = state =>
+ reduceDiscussionsToLineCodes(state.discussions);
export const noteableType = state => {
const { ISSUE_NOTEABLE_TYPE, MERGE_REQUEST_NOTEABLE_TYPE, EPIC_NOTEABLE_TYPE } = constants;
diff --git a/app/assets/javascripts/notes/stores/index.js b/app/assets/javascripts/notes/stores/index.js
index 0f48b8880f4..f105b7d0d11 100644
--- a/app/assets/javascripts/notes/stores/index.js
+++ b/app/assets/javascripts/notes/stores/index.js
@@ -1,16 +1,8 @@
import Vue from 'vue';
import Vuex from 'vuex';
-import * as actions from './actions';
-import * as getters from './getters';
-import mutations from './mutations';
-import module from './modules';
+import notesModule from './modules';
Vue.use(Vuex);
export default () =>
- new Vuex.Store({
- state: module.state,
- actions,
- getters,
- mutations,
- });
+ new Vuex.Store(notesModule());
diff --git a/app/assets/javascripts/notes/stores/modules/index.js b/app/assets/javascripts/notes/stores/modules/index.js
index b4cb9267e0f..61dbb075586 100644
--- a/app/assets/javascripts/notes/stores/modules/index.js
+++ b/app/assets/javascripts/notes/stores/modules/index.js
@@ -2,7 +2,7 @@ import * as actions from '../actions';
import * as getters from '../getters';
import mutations from '../mutations';
-export default {
+export default () => ({
state: {
discussions: [],
targetNoteHash: null,
@@ -24,4 +24,4 @@ export default {
actions,
getters,
mutations,
-};
+});
diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js
index ab6a95e2601..73e55705f39 100644
--- a/app/assets/javascripts/notes/stores/mutations.js
+++ b/app/assets/javascripts/notes/stores/mutations.js
@@ -4,7 +4,8 @@ import * as constants from '../constants';
import { isInMRPage } from '../../lib/utils/common_utils';
export default {
- [types.ADD_NEW_NOTE](state, note) {
+ [types.ADD_NEW_NOTE](state, data) {
+ const note = data.discussion ? data.discussion.notes[0] : data;
const { discussion_id, type } = note;
const [exists] = state.discussions.filter(n => n.id === note.discussion_id);
const isDiscussion = type === constants.DISCUSSION_NOTE || type === constants.DIFF_NOTE;
@@ -54,13 +55,12 @@ export default {
[types.EXPAND_DISCUSSION](state, { discussionId }) {
const discussion = utils.findNoteObjectById(state.discussions, discussionId);
-
- discussion.expanded = true;
+ Object.assign(discussion, { expanded: true });
},
[types.COLLAPSE_DISCUSSION](state, { discussionId }) {
const discussion = utils.findNoteObjectById(state.discussions, discussionId);
- discussion.expanded = false;
+ Object.assign(discussion, { expanded: false });
},
[types.REMOVE_PLACEHOLDER_NOTES](state) {
@@ -95,10 +95,18 @@ export default {
[types.SET_USER_DATA](state, data) {
Object.assign(state, { userData: data });
},
+
[types.SET_INITIAL_DISCUSSIONS](state, discussionsData) {
const discussions = [];
discussionsData.forEach(discussion => {
+ if (discussion.diff_file) {
+ Object.assign(discussion, {
+ fileHash: discussion.diff_file.file_hash,
+ truncated_diff_lines: discussion.truncated_diff_lines || [],
+ });
+ }
+
// To support legacy notes, should be very rare case.
if (discussion.individual_note && discussion.notes.length > 1) {
discussion.notes.forEach(n => {
@@ -168,8 +176,7 @@ export default {
[types.TOGGLE_DISCUSSION](state, { discussionId }) {
const discussion = utils.findNoteObjectById(state.discussions, discussionId);
-
- discussion.expanded = !discussion.expanded;
+ Object.assign(discussion, { expanded: !discussion.expanded });
},
[types.UPDATE_NOTE](state, note) {
@@ -185,16 +192,12 @@ export default {
[types.UPDATE_DISCUSSION](state, noteData) {
const note = noteData;
- let index = 0;
-
- state.discussions.forEach((n, i) => {
- if (n.id === note.id) {
- index = i;
- }
- });
-
+ const selectedDiscussion = state.discussions.find(disc => disc.id === note.id);
note.expanded = true; // override expand flag to prevent collapse
- state.discussions.splice(index, 1, note);
+ if (note.diff_file) {
+ Object.assign(note, { fileHash: note.diff_file.file_hash });
+ }
+ Object.assign(selectedDiscussion, { ...note });
},
[types.CLOSE_ISSUE](state) {
@@ -215,12 +218,7 @@ export default {
[types.SET_DISCUSSION_DIFF_LINES](state, { discussionId, diffLines }) {
const discussion = utils.findNoteObjectById(state.discussions, discussionId);
- const index = state.discussions.indexOf(discussion);
-
- const discussionWithDiffLines = Object.assign({}, discussion, {
- truncated_diff_lines: diffLines,
- });
- state.discussions.splice(index, 1, discussionWithDiffLines);
+ discussion.truncated_diff_lines = diffLines;
},
};
diff --git a/app/assets/javascripts/notes/stores/utils.js b/app/assets/javascripts/notes/stores/utils.js
index a0e096ebfaf..0e41ff03d67 100644
--- a/app/assets/javascripts/notes/stores/utils.js
+++ b/app/assets/javascripts/notes/stores/utils.js
@@ -2,13 +2,11 @@ import AjaxCache from '~/lib/utils/ajax_cache';
const REGEX_QUICK_ACTIONS = /^\/\w+.*$/gm;
-export const findNoteObjectById = (notes, id) =>
- notes.filter(n => n.id === id)[0];
+export const findNoteObjectById = (notes, id) => notes.filter(n => n.id === id)[0];
export const getQuickActionText = note => {
let text = 'Applying command';
- const quickActions =
- AjaxCache.get(gl.GfmAutoComplete.dataSources.commands) || [];
+ const quickActions = AjaxCache.get(gl.GfmAutoComplete.dataSources.commands) || [];
const executedCommands = quickActions.filter(command => {
const commandRegex = new RegExp(`/${command.name}`);
@@ -27,7 +25,18 @@ export const getQuickActionText = note => {
return text;
};
+export const reduceDiscussionsToLineCodes = selectedDiscussions =>
+ selectedDiscussions.reduce((acc, note) => {
+ if (note.diff_discussion && note.line_code) {
+ // For context about line notes: there might be multiple notes with the same line code
+ const items = acc[note.line_code] || [];
+ items.push(note);
+
+ Object.assign(acc, { [note.line_code]: items });
+ }
+ return acc;
+ }, {});
+
export const hasQuickActions = note => REGEX_QUICK_ACTIONS.test(note);
-export const stripQuickActions = note =>
- note.replace(REGEX_QUICK_ACTIONS, '').trim();
+export const stripQuickActions = note => note.replace(REGEX_QUICK_ACTIONS, '').trim();
diff --git a/app/assets/javascripts/pages/admin/application_settings/metrics_and_profiling/index.js b/app/assets/javascripts/pages/admin/application_settings/metrics_and_profiling/index.js
new file mode 100644
index 00000000000..c40503603be
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/application_settings/metrics_and_profiling/index.js
@@ -0,0 +1,8 @@
+import UsagePingPayload from './../usage_ping_payload';
+
+document.addEventListener('DOMContentLoaded', () => {
+ new UsagePingPayload(
+ document.querySelector('.js-usage-ping-payload-trigger'),
+ document.querySelector('.js-usage-ping-payload'),
+ ).init();
+});
diff --git a/app/assets/javascripts/pages/admin/application_settings/usage_ping_payload.js b/app/assets/javascripts/pages/admin/application_settings/usage_ping_payload.js
new file mode 100644
index 00000000000..9a1bc46bf4a
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/application_settings/usage_ping_payload.js
@@ -0,0 +1,62 @@
+import axios from '../../../lib/utils/axios_utils';
+import { __ } from '../../../locale';
+import flash from '../../../flash';
+
+export default class UsagePingPayload {
+ constructor(trigger, container) {
+ this.trigger = trigger;
+ this.container = container;
+ this.isVisible = false;
+ this.isInserted = false;
+ }
+
+ init() {
+ this.spinner = this.trigger.querySelector('.js-spinner');
+ this.text = this.trigger.querySelector('.js-text');
+
+ this.trigger.addEventListener('click', event => {
+ event.preventDefault();
+
+ if (this.isVisible) return this.hidePayload();
+
+ return this.requestPayload();
+ });
+ }
+
+ requestPayload() {
+ if (this.isInserted) return this.showPayload();
+
+ this.spinner.classList.add('d-inline');
+
+ return axios
+ .get(this.container.dataset.endpoint, {
+ responseType: 'text',
+ })
+ .then(({ data }) => {
+ this.spinner.classList.remove('d-inline');
+ this.insertPayload(data);
+ })
+ .catch(() => {
+ this.spinner.classList.remove('d-inline');
+ flash(__('Error fetching usage ping data.'));
+ });
+ }
+
+ hidePayload() {
+ this.isVisible = false;
+ this.container.classList.add('d-none');
+ this.text.textContent = __('Preview payload');
+ }
+
+ showPayload() {
+ this.isVisible = true;
+ this.container.classList.remove('d-none');
+ this.text.textContent = __('Hide payload');
+ }
+
+ insertPayload(data) {
+ this.isInserted = true;
+ this.container.innerHTML = data;
+ this.showPayload();
+ }
+}
diff --git a/app/assets/javascripts/pages/admin/runners/index.js b/app/assets/javascripts/pages/admin/runners/index.js
new file mode 100644
index 00000000000..ce8fd18b6a2
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/runners/index.js
@@ -0,0 +1,10 @@
+import initFilteredSearch from '~/pages/search/init_filtered_search';
+import AdminRunnersFilteredSearchTokenKeys from '~/filtered_search/admin_runners_filtered_search_token_keys';
+import { FILTERED_SEARCH } from '~/pages/constants';
+
+document.addEventListener('DOMContentLoaded', () => {
+ initFilteredSearch({
+ page: FILTERED_SEARCH.ADMIN_RUNNERS,
+ filteredSearchTokenKeys: AdminRunnersFilteredSearchTokenKeys,
+ });
+});
diff --git a/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue b/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue
index d6aa4bb95d2..8d5efcdcd96 100644
--- a/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue
+++ b/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue
@@ -155,10 +155,7 @@
/>
</form>
</template>
- <template
- slot="secondary-button"
- slot-scope="props"
- >
+ <template slot="secondary-button">
<button
:disabled="!canSubmit"
type="button"
diff --git a/app/assets/javascripts/pages/constants.js b/app/assets/javascripts/pages/constants.js
index 328b6541636..5e119454ce1 100644
--- a/app/assets/javascripts/pages/constants.js
+++ b/app/assets/javascripts/pages/constants.js
@@ -3,4 +3,5 @@
export const FILTERED_SEARCH = {
MERGE_REQUESTS: 'merge_requests',
ISSUES: 'issues',
+ ADMIN_RUNNERS: 'admin/runners',
};
diff --git a/app/assets/javascripts/pages/dashboard/groups/index/index.js b/app/assets/javascripts/pages/dashboard/groups/index/index.js
index 79987642796..b9277106a71 100644
--- a/app/assets/javascripts/pages/dashboard/groups/index/index.js
+++ b/app/assets/javascripts/pages/dashboard/groups/index/index.js
@@ -1,3 +1,5 @@
import initGroupsList from '~/groups';
-document.addEventListener('DOMContentLoaded', initGroupsList);
+document.addEventListener('DOMContentLoaded', () => {
+ initGroupsList();
+});
diff --git a/app/assets/javascripts/pages/groups/boards/index.js b/app/assets/javascripts/pages/groups/boards/index.js
index 5cfe8723204..79c3be771d0 100644
--- a/app/assets/javascripts/pages/groups/boards/index.js
+++ b/app/assets/javascripts/pages/groups/boards/index.js
@@ -1,5 +1,5 @@
import UsersSelect from '~/users_select';
-import ShortcutsNavigation from '~/shortcuts_navigation';
+import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import initBoards from '~/boards';
document.addEventListener('DOMContentLoaded', () => {
diff --git a/app/assets/javascripts/pages/groups/issues/index.js b/app/assets/javascripts/pages/groups/issues/index.js
index 914f804fdd3..736c6a62610 100644
--- a/app/assets/javascripts/pages/groups/issues/index.js
+++ b/app/assets/javascripts/pages/groups/issues/index.js
@@ -1,11 +1,13 @@
import projectSelect from '~/project_select';
import initFilteredSearch from '~/pages/search/init_filtered_search';
import { FILTERED_SEARCH } from '~/pages/constants';
+import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
document.addEventListener('DOMContentLoaded', () => {
initFilteredSearch({
page: FILTERED_SEARCH.ISSUES,
isGroupDecendent: true,
+ filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys,
});
projectSelect();
});
diff --git a/app/assets/javascripts/pages/groups/merge_requests/index.js b/app/assets/javascripts/pages/groups/merge_requests/index.js
index 1600faa3611..b798a254459 100644
--- a/app/assets/javascripts/pages/groups/merge_requests/index.js
+++ b/app/assets/javascripts/pages/groups/merge_requests/index.js
@@ -1,11 +1,13 @@
import projectSelect from '~/project_select';
import initFilteredSearch from '~/pages/search/init_filtered_search';
+import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
import { FILTERED_SEARCH } from '~/pages/constants';
document.addEventListener('DOMContentLoaded', () => {
initFilteredSearch({
page: FILTERED_SEARCH.MERGE_REQUESTS,
isGroupDecendent: true,
+ filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys,
});
projectSelect();
});
diff --git a/app/assets/javascripts/pages/groups/show/group_tabs.js b/app/assets/javascripts/pages/groups/show/group_tabs.js
new file mode 100644
index 00000000000..c6fe61d2bd9
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/show/group_tabs.js
@@ -0,0 +1,136 @@
+import $ from 'jquery';
+import { removeParams } from '~/lib/utils/url_utility';
+import createGroupTree from '~/groups';
+import {
+ ACTIVE_TAB_SUBGROUPS_AND_PROJECTS,
+ ACTIVE_TAB_SHARED,
+ ACTIVE_TAB_ARCHIVED,
+ CONTENT_LIST_CLASS,
+ GROUPS_LIST_HOLDER_CLASS,
+ GROUPS_FILTER_FORM_CLASS,
+} from '~/groups/constants';
+import UserTabs from '~/pages/users/user_tabs';
+import GroupFilterableList from '~/groups/groups_filterable_list';
+
+export default class GroupTabs extends UserTabs {
+ constructor({ defaultAction = 'subgroups_and_projects', action, parentEl }) {
+ super({ defaultAction, action, parentEl });
+ }
+
+ bindEvents() {
+ this.$parentEl
+ .off('shown.bs.tab', '.nav-links a[data-toggle="tab"]')
+ .on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', event => this.tabShown(event));
+ }
+
+ tabShown(event) {
+ const $target = $(event.target);
+ const action = $target.data('action') || $target.data('targetSection');
+ const source = $target.attr('href') || $target.data('targetPath');
+
+ document.querySelector(GROUPS_FILTER_FORM_CLASS).action = source;
+
+ this.setTab(action);
+ return this.setCurrentAction(source);
+ }
+
+ setTab(action) {
+ const loadableActions = [
+ ACTIVE_TAB_SUBGROUPS_AND_PROJECTS,
+ ACTIVE_TAB_SHARED,
+ ACTIVE_TAB_ARCHIVED,
+ ];
+ this.enableSearchBar(action);
+ this.action = action;
+
+ if (this.loaded[action]) {
+ return;
+ }
+
+ if (loadableActions.includes(action)) {
+ this.cleanFilterState();
+ this.loadTab(action);
+ }
+ }
+
+ loadTab(action) {
+ const elId = `js-groups-${action}-tree`;
+ const endpoint = this.getEndpoint(action);
+
+ this.toggleLoading(true);
+
+ createGroupTree(elId, endpoint, action);
+ this.loaded[action] = true;
+
+ this.toggleLoading(false);
+ }
+
+ getEndpoint(action) {
+ const { endpointsDefault, endpointsShared } = this.$parentEl.data();
+ let endpoint;
+
+ switch (action) {
+ case ACTIVE_TAB_ARCHIVED:
+ endpoint = `${endpointsDefault}?archived=only`;
+ break;
+ case ACTIVE_TAB_SHARED:
+ endpoint = endpointsShared;
+ break;
+ default:
+ // ACTIVE_TAB_SUBGROUPS_AND_PROJECTS
+ endpoint = endpointsDefault;
+ break;
+ }
+
+ return endpoint;
+ }
+
+ enableSearchBar(action) {
+ const containerEl = document.getElementById(action);
+ const form = document.querySelector(GROUPS_FILTER_FORM_CLASS);
+ const filter = form.querySelector('.js-groups-list-filter');
+ const holder = containerEl.querySelector(GROUPS_LIST_HOLDER_CLASS);
+ const dataEl = containerEl.querySelector(CONTENT_LIST_CLASS);
+ const endpoint = this.getEndpoint(action);
+
+ if (!dataEl) {
+ return;
+ }
+
+ const { dataset } = dataEl;
+ const opts = {
+ form,
+ filter,
+ holder,
+ filterEndpoint: endpoint || dataset.endpoint,
+ pagePath: null,
+ dropdownSel: '.js-group-filter-dropdown-wrap',
+ filterInputField: 'filter',
+ action,
+ };
+
+ if (!this.loaded[action]) {
+ const filterableList = new GroupFilterableList(opts);
+ filterableList.initSearch();
+ }
+ }
+
+ cleanFilterState() {
+ const values = Object.values(this.loaded);
+ const loadedTabs = values.filter(e => e === true);
+
+ if (!loadedTabs.length) {
+ return;
+ }
+
+ const newState = removeParams(['page'], window.location.search);
+
+ window.history.replaceState(
+ {
+ url: newState,
+ },
+ document.title,
+ newState,
+ );
+ }
+}
diff --git a/app/assets/javascripts/pages/groups/show/index.js b/app/assets/javascripts/pages/groups/show/index.js
index d7b35d2b26b..3a45fd70d02 100644
--- a/app/assets/javascripts/pages/groups/show/index.js
+++ b/app/assets/javascripts/pages/groups/show/index.js
@@ -1,14 +1,22 @@
/* eslint-disable no-new */
+import { getPagePath } from '~/lib/utils/common_utils';
+import { ACTIVE_TAB_SHARED, ACTIVE_TAB_ARCHIVED } from '~/groups/constants';
import NewGroupChild from '~/groups/new_group_child';
import notificationsDropdown from '~/notifications_dropdown';
import NotificationsForm from '~/notifications_form';
import ProjectsList from '~/projects_list';
-import ShortcutsNavigation from '~/shortcuts_navigation';
-import initGroupsList from '~/groups';
+import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
+import GroupTabs from './group_tabs';
document.addEventListener('DOMContentLoaded', () => {
const newGroupChildWrapper = document.querySelector('.js-new-project-subgroup');
+ const loadableActions = [ACTIVE_TAB_SHARED, ACTIVE_TAB_ARCHIVED];
+ const paths = window.location.pathname.split('/');
+ const subpath = paths[paths.length - 1];
+ const action = loadableActions.includes(subpath) ? subpath : getPagePath(1);
+
+ new GroupTabs({ parentEl: '.groups-listing', action });
new ShortcutsNavigation();
new NotificationsForm();
notificationsDropdown();
@@ -17,6 +25,4 @@ document.addEventListener('DOMContentLoaded', () => {
if (newGroupChildWrapper) {
new NewGroupChild(newGroupChildWrapper);
}
-
- initGroupsList();
});
diff --git a/app/assets/javascripts/pages/instance_statistics/cohorts/index.js b/app/assets/javascripts/pages/instance_statistics/cohorts/index.js
deleted file mode 100644
index 2d5020dbef4..00000000000
--- a/app/assets/javascripts/pages/instance_statistics/cohorts/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import initUsagePing from './usage_ping';
-
-document.addEventListener('DOMContentLoaded', initUsagePing);
diff --git a/app/assets/javascripts/pages/instance_statistics/cohorts/usage_ping.js b/app/assets/javascripts/pages/instance_statistics/cohorts/usage_ping.js
deleted file mode 100644
index 914a9661c27..00000000000
--- a/app/assets/javascripts/pages/instance_statistics/cohorts/usage_ping.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import axios from '../../../lib/utils/axios_utils';
-import { __ } from '../../../locale';
-import flash from '../../../flash';
-
-export default function UsagePing() {
- const el = document.querySelector('.usage-data');
-
- axios.get(el.dataset.endpoint, {
- responseType: 'text',
- }).then(({ data }) => {
- el.innerHTML = data;
- }).catch(() => flash(__('Error fetching usage ping data.')));
-}
diff --git a/app/assets/javascripts/pages/projects/activity/index.js b/app/assets/javascripts/pages/projects/activity/index.js
index 5543ad82428..d39ea3d10bf 100644
--- a/app/assets/javascripts/pages/projects/activity/index.js
+++ b/app/assets/javascripts/pages/projects/activity/index.js
@@ -1,5 +1,5 @@
import Activities from '~/activities';
-import ShortcutsNavigation from '~/shortcuts_navigation';
+import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
document.addEventListener('DOMContentLoaded', () => {
new Activities(); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/pages/projects/artifacts/browse/index.js b/app/assets/javascripts/pages/projects/artifacts/browse/index.js
index ea7458fe9b8..26dc90a56d7 100644
--- a/app/assets/javascripts/pages/projects/artifacts/browse/index.js
+++ b/app/assets/javascripts/pages/projects/artifacts/browse/index.js
@@ -1,5 +1,5 @@
import BuildArtifacts from '~/build_artifacts';
-import ShortcutsNavigation from '~/shortcuts_navigation';
+import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
document.addEventListener('DOMContentLoaded', () => {
new ShortcutsNavigation(); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/pages/projects/artifacts/file/index.js b/app/assets/javascripts/pages/projects/artifacts/file/index.js
index 8484e5e9848..249900d6cb7 100644
--- a/app/assets/javascripts/pages/projects/artifacts/file/index.js
+++ b/app/assets/javascripts/pages/projects/artifacts/file/index.js
@@ -1,5 +1,5 @@
import BlobViewer from '~/blob/viewer/index';
-import ShortcutsNavigation from '~/shortcuts_navigation';
+import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
document.addEventListener('DOMContentLoaded', () => {
new ShortcutsNavigation(); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/pages/projects/boards/index.js b/app/assets/javascripts/pages/projects/boards/index.js
index 5cfe8723204..79c3be771d0 100644
--- a/app/assets/javascripts/pages/projects/boards/index.js
+++ b/app/assets/javascripts/pages/projects/boards/index.js
@@ -1,5 +1,5 @@
import UsersSelect from '~/users_select';
-import ShortcutsNavigation from '~/shortcuts_navigation';
+import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import initBoards from '~/boards';
document.addEventListener('DOMContentLoaded', () => {
diff --git a/app/assets/javascripts/pages/projects/commit/show/index.js b/app/assets/javascripts/pages/projects/commit/show/index.js
index 2e23cce11ce..f477424811d 100644
--- a/app/assets/javascripts/pages/projects/commit/show/index.js
+++ b/app/assets/javascripts/pages/projects/commit/show/index.js
@@ -3,7 +3,7 @@
import $ from 'jquery';
import Diff from '~/diff';
import ZenMode from '~/zen_mode';
-import ShortcutsNavigation from '~/shortcuts_navigation';
+import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown';
import initNotes from '~/init_notes';
import initChangesDropdown from '~/init_changes_dropdown';
diff --git a/app/assets/javascripts/pages/projects/commits/show/index.js b/app/assets/javascripts/pages/projects/commits/show/index.js
index 3682020579b..ad671ce9351 100644
--- a/app/assets/javascripts/pages/projects/commits/show/index.js
+++ b/app/assets/javascripts/pages/projects/commits/show/index.js
@@ -1,6 +1,6 @@
import CommitsList from '~/commits';
import GpgBadges from '~/gpg_badges';
-import ShortcutsNavigation from '~/shortcuts_navigation';
+import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
document.addEventListener('DOMContentLoaded', () => {
new CommitsList(document.querySelector('.js-project-commits-show').dataset.commitsLimit); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/pages/projects/find_file/show/index.js b/app/assets/javascripts/pages/projects/find_file/show/index.js
index 24630c2aa05..388d7d7bdda 100644
--- a/app/assets/javascripts/pages/projects/find_file/show/index.js
+++ b/app/assets/javascripts/pages/projects/find_file/show/index.js
@@ -1,6 +1,6 @@
import $ from 'jquery';
import ProjectFindFile from '~/project_find_file';
-import ShortcutsFindFile from '~/shortcuts_find_file';
+import ShortcutsFindFile from '~/behaviors/shortcuts/shortcuts_find_file';
document.addEventListener('DOMContentLoaded', () => {
const findElement = document.querySelector('.js-file-finder');
diff --git a/app/assets/javascripts/pages/projects/index.js b/app/assets/javascripts/pages/projects/index.js
index cc0e6553e83..5659e13981a 100644
--- a/app/assets/javascripts/pages/projects/index.js
+++ b/app/assets/javascripts/pages/projects/index.js
@@ -1,7 +1,7 @@
-import gcpSignupOffer from '~/clusters/components/gcp_signup_offer';
+import initDismissableCallout from '~/dismissable_callout';
import initGkeDropdowns from '~/projects/gke_cluster_dropdowns';
import Project from './project';
-import ShortcutsNavigation from '../../shortcuts_navigation';
+import ShortcutsNavigation from '../../behaviors/shortcuts/shortcuts_navigation';
document.addEventListener('DOMContentLoaded', () => {
const { page } = document.body.dataset;
@@ -12,7 +12,7 @@ document.addEventListener('DOMContentLoaded', () => {
];
if (newClusterViews.indexOf(page) > -1) {
- gcpSignupOffer();
+ initDismissableCallout('.gcp-signup-offer');
initGkeDropdowns();
}
diff --git a/app/assets/javascripts/pages/projects/init_blob.js b/app/assets/javascripts/pages/projects/init_blob.js
index 56ab3fcdfcb..bc08ccf3584 100644
--- a/app/assets/javascripts/pages/projects/init_blob.js
+++ b/app/assets/javascripts/pages/projects/init_blob.js
@@ -1,7 +1,7 @@
import LineHighlighter from '~/line_highlighter';
import BlobLinePermalinkUpdater from '~/blob/blob_line_permalink_updater';
-import ShortcutsNavigation from '~/shortcuts_navigation';
-import ShortcutsBlob from '~/shortcuts_blob';
+import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
+import ShortcutsBlob from '~/behaviors/shortcuts/shortcuts_blob';
import BlobForkSuggestion from '~/blob/blob_fork_suggestion';
import initBlobBundle from '~/blob_edit/blob_bundle';
diff --git a/app/assets/javascripts/pages/projects/issues/form.js b/app/assets/javascripts/pages/projects/issues/form.js
index b2b8e5d2300..197bfa8a394 100644
--- a/app/assets/javascripts/pages/projects/issues/form.js
+++ b/app/assets/javascripts/pages/projects/issues/form.js
@@ -5,7 +5,7 @@ import GLForm from '~/gl_form';
import IssuableForm from '~/issuable_form';
import LabelsSelect from '~/labels_select';
import MilestoneSelect from '~/milestone_select';
-import ShortcutsNavigation from '~/shortcuts_navigation';
+import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import IssuableTemplateSelectors from '~/templates/issuable_template_selectors';
export default () => {
diff --git a/app/assets/javascripts/pages/projects/issues/index/index.js b/app/assets/javascripts/pages/projects/issues/index/index.js
index 70fdb0ef40d..a56c0bb6be8 100644
--- a/app/assets/javascripts/pages/projects/issues/index/index.js
+++ b/app/assets/javascripts/pages/projects/issues/index/index.js
@@ -1,15 +1,17 @@
/* eslint-disable no-new */
import IssuableIndex from '~/issuable_index';
-import ShortcutsNavigation from '~/shortcuts_navigation';
+import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import UsersSelect from '~/users_select';
import initFilteredSearch from '~/pages/search/init_filtered_search';
+import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
import { FILTERED_SEARCH } from '~/pages/constants';
import { ISSUABLE_INDEX } from '~/pages/projects/constants';
document.addEventListener('DOMContentLoaded', () => {
initFilteredSearch({
page: FILTERED_SEARCH.ISSUES,
+ filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys,
});
new IssuableIndex(ISSUABLE_INDEX.ISSUE);
diff --git a/app/assets/javascripts/pages/projects/issues/show.js b/app/assets/javascripts/pages/projects/issues/show.js
index 500fbd27340..74b3a515e84 100644
--- a/app/assets/javascripts/pages/projects/issues/show.js
+++ b/app/assets/javascripts/pages/projects/issues/show.js
@@ -1,6 +1,6 @@
import initIssuableSidebar from '~/init_issuable_sidebar';
import Issue from '~/issue';
-import ShortcutsIssuable from '~/shortcuts_issuable';
+import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
import ZenMode from '~/zen_mode';
import '~/notes/index';
import '~/issue_show/index';
diff --git a/app/assets/javascripts/pages/projects/merge_requests/index/index.js b/app/assets/javascripts/pages/projects/merge_requests/index/index.js
index a7aa616319f..3647048a872 100644
--- a/app/assets/javascripts/pages/projects/merge_requests/index/index.js
+++ b/app/assets/javascripts/pages/projects/merge_requests/index/index.js
@@ -1,13 +1,15 @@
import IssuableIndex from '~/issuable_index';
-import ShortcutsNavigation from '~/shortcuts_navigation';
+import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import UsersSelect from '~/users_select';
import initFilteredSearch from '~/pages/search/init_filtered_search';
+import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
import { FILTERED_SEARCH } from '~/pages/constants';
import { ISSUABLE_INDEX } from '~/pages/projects/constants';
document.addEventListener('DOMContentLoaded', () => {
initFilteredSearch({
page: FILTERED_SEARCH.MERGE_REQUESTS,
+ filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys,
});
new IssuableIndex(ISSUABLE_INDEX.MERGE_REQUEST); // eslint-disable-line no-new
new ShortcutsNavigation(); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js
index 3a3c21f2202..e3971618da5 100644
--- a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js
+++ b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js
@@ -2,7 +2,7 @@
import $ from 'jquery';
import Diff from '~/diff';
-import ShortcutsNavigation from '~/shortcuts_navigation';
+import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import GLForm from '~/gl_form';
import IssuableForm from '~/issuable_form';
import LabelsSelect from '~/labels_select';
diff --git a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js
index 26ead75cec4..7bfb83a2204 100644
--- a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js
+++ b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js
@@ -1,6 +1,6 @@
import ZenMode from '~/zen_mode';
import initIssuableSidebar from '~/init_issuable_sidebar';
-import ShortcutsIssuable from '~/shortcuts_issuable';
+import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
import { handleLocationHash } from '~/lib/utils/common_utils';
import howToMerge from '~/how_to_merge';
import initPipelines from '~/commit/pipelines/pipelines_bundle';
diff --git a/app/assets/javascripts/pages/projects/network/show/index.js b/app/assets/javascripts/pages/projects/network/show/index.js
index a0b14fed10f..9f05f63b742 100644
--- a/app/assets/javascripts/pages/projects/network/show/index.js
+++ b/app/assets/javascripts/pages/projects/network/show/index.js
@@ -1,5 +1,5 @@
import $ from 'jquery';
-import ShortcutsNetwork from '../../../../shortcuts_network';
+import ShortcutsNetwork from '~/behaviors/shortcuts/shortcuts_network';
import Network from '../network';
document.addEventListener('DOMContentLoaded', () => {
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue
index 0d05668b285..ef53d67e7cb 100644
--- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue
@@ -147,8 +147,8 @@
<div class="cron-interval-input-wrapper">
<input
id="schedule_cron"
- :placeholder="__('Define a custom pattern with cron syntax')"
v-model="cronInterval"
+ :placeholder="__('Define a custom pattern with cron syntax')"
:name="inputNameAttribute"
:disabled="!isEditable"
class="form-control inline cron-interval-input"
diff --git a/app/assets/javascripts/pages/projects/project.js b/app/assets/javascripts/pages/projects/project.js
index a853624e944..34a13eb3251 100644
--- a/app/assets/javascripts/pages/projects/project.js
+++ b/app/assets/javascripts/pages/projects/project.js
@@ -13,40 +13,59 @@ export default class Project {
constructor() {
const $cloneOptions = $('ul.clone-options-dropdown');
const $projectCloneField = $('#project_clone');
- const $cloneBtnText = $('a.clone-dropdown-btn span');
+ const $cloneBtnLabel = $('.js-git-clone-holder .js-clone-dropdown-label');
- const selectedCloneOption = $cloneBtnText.text().trim();
+ const selectedCloneOption = $cloneBtnLabel.text().trim();
if (selectedCloneOption.length > 0) {
$(`a:contains('${selectedCloneOption}')`, $cloneOptions).addClass('is-active');
}
- $('a', $cloneOptions).on('click', (e) => {
+ $('a', $cloneOptions).on('click', e => {
+ e.preventDefault();
const $this = $(e.currentTarget);
const url = $this.attr('href');
- const activeText = $this.find('.dropdown-menu-inner-title').text();
+ const cloneType = $this.data('cloneType');
- e.preventDefault();
+ $('.is-active', $cloneOptions).removeClass('is-active');
+ $(`a[data-clone-type="${cloneType}"]`).each(function() {
+ const $el = $(this);
+ const activeText = $el.find('.dropdown-menu-inner-title').text();
+ const $container = $el.closest('.project-clone-holder');
+ const $label = $container.find('.js-clone-dropdown-label');
- $('.is-active', $cloneOptions).not($this).removeClass('is-active');
- $this.toggleClass('is-active');
- $projectCloneField.val(url);
- $cloneBtnText.text(activeText);
+ $el.toggleClass('is-active');
+ $label.text(activeText);
+ });
- return $('.clone').text(url);
+ $projectCloneField.val(url);
+ $('.js-git-empty .js-clone').text(url);
});
// Ref switcher
Project.initRefSwitcher();
$('.project-refs-select').on('change', function() {
- return $(this).parents('form').submit();
+ return $(this)
+ .parents('form')
+ .submit();
});
$('.hide-no-ssh-message').on('click', function(e) {
Cookies.set('hide_no_ssh_message', 'false');
- $(this).parents('.no-ssh-key-message').remove();
+ $(this)
+ .parents('.no-ssh-key-message')
+ .remove();
return e.preventDefault();
});
$('.hide-no-password-message').on('click', function(e) {
Cookies.set('hide_no_password_message', 'false');
- $(this).parents('.no-password-message').remove();
+ $(this)
+ .parents('.no-password-message')
+ .remove();
+ return e.preventDefault();
+ });
+ $('.hide-auto-devops-implicitly-enabled-banner').on('click', function(e) {
+ const projectId = $(this).data('project-id');
+ const cookieKey = `hide_auto_devops_implicitly_enabled_banner_${projectId}`;
+ Cookies.set(cookieKey, 'false');
+ $(this).parents('.auto-devops-implicitly-enabled-banner').remove();
return e.preventDefault();
});
Project.projectSelectDropdown();
@@ -58,7 +77,7 @@ export default class Project {
}
static changeProject(url) {
- return window.location = url;
+ return (window.location = url);
}
static initRefSwitcher() {
@@ -73,14 +92,15 @@ export default class Project {
selected = $dropdown.data('selected');
return $dropdown.glDropdown({
data(term, callback) {
- axios.get($dropdown.data('refsUrl'), {
- params: {
- ref: $dropdown.data('ref'),
- search: term,
- },
- })
- .then(({ data }) => callback(data))
- .catch(() => flash(__('An error occurred while getting projects')));
+ axios
+ .get($dropdown.data('refsUrl'), {
+ params: {
+ ref: $dropdown.data('ref'),
+ search: term,
+ },
+ })
+ .then(({ data }) => callback(data))
+ .catch(() => flash(__('An error occurred while getting projects')));
},
selectable: true,
filterable: true,
diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
index ae88b765abf..875f6928bed 100644
--- a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
+++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
@@ -240,8 +240,8 @@
help-text="Lightweight issue tracking system for this project"
>
<project-feature-setting
- :options="featureAccessLevelOptions"
v-model="issuesAccessLevel"
+ :options="featureAccessLevelOptions"
name="project[project_feature_attributes][issues_access_level]"
/>
</project-setting-row>
@@ -250,8 +250,8 @@
help-text="View and edit files in this project"
>
<project-feature-setting
- :options="featureAccessLevelOptions"
v-model="repositoryAccessLevel"
+ :options="featureAccessLevelOptions"
name="project[project_feature_attributes][repository_access_level]"
/>
</project-setting-row>
@@ -261,8 +261,8 @@
help-text="Submit changes to be merged upstream"
>
<project-feature-setting
- :options="repoFeatureAccessLevelOptions"
v-model="mergeRequestsAccessLevel"
+ :options="repoFeatureAccessLevelOptions"
:disabled-input="!repositoryEnabled"
name="project[project_feature_attributes][merge_requests_access_level]"
/>
@@ -272,8 +272,8 @@
help-text="Build, test, and deploy your changes"
>
<project-feature-setting
- :options="repoFeatureAccessLevelOptions"
v-model="buildsAccessLevel"
+ :options="repoFeatureAccessLevelOptions"
:disabled-input="!repositoryEnabled"
name="project[project_feature_attributes][builds_access_level]"
/>
@@ -308,8 +308,8 @@
help-text="Pages for project documentation"
>
<project-feature-setting
- :options="featureAccessLevelOptions"
v-model="wikiAccessLevel"
+ :options="featureAccessLevelOptions"
name="project[project_feature_attributes][wiki_access_level]"
/>
</project-setting-row>
@@ -318,8 +318,8 @@
help-text="Share code pastes with others out of Git repository"
>
<project-feature-setting
- :options="featureAccessLevelOptions"
v-model="snippetsAccessLevel"
+ :options="featureAccessLevelOptions"
name="project[project_feature_attributes][snippets_access_level]"
/>
</project-setting-row>
diff --git a/app/assets/javascripts/pages/projects/show/index.js b/app/assets/javascripts/pages/projects/show/index.js
index b76f2f76449..7302c1ab202 100644
--- a/app/assets/javascripts/pages/projects/show/index.js
+++ b/app/assets/javascripts/pages/projects/show/index.js
@@ -1,6 +1,6 @@
import $ from 'jquery';
import initBlob from '~/blob_edit/blob_bundle';
-import ShortcutsNavigation from '~/shortcuts_navigation';
+import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import NotificationsForm from '~/notifications_form';
import UserCallout from '~/user_callout';
import TreeView from '~/tree';
@@ -8,15 +8,18 @@ import BlobViewer from '~/blob/viewer/index';
import Activities from '~/activities';
import { ajaxGet } from '~/lib/utils/common_utils';
import GpgBadges from '~/gpg_badges';
+import initReadMore from '~/read_more';
import Star from '../../../star';
import notificationsDropdown from '../../../notifications_dropdown';
document.addEventListener('DOMContentLoaded', () => {
+ initReadMore();
new Star(); // eslint-disable-line no-new
notificationsDropdown();
new ShortcutsNavigation(); // eslint-disable-line no-new
new NotificationsForm(); // eslint-disable-line no-new
- new UserCallout({ // eslint-disable-line no-new
+ // eslint-disable-next-line no-new
+ new UserCallout({
setCalloutPerProject: false,
className: 'js-autodevops-banner',
});
diff --git a/app/assets/javascripts/pages/projects/tree/show/index.js b/app/assets/javascripts/pages/projects/tree/show/index.js
index 33d69d891d8..400aed35e32 100644
--- a/app/assets/javascripts/pages/projects/tree/show/index.js
+++ b/app/assets/javascripts/pages/projects/tree/show/index.js
@@ -4,7 +4,7 @@ import initBlob from '~/blob_edit/blob_bundle';
import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
import GpgBadges from '~/gpg_badges';
import TreeView from '../../../../tree';
-import ShortcutsNavigation from '../../../../shortcuts_navigation';
+import ShortcutsNavigation from '../../../../behaviors/shortcuts/shortcuts_navigation';
import BlobViewer from '../../../../blob/viewer';
import NewCommitForm from '../../../../new_commit_form';
import { ajaxGet } from '../../../../lib/utils/common_utils';
diff --git a/app/assets/javascripts/pages/projects/wikis/components/delete_wiki_modal.vue b/app/assets/javascripts/pages/projects/wikis/components/delete_wiki_modal.vue
index 0289209ff1e..75cb6374ad5 100644
--- a/app/assets/javascripts/pages/projects/wikis/components/delete_wiki_modal.vue
+++ b/app/assets/javascripts/pages/projects/wikis/components/delete_wiki_modal.vue
@@ -1,12 +1,8 @@
<script>
import _ from 'underscore';
-import GlModal from '~/vue_shared/components/gl_modal.vue';
import { s__, sprintf } from '~/locale';
export default {
- components: {
- GlModal,
- },
props: {
deleteWikiUrl: {
type: String,
@@ -25,6 +21,9 @@ export default {
},
},
computed: {
+ modalId() {
+ return 'delete-wiki-modal';
+ },
message() {
return s__('WikiPageConfirmDelete|Are you sure you want to delete this page?');
},
@@ -47,31 +46,41 @@ export default {
</script>
<template>
- <gl-modal
- id="delete-wiki-modal"
- :header-title-text="title"
- :footer-primary-button-text="s__('WikiPageConfirmDelete|Delete page')"
- footer-primary-button-variant="danger"
- @submit="onSubmit"
- >
- {{ message }}
- <form
- ref="form"
- :action="deleteWikiUrl"
- method="post"
- class="js-requires-input"
+ <div class="d-inline-block">
+ <button
+ v-gl-modal="modalId"
+ type="button"
+ class="btn btn-danger"
+ >
+ {{ __('Delete') }}
+ </button>
+ <gl-ui-modal
+ :title="title"
+ :ok-title="s__('WikiPageConfirmDelete|Delete page')"
+ :modal-id="modalId"
+ title-tag="h4"
+ ok-variant="danger"
+ @ok="onSubmit"
>
- <input
- ref="method"
- type="hidden"
- name="_method"
- value="delete"
- />
- <input
- :value="csrfToken"
- type="hidden"
- name="authenticity_token"
- />
- </form>
- </gl-modal>
+ {{ message }}
+ <form
+ ref="form"
+ :action="deleteWikiUrl"
+ method="post"
+ class="js-requires-input"
+ >
+ <input
+ ref="method"
+ type="hidden"
+ name="_method"
+ value="delete"
+ />
+ <input
+ :value="csrfToken"
+ type="hidden"
+ name="authenticity_token"
+ />
+ </form>
+ </gl-ui-modal>
+ </div>
</template>
diff --git a/app/assets/javascripts/pages/projects/wikis/index.js b/app/assets/javascripts/pages/projects/wikis/index.js
index 0a0fe3fc137..c2629090f01 100644
--- a/app/assets/javascripts/pages/projects/wikis/index.js
+++ b/app/assets/javascripts/pages/projects/wikis/index.js
@@ -2,8 +2,8 @@ import $ from 'jquery';
import Vue from 'vue';
import Translate from '~/vue_shared/translate';
import csrf from '~/lib/utils/csrf';
+import ShortcutsWiki from '~/behaviors/shortcuts/shortcuts_wiki';
import Wikis from './wikis';
-import ShortcutsWiki from '../../../shortcuts_wiki';
import ZenMode from '../../../zen_mode';
import GLForm from '../../../gl_form';
import deleteWikiModal from './components/delete_wiki_modal.vue';
@@ -14,15 +14,15 @@ document.addEventListener('DOMContentLoaded', () => {
new ZenMode(); // eslint-disable-line no-new
new GLForm($('.wiki-form')); // eslint-disable-line no-new
- const deleteWikiButton = document.getElementById('delete-wiki-button');
+ const deleteWikiModalWrapperEl = document.getElementById('delete-wiki-modal-wrapper');
- if (deleteWikiButton) {
+ if (deleteWikiModalWrapperEl) {
Vue.use(Translate);
- const { deleteWikiUrl, pageTitle } = deleteWikiButton.dataset;
- const deleteWikiModalEl = document.getElementById('delete-wiki-modal');
- const deleteModal = new Vue({ // eslint-disable-line
- el: deleteWikiModalEl,
+ const { deleteWikiUrl, pageTitle } = deleteWikiModalWrapperEl.dataset;
+
+ new Vue({ // eslint-disable-line no-new
+ el: deleteWikiModalWrapperEl,
data: {
deleteWikiUrl: '',
},
diff --git a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
index 0fdb0a080cf..7836d4f3b09 100644
--- a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
+++ b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
@@ -130,8 +130,8 @@ export default {
</div>
<simple-metric
v-for="metric in $options.simpleMetrics"
- :current-request="currentRequest"
:key="metric"
+ :current-request="currentRequest"
:metric="metric"
/>
<div
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
index 1952dd453f4..9b4ba0c1a9a 100644
--- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
@@ -1,12 +1,10 @@
<script>
import _ from 'underscore';
-import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
import StageColumnComponent from './stage_column_component.vue';
export default {
components: {
StageColumnComponent,
- LoadingIcon,
},
props: {
isLoading: {
@@ -59,9 +57,9 @@ export default {
<div class="build-content middle-block js-pipeline-graph">
<div class="pipeline-visualization pipeline-graph pipeline-tab-content">
<div class="text-center">
- <loading-icon
+ <gl-loading-icon
v-if="isLoading"
- size="3"
+ :size="3"
/>
</div>
@@ -70,9 +68,9 @@ export default {
class="stage-column-list">
<stage-column-component
v-for="(stage, index) in graph"
+ :key="stage.name"
:title="capitalizeStageName(stage.name)"
:jobs="stage.groups"
- :key="stage.name"
:stage-connector-class="stageConnectorClass(index, stage)"
:is-first-column="isFirstColumn(index)"
@refreshPipelineGraph="refreshPipelineGraph"
diff --git a/app/assets/javascripts/pipelines/components/graph/job_component.vue b/app/assets/javascripts/pipelines/components/graph/job_component.vue
index 9ac16b7e541..a1504592bbc 100644
--- a/app/assets/javascripts/pipelines/components/graph/job_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/job_component.vue
@@ -98,8 +98,8 @@ export default {
<template>
<div class="ci-job-component">
<a
- v-tooltip
v-if="status.has_details"
+ v-tooltip
:href="status.details_path"
:title="tooltipText"
:class="cssClassJobName"
@@ -115,8 +115,8 @@ export default {
</a>
<div
- v-tooltip
v-else
+ v-tooltip
:title="tooltipText"
:class="cssClassJobName"
class="js-job-component-tooltip non-details-job-component"
diff --git a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
index e7b2de52f76..567ea119343 100644
--- a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
@@ -62,9 +62,9 @@ export default {
<ul>
<li
v-for="(job, index) in jobs"
+ :id="jobId(job)"
:key="job.id"
:class="buildConnnectorClass(index)"
- :id="jobId(job)"
class="build"
>
diff --git a/app/assets/javascripts/pipelines/components/header_component.vue b/app/assets/javascripts/pipelines/components/header_component.vue
index 001eaeaa065..1f9187c3d65 100644
--- a/app/assets/javascripts/pipelines/components/header_component.vue
+++ b/app/assets/javascripts/pipelines/components/header_component.vue
@@ -1,13 +1,11 @@
<script>
import ciHeader from '../../vue_shared/components/header_ci_component.vue';
import eventHub from '../event_hub';
-import loadingIcon from '../../vue_shared/components/loading_icon.vue';
export default {
name: 'PipelineHeaderSection',
components: {
ciHeader,
- loadingIcon,
},
props: {
pipeline: {
@@ -89,9 +87,9 @@ export default {
item-name="Pipeline"
@actionClicked="postAction"
/>
- <loading-icon
+ <gl-loading-icon
v-if="isLoading"
- size="2"
+ :size="2"
class="prepend-top-default append-bottom-default"
/>
</div>
diff --git a/app/assets/javascripts/pipelines/components/nav_controls.vue b/app/assets/javascripts/pipelines/components/nav_controls.vue
index 9501afb7493..efb80d3a3c0 100644
--- a/app/assets/javascripts/pipelines/components/nav_controls.vue
+++ b/app/assets/javascripts/pipelines/components/nav_controls.vue
@@ -43,7 +43,7 @@ export default {
<a
v-if="newPipelinePath"
:href="newPipelinePath"
- class="btn btn-create js-run-pipeline"
+ class="btn btn-success js-run-pipeline"
>
{{ s__('Pipelines|Run Pipeline') }}
</a>
diff --git a/app/assets/javascripts/pipelines/components/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipeline_url.vue
index 75db1e9ae7c..40df07650c9 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_url.vue
+++ b/app/assets/javascripts/pipelines/components/pipeline_url.vue
@@ -67,29 +67,29 @@ export default {
</span>
<div class="label-container">
<span
- v-tooltip
v-if="pipeline.flags.latest"
+ v-tooltip
class="js-pipeline-url-latest badge badge-success"
title="Latest pipeline for this branch">
latest
</span>
<span
- v-tooltip
v-if="pipeline.flags.yaml_errors"
+ v-tooltip
:title="pipeline.yaml_errors"
class="js-pipeline-url-yaml badge badge-danger">
yaml invalid
</span>
<span
- v-tooltip
v-if="pipeline.flags.failure_reason"
+ v-tooltip
:title="pipeline.failure_reason"
class="js-pipeline-url-failure badge badge-danger">
error
</span>
<a
- v-popover="popoverOptions"
v-if="pipeline.flags.auto_devops"
+ v-popover="popoverOptions"
tabindex="0"
class="js-pipeline-url-autodevops badge badge-info autodevops-badge"
role="button">
diff --git a/app/assets/javascripts/pipelines/components/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines.vue
index c9d2dc3a3c5..ea526cf1309 100644
--- a/app/assets/javascripts/pipelines/components/pipelines.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines.vue
@@ -319,10 +319,10 @@ export default {
<div class="content-list pipelines">
- <loading-icon
+ <gl-loading-icon
v-if="stateToRender === $options.stateMap.loading"
:label="s__('Pipelines|Loading Pipelines')"
- size="3"
+ :size="3"
class="prepend-top-20"
/>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_actions.vue
index 1c8d7303c52..017dd560621 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_actions.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_actions.vue
@@ -1,6 +1,5 @@
<script>
import eventHub from '../event_hub';
-import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import icon from '../../vue_shared/components/icon.vue';
import tooltip from '../../vue_shared/directives/tooltip';
@@ -9,7 +8,6 @@ export default {
tooltip,
},
components: {
- loadingIcon,
icon,
},
props: {
@@ -60,7 +58,7 @@ export default {
class="fa fa-caret-down"
aria-hidden="true">
</i>
- <loading-icon v-if="isLoading" />
+ <gl-loading-icon v-if="isLoading" />
</button>
<ul class="dropdown-menu dropdown-menu-right">
diff --git a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
index 29b347824de..a39cc265601 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
@@ -132,10 +132,8 @@ export default {
if (this.pipeline.ref) {
return Object.keys(this.pipeline.ref).reduce((accumulator, prop) => {
if (prop === 'path') {
- // eslint-disable-next-line no-param-reassign
accumulator.ref_url = this.pipeline.ref[prop];
} else {
- // eslint-disable-next-line no-param-reassign
accumulator[prop] = this.pipeline.ref[prop];
}
return accumulator;
diff --git a/app/assets/javascripts/pipelines/components/stage.vue b/app/assets/javascripts/pipelines/components/stage.vue
index c7df69c69ed..47c15b1a9c4 100644
--- a/app/assets/javascripts/pipelines/components/stage.vue
+++ b/app/assets/javascripts/pipelines/components/stage.vue
@@ -18,14 +18,12 @@ import Flash from '../../flash';
import axios from '../../lib/utils/axios_utils';
import eventHub from '../event_hub';
import Icon from '../../vue_shared/components/icon.vue';
-import LoadingIcon from '../../vue_shared/components/loading_icon.vue';
import JobComponent from './graph/job_component.vue';
import tooltip from '../../vue_shared/directives/tooltip';
import { PIPELINES_TABLE } from '../constants';
export default {
components: {
- LoadingIcon,
Icon,
JobComponent,
},
@@ -157,9 +155,9 @@ export default {
<template>
<div class="dropdown">
<button
- v-tooltip
id="stageDropdown"
ref="dropdown"
+ v-tooltip
:class="triggerButtonClass"
:title="stage.title"
class="mini-pipeline-graph-dropdown-toggle js-builds-dropdown-button"
@@ -191,7 +189,7 @@ export default {
class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container"
aria-labelledby="stageDropdown"
>
- <loading-icon v-if="isLoading"/>
+ <gl-loading-icon v-if="isLoading"/>
<ul
v-else
class="js-builds-dropdown-list scrollable-menu"
diff --git a/app/assets/javascripts/pipelines/mixins/pipelines.js b/app/assets/javascripts/pipelines/mixins/pipelines.js
index 2cb558b0dec..8929b397f6c 100644
--- a/app/assets/javascripts/pipelines/mixins/pipelines.js
+++ b/app/assets/javascripts/pipelines/mixins/pipelines.js
@@ -4,7 +4,6 @@ import Flash from '../../flash';
import Poll from '../../lib/utils/poll';
import EmptyState from '../components/empty_state.vue';
import SvgBlankState from '../components/blank_state.vue';
-import LoadingIcon from '../../vue_shared/components/loading_icon.vue';
import PipelinesTableComponent from '../components/pipelines_table.vue';
import eventHub from '../event_hub';
import { CANCEL_REQUEST } from '../constants';
@@ -14,7 +13,6 @@ export default {
PipelinesTableComponent,
SvgBlankState,
EmptyState,
- LoadingIcon,
},
data() {
return {
diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_dropdown_mixin.js b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_dropdown_mixin.js
index c15d8ba49e1..d5266544307 100644
--- a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_dropdown_mixin.js
+++ b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_dropdown_mixin.js
@@ -1,5 +1,4 @@
import _ from 'underscore';
-import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
import DropdownSearchInput from '~/vue_shared/components/dropdown/dropdown_search_input.vue';
import DropdownHiddenInput from '~/vue_shared/components/dropdown/dropdown_hidden_input.vue';
import DropdownButton from '~/vue_shared/components/dropdown/dropdown_button.vue';
@@ -9,7 +8,6 @@ import store from '../store';
export default {
store,
components: {
- LoadingIcon,
DropdownButton,
DropdownSearchInput,
DropdownHiddenInput,
diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown.vue b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown.vue
index d4497924ad8..2c02f436b69 100644
--- a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown.vue
+++ b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown.vue
@@ -126,7 +126,7 @@ export default {
</ul>
</div>
<div class="dropdown-loading">
- <loading-icon />
+ <gl-loading-icon />
</div>
</div>
</div>
diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue
index 08d0a122579..fc17e2fab49 100644
--- a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue
+++ b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue
@@ -187,7 +187,7 @@ export default {
</ul>
</div>
<div class="dropdown-loading">
- <loading-icon />
+ <gl-loading-icon />
</div>
</div>
</div>
diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue
index b5476684c6a..ca7c79f75f0 100644
--- a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue
+++ b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue
@@ -100,7 +100,7 @@ export default {
</ul>
</div>
<div class="dropdown-loading">
- <loading-icon />
+ <gl-loading-icon />
</div>
</div>
</div>
diff --git a/app/assets/javascripts/projects/project_import_gitlab_project.js b/app/assets/javascripts/projects/project_import_gitlab_project.js
index 4e20fce1460..fbef3a0b059 100644
--- a/app/assets/javascripts/projects/project_import_gitlab_project.js
+++ b/app/assets/javascripts/projects/project_import_gitlab_project.js
@@ -1,9 +1,19 @@
import $ from 'jquery';
import { getParameterValues } from '../lib/utils/url_utility';
+import projectNew from './project_new';
export default () => {
- const path = getParameterValues('path')[0];
+ const pathParam = getParameterValues('path')[0];
+ const nameParam = getParameterValues('name')[0];
+ const $projectPath = $('.js-path-name');
+ const $projectName = $('.js-project-name');
- // get the path url and append it in the inputS
- $('.js-path-name').val(path);
+ // get the path url and append it in the input
+ $projectPath.val(pathParam);
+
+ // get the project name from the URL and set it as input value
+ $projectName.val(nameParam);
+
+ // generate slug when project name changes
+ $projectName.keyup(() => projectNew.onProjectNameChange($projectName, $projectPath));
};
diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js
index 04badad0f34..8a079b4b38a 100644
--- a/app/assets/javascripts/projects/project_new.js
+++ b/app/assets/javascripts/projects/project_new.js
@@ -1,5 +1,6 @@
import $ from 'jquery';
import { addSelectOnFocusBehaviour } from '../lib/utils/common_utils';
+import { slugifyWithHyphens } from '../lib/utils/text_utility';
let hasUserDefinedProjectPath = false;
@@ -29,18 +30,23 @@ const deriveProjectPathFromUrl = ($projectImportUrl) => {
}
};
+const onProjectNameChange = ($projectNameInput, $projectPathInput) => {
+ const slug = slugifyWithHyphens($projectNameInput.val());
+ $projectPathInput.val(slug);
+};
+
const bindEvents = () => {
const $newProjectForm = $('#new_project');
const $projectImportUrl = $('#project_import_url');
- const $projectPath = $('#project_path');
+ const $projectPath = $('.tab-pane.active #project_path');
const $useTemplateBtn = $('.template-button > input');
const $projectFieldsForm = $('.project-fields-form');
const $selectedTemplateText = $('.selected-template');
const $changeTemplateBtn = $('.change-template');
const $selectedIcon = $('.selected-icon');
- const $templateProjectNameInput = $('#template-project-name #project_path');
const $pushNewProjectTipTrigger = $('.push-new-project-tip');
const $projectTemplateButtons = $('.project-templates-buttons');
+ const $projectName = $('.tab-pane.active #project_name');
if ($newProjectForm.length !== 1) {
return;
@@ -57,7 +63,8 @@ const bindEvents = () => {
$('.btn_import_gitlab_project').on('click', () => {
const importHref = $('a.btn_import_gitlab_project').attr('href');
- $('.btn_import_gitlab_project').attr('href', `${importHref}?namespace_id=${$('#project_namespace_id').val()}&path=${$projectPath.val()}`);
+ $('.btn_import_gitlab_project')
+ .attr('href', `${importHref}?namespace_id=${$('#project_namespace_id').val()}&name=${$projectName.val()}&path=${$projectPath.val()}`);
});
if ($pushNewProjectTipTrigger) {
@@ -111,7 +118,15 @@ const bindEvents = () => {
const selectedTemplate = templates[value];
$selectedTemplateText.text(selectedTemplate.text);
$(selectedTemplate.icon).clone().addClass('d-block').appendTo($selectedIcon);
- $templateProjectNameInput.focus();
+
+ const $activeTabProjectName = $('.tab-pane.active #project_name');
+ const $activeTabProjectPath = $('.tab-pane.active #project_path');
+ $activeTabProjectName.focus();
+ $activeTabProjectName
+ .keyup(() => {
+ onProjectNameChange($activeTabProjectName, $activeTabProjectPath);
+ hasUserDefinedProjectPath = $activeTabProjectPath.val().trim().length > 0;
+ });
}
$useTemplateBtn.on('change', chooseTemplate);
@@ -131,9 +146,15 @@ const bindEvents = () => {
});
$projectImportUrl.keyup(() => deriveProjectPathFromUrl($projectImportUrl));
+
+ $projectName.keyup(() => {
+ onProjectNameChange($projectName, $projectPath);
+ hasUserDefinedProjectPath = $projectPath.val().trim().length > 0;
+ });
};
export default {
bindEvents,
deriveProjectPathFromUrl,
+ onProjectNameChange,
};
diff --git a/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue b/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue
index 1c1e17563a1..120b4fc2f2b 100644
--- a/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue
+++ b/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue
@@ -1,7 +1,6 @@
<script>
import Visibility from 'visibilityjs';
import ciIcon from '~/vue_shared/components/ci_icon.vue';
-import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import Poll from '~/lib/utils/poll';
import Flash from '~/flash';
import { s__, sprintf } from '~/locale';
@@ -14,7 +13,6 @@ export default {
},
components: {
ciIcon,
- loadingIcon,
},
props: {
endpoint: {
@@ -100,10 +98,10 @@ export default {
</script>
<template>
<div class="ci-status-link">
- <loading-icon
+ <gl-loading-icon
v-if="isLoading"
+ :size="3"
label="Loading pipeline status"
- size="3"
/>
<a
v-else
diff --git a/app/assets/javascripts/read_more.js b/app/assets/javascripts/read_more.js
new file mode 100644
index 00000000000..d2d1ac8c76a
--- /dev/null
+++ b/app/assets/javascripts/read_more.js
@@ -0,0 +1,41 @@
+/**
+ * ReadMore
+ *
+ * Adds "read more" functionality to elements.
+ *
+ * Specifically, it looks for a trigger, by default ".js-read-more-trigger", and adds the class
+ * "is-expanded" to the previous element in order to provide a click to expand functionality.
+ *
+ * This is useful for long text elements that you would like to truncate, especially for mobile.
+ *
+ * Example Markup
+ * <div class="read-more-container">
+ * <p>Some text that should be long enough to have to truncate within a specified container.</p>
+ * <p>This text will not appear in the container, as only the first line can be truncated.</p>
+ * <p>This should also not appear, if everything is working correctly!</p>
+ * </div>
+ * <button class="js-read-more-trigger">Read more</button>
+ *
+ */
+export default function initReadMore(triggerSelector = '.js-read-more-trigger') {
+ const triggerEls = document.querySelectorAll(triggerSelector);
+
+ if (!triggerEls) return;
+
+ triggerEls.forEach(triggerEl => {
+ const targetEl = triggerEl.previousElementSibling;
+
+ if (!targetEl) {
+ return;
+ }
+
+ triggerEl.addEventListener(
+ 'click',
+ e => {
+ targetEl.classList.add('is-expanded');
+ e.target.remove();
+ },
+ { once: true },
+ );
+ });
+}
diff --git a/app/assets/javascripts/registry/components/app.vue b/app/assets/javascripts/registry/components/app.vue
index 31f88675912..7e2287ac4db 100644
--- a/app/assets/javascripts/registry/components/app.vue
+++ b/app/assets/javascripts/registry/components/app.vue
@@ -1,7 +1,6 @@
<script>
import { mapGetters, mapActions } from 'vuex';
import Flash from '../../flash';
- import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import store from '../stores';
import collapsibleContainer from './collapsible_container.vue';
import { errorMessages, errorMessagesTypes } from '../constants';
@@ -10,7 +9,6 @@
name: 'RegistryListApp',
components: {
collapsibleContainer,
- loadingIcon,
},
props: {
endpoint: {
@@ -42,9 +40,9 @@
</script>
<template>
<div>
- <loading-icon
+ <gl-loading-icon
v-if="isLoading"
- size="3"
+ :size="3"
/>
<collapsible-container
diff --git a/app/assets/javascripts/registry/components/collapsible_container.vue b/app/assets/javascripts/registry/components/collapsible_container.vue
index 4116c4a0489..d9bf41924d1 100644
--- a/app/assets/javascripts/registry/components/collapsible_container.vue
+++ b/app/assets/javascripts/registry/components/collapsible_container.vue
@@ -2,16 +2,15 @@
import { mapActions } from 'vuex';
import Flash from '../../flash';
import clipboardButton from '../../vue_shared/components/clipboard_button.vue';
- import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tooltip from '../../vue_shared/directives/tooltip';
import tableRegistry from './table_registry.vue';
import { errorMessages, errorMessagesTypes } from '../constants';
+ import { __ } from '../../locale';
export default {
name: 'CollapsibeContainerRegisty',
components: {
clipboardButton,
- loadingIcon,
tableRegistry,
},
directives: {
@@ -46,7 +45,10 @@
handleDeleteRepository() {
this.deleteRepo(this.repo)
- .then(() => this.fetchRepos())
+ .then(() => {
+ Flash(__('This container registry has been scheduled for deletion.'), 'notice');
+ this.fetchRepos();
+ })
.catch(() => this.showError(errorMessagesTypes.DELETE_REPO));
},
@@ -86,8 +88,8 @@
<div class="controls d-none d-sm-block float-right">
<button
- v-tooltip
v-if="repo.canDelete"
+ v-tooltip
:title="s__('ContainerRegistry|Remove repository')"
:aria-label="s__('ContainerRegistry|Remove repository')"
type="button"
@@ -103,10 +105,10 @@
</div>
</div>
- <loading-icon
+ <gl-loading-icon
v-if="repo.isLoading"
+ :size="2"
class="append-bottom-20"
- size="2"
/>
<div
diff --git a/app/assets/javascripts/registry/components/table_registry.vue b/app/assets/javascripts/registry/components/table_registry.vue
index 9f4973c3490..fafb35bd69a 100644
--- a/app/assets/javascripts/registry/components/table_registry.vue
+++ b/app/assets/javascripts/registry/components/table_registry.vue
@@ -118,8 +118,8 @@
<td class="content">
<button
- v-tooltip
v-if="item.canDelete"
+ v-tooltip
:title="s__('ContainerRegistry|Remove tag')"
:aria-label="s__('ContainerRegistry|Remove tag')"
type="button"
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 7b37f4e9a97..fb8c6402d02 100644
--- a/app/assets/javascripts/reports/components/grouped_test_reports_app.vue
+++ b/app/assets/javascripts/reports/components/grouped_test_reports_app.vue
@@ -92,16 +92,16 @@
v-for="(report, i) in reports"
>
<summary-row
+ :key="`summary-row-${i}`"
:summary="reportText(report)"
:status-icon="getReportIcon(report)"
- :key="`summary-row-${i}`"
/>
<issues-list
v-if="shouldRenderIssuesList(report)"
+ :key="`issues-list-${i}`"
:unresolved-issues="report.existing_failures"
:new-issues="report.new_failures"
:resolved-issues="report.resolved_failures"
- :key="`issues-list-${i}`"
:component="$options.componentNames.TestIssueBody"
class="report-block-group-list"
/>
diff --git a/app/assets/javascripts/reports/components/report_issues.vue b/app/assets/javascripts/reports/components/report_issues.vue
index c553a374f66..a2a03945ae3 100644
--- a/app/assets/javascripts/reports/components/report_issues.vue
+++ b/app/assets/javascripts/reports/components/report_issues.vue
@@ -37,8 +37,8 @@ export default {
<ul class="report-block-list">
<li
v-for="(issue, index) in issues"
- :class="{ 'is-dismissed': issue.isDismissed }"
:key="index"
+ :class="{ 'is-dismissed': issue.isDismissed }"
class="report-block-list-issue"
>
<issue-status-icon
@@ -47,8 +47,8 @@ export default {
/>
<component
- v-if="component"
:is="component"
+ v-if="component"
:issue="issue"
:status="issue.status || status"
:is-new="isNew"
diff --git a/app/assets/javascripts/reports/components/summary_row.vue b/app/assets/javascripts/reports/components/summary_row.vue
index 4456d84c968..51188981bed 100644
--- a/app/assets/javascripts/reports/components/summary_row.vue
+++ b/app/assets/javascripts/reports/components/summary_row.vue
@@ -1,6 +1,5 @@
<script>
import CiIcon from '~/vue_shared/components/ci_icon.vue';
-import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
import Popover from '~/vue_shared/components/help_popover.vue';
/**
@@ -15,7 +14,6 @@ export default {
name: 'ReportSummaryRow',
components: {
CiIcon,
- LoadingIcon,
Popover,
},
props: {
@@ -46,7 +44,7 @@ export default {
<template>
<div class="report-block-list-issue report-block-list-issue-parent">
<div class="report-block-list-icon append-right-10 prepend-left-5">
- <loading-icon
+ <gl-loading-icon
v-if="statusIcon === 'loading'"
css-class="report-block-list-loading-icon"
/>
diff --git a/app/assets/javascripts/reports/store/mutations.js b/app/assets/javascripts/reports/store/mutations.js
index 1983a8c9e56..b88bff97075 100644
--- a/app/assets/javascripts/reports/store/mutations.js
+++ b/app/assets/javascripts/reports/store/mutations.js
@@ -1,4 +1,3 @@
-/* eslint-disable no-param-reassign */
import * as types from './mutation_types';
export default {
diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js
index aec09b8bc0a..50dd3c12382 100644
--- a/app/assets/javascripts/search_autocomplete.js
+++ b/app/assets/javascripts/search_autocomplete.js
@@ -68,7 +68,7 @@ function setSearchOptions() {
}
}
-export default class SearchAutocomplete {
+export class SearchAutocomplete {
constructor({ wrap, optsEl, autocompletePath, projectId, projectRef } = {}) {
setSearchOptions();
this.bindEventContext();
@@ -499,3 +499,7 @@ export default class SearchAutocomplete {
this.dropdownMenu.toggleClass('fade-out', !this.isScrolledUp());
}
}
+
+export default function initSearchAutocomplete(opts) {
+ return new SearchAutocomplete(opts);
+}
diff --git a/app/assets/javascripts/sidebar/components/participants/participants.vue b/app/assets/javascripts/sidebar/components/participants/participants.vue
index 56d57f6aac8..286a16f7bbf 100644
--- a/app/assets/javascripts/sidebar/components/participants/participants.vue
+++ b/app/assets/javascripts/sidebar/components/participants/participants.vue
@@ -1,7 +1,6 @@
<script>
import { __, n__, sprintf } from '~/locale';
import tooltip from '~/vue_shared/directives/tooltip';
- import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import userAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue';
export default {
@@ -9,7 +8,6 @@
tooltip,
},
components: {
- loadingIcon,
userAvatarImage,
},
props: {
@@ -93,7 +91,7 @@
aria-hidden="true"
>
</i>
- <loading-icon
+ <gl-loading-icon
v-if="loading"
class="js-participants-collapsed-loading-icon"
/>
@@ -105,7 +103,7 @@
</span>
</div>
<div class="title hide-collapsed">
- <loading-icon
+ <gl-loading-icon
v-if="loading"
:inline="true"
class="js-participants-expanded-loading-icon"
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
index ca3b9338c29..2ee3e1f322e 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
@@ -19,19 +19,23 @@ export default {
TimeTrackingHelpState,
},
props: {
+ // eslint-disable-next-line vue/prop-name-casing
time_estimate: {
type: Number,
required: true,
},
+ // eslint-disable-next-line vue/prop-name-casing
time_spent: {
type: Number,
required: true,
},
+ // eslint-disable-next-line vue/prop-name-casing
human_time_estimate: {
type: String,
required: false,
default: '',
},
+ // eslint-disable-next-line vue/prop-name-casing
human_time_spent: {
type: String,
required: false,
diff --git a/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue b/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue
index ffaed9c7193..a6b3a674952 100644
--- a/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue
+++ b/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue
@@ -3,7 +3,6 @@ import { __ } from '~/locale';
import tooltip from '~/vue_shared/directives/tooltip';
import Icon from '~/vue_shared/components/icon.vue';
-import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
const MARK_TEXT = __('Mark todo as done');
const TODO_TEXT = __('Add todo');
@@ -14,7 +13,6 @@ export default {
},
components: {
Icon,
- LoadingIcon,
},
props: {
issuableId: {
@@ -90,7 +88,7 @@ export default {
>
{{ buttonLabel }}
</span>
- <loading-icon
+ <gl-loading-icon
v-show="isActionActive"
:inline="true"
/>
diff --git a/app/assets/javascripts/usage_ping_consent.js b/app/assets/javascripts/usage_ping_consent.js
new file mode 100644
index 00000000000..ae3fde190e3
--- /dev/null
+++ b/app/assets/javascripts/usage_ping_consent.js
@@ -0,0 +1,30 @@
+import $ from 'jquery';
+import axios from './lib/utils/axios_utils';
+import Flash, { hideFlash } from './flash';
+import { convertPermissionToBoolean } from './lib/utils/common_utils';
+
+export default () => {
+ $('body').on('click', '.js-usage-consent-action', (e) => {
+ e.preventDefault();
+ e.stopImmediatePropagation(); // overwrite rails listener
+
+ const { url, checkEnabled, pingEnabled } = e.target.dataset;
+ const data = {
+ application_setting: {
+ version_check_enabled: convertPermissionToBoolean(checkEnabled),
+ usage_ping_enabled: convertPermissionToBoolean(pingEnabled),
+ },
+ };
+
+ const hideConsentMessage = () => hideFlash(document.querySelector('.ping-consent-message'));
+
+ axios.put(url, data)
+ .then(() => {
+ hideConsentMessage();
+ })
+ .catch(() => {
+ hideConsentMessage();
+ Flash('Something went wrong. Try again later.');
+ });
+ });
+};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
index d530ab2767b..70518ad73e8 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
@@ -106,8 +106,8 @@ export default {
</tooltip-on-truncate>
</template>
<span
- v-tooltip
v-if="hasDeploymentTime"
+ v-tooltip
:title="deployment.deployed_at_formatted"
class="js-deploy-time"
>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue
index 9aff95dcfec..035ae791a1d 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue
@@ -1,11 +1,9 @@
<script>
import ciIcon from '../../vue_shared/components/ci_icon.vue';
- import loadingIcon from '../../vue_shared/components/loading_icon.vue';
export default {
components: {
ciIcon,
- loadingIcon,
},
props: {
status: {
@@ -37,7 +35,7 @@
v-if="isLoading"
class="mr-widget-icon"
>
- <loading-icon />
+ <gl-loading-icon />
</div>
<ci-icon
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue
index 2133124347c..01294d5b40c 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue
@@ -1,5 +1,4 @@
<script>
- import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import eventHub from '../../event_hub';
import statusIcon from '../mr_widget_status_icon.vue';
@@ -7,7 +6,6 @@
name: 'MRWidgetAutoMergeFailed',
components: {
statusIcon,
- loadingIcon,
},
props: {
mr: {
@@ -44,7 +42,7 @@
class="btn btn-sm btn-default"
@click="refreshWidget"
>
- <loading-icon
+ <gl-loading-icon
v-if="isRefreshing"
:inline="true"
/>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue
index 1a444c04a1d..8184ef33022 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue
@@ -1,7 +1,6 @@
<script>
import Flash from '~/flash';
import tooltip from '~/vue_shared/directives/tooltip';
- import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import { s__, __ } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import MrWidgetAuthorTime from '../../components/mr_widget_author_time.vue';
@@ -15,7 +14,6 @@
},
components: {
MrWidgetAuthorTime,
- loadingIcon,
statusIcon,
ClipboardButton,
},
@@ -116,8 +114,8 @@
:date-readable="mr.metrics.readableMergedAt"
/>
<a
- v-tooltip
v-if="mr.canRevertInCurrentMR"
+ v-tooltip
:title="revertTitle"
class="btn btn-close btn-sm"
href="#modal-revert-commit"
@@ -127,8 +125,8 @@
{{ revertLabel }}
</a>
<a
- v-tooltip
v-else-if="mr.revertInForkPath"
+ v-tooltip
:href="mr.revertInForkPath"
:title="revertTitle"
class="btn btn-close btn-sm"
@@ -137,8 +135,8 @@
{{ revertLabel }}
</a>
<a
- v-tooltip
v-if="mr.canCherryPickInCurrentMR"
+ v-tooltip
:title="cherryPickTitle"
class="btn btn-default btn-sm"
href="#modal-cherry-pick-commit"
@@ -148,8 +146,8 @@
{{ cherryPickLabel }}
</a>
<a
- v-tooltip
v-else-if="mr.cherryPickInForkPath"
+ v-tooltip
:href="mr.cherryPickInForkPath"
:title="cherryPickTitle"
class="btn btn-default btn-sm"
@@ -195,7 +193,7 @@
</button>
</p>
<p v-if="shouldShowSourceBranchRemoving">
- <loading-icon :inline="true" />
+ <gl-loading-icon :inline="true" />
<span>
{{ s__("mrWidget|The source branch is being removed") }}
</span>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue
index 2d8c3d6be87..f31c7a3edb8 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue
@@ -2,14 +2,12 @@
import simplePoll from '../../../lib/utils/simple_poll';
import eventHub from '../../event_hub';
import statusIcon from '../mr_widget_status_icon.vue';
- import loadingIcon from '../../../vue_shared/components/loading_icon.vue';
import Flash from '../../../flash';
export default {
name: 'MRWidgetRebase',
components: {
statusIcon,
- loadingIcon,
},
props: {
mr: {
@@ -115,7 +113,7 @@ js-toggle-container accept-action media space-children"
class="btn btn-sm btn-reopen btn-success qa-mr-rebase-button"
@click="rebase"
>
- <loading-icon v-if="isMakingRequest" />
+ <gl-loading-icon v-if="isMakingRequest" />
Rebase
</button>
<span
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_squash_before_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_squash_before_merge.vue
index 25c1044fe2b..25ad329e196 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_squash_before_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_squash_before_merge.vue
@@ -37,8 +37,8 @@ export default {
<div class="accept-control inline">
<label class="merge-param-checkbox">
<input
- :disabled="isMergeButtonDisabled"
v-model="squashBeforeMerge"
+ :disabled="isMergeButtonDisabled"
type="checkbox"
name="squash"
class="qa-squash-checkbox"
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue
index 086dbabe77e..e73b7e410d5 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue
@@ -37,7 +37,7 @@ export default {
<a
v-if="mr.newBlobPath"
:href="mr.newBlobPath"
- class="btn btn-inverted btn-save">
+ class="btn btn-inverted btn-success">
Create file
</a>
</div>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
index a5ca7b719a1..23c3284cd21 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
@@ -255,7 +255,7 @@ export default {
data-toggle="dropdown"
aria-label="Select merge moment">
<i
- class="fa fa-chevron-down"
+ class="fa fa-chevron-down qa-merge-moment-dropdown"
aria-hidden="true"
></i>
</button>
@@ -265,7 +265,7 @@ export default {
role="menu">
<li>
<a
- class="merge_when_pipeline_succeeds"
+ class="merge_when_pipeline_succeeds qa-merge-when-pipeline-succeeds-option"
href="#"
@click.prevent="handleMergeButtonClick(true)">
<span class="media">
@@ -279,7 +279,7 @@ export default {
</li>
<li>
<a
- class="accept-merge-request"
+ class="accept-merge-request qa-merge-immediately-option"
href="#"
@click.prevent="handleMergeButtonClick(false, true)">
<span class="media">
diff --git a/app/assets/javascripts/vue_merge_request_widget/index.js b/app/assets/javascripts/vue_merge_request_widget/index.js
index 69a9132a2da..cc6e620f365 100644
--- a/app/assets/javascripts/vue_merge_request_widget/index.js
+++ b/app/assets/javascripts/vue_merge_request_widget/index.js
@@ -1,7 +1,4 @@
-import {
- Vue,
- mrWidgetOptions,
-} from './dependencies';
+import { Vue, mrWidgetOptions } from './dependencies';
import Translate from '../vue_shared/translate';
Vue.use(Translate);
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
index dc6be025f11..b5eaaf054e7 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
@@ -107,10 +107,14 @@ export default {
created() {
this.initPolling();
this.bindEventHubListeners();
+ eventHub.$on('mr.discussion.updated', this.checkStatus);
},
mounted() {
this.handleMounted();
},
+ beforeDestroy() {
+ eventHub.$off('mr.discussion.updated', this.checkStatus);
+ },
methods: {
createService(store) {
const endpoints = {
diff --git a/app/assets/javascripts/vue_shared/components/bar_chart.vue b/app/assets/javascripts/vue_shared/components/bar_chart.vue
index 3ced4eb691a..33af7a7f1df 100644
--- a/app/assets/javascripts/vue_shared/components/bar_chart.vue
+++ b/app/assets/javascripts/vue_shared/components/bar_chart.vue
@@ -291,8 +291,8 @@ export default {
<template
v-for="(data, index) in graphData">
<rect
- v-tooltip
:key="index"
+ v-tooltip
:width="xScale.bandwidth()"
:x="xScale(data.name)"
:y="yScale(data.value)"
diff --git a/app/assets/javascripts/vue_shared/components/diff_viewer/diff_viewer.vue b/app/assets/javascripts/vue_shared/components/diff_viewer/diff_viewer.vue
index d3cbe3c7e74..cfc5343217c 100644
--- a/app/assets/javascripts/vue_shared/components/diff_viewer/diff_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/diff_viewer/diff_viewer.vue
@@ -46,7 +46,7 @@ export default {
}
},
basePath() {
- // We might get the project path from rails with the relative url already setup
+ // We might get the project path from rails with the relative url already set up
return this.projectPath.indexOf('/') === 0 ? '' : `${gon.relative_url_root}/`;
},
fullOldPath() {
diff --git a/app/assets/javascripts/vue_shared/components/dropdown/dropdown_button.vue b/app/assets/javascripts/vue_shared/components/dropdown/dropdown_button.vue
index af5ebcdc40a..31087017968 100644
--- a/app/assets/javascripts/vue_shared/components/dropdown/dropdown_button.vue
+++ b/app/assets/javascripts/vue_shared/components/dropdown/dropdown_button.vue
@@ -1,11 +1,7 @@
<script>
import { __ } from '~/locale';
-import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
export default {
- components: {
- LoadingIcon,
- },
props: {
isDisabled: {
type: Boolean,
@@ -34,7 +30,7 @@ export default {
data-toggle="dropdown"
aria-expanded="false"
>
- <loading-icon
+ <gl-loading-icon
v-show="isLoading"
:inline="true"
/>
diff --git a/app/assets/javascripts/vue_shared/components/file_icon.vue b/app/assets/javascripts/vue_shared/components/file_icon.vue
index 878c805ada5..408f7d7965f 100644
--- a/app/assets/javascripts/vue_shared/components/file_icon.vue
+++ b/app/assets/javascripts/vue_shared/components/file_icon.vue
@@ -1,6 +1,5 @@
<script>
import getIconForFile from './file_icon/file_icon_map';
-import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import icon from '../../vue_shared/components/icon.vue';
/* This is a re-usable vue component for rendering a svg sprite
@@ -17,7 +16,6 @@ import icon from '../../vue_shared/components/icon.vue';
*/
export default {
components: {
- loadingIcon,
icon,
},
props: {
@@ -84,7 +82,7 @@ export default {
:size="size"
css-classes="folder-icon"
/>
- <loading-icon
+ <gl-loading-icon
v-if="loading"
:inline="true"
/>
diff --git a/app/assets/javascripts/vue_shared/components/file_row.vue b/app/assets/javascripts/vue_shared/components/file_row.vue
new file mode 100644
index 00000000000..c797ad62a5d
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/file_row.vue
@@ -0,0 +1,210 @@
+<script>
+import Icon from '~/vue_shared/components/icon.vue';
+import FileIcon from '~/vue_shared/components/file_icon.vue';
+
+export default {
+ name: 'FileRow',
+ components: {
+ FileIcon,
+ Icon,
+ },
+ props: {
+ file: {
+ type: Object,
+ required: true,
+ },
+ level: {
+ type: Number,
+ required: true,
+ },
+ extraComponent: {
+ type: Object,
+ required: false,
+ default: null,
+ },
+ },
+ data() {
+ return {
+ mouseOver: false,
+ };
+ },
+ computed: {
+ isTree() {
+ return this.file.type === 'tree';
+ },
+ isBlob() {
+ return this.file.type === 'blob';
+ },
+ levelIndentation() {
+ return {
+ marginLeft: `${this.level * 16}px`,
+ };
+ },
+ fileClass() {
+ return {
+ 'file-open': this.isBlob && this.file.opened,
+ 'is-active': this.isBlob && this.file.active,
+ folder: this.isTree,
+ 'is-open': this.file.opened,
+ };
+ },
+ },
+ watch: {
+ 'file.active': function fileActiveWatch(active) {
+ if (this.file.type === 'blob' && active) {
+ this.scrollIntoView();
+ }
+ },
+ },
+ mounted() {
+ if (this.hasPathAtCurrentRoute()) {
+ this.scrollIntoView(true);
+ }
+ },
+ methods: {
+ toggleTreeOpen(path) {
+ this.$emit('toggleTreeOpen', path);
+ },
+ clickFile() {
+ // Manual Action if a tree is selected/opened
+ if (this.isTree && this.hasUrlAtCurrentRoute()) {
+ this.toggleTreeOpen(this.file.path);
+ }
+
+ if (this.$router) this.$router.push(`/project${this.file.url}`);
+ },
+ scrollIntoView(isInit = false) {
+ const block = isInit && this.isTree ? 'center' : 'nearest';
+
+ this.$el.scrollIntoView({
+ behavior: 'smooth',
+ block,
+ });
+ },
+ hasPathAtCurrentRoute() {
+ if (!this.$router || !this.$router.currentRoute) {
+ return false;
+ }
+
+ // - strip route up to "/-/" and ending "/"
+ const routePath = this.$router.currentRoute.path
+ .replace(/^.*?[/]-[/]/g, '')
+ .replace(/[/]$/g, '');
+
+ // - strip ending "/"
+ const filePath = this.file.path.replace(/[/]$/g, '');
+
+ return filePath === routePath;
+ },
+ hasUrlAtCurrentRoute() {
+ if (!this.$router || !this.$router.currentRoute) return true;
+
+ return this.$router.currentRoute.path === `/project${this.file.url}`;
+ },
+ toggleHover(over) {
+ this.mouseOver = over;
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <div
+ :class="fileClass"
+ class="file-row"
+ role="button"
+ @click="clickFile"
+ @mouseover="toggleHover(true)"
+ @mouseout="toggleHover(false)"
+ >
+ <div
+ class="file-row-name-container"
+ >
+ <span
+ :style="levelIndentation"
+ class="file-row-name str-truncated"
+ >
+ <file-icon
+ :file-name="file.name"
+ :loading="file.loading"
+ :folder="isTree"
+ :opened="file.opened"
+ :size="16"
+ />
+ {{ file.name }}
+ </span>
+ <component
+ :is="extraComponent"
+ v-if="extraComponent"
+ :file="file"
+ :mouse-over="mouseOver"
+ />
+ </div>
+ </div>
+ <template v-if="file.opened">
+ <file-row
+ v-for="childFile in file.tree"
+ :key="childFile.key"
+ :file="childFile"
+ :level="level + 1"
+ :extra-component="extraComponent"
+ @toggleTreeOpen="toggleTreeOpen"
+ />
+ </template>
+ </div>
+</template>
+
+<style>
+.file-row {
+ display: flex;
+ align-items: center;
+ height: 32px;
+ padding: 4px 8px;
+ margin-left: -8px;
+ margin-right: -8px;
+ border-radius: 3px;
+ text-align: left;
+ cursor: pointer;
+}
+
+.file-row:hover,
+.file-row:focus {
+ background: #f2f2f2;
+}
+
+.file-row:active {
+ background: #dfdfdf;
+}
+
+.file-row.is-active {
+ background: #f2f2f2;
+}
+
+.file-row-name-container {
+ display: flex;
+ width: 100%;
+ align-items: center;
+ overflow: visible;
+}
+
+.file-row-name {
+ display: inline-block;
+ flex: 1;
+ max-width: inherit;
+ height: 18px;
+ line-height: 16px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.file-row-name svg {
+ margin-right: 2px;
+ vertical-align: middle;
+}
+
+.file-row-name .loading-container {
+ display: inline-block;
+ margin-right: 4px;
+}
+</style>
diff --git a/app/assets/javascripts/vue_shared/components/header_ci_component.vue b/app/assets/javascripts/vue_shared/components/header_ci_component.vue
index 49fbce75110..b371b6adf7e 100644
--- a/app/assets/javascripts/vue_shared/components/header_ci_component.vue
+++ b/app/assets/javascripts/vue_shared/components/header_ci_component.vue
@@ -1,6 +1,5 @@
<script>
import CiIconBadge from './ci_badge_link.vue';
-import LoadingIcon from './loading_icon.vue';
import TimeagoTooltip from './time_ago_tooltip.vue';
import tooltip from '../directives/tooltip';
import UserAvatarImage from './user_avatar/user_avatar_image.vue';
@@ -15,7 +14,6 @@ import UserAvatarImage from './user_avatar/user_avatar_image.vue';
export default {
components: {
CiIconBadge,
- LoadingIcon,
TimeagoTooltip,
UserAvatarImage,
},
@@ -128,18 +126,18 @@ export default {
>
<a
v-if="action.type === 'link'"
+ :key="i"
:href="action.path"
:class="action.cssClass"
- :key="i"
>
{{ action.label }}
</a>
<a
v-else-if="action.type === 'ujs-link'"
+ :key="i"
:href="action.path"
:class="action.cssClass"
- :key="i"
data-method="post"
rel="nofollow"
>
@@ -148,9 +146,9 @@ export default {
<button
v-else-if="action.type === 'button'"
+ :key="i"
:disabled="action.isLoading"
:class="action.cssClass"
- :key="i"
type="button"
@click="onClickAction(action)"
>
diff --git a/app/assets/javascripts/vue_shared/components/loading_button.vue b/app/assets/javascripts/vue_shared/components/loading_button.vue
index 2ff0c056b9c..4cbd3e6429d 100644
--- a/app/assets/javascripts/vue_shared/components/loading_button.vue
+++ b/app/assets/javascripts/vue_shared/components/loading_button.vue
@@ -17,12 +17,7 @@
*/
- import loadingIcon from './loading_icon.vue';
-
export default {
- components: {
- loadingIcon,
- },
props: {
loading: {
type: Boolean,
@@ -60,7 +55,7 @@
@click="onClick"
>
<transition name="fade">
- <loading-icon
+ <gl-loading-icon
v-if="loading"
:inline="true"
:class="{
diff --git a/app/assets/javascripts/vue_shared/components/loading_icon.vue b/app/assets/javascripts/vue_shared/components/loading_icon.vue
deleted file mode 100644
index db22c5f02cd..00000000000
--- a/app/assets/javascripts/vue_shared/components/loading_icon.vue
+++ /dev/null
@@ -1,45 +0,0 @@
-<script>
- export default {
- props: {
- label: {
- type: String,
- required: false,
- default: 'Loading',
- },
-
- size: {
- type: String,
- required: false,
- default: '1',
- },
-
- inline: {
- type: Boolean,
- required: false,
- default: false,
- },
- },
-
- computed: {
- rootElementType() {
- return this.inline ? 'span' : 'div';
- },
- cssClass() {
- return `fa-${this.size}x`;
- },
- },
- };
-</script>
-<template>
- <component
- :is="rootElementType"
- class="loading-container text-center">
- <i
- :class="cssClass"
- :aria-label="label"
- class="fa fa-spin fa-spinner"
- aria-hidden="true"
- >
- </i>
- </component>
-</template>
diff --git a/app/assets/javascripts/vue_shared/components/pagination_links.vue b/app/assets/javascripts/vue_shared/components/pagination_links.vue
new file mode 100644
index 00000000000..1f2a679c145
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/pagination_links.vue
@@ -0,0 +1,34 @@
+<script>
+import { s__ } from '../../locale';
+
+export default {
+ props: {
+ change: {
+ type: Function,
+ required: true,
+ },
+ pageInfo: {
+ type: Object,
+ required: true,
+ },
+ },
+ firstText: s__('Pagination|« First'),
+ prevText: s__('Pagination|Prev'),
+ nextText: s__('Pagination|Next'),
+ lastText: s__('Pagination|Last »'),
+};
+</script>
+
+<template>
+ <gl-pagination
+ v-bind="$attrs"
+ :change="change"
+ :page="pageInfo.page"
+ :per-page="pageInfo.perPage"
+ :total-items="pageInfo.total"
+ :first-text="$options.firstText"
+ :prev-text="$options.prevText"
+ :next-text="$options.nextText"
+ :last-text="$options.lastText"
+ />
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue b/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue
index 74998a4787d..9d757b27edc 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue
@@ -1,6 +1,5 @@
<script>
import datePicker from '../pikaday.vue';
- import loadingIcon from '../loading_icon.vue';
import toggleSidebar from './toggle_sidebar.vue';
import collapsedCalendarIcon from './collapsed_calendar_icon.vue';
import { dateInWords } from '../../../lib/utils/datetime_utility';
@@ -10,7 +9,6 @@
components: {
datePicker,
toggleSidebar,
- loadingIcon,
collapsedCalendarIcon,
},
props: {
@@ -112,7 +110,7 @@
/>
<div class="title">
{{ label }}
- <loading-icon
+ <gl-loading-icon
v-if="isLoading"
:inline="true"
/>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue
index a3fc358130f..3df286de129 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue
@@ -3,7 +3,6 @@ import $ from 'jquery';
import { __ } from '~/locale';
import LabelsSelect from '~/labels_select';
import DropdownHiddenInput from '~/vue_shared/components/dropdown/dropdown_hidden_input.vue';
-import LoadingIcon from '../../loading_icon.vue';
import DropdownTitle from './dropdown_title.vue';
import DropdownValue from './dropdown_value.vue';
@@ -16,7 +15,6 @@ import DropdownCreateLabel from './dropdown_create_label.vue';
export default {
components: {
- LoadingIcon,
DropdownTitle,
DropdownValue,
DropdownValueCollapsed,
@@ -164,7 +162,7 @@ dropdown-menu-labels dropdown-menu-selectable"
<dropdown-search-input/>
<div class="dropdown-content"></div>
<div class="dropdown-loading">
- <loading-icon />
+ <gl-loading-icon />
</div>
<dropdown-footer
v-if="showCreate"
diff --git a/app/assets/javascripts/vue_shared/components/stacked_progress_bar.vue b/app/assets/javascripts/vue_shared/components/stacked_progress_bar.vue
index 78fde463507..cd3ee544344 100644
--- a/app/assets/javascripts/vue_shared/components/stacked_progress_bar.vue
+++ b/app/assets/javascripts/vue_shared/components/stacked_progress_bar.vue
@@ -99,8 +99,8 @@ export default {
{{ __("Not available") }}
</span>
<span
- v-tooltip
v-if="successPercent"
+ v-tooltip
:title="successTooltip"
:style="successBarStyle"
class="status-green"
@@ -109,8 +109,8 @@ export default {
{{ successPercent }}%
</span>
<span
- v-tooltip
v-if="neutralPercent"
+ v-tooltip
:title="neutralTooltip"
:style="neutralBarStyle"
class="status-neutral"
@@ -119,8 +119,8 @@ export default {
{{ neutralPercent }}%
</span>
<span
- v-tooltip
v-if="failurePercent"
+ v-tooltip
:title="failureTooltip"
:style="failureBarStyle"
class="status-red"
diff --git a/app/assets/javascripts/vue_shared/components/toggle_button.vue b/app/assets/javascripts/vue_shared/components/toggle_button.vue
index a897300b62b..5b9c51786d6 100644
--- a/app/assets/javascripts/vue_shared/components/toggle_button.vue
+++ b/app/assets/javascripts/vue_shared/components/toggle_button.vue
@@ -1,7 +1,6 @@
<script>
import { s__ } from '../../locale';
import icon from './icon.vue';
- import loadingIcon from './loading_icon.vue';
const ICON_ON = 'status_success_borderless';
const ICON_OFF = 'status_failed_borderless';
@@ -11,7 +10,6 @@
export default {
components: {
icon,
- loadingIcon,
},
model: {
@@ -78,7 +76,7 @@
class="project-feature-toggle"
@click="toggleFeature"
>
- <loadingIcon class="loading-icon" />
+ <gl-loading-icon class="loading-icon" />
<span class="toggle-icon">
<icon
:name="toggleIcon"
diff --git a/app/assets/javascripts/vue_shared/components/tooltip_on_truncate.vue b/app/assets/javascripts/vue_shared/components/tooltip_on_truncate.vue
index 125826da6c3..d5b58574123 100644
--- a/app/assets/javascripts/vue_shared/components/tooltip_on_truncate.vue
+++ b/app/assets/javascripts/vue_shared/components/tooltip_on_truncate.vue
@@ -51,8 +51,8 @@ export default {
<template>
<span
- v-tooltip
v-if="showTooltip"
+ v-tooltip
:title="title"
:data-placement="placement"
class="js-show-tooltip"
diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue
index 01c36fec41a..08e102e57c3 100644
--- a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue
+++ b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue
@@ -94,8 +94,8 @@ export default {
:tooltip-text="avatarTooltipText"
:tooltip-placement="tooltipPlacement"
/><span
- v-tooltip
v-if="shouldShowUsername"
+ v-tooltip
:title="tooltipText"
:tooltip-placement="tooltipPlacement"
>{{ username }}</span>
diff --git a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js
index 73b9131e5ba..b9693892f45 100644
--- a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js
+++ b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js
@@ -28,7 +28,7 @@ Vue.http.interceptors.push((request, next) => {
response.headers.forEach((value, key) => {
headers[key] = value;
});
-
+ // eslint-disable-next-line no-param-reassign
response.headers = headers;
});
});
diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss
index c91f5e279ea..af73954bd2e 100644
--- a/app/assets/stylesheets/bootstrap_migration.scss
+++ b/app/assets/stylesheets/bootstrap_migration.scss
@@ -93,7 +93,6 @@ hr {
}
.form-group.row .col-form-label {
- padding-top: 0;
// Bootstrap 4 aligns labels to the left
// for horizontal forms
@include media-breakpoint-up(md) {
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index b1a20c06910..4ffb3e9ab42 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -27,7 +27,6 @@
@import 'framework/header';
@import 'framework/highlight';
@import 'framework/issue_box';
-@import 'framework/jquery';
@import 'framework/lists';
@import 'framework/logo';
@import 'framework/markdown_area';
@@ -64,3 +63,4 @@
@import 'framework/ci_variable_list';
@import 'framework/feature_highlight';
@import 'framework/terms';
+@import 'framework/read_more';
diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss
index 9dd0384a228..fcf282a7d7c 100644
--- a/app/assets/stylesheets/framework/avatar.scss
+++ b/app/assets/stylesheets/framework/avatar.scss
@@ -69,7 +69,7 @@
.identicon {
text-align: center;
vertical-align: top;
- color: $identicon-fg-color;
+ color: $gl-gray-700;
background-color: $gray-darker;
// Sizes
@@ -104,6 +104,7 @@
a {
width: 100%;
+ height: 100%;
display: flex;
}
diff --git a/app/assets/stylesheets/framework/awards.scss b/app/assets/stylesheets/framework/awards.scss
index a265e4206f1..702276780e9 100644
--- a/app/assets/stylesheets/framework/awards.scss
+++ b/app/assets/stylesheets/framework/awards.scss
@@ -229,8 +229,8 @@
svg {
margin-bottom: 1px;
- height: 18px;
- width: 18px;
+ height: $default-icon-size;
+ width: $default-icon-size;
border-radius: 50%;
path {
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index 72b4ed0ac33..686ce0c63a4 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -147,17 +147,12 @@
}
&.btn-success,
- &.btn-new,
- &.btn-create,
- &.btn-save {
+ &.btn-register {
@include btn-green;
}
&.btn-inverted {
- &.btn-success,
- &.btn-new,
- &.btn-create,
- &.btn-save {
+ &.btn-success {
@include btn-outline($white-light, $green-600, $green-500, $green-500, $white-light, $green-600, $green-600, $green-700);
}
@@ -165,6 +160,10 @@
@include btn-outline($white-light, $red-500, $red-500, $red-500, $white-light, $red-600, $red-600, $red-700);
}
+ &.btn-warning {
+ @include btn-outline($white-light, $orange-500, $orange-500, $orange-500, $white-light, $orange-600, $orange-600, $orange-700);
+ }
+
&.btn-primary,
&.btn-info {
@include btn-outline($white-light, $blue-500, $blue-500, $blue-500, $white-light, $blue-600, $blue-600, $blue-700);
@@ -172,8 +171,7 @@
}
&.btn-info,
- &.btn-primary,
- &.btn-register {
+ &.btn-primary {
@include btn-blue;
}
@@ -248,7 +246,7 @@
.btn-terminal {
svg {
height: 14px;
- width: 18px;
+ width: $default-icon-size;
}
}
@@ -365,7 +363,7 @@
}
.clone-dropdown-btn a {
- color: $dropdown-link-color;
+ color: $gl-gray-700;
&:hover {
text-decoration: none;
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 72e27f9ad16..28dda65091d 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -43,7 +43,7 @@
color: $brand-info;
}
-.hint { font-style: italic; color: $hint-color; }
+.hint { font-style: italic; color: $gl-gray-400; }
.light { color: $gl-text-color; }
.slead {
@@ -70,13 +70,6 @@ pre {
padding: 0;
}
- &.card.card-body-pre {
- border: 1px solid $gray-darker;
- background: $gray-light;
- border-radius: 0;
- color: $well-pre-color;
- }
-
&.wrap {
word-break: break-word;
white-space: pre-wrap;
@@ -121,49 +114,24 @@ hr {
text-decoration: none;
}
-.back-link {
- font-size: 14px;
-}
-
table {
a code {
position: relative;
top: -2px;
margin-right: 3px;
}
-
- td.permission-x {
- background: $table-permission-x-bg !important;
- text-align: center;
- }
}
.loading {
margin: 20px auto;
height: 40px;
- color: $loading-color;
+ color: $gl-gray-700;
font-size: 32px;
text-align: center;
}
-span.update-author {
- display: block;
- color: $update-author-color;
- font-weight: $gl-font-weight-normal;
- font-style: italic;
-
- strong {
- font-weight: $gl-font-weight-bold;
- font-style: normal;
- }
-}
-
-.field_with_errors {
- display: inline;
-}
-
p.time {
- color: $time-color;
+ color: $gl-gray-400;
font-size: 90%;
margin: 30px 3px 3px 2px;
}
@@ -197,40 +165,11 @@ li.note {
background-color: inherit;
}
-.project_member_show {
- td:first-child {
- color: $project-member-show-color;
- }
-}
-
-.rss-icon {
- img {
- width: 24px;
- vertical-align: top;
- }
-
- strong {
- line-height: 24px;
- }
-}
-
.show-suppressed-diff,
.show-all-commits {
cursor: pointer;
}
-.git_error_tips {
- @extend .col-lg-6;
- text-align: left;
- margin-top: 40px;
-
- pre {
- background: $white-light;
- border: 0;
- font-size: 12px;
- }
-}
-
.error-message {
padding: 10px;
background: $red-400;
@@ -258,7 +197,7 @@ li.note {
.gitlab-promo {
a {
- color: $gl-promo-color;
+ color: $gl-gray-350;
margin-right: 30px;
}
}
@@ -271,19 +210,6 @@ li.note {
}
}
-.control-group {
- .controls {
- span {
- &.descr {
- position: relative;
- top: 2px;
- left: 5px;
- color: $control-group-descr-color;
- }
- }
- }
-}
-
img.emoji {
height: 20px;
vertical-align: top;
@@ -302,12 +228,6 @@ img.emoji {
margin-bottom: 10px;
}
-.side-filters {
- fieldset {
- margin-bottom: 15px;
- }
-}
-
.footer-links {
margin-bottom: 20px;
@@ -329,25 +249,6 @@ img.emoji {
text-align: center;
}
-.header-with-avatar {
- h3 {
- margin: 0;
- font-weight: $gl-font-weight-bold;
- }
-
- .username {
- font-size: 18px;
- color: $username-color;
- margin-top: 8px;
- }
-
- .description {
- font-size: $gl-font-size;
- color: $description-color;
- margin-top: 8px;
- }
-}
-
.dropzone .dz-preview .dz-progress {
border-color: $border-color !important;
@@ -386,16 +287,6 @@ img.emoji {
}
}
-.content-separator {
- margin-left: -$gl-padding;
- margin-right: -$gl-padding;
- border-top: 1px solid $border-color;
-}
-
-.hide-bottom-border {
- border-bottom: 0 !important;
-}
-
.gl-accessibility {
&:focus {
display: flex;
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 8a224dc517e..8603714f709 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -607,25 +607,25 @@
width: 100%;
min-height: 30px;
padding: 0 7px;
- color: $dropdown-input-color;
+ color: $gl-gray-700;
line-height: 30px;
border: 1px solid $dropdown-divider-color;
border-radius: 2px;
outline: 0;
&:focus {
- color: $dropdown-link-color;
+ color: $gl-gray-700;
border-color: $blue-300;
box-shadow: 0 0 4px $dropdown-input-focus-shadow;
~ .fa {
- color: $dropdown-link-color;
+ color: $gl-gray-700;
}
}
&:hover {
~ .fa {
- color: $dropdown-link-color;
+ color: $gl-gray-700;
}
}
}
@@ -890,7 +890,7 @@ header.header-content .dropdown-menu.frequent-items-dropdown-menu {
position: absolute;
top: 13px;
right: 25px;
- color: $md-area-border;
+ color: $gray-100;
}
}
@@ -929,7 +929,7 @@ header.header-content .dropdown-menu.frequent-items-dropdown-menu {
&:hover {
.frequent-items-item-avatar-container .avatar {
- border-color: $md-area-border;
+ border-color: $gray-100;
}
}
diff --git a/app/assets/stylesheets/framework/emojis.scss b/app/assets/stylesheets/framework/emojis.scss
index 6c50ea719d3..be85e03430e 100644
--- a/app/assets/stylesheets/framework/emojis.scss
+++ b/app/assets/stylesheets/framework/emojis.scss
@@ -6,3 +6,13 @@ gl-emoji {
font-size: 1.4em;
line-height: 1em;
}
+
+.user-status-emoji {
+ margin-right: $gl-padding-4;
+
+ gl-emoji {
+ font-size: 1em;
+ line-height: 16px;
+ vertical-align: baseline;
+ }
+}
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 1d3512bbb4c..53f198b47c6 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -184,7 +184,7 @@
&.line-numbers {
float: none;
- border-left: 1px solid $blame-line-numbers-border;
+ border-left: 1px solid $gl-gray-100;
i {
float: none;
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index abfe350677e..d5693a5d1a1 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -92,8 +92,8 @@
display: -webkit-flex;
display: flex;
flex-shrink: 0;
- margin-top: 5px;
- margin-bottom: 5px;
+ margin-top: 4px;
+ margin-bottom: 4px;
.selectable {
display: -webkit-flex;
@@ -216,8 +216,8 @@
vertical-align: inherit;
img {
- height: 18px;
- width: 18px;
+ height: $default-icon-size;
+ width: $default-icon-size;
}
}
@@ -389,9 +389,8 @@
.btn {
text-overflow: ellipsis;
- .fa {
- width: 15px;
- line-height: $line-height-base;
+ svg {
+ margin-right: $gl-padding-8;
}
.dropdown-label-box {
diff --git a/app/assets/stylesheets/framework/jquery.scss b/app/assets/stylesheets/framework/jquery.scss
deleted file mode 100644
index d1360a0c0eb..00000000000
--- a/app/assets/stylesheets/framework/jquery.scss
+++ /dev/null
@@ -1,15 +0,0 @@
-.ui-widget {
- font-family: $regular-font;
- font-size: $font-size-base;
-
- .ui-state-default {
- border: 1px solid $white-light;
- background: $white-light;
- color: $jq-ui-default-color;
- }
-
- .ui-state-highlight {
- border: 0;
- background: transparent;
- }
-}
diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss
index d4bae4cb137..9218df9b40f 100644
--- a/app/assets/stylesheets/framework/layout.scss
+++ b/app/assets/stylesheets/framework/layout.scss
@@ -69,10 +69,14 @@ body {
float: right;
}
- /* Center alert text and alert action links on smaller screens */
- @include media-breakpoint-down(sm) {
- .alert {
- text-align: center;
+ .flex-alert {
+ @include media-breakpoint-up(lg) {
+ display: flex;
+
+ .alert-message {
+ flex: 1;
+ padding-right: 40px;
+ }
}
.alert-link-group {
@@ -80,6 +84,13 @@ body {
}
}
+ @include media-breakpoint-down(sm) {
+ .alert-link-group {
+ float: none;
+ margin-top: $gl-padding-8;
+ }
+ }
+
/* Stripe the background colors so that adjacent alert-warnings are distinct from one another */
.alert-warning {
transition: background-color 0.15s, border-color 0.15s;
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index fdc0454d837..d9d4a210f5f 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -111,6 +111,7 @@ ul.content-list {
border-color: $white-normal;
font-size: $gl-font-size;
color: $gl-text-color;
+ word-break: break-word;
&.no-description {
.title {
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index d8391b59a8c..554e2b6720a 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -122,7 +122,7 @@
.markdown-area {
border-radius: 0;
background: $white-light;
- border: 1px solid $md-area-border;
+ border: 1px solid $gray-100;
min-height: 140px;
max-height: 500px;
padding: 5px;
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index 7edb89ce6f3..7f37dd3de91 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -20,7 +20,7 @@
display: inline-block;
overflow-x: auto;
border: 0;
- border-color: $md-area-border;
+ border-color: $gray-100;
@supports (width: fit-content) {
display: block;
@@ -29,11 +29,11 @@
tr {
th {
- border-bottom: solid 2px $md-area-border;
+ border-bottom: solid 2px $gray-100;
}
td {
- border-color: $md-area-border;
+ border-color: $gray-100;
}
}
}
diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss
index 033e5e57177..6d20c46b99d 100644
--- a/app/assets/stylesheets/framework/mobile.scss
+++ b/app/assets/stylesheets/framework/mobile.scss
@@ -44,12 +44,8 @@
.project-repo-buttons {
display: block;
- .count-buttons .btn {
- margin: 0 10px;
- }
-
- .count-buttons .count-with-arrow {
- display: none;
+ .count-buttons .count-badge {
+ margin-top: $gl-padding-8;
}
}
}
diff --git a/app/assets/stylesheets/framework/read_more.scss b/app/assets/stylesheets/framework/read_more.scss
new file mode 100644
index 00000000000..b84b6e0b256
--- /dev/null
+++ b/app/assets/stylesheets/framework/read_more.scss
@@ -0,0 +1,13 @@
+.read-more-container {
+ @include media-breakpoint-down(md) {
+ &:not(.is-expanded) {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+
+ > * {
+ display: inline;
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/framework/responsive_tables.scss b/app/assets/stylesheets/framework/responsive_tables.scss
index 764bebd82c6..fc185ccfaad 100644
--- a/app/assets/stylesheets/framework/responsive_tables.scss
+++ b/app/assets/stylesheets/framework/responsive_tables.scss
@@ -39,7 +39,7 @@
.table-section {
white-space: nowrap;
- $section-widths: 10 15 20 25 30 40 50 100;
+ $section-widths: 5 10 15 20 25 30 40 50 100;
@each $width in $section-widths {
&.section-#{$width} {
flex: 0 0 #{$width + '%'};
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index 3ae2c7078d6..381c0290d32 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -237,7 +237,7 @@
}
.group-path {
- color: $group-path-color;
+ color: $gl-gray-400;
}
}
@@ -257,7 +257,7 @@
.namespace-result {
.namespace-kind {
- color: $namespace-kind-color;
+ color: $gl-gray-350;
font-weight: $gl-font-weight-normal;
}
diff --git a/app/assets/stylesheets/framework/snippets.scss b/app/assets/stylesheets/framework/snippets.scss
index 7152ef9bcfd..36ab38f1c9d 100644
--- a/app/assets/stylesheets/framework/snippets.scss
+++ b/app/assets/stylesheets/framework/snippets.scss
@@ -45,7 +45,7 @@
}
}
-.snippet-scope-menu .btn-new {
+.snippet-scope-menu .btn-success {
margin-top: 15px;
}
diff --git a/app/assets/stylesheets/framework/toggle.scss b/app/assets/stylesheets/framework/toggle.scss
index 20394cc1e52..8258da07e4d 100644
--- a/app/assets/stylesheets/framework/toggle.scss
+++ b/app/assets/stylesheets/framework/toggle.scss
@@ -31,7 +31,7 @@
height: 24px;
cursor: pointer;
user-select: none;
- background: $feature-toggle-color-disabled;
+ background: $gl-gray-400;
border-radius: 12px;
padding: 3px;
transition: all .4s ease;
@@ -56,12 +56,12 @@
&,
.toggle-icon-svg {
- width: 18px;
- height: 18px;
+ width: $default-icon-size;
+ height: $default-icon-size;
}
.toggle-icon-svg {
- fill: $feature-toggle-color-disabled;
+ fill: $gl-gray-400;
}
.toggle-status-checked {
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 9929f1bdebf..0c1b8b92de3 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -61,12 +61,12 @@
padding: 3px 5px;
font-size: 11px;
line-height: 10px;
- color: $kdb-color;
+ color: $gl-gray-700;
vertical-align: middle;
background-color: $kdb-bg;
border-width: 1px;
border-style: solid;
- border-color: $kdb-border $kdb-border $kdb-border-bottom;
+ border-color: $gl-gray-200 $gl-gray-200 $kdb-border-bottom;
border-image: none;
border-radius: 3px;
box-shadow: 0 -1px 0 $kdb-shadow inset;
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index d76f5cbd9ff..f66782ab882 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -31,6 +31,14 @@ $gray-dark: darken($gray-light, $darken-dark-factor);
$gray-darker: #eee;
$gray-darkest: #c4c4c4;
+$gl-gray-100: #dddddd;
+$gl-gray-200: #cccccc;
+$gl-gray-350: #aaaaaa;
+$gl-gray-400: #999999;
+$gl-gray-500: #777777;
+$gl-gray-600: #666666;
+$gl-gray-700: #555555;
+
$green-50: #f1fdf6;
$green-100: #dcf5e7;
$green-200: #b3e6c8;
@@ -207,11 +215,6 @@ $list-border: rgba(0, 0, 0, 0.05);
$list-text-height: 42px;
/*
- * Markdown
- */
-$md-area-border: #ddd;
-
-/*
* Code
*/
$code-font-size: 90%;
@@ -241,7 +244,6 @@ $input-horizontal-padding: 12px;
/*
* Misc
*/
-$progress-color: #c0392b;
$header-height: 40px;
$ide-statusbar-height: 25px;
$fixed-layout-width: 1280px;
@@ -250,20 +252,13 @@ $container-text-max-width: 540px;
$gl-avatar-size: 40px;
$border-radius-default: 4px;
$border-radius-small: 2px;
-$settings-icon-size: 18px;
+$default-icon-size: 18px;
$layout-link-gray: #7e7c7c;
$btn-side-margin: 10px;
$btn-sm-side-margin: 7px;
$btn-margin-5: 5px;
$sidebar-block-hover-color: #ebebeb;
-$group-path-color: #999;
-$namespace-kind-color: #aaa;
-$panel-heading-link-color: #777;
-$graph-author-email-color: #777;
$count-arrow-border: #dce0e5;
-$save-project-loader-color: #555;
-$divergence-graph-bar-bg: #ccc;
-$divergence-graph-separator-bg: #ccc;
$general-hover-transition-duration: 100ms;
$general-hover-transition-curve: linear;
$highlight-changes-color: rgb(235, 255, 232);
@@ -271,24 +266,13 @@ $performance-bar-height: 35px;
$flash-height: 52px;
$context-header-height: 60px;
$breadcrumb-min-height: 48px;
+$project-title-row-height: 24px;
/*
* Common component specific colors
*/
-$hint-color: #999;
-$well-pre-color: #555;
-$loading-color: #555;
-$update-author-color: #999;
$user-mention-bg: rgba($blue-500, 0.044);
$user-mention-bg-hover: rgba($blue-500, 0.15);
-$time-color: #999;
-$project-member-show-color: #aaa;
-$gl-promo-color: #aaa;
-$control-group-descr-color: #666;
-$table-permission-x-bg: #d9edf7;
-$username-color: #666;
-$description-color: #666;
-$profiler-border: #eee;
/* tanuki logo colors */
$tanuki-red: #e24329;
@@ -319,9 +303,7 @@ $line-select-yellow: #fcf8e7;
$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;
$diff-image-info-color: gray;
-$diff-swipe-border: #999;
$diff-view-modes-color: gray;
$diff-view-modes-border: #c1c1c1;
$diff-jagged-border-gradient-color: darken($white-normal, 8%);
@@ -341,12 +323,10 @@ $dropdown-width: 300px;
$dropdown-min-height: 40px;
$dropdown-max-height: 312px;
$dropdown-vertical-offset: 4px;
-$dropdown-link-color: #555;
$dropdown-empty-row-bg: rgba(#000, 0.04);
$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-shadow: rgba($blue-300, 0.4);
$dropdown-loading-bg: rgba(#fff, 0.6);
@@ -419,15 +399,9 @@ $location-icon-color: #e7e9ed;
$note-disabled-comment-color: #b2b2b2;
$note-targe3-outside: #fffff0;
$note-targe3-inside: #ffffd3;
-$note-line2-border: #ddd;
$note-icon-gutter-width: 55px;
/*
-* Zen
-*/
-$zen-control-color: #555;
-
-/*
* Identicon
*/
$identicon-red: #ffebee;
@@ -436,7 +410,6 @@ $identicon-indigo: #e8eaf6;
$identicon-blue: #e3f2fd;
$identicon-teal: #e0f2f1;
$identicon-orange: #fbe9e7;
-$identicon-fg-color: #555555;
/*
* Calendar
@@ -505,16 +478,8 @@ $common-gray-light: #bbb;
$common-gray-dark: #444;
/*
-* Events
-*/
-$events-pre-color: #777;
-$events-note-icon-color: #777;
-$events-body-border: #ddd;
-
-/*
* Files
*/
-$blame-line-numbers-border: #ddd;
$logs-li-color: #888;
$logs-p-color: #333;
@@ -533,8 +498,6 @@ $input-short-md-width: 280px;
* Help
*/
$document-index-color: #888;
-$help-shortcut-color: #999;
-$help-shortcut-mapping-color: #555;
$help-shortcut-header-color: #333;
/*
@@ -545,12 +508,6 @@ $issues-today-border: #e1e8d5;
$compare-display-color: #888;
/*
-* jQuery UI
-*/
-$jq-ui-border: #ddd;
-$jq-ui-default-color: #777;
-
-/*
* Label
*/
$label-font-size: 12px;
@@ -574,34 +531,19 @@ $fade-mask-transition-curve: ease-in-out;
$login-brand-holder-color: #888;
/*
-* Nav
-*/
-$nav-link-gray: #959494;
-$nav-toggle-gray: #666;
-
-/*
-* Notify
-*/
-$notify-details: #777;
-$notify-footer: #777;
-
-/*
* Projects
*/
$project-option-descr-color: #54565b;
-$project-breadcrumb-color: #999;
$project-network-controls-color: #888;
$feature-toggle-color: #fff;
$feature-toggle-text-color: #fff;
-$feature-toggle-color-disabled: #999;
$feature-toggle-color-enabled: #4a8bee;
/*
Stat Graph
*/
$stat-graph-common-bg: #f3f3f3;
-$stat-graph-axis-fill: #aaa;
$stat-graph-selection-fill: #333;
$stat-graph-selection-stroke: #333;
@@ -612,17 +554,9 @@ $select2-drop-shadow1: rgba(76, 86, 103, 0.247059);
$select2-drop-shadow2: rgba(31, 37, 50, 0.317647);
/*
-* Todo
-*/
-$todo-body-pre-color: #777;
-$todo-body-border: #ddd;
-
-/*
* Typography
*/
$kdb-bg: #fcfcfc;
-$kdb-color: #555;
-$kdb-border: #ccc;
$kdb-border-bottom: #bbb;
$kdb-shadow: #bbb;
$body-text-shadow: rgba(255, 255, 255, 0.01);
@@ -631,7 +565,6 @@ $body-text-shadow: rgba(255, 255, 255, 0.01);
* UI Dev Kit
*/
$ui-dev-kit-example-color: #bbb;
-$ui-dev-kit-example-border: #ddd;
/*
Pipeline Graph
@@ -665,12 +598,10 @@ $dropdown-animation-timing: cubic-bezier(0.23, 1, 0.32, 1);
/*
Performance Bar
*/
-$perf-bar-text: #999;
$perf-bar-production: #222;
$perf-bar-staging: #291430;
$perf-bar-development: #4c1210;
$perf-bar-bucket-bg: #111;
-$perf-bar-bucket-color: #ccc;
$perf-bar-bucket-box-shadow-from: rgba($white-light, 0.2);
$perf-bar-bucket-box-shadow-to: rgba($black, 0.25);
diff --git a/app/assets/stylesheets/framework/zen.scss b/app/assets/stylesheets/framework/zen.scss
index f2d296fb875..a4fbd9c073f 100644
--- a/app/assets/stylesheets/framework/zen.scss
+++ b/app/assets/stylesheets/framework/zen.scss
@@ -35,7 +35,7 @@
.zen-control {
padding: 0;
- color: $zen-control-color;
+ color: $gl-gray-700;
background: none;
border: 0;
}
diff --git a/app/assets/stylesheets/notify.scss b/app/assets/stylesheets/notify.scss
index a81e5eb5ebf..f24c80bd81c 100644
--- a/app/assets/stylesheets/notify.scss
+++ b/app/assets/stylesheets/notify.scss
@@ -7,12 +7,12 @@ img {
p.details {
font-style: italic;
- color: $notify-details;
+ color: $gl-gray-500;
}
.footer > p {
font-size: small;
- color: $notify-footer;
+ color: $gl-gray-500;
}
pre.commit-message {
diff --git a/app/assets/stylesheets/page_bundles/ide.scss b/app/assets/stylesheets/page_bundles/ide.scss
index 5ff4e487d04..65f0a0d18e2 100644
--- a/app/assets/stylesheets/page_bundles/ide.scss
+++ b/app/assets/stylesheets/page_bundles/ide.scss
@@ -7,6 +7,8 @@ $ide-context-header-padding: 10px;
$ide-project-avatar-end: $ide-context-header-padding + 48px;
$ide-tree-padding: $gl-padding;
$ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
+$ide-commit-row-height: 32px;
+$ide-commit-header-height: 48px;
.project-refs-form,
.project-refs-target-form {
@@ -51,83 +53,9 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
flex: 1;
min-height: 0; // firefox fix
- .file {
- height: 32px;
- cursor: pointer;
-
- &.file-active {
- background: $theme-gray-100;
- }
-
- .ide-file-name {
- flex: 1;
- white-space: nowrap;
- text-overflow: ellipsis;
- max-width: inherit;
- line-height: 16px;
- display: inline-block;
- height: 18px;
-
- svg {
- vertical-align: middle;
- margin-right: 2px;
- }
-
- .loading-container {
- margin-right: 4px;
- display: inline-block;
- }
- }
-
- .ide-file-icon-holder {
- display: flex;
- align-items: center;
- color: $theme-gray-700;
- }
-
- .ide-file-changed-icon {
- margin-left: auto;
-
- > svg {
- display: block;
- }
- }
-
- .ide-new-btn {
- display: none;
-
- .btn {
- padding: 2px 5px;
- }
- }
-
- &:hover,
- &:focus {
- .ide-new-btn {
- display: block;
- }
- }
-
- .folder-icon {
- fill: $gl-text-color-secondary;
- }
- }
-
a {
color: $gl-text-color;
}
-
- th {
- position: sticky;
- top: 0;
- }
-}
-
-.file-name {
- display: flex;
- overflow: visible;
- align-items: center;
- width: 100%;
}
.multi-file-loading-container {
@@ -567,24 +495,11 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
}
.multi-file-commit-panel-header {
- display: flex;
- align-items: center;
- margin-bottom: 0;
+ height: $ide-commit-header-height;
border-bottom: 1px solid $white-dark;
padding: 12px 0;
}
-.multi-file-commit-panel-header-title {
- display: flex;
- flex: 1;
- align-items: center;
-
- svg {
- margin-right: $gl-btn-padding;
- color: $theme-gray-700;
- }
-}
-
.multi-file-commit-panel-collapse-btn {
border-left: 1px solid $white-dark;
margin-left: auto;
@@ -594,8 +509,6 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
flex: 1;
overflow: auto;
padding: $grid-size 0;
- margin-left: -$grid-size;
- margin-right: -$grid-size;
min-height: 60px;
&.form-text.text-muted {
@@ -638,8 +551,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
}
}
-.multi-file-commit-list-path,
-.ide-file-list .file {
+.multi-file-commit-list-path {
display: flex;
align-items: center;
margin-left: -$grid-size;
@@ -647,29 +559,31 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
padding: $grid-size / 2 $grid-size;
border-radius: $border-radius-default;
text-align: left;
+ cursor: pointer;
+ height: $ide-commit-row-height;
+ padding-right: 0;
&:hover,
&:focus {
background: $theme-gray-100;
+
+ outline: 0;
+
+ .multi-file-discard-btn {
+ > .btn {
+ display: flex;
+ }
+ }
}
&:active {
background: $theme-gray-200;
}
-}
-
-.multi-file-commit-list-path {
- cursor: pointer;
&.is-active {
background-color: $white-normal;
}
- &:hover,
- &:focus {
- outline: 0;
- }
-
svg {
min-width: 16px;
vertical-align: middle;
@@ -679,6 +593,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
.multi-file-commit-list-file-path {
@include str-truncated(calc(100% - 30px));
+ user-select: none;
&:active {
text-decoration: none;
@@ -686,9 +601,11 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
}
.multi-file-discard-btn {
- top: 4px;
- right: 8px;
- bottom: 4px;
+ > .btn {
+ display: none;
+ width: $ide-commit-row-height;
+ height: $ide-commit-row-height;
+ }
svg {
top: 0;
@@ -807,10 +724,9 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
}
.ide-staged-action-btn {
- width: 22px;
- margin-left: -1px;
- border-top-left-radius: 0;
- border-bottom-left-radius: 0;
+ width: $ide-commit-row-height;
+ height: $ide-commit-row-height;
+ color: inherit;
> svg {
top: 0;
@@ -1401,9 +1317,17 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
}
}
-.ide-new-btn .dropdown.show .ide-entry-dropdown-toggle {
- color: $white-normal;
- background-color: $blue-500;
+.ide-new-btn {
+ display: none;
+
+ .btn {
+ padding: 2px 5px;
+ }
+
+ .dropdown.show .ide-entry-dropdown-toggle {
+ color: $white-normal;
+ background-color: $blue-500;
+ }
}
.ide-preview-header {
@@ -1442,3 +1366,54 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
top: 50%;
transform: translateY(-50%);
}
+
+.ide-file-templates {
+ padding: $grid-size $gl-padding;
+ background-color: $gray-light;
+ border-bottom: 1px solid $white-dark;
+
+ .dropdown {
+ min-width: 180px;
+ }
+
+ .dropdown-content {
+ max-height: 222px;
+ }
+}
+
+.ide-commit-editor-header {
+ height: 65px;
+ padding: 8px 16px;
+ background-color: $theme-gray-50;
+ box-shadow: inset 0 -1px $white-dark;
+}
+
+.ide-commit-list-changed-icon {
+ width: $ide-commit-row-height;
+ height: $ide-commit-row-height;
+}
+
+.ide-file-icon-holder {
+ display: flex;
+ align-items: center;
+ color: $theme-gray-700;
+}
+
+.ide-file-changed-icon {
+ margin-left: auto;
+
+ > svg {
+ display: block;
+ }
+}
+
+.file-row:hover,
+.file-row:focus {
+ .ide-new-btn {
+ display: block;
+ }
+
+ .folder-icon {
+ fill: $gl-text-color-secondary;
+ }
+}
diff --git a/app/assets/stylesheets/pages/xterm.scss b/app/assets/stylesheets/page_bundles/xterm.scss
index 7d40c61da26..7f040ac9b96 100644
--- a/app/assets/stylesheets/pages/xterm.scss
+++ b/app/assets/stylesheets/page_bundles/xterm.scss
@@ -1,3 +1,5 @@
+@import 'framework/variables';
+
.build-page {
// color codes are based on http://en.wikipedia.org/wiki/File:Xterm_256color_chart.svg
// see also: https://gist.github.com/jasonm23/2868981
diff --git a/app/assets/stylesheets/pages/admin.scss b/app/assets/stylesheets/pages/admin.scss
index 6c555aee20a..f0acb78f731 100644
--- a/app/assets/stylesheets/pages/admin.scss
+++ b/app/assets/stylesheets/pages/admin.scss
@@ -4,3 +4,7 @@
padding-bottom: 46px;
}
}
+
+.usage-data {
+ max-height: 400px;
+}
diff --git a/app/assets/stylesheets/pages/branches.scss b/app/assets/stylesheets/pages/branches.scss
index 49fe50977f5..38fec3f0aa8 100644
--- a/app/assets/stylesheets/pages/branches.scss
+++ b/app/assets/stylesheets/pages/branches.scss
@@ -23,7 +23,7 @@
.bar {
position: absolute;
height: 4px;
- background-color: $divergence-graph-bar-bg;
+ background-color: $gl-gray-200;
}
.bar-behind {
@@ -61,7 +61,7 @@
height: 18px;
margin: 5px 0 0;
float: left;
- background-color: $divergence-graph-separator-bg;
+ background-color: $gl-gray-200;
}
}
diff --git a/app/assets/stylesheets/pages/clusters.scss b/app/assets/stylesheets/pages/clusters.scss
index 0f22fe21143..71a3fd544f2 100644
--- a/app/assets/stylesheets/pages/clusters.scss
+++ b/app/assets/stylesheets/pages/clusters.scss
@@ -4,9 +4,60 @@
}
}
-.cluster-applications-table {
- // Wait for the Vue to kick-in and render the applications block
- min-height: 628px;
+.cluster-application-row {
+ background: $gray-lighter;
+
+ &.cluster-application-installed {
+ background: none;
+ }
+
+ .settings-message {
+ padding: $gl-vert-padding $gl-padding-8;
+ }
+}
+
+@media (min-width: map-get($grid-breakpoints, md)) {
+ .cluster-application-list {
+ border: 1px solid $border-color;
+ border-radius: $border-radius-default;
+ }
+
+ .cluster-application-row {
+ border-bottom: 1px solid $border-color;
+ padding: $gl-padding;
+ }
+}
+
+.cluster-application-logo {
+ border: 3px solid $white-light;
+ box-shadow: 0 0 0 1px $gray-normal;
+
+ &.avatar:hover {
+ border-color: $white-light;
+ }
+}
+
+.cluster-application-warning {
+ font-weight: bold;
+ text-align: center;
+ padding: $gl-padding;
+ border-bottom: 1px solid $white-normal;
+
+ .svg-container {
+ display: inline-block;
+ vertical-align: middle;
+ margin-right: $gl-padding-8;
+ width: 40px;
+ height: 40px;
+ }
+}
+
+.cluster-application-description {
+ flex: 1;
+}
+
+.cluster-application-disabled {
+ opacity: 0.5;
}
.clusters-dropdown-menu {
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 7d7143631f2..987dcd32e3a 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -31,7 +31,7 @@
.file-mode-changed {
padding: 10px;
- color: $file-mode-changed;
+ color: $gl-gray-500;
}
.suppressed-container {
@@ -72,6 +72,7 @@
.line_holder td {
line-height: $code-line-height;
font-size: $code-font-size;
+ vertical-align: top;
&.noteable_line {
position: relative;
@@ -244,7 +245,7 @@
.swipe-wrap {
overflow: hidden;
- border-left: 1px solid $diff-swipe-border;
+ border-left: 1px solid $gl-gray-400;
position: absolute;
display: block;
top: 13px;
@@ -749,6 +750,10 @@
left: $gl-padding;
}
+ .dropdown-input .dropdown-input-search {
+ pointer-events: all;
+ }
+
.diff-changed-file {
display: flex;
padding-top: 8px;
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index 196f6ae6d8c..79984c1a546 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -153,7 +153,7 @@
.x-axis path,
.y-axis path {
- stroke: $stat-graph-axis-fill;
+ stroke: $gl-gray-350;
}
.label-x-axis-line,
@@ -163,7 +163,7 @@
.y-axis {
line {
- stroke: $stat-graph-axis-fill;
+ stroke: $gl-gray-350;
stroke-width: 1;
}
}
diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss
index da0c9b44498..a91d44805ee 100644
--- a/app/assets/stylesheets/pages/events.scss
+++ b/app/assets/stylesheets/pages/events.scss
@@ -87,7 +87,7 @@
border: 0;
background: $gray-light;
border-radius: 0;
- color: $events-pre-color;
+ color: $gl-gray-500;
overflow: hidden;
}
@@ -104,7 +104,7 @@
}
.event-note-icon {
- color: $events-pre-color;
+ color: $gl-gray-500;
float: left;
font-size: $gl-font-size;
line-height: 16px;
diff --git a/app/assets/stylesheets/pages/graph.scss b/app/assets/stylesheets/pages/graph.scss
index 22fce893fd7..4fb1a956fab 100644
--- a/app/assets/stylesheets/pages/graph.scss
+++ b/app/assets/stylesheets/pages/graph.scss
@@ -20,7 +20,7 @@
.graphs {
.graph-author-email {
float: right;
- color: $graph-author-email-color;
+ color: $gl-gray-500;
}
.graph-additions {
@@ -58,7 +58,7 @@
.y-axis-label {
line {
- stroke: $stat-graph-axis-fill;
+ stroke: $gl-gray-350;
}
text {
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index 60b4d39bb1a..fe792a53b44 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -3,7 +3,6 @@
}
.dashboard .side .card .card-header .input-group {
-
.form-control {
height: 42px;
}
@@ -30,14 +29,15 @@
}
}
+.group-nav-container .group-search,
.group-nav-container .nav-controls {
display: flex;
align-items: flex-start;
- padding: $gl-padding-top 0;
- border-bottom: 1px solid $border-color;
+ padding: $gl-padding-top 0 0;
.group-filter-form {
- flex: 1;
+ flex: 1 1 auto;
+ margin-right: $gl-padding-8;
}
.dropdown-menu-right {
@@ -106,7 +106,7 @@
&,
.dropdown,
.dropdown .dropdown-toggle,
- .btn-new {
+ .btn-success {
display: block;
}
@@ -118,7 +118,7 @@
.group-filter-form,
.dropdown .dropdown-toggle,
- .btn-new {
+ .btn-success {
width: 100%;
}
@@ -136,6 +136,10 @@
flex: 1;
}
+ .dropdown-toggle {
+ width: auto;
+ }
+
.dropdown-menu {
width: 100%;
max-width: inherit;
@@ -145,38 +149,14 @@
}
}
-.groups-empty-state {
- padding: 50px 100px;
- overflow: hidden;
-
- @include media-breakpoint-down(sm) {
- padding: 50px 0;
- }
-
- svg {
- float: right;
-
- @include media-breakpoint-down(sm) {
- float: none;
- display: block;
- width: 250px;
- position: relative;
- left: 50%;
- margin-left: -125px;
- }
- }
-
- .text-content {
- float: left;
- width: 460px;
- margin-top: 120px;
+.group-nav-container .group-search {
+ padding: $gl-padding 0;
+ border-bottom: 1px solid $border-color;
+}
- @include media-breakpoint-down(sm) {
- float: none;
- margin-top: 60px;
- width: auto;
- text-align: center;
- }
+.groups-listing {
+ .group-list-tree .group-row:first-child {
+ border-top: 0;
}
}
@@ -278,12 +258,12 @@
}
&::after {
- content: "";
+ content: '';
position: absolute;
height: 100%;
width: 100%;
background-color: transparent;
- border: 2px outset $kdb-border;
+ border: 2px outset $gl-gray-200;
border-radius: 50%;
animation: spin-avatar 3s infinite linear;
}
@@ -346,7 +326,7 @@
position: relative;
&::before {
- content: "";
+ content: '';
display: block;
width: 10px;
height: 0;
diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss
index 0350fe5752e..2c23f31c240 100644
--- a/app/assets/stylesheets/pages/help.scss
+++ b/app/assets/stylesheets/pages/help.scss
@@ -1,6 +1,6 @@
.shortcut-mappings {
font-size: 12px;
- color: $help-shortcut-mapping-color;
+ color: $gl-gray-700;
tbody:first-child tr:first-child {
padding-top: 0;
@@ -22,7 +22,7 @@
.shortcut {
padding-right: 10px;
- color: $help-shortcut-color;
+ color: $gl-gray-400;
text-align: right;
white-space: nowrap;
}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 9ac47a771a5..62a9f97caa9 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -701,6 +701,11 @@
align-self: center;
overflow: hidden;
text-overflow: ellipsis;
+
+ .user-status-emoji {
+ margin-left: $gl-padding-4;
+ margin-right: 0;
+ }
}
.js-issuable-selector-wrap {
@@ -721,13 +726,13 @@
display: flex;
}
- .issue-info-container {
+ .issuable-info-container {
-webkit-flex: 1;
flex: 1;
display: flex;
padding-right: $gl-padding;
- .issue-main-info {
+ .issuable-main-info {
flex: 1 auto;
margin-right: 10px;
}
@@ -763,7 +768,7 @@
margin-bottom: 10px;
min-width: 15px;
- .selected_issue {
+ .selected-issuable {
vertical-align: text-top;
}
}
@@ -795,7 +800,7 @@
}
.issuable-list li,
-.issue-info-container .controls {
+.issuable-info-container .controls {
.avatar-counter {
display: inline-block;
vertical-align: middle;
diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss
index c9e5fb9c579..fa0ab1a3bae 100644
--- a/app/assets/stylesheets/pages/login.scss
+++ b/app/assets/stylesheets/pages/login.scss
@@ -100,6 +100,22 @@
p {
margin: 0;
}
+
+ .omniauth-btn {
+ margin-bottom: $gl-padding;
+ width: 48%;
+ padding: $gl-padding-8;
+
+ @include media-breakpoint-down(md) {
+ width: 100%;
+ }
+
+ img {
+ width: $default-icon-size;
+ height: $default-icon-size;
+ margin-right: $gl-padding;
+ }
+ }
}
.new-session-tabs {
@@ -169,10 +185,6 @@
}
}
- label {
- font-weight: $gl-font-weight-normal;
- }
-
.submit-container {
margin-top: 16px;
}
@@ -200,15 +212,6 @@
}
}
-.oauth-image-link {
- margin-right: 10px;
-
- img {
- width: 32px;
- height: 32px;
- }
-}
-
.devise-layout-html {
margin: 0;
padding: 0;
diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss
index 5fdb2b4a90a..99609a96976 100644
--- a/app/assets/stylesheets/pages/members.scss
+++ b/app/assets/stylesheets/pages/members.scss
@@ -4,7 +4,7 @@
}
.users-project-form {
- .btn-create {
+ .btn-success {
margin-right: 10px;
}
}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 7b8cad254c7..97b131687d3 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -460,7 +460,7 @@
display: -webkit-flex;
display: flex;
- .issue-info-container {
+ .issuable-info-container {
-webkit-flex: 1;
flex: 1;
}
@@ -910,7 +910,7 @@
opacity: .65;
&:hover {
- color: $file-mode-changed;
+ color: $gl-gray-500;
text-decoration: none;
}
}
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index ac7b701c2e2..4268e194ed7 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -2,7 +2,7 @@
* Note Form
*/
.comment-btn {
- @extend .btn-create;
+ @extend .btn-success;
}
.diff-file .diff-content {
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index dbe9f0c03fb..c9e0899425f 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -94,8 +94,8 @@ ul.notes {
opacity: 0.5;
.dummy-avatar {
- background-color: $kdb-border;
- border: 1px solid darken($kdb-border, 25%);
+ background-color: $gl-gray-200;
+ border: 1px solid darken($gl-gray-200, 25%);
}
.note-headline-light,
@@ -334,20 +334,6 @@ ul.notes {
border: 1px solid $white-normal;
border-left: 0;
- &.notes_line {
- vertical-align: middle;
- text-align: center;
- padding: 10px 0;
- background: $gray-light;
- color: $text-color;
- }
-
- &.notes_line2 {
- text-align: center;
- padding: 10px 0;
- border-left: 1px solid $note-line2-border !important;
- }
-
&.notes_content {
background-color: $gray-light;
border-width: 1px 0;
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index 17f34319050..caa839c32a5 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -279,6 +279,10 @@ table.u2f-registrations {
}
}
+.codes {
+ padding-top: 14px;
+}
+
.oauth-application-show {
.scope-name {
font-weight: $gl-font-weight-bold;
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index a95e78931b1..7c42dcad959 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -115,7 +115,7 @@
.project-feature-controls {
display: flex;
align-items: center;
- margin: 8px 0;
+ margin: $gl-padding-8 0;
max-width: 432px;
.toggle-wrapper {
@@ -144,12 +144,8 @@
.group-home-panel {
padding-top: 24px;
padding-bottom: 24px;
+ border-bottom: 1px solid $border-color;
- @include media-breakpoint-up(sm) {
- border-bottom: 1px solid $border-color;
- }
-
- .project-avatar,
.group-avatar {
float: none;
margin: 0 auto;
@@ -175,7 +171,6 @@
}
}
- .project-home-desc,
.group-home-desc {
margin-left: auto;
margin-right: auto;
@@ -199,6 +194,62 @@
}
}
+.project-home-panel {
+ padding-top: $gl-padding-8;
+ padding-bottom: $gl-padding-24;
+
+ .project-title-row {
+ margin-right: $gl-padding-8;
+ }
+
+ .project-avatar {
+ width: $project-title-row-height;
+ height: $project-title-row-height;
+ flex-shrink: 0;
+ flex-basis: $project-title-row-height;
+ margin: 0 $gl-padding-8 0 0;
+ }
+
+ .project-title {
+ font-size: 20px;
+ line-height: $project-title-row-height;
+ font-weight: bold;
+ }
+
+ .project-metadata {
+ font-weight: normal;
+ font-size: 14px;
+ line-height: $gl-btn-line-height;
+ color: $gl-text-color-secondary;
+
+ .icon {
+ margin-right: $gl-padding-4;
+ font-size: 16px;
+ }
+
+ .project-visibility,
+ .project-license,
+ .project-tag-list {
+ margin-right: $gl-padding-8;
+ }
+
+ .project-license {
+ .btn {
+ line-height: 0;
+ border-width: 0;
+ }
+ }
+
+ .project-tag-list,
+ .project-license {
+ .icon {
+ position: relative;
+ top: 2px;
+ }
+ }
+ }
+}
+
.nav > .project-repo-buttons {
margin-top: 0;
}
@@ -206,8 +257,6 @@
.project-repo-buttons,
.group-buttons {
.btn {
- padding: 3px 10px;
-
&:last-child {
margin-left: 0;
}
@@ -222,11 +271,15 @@
.fa-caret-down {
margin-left: 3px;
+
+ &.dropdown-btn-icon {
+ margin-left: 0;
+ }
}
}
.project-action-button {
- margin: 15px 5px 0;
+ margin: $gl-padding $gl-padding-8 0 0;
vertical-align: top;
}
@@ -243,82 +296,45 @@
.count-buttons {
display: inline-block;
vertical-align: top;
- margin-top: 15px;
- }
+ margin-top: $gl-padding;
- .project-clone-holder {
- display: inline-block;
- margin: 15px 5px 0 0;
+ .count-badge {
+ height: $input-height;
- input {
- height: 28px;
+ .icon {
+ top: -1px;
+ }
}
- }
- .count-with-arrow {
- display: inline-block;
- position: relative;
- margin-left: 4px;
+ .count-badge-count,
+ .count-badge-button {
+ border: 1px solid $border-color;
+ line-height: 1;
+ }
- .arrow {
- &::before {
- content: '';
- display: inline-block;
- position: absolute;
- width: 0;
- height: 0;
- border-color: transparent;
- border-style: solid;
- top: 50%;
- left: 0;
- margin-top: -6px;
- border-width: 7px 5px 7px 0;
- border-right-color: $count-arrow-border;
- pointer-events: none;
- }
+ .count,
+ .count-badge-button {
+ color: $gl-text-color;
+ }
- &::after {
- content: '';
- position: absolute;
- width: 0;
- height: 0;
- border-color: transparent;
- border-style: solid;
- top: 50%;
- left: 1px;
- margin-top: -9px;
- border-width: 10px 7px 10px 0;
- border-right-color: $white-light;
- pointer-events: none;
- }
+ .count-badge-count {
+ padding: 0 12px;
+ border-right: 0;
+ border-radius: $border-radius-base 0 0 $border-radius-base;
+ background: $gray-light;
}
- .count {
- @include btn-white;
- display: inline-block;
- background: $white-light;
- border-radius: 2px;
- border-width: 1px;
- border-style: solid;
- font-size: 13px;
- font-weight: $gl-font-weight-bold;
- line-height: 13px;
- letter-spacing: 0.4px;
- padding: 6px 14px;
- text-align: center;
- vertical-align: middle;
- touch-action: manipulation;
- background-image: none;
- white-space: nowrap;
- margin: 0 10px 0 4px;
+ .count-badge-button {
+ border-radius: 0 $border-radius-base $border-radius-base 0;
+ }
+ }
- a {
- color: inherit;
- }
+ .project-clone-holder {
+ display: inline-block;
+ margin: $gl-padding $gl-padding-8 0 0;
- &:hover {
- background: $white-light;
- }
+ input {
+ height: $input-height;
}
}
@@ -333,6 +349,14 @@
min-width: 320px;
}
}
+
+ .mobile-git-clone {
+ margin-top: $gl-padding-8;
+
+ .dropdown-menu-inner-content {
+ @extend .monospace;
+ }
+ }
}
.split-one {
@@ -347,7 +371,7 @@
.save-project-loader {
margin-top: 50px;
margin-bottom: 50px;
- color: $save-project-loader-color;
+ color: $gl-gray-700;
}
.transfer-project .select2-container {
@@ -423,7 +447,7 @@
> li + li::before {
padding: 0 3px;
- color: $project-breadcrumb-color;
+ color: $gl-gray-400;
}
a {
@@ -511,7 +535,6 @@
.controls {
margin-left: auto;
}
-
}
.choose-template {
@@ -574,7 +597,7 @@
flex-wrap: wrap;
.btn {
- padding: 8px;
+ padding: $gl-padding-8;
margin-right: 10px;
}
@@ -651,7 +674,7 @@
left: -10px;
top: 50%;
z-index: 10;
- padding: 8px 0;
+ padding: $gl-padding-8 0;
text-align: center;
background-color: $white-light;
color: $gl-text-color-tertiary;
@@ -665,7 +688,7 @@
left: 50%;
top: 0;
transform: translateX(-50%);
- padding: 0 8px;
+ padding: 0 $gl-padding-8;
}
}
@@ -699,17 +722,51 @@
.project-stats {
font-size: 0;
text-align: center;
- max-width: 100%;
border-bottom: 1px solid $border-color;
- .nav {
- margin-top: $gl-padding-8;
- margin-bottom: $gl-padding-8;
+ .scrolling-tabs-container {
+ .scrolling-tabs {
+ margin-top: $gl-padding-8;
+ margin-bottom: $gl-padding-8;
+ flex-wrap: wrap;
+ border-bottom: 0;
+ }
+ .fade-left,
+ .fade-right {
+ top: 0;
+ height: 100%;
+
+ .fa {
+ top: 50%;
+ margin-top: -$gl-padding-8;
+ }
+ }
+
+ .nav {
+ flex-basis: 100%;
+
+ + .nav {
+ margin: $gl-padding-8 0;
+ }
+ }
+
+ @include media-breakpoint-down(md) {
+ flex-direction: column;
+
+ .nav {
+ flex-wrap: nowrap;
+ }
+
+ .nav:first-child {
+ margin-right: $gl-padding-8;
+ }
+ }
+ }
+
+ .nav {
> li {
display: inline-block;
- margin-top: $gl-padding-4;
- margin-bottom: $gl-padding-4;
&:not(:last-child) {
margin-right: $gl-padding;
@@ -732,13 +789,17 @@
font-size: $gl-font-size;
line-height: $gl-btn-line-height;
color: $gl-text-color-secondary;
+ white-space: nowrap;
}
.stat-link {
+ border-bottom: 0;
+
&:hover,
&:focus {
color: $gl-text-color;
text-decoration: underline;
+ border-bottom: 0;
}
}
@@ -868,7 +929,7 @@ pre.light-well {
}
.git-clone-holder {
- width: 380px;
+ width: 320px;
.btn-clipboard {
border: 1px solid $border-color;
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index 77119aea9e2..04151b1cd59 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -218,7 +218,7 @@ input[type='checkbox']:hover {
}
.btn-search,
- .btn-new {
+ .btn-success {
width: 100%;
margin-top: 5px;
diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss
index e351dd7c0bb..dbf8692d69b 100644
--- a/app/assets/stylesheets/pages/settings.scss
+++ b/app/assets/stylesheets/pages/settings.scss
@@ -106,7 +106,7 @@
.settings-list-icon {
color: $gl-text-color-secondary;
- font-size: $settings-icon-size;
+ font-size: $default-icon-size;
line-height: 42px;
}
@@ -249,7 +249,7 @@
}
.loading-metrics .metrics-load-spinner {
- color: $loading-color;
+ color: $gl-gray-700;
}
.metrics-list {
diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss
index 5d3b7b21ce4..3fc37e20c36 100644
--- a/app/assets/stylesheets/pages/todos.scss
+++ b/app/assets/stylesheets/pages/todos.scss
@@ -143,7 +143,7 @@
border: 0;
background: $gray-light;
border-radius: 0;
- color: $todo-body-pre-color;
+ color: $gl-gray-500;
margin: 0 20px;
overflow: hidden;
}
@@ -205,7 +205,7 @@
.todo-body {
margin: 0;
- border-left: 2px solid $todo-body-border;
+ border-left: 2px solid $gl-gray-100;
padding-left: 10px;
}
}
diff --git a/app/assets/stylesheets/pages/ui_dev_kit.scss b/app/assets/stylesheets/pages/ui_dev_kit.scss
index 48ac5b21db8..84c617c7ec0 100644
--- a/app/assets/stylesheets/pages/ui_dev_kit.scss
+++ b/app/assets/stylesheets/pages/ui_dev_kit.scss
@@ -6,7 +6,7 @@
.example {
padding: 15px;
- border: 1px dashed $ui-dev-kit-example-border;
+ border: 1px dashed $gl-gray-100;
margin-bottom: 15px;
&::before {
diff --git a/app/assets/stylesheets/performance_bar.scss b/app/assets/stylesheets/performance_bar.scss
index 57d43beaf21..2e2ab8532d2 100644
--- a/app/assets/stylesheets/performance_bar.scss
+++ b/app/assets/stylesheets/performance_bar.scss
@@ -11,10 +11,10 @@
height: $performance-bar-height;
background: $black;
line-height: $performance-bar-height;
- color: $perf-bar-text;
+ color: $gl-gray-400;
select {
- color: $perf-bar-text;
+ color: $gl-gray-400;
width: 200px;
}
@@ -53,7 +53,7 @@
padding: 4px 6px;
font-family: Consolas, 'Liberation Mono', Courier, monospace;
line-height: 1;
- color: $perf-bar-bucket-color;
+ color: $gl-gray-200;
border-radius: 3px;
box-shadow: 0 1px 0 $perf-bar-bucket-box-shadow-from,
inset 0 1px 2px $perf-bar-bucket-box-shadow-to;
diff --git a/app/controllers/abuse_reports_controller.rb b/app/controllers/abuse_reports_controller.rb
index ed13ead63f9..68e14f0c2e5 100644
--- a/app/controllers/abuse_reports_controller.rb
+++ b/app/controllers/abuse_reports_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class AbuseReportsController < ApplicationController
before_action :set_user, only: [:new]
@@ -30,6 +32,7 @@ class AbuseReportsController < ApplicationController
))
end
+ # rubocop: disable CodeReuse/ActiveRecord
def set_user
@user = User.find_by(id: params[:user_id])
@@ -39,4 +42,5 @@ class AbuseReportsController < ApplicationController
redirect_to @user, alert: "Cannot create the abuse report. This user has been blocked."
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/controllers/admin/abuse_reports_controller.rb b/app/controllers/admin/abuse_reports_controller.rb
index dc9a6df5f75..d5537023b26 100644
--- a/app/controllers/admin/abuse_reports_controller.rb
+++ b/app/controllers/admin/abuse_reports_controller.rb
@@ -1,8 +1,12 @@
+# frozen_string_literal: true
+
class Admin::AbuseReportsController < Admin::ApplicationController
+ # rubocop: disable CodeReuse/ActiveRecord
def index
@abuse_reports = AbuseReport.order(id: :desc).page(params[:page])
@abuse_reports.includes(:reporter, :user)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def destroy
abuse_report = AbuseReport.find(params[:id])
diff --git a/app/controllers/admin/appearances_controller.rb b/app/controllers/admin/appearances_controller.rb
index 9aaec905734..fdd3b4126ff 100644
--- a/app/controllers/admin/appearances_controller.rb
+++ b/app/controllers/admin/appearances_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Admin::AppearancesController < Admin::ApplicationController
before_action :set_appearance, except: :create
diff --git a/app/controllers/admin/application_controller.rb b/app/controllers/admin/application_controller.rb
index a4648b33cfa..ef182b981f1 100644
--- a/app/controllers/admin/application_controller.rb
+++ b/app/controllers/admin/application_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Provides a base class for Admin controllers to subclass
#
# Automatically sets the layout and ensures an administrator is logged in
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 9723e400574..875e46969fe 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -1,19 +1,58 @@
+# frozen_string_literal: true
+
class Admin::ApplicationSettingsController < Admin::ApplicationController
+ include InternalRedirect
before_action :set_application_setting
def show
end
+ def integrations
+ end
+
+ def repository
+ end
+
+ def templates
+ end
+
+ def ci_cd
+ end
+
+ def reporting
+ end
+
+ def metrics_and_profiling
+ end
+
+ def network
+ end
+
+ def geo
+ end
+
+ def preferences
+ end
+
def update
successful = ApplicationSettings::UpdateService
.new(@application_setting, current_user, application_setting_params)
.execute
- if successful
- redirect_to admin_application_settings_path,
- notice: 'Application settings saved successfully'
- else
- render :show
+ if recheck_user_consent?
+ session[:ask_for_usage_stats_consent] = current_user.requires_usage_stats_consent?
+ end
+
+ redirect_path = referer_path(request) || admin_application_settings_path
+
+ respond_to do |format|
+ if successful
+ format.json { head :ok }
+ format.html { redirect_to redirect_path, notice: 'Application settings saved successfully' }
+ else
+ format.json { head :bad_request }
+ format.html { render :show }
+ end
end
end
@@ -76,6 +115,13 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
)
end
+ def recheck_user_consent?
+ return false unless session[:ask_for_usage_stats_consent]
+ return false unless params[:application_setting]
+
+ params[:application_setting].key?(:usage_ping_enabled) || params[:application_setting].key?(:version_check_enabled)
+ end
+
def visible_application_setting_attributes
ApplicationSettingsHelper.visible_attributes + [
:domain_blacklist_file,
diff --git a/app/controllers/admin/applications_controller.rb b/app/controllers/admin/applications_controller.rb
index 5be23c76a95..00d2cc01192 100644
--- a/app/controllers/admin/applications_controller.rb
+++ b/app/controllers/admin/applications_controller.rb
@@ -1,12 +1,16 @@
+# frozen_string_literal: true
+
class Admin::ApplicationsController < Admin::ApplicationController
include OauthApplications
before_action :set_application, only: [:show, :edit, :update, :destroy]
before_action :load_scopes, only: [:new, :create, :edit, :update]
+ # rubocop: disable CodeReuse/ActiveRecord
def index
@applications = Doorkeeper::Application.where("owner_id IS NULL")
end
+ # rubocop: enable CodeReuse/ActiveRecord
def show
end
@@ -45,9 +49,11 @@ class Admin::ApplicationsController < Admin::ApplicationController
private
+ # rubocop: disable CodeReuse/ActiveRecord
def set_application
@application = Doorkeeper::Application.where("owner_id IS NULL").find(params[:id])
end
+ # rubocop: enable CodeReuse/ActiveRecord
# Only allow a trusted parameter "white list" through.
def application_params
diff --git a/app/controllers/admin/background_jobs_controller.rb b/app/controllers/admin/background_jobs_controller.rb
index 5f90ad7137d..7701f2e645b 100644
--- a/app/controllers/admin/background_jobs_controller.rb
+++ b/app/controllers/admin/background_jobs_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Admin::BackgroundJobsController < Admin::ApplicationController
def show
ps_output, _ = Gitlab::Popen.popen(%W(ps ww -U #{Gitlab.config.gitlab.user} -o pid,pcpu,pmem,stat,start,command))
diff --git a/app/controllers/admin/broadcast_messages_controller.rb b/app/controllers/admin/broadcast_messages_controller.rb
index a9109a1d4d0..a91d9a534cd 100644
--- a/app/controllers/admin/broadcast_messages_controller.rb
+++ b/app/controllers/admin/broadcast_messages_controller.rb
@@ -1,12 +1,16 @@
+# frozen_string_literal: true
+
class Admin::BroadcastMessagesController < Admin::ApplicationController
include BroadcastMessagesHelper
before_action :finder, only: [:edit, :update, :destroy]
+ # rubocop: disable CodeReuse/ActiveRecord
def index
@broadcast_messages = BroadcastMessage.order(ends_at: :desc).page(params[:page])
@broadcast_message = BroadcastMessage.new
end
+ # rubocop: enable CodeReuse/ActiveRecord
def edit
end
diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb
index 737942f3eb2..b5fb5511638 100644
--- a/app/controllers/admin/dashboard_controller.rb
+++ b/app/controllers/admin/dashboard_controller.rb
@@ -1,13 +1,17 @@
+# frozen_string_literal: true
+
class Admin::DashboardController < Admin::ApplicationController
include CountHelper
COUNTED_ITEMS = [Project, User, Group, ForkedProjectLink, Issue, MergeRequest,
Note, Snippet, Key, Milestone].freeze
+ # rubocop: disable CodeReuse/ActiveRecord
def index
@counts = Gitlab::Database::Count.approximate_counts(COUNTED_ITEMS)
@projects = Project.order_id_desc.without_deleted.with_route.limit(10)
@users = User.order_id_desc.limit(10)
@groups = Group.order_id_desc.with_route.limit(10)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/controllers/admin/deploy_keys_controller.rb b/app/controllers/admin/deploy_keys_controller.rb
index 5c2025c1988..49ce275ad14 100644
--- a/app/controllers/admin/deploy_keys_controller.rb
+++ b/app/controllers/admin/deploy_keys_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Admin::DeployKeysController < Admin::ApplicationController
before_action :deploy_keys, only: [:index]
before_action :deploy_key, only: [:destroy, :edit, :update]
diff --git a/app/controllers/admin/gitaly_servers_controller.rb b/app/controllers/admin/gitaly_servers_controller.rb
index 11c4dfe3d8d..0a5566bfe70 100644
--- a/app/controllers/admin/gitaly_servers_controller.rb
+++ b/app/controllers/admin/gitaly_servers_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Admin::GitalyServersController < Admin::ApplicationController
def index
@gitaly_servers = Gitaly::Server.all
diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb
index d7a5b745d3f..46e85e1424f 100644
--- a/app/controllers/admin/groups_controller.rb
+++ b/app/controllers/admin/groups_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Admin::GroupsController < Admin::ApplicationController
include MembersPresentation
@@ -10,6 +12,7 @@ class Admin::GroupsController < Admin::ApplicationController
@groups = @groups.page(params[:page])
end
+ # rubocop: disable CodeReuse/ActiveRecord
def show
@group = Group.with_statistics.joins(:route).group('routes.path').find_by_full_path(params[:id])
@members = present_members(
@@ -18,6 +21,7 @@ class Admin::GroupsController < Admin::ApplicationController
AccessRequestsFinder.new(@group).execute(current_user))
@projects = @group.projects.with_statistics.page(params[:projects_page])
end
+ # rubocop: enable CodeReuse/ActiveRecord
def new
@group = Group.new
diff --git a/app/controllers/admin/health_check_controller.rb b/app/controllers/admin/health_check_controller.rb
index 61247b280b3..44864f9c7d0 100644
--- a/app/controllers/admin/health_check_controller.rb
+++ b/app/controllers/admin/health_check_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Admin::HealthCheckController < Admin::ApplicationController
def show
@errors = HealthCheck::Utils.process_checks(['standard'])
diff --git a/app/controllers/admin/hook_logs_controller.rb b/app/controllers/admin/hook_logs_controller.rb
index 3017f96c26f..8301b3aa880 100644
--- a/app/controllers/admin/hook_logs_controller.rb
+++ b/app/controllers/admin/hook_logs_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Admin::HookLogsController < Admin::ApplicationController
include HooksExecution
diff --git a/app/controllers/admin/hooks_controller.rb b/app/controllers/admin/hooks_controller.rb
index a98c355c7ba..d0abdec50ae 100644
--- a/app/controllers/admin/hooks_controller.rb
+++ b/app/controllers/admin/hooks_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Admin::HooksController < Admin::ApplicationController
include HooksExecution
diff --git a/app/controllers/admin/identities_controller.rb b/app/controllers/admin/identities_controller.rb
index ceb45865804..b51c2f678ca 100644
--- a/app/controllers/admin/identities_controller.rb
+++ b/app/controllers/admin/identities_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Admin::IdentitiesController < Admin::ApplicationController
before_action :user
before_action :identity, except: [:index, :new, :create]
@@ -44,9 +46,11 @@ class Admin::IdentitiesController < Admin::ApplicationController
protected
+ # rubocop: disable CodeReuse/ActiveRecord
def user
@user ||= User.find_by!(username: params[:user_id])
end
+ # rubocop: enable CodeReuse/ActiveRecord
def identity
@identity ||= user.identities.find(params[:id])
diff --git a/app/controllers/admin/impersonation_tokens_controller.rb b/app/controllers/admin/impersonation_tokens_controller.rb
index a7b562b1d8e..f5825ecb19a 100644
--- a/app/controllers/admin/impersonation_tokens_controller.rb
+++ b/app/controllers/admin/impersonation_tokens_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Admin::ImpersonationTokensController < Admin::ApplicationController
before_action :user
@@ -30,9 +32,11 @@ class Admin::ImpersonationTokensController < Admin::ApplicationController
private
+ # rubocop: disable CodeReuse/ActiveRecord
def user
@user ||= User.find_by!(username: params[:user_id])
end
+ # rubocop: enable CodeReuse/ActiveRecord
def finder(options = {})
PersonalAccessTokensFinder.new({ user: user, impersonation: true }.merge(options))
@@ -42,6 +46,7 @@ class Admin::ImpersonationTokensController < Admin::ApplicationController
params.require(:personal_access_token).permit(:name, :expires_at, :impersonation, scopes: [])
end
+ # rubocop: disable CodeReuse/ActiveRecord
def set_index_vars
@scopes = Gitlab::Auth.available_scopes(current_user)
@@ -49,4 +54,5 @@ class Admin::ImpersonationTokensController < Admin::ApplicationController
@inactive_impersonation_tokens = finder(state: 'inactive').execute
@active_impersonation_tokens = finder(state: 'active').execute.order(:expires_at)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/controllers/admin/impersonations_controller.rb b/app/controllers/admin/impersonations_controller.rb
index d2f947d2c66..08d7e3b4fa2 100644
--- a/app/controllers/admin/impersonations_controller.rb
+++ b/app/controllers/admin/impersonations_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Admin::ImpersonationsController < Admin::ApplicationController
skip_before_action :authenticate_admin!
before_action :authenticate_impersonator!
diff --git a/app/controllers/admin/jobs_controller.rb b/app/controllers/admin/jobs_controller.rb
index e355d5fdea7..0c1afdc3d3b 100644
--- a/app/controllers/admin/jobs_controller.rb
+++ b/app/controllers/admin/jobs_controller.rb
@@ -1,4 +1,7 @@
+# frozen_string_literal: true
+
class Admin::JobsController < Admin::ApplicationController
+ # rubocop: disable CodeReuse/ActiveRecord
def index
@scope = params[:scope]
@all_builds = Ci::Build
@@ -16,6 +19,7 @@ class Admin::JobsController < Admin::ApplicationController
end
@builds = @builds.page(params[:page]).per(30)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def cancel_all
Ci::Build.running_or_pending.each(&:cancel)
diff --git a/app/controllers/admin/keys_controller.rb b/app/controllers/admin/keys_controller.rb
index 0b76193a90e..4e9262ccc96 100644
--- a/app/controllers/admin/keys_controller.rb
+++ b/app/controllers/admin/keys_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Admin::KeysController < Admin::ApplicationController
before_action :user, only: [:show, :destroy]
@@ -24,9 +26,11 @@ class Admin::KeysController < Admin::ApplicationController
protected
+ # rubocop: disable CodeReuse/ActiveRecord
def user
@user ||= User.find_by!(username: params[:user_id])
end
+ # rubocop: enable CodeReuse/ActiveRecord
def key_params
params.require(:user_id, :id)
diff --git a/app/controllers/admin/labels_controller.rb b/app/controllers/admin/labels_controller.rb
index 7eb8f758807..aa5eae7a474 100644
--- a/app/controllers/admin/labels_controller.rb
+++ b/app/controllers/admin/labels_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Admin::LabelsController < Admin::ApplicationController
before_action :set_label, only: [:show, :edit, :update, :destroy]
diff --git a/app/controllers/admin/logs_controller.rb b/app/controllers/admin/logs_controller.rb
index 12a27cede75..06b0e6a15a3 100644
--- a/app/controllers/admin/logs_controller.rb
+++ b/app/controllers/admin/logs_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Admin::LogsController < Admin::ApplicationController
before_action :loggers
@@ -12,7 +14,8 @@ class Admin::LogsController < Admin::ApplicationController
Gitlab::GitLogger,
Gitlab::EnvironmentLogger,
Gitlab::SidekiqLogger,
- Gitlab::RepositoryCheckLogger
+ Gitlab::RepositoryCheckLogger,
+ Gitlab::ProjectServiceLogger
]
end
end
diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb
index 3afe66c3566..550f29a58d2 100644
--- a/app/controllers/admin/projects_controller.rb
+++ b/app/controllers/admin/projects_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Admin::ProjectsController < Admin::ApplicationController
include MembersPresentation
@@ -19,6 +21,7 @@ class Admin::ProjectsController < Admin::ApplicationController
end
end
+ # rubocop: disable CodeReuse/ActiveRecord
def show
if @group
@group_members = present_members(
@@ -30,7 +33,9 @@ class Admin::ProjectsController < Admin::ApplicationController
@requesters = present_members(
AccessRequestsFinder.new(@project).execute(current_user))
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def transfer
namespace = Namespace.find_by(id: params[:new_namespace_id])
::Projects::TransferService.new(@project, current_user, params.dup).execute(namespace)
@@ -38,6 +43,7 @@ class Admin::ProjectsController < Admin::ApplicationController
@project.reload
redirect_to admin_project_path(@project)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def repository_check
RepositoryCheck::SingleRepositoryWorker.perform_async(@project.id)
diff --git a/app/controllers/admin/requests_profiles_controller.rb b/app/controllers/admin/requests_profiles_controller.rb
index a478176e138..64d74ae4231 100644
--- a/app/controllers/admin/requests_profiles_controller.rb
+++ b/app/controllers/admin/requests_profiles_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Admin::RequestsProfilesController < Admin::ApplicationController
def index
@profile_token = Gitlab::RequestProfiler.profile_token
diff --git a/app/controllers/admin/runner_projects_controller.rb b/app/controllers/admin/runner_projects_controller.rb
index 51d5799cd89..774ce04d079 100644
--- a/app/controllers/admin/runner_projects_controller.rb
+++ b/app/controllers/admin/runner_projects_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Admin::RunnerProjectsController < Admin::ApplicationController
before_action :project, only: [:create]
diff --git a/app/controllers/admin/runners_controller.rb b/app/controllers/admin/runners_controller.rb
index 6c76c55a9d4..0b6ff491c66 100644
--- a/app/controllers/admin/runners_controller.rb
+++ b/app/controllers/admin/runners_controller.rb
@@ -1,12 +1,13 @@
+# frozen_string_literal: true
+
class Admin::RunnersController < Admin::ApplicationController
before_action :runner, except: :index
def index
- sort = params[:sort] == 'contacted_asc' ? { contacted_at: :asc } : { id: :desc }
- @runners = Ci::Runner.order(sort)
- @runners = @runners.search(params[:search]) if params[:search].present?
- @runners = @runners.page(params[:page]).per(30)
- @active_runners_cnt = Ci::Runner.online.count
+ finder = Admin::RunnersFinder.new(params: params)
+ @runners = finder.execute
+ @active_runners_count = Ci::Runner.online.count
+ @sort = finder.sort_key
end
def show
@@ -57,6 +58,7 @@ class Admin::RunnersController < Admin::ApplicationController
params.require(:runner).permit(Ci::Runner::FORM_EDITABLE)
end
+ # rubocop: disable CodeReuse/ActiveRecord
def assign_builds_and_projects
@builds = runner.builds.order('id DESC').first(30)
@projects =
@@ -69,4 +71,5 @@ class Admin::RunnersController < Admin::ApplicationController
@projects = @projects.where.not(id: runner.projects.select(:id)) if runner.projects.any?
@projects = @projects.page(params[:page]).per(30)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/controllers/admin/services_controller.rb b/app/controllers/admin/services_controller.rb
index 91a36af34f3..c455930c044 100644
--- a/app/controllers/admin/services_controller.rb
+++ b/app/controllers/admin/services_controller.rb
@@ -30,16 +30,20 @@ class Admin::ServicesController < Admin::ApplicationController
private
+ # rubocop: disable CodeReuse/ActiveRecord
def services_templates
Service.available_services_names.map do |service_name|
service_template = "#{service_name}_service".camelize.constantize
service_template.where(template: true).first_or_create
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def service
@service ||= Service.where(id: params[:id], template: true).first
end
+ # rubocop: enable CodeReuse/ActiveRecord
def whitelist_query_limiting
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42430')
diff --git a/app/controllers/admin/spam_logs_controller.rb b/app/controllers/admin/spam_logs_controller.rb
index d52d67a67a5..18d22c95b61 100644
--- a/app/controllers/admin/spam_logs_controller.rb
+++ b/app/controllers/admin/spam_logs_controller.rb
@@ -1,7 +1,11 @@
+# frozen_string_literal: true
+
class Admin::SpamLogsController < Admin::ApplicationController
+ # rubocop: disable CodeReuse/ActiveRecord
def index
@spam_logs = SpamLog.order(id: :desc).page(params[:page])
end
+ # rubocop: enable CodeReuse/ActiveRecord
def destroy
spam_log = SpamLog.find(params[:id])
diff --git a/app/controllers/admin/system_info_controller.rb b/app/controllers/admin/system_info_controller.rb
index 99039724521..244fc2b31bb 100644
--- a/app/controllers/admin/system_info_controller.rb
+++ b/app/controllers/admin/system_info_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Admin::SystemInfoController < Admin::ApplicationController
EXCLUDED_MOUNT_OPTIONS = [
'nobrowse',
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index a51a8c3ed4a..b783c0e2a6f 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Admin::UsersController < Admin::ApplicationController
before_action :user, except: [:index, :new, :create]
@@ -174,9 +176,11 @@ class Admin::UsersController < Admin::ApplicationController
user == current_user
end
+ # rubocop: disable CodeReuse/ActiveRecord
def user
@user ||= User.find_by!(username: params[:id])
end
+ # rubocop: enable CodeReuse/ActiveRecord
def redirect_back_or_admin_user(options = {})
redirect_back_or_default(default: default_route, options: options)
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index e5b38898a67..838527aaa41 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'gon'
require 'fogbugz'
@@ -22,6 +24,7 @@ class ApplicationController < ActionController::Base
before_action :add_gon_variables, unless: [:peek_request?, :json_request?]
before_action :configure_permitted_parameters, if: :devise_controller?
before_action :require_email, unless: :devise_controller?
+ before_action :set_usage_stats_consent_flag
around_action :set_locale
@@ -110,6 +113,7 @@ class ApplicationController < ActionController::Base
def append_info_to_payload(payload)
super
+ payload[:ua] = request.env["HTTP_USER_AGENT"]
payload[:remote_ip] = request.remote_ip
logged_user = auth_user
@@ -433,4 +437,29 @@ class ApplicationController < ActionController::Base
!(peek_request? || devise_controller?)
end
+
+ def set_usage_stats_consent_flag
+ return unless current_user
+ return if sessionless_user?
+ return if session.has_key?(:ask_for_usage_stats_consent)
+
+ session[:ask_for_usage_stats_consent] = current_user.requires_usage_stats_consent?
+
+ if session[:ask_for_usage_stats_consent]
+ disable_usage_stats
+ end
+ end
+
+ def disable_usage_stats
+ application_setting_params = {
+ usage_ping_enabled: false,
+ version_check_enabled: false,
+ skip_usage_stats_user: true
+ }
+ settings = Gitlab::CurrentSettings.current_application_settings
+
+ ApplicationSettings::UpdateService
+ .new(settings, current_user, application_setting_params)
+ .execute
+ end
end
diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb
index 9e30b982b06..3766b64a091 100644
--- a/app/controllers/autocomplete_controller.rb
+++ b/app/controllers/autocomplete_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class AutocompleteController < ApplicationController
skip_before_action :authenticate_user!, only: [:users, :award_emojis]
diff --git a/app/controllers/boards/application_controller.rb b/app/controllers/boards/application_controller.rb
index b2675025fc0..eab908ba5ed 100644
--- a/app/controllers/boards/application_controller.rb
+++ b/app/controllers/boards/application_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Boards
class ApplicationController < ::ApplicationController
respond_to :json
diff --git a/app/controllers/boards/issues_controller.rb b/app/controllers/boards/issues_controller.rb
index 7dd19f87ef5..4f3d737e3ce 100644
--- a/app/controllers/boards/issues_controller.rb
+++ b/app/controllers/boards/issues_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Boards
class IssuesController < Boards::ApplicationController
include BoardsResponses
@@ -11,6 +13,7 @@ module Boards
before_action :authorize_update_issue, only: [:update]
skip_before_action :authenticate_user!, only: [:index]
+ # rubocop: disable CodeReuse/ActiveRecord
def index
list_service = Boards::Issues::ListService.new(board_parent, current_user, filter_params)
issues = list_service.execute
@@ -25,6 +28,7 @@ module Boards
render_issues(issues, list_service.metadata)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def create
service = Boards::Issues::CreateService.new(board_parent, project, current_user, issue_params)
diff --git a/app/controllers/boards/lists_controller.rb b/app/controllers/boards/lists_controller.rb
index e8b5934f2a9..ccd02144671 100644
--- a/app/controllers/boards/lists_controller.rb
+++ b/app/controllers/boards/lists_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Boards
class ListsController < Boards::ApplicationController
include BoardsResponses
diff --git a/app/controllers/ci/lints_controller.rb b/app/controllers/ci/lints_controller.rb
index 738a6a5173e..99ce24bd435 100644
--- a/app/controllers/ci/lints_controller.rb
+++ b/app/controllers/ci/lints_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Ci
class LintsController < ::ApplicationController
before_action :authenticate_user!
diff --git a/app/controllers/concerns/accepts_pending_invitations.rb b/app/controllers/concerns/accepts_pending_invitations.rb
index 6e8aef52b52..cb66c1a055d 100644
--- a/app/controllers/concerns/accepts_pending_invitations.rb
+++ b/app/controllers/concerns/accepts_pending_invitations.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module AcceptsPendingInvitations
extend ActiveSupport::Concern
diff --git a/app/controllers/concerns/authenticates_with_two_factor.rb b/app/controllers/concerns/authenticates_with_two_factor.rb
index dfa1da7872c..5507328f8ae 100644
--- a/app/controllers/concerns/authenticates_with_two_factor.rb
+++ b/app/controllers/concerns/authenticates_with_two_factor.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# == AuthenticatesWithTwoFactor
#
# Controller concern to handle two-factor authentication
@@ -88,6 +90,7 @@ module AuthenticatesWithTwoFactor
# Setup in preparation of communication with a U2F (universal 2nd factor) device
# Actual communication is performed using a Javascript API
+ # rubocop: disable CodeReuse/ActiveRecord
def setup_u2f_authentication(user)
key_handles = user.u2f_registrations.pluck(:key_handle)
u2f = U2F::U2F.new(u2f_app_id)
@@ -99,4 +102,5 @@ module AuthenticatesWithTwoFactor
sign_requests: sign_requests })
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/controllers/concerns/boards_responses.rb b/app/controllers/concerns/boards_responses.rb
index da830ec2cb1..b7e4f9b81f1 100644
--- a/app/controllers/concerns/boards_responses.rb
+++ b/app/controllers/concerns/boards_responses.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module BoardsResponses
include Gitlab::Utils::StrongMemoize
diff --git a/app/controllers/concerns/checks_collaboration.rb b/app/controllers/concerns/checks_collaboration.rb
index 81367663a06..1fa82f7dcd4 100644
--- a/app/controllers/concerns/checks_collaboration.rb
+++ b/app/controllers/concerns/checks_collaboration.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ChecksCollaboration
def can_collaborate_with_project?(project, ref: nil)
return true if can?(current_user, :push_code, project)
diff --git a/app/controllers/concerns/continue_params.rb b/app/controllers/concerns/continue_params.rb
index 8b7355974df..f0e6adf4dec 100644
--- a/app/controllers/concerns/continue_params.rb
+++ b/app/controllers/concerns/continue_params.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ContinueParams
include InternalRedirect
extend ActiveSupport::Concern
diff --git a/app/controllers/concerns/controller_with_cross_project_access_check.rb b/app/controllers/concerns/controller_with_cross_project_access_check.rb
index a45c3384578..3f72f092683 100644
--- a/app/controllers/concerns/controller_with_cross_project_access_check.rb
+++ b/app/controllers/concerns/controller_with_cross_project_access_check.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ControllerWithCrossProjectAccessCheck
extend ActiveSupport::Concern
diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb
index b26a76d2b62..b3777fd2b0f 100644
--- a/app/controllers/concerns/creates_commit.rb
+++ b/app/controllers/concerns/creates_commit.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module CreatesCommit
extend ActiveSupport::Concern
include Gitlab::Utils::StrongMemoize
@@ -65,7 +67,7 @@ module CreatesCommit
flash[:notice] = nil
else
target = different_project? ? "project" : "branch"
- flash[:notice] << " You can now submit a merge request to get this change into the original #{target}."
+ flash[:notice] = flash[:notice] + " You can now submit a merge request to get this change into the original #{target}."
end
end
end
@@ -99,6 +101,7 @@ module CreatesCommit
end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
+ # rubocop: disable CodeReuse/ActiveRecord
def merge_request_exists?
strong_memoize(:merge_request) do
MergeRequestsFinder.new(current_user, project_id: @project.id)
@@ -110,6 +113,7 @@ module CreatesCommit
target_branch: @start_branch)
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
# rubocop:enable Gitlab/ModuleWithInstanceVariables
def different_project?
diff --git a/app/controllers/concerns/cycle_analytics_params.rb b/app/controllers/concerns/cycle_analytics_params.rb
index 1ab107168c0..c1ef848e1e7 100644
--- a/app/controllers/concerns/cycle_analytics_params.rb
+++ b/app/controllers/concerns/cycle_analytics_params.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module CycleAnalyticsParams
extend ActiveSupport::Concern
diff --git a/app/controllers/concerns/diff_for_path.rb b/app/controllers/concerns/diff_for_path.rb
index d5388c4cd20..6be7a2a18a2 100644
--- a/app/controllers/concerns/diff_for_path.rb
+++ b/app/controllers/concerns/diff_for_path.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module DiffForPath
extend ActiveSupport::Concern
diff --git a/app/controllers/concerns/enforces_two_factor_authentication.rb b/app/controllers/concerns/enforces_two_factor_authentication.rb
index 997af4ab9e9..71bdef8ce03 100644
--- a/app/controllers/concerns/enforces_two_factor_authentication.rb
+++ b/app/controllers/concerns/enforces_two_factor_authentication.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# == EnforcesTwoFactorAuthentication
#
# Controller concern to enforce two-factor authentication requirements
@@ -24,6 +26,7 @@ module EnforcesTwoFactorAuthentication
current_user.try(:require_two_factor_authentication_from_group?)
end
+ # rubocop: disable CodeReuse/ActiveRecord
def two_factor_authentication_reason(global: -> {}, group: -> {})
if two_factor_authentication_required?
if Gitlab::CurrentSettings.require_two_factor_authentication?
@@ -34,6 +37,7 @@ module EnforcesTwoFactorAuthentication
end
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def two_factor_grace_period
periods = [Gitlab::CurrentSettings.two_factor_grace_period]
diff --git a/app/controllers/concerns/group_tree.rb b/app/controllers/concerns/group_tree.rb
index 6ec6897e707..4f56346832c 100644
--- a/app/controllers/concerns/group_tree.rb
+++ b/app/controllers/concerns/group_tree.rb
@@ -1,5 +1,8 @@
+# frozen_string_literal: true
+
module GroupTree
# rubocop:disable Gitlab/ModuleWithInstanceVariables
+ # rubocop: disable CodeReuse/ActiveRecord
def render_group_tree(groups)
groups = groups.sort_by_attribute(@sort = params[:sort])
@@ -23,7 +26,9 @@ module GroupTree
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def filtered_groups_with_ancestors(groups)
filtered_groups = groups.search(params[:filter]).page(params[:page])
@@ -40,4 +45,5 @@ module GroupTree
filtered_groups
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/controllers/concerns/hooks_execution.rb b/app/controllers/concerns/hooks_execution.rb
index a22e46b4860..e8add1f4055 100644
--- a/app/controllers/concerns/hooks_execution.rb
+++ b/app/controllers/concerns/hooks_execution.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module HooksExecution
extend ActiveSupport::Concern
diff --git a/app/controllers/concerns/internal_redirect.rb b/app/controllers/concerns/internal_redirect.rb
index 10b9852e329..6785e6972d0 100644
--- a/app/controllers/concerns/internal_redirect.rb
+++ b/app/controllers/concerns/internal_redirect.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module InternalRedirect
extend ActiveSupport::Concern
@@ -36,4 +38,10 @@ module InternalRedirect
path_with_query = [uri.path, uri.query].compact.join('?')
[path_with_query, uri.fragment].compact.join("#")
end
+
+ def referer_path(request)
+ return unless request.referer.presence
+
+ URI(request.referer).path
+ end
end
diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb
index 37e03d70b6f..07e01e903ea 100644
--- a/app/controllers/concerns/issuable_actions.rb
+++ b/app/controllers/concerns/issuable_actions.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module IssuableActions
extend ActiveSupport::Concern
@@ -89,12 +91,14 @@ module IssuableActions
render json: { notice: "#{quantity} #{resource_name.pluralize(quantity)} updated" }
end
+ # rubocop: disable CodeReuse/ActiveRecord
def discussions
notes = issuable.discussion_notes
.inc_relations_for_view
.includes(:noteable)
.fresh
+ notes = ResourceEvents::MergeIntoNotesService.new(issuable, current_user).execute(notes)
notes = prepare_notes_for_rendering(notes)
notes = notes.reject { |n| n.cross_reference_not_visible_for?(current_user) }
@@ -102,6 +106,7 @@ module IssuableActions
render json: discussion_serializer.represent(discussions, context: self)
end
+ # rubocop: enable CodeReuse/ActiveRecord
private
diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb
index a2c96f5d635..5217b4be928 100644
--- a/app/controllers/concerns/issuable_collections.rb
+++ b/app/controllers/concerns/issuable_collections.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module IssuableCollections
extend ActiveSupport::Concern
include CookiesHelper
@@ -48,9 +50,11 @@ module IssuableCollections
false
end
+ # rubocop: disable CodeReuse/ActiveRecord
def issuables_collection
finder.execute.preload(preload_for_collection)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def redirect_out_of_range(total_pages)
return false if total_pages.nil? || total_pages.zero?
@@ -81,6 +85,7 @@ module IssuableCollections
end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
+ # rubocop: disable CodeReuse/ActiveRecord
def filter_params
set_sort_order_from_cookie
set_default_state
@@ -101,6 +106,7 @@ module IssuableCollections
@filter_params.permit(finder_type.valid_params)
end
+ # rubocop: enable CodeReuse/ActiveRecord
# rubocop:enable Gitlab/ModuleWithInstanceVariables
def set_default_state
diff --git a/app/controllers/concerns/issues_action.rb b/app/controllers/concerns/issues_action.rb
index 9d58656773d..a75590457d6 100644
--- a/app/controllers/concerns/issues_action.rb
+++ b/app/controllers/concerns/issues_action.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module IssuesAction
extend ActiveSupport::Concern
include IssuableCollections
diff --git a/app/controllers/concerns/issues_calendar.rb b/app/controllers/concerns/issues_calendar.rb
index 671a204621d..1fdfde4c869 100644
--- a/app/controllers/concerns/issues_calendar.rb
+++ b/app/controllers/concerns/issues_calendar.rb
@@ -1,7 +1,10 @@
+# frozen_string_literal: true
+
module IssuesCalendar
extend ActiveSupport::Concern
# rubocop:disable Gitlab/ModuleWithInstanceVariables
+ # rubocop: disable CodeReuse/ActiveRecord
def render_issues_calendar(issuables)
@issues = issuables
.non_archived
@@ -20,5 +23,6 @@ module IssuesCalendar
end
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
# rubocop:enable Gitlab/ModuleWithInstanceVariables
end
diff --git a/app/controllers/concerns/lfs_request.rb b/app/controllers/concerns/lfs_request.rb
index 4584ff782a3..9576eb14fdd 100644
--- a/app/controllers/concerns/lfs_request.rb
+++ b/app/controllers/concerns/lfs_request.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# This concern assumes:
# - a `#project` accessor
# - a `#user` accessor
diff --git a/app/controllers/concerns/members_presentation.rb b/app/controllers/concerns/members_presentation.rb
index 215e0bdf3cb..c6c3598a976 100644
--- a/app/controllers/concerns/members_presentation.rb
+++ b/app/controllers/concerns/members_presentation.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module MembersPresentation
extend ActiveSupport::Concern
@@ -10,10 +12,12 @@ module MembersPresentation
).fabricate!
end
+ # rubocop: disable CodeReuse/ActiveRecord
def preload_associations(members)
ActiveRecord::Associations::Preloader.new.preload(members, :user)
ActiveRecord::Associations::Preloader.new.preload(members, :source)
ActiveRecord::Associations::Preloader.new.preload(members.map(&:user), :status)
ActiveRecord::Associations::Preloader.new.preload(members.map(&:user), :u2f_registrations)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/controllers/concerns/membership_actions.rb b/app/controllers/concerns/membership_actions.rb
index 409e6d4c4d2..ca713192c9e 100644
--- a/app/controllers/concerns/membership_actions.rb
+++ b/app/controllers/concerns/membership_actions.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module MembershipActions
include MembersPresentation
extend ActiveSupport::Concern
@@ -57,6 +59,7 @@ module MembershipActions
redirect_to members_page_url
end
+ # rubocop: disable CodeReuse/ActiveRecord
def leave
member = membershipable.members_and_requesters.find_by!(user_id: current_user.id)
Members::DestroyService.new(current_user).execute(member)
@@ -77,6 +80,7 @@ module MembershipActions
format.json { render json: { notice: notice } }
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def resend_invite
member = membershipable.members.find(params[:id])
diff --git a/app/controllers/concerns/merge_requests_action.rb b/app/controllers/concerns/merge_requests_action.rb
index b70db99b157..285f2c3a8a0 100644
--- a/app/controllers/concerns/merge_requests_action.rb
+++ b/app/controllers/concerns/merge_requests_action.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module MergeRequestsAction
extend ActiveSupport::Concern
include IssuableCollections
diff --git a/app/controllers/concerns/milestone_actions.rb b/app/controllers/concerns/milestone_actions.rb
index d92cf8b4894..eccbe35577b 100644
--- a/app/controllers/concerns/milestone_actions.rb
+++ b/app/controllers/concerns/milestone_actions.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module MilestoneActions
extend ActiveSupport::Concern
diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb
index 5127db3f5fb..3a45d6205ab 100644
--- a/app/controllers/concerns/notes_actions.rb
+++ b/app/controllers/concerns/notes_actions.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module NotesActions
include RendersNotes
include Gitlab::Utils::StrongMemoize
@@ -18,6 +20,7 @@ module NotesActions
notes = notes_finder.execute
.inc_relations_for_view
+ notes = ResourceEvents::MergeIntoNotesService.new(noteable, current_user, last_fetched_at: current_fetched_at).execute(notes)
notes = prepare_notes_for_rendering(notes)
notes = notes.reject { |n| n.cross_reference_not_visible_for?(current_user) }
@@ -40,12 +43,26 @@ module NotesActions
@note = Notes::CreateService.new(note_project, current_user, create_params).execute
- if @note.is_a?(Note)
- prepare_notes_for_rendering([@note], noteable)
- end
-
respond_to do |format|
- format.json { render json: note_json(@note) }
+ format.json do
+ json = {
+ commands_changes: @note.commands_changes
+ }
+
+ if @note.persisted? && return_discussion?
+ json[:valid] = true
+
+ discussion = @note.discussion
+ prepare_notes_for_rendering(discussion.notes)
+ json[:discussion] = discussion_serializer.represent(discussion, context: self)
+ else
+ prepare_notes_for_rendering([@note])
+
+ json.merge!(note_json(@note))
+ end
+
+ render json: json
+ end
format.html { redirect_back_or_default }
end
end
@@ -54,10 +71,7 @@ module NotesActions
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def update
@note = Notes::UpdateService.new(project, current_user, note_params).execute(note)
-
- if @note.is_a?(Note)
- prepare_notes_for_rendering([@note])
- end
+ prepare_notes_for_rendering([@note])
respond_to do |format|
format.json { render json: note_json(@note) }
@@ -88,14 +102,17 @@ module NotesActions
end
def note_json(note)
- attrs = {
- commands_changes: note.commands_changes
- }
+ attrs = {}
if note.persisted?
attrs[:valid] = true
- if use_note_serializer?
+ if return_discussion?
+ discussion = note.discussion
+ prepare_notes_for_rendering(discussion.notes)
+
+ attrs[:discussion] = discussion_serializer.represent(discussion, context: self)
+ elsif use_note_serializer?
attrs.merge!(note_serializer.represent(note))
else
attrs.merge!(
@@ -215,6 +232,10 @@ module NotesActions
ProjectNoteSerializer.new(project: project, noteable: noteable, current_user: current_user)
end
+ def discussion_serializer
+ DiscussionSerializer.new(project: project, noteable: noteable, current_user: current_user, note_entity: ProjectNoteEntity)
+ end
+
def note_project
strong_memoize(:note_project) do
next nil unless project
@@ -234,6 +255,10 @@ module NotesActions
end
end
+ def return_discussion?
+ Gitlab::Utils.to_boolean(params[:return_discussion])
+ end
+
def use_note_serializer?
return false if params['html']
diff --git a/app/controllers/concerns/oauth_applications.rb b/app/controllers/concerns/oauth_applications.rb
index f0a68f23566..d97e22df472 100644
--- a/app/controllers/concerns/oauth_applications.rb
+++ b/app/controllers/concerns/oauth_applications.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module OauthApplications
extend ActiveSupport::Concern
diff --git a/app/controllers/concerns/params_backward_compatibility.rb b/app/controllers/concerns/params_backward_compatibility.rb
index b0e3d9c7b34..c972d6e3161 100644
--- a/app/controllers/concerns/params_backward_compatibility.rb
+++ b/app/controllers/concerns/params_backward_compatibility.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ParamsBackwardCompatibility
private
diff --git a/app/controllers/concerns/preview_markdown.rb b/app/controllers/concerns/preview_markdown.rb
index 99123fcb3b0..c61b9fabe9e 100644
--- a/app/controllers/concerns/preview_markdown.rb
+++ b/app/controllers/concerns/preview_markdown.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module PreviewMarkdown
extend ActiveSupport::Concern
diff --git a/app/controllers/concerns/renders_blob.rb b/app/controllers/concerns/renders_blob.rb
index ba7adcfea86..b8026c7a01d 100644
--- a/app/controllers/concerns/renders_blob.rb
+++ b/app/controllers/concerns/renders_blob.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module RendersBlob
extend ActiveSupport::Concern
diff --git a/app/controllers/concerns/renders_commits.rb b/app/controllers/concerns/renders_commits.rb
index b1c9b1e532f..f48e0586211 100644
--- a/app/controllers/concerns/renders_commits.rb
+++ b/app/controllers/concerns/renders_commits.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module RendersCommits
def limited_commits(commits)
if commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
diff --git a/app/controllers/concerns/renders_member_access.rb b/app/controllers/concerns/renders_member_access.rb
index d640378c24d..955ac1a1bc8 100644
--- a/app/controllers/concerns/renders_member_access.rb
+++ b/app/controllers/concerns/renders_member_access.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module RendersMemberAccess
def prepare_groups_for_rendering(groups)
preload_max_member_access_for_collection(Group, groups)
@@ -13,6 +15,7 @@ module RendersMemberAccess
private
+ # rubocop: disable CodeReuse/ActiveRecord
def preload_max_member_access_for_collection(klass, collection)
return if !current_user || collection.blank?
@@ -20,4 +23,5 @@ module RendersMemberAccess
current_user.public_send(method_name, collection.ids) # rubocop:disable GitlabSecurity/PublicSend
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/controllers/concerns/renders_notes.rb b/app/controllers/concerns/renders_notes.rb
index cf04023080a..ce36da6b715 100644
--- a/app/controllers/concerns/renders_notes.rb
+++ b/app/controllers/concerns/renders_notes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module RendersNotes
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def prepare_notes_for_rendering(notes, noteable = nil)
@@ -20,9 +22,11 @@ module RendersNotes
project.team.max_member_access_for_user_ids(user_ids)
end
+ # rubocop: disable CodeReuse/ActiveRecord
def preload_noteable_for_regular_notes(notes)
ActiveRecord::Associations::Preloader.new.preload(notes.reject(&:for_commit?), :noteable)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def preload_first_time_contribution_for_authors(noteable, notes)
return unless noteable.is_a?(Issuable) && noteable.first_contribution?
@@ -30,7 +34,9 @@ module RendersNotes
notes.each {|n| n.specialize_for_first_contribution!(noteable)}
end
+ # rubocop: disable CodeReuse/ActiveRecord
def preload_author_status(notes)
ActiveRecord::Associations::Preloader.new.preload(notes, { author: :status })
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/controllers/concerns/repository_settings_redirect.rb b/app/controllers/concerns/repository_settings_redirect.rb
index f3db3cd563b..0f18735c29e 100644
--- a/app/controllers/concerns/repository_settings_redirect.rb
+++ b/app/controllers/concerns/repository_settings_redirect.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module RepositorySettingsRedirect
extend ActiveSupport::Concern
diff --git a/app/controllers/concerns/requires_whitelisted_monitoring_client.rb b/app/controllers/concerns/requires_whitelisted_monitoring_client.rb
index 88d1b34bb06..426f224d26b 100644
--- a/app/controllers/concerns/requires_whitelisted_monitoring_client.rb
+++ b/app/controllers/concerns/requires_whitelisted_monitoring_client.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module RequiresWhitelistedMonitoringClient
extend ActiveSupport::Concern
diff --git a/app/controllers/concerns/routable_actions.rb b/app/controllers/concerns/routable_actions.rb
index 0931bdf4c04..88939b002b2 100644
--- a/app/controllers/concerns/routable_actions.rb
+++ b/app/controllers/concerns/routable_actions.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module RoutableActions
extend ActiveSupport::Concern
diff --git a/app/controllers/concerns/send_file_upload.rb b/app/controllers/concerns/send_file_upload.rb
index 237c93daee8..0bb7b7efed0 100644
--- a/app/controllers/concerns/send_file_upload.rb
+++ b/app/controllers/concerns/send_file_upload.rb
@@ -1,7 +1,13 @@
+# frozen_string_literal: true
+
module SendFileUpload
def send_upload(file_upload, send_params: {}, redirect_params: {}, attachment: nil, disposition: 'attachment')
if attachment
- redirect_params[:query] = { "response-content-disposition" => "#{disposition};filename=#{attachment.inspect}" }
+ # Response-Content-Type will not override an existing Content-Type in
+ # Google Cloud Storage, so the metadata needs to be cleared on GCS for
+ # this to work. However, this override works with AWS.
+ redirect_params[:query] = { "response-content-disposition" => "#{disposition};filename=#{attachment.inspect}",
+ "response-content-type" => guess_content_type(attachment) }
# By default, Rails will send uploads with an extension of .js with a
# content-type of text/javascript, which will trigger Rails'
# cross-origin JavaScript protection.
@@ -18,4 +24,14 @@ module SendFileUpload
redirect_to file_upload.url(**redirect_params)
end
end
+
+ def guess_content_type(filename)
+ types = MIME::Types.type_for(filename)
+
+ if types.present?
+ types.first.content_type
+ else
+ "application/octet-stream"
+ end
+ end
end
diff --git a/app/controllers/concerns/service_params.rb b/app/controllers/concerns/service_params.rb
index c1acb50b76c..8bd93a349ef 100644
--- a/app/controllers/concerns/service_params.rb
+++ b/app/controllers/concerns/service_params.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ServiceParams
extend ActiveSupport::Concern
diff --git a/app/controllers/concerns/snippets_actions.rb b/app/controllers/concerns/snippets_actions.rb
index 120614739aa..8c22490700c 100644
--- a/app/controllers/concerns/snippets_actions.rb
+++ b/app/controllers/concerns/snippets_actions.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SnippetsActions
extend ActiveSupport::Concern
diff --git a/app/controllers/concerns/spammable_actions.rb b/app/controllers/concerns/spammable_actions.rb
index 922aa58a00f..c3a1b12af84 100644
--- a/app/controllers/concerns/spammable_actions.rb
+++ b/app/controllers/concerns/spammable_actions.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SpammableActions
extend ActiveSupport::Concern
diff --git a/app/controllers/concerns/todos_actions.rb b/app/controllers/concerns/todos_actions.rb
index c0acdb3498d..78b65f7961b 100644
--- a/app/controllers/concerns/todos_actions.rb
+++ b/app/controllers/concerns/todos_actions.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module TodosActions
extend ActiveSupport::Concern
diff --git a/app/controllers/concerns/toggle_award_emoji.rb b/app/controllers/concerns/toggle_award_emoji.rb
index ae0b815f85e..97b343f8b1a 100644
--- a/app/controllers/concerns/toggle_award_emoji.rb
+++ b/app/controllers/concerns/toggle_award_emoji.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ToggleAwardEmoji
extend ActiveSupport::Concern
diff --git a/app/controllers/concerns/toggle_subscription_action.rb b/app/controllers/concerns/toggle_subscription_action.rb
index 776583579e8..e613bfaeef2 100644
--- a/app/controllers/concerns/toggle_subscription_action.rb
+++ b/app/controllers/concerns/toggle_subscription_action.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ToggleSubscriptionAction
extend ActiveSupport::Concern
diff --git a/app/controllers/concerns/uploads_actions.rb b/app/controllers/concerns/uploads_actions.rb
index 434459a225a..7a1c7abfb8f 100644
--- a/app/controllers/concerns/uploads_actions.rb
+++ b/app/controllers/concerns/uploads_actions.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module UploadsActions
extend ActiveSupport::Concern
@@ -53,6 +55,8 @@ module UploadsActions
maximum_size: Gitlab::CurrentSettings.max_attachment_size.megabytes.to_i)
render json: authorized
+ rescue SocketError
+ render json: "Error uploading file", status: :internal_server_error
end
private
@@ -87,6 +91,7 @@ module UploadsActions
end
end
+ # rubocop: disable CodeReuse/ActiveRecord
def build_uploader_from_upload
return unless uploader = build_uploader
@@ -94,6 +99,7 @@ module UploadsActions
upload = Upload.find_by(uploader: uploader_class.to_s, path: upload_paths)
upload&.build_uploader
end
+ # rubocop: enable CodeReuse/ActiveRecord
def build_uploader_from_params
return unless uploader = build_uploader
diff --git a/app/controllers/concerns/with_performance_bar.rb b/app/controllers/concerns/with_performance_bar.rb
index 6a8b1a4de7b..c12839c7bbd 100644
--- a/app/controllers/concerns/with_performance_bar.rb
+++ b/app/controllers/concerns/with_performance_bar.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module WithPerformanceBar
extend ActiveSupport::Concern
diff --git a/app/controllers/concerns/workhorse_request.rb b/app/controllers/concerns/workhorse_request.rb
index 43c0f1b173c..028f10e866a 100644
--- a/app/controllers/concerns/workhorse_request.rb
+++ b/app/controllers/concerns/workhorse_request.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module WorkhorseRequest
extend ActiveSupport::Concern
diff --git a/app/controllers/confirmations_controller.rb b/app/controllers/confirmations_controller.rb
index 7bc46a6ccc0..2c4aab67448 100644
--- a/app/controllers/confirmations_controller.rb
+++ b/app/controllers/confirmations_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ConfirmationsController < Devise::ConfirmationsController
include AcceptsPendingInvitations
@@ -20,7 +22,7 @@ class ConfirmationsController < Devise::ConfirmationsController
after_sign_in(resource)
else
Gitlab::AppLogger.info("Email Confirmed: username=#{resource.username} email=#{resource.email} ip=#{request.remote_ip}")
- flash[:notice] += " Please sign in."
+ flash[:notice] = flash[:notice] + " Please sign in."
new_session_path(:user, anchor: 'login-pane')
end
end
diff --git a/app/controllers/dashboard/milestones_controller.rb b/app/controllers/dashboard/milestones_controller.rb
index 0469e7e1e1f..78f7f6d4e23 100644
--- a/app/controllers/dashboard/milestones_controller.rb
+++ b/app/controllers/dashboard/milestones_controller.rb
@@ -22,7 +22,7 @@ class Dashboard::MilestonesController < Dashboard::ApplicationController
private
def group_milestones
- groups = GroupsFinder.new(current_user, all_available: true).execute
+ groups = GroupsFinder.new(current_user, all_available: false).execute
DashboardGroupMilestone.build_collection(groups)
end
diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb
index ccfcbbdc776..e8f796f17ca 100644
--- a/app/controllers/dashboard/projects_controller.rb
+++ b/app/controllers/dashboard/projects_controller.rb
@@ -23,6 +23,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
end
end
+ # rubocop: disable CodeReuse/ActiveRecord
def starred
@projects = load_projects(params.merge(starred: true))
.includes(:forked_from_project, :tags)
@@ -38,6 +39,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
end
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
private
@@ -46,6 +48,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
@sort = params[:sort]
end
+ # rubocop: disable CodeReuse/ActiveRecord
def load_projects(finder_params)
projects = ProjectsFinder
.new(params: finder_params, current_user: current_user)
@@ -55,6 +58,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
prepare_projects_for_rendering(projects)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def load_events
projects = load_projects(params.merge(non_public: true))
diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb
index bd7111e28bc..231a23427f7 100644
--- a/app/controllers/dashboard/todos_controller.rb
+++ b/app/controllers/dashboard/todos_controller.rb
@@ -73,6 +73,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
params.permit(:action_id, :author_id, :project_id, :type, :sort, :state, :group_id)
end
+ # rubocop: disable CodeReuse/ActiveRecord
def redirect_out_of_range(todos)
total_pages =
if todo_params.except(:sort, :page).empty?
@@ -91,4 +92,5 @@ class Dashboard::TodosController < Dashboard::ApplicationController
out_of_range
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index ff133001b84..241753a505a 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class DashboardController < Dashboard::ApplicationController
include IssuesAction
include MergeRequestsAction
diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb
index c7273606a85..03a2ee07fea 100644
--- a/app/controllers/explore/projects_controller.rb
+++ b/app/controllers/explore/projects_controller.rb
@@ -34,6 +34,7 @@ class Explore::ProjectsController < Explore::ApplicationController
end
end
+ # rubocop: disable CodeReuse/ActiveRecord
def starred
@projects = load_projects.reorder('star_count DESC')
@@ -46,9 +47,11 @@ class Explore::ProjectsController < Explore::ApplicationController
end
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
private
+ # rubocop: disable CodeReuse/ActiveRecord
def load_projects
projects = ProjectsFinder.new(current_user: current_user, params: params)
.execute
@@ -58,4 +61,5 @@ class Explore::ProjectsController < Explore::ApplicationController
prepare_projects_for_rendering(projects)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/controllers/graphql_controller.rb b/app/controllers/graphql_controller.rb
index 0a1cf169aca..a1ec144410b 100644
--- a/app/controllers/graphql_controller.rb
+++ b/app/controllers/graphql_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class GraphqlController < ApplicationController
# Unauthenticated users have access to the API for public data
skip_before_action :authenticate_user!
diff --git a/app/controllers/groups/labels_controller.rb b/app/controllers/groups/labels_controller.rb
index 3e0076ac935..ae31313db64 100644
--- a/app/controllers/groups/labels_controller.rb
+++ b/app/controllers/groups/labels_controller.rb
@@ -2,7 +2,6 @@ class Groups::LabelsController < Groups::ApplicationController
include ToggleSubscriptionAction
before_action :label, only: [:edit, :update, :destroy]
- before_action :available_labels, only: [:index]
before_action :authorize_admin_labels!, only: [:new, :create, :edit, :update, :destroy]
before_action :save_previous_label_path, only: [:edit]
@@ -11,10 +10,10 @@ class Groups::LabelsController < Groups::ApplicationController
def index
respond_to do |format|
format.html do
- @labels = @available_labels.page(params[:page])
+ @labels = GroupLabelsFinder.new(@group, params.merge(sort: sort)).execute
end
format.json do
- render json: LabelSerializer.new.represent_appearance(@available_labels)
+ render json: LabelSerializer.new.represent_appearance(available_labels)
end
end
end
@@ -116,4 +115,8 @@ class Groups::LabelsController < Groups::ApplicationController
include_descendant_groups: params[:include_descendant_groups],
search: params[:search]).execute
end
+
+ def sort
+ @sort ||= params[:sort] || 'name_asc'
+ end
end
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index e57b9ff23a7..062c8c4e9e1 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class GroupsController < Groups::ApplicationController
include API::Helpers::RelatedResourcesHelpers
include IssuesAction
@@ -17,7 +19,7 @@ class GroupsController < Groups::ApplicationController
before_action :group_projects, only: [:projects, :activity, :issues, :merge_requests]
before_action :event_filter, only: [:activity]
- before_action :user_actions, only: [:show, :subgroups]
+ before_action :user_actions, only: [:show]
skip_cross_project_access_check :index, :new, :create, :edit, :update,
:destroy, :projects
@@ -53,11 +55,7 @@ class GroupsController < Groups::ApplicationController
def show
respond_to do |format|
- format.html do
- @has_children = GroupDescendantsFinder.new(current_user: current_user,
- parent_group: @group,
- params: params).has_children?
- end
+ format.html
format.atom do
load_events
@@ -101,6 +99,7 @@ class GroupsController < Groups::ApplicationController
redirect_to root_path, status: 302, alert: "Group '#{@group.name}' was scheduled for deletion."
end
+ # rubocop: disable CodeReuse/ActiveRecord
def transfer
parent_group = Group.find_by(id: params[:new_parent_group_id])
service = ::Groups::TransferService.new(@group, current_user)
@@ -113,9 +112,11 @@ class GroupsController < Groups::ApplicationController
render :edit
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
protected
+ # rubocop: disable CodeReuse/ActiveRecord
def authorize_create_group!
allowed = if params[:parent_id].present?
parent = Group.find_by(id: params[:parent_id])
@@ -126,6 +127,7 @@ class GroupsController < Groups::ApplicationController
render_404 unless allowed
end
+ # rubocop: enable CodeReuse/ActiveRecord
def determine_layout
if [:new, :create].include?(action_name.to_sym)
@@ -160,6 +162,7 @@ class GroupsController < Groups::ApplicationController
]
end
+ # rubocop: disable CodeReuse/ActiveRecord
def load_events
params[:sort] ||= 'latest_activity_desc'
@@ -179,6 +182,7 @@ class GroupsController < Groups::ApplicationController
.new(current_user)
.execute(@events, atom_request: request.format.atom?)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def user_actions
if current_user
diff --git a/app/controllers/health_check_controller.rb b/app/controllers/health_check_controller.rb
index c3d18991fd4..a2abed7ba4e 100644
--- a/app/controllers/health_check_controller.rb
+++ b/app/controllers/health_check_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class HealthCheckController < HealthCheck::HealthCheckController
include RequiresWhitelistedMonitoringClient
end
diff --git a/app/controllers/health_controller.rb b/app/controllers/health_controller.rb
index 3fedd5bfb29..ab4bc911e17 100644
--- a/app/controllers/health_controller.rb
+++ b/app/controllers/health_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class HealthController < ActionController::Base
protect_from_forgery with: :exception, except: :storage_check, prepend: true
include RequiresWhitelistedMonitoringClient
diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb
index a394521698c..e5a1fc9d6ff 100644
--- a/app/controllers/help_controller.rb
+++ b/app/controllers/help_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class HelpController < ApplicationController
skip_before_action :authenticate_user!
diff --git a/app/controllers/ide_controller.rb b/app/controllers/ide_controller.rb
index 96bb2237d90..eeeebe430a7 100644
--- a/app/controllers/ide_controller.rb
+++ b/app/controllers/ide_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class IdeController < ApplicationController
layout 'fullscreen'
diff --git a/app/controllers/import/base_controller.rb b/app/controllers/import/base_controller.rb
index 5766c6924cd..14b8c6e4137 100644
--- a/app/controllers/import/base_controller.rb
+++ b/app/controllers/import/base_controller.rb
@@ -1,16 +1,20 @@
class Import::BaseController < ApplicationController
private
+ # rubocop: disable CodeReuse/ActiveRecord
def find_already_added_projects(import_type)
current_user.created_projects.where(import_type: import_type).includes(:import_state)
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def find_jobs(import_type)
current_user.created_projects
.includes(:import_state)
.where(import_type: import_type)
.to_json(only: [:id], methods: [:import_status])
end
+ # rubocop: enable CodeReuse/ActiveRecord
def find_or_create_namespace(names, owner)
names = params[:target_namespace].presence || names
diff --git a/app/controllers/import/bitbucket_controller.rb b/app/controllers/import/bitbucket_controller.rb
index fa31933e778..f885e04b198 100644
--- a/app/controllers/import/bitbucket_controller.rb
+++ b/app/controllers/import/bitbucket_controller.rb
@@ -16,6 +16,7 @@ class Import::BitbucketController < Import::BaseController
redirect_to status_import_bitbucket_url
end
+ # rubocop: disable CodeReuse/ActiveRecord
def status
bitbucket_client = Bitbucket::Client.new(credentials)
repos = bitbucket_client.repos
@@ -27,6 +28,7 @@ class Import::BitbucketController < Import::BaseController
@repos.to_a.reject! { |repo| already_added_projects_names.include?(repo.full_name) }
end
+ # rubocop: enable CodeReuse/ActiveRecord
def jobs
render json: find_jobs('bitbucket')
diff --git a/app/controllers/import/bitbucket_server_controller.rb b/app/controllers/import/bitbucket_server_controller.rb
index 798daeca6c9..fdd1078cdf7 100644
--- a/app/controllers/import/bitbucket_server_controller.rb
+++ b/app/controllers/import/bitbucket_server_controller.rb
@@ -52,6 +52,7 @@ class Import::BitbucketServerController < Import::BaseController
redirect_to status_import_bitbucket_server_path
end
+ # rubocop: disable CodeReuse/ActiveRecord
def status
repos = bitbucket_client.repos
@@ -66,6 +67,7 @@ class Import::BitbucketServerController < Import::BaseController
clear_session_data
redirect_to new_import_bitbucket_server_path
end
+ # rubocop: enable CodeReuse/ActiveRecord
def jobs
render json: find_jobs('bitbucket_server')
diff --git a/app/controllers/import/fogbugz_controller.rb b/app/controllers/import/fogbugz_controller.rb
index 2d665e05ac3..df96d181153 100644
--- a/app/controllers/import/fogbugz_controller.rb
+++ b/app/controllers/import/fogbugz_controller.rb
@@ -39,6 +39,7 @@ class Import::FogbugzController < Import::BaseController
redirect_to status_import_fogbugz_path
end
+ # rubocop: disable CodeReuse/ActiveRecord
def status
unless client.valid?
return redirect_to new_import_fogbugz_path
@@ -51,6 +52,7 @@ class Import::FogbugzController < Import::BaseController
@repos.reject! { |repo| already_added_projects_names.include? repo.name }
end
+ # rubocop: enable CodeReuse/ActiveRecord
def jobs
render json: find_jobs('fogbugz')
diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb
index c9870332c0f..f8b43b4fde5 100644
--- a/app/controllers/import/github_controller.rb
+++ b/app/controllers/import/github_controller.rb
@@ -22,6 +22,7 @@ class Import::GithubController < Import::BaseController
redirect_to status_import_url
end
+ # rubocop: disable CodeReuse/ActiveRecord
def status
@repos = client.repos
@already_added_projects = find_already_added_projects(provider)
@@ -29,6 +30,7 @@ class Import::GithubController < Import::BaseController
@repos.reject! { |repo| already_added_projects_names.include? repo.full_name }
end
+ # rubocop: enable CodeReuse/ActiveRecord
def jobs
render json: find_jobs(provider)
@@ -104,9 +106,11 @@ class Import::GithubController < Import::BaseController
:github
end
+ # rubocop: disable CodeReuse/ActiveRecord
def logged_in_with_provider?
current_user.identities.exists?(provider: provider)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def provider_auth
if session[access_token_key].blank?
diff --git a/app/controllers/import/gitlab_controller.rb b/app/controllers/import/gitlab_controller.rb
index 53f70446d95..bdc402b0a77 100644
--- a/app/controllers/import/gitlab_controller.rb
+++ b/app/controllers/import/gitlab_controller.rb
@@ -12,6 +12,7 @@ class Import::GitlabController < Import::BaseController
redirect_to status_import_gitlab_url
end
+ # rubocop: disable CodeReuse/ActiveRecord
def status
@repos = client.projects(starting_page: 1, page_limit: MAX_PROJECT_PAGES, per_page: PER_PAGE_PROJECTS)
@@ -20,6 +21,7 @@ class Import::GitlabController < Import::BaseController
@repos = @repos.to_a.reject { |repo| already_added_projects_names.include? repo["path_with_namespace"] }
end
+ # rubocop: enable CodeReuse/ActiveRecord
def jobs
render json: find_jobs('gitlab')
diff --git a/app/controllers/import/gitlab_projects_controller.rb b/app/controllers/import/gitlab_projects_controller.rb
index f22df992fe9..f21c38a4c27 100644
--- a/app/controllers/import/gitlab_projects_controller.rb
+++ b/app/controllers/import/gitlab_projects_controller.rb
@@ -11,7 +11,7 @@ class Import::GitlabProjectsController < Import::BaseController
def create
unless file_is_valid?
- return redirect_back_or_default(options: { alert: "You need to upload a GitLab project export archive." })
+ return redirect_back_or_default(options: { alert: "You need to upload a GitLab project export archive (ending in .gz)." })
end
@project = ::Projects::GitlabProjectsImportService.new(current_user, project_params).execute
@@ -29,7 +29,11 @@ class Import::GitlabProjectsController < Import::BaseController
private
def file_is_valid?
- project_params[:file] && project_params[:file].respond_to?(:read)
+ return false unless project_params[:file] && project_params[:file].respond_to?(:read)
+
+ filename = project_params[:file].original_filename
+
+ ImportExportUploader::EXTENSION_WHITELIST.include?(File.extname(filename).delete('.'))
end
def verify_gitlab_project_import_enabled
diff --git a/app/controllers/import/google_code_controller.rb b/app/controllers/import/google_code_controller.rb
index 3bce27e810a..e9387d0ad14 100644
--- a/app/controllers/import/google_code_controller.rb
+++ b/app/controllers/import/google_code_controller.rb
@@ -65,6 +65,7 @@ class Import::GoogleCodeController < Import::BaseController
redirect_to status_import_google_code_path
end
+ # rubocop: disable CodeReuse/ActiveRecord
def status
unless client.valid?
return redirect_to new_import_google_code_path
@@ -78,6 +79,7 @@ class Import::GoogleCodeController < Import::BaseController
@repos.reject! { |repo| already_added_projects_names.include? repo.name }
end
+ # rubocop: enable CodeReuse/ActiveRecord
def jobs
render json: find_jobs('google_code')
diff --git a/app/controllers/import/manifest_controller.rb b/app/controllers/import/manifest_controller.rb
index e5a719fa0df..4ed9dca2475 100644
--- a/app/controllers/import/manifest_controller.rb
+++ b/app/controllers/import/manifest_controller.rb
@@ -6,6 +6,7 @@ class Import::ManifestController < Import::BaseController
def new
end
+ # rubocop: disable CodeReuse/ActiveRecord
def status
@already_added_projects = find_already_added_projects
already_added_import_urls = @already_added_projects.pluck(:import_url)
@@ -14,6 +15,7 @@ class Import::ManifestController < Import::BaseController
already_added_import_urls.include?(repository[:url])
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def upload
group = Group.find(params[:group_id])
@@ -64,9 +66,11 @@ class Import::ManifestController < Import::BaseController
end
end
+ # rubocop: disable CodeReuse/ActiveRecord
def group
@group ||= Group.find_by(id: session[:manifest_import_group_id])
end
+ # rubocop: enable CodeReuse/ActiveRecord
def repositories
@repositories ||= session[:manifest_import_repositories]
@@ -76,12 +80,14 @@ class Import::ManifestController < Import::BaseController
find_already_added_projects.to_json(only: [:id], methods: [:import_status])
end
+ # rubocop: disable CodeReuse/ActiveRecord
def find_already_added_projects
group.all_projects
.where(import_type: 'manifest')
.where(creator_id: current_user)
.includes(:import_state)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def verify_import_enabled
render_404 unless manifest_import_enabled?
diff --git a/app/controllers/instance_statistics/cohorts_controller.rb b/app/controllers/instance_statistics/cohorts_controller.rb
index 7eba0a5ecdd..4b4e39db2e1 100644
--- a/app/controllers/instance_statistics/cohorts_controller.rb
+++ b/app/controllers/instance_statistics/cohorts_controller.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class InstanceStatistics::CohortsController < InstanceStatistics::ApplicationController
+ before_action :authenticate_usage_ping_enabled_or_admin!
+
def index
if Gitlab::CurrentSettings.usage_ping_enabled
cohorts_results = Rails.cache.fetch('cohorts', expires_in: 1.day) do
@@ -10,4 +12,8 @@ class InstanceStatistics::CohortsController < InstanceStatistics::ApplicationCon
@cohorts = CohortsSerializer.new.represent(cohorts_results)
end
end
+
+ def authenticate_usage_ping_enabled_or_admin!
+ render_404 unless Gitlab::CurrentSettings.usage_ping_enabled || current_user.admin?
+ end
end
diff --git a/app/controllers/instance_statistics/conversational_development_index_controller.rb b/app/controllers/instance_statistics/conversational_development_index_controller.rb
index d6d2191849f..306c16d559c 100644
--- a/app/controllers/instance_statistics/conversational_development_index_controller.rb
+++ b/app/controllers/instance_statistics/conversational_development_index_controller.rb
@@ -1,7 +1,9 @@
# frozen_string_literal: true
class InstanceStatistics::ConversationalDevelopmentIndexController < InstanceStatistics::ApplicationController
+ # rubocop: disable CodeReuse/ActiveRecord
def index
@metric = ConversationalDevelopmentIndex::Metric.order(:created_at).last&.present
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb
index 025d8270b7c..315d1375e02 100644
--- a/app/controllers/invites_controller.rb
+++ b/app/controllers/invites_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class InvitesController < ApplicationController
before_action :member
skip_before_action :authenticate_user!, only: :decline
@@ -50,9 +52,9 @@ class InvitesController < ApplicationController
def authenticate_user!
return if current_user
- notice = "To accept this invitation, sign in"
- notice << " or create an account" if Gitlab::CurrentSettings.allow_signup?
- notice << "."
+ notice = ["To accept this invitation, sign in"]
+ notice << "or create an account" if Gitlab::CurrentSettings.allow_signup?
+ notice = notice.join(' ') + "."
store_location_for :user, request.fullpath
redirect_to new_user_session_path, notice: notice
diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb
index d172aee5436..f9008a5b67e 100644
--- a/app/controllers/jwt_controller.rb
+++ b/app/controllers/jwt_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class JwtController < ApplicationController
skip_before_action :authenticate_user!
skip_before_action :verify_authenticity_token
diff --git a/app/controllers/koding_controller.rb b/app/controllers/koding_controller.rb
index 745abf3c0f5..72aa9d4f17f 100644
--- a/app/controllers/koding_controller.rb
+++ b/app/controllers/koding_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class KodingController < ApplicationController
before_action :check_integration!
layout 'koding'
diff --git a/app/controllers/metrics_controller.rb b/app/controllers/metrics_controller.rb
index 0400ffcfee5..7353be478e1 100644
--- a/app/controllers/metrics_controller.rb
+++ b/app/controllers/metrics_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class MetricsController < ActionController::Base
include RequiresWhitelistedMonitoringClient
diff --git a/app/controllers/notification_settings_controller.rb b/app/controllers/notification_settings_controller.rb
index 461f26561f1..84dce74ace8 100644
--- a/app/controllers/notification_settings_controller.rb
+++ b/app/controllers/notification_settings_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class NotificationSettingsController < ApplicationController
before_action :authenticate_user!
diff --git a/app/controllers/oauth/applications_controller.rb b/app/controllers/oauth/applications_controller.rb
index a1fe02dc852..9e700f648f4 100644
--- a/app/controllers/oauth/applications_controller.rb
+++ b/app/controllers/oauth/applications_controller.rb
@@ -4,7 +4,7 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
include PageLayoutHelper
include OauthApplications
- before_action :verify_user_oauth_applications_enabled
+ before_action :verify_user_oauth_applications_enabled, except: :index
before_action :authenticate_user!
before_action :add_gon_variables
before_action :load_scopes, only: [:index, :create, :edit]
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index 1547d4b5972..30be50d4595 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
include AuthenticatesWithTwoFactor
include Devise::Controllers::Rememberable
@@ -135,14 +137,13 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def handle_signup_error
label = Gitlab::Auth::OAuth::Provider.label_for(oauth['provider'])
- message = "Signing in using your #{label} account without a pre-existing GitLab account is not allowed."
+ message = ["Signing in using your #{label} account without a pre-existing GitLab account is not allowed."]
if Gitlab::CurrentSettings.allow_signup?
- message << " Create a GitLab account first, and then connect it to your #{label} account."
+ message << "Create a GitLab account first, and then connect it to your #{label} account."
end
- flash[:notice] = message
-
+ flash[:notice] = message.join(' ')
redirect_to new_user_session_path
end
diff --git a/app/controllers/passwords_controller.rb b/app/controllers/passwords_controller.rb
index 331583c49e6..2912a22411e 100644
--- a/app/controllers/passwords_controller.rb
+++ b/app/controllers/passwords_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class PasswordsController < Devise::PasswordsController
skip_before_action :require_no_authentication, only: [:edit, :update]
@@ -5,6 +7,7 @@ class PasswordsController < Devise::PasswordsController
before_action :check_password_authentication_available, only: [:create]
before_action :throttle_reset, only: [:create]
+ # rubocop: disable CodeReuse/ActiveRecord
def edit
super
reset_password_token = Devise.token_generator.digest(
@@ -24,6 +27,7 @@ class PasswordsController < Devise::PasswordsController
end
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def update
super do |resource|
diff --git a/app/controllers/profiles/accounts_controller.rb b/app/controllers/profiles/accounts_controller.rb
index 7d1aa8d1ce0..fd9cb9fca3e 100644
--- a/app/controllers/profiles/accounts_controller.rb
+++ b/app/controllers/profiles/accounts_controller.rb
@@ -5,6 +5,7 @@ class Profiles::AccountsController < Profiles::ApplicationController
@user = current_user
end
+ # rubocop: disable CodeReuse/ActiveRecord
def unlink
provider = params[:provider]
identity = current_user.identities.find_by(provider: provider)
@@ -19,4 +20,5 @@ class Profiles::AccountsController < Profiles::ApplicationController
redirect_to profile_account_path
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/controllers/profiles/notifications_controller.rb b/app/controllers/profiles/notifications_controller.rb
index 8a38ba65d4c..00bd2040b9a 100644
--- a/app/controllers/profiles/notifications_controller.rb
+++ b/app/controllers/profiles/notifications_controller.rb
@@ -1,10 +1,12 @@
class Profiles::NotificationsController < Profiles::ApplicationController
+ # rubocop: disable CodeReuse/ActiveRecord
def show
@user = current_user
@group_notifications = current_user.notification_settings.for_groups.order(:id)
@project_notifications = current_user.notification_settings.for_projects.order(:id)
@global_notification_setting = current_user.global_notification_setting
end
+ # rubocop: enable CodeReuse/ActiveRecord
def update
result = Users::UpdateService.new(current_user, user_params.merge(user: current_user)).execute
diff --git a/app/controllers/profiles/personal_access_tokens_controller.rb b/app/controllers/profiles/personal_access_tokens_controller.rb
index 346eab4ba19..b357741e3fb 100644
--- a/app/controllers/profiles/personal_access_tokens_controller.rb
+++ b/app/controllers/profiles/personal_access_tokens_controller.rb
@@ -38,6 +38,7 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
params.require(:personal_access_token).permit(:name, :expires_at, scopes: [])
end
+ # rubocop: disable CodeReuse/ActiveRecord
def set_index_vars
@scopes = Gitlab::Auth.available_scopes(current_user)
@@ -46,4 +47,5 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
@new_personal_access_token = PersonalAccessToken.redis_getdel(current_user.id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index 6f50cbb4a36..15248d2d08f 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ProfilesController < Profiles::ApplicationController
include ActionView::Helpers::SanitizeHelper
@@ -44,11 +46,13 @@ class ProfilesController < Profiles::ApplicationController
redirect_to profile_personal_access_tokens_path
end
+ # rubocop: disable CodeReuse/ActiveRecord
def audit_log
@events = AuditEvent.where(entity_type: "User", entity_id: current_user.id)
.order("created_at DESC")
.page(params[:page])
end
+ # rubocop: enable CodeReuse/ActiveRecord
def update_username
result = Users::UpdateService.new(current_user, user: @user, username: username_param).execute
@@ -94,6 +98,7 @@ class ProfilesController < Profiles::ApplicationController
:location,
:name,
:public_email,
+ :commit_email,
:skype,
:twitter,
:username,
@@ -101,6 +106,7 @@ class ProfilesController < Profiles::ApplicationController
:organization,
:preferred_language,
:private_profile,
+ :include_private_contributions,
status: [:emoji, :message]
)
end
diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb
index 6484a713f8e..3e8ffa485dd 100644
--- a/app/controllers/projects/artifacts_controller.rb
+++ b/app/controllers/projects/artifacts_controller.rb
@@ -82,16 +82,20 @@ class Projects::ArtifactsController < Projects::ApplicationController
end
end
+ # rubocop: disable CodeReuse/ActiveRecord
def build_from_id
project.builds.find_by(id: params[:job_id]) if params[:job_id]
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def build_from_ref
return unless @ref_name
builds = project.latest_successful_builds_for(@ref_name)
builds.find_by(name: params[:job])
end
+ # rubocop: enable CodeReuse/ActiveRecord
def artifacts_file
@artifacts_file ||= build.artifacts_file
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 56dafa31332..bfe4e7f934f 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -177,6 +177,7 @@ class Projects::BlobController < Projects::ApplicationController
render_404
end
+ # rubocop: disable CodeReuse/ActiveRecord
def after_edit_path
from_merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).find_by(iid: params[:from_merge_request_iid])
if from_merge_request && @branch_name == @ref
@@ -186,6 +187,7 @@ class Projects::BlobController < Projects::ApplicationController
project_blob_path(@project, File.join(@branch_name, @path))
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def editor_variables
@branch_name = params[:branch_name]
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index d1dc9fe9600..d14795e787b 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -48,6 +48,7 @@ class Projects::BranchesController < Projects::ApplicationController
@branches = @repository.recent_branches
end
+ # rubocop: disable CodeReuse/ActiveRecord
def create
branch_name = sanitize(strip_tags(params[:branch_name]))
branch_name = Addressable::URI.unescape(branch_name)
@@ -88,6 +89,7 @@ class Projects::BranchesController < Projects::ApplicationController
end
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def destroy
@branch_name = Addressable::URI.unescape(params[:id])
diff --git a/app/controllers/projects/build_artifacts_controller.rb b/app/controllers/projects/build_artifacts_controller.rb
index b45e5d7ff43..9e99a84fac7 100644
--- a/app/controllers/projects/build_artifacts_controller.rb
+++ b/app/controllers/projects/build_artifacts_controller.rb
@@ -42,14 +42,18 @@ class Projects::BuildArtifactsController < Projects::ApplicationController
@job ||= job_from_id || job_from_ref
end
+ # rubocop: disable CodeReuse/ActiveRecord
def job_from_id
project.builds.find_by(id: params[:build_id]) if params[:build_id]
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def job_from_ref
return unless @ref_name
jobs = project.latest_successful_builds_for(@ref_name)
jobs.find_by(name: params[:job])
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/controllers/projects/clusters/applications_controller.rb b/app/controllers/projects/clusters/applications_controller.rb
index a5c82caa897..8c9df51981a 100644
--- a/app/controllers/projects/clusters/applications_controller.rb
+++ b/app/controllers/projects/clusters/applications_controller.rb
@@ -4,6 +4,7 @@ class Projects::Clusters::ApplicationsController < Projects::ApplicationControll
before_action :authorize_read_cluster!
before_action :authorize_create_cluster!, only: [:create]
+ # rubocop: disable CodeReuse/ActiveRecord
def create
application = @application_class.find_or_initialize_by(cluster: @cluster)
@@ -23,6 +24,7 @@ class Projects::Clusters::ApplicationsController < Projects::ApplicationControll
rescue StandardError
head :bad_request
end
+ # rubocop: enable CodeReuse/ActiveRecord
private
diff --git a/app/controllers/projects/clusters_controller.rb b/app/controllers/projects/clusters_controller.rb
index 358fe59618b..eb0fad6cbb2 100644
--- a/app/controllers/projects/clusters_controller.rb
+++ b/app/controllers/projects/clusters_controller.rb
@@ -141,7 +141,8 @@ class Projects::ClustersController < Projects::ApplicationController
:gcp_project_id,
:zone,
:num_nodes,
- :machine_type
+ :machine_type,
+ :legacy_abac
]).merge(
provider_type: :gcp,
platform_type: :kubernetes
@@ -157,7 +158,8 @@ class Projects::ClustersController < Projects::ApplicationController
:namespace,
:api_url,
:token,
- :ca_cert
+ :ca_cert,
+ :authorization_type
]).merge(
provider_type: :user,
platform_type: :kubernetes
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 53637780a07..81f375875b2 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -38,6 +38,7 @@ class Projects::CommitController < Projects::ApplicationController
render_diff_for_path(@commit.diffs(diff_options))
end
+ # rubocop: disable CodeReuse/ActiveRecord
def pipelines
@pipelines = @commit.pipelines.order(id: :desc)
@pipelines = @pipelines.where(ref: params[:ref]) if params[:ref]
@@ -58,6 +59,7 @@ class Projects::CommitController < Projects::ApplicationController
end
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def merge_requests
@merge_requests = @commit.merge_requests.map do |mr|
@@ -144,6 +146,7 @@ class Projects::CommitController < Projects::ApplicationController
@environment = EnvironmentsFinder.new(@project, current_user, commit: @commit).execute.last
end
+ # rubocop: disable CodeReuse/ActiveRecord
def define_note_vars
@noteable = @commit
@note = @project.build_commit_note(commit)
@@ -176,6 +179,7 @@ class Projects::CommitController < Projects::ApplicationController
@notes = (@grouped_diff_discussions.values.flatten + @discussions).flat_map(&:notes)
@notes = prepare_notes_for_rendering(@notes, @commit)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def assign_change_commit_vars
@start_branch = params[:start_branch]
diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb
index 5546bef850b..cd9c9aa30f1 100644
--- a/app/controllers/projects/commits_controller.rb
+++ b/app/controllers/projects/commits_controller.rb
@@ -15,6 +15,7 @@ class Projects::CommitsController < Projects::ApplicationController
redirect_to project_commits_path(@project, @project.default_branch)
end
+ # rubocop: disable CodeReuse/ActiveRecord
def show
@merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened
.find_by(source_project: @project, source_branch: @ref, target_branch: @repository.root_ref)
@@ -32,6 +33,7 @@ class Projects::CommitsController < Projects::ApplicationController
end
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def signatures
respond_to do |format|
diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb
index a1e12821caf..cca77903250 100644
--- a/app/controllers/projects/compare_controller.rb
+++ b/app/controllers/projects/compare_controller.rb
@@ -96,8 +96,10 @@ class Projects::CompareController < Projects::ApplicationController
@diff_notes_disabled = compare.present?
end
+ # rubocop: disable CodeReuse/ActiveRecord
def merge_request
@merge_request ||= MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened
.find_by(source_project: @project, source_branch: head_ref, target_branch: start_ref)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb
index 28fea322334..2555139cd2c 100644
--- a/app/controllers/projects/deploy_keys_controller.rb
+++ b/app/controllers/projects/deploy_keys_controller.rb
@@ -52,6 +52,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
end
end
+ # rubocop: disable CodeReuse/ActiveRecord
def disable
deploy_key_project = @project.deploy_keys_projects.find_by(deploy_key_id: params[:id])
return render_404 unless deploy_key_project
@@ -63,6 +64,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
format.json { head :ok }
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
protected
diff --git a/app/controllers/projects/deployments_controller.rb b/app/controllers/projects/deployments_controller.rb
index b68cdc39cb8..5a2da7274d1 100644
--- a/app/controllers/projects/deployments_controller.rb
+++ b/app/controllers/projects/deployments_controller.rb
@@ -2,6 +2,7 @@ class Projects::DeploymentsController < Projects::ApplicationController
before_action :authorize_read_environment!
before_action :authorize_read_deployment!
+ # rubocop: disable CodeReuse/ActiveRecord
def index
deployments = environment.deployments.reorder(created_at: :desc)
deployments = deployments.where('created_at > ?', params[:after].to_time) if params[:after]&.to_time
@@ -9,6 +10,7 @@ class Projects::DeploymentsController < Projects::ApplicationController
render json: { deployments: DeploymentSerializer.new(project: project)
.represent_concise(deployments) }
end
+ # rubocop: enable CodeReuse/ActiveRecord
def metrics
return render_404 unless deployment.has_metrics?
@@ -41,9 +43,11 @@ class Projects::DeploymentsController < Projects::ApplicationController
private
+ # rubocop: disable CodeReuse/ActiveRecord
def deployment
@deployment ||= environment.deployments.find_by(iid: params[:id])
end
+ # rubocop: enable CodeReuse/ActiveRecord
def environment
@environment ||= project.environments.find(params[:environment_id])
diff --git a/app/controllers/projects/discussions_controller.rb b/app/controllers/projects/discussions_controller.rb
index 78b9d53a780..efdddb24290 100644
--- a/app/controllers/projects/discussions_controller.rb
+++ b/app/controllers/projects/discussions_controller.rb
@@ -50,9 +50,11 @@ class Projects::DiscussionsController < Projects::ApplicationController
}
end
+ # rubocop: disable CodeReuse/ActiveRecord
def merge_request
@merge_request ||= MergeRequestsFinder.new(current_user, project_id: @project.id).find_by!(iid: params[:merge_request_id])
end
+ # rubocop: enable CodeReuse/ActiveRecord
def discussion
@discussion ||= @merge_request.find_discussion(params[:id]) || render_404
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index 68353e6a210..be22950286e 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -31,6 +31,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
end
end
+ # rubocop: disable CodeReuse/ActiveRecord
def folder
folder_environments = project.environments.where(environment_type: params[:id])
@environments = folder_environments.with_state(params[:scope] || :available)
@@ -51,10 +52,13 @@ class Projects::EnvironmentsController < Projects::ApplicationController
end
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def show
@deployments = environment.deployments.order(id: :desc).page(params[:page])
end
+ # rubocop: enable CodeReuse/ActiveRecord
def new
@environment = project.environments.new
diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb
index f43bba18d81..b709edc8f10 100644
--- a/app/controllers/projects/forks_controller.rb
+++ b/app/controllers/projects/forks_controller.rb
@@ -7,6 +7,7 @@ class Projects::ForksController < Projects::ApplicationController
before_action :authorize_download_code!
before_action :authenticate_user!, only: [:new, :create]
+ # rubocop: disable CodeReuse/ActiveRecord
def index
base_query = project.forks.includes(:creator)
@@ -27,12 +28,14 @@ class Projects::ForksController < Projects::ApplicationController
end
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def new
@namespaces = current_user.manageable_namespaces
@namespaces.delete(@project.namespace)
end
+ # rubocop: disable CodeReuse/ActiveRecord
def create
namespace = Namespace.find(params[:namespace_key])
@@ -55,6 +58,7 @@ class Projects::ForksController < Projects::ApplicationController
render :error
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def whitelist_query_limiting
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42335')
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index c3ac8e107fb..632e498e4ba 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -161,6 +161,7 @@ class Projects::IssuesController < Projects::ApplicationController
protected
+ # rubocop: disable CodeReuse/ActiveRecord
def issue
return @issue if defined?(@issue)
@@ -172,6 +173,7 @@ class Projects::IssuesController < Projects::ApplicationController
@issue
end
+ # rubocop: enable CodeReuse/ActiveRecord
alias_method :subscribable_resource, :issue
alias_method :issuable, :issue
alias_method :awardable, :issue
diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb
index e69faae754a..62b74e84c2c 100644
--- a/app/controllers/projects/jobs_controller.rb
+++ b/app/controllers/projects/jobs_controller.rb
@@ -11,6 +11,7 @@ class Projects::JobsController < Projects::ApplicationController
layout 'project'
+ # rubocop: disable CodeReuse/ActiveRecord
def index
@scope = params[:scope]
@all_builds = project.builds.relevant
@@ -33,6 +34,7 @@ class Projects::JobsController < Projects::ApplicationController
])
@builds = @builds.page(params[:page]).per(30).without_count
end
+ # rubocop: enable CodeReuse/ActiveRecord
def cancel_all
return access_denied! unless can?(current_user, :update_build, project)
@@ -44,6 +46,7 @@ class Projects::JobsController < Projects::ApplicationController
redirect_to project_jobs_path(project)
end
+ # rubocop: disable CodeReuse/ActiveRecord
def show
@pipeline = @build.pipeline
@builds = @pipeline.builds
@@ -61,6 +64,7 @@ class Projects::JobsController < Projects::ApplicationController
end
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def trace
build.trace.read do |stream|
diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb
index 8a2bce6e7b5..1fd4f0721a7 100644
--- a/app/controllers/projects/labels_controller.rb
+++ b/app/controllers/projects/labels_controller.rb
@@ -90,6 +90,7 @@ class Projects::LabelsController < Projects::ApplicationController
end
end
+ # rubocop: disable CodeReuse/ActiveRecord
def set_priorities
Label.transaction do
available_labels_ids = @available_labels.where(id: params[:label_ids]).pluck(:id)
@@ -105,6 +106,7 @@ class Projects::LabelsController < Projects::ApplicationController
format.json { render json: { message: 'success' } }
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def promote
promote_service = Labels::PromoteService.new(@project, @current_user)
@@ -163,7 +165,12 @@ class Projects::LabelsController < Projects::ApplicationController
LabelsFinder.new(current_user,
project_id: @project.id,
include_ancestor_groups: params[:include_ancestor_groups],
- search: params[:search]).execute
+ search: params[:search],
+ sort: sort).execute
+ end
+
+ def sort
+ @sort ||= params[:sort] || 'name_asc'
end
def authorize_admin_labels!
diff --git a/app/controllers/projects/lfs_api_controller.rb b/app/controllers/projects/lfs_api_controller.rb
index a01351ba292..6d6f88c1075 100644
--- a/app/controllers/projects/lfs_api_controller.rb
+++ b/app/controllers/projects/lfs_api_controller.rb
@@ -41,11 +41,13 @@ class Projects::LfsApiController < Projects::GitHttpClientController
params[:operation] == 'upload'
end
+ # rubocop: disable CodeReuse/ActiveRecord
def existing_oids
@existing_oids ||= begin
project.all_lfs_objects.where(oid: objects.map { |o| o['oid'].to_s }).pluck(:oid)
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def download_objects!
objects.each do |object|
diff --git a/app/controllers/projects/lfs_storage_controller.rb b/app/controllers/projects/lfs_storage_controller.rb
index dd7e673ec75..930d9a05c50 100644
--- a/app/controllers/projects/lfs_storage_controller.rb
+++ b/app/controllers/projects/lfs_storage_controller.rb
@@ -56,6 +56,7 @@ class Projects::LfsStorageController < Projects::GitHttpClientController
params[:size].to_i
end
+ # rubocop: disable CodeReuse/ActiveRecord
def store_file!(oid, size)
object = LfsObject.find_by(oid: oid, size: size)
unless object&.file&.exists?
@@ -66,6 +67,7 @@ class Projects::LfsStorageController < Projects::GitHttpClientController
link_to_project!(object)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def create_file!(oid, size)
uploaded_file = UploadedFile.from_params(
@@ -75,9 +77,11 @@ class Projects::LfsStorageController < Projects::GitHttpClientController
LfsObject.create!(oid: oid, size: size, file: uploaded_file)
end
+ # rubocop: disable CodeReuse/ActiveRecord
def link_to_project!(object)
if object && !object.projects.exists?(storage_project.id)
object.lfs_objects_projects.create!(project: storage_project)
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/controllers/projects/merge_requests/application_controller.rb b/app/controllers/projects/merge_requests/application_controller.rb
index fead81dd472..aa2008722ec 100644
--- a/app/controllers/projects/merge_requests/application_controller.rb
+++ b/app/controllers/projects/merge_requests/application_controller.rb
@@ -5,9 +5,11 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
private
+ # rubocop: disable CodeReuse/ActiveRecord
def merge_request
@issuable = @merge_request ||= @project.merge_requests.includes(author: :status).find_by!(iid: params[:id])
end
+ # rubocop: enable CodeReuse/ActiveRecord
def merge_request_params
params.require(:merge_request).permit(merge_request_params_attributes)
diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb
index 03d0290ac1d..2ccb3896857 100644
--- a/app/controllers/projects/merge_requests/creations_controller.rb
+++ b/app/controllers/projects/merge_requests/creations_controller.rb
@@ -109,6 +109,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
set_pipeline_variables
end
+ # rubocop: disable CodeReuse/ActiveRecord
def selected_target_project
if @project.id.to_s == params[:target_project_id] || !@project.forked?
@project
@@ -119,6 +120,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
@project.forked_from_project
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def whitelist_query_limiting
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42384')
diff --git a/app/controllers/projects/merge_requests/diffs_controller.rb b/app/controllers/projects/merge_requests/diffs_controller.rb
index 48e02581d54..666e65b6c5e 100644
--- a/app/controllers/projects/merge_requests/diffs_controller.rb
+++ b/app/controllers/projects/merge_requests/diffs_controller.rb
@@ -21,6 +21,8 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
def render_diffs
@environment = @merge_request.environments_for(current_user).last
+ @diffs.write_cache
+
render json: DiffsSerializer.new(current_user: current_user).represent(@diffs, additional_attributes)
end
@@ -32,13 +34,16 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
@diffs = @compare.diffs(diff_options)
end
+ # rubocop: disable CodeReuse/ActiveRecord
def commit
return nil unless commit_id = params[:commit_id].presence
return nil unless @merge_request.all_commits.exists?(sha: commit_id)
@commit ||= @project.commit(commit_id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def find_merge_request_diff_compare
@merge_request_diff =
if diff_id = params[:diff_id].presence
@@ -66,6 +71,7 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
@merge_request_diff
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def additional_attributes
{
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index d31b58972ca..75a85fafa3f 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -330,6 +330,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
@source_project = @merge_request.source_project
@target_project = @merge_request.target_project
@target_branches = @merge_request.target_project.repository.branch_names
+ @noteable = @merge_request
end
def finder_type
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index b9b3dcd5a85..e2c05171cd6 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -118,9 +118,11 @@ class Projects::MilestonesController < Projects::ApplicationController
end
end
+ # rubocop: disable CodeReuse/ActiveRecord
def milestone
@milestone ||= @project.milestones.find_by!(iid: params[:id])
end
+ # rubocop: enable CodeReuse/ActiveRecord
def authorize_admin_milestone!
return render_404 unless can?(current_user, :admin_milestone, @project)
diff --git a/app/controllers/projects/pages_controller.rb b/app/controllers/projects/pages_controller.rb
index ff49911d892..e1eba4f8327 100644
--- a/app/controllers/projects/pages_controller.rb
+++ b/app/controllers/projects/pages_controller.rb
@@ -5,9 +5,11 @@ class Projects::PagesController < Projects::ApplicationController
before_action :authorize_read_pages!, only: [:show]
before_action :authorize_update_pages!, except: [:show]
+ # rubocop: disable CodeReuse/ActiveRecord
def show
@domains = @project.pages_domains.order(:domain)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def destroy
project.remove_pages
diff --git a/app/controllers/projects/pages_domains_controller.rb b/app/controllers/projects/pages_domains_controller.rb
index 4856be61e88..c29b3c953a6 100644
--- a/app/controllers/projects/pages_domains_controller.rb
+++ b/app/controllers/projects/pages_domains_controller.rb
@@ -70,7 +70,9 @@ class Projects::PagesDomainsController < Projects::ApplicationController
params.require(:pages_domain).permit(:key, :certificate)
end
+ # rubocop: disable CodeReuse/ActiveRecord
def domain
@domain ||= @project.pages_domains.find_by!(domain: params[:id].to_s)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/controllers/projects/pipeline_schedules_controller.rb b/app/controllers/projects/pipeline_schedules_controller.rb
index aeda7b3edf5..d8adeffd0b2 100644
--- a/app/controllers/projects/pipeline_schedules_controller.rb
+++ b/app/controllers/projects/pipeline_schedules_controller.rb
@@ -8,12 +8,14 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
before_action :authorize_update_pipeline_schedule!, except: [:index, :new, :create, :play]
before_action :authorize_admin_pipeline_schedule!, only: [:destroy]
+ # rubocop: disable CodeReuse/ActiveRecord
def index
@scope = params[:scope]
@all_schedules = PipelineSchedulesFinder.new(@project).execute
@schedules = PipelineSchedulesFinder.new(@project).execute(scope: params[:scope])
.includes(:last_pipeline)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def new
@schedule = project.pipeline_schedules.new
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index b5db646bf57..5b2091d68f8 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -96,7 +96,7 @@ class Projects::PipelinesController < Projects::ApplicationController
render json: StageSerializer
.new(project: @project, current_user: @current_user)
- .represent(@stage, details: true)
+ .represent(@stage, details: true, retried: params[:retried])
end
# TODO: This endpoint is used by mini-pipeline-graph
@@ -159,6 +159,7 @@ class Projects::PipelinesController < Projects::ApplicationController
params.require(:pipeline).permit(:ref, variables_attributes: %i[key secret_value])
end
+ # rubocop: disable CodeReuse/ActiveRecord
def pipeline
@pipeline ||= project
.pipelines
@@ -166,6 +167,7 @@ class Projects::PipelinesController < Projects::ApplicationController
.find_by!(id: params[:id])
.present(current_user: current_user)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def whitelist_query_limiting
# Also see https://gitlab.com/gitlab-org/gitlab-ce/issues/42343
diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb
index cfa5e72af64..08d5e377941 100644
--- a/app/controllers/projects/project_members_controller.rb
+++ b/app/controllers/projects/project_members_controller.rb
@@ -6,6 +6,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
# Authorize
before_action :authorize_admin_project_member!, except: [:index, :leave, :request_access]
+ # rubocop: disable CodeReuse/ActiveRecord
def index
@sort = params[:sort].presence || sort_value_name
@group_links = @project.project_group_links
@@ -25,6 +26,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
@requesters = present_members(AccessRequestsFinder.new(@project).execute(current_user))
@project_member = @project.project_members.new
end
+ # rubocop: enable CodeReuse/ActiveRecord
def import
@projects = current_user.authorized_projects.order_id_desc
diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb
index 48a09e1ddb8..0fed7f6576c 100644
--- a/app/controllers/projects/refs_controller.rb
+++ b/app/controllers/projects/refs_controller.rb
@@ -36,54 +36,47 @@ class Projects::RefsController < Projects::ApplicationController
end
def logs_tree
- @offset = if params[:offset].present?
- params[:offset].to_i
- else
- 0
- end
+ summary = ::Gitlab::TreeSummary.new(
+ @commit,
+ @project,
+ path: @path,
+ offset: params[:offset],
+ limit: 25
+ )
- @limit = 25
-
- @path = params[:path]
-
- contents = []
- contents.push(*tree.trees)
- contents.push(*tree.blobs)
- contents.push(*tree.submodules)
-
- # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37433
- @logs = Gitlab::GitalyClient.allow_n_plus_1_calls do
- contents[@offset, @limit].to_a.map do |content|
- file = @path ? File.join(@path, content.name) : content.name
- last_commit = @repo.last_commit_for_path(@commit.id, file)
- commit_path = project_commit_path(@project, last_commit) if last_commit
- {
- file_name: content.name,
- commit: last_commit,
- type: content.type,
- commit_path: commit_path
- }
- end
- end
-
- offset = (@offset + @limit)
- if contents.size > offset
- @more_log_url = logs_file_project_ref_path(@project, @ref, @path || '', offset: offset)
- end
+ @logs, commits = summary.summarize
+ @more_log_url = more_url(summary.next_offset) if summary.more?
respond_to do |format|
format.html { render_404 }
format.json do
- response.headers["More-Logs-Url"] = @more_log_url
-
+ response.headers["More-Logs-Url"] = @more_log_url if summary.more?
render json: @logs
end
- format.js
+
+ # The commit titles must be rendered and redacted before being shown.
+ # Doing it here allows us to apply performance optimizations that avoid
+ # N+1 problems
+ format.js do
+ prerender_commit_full_titles!(commits)
+ end
end
end
private
+ def more_url(offset)
+ logs_file_project_ref_path(@project, @ref, @path, offset: offset)
+ end
+
+ def prerender_commit_full_titles!(commits)
+ # Preload commit authors as they are used in rendering
+ commits.each(&:lazy_author)
+
+ renderer = Banzai::ObjectRenderer.new(user: current_user, default_project: @project)
+ renderer.render(commits, :full_title)
+ end
+
def validate_ref_id
return not_found! if params[:id].present? && params[:id] !~ Gitlab::PathRegex.git_reference_regex
end
diff --git a/app/controllers/projects/registry/repositories_controller.rb b/app/controllers/projects/registry/repositories_controller.rb
index 32c0fc6d14a..ef0433795f4 100644
--- a/app/controllers/projects/registry/repositories_controller.rb
+++ b/app/controllers/projects/registry/repositories_controller.rb
@@ -18,14 +18,10 @@ module Projects
end
def destroy
- if image.destroy
- respond_to do |format|
- format.json { head :no_content }
- end
- else
- respond_to do |format|
- format.json { head :bad_request }
- end
+ DeleteContainerRepositoryWorker.perform_async(current_user.id, image.id)
+
+ respond_to do |format|
+ format.json { head :no_content }
end
end
@@ -41,10 +37,10 @@ module Projects
# Needed to maintain a backwards compatibility.
#
def ensure_root_container_repository!
- ContainerRegistry::Path.new(@project.full_path).tap do |path|
+ ::ContainerRegistry::Path.new(@project.full_path).tap do |path|
break if path.has_repository?
- ContainerRepository.build_from_path(path).tap do |repository|
+ ::ContainerRepository.build_from_path(path).tap do |repository|
repository.save! if repository.has_tags?
end
end
diff --git a/app/controllers/projects/releases_controller.rb b/app/controllers/projects/releases_controller.rb
index 19e09b3af6f..caf400ecd92 100644
--- a/app/controllers/projects/releases_controller.rb
+++ b/app/controllers/projects/releases_controller.rb
@@ -28,9 +28,11 @@ class Projects::ReleasesController < Projects::ApplicationController
@tag ||= @repository.find_tag(params[:tag_id])
end
+ # rubocop: disable CodeReuse/ActiveRecord
def release
@release ||= @project.releases.find_or_initialize_by(tag: @tag.name)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def release_params
params.require(:release).permit(:description)
diff --git a/app/controllers/projects/settings/repository_controller.rb b/app/controllers/projects/settings/repository_controller.rb
index 4697af4f26a..ccd481b4dbd 100644
--- a/app/controllers/projects/settings/repository_controller.rb
+++ b/app/controllers/projects/settings/repository_controller.rb
@@ -31,6 +31,7 @@ module Projects
render 'show'
end
+ # rubocop: disable CodeReuse/ActiveRecord
def define_protected_refs
@protected_branches = @project.protected_branches.order(:name).page(params[:page])
@protected_tags = @project.protected_tags.order(:name).page(params[:page])
@@ -42,6 +43,7 @@ module Projects
load_gon_index
end
+ # rubocop: enable CodeReuse/ActiveRecord
def remote_mirror
@remote_mirror = project.remote_mirrors.first_or_initialize
diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb
index 7f2c3ca38ad..74bba97987f 100644
--- a/app/controllers/projects/tags_controller.rb
+++ b/app/controllers/projects/tags_controller.rb
@@ -7,6 +7,7 @@ class Projects::TagsController < Projects::ApplicationController
before_action :authorize_push_code!, only: [:new, :create]
before_action :authorize_admin_project!, only: [:destroy]
+ # rubocop: disable CodeReuse/ActiveRecord
def index
params[:sort] = params[:sort].presence || sort_value_recently_updated
@@ -23,7 +24,9 @@ class Projects::TagsController < Projects::ApplicationController
format.atom { render layout: 'xml.atom' }
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def show
@tag = @repository.find_tag(params[:id])
@@ -32,6 +35,7 @@ class Projects::TagsController < Projects::ApplicationController
@release = @project.releases.find_or_initialize_by(tag: @tag.name)
@commit = @repository.commit(@tag.dereferenced_target)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def create
result = Tags::CreateService.new(@project, current_user)
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 0eaf9f94e37..7352c5e9bec 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ProjectsController < Projects::ApplicationController
include API::Helpers::RelatedResourcesHelpers
include IssuableCollections
@@ -25,12 +27,14 @@ class ProjectsController < Projects::ApplicationController
redirect_to(current_user ? root_path : explore_root_path)
end
+ # rubocop: disable CodeReuse/ActiveRecord
def new
namespace = Namespace.find_by(id: params[:namespace_id]) if params[:namespace_id]
return access_denied! if namespace && !can?(current_user, :create_projects, namespace)
@project = Project.new(namespace_id: namespace&.id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def edit
@badge_api_endpoint = expose_url(api_v4_projects_badges_path(id: @project.id))
@@ -75,6 +79,7 @@ class ProjectsController < Projects::ApplicationController
end
end
+ # rubocop: disable CodeReuse/ActiveRecord
def transfer
return access_denied! unless can?(current_user, :change_namespace, @project)
@@ -85,6 +90,7 @@ class ProjectsController < Projects::ApplicationController
flash[:alert] = @project.errors[:new_namespace].first
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def remove_fork
return access_denied! unless can?(current_user, :remove_fork_project, @project)
@@ -191,10 +197,8 @@ class ProjectsController < Projects::ApplicationController
end
def download_export
- if export_project_object_storage?
- send_upload(@project.import_export_upload.export_file)
- elsif export_project_path
- send_file export_project_path, disposition: 'attachment'
+ if @project.export_file_exists?
+ send_upload(@project.export_file, attachment: @project.export_file.filename)
else
redirect_to(
edit_project_path(@project, anchor: 'js-export-project'),
@@ -233,6 +237,7 @@ class ProjectsController < Projects::ApplicationController
}
end
+ # rubocop: disable CodeReuse/ActiveRecord
def refs
find_refs = params['find']
@@ -267,6 +272,7 @@ class ProjectsController < Projects::ApplicationController
render json: options.to_json
end
+ # rubocop: enable CodeReuse/ActiveRecord
# Render project landing depending of which features are available
# So if page is not availble in the list it renders the next page
@@ -305,6 +311,7 @@ class ProjectsController < Projects::ApplicationController
end
end
+ # rubocop: disable CodeReuse/ActiveRecord
def load_events
projects = Project.where(id: @project.id)
@@ -314,6 +321,7 @@ class ProjectsController < Projects::ApplicationController
Events::RenderService.new(current_user).execute(@events, atom_request: request.format.atom?)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def project_params
params.require(:project)
@@ -425,12 +433,4 @@ class ProjectsController < Projects::ApplicationController
def whitelist_query_limiting
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42440')
end
-
- def export_project_path
- @export_project_path ||= @project.export_project_path
- end
-
- def export_project_object_storage?
- @project.export_project_object_exists?
- end
end
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index e6d6965036e..8b8d87524a8 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class RegistrationsController < Devise::RegistrationsController
include Recaptcha::Verify
include AcceptsPendingInvitations
diff --git a/app/controllers/root_controller.rb b/app/controllers/root_controller.rb
index 651b82f04f4..ebf70f25bda 100644
--- a/app/controllers/root_controller.rb
+++ b/app/controllers/root_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# RootController
#
# This controller exists solely to handle requests to `root_url`. When a user is
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index 983f888b8ec..1b22907c10f 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class SearchController < ApplicationController
include ControllerWithCrossProjectAccessCheck
include SearchHelper
@@ -31,6 +33,7 @@ class SearchController < ApplicationController
check_single_commit_result
end
+ # rubocop: disable CodeReuse/ActiveRecord
def autocomplete
term = params[:term]
@@ -43,6 +46,7 @@ class SearchController < ApplicationController
render json: search_autocomplete_opts(term).to_json
end
+ # rubocop: enable CodeReuse/ActiveRecord
private
diff --git a/app/controllers/sent_notifications_controller.rb b/app/controllers/sent_notifications_controller.rb
index 93a71103a09..2b76921ebd8 100644
--- a/app/controllers/sent_notifications_controller.rb
+++ b/app/controllers/sent_notifications_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class SentNotificationsController < ApplicationController
skip_before_action :authenticate_user!
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index ab8e2e35b98..643eb75c83c 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class SessionsController < Devise::SessionsController
include InternalRedirect
include AuthenticatesWithTwoFactor
@@ -107,6 +109,7 @@ class SessionsController < Devise::SessionsController
# Handle an "initial setup" state, where there's only one user, it's an admin,
# and they require a password change.
+ # rubocop: disable CodeReuse/ActiveRecord
def check_initial_setup
return unless User.limit(2).count == 1 # Count as much 2 to know if we have exactly one
@@ -121,6 +124,7 @@ class SessionsController < Devise::SessionsController
redirect_to edit_user_password_path(reset_password_token: @token),
notice: "Please create a password for your new account."
end
+ # rubocop: enable CodeReuse/ActiveRecord
def user_params
params.require(:user).permit(:login, :password, :remember_me, :otp_attempt, :device_response)
diff --git a/app/controllers/snippets/notes_controller.rb b/app/controllers/snippets/notes_controller.rb
index 217da89a1fd..e992afc0026 100644
--- a/app/controllers/snippets/notes_controller.rb
+++ b/app/controllers/snippets/notes_controller.rb
@@ -17,9 +17,11 @@ class Snippets::NotesController < ApplicationController
nil
end
+ # rubocop: disable CodeReuse/ActiveRecord
def snippet
PersonalSnippet.find_by(id: params[:snippet_id])
end
+ # rubocop: enable CodeReuse/ActiveRecord
alias_method :noteable, :snippet
def note_params
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index dcf18c1f751..694c3a59e2b 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class SnippetsController < ApplicationController
include RendersNotes
include ToggleAwardEmoji
@@ -24,6 +26,7 @@ class SnippetsController < ApplicationController
layout 'snippets'
respond_to :html
+ # rubocop: disable CodeReuse/ActiveRecord
def index
if params[:username].present?
@user = User.find_by(username: params[:username])
@@ -38,6 +41,7 @@ class SnippetsController < ApplicationController
redirect_to(current_user ? dashboard_snippets_path : explore_snippets_path)
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def new
@snippet = PersonalSnippet.new
@@ -94,9 +98,11 @@ class SnippetsController < ApplicationController
protected
+ # rubocop: disable CodeReuse/ActiveRecord
def snippet
@snippet ||= PersonalSnippet.inc_relations_for_view.find_by(id: params[:id])
end
+ # rubocop: enable CodeReuse/ActiveRecord
alias_method :awardable, :snippet
alias_method :spammable, :snippet
diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb
index 3d227b0a955..fa5d84633b5 100644
--- a/app/controllers/uploads_controller.rb
+++ b/app/controllers/uploads_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class UploadsController < ApplicationController
include UploadsActions
diff --git a/app/controllers/user_callouts_controller.rb b/app/controllers/user_callouts_controller.rb
index 18cde4a7b1a..ebf1dd8ca02 100644
--- a/app/controllers/user_callouts_controller.rb
+++ b/app/controllers/user_callouts_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class UserCalloutsController < ApplicationController
def create
if ensure_callout.persisted?
@@ -13,9 +15,11 @@ class UserCalloutsController < ApplicationController
private
+ # rubocop: disable CodeReuse/ActiveRecord
def ensure_callout
current_user.callouts.find_or_create_by(feature_name: UserCallout.feature_names[feature_name])
end
+ # rubocop: enable CodeReuse/ActiveRecord
def feature_name
params.require(:feature_name)
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 2f65f4a7403..e509098d778 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class UsersController < ApplicationController
include RoutableActions
include RendersMemberAccess
diff --git a/app/finders/access_requests_finder.rb b/app/finders/access_requests_finder.rb
index b6ee49df99b..2cc8a978877 100644
--- a/app/finders/access_requests_finder.rb
+++ b/app/finders/access_requests_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class AccessRequestsFinder
attr_accessor :source
diff --git a/app/finders/admin/projects_finder.rb b/app/finders/admin/projects_finder.rb
index 543bf1a1415..e2b9b0b44c1 100644
--- a/app/finders/admin/projects_finder.rb
+++ b/app/finders/admin/projects_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Admin::ProjectsFinder
attr_reader :params, :current_user
@@ -6,6 +8,7 @@ class Admin::ProjectsFinder
@current_user = current_user
end
+ # rubocop: disable CodeReuse/ActiveRecord
def execute
items = Project.without_deleted.with_statistics.with_route
items = by_namespace_id(items)
@@ -19,6 +22,7 @@ class Admin::ProjectsFinder
items = items.includes(namespace: [:owner, :route])
sort(items).page(params[:page])
end
+ # rubocop: enable CodeReuse/ActiveRecord
private
@@ -26,9 +30,11 @@ class Admin::ProjectsFinder
params[:namespace_id].present? ? items.in_namespace(params[:namespace_id]) : items
end
+ # rubocop: disable CodeReuse/ActiveRecord
def by_visibilty_level(items)
params[:visibility_level].present? ? items.where(visibility_level: params[:visibility_level]) : items
end
+ # rubocop: enable CodeReuse/ActiveRecord
def by_with_push(items)
params[:with_push].present? ? items.with_push : items
@@ -38,9 +44,11 @@ class Admin::ProjectsFinder
params[:abandoned].present? ? items.abandoned : items
end
+ # rubocop: disable CodeReuse/ActiveRecord
def by_last_repository_check_failed(items)
params[:last_repository_check_failed].present? ? items.where(last_repository_check_failed: true) : items
end
+ # rubocop: enable CodeReuse/ActiveRecord
def by_archived(items)
if params[:archived] == 'only'
diff --git a/app/finders/admin/runners_finder.rb b/app/finders/admin/runners_finder.rb
new file mode 100644
index 00000000000..3c2d7ee7d76
--- /dev/null
+++ b/app/finders/admin/runners_finder.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+class Admin::RunnersFinder < UnionFinder
+ NUMBER_OF_RUNNERS_PER_PAGE = 30
+
+ def initialize(params:)
+ @params = params
+ end
+
+ def execute
+ search!
+ filter_by_status!
+ sort!
+ paginate!
+
+ @runners
+ end
+
+ def sort_key
+ if @params[:sort] == 'contacted_asc'
+ 'contacted_asc'
+ else
+ 'created_date'
+ end
+ end
+
+ private
+
+ def search!
+ @runners =
+ if @params[:search].present?
+ Ci::Runner.search(@params[:search])
+ else
+ Ci::Runner.all
+ end
+ end
+
+ def filter_by_status!
+ status = @params[:status_status]
+ if status.present? && Ci::Runner::AVAILABLE_STATUSES.include?(status)
+ @runners = @runners.public_send(status) # rubocop:disable GitlabSecurity/PublicSend
+ end
+ end
+
+ def sort!
+ @runners = @runners.order_by(sort_key)
+ end
+
+ def paginate!
+ @runners = @runners.page(@params[:page]).per(NUMBER_OF_RUNNERS_PER_PAGE)
+ end
+end
diff --git a/app/finders/autocomplete/users_finder.rb b/app/finders/autocomplete/users_finder.rb
index b2557469079..e2283f3266e 100644
--- a/app/finders/autocomplete/users_finder.rb
+++ b/app/finders/autocomplete/users_finder.rb
@@ -44,6 +44,7 @@ module Autocomplete
# Returns the users based on the input parameters, as an Array.
#
# This method is separate so it is easier to extend in EE.
+ # rubocop: disable CodeReuse/ActiveRecord
def limited_users
# When changing the order of these method calls, make sure that
# reorder_by_name() is called _before_ optionally_search(), otherwise
@@ -61,6 +62,7 @@ module Autocomplete
.limit(LIMIT)
.to_a
end
+ # rubocop: enable CodeReuse/ActiveRecord
def prepend_current_user?
filter_by_current_user.present? && current_user
@@ -70,6 +72,7 @@ module Autocomplete
author_id.present? && current_user
end
+ # rubocop: disable CodeReuse/ActiveRecord
def find_users
if project
project.authorized_users.union_with_user(author_id)
@@ -81,5 +84,6 @@ module Autocomplete
User.none
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/app/finders/branches_finder.rb b/app/finders/branches_finder.rb
index 8bb1366867c..970efa79dfb 100644
--- a/app/finders/branches_finder.rb
+++ b/app/finders/branches_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class BranchesFinder
def initialize(repository, params = {})
@repository = repository
diff --git a/app/finders/clusters_finder.rb b/app/finders/clusters_finder.rb
index c13f98257bf..b40d6c41b71 100644
--- a/app/finders/clusters_finder.rb
+++ b/app/finders/clusters_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ClustersFinder
def initialize(project, user, scope)
@project = project
diff --git a/app/finders/concerns/created_at_filter.rb b/app/finders/concerns/created_at_filter.rb
index ac9ac77732c..6b5863a5c53 100644
--- a/app/finders/concerns/created_at_filter.rb
+++ b/app/finders/concerns/created_at_filter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module CreatedAtFilter
def by_created_at(items)
items = items.created_before(params[:created_before]) if params[:created_before].present?
diff --git a/app/finders/concerns/custom_attributes_filter.rb b/app/finders/concerns/custom_attributes_filter.rb
index 5bbf9ca242d..825c3a6b5b7 100644
--- a/app/finders/concerns/custom_attributes_filter.rb
+++ b/app/finders/concerns/custom_attributes_filter.rb
@@ -1,4 +1,7 @@
+# frozen_string_literal: true
+
module CustomAttributesFilter
+ # rubocop: disable CodeReuse/ActiveRecord
def by_custom_attributes(items)
return items unless params[:custom_attributes].is_a?(Hash)
return items unless Ability.allowed?(current_user, :read_custom_attribute)
@@ -17,4 +20,5 @@ module CustomAttributesFilter
scope.where('EXISTS (?)', custom_attributes.where(key: key, value: value))
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/finders/concerns/finder_methods.rb b/app/finders/concerns/finder_methods.rb
index 2e905fa5750..5290313585f 100644
--- a/app/finders/concerns/finder_methods.rb
+++ b/app/finders/concerns/finder_methods.rb
@@ -1,11 +1,17 @@
+# frozen_string_literal: true
+
module FinderMethods
+ # rubocop: disable CodeReuse/ActiveRecord
def find_by!(*args)
raise_not_found_unless_authorized execute.find_by!(*args)
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def find_by(*args)
if_authorized execute.find_by(*args)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def find(*args)
raise_not_found_unless_authorized model.find(*args)
diff --git a/app/finders/concerns/finder_with_cross_project_access.rb b/app/finders/concerns/finder_with_cross_project_access.rb
index 92bf98d7cd2..e038636f0c4 100644
--- a/app/finders/concerns/finder_with_cross_project_access.rb
+++ b/app/finders/concerns/finder_with_cross_project_access.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Module to prepend into finders to specify wether or not the finder requires
# cross project access
#
@@ -14,6 +16,7 @@ module FinderWithCrossProjectAccess
end
override :execute
+ # rubocop: disable CodeReuse/ActiveRecord
def execute(*args)
check = Gitlab::CrossProjectAccess.find_check(self)
original = super
@@ -27,6 +30,7 @@ module FinderWithCrossProjectAccess
original
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
# We can skip the cross project check for finding indivitual records.
# this would be handled by the `can?(:read_*, result)` call in `FinderMethods`
diff --git a/app/finders/contributed_projects_finder.rb b/app/finders/contributed_projects_finder.rb
index a685719555c..c1ef9dfefa7 100644
--- a/app/finders/contributed_projects_finder.rb
+++ b/app/finders/contributed_projects_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ContributedProjectsFinder < UnionFinder
def initialize(user)
@user = user
@@ -10,11 +12,13 @@ class ContributedProjectsFinder < UnionFinder
# visible by this user.
#
# Returns an ActiveRecord::Relation.
+ # rubocop: disable CodeReuse/ActiveRecord
def execute(current_user = nil)
segments = all_projects(current_user)
find_union(segments, Project).includes(:namespace).order_id_desc
end
+ # rubocop: enable CodeReuse/ActiveRecord
private
diff --git a/app/finders/environments_finder.rb b/app/finders/environments_finder.rb
index a59f8c1efa3..419be46fafe 100644
--- a/app/finders/environments_finder.rb
+++ b/app/finders/environments_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class EnvironmentsFinder
attr_reader :project, :current_user, :params
@@ -5,6 +7,7 @@ class EnvironmentsFinder
@project, @current_user, @params = project, current_user, params
end
+ # rubocop: disable CodeReuse/ActiveRecord
def execute
deployments = project.deployments
deployments =
@@ -42,6 +45,7 @@ class EnvironmentsFinder
environments
end
+ # rubocop: enable CodeReuse/ActiveRecord
private
diff --git a/app/finders/events_finder.rb b/app/finders/events_finder.rb
index 8676925a540..fd7aeca0d8b 100644
--- a/app/finders/events_finder.rb
+++ b/app/finders/events_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class EventsFinder
prepend FinderMethods
prepend FinderWithCrossProjectAccess
@@ -36,32 +38,42 @@ class EventsFinder
private
+ # rubocop: disable CodeReuse/ActiveRecord
def by_current_user_access(events)
- events.merge(ProjectsFinder.new(current_user: current_user).execute)
+ events.merge(ProjectsFinder.new(current_user: current_user).execute) # rubocop: disable CodeReuse/Finder
.joins(:project)
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def by_action(events)
return events unless Event::ACTIONS[params[:action]]
events.where(action: Event::ACTIONS[params[:action]])
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def by_target_type(events)
return events unless Event::TARGET_TYPES[params[:target_type]]
events.where(target_type: Event::TARGET_TYPES[params[:target_type]])
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def by_created_at_before(events)
return events unless params[:before]
events.where('events.created_at < ?', params[:before].beginning_of_day)
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def by_created_at_after(events)
return events unless params[:after]
events.where('events.created_at > ?', params[:after].end_of_day)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/finders/fork_projects_finder.rb b/app/finders/fork_projects_finder.rb
index 28d1b31868e..03ace7e8057 100644
--- a/app/finders/fork_projects_finder.rb
+++ b/app/finders/fork_projects_finder.rb
@@ -1,6 +1,10 @@
+# frozen_string_literal: true
+
class ForkProjectsFinder < ProjectsFinder
+ # rubocop: disable CodeReuse/ActiveRecord
def initialize(project, params: {}, current_user: nil)
project_ids = project.forks.includes(:creator).select(:id)
super(params: params, current_user: current_user, project_ids_relation: project_ids)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/finders/group_descendants_finder.rb b/app/finders/group_descendants_finder.rb
index 051ea108e06..9d57d2d3bc9 100644
--- a/app/finders/group_descendants_finder.rb
+++ b/app/finders/group_descendants_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# GroupDescendantsFinder
#
# Used to find and filter all subgroups and projects of a passed parent group
@@ -61,12 +63,16 @@ class GroupDescendantsFinder
end
def direct_child_groups
+ # rubocop: disable CodeReuse/Finder
GroupsFinder.new(current_user,
parent: parent_group,
all_available: true).execute
+ # rubocop: enable CodeReuse/Finder
end
+ # rubocop: disable CodeReuse/ActiveRecord
def all_visible_descendant_groups
+ # rubocop: disable CodeReuse/Finder
groups_table = Group.arel_table
visible_to_user = groups_table[:visibility_level]
.in(Gitlab::VisibilityLevel.levels_for_user(current_user))
@@ -84,7 +90,9 @@ class GroupDescendantsFinder
hierarchy_for_parent
.descendants
.where(visible_to_user)
+ # rubocop: enable CodeReuse/Finder
end
+ # rubocop: enable CodeReuse/ActiveRecord
def subgroups_matching_filter
all_visible_descendant_groups
@@ -101,24 +109,29 @@ class GroupDescendantsFinder
#
# So when searching 'project', on the 'subgroup' page we want to preload
# 'nested-group' but not 'subgroup' or 'root'
+ # rubocop: disable CodeReuse/ActiveRecord
def ancestors_of_groups(base_for_ancestors)
group_ids = base_for_ancestors.except(:select, :sort).select(:id)
Gitlab::GroupHierarchy.new(Group.where(id: group_ids))
.base_and_ancestors(upto: parent_group.id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def ancestors_of_filtered_projects
projects_to_load_ancestors_of = projects.where.not(namespace: parent_group)
groups_to_load_ancestors_of = Group.where(id: projects_to_load_ancestors_of.select(:namespace_id))
ancestors_of_groups(groups_to_load_ancestors_of)
.with_selects_for_list(archived: params[:archived])
end
+ # rubocop: enable CodeReuse/ActiveRecord
def ancestors_of_filtered_subgroups
ancestors_of_groups(subgroups)
.with_selects_for_list(archived: params[:archived])
end
+ # rubocop: disable CodeReuse/ActiveRecord
def subgroups
return Group.none unless Group.supports_nested_groups?
@@ -132,22 +145,29 @@ class GroupDescendantsFinder
groups.with_selects_for_list(archived: params[:archived]).order_by(sort)
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/Finder
def direct_child_projects
- GroupProjectsFinder.new(group: parent_group, current_user: current_user, params: params)
+ GroupProjectsFinder.new(group: parent_group, current_user: current_user, params: params, options: { only_owned: true })
.execute
end
+ # rubocop: enable CodeReuse/Finder
# Finds all projects nested under `parent_group` or any of its descendant
# groups
+ # rubocop: disable CodeReuse/ActiveRecord
def projects_matching_filter
+ # rubocop: disable CodeReuse/Finder
projects_nested_in_group = Project.where(namespace_id: hierarchy_for_parent.base_and_descendants.select(:id))
params_with_search = params.merge(search: params[:filter])
ProjectsFinder.new(params: params_with_search,
current_user: current_user,
project_ids_relation: projects_nested_in_group).execute
+ # rubocop: enable CodeReuse/Finder
end
+ # rubocop: enable CodeReuse/ActiveRecord
def projects
projects = if params[:filter]
@@ -163,7 +183,9 @@ class GroupDescendantsFinder
params.fetch(:sort, 'id_asc')
end
+ # rubocop: disable CodeReuse/ActiveRecord
def hierarchy_for_parent
@hierarchy ||= Gitlab::GroupHierarchy.new(Group.where(id: parent_group.id))
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/finders/group_finder.rb b/app/finders/group_finder.rb
index 24c84d2d1aa..d2ad8a372b1 100644
--- a/app/finders/group_finder.rb
+++ b/app/finders/group_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class GroupFinder
include Gitlab::Allowable
@@ -5,6 +7,7 @@ class GroupFinder
@current_user = current_user
end
+ # rubocop: disable CodeReuse/ActiveRecord
def execute(*params)
group = Group.find_by(*params)
@@ -14,4 +17,5 @@ class GroupFinder
nil
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/finders/group_labels_finder.rb b/app/finders/group_labels_finder.rb
new file mode 100644
index 00000000000..903023033ed
--- /dev/null
+++ b/app/finders/group_labels_finder.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class GroupLabelsFinder
+ attr_reader :group, :params
+
+ def initialize(group, params = {})
+ @group = group
+ @params = params
+ end
+
+ def execute
+ group.labels
+ .optionally_search(params[:search])
+ .order_by(params[:sort])
+ .page(params[:page])
+ end
+end
diff --git a/app/finders/group_members_finder.rb b/app/finders/group_members_finder.rb
index 2a656c0d31c..eebc67cfa9e 100644
--- a/app/finders/group_members_finder.rb
+++ b/app/finders/group_members_finder.rb
@@ -1,8 +1,11 @@
+# frozen_string_literal: true
+
class GroupMembersFinder
def initialize(group)
@group = group
end
+ # rubocop: disable CodeReuse/ActiveRecord
def execute(include_descendants: false)
group_members = @group.members
wheres = []
@@ -29,4 +32,5 @@ class GroupMembersFinder
GroupMember.where(wheres.join(' OR '))
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/finders/group_projects_finder.rb b/app/finders/group_projects_finder.rb
index b6bdb2b7b0f..4155b6af8da 100644
--- a/app/finders/group_projects_finder.rb
+++ b/app/finders/group_projects_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# GroupProjectsFinder
#
# Used to filter Projects by set of params
@@ -82,6 +84,7 @@ class GroupProjectsFinder < ProjectsFinder
options.fetch(:include_subgroups, false)
end
+ # rubocop: disable CodeReuse/ActiveRecord
def owned_projects
if include_subgroups?
Project.where(namespace_id: group.self_and_descendants.select(:id))
@@ -89,6 +92,7 @@ class GroupProjectsFinder < ProjectsFinder
group.projects
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def shared_projects
group.shared_projects
diff --git a/app/finders/groups_finder.rb b/app/finders/groups_finder.rb
index 0eeba1d2428..a35a3ed6142 100644
--- a/app/finders/groups_finder.rb
+++ b/app/finders/groups_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# GroupsFinder
#
# Used to filter Groups by a set of params
@@ -38,6 +40,7 @@ class GroupsFinder < UnionFinder
attr_reader :current_user, :params
+ # rubocop: disable CodeReuse/ActiveRecord
def all_groups
return [owned_groups] if params[:owned]
return [groups_with_min_access_level] if min_access_level?
@@ -49,6 +52,7 @@ class GroupsFinder < UnionFinder
groups << Group.none if groups.empty?
groups
end
+ # rubocop: enable CodeReuse/ActiveRecord
def groups_for_ancestors
current_user.authorized_groups
@@ -58,6 +62,7 @@ class GroupsFinder < UnionFinder
current_user.groups
end
+ # rubocop: disable CodeReuse/ActiveRecord
def groups_with_min_access_level
groups = current_user
.groups
@@ -67,16 +72,21 @@ class GroupsFinder < UnionFinder
.new(groups)
.base_and_descendants
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def by_parent(groups)
return groups unless params[:parent]
groups.where(parent: params[:parent])
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def owned_groups
current_user&.owned_groups || Group.none
end
+ # rubocop: enable CodeReuse/ActiveRecord
def include_public_groups?
current_user.nil? || all_available?
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 372e2a96c2c..251a559878a 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# IssuableFinder
#
# Used to filter Issues and MergeRequests collections by set of params
@@ -109,6 +111,7 @@ class IssuableFinder
# (even if that query is slower than any of the individual state queries) and
# grouping and counting within that query.
#
+ # rubocop: disable CodeReuse/ActiveRecord
def count_by_state
count_params = params.merge(state: nil, sort: nil)
finder = self.class.new(current_user, count_params)
@@ -132,6 +135,7 @@ class IssuableFinder
counts.with_indifferent_access
end
+ # rubocop: enable CodeReuse/ActiveRecord
def group
return @group if defined?(@group)
@@ -157,6 +161,7 @@ class IssuableFinder
@project = project
end
+ # rubocop: disable CodeReuse/ActiveRecord
def projects(items = nil)
return @projects = project if project?
@@ -165,13 +170,14 @@ class IssuableFinder
current_user.authorized_projects
elsif group
finder_options = { include_subgroups: params[:include_subgroups], only_owned: true }
- GroupProjectsFinder.new(group: group, current_user: current_user, options: finder_options).execute
+ GroupProjectsFinder.new(group: group, current_user: current_user, options: finder_options).execute # rubocop: disable CodeReuse/Finder
else
- ProjectsFinder.new(current_user: current_user).execute
+ ProjectsFinder.new(current_user: current_user).execute # rubocop: disable CodeReuse/Finder
end
@projects = projects.with_feature_available_for_user(klass, current_user).reorder(nil)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def search
params[:search].presence
@@ -185,6 +191,7 @@ class IssuableFinder
milestones? && params[:milestone_title] == Milestone::None.title
end
+ # rubocop: disable CodeReuse/ActiveRecord
def milestones
return @milestones if defined?(@milestones)
@@ -200,11 +207,12 @@ class IssuableFinder
search_params =
{ title: params[:milestone_title], project_ids: project_id, group_ids: group_id }
- MilestonesFinder.new(search_params).execute
+ MilestonesFinder.new(search_params).execute # rubocop: disable CodeReuse/Finder
else
Milestone.none
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def labels?
params[:label_name].present?
@@ -214,16 +222,18 @@ class IssuableFinder
labels? && params[:label_name].include?(Label::None.title)
end
+ # rubocop: disable CodeReuse/ActiveRecord
def labels
return @labels if defined?(@labels)
@labels =
if labels? && !filter_by_no_label?
- LabelsFinder.new(current_user, project_ids: projects, title: label_names).execute(skip_authorization: true)
+ LabelsFinder.new(current_user, project_ids: projects, title: label_names).execute(skip_authorization: true) # rubocop: disable CodeReuse/Finder
else
Label.none
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def assignee_id?
params[:assignee_id].present? && params[:assignee_id] != NONE
@@ -238,6 +248,7 @@ class IssuableFinder
params[:assignee_id] == NONE || params[:assignee_username] == NONE
end
+ # rubocop: disable CodeReuse/ActiveRecord
def assignee
return @assignee if defined?(@assignee)
@@ -250,6 +261,7 @@ class IssuableFinder
nil
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def author_id?
params[:author_id].present? && params[:author_id] != NONE
@@ -264,6 +276,7 @@ class IssuableFinder
params[:author_id] == NONE || params[:author_username] == NONE
end
+ # rubocop: disable CodeReuse/ActiveRecord
def author
return @author if defined?(@author)
@@ -276,6 +289,7 @@ class IssuableFinder
nil
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
private
@@ -283,6 +297,7 @@ class IssuableFinder
klass.all
end
+ # rubocop: disable CodeReuse/ActiveRecord
def by_scope(items)
return items.none if current_user_related? && !current_user
@@ -295,6 +310,7 @@ class IssuableFinder
items
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def by_updated_at(items)
items = items.updated_after(params[:updated_after]) if params[:updated_after].present?
@@ -303,6 +319,7 @@ class IssuableFinder
items
end
+ # rubocop: disable CodeReuse/ActiveRecord
def by_state(items)
case params[:state].to_s
when 'closed'
@@ -317,12 +334,14 @@ class IssuableFinder
items
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def by_group(items)
# Selection by group is already covered by `by_project` and `projects`
items
end
+ # rubocop: disable CodeReuse/ActiveRecord
def by_project(items)
items =
if project?
@@ -335,6 +354,7 @@ class IssuableFinder
items
end
+ # rubocop: enable CodeReuse/ActiveRecord
def use_cte_for_search?
return false unless search
@@ -343,6 +363,7 @@ class IssuableFinder
params[:use_cte_for_search]
end
+ # rubocop: disable CodeReuse/ActiveRecord
def by_search(items)
return items unless search
@@ -355,17 +376,23 @@ class IssuableFinder
items.full_search(search)
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def by_iids(items)
params[:iids].present? ? items.where(iid: params[:iids]) : items
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def sort(items)
# Ensure we always have an explicit sort order (instead of inheriting
# multiple orders when combining ActiveRecord::Relation objects).
params[:sort] ? items.sort_by_attribute(params[:sort], excluded_labels: label_names) : items.reorder(id: :desc)
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def by_assignee(items)
if assignee
items = items.where(assignee_id: assignee.id)
@@ -377,7 +404,9 @@ class IssuableFinder
items
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def by_author(items)
if author
items = items.where(author_id: author.id)
@@ -389,6 +418,7 @@ class IssuableFinder
items
end
+ # rubocop: enable CodeReuse/ActiveRecord
def filter_by_upcoming_milestone?
params[:milestone_title] == Milestone::Upcoming.name
@@ -398,6 +428,7 @@ class IssuableFinder
params[:milestone_title] == Milestone::Started.name
end
+ # rubocop: disable CodeReuse/ActiveRecord
def by_milestone(items)
if milestones?
if filter_by_no_milestone?
@@ -414,6 +445,7 @@ class IssuableFinder
items
end
+ # rubocop: enable CodeReuse/ActiveRecord
def by_label(items)
return items unless labels?
diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb
index 24a6b9349a0..770e0bfe1a3 100644
--- a/app/finders/issues_finder.rb
+++ b/app/finders/issues_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Finders::Issues class
#
# Used to filter Issues collections by set of params
@@ -29,10 +31,13 @@ class IssuesFinder < IssuableFinder
@scalar_params ||= super + [:due_date]
end
+ # rubocop: disable CodeReuse/ActiveRecord
def klass
Issue.includes(:author)
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def with_confidentiality_access_check
return Issue.all if user_can_see_all_confidential_issues?
return Issue.where('issues.confidential IS NOT TRUE') if user_cannot_see_confidential_issues?
@@ -46,6 +51,7 @@ class IssuesFinder < IssuableFinder
user_id: current_user.id,
project_ids: current_user.authorized_projects(CONFIDENTIAL_ACCESS_LEVEL).select(:id))
end
+ # rubocop: enable CodeReuse/ActiveRecord
private
@@ -125,6 +131,7 @@ class IssuesFinder < IssuableFinder
current_user.blank?
end
+ # rubocop: disable CodeReuse/ActiveRecord
def by_assignee(items)
if assignee
items.assigned_to(assignee)
@@ -136,4 +143,5 @@ class IssuesFinder < IssuableFinder
items
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/finders/joined_groups_finder.rb b/app/finders/joined_groups_finder.rb
index 47174980258..18cc6891ca4 100644
--- a/app/finders/joined_groups_finder.rb
+++ b/app/finders/joined_groups_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class JoinedGroupsFinder < UnionFinder
def initialize(user)
@user = user
diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb
index 1d05bf28438..08fc2968e77 100644
--- a/app/finders/labels_finder.rb
+++ b/app/finders/labels_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class LabelsFinder < UnionFinder
prepend FinderWithCrossProjectAccess
include FinderMethods
@@ -10,6 +12,7 @@ class LabelsFinder < UnionFinder
@params = params
end
+ # rubocop: disable CodeReuse/ActiveRecord
def execute(skip_authorization: false)
@skip_authorization = skip_authorization
items = find_union(label_ids, Label) || Label.none
@@ -17,11 +20,13 @@ class LabelsFinder < UnionFinder
items = by_search(items)
sort(items)
end
+ # rubocop: enable CodeReuse/ActiveRecord
private
attr_reader :current_user, :params, :skip_authorization
+ # rubocop: disable CodeReuse/ActiveRecord
def label_ids
label_ids = []
@@ -52,17 +57,26 @@ class LabelsFinder < UnionFinder
label_ids
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def sort(items)
- items.reorder(title: :asc)
+ if params[:sort]
+ items.order_by(params[:sort])
+ else
+ items.reorder(title: :asc)
+ end
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def with_title(items)
return items if title.nil?
return items.none if title.blank?
items.where(title: title)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def by_search(labels)
return labels unless search?
@@ -134,13 +148,14 @@ class LabelsFinder < UnionFinder
@project
end
+ # rubocop: disable CodeReuse/ActiveRecord
def projects
return @projects if defined?(@projects)
@projects = if skip_authorization
Project.all
else
- ProjectsFinder.new(params: { non_archived: true }, current_user: current_user).execute
+ ProjectsFinder.new(params: { non_archived: true }, current_user: current_user).execute # rubocop: disable CodeReuse/Finder
end
@projects = @projects.in_namespace(params[:group_id]) if group?
@@ -149,6 +164,7 @@ class LabelsFinder < UnionFinder
@projects
end
+ # rubocop: enable CodeReuse/ActiveRecord
def authorized_to_read_labels?(label_parent)
return true if skip_authorization
diff --git a/app/finders/license_template_finder.rb b/app/finders/license_template_finder.rb
index fad33f0eca2..196922709f7 100644
--- a/app/finders/license_template_finder.rb
+++ b/app/finders/license_template_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# LicenseTemplateFinder
#
# Used to find license templates, which may come from a variety of external
diff --git a/app/finders/members_finder.rb b/app/finders/members_finder.rb
index 4c893ae2de6..f90a7868102 100644
--- a/app/finders/members_finder.rb
+++ b/app/finders/members_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class MembersFinder
attr_reader :project, :current_user, :group
@@ -7,15 +9,16 @@ class MembersFinder
@group = project.group
end
+ # rubocop: disable CodeReuse/ActiveRecord
def execute(include_descendants: false)
project_members = project.project_members
project_members = project_members.non_invite unless can?(current_user, :admin_project, project)
if group
- group_members = GroupMembersFinder.new(group).execute(include_descendants: include_descendants)
+ group_members = GroupMembersFinder.new(group).execute(include_descendants: include_descendants) # rubocop: disable CodeReuse/Finder
group_members = group_members.non_invite
- union = Gitlab::SQL::Union.new([project_members, group_members], remove_duplicates: false)
+ union = Gitlab::SQL::Union.new([project_members, group_members], remove_duplicates: false) # rubocop: disable Gitlab/Union
sql = distinct_on(union)
@@ -24,6 +27,7 @@ class MembersFinder
project_members
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def can?(*args)
Ability.allowed?(*args)
diff --git a/app/finders/merge_request_target_project_finder.rb b/app/finders/merge_request_target_project_finder.rb
index 188ec447a94..5f0589f6c8b 100644
--- a/app/finders/merge_request_target_project_finder.rb
+++ b/app/finders/merge_request_target_project_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class MergeRequestTargetProjectFinder
include FinderMethods
@@ -8,6 +10,7 @@ class MergeRequestTargetProjectFinder
@source_project = source_project
end
+ # rubocop: disable CodeReuse/ActiveRecord
def execute
if @source_project.fork_network
@source_project.fork_network.projects
@@ -18,4 +21,5 @@ class MergeRequestTargetProjectFinder
Project.where(id: source_project)
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/finders/merge_requests_finder.rb b/app/finders/merge_requests_finder.rb
index 40089c082c1..b698a3c7b09 100644
--- a/app/finders/merge_requests_finder.rb
+++ b/app/finders/merge_requests_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Finders::MergeRequest class
#
# Used to filter MergeRequests collections by set of params
@@ -41,19 +43,23 @@ class MergeRequestsFinder < IssuableFinder
@source_branch ||= params[:source_branch].presence
end
+ # rubocop: disable CodeReuse/ActiveRecord
def by_source_branch(items)
return items unless source_branch
items.where(source_branch: source_branch)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def target_branch
@target_branch ||= params[:target_branch].presence
end
+ # rubocop: disable CodeReuse/ActiveRecord
def by_target_branch(items)
return items unless target_branch
items.where(target_branch: target_branch)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/finders/milestones_finder.rb b/app/finders/milestones_finder.rb
index f5d2b9f253a..47231ea80c7 100644
--- a/app/finders/milestones_finder.rb
+++ b/app/finders/milestones_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Search for milestones
#
# params - Hash
@@ -18,6 +20,7 @@ class MilestonesFinder
@params = params
end
+ # rubocop: disable CodeReuse/ActiveRecord
def execute
return Milestone.none if project_ids.empty? && group_ids.empty?
@@ -28,6 +31,7 @@ class MilestonesFinder
order(items)
end
+ # rubocop: enable CodeReuse/ActiveRecord
private
@@ -35,6 +39,7 @@ class MilestonesFinder
items.for_projects_and_groups(project_ids, group_ids)
end
+ # rubocop: disable CodeReuse/ActiveRecord
def by_title(items)
if params[:title]
items.where(title: params[:title])
@@ -42,13 +47,16 @@ class MilestonesFinder
items
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def by_state(items)
Milestone.filter_by_state(items, params[:state])
end
+ # rubocop: disable CodeReuse/ActiveRecord
def order(items)
order_statement = Gitlab::Database.nulls_last_order('due_date', 'ASC')
items.reorder(order_statement).order('title ASC')
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb
index 9b7a35fb3b5..c67c2065440 100644
--- a/app/finders/notes_finder.rb
+++ b/app/finders/notes_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class NotesFinder
FETCH_OVERLAP = 5.seconds
@@ -65,21 +67,23 @@ class NotesFinder
@params[:target_type]
end
+ # rubocop: disable CodeReuse/ActiveRecord
def notes_of_any_type
types = %w(commit issue merge_request snippet)
note_relations = types.map { |t| notes_for_type(t) }
note_relations.map! { |notes| search(notes) }
- UnionFinder.new.find_union(note_relations, Note.includes(:author))
+ UnionFinder.new.find_union(note_relations, Note.includes(:author)) # rubocop: disable CodeReuse/Finder
end
+ # rubocop: enable CodeReuse/ActiveRecord
def noteables_for_type(noteable_type)
case noteable_type
when "issue"
- IssuesFinder.new(@current_user, project_id: @project.id).execute
+ IssuesFinder.new(@current_user, project_id: @project.id).execute # rubocop: disable CodeReuse/Finder
when "merge_request"
- MergeRequestsFinder.new(@current_user, project_id: @project.id).execute
+ MergeRequestsFinder.new(@current_user, project_id: @project.id).execute # rubocop: disable CodeReuse/Finder
when "snippet", "project_snippet"
- SnippetsFinder.new(@current_user, project: @project).execute
+ SnippetsFinder.new(@current_user, project: @project).execute # rubocop: disable CodeReuse/Finder
when "personal_snippet"
PersonalSnippet.all
else
@@ -87,6 +91,7 @@ class NotesFinder
end
end
+ # rubocop: disable CodeReuse/ActiveRecord
def notes_for_type(noteable_type)
if noteable_type == "commit"
if Ability.allowed?(@current_user, :download_code, @project)
@@ -99,6 +104,7 @@ class NotesFinder
@project.notes.where(noteable_type: finder.base_class.name, noteable_id: finder.reorder(nil))
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def notes_on_target
if target.respond_to?(:related_notes)
diff --git a/app/finders/personal_access_tokens_finder.rb b/app/finders/personal_access_tokens_finder.rb
index d975f354a88..5beea92689f 100644
--- a/app/finders/personal_access_tokens_finder.rb
+++ b/app/finders/personal_access_tokens_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class PersonalAccessTokensFinder
attr_accessor :params
@@ -16,11 +18,13 @@ class PersonalAccessTokensFinder
private
+ # rubocop: disable CodeReuse/ActiveRecord
def by_user(tokens)
return tokens unless @params[:user]
tokens.where(user: @params[:user])
end
+ # rubocop: enable CodeReuse/ActiveRecord
def by_impersonation(tokens)
case @params[:impersonation]
diff --git a/app/finders/personal_projects_finder.rb b/app/finders/personal_projects_finder.rb
index a56a3a1e1a9..20f5b221a89 100644
--- a/app/finders/personal_projects_finder.rb
+++ b/app/finders/personal_projects_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class PersonalProjectsFinder < UnionFinder
include Gitlab::Allowable
@@ -15,6 +17,7 @@ class PersonalProjectsFinder < UnionFinder
# min_access_level: integer
#
# Returns an ActiveRecord::Relation.
+ # rubocop: disable CodeReuse/ActiveRecord
def execute(current_user = nil)
return Project.none unless can?(current_user, :read_user_profile, @user)
@@ -22,6 +25,7 @@ class PersonalProjectsFinder < UnionFinder
find_union(segments, Project).includes(:namespace).order_updated_desc
end
+ # rubocop: enable CodeReuse/ActiveRecord
private
diff --git a/app/finders/pipeline_schedules_finder.rb b/app/finders/pipeline_schedules_finder.rb
index 2ac4289fbbe..3beee608268 100644
--- a/app/finders/pipeline_schedules_finder.rb
+++ b/app/finders/pipeline_schedules_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class PipelineSchedulesFinder
attr_reader :project, :pipeline_schedules
@@ -6,6 +8,7 @@ class PipelineSchedulesFinder
@pipeline_schedules = project.pipeline_schedules
end
+ # rubocop: disable CodeReuse/ActiveRecord
def execute(scope: nil)
scoped_schedules =
case scope
@@ -19,4 +22,5 @@ class PipelineSchedulesFinder
scoped_schedules.order(id: :desc)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/finders/pipelines_finder.rb b/app/finders/pipelines_finder.rb
index a99a889a7e9..3d0d3219a94 100644
--- a/app/finders/pipelines_finder.rb
+++ b/app/finders/pipelines_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class PipelinesFinder
attr_reader :project, :pipelines, :params, :current_user
@@ -10,6 +12,7 @@ class PipelinesFinder
@params = params
end
+ # rubocop: disable CodeReuse/ActiveRecord
def execute
unless Ability.allowed?(current_user, :read_pipeline, project)
return Ci::Pipeline.none
@@ -25,16 +28,21 @@ class PipelinesFinder
items = by_yaml_errors(items)
sort_items(items)
end
+ # rubocop: enable CodeReuse/ActiveRecord
private
+ # rubocop: disable CodeReuse/ActiveRecord
def ids_for_ref(refs)
pipelines.where(ref: refs).group(:ref).select('max(id)')
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def from_ids(ids)
pipelines.unscoped.where(id: ids)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def branches
project.repository.branch_names
@@ -61,12 +69,15 @@ class PipelinesFinder
end
end
+ # rubocop: disable CodeReuse/ActiveRecord
def by_status(items)
return items unless HasStatus::AVAILABLE_STATUSES.include?(params[:status])
items.where(status: params[:status])
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def by_ref(items)
if params[:ref].present?
items.where(ref: params[:ref])
@@ -74,7 +85,9 @@ class PipelinesFinder
items
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def by_sha(items)
if params[:sha].present?
items.where(sha: params[:sha])
@@ -82,7 +95,9 @@ class PipelinesFinder
items
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def by_name(items)
if params[:name].present?
items.joins(:user).where(users: { name: params[:name] })
@@ -90,7 +105,9 @@ class PipelinesFinder
items
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def by_username(items)
if params[:username].present?
items.joins(:user).where(users: { username: params[:username] })
@@ -98,7 +115,9 @@ class PipelinesFinder
items
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def by_yaml_errors(items)
case Gitlab::Utils.to_boolean(params[:yaml_errors])
when true
@@ -109,7 +128,9 @@ class PipelinesFinder
items
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def sort_items(items)
order_by = if ALLOWED_INDEXED_COLUMNS.include?(params[:order_by])
params[:order_by]
@@ -125,4 +146,5 @@ class PipelinesFinder
items.order(order_by => sort)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb
index cac6643eff3..c2404412006 100644
--- a/app/finders/projects_finder.rb
+++ b/app/finders/projects_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# ProjectsFinder
#
# Used to filter Projects by set of params
@@ -35,7 +37,7 @@ class ProjectsFinder < UnionFinder
user = params.delete(:user)
collection =
if user
- PersonalProjectsFinder.new(user, finder_params).execute(current_user)
+ PersonalProjectsFinder.new(user, finder_params).execute(current_user) # rubocop: disable CodeReuse/Finder
else
init_collection
end
@@ -49,6 +51,7 @@ class ProjectsFinder < UnionFinder
collection = by_search(collection)
collection = by_archived(collection)
collection = by_custom_attributes(collection)
+ collection = by_deleted_status(collection)
sort(collection)
end
@@ -63,6 +66,7 @@ class ProjectsFinder < UnionFinder
end
end
+ # rubocop: disable CodeReuse/ActiveRecord
def collection_with_user
if owned_projects?
current_user.owned_projects
@@ -76,8 +80,10 @@ class ProjectsFinder < UnionFinder
end
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
# Builds a collection for an anonymous user.
+ # rubocop: disable CodeReuse/ActiveRecord
def collection_without_user
if private_only? || owned_projects? || min_access_level?
Project.none
@@ -85,6 +91,7 @@ class ProjectsFinder < UnionFinder
Project.public_to_user
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def owned_projects?
params[:owned].present?
@@ -98,9 +105,11 @@ class ProjectsFinder < UnionFinder
params[:min_access_level].present?
end
+ # rubocop: disable CodeReuse/ActiveRecord
def by_ids(items)
project_ids_relation ? items.where(id: project_ids_relation) : items
end
+ # rubocop: enable CodeReuse/ActiveRecord
def union(items)
find_union(items, Project).with_route
@@ -118,9 +127,11 @@ class ProjectsFinder < UnionFinder
params[:trending].present? ? items.trending : items
end
+ # rubocop: disable CodeReuse/ActiveRecord
def by_visibilty_level(items)
params[:visibility_level].present? ? items.where(visibility_level: params[:visibility_level]) : items
end
+ # rubocop: enable CodeReuse/ActiveRecord
def by_tags(items)
params[:tag].present? ? items.tagged_with(params[:tag]) : items
@@ -131,6 +142,10 @@ class ProjectsFinder < UnionFinder
params[:search].present? ? items.search(params[:search]) : items
end
+ def by_deleted_status(items)
+ params[:without_deleted].present? ? items.without_deleted : items
+ end
+
def sort(items)
params[:sort].present? ? items.sort_by_attribute(params[:sort]) : items.order_id_desc
end
diff --git a/app/finders/runner_jobs_finder.rb b/app/finders/runner_jobs_finder.rb
index 52340f94523..4fca4ec94f3 100644
--- a/app/finders/runner_jobs_finder.rb
+++ b/app/finders/runner_jobs_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class RunnerJobsFinder
attr_reader :runner, :params
@@ -14,9 +16,11 @@ class RunnerJobsFinder
private
+ # rubocop: disable CodeReuse/ActiveRecord
def by_status(items)
return items unless HasStatus::AVAILABLE_STATUSES.include?(params[:status])
items.where(status: params[:status])
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb
index 9d3772d7541..3528e4228b2 100644
--- a/app/finders/snippets_finder.rb
+++ b/app/finders/snippets_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Snippets Finder
#
# Used to filter Snippets collections by a set of params
@@ -41,6 +43,7 @@ class SnippetsFinder < UnionFinder
end
end
+ # rubocop: disable CodeReuse/ActiveRecord
def authorized_snippets_from_project
if can?(current_user, :read_project_snippet, project)
if project.team.member?(current_user)
@@ -52,7 +55,9 @@ class SnippetsFinder < UnionFinder
Snippet.none
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def authorized_snippets
# This query was intentionally converted to a raw one to get it work in Rails 5.0.
# In Rails 5.0 and 5.1 there's a bug: https://github.com/rails/arel/issues/531
@@ -60,6 +65,7 @@ class SnippetsFinder < UnionFinder
Snippet.where("#{feature_available_projects} OR #{not_project_related}")
.public_or_visible_to_user(current_user)
end
+ # rubocop: enable CodeReuse/ActiveRecord
# Returns a collection of projects that is either public or visible to the
# logged in user.
@@ -68,6 +74,7 @@ class SnippetsFinder < UnionFinder
# the query, e.g. to apply .with_feature_available_for_user on top of it.
# This is useful for performance as we can stick those additional filters
# at the bottom of e.g. the UNION.
+ # rubocop: disable CodeReuse/ActiveRecord
def projects_for_user
return yield(Project.public_to_user) unless current_user
@@ -82,10 +89,9 @@ class SnippetsFinder < UnionFinder
# We use a UNION here instead of OR clauses since this results in better
# performance.
- union = Gitlab::SQL::Union.new([authorized_projects.select('projects.id'), visible_projects.select('projects.id')])
-
- Project.from("(#{union.to_sql}) AS #{Project.table_name}")
+ Project.from_union([authorized_projects, visible_projects])
end
+ # rubocop: enable CodeReuse/ActiveRecord
def feature_available_projects
# Don't return any project related snippets if the user cannot read cross project
@@ -109,6 +115,7 @@ class SnippetsFinder < UnionFinder
Snippet.arel_table
end
+ # rubocop: disable CodeReuse/ActiveRecord
def by_visibility(items)
visibility = params[:visibility] || visibility_from_scope
@@ -116,12 +123,15 @@ class SnippetsFinder < UnionFinder
items.where(visibility_level: visibility)
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def by_author(items)
return items unless params[:author]
items.where(author_id: params[:author].id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def visibility_from_scope
case params[:scope].to_s
diff --git a/app/finders/tags_finder.rb b/app/finders/tags_finder.rb
index b474f0805dc..2ffd46245e9 100644
--- a/app/finders/tags_finder.rb
+++ b/app/finders/tags_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class TagsFinder
def initialize(repository, params)
@repository = repository
diff --git a/app/finders/template_finder.rb b/app/finders/template_finder.rb
index ea0251bffb6..c92ee9ca9ac 100644
--- a/app/finders/template_finder.rb
+++ b/app/finders/template_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class TemplateFinder
VENDORED_TEMPLATES = {
dockerfiles: ::Gitlab::Template::DockerfileTemplate,
@@ -8,7 +10,7 @@ class TemplateFinder
class << self
def build(type, params = {})
if type == :licenses
- LicenseTemplateFinder.new(params)
+ LicenseTemplateFinder.new(params) # rubocop: disable CodeReuse/Finder
else
new(type, params)
end
diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb
index 6e9c8ea6fde..74baf79e4f2 100644
--- a/app/finders/todos_finder.rb
+++ b/app/finders/todos_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# TodosFinder
#
# Used to filter Todos by set of params
@@ -120,6 +122,7 @@ class TodosFinder
params[:sort] ? items.sort_by_attribute(params[:sort]) : items.order_id_desc
end
+ # rubocop: disable CodeReuse/ActiveRecord
def by_action(items)
if action?
items = items.where(action: to_action_id)
@@ -127,7 +130,9 @@ class TodosFinder
items
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def by_action_id(items)
if action_id?
items = items.where(action: action_id)
@@ -135,7 +140,9 @@ class TodosFinder
items
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def by_author(items)
if author?
items = items.where(author_id: author.try(:id))
@@ -143,7 +150,9 @@ class TodosFinder
items
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def by_project(items)
if project?
items = items.where(project: project)
@@ -151,19 +160,19 @@ class TodosFinder
items
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def by_group(items)
- if group?
- groups = group.self_and_descendants
- project_todos = items.where(project_id: Project.where(group: groups).select(:id))
- group_todos = items.where(group_id: groups.select(:id))
+ return items unless group?
- union = Gitlab::SQL::Union.new([project_todos, group_todos])
- items = Todo.from("(#{union.to_sql}) #{Todo.table_name}")
- end
+ groups = group.self_and_descendants
+ project_todos = items.where(project_id: Project.where(group: groups).select(:id))
+ group_todos = items.where(group_id: groups.select(:id))
- items
+ Todo.from_union([project_todos, group_todos])
end
+ # rubocop: enable CodeReuse/ActiveRecord
def by_state(items)
case params[:state].to_s
@@ -174,6 +183,7 @@ class TodosFinder
end
end
+ # rubocop: disable CodeReuse/ActiveRecord
def by_type(items)
if type?
items = items.where(target_type: type)
@@ -181,4 +191,5 @@ class TodosFinder
items
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/finders/union_finder.rb b/app/finders/union_finder.rb
index 33cd1a491f3..c3b02f7e52f 100644
--- a/app/finders/union_finder.rb
+++ b/app/finders/union_finder.rb
@@ -1,9 +1,13 @@
+# frozen_string_literal: true
+
class UnionFinder
def find_union(segments, klass)
- if segments.length > 1
- union = Gitlab::SQL::Union.new(segments.map { |s| s.select(:id) })
+ unless klass < FromUnion
+ raise TypeError, "#{klass.inspect} must include the FromUnion module"
+ end
- klass.where("#{klass.table_name}.id IN (#{union.to_sql})")
+ if segments.length > 1
+ klass.from_union(segments)
else
segments.first
end
diff --git a/app/finders/user_finder.rb b/app/finders/user_finder.rb
index 484a93c9873..815388c894e 100644
--- a/app/finders/user_finder.rb
+++ b/app/finders/user_finder.rb
@@ -14,9 +14,11 @@ class UserFinder
end
# Tries to find a User, returning nil if none could be found.
+ # rubocop: disable CodeReuse/ActiveRecord
def execute
User.find_by(id: params[:id])
end
+ # rubocop: enable CodeReuse/ActiveRecord
# Tries to find a User, raising a `ActiveRecord::RecordNotFound` if it could
# not be found.
diff --git a/app/finders/user_recent_events_finder.rb b/app/finders/user_recent_events_finder.rb
index 876f086a3ef..a4daf5b5841 100644
--- a/app/finders/user_recent_events_finder.rb
+++ b/app/finders/user_recent_events_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Get user activity feed for projects common for a user and a logged in user
#
# - current_user: The user viewing the events
@@ -21,6 +23,7 @@ class UserRecentEventsFinder
@params = params
end
+ # rubocop: disable CodeReuse/ActiveRecord
def execute
return Event.none unless can?(current_user, :read_user_profile, target_user)
@@ -29,9 +32,11 @@ class UserRecentEventsFinder
.with_associations
.limit_recent(LIMIT, params[:offset])
end
+ # rubocop: enable CodeReuse/ActiveRecord
private
+ # rubocop: disable CodeReuse/ActiveRecord
def recent_events(offset)
sql = <<~SQL
(#{projects}) AS projects_for_join
@@ -42,26 +47,15 @@ class UserRecentEventsFinder
# Workaround for https://github.com/rails/rails/issues/24193
Event.from([Arel.sql(sql)])
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def target_events
Event.where(author: target_user)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def projects
- # Compile a list of projects `current_user` interacted with
- # and `target_user` is allowed to see.
-
- authorized = target_user
- .project_interactions
- .joins(:project_authorizations)
- .where(project_authorizations: { user: current_user })
- .select(:id)
-
- visible = target_user
- .project_interactions
- .where(visibility_level: Gitlab::VisibilityLevel.levels_for_user(current_user))
- .select(:id)
-
- Gitlab::SQL::Union.new([authorized, visible]).to_sql
+ target_user.project_interactions.to_sql
end
end
diff --git a/app/finders/users_finder.rb b/app/finders/users_finder.rb
index 65824a51919..f2ad9b4bda5 100644
--- a/app/finders/users_finder.rb
+++ b/app/finders/users_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# UsersFinder
#
# Used to filter users by set of params
@@ -41,11 +43,13 @@ class UsersFinder
private
+ # rubocop: disable CodeReuse/ActiveRecord
def by_username(users)
return users unless params[:username]
users.where(username: params[:username])
end
+ # rubocop: enable CodeReuse/ActiveRecord
def by_search(users)
return users unless params[:search].present?
@@ -65,18 +69,22 @@ class UsersFinder
users.active
end
+ # rubocop: disable CodeReuse/ActiveRecord
def by_external_identity(users)
return users unless current_user&.admin? && params[:extern_uid] && params[:provider]
users.joins(:identities).merge(Identity.with_extern_uid(params[:provider], params[:extern_uid]))
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def by_external(users)
return users = users.where.not(external: true) unless current_user&.admin?
return users unless params[:external]
users.external
end
+ # rubocop: enable CodeReuse/ActiveRecord
def by_2fa(users)
case params[:two_factor]
diff --git a/app/graphql/functions/base_function.rb b/app/graphql/functions/base_function.rb
index 42fb8f99acc..2512ecbd255 100644
--- a/app/graphql/functions/base_function.rb
+++ b/app/graphql/functions/base_function.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Functions
class BaseFunction < GraphQL::Function
end
diff --git a/app/graphql/functions/echo.rb b/app/graphql/functions/echo.rb
index e5bf109b8d7..3104486faac 100644
--- a/app/graphql/functions/echo.rb
+++ b/app/graphql/functions/echo.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Functions
class Echo < BaseFunction
argument :text, GraphQL::STRING_TYPE
diff --git a/app/graphql/gitlab_schema.rb b/app/graphql/gitlab_schema.rb
index 8755a1a62e7..06d26309b5b 100644
--- a/app/graphql/gitlab_schema.rb
+++ b/app/graphql/gitlab_schema.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class GitlabSchema < GraphQL::Schema
use BatchLoader::GraphQL
use Gitlab::Graphql::Authorize
diff --git a/app/graphql/mutations/concerns/mutations/resolves_project.rb b/app/graphql/mutations/concerns/mutations/resolves_project.rb
index 0dd1f264a52..da9814e88b0 100644
--- a/app/graphql/mutations/concerns/mutations/resolves_project.rb
+++ b/app/graphql/mutations/concerns/mutations/resolves_project.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Mutations
module ResolvesProject
extend ActiveSupport::Concern
diff --git a/app/graphql/mutations/merge_requests/base.rb b/app/graphql/mutations/merge_requests/base.rb
index 2149e72e2df..54f01c99d78 100644
--- a/app/graphql/mutations/merge_requests/base.rb
+++ b/app/graphql/mutations/merge_requests/base.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Mutations
module MergeRequests
class Base < BaseMutation
diff --git a/app/graphql/resolvers/base_resolver.rb b/app/graphql/resolvers/base_resolver.rb
index 89b7f9dad6f..459933af9d3 100644
--- a/app/graphql/resolvers/base_resolver.rb
+++ b/app/graphql/resolvers/base_resolver.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Resolvers
class BaseResolver < GraphQL::Schema::Resolver
end
diff --git a/app/graphql/resolvers/concerns/resolves_pipelines.rb b/app/graphql/resolvers/concerns/resolves_pipelines.rb
index 9ec45378d8e..8fd26d85994 100644
--- a/app/graphql/resolvers/concerns/resolves_pipelines.rb
+++ b/app/graphql/resolvers/concerns/resolves_pipelines.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ResolvesPipelines
extend ActiveSupport::Concern
diff --git a/app/graphql/resolvers/full_path_resolver.rb b/app/graphql/resolvers/full_path_resolver.rb
index 4eb28aaed6c..8d3da33e8d2 100644
--- a/app/graphql/resolvers/full_path_resolver.rb
+++ b/app/graphql/resolvers/full_path_resolver.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Resolvers
module FullPathResolver
extend ActiveSupport::Concern
diff --git a/app/graphql/resolvers/merge_request_pipelines_resolver.rb b/app/graphql/resolvers/merge_request_pipelines_resolver.rb
index 00b51ee1381..b371f1335f8 100644
--- a/app/graphql/resolvers/merge_request_pipelines_resolver.rb
+++ b/app/graphql/resolvers/merge_request_pipelines_resolver.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Resolvers
class MergeRequestPipelinesResolver < BaseResolver
include ::ResolvesPipelines
diff --git a/app/graphql/resolvers/merge_request_resolver.rb b/app/graphql/resolvers/merge_request_resolver.rb
index 9f2d348e95f..b87c95217f7 100644
--- a/app/graphql/resolvers/merge_request_resolver.rb
+++ b/app/graphql/resolvers/merge_request_resolver.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Resolvers
class MergeRequestResolver < BaseResolver
argument :iid, GraphQL::ID_TYPE,
@@ -8,6 +10,7 @@ module Resolvers
alias_method :project, :object
+ # rubocop: disable CodeReuse/ActiveRecord
def resolve(iid:)
return unless project.present?
@@ -16,5 +19,6 @@ module Resolvers
results.each { |mr| loader.call(mr.iid.to_s, mr) }
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/app/graphql/resolvers/project_pipelines_resolver.rb b/app/graphql/resolvers/project_pipelines_resolver.rb
index 7f175a3b26c..86094c46c2a 100644
--- a/app/graphql/resolvers/project_pipelines_resolver.rb
+++ b/app/graphql/resolvers/project_pipelines_resolver.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Resolvers
class ProjectPipelinesResolver < BaseResolver
include ResolvesPipelines
diff --git a/app/graphql/resolvers/project_resolver.rb b/app/graphql/resolvers/project_resolver.rb
index ec115bad896..ac7c9b0ce2e 100644
--- a/app/graphql/resolvers/project_resolver.rb
+++ b/app/graphql/resolvers/project_resolver.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Resolvers
class ProjectResolver < BaseResolver
prepend FullPathResolver
diff --git a/app/graphql/types/base_enum.rb b/app/graphql/types/base_enum.rb
index b45a845f74f..cf43fea45e6 100644
--- a/app/graphql/types/base_enum.rb
+++ b/app/graphql/types/base_enum.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Types
class BaseEnum < GraphQL::Schema::Enum
end
diff --git a/app/graphql/types/base_field.rb b/app/graphql/types/base_field.rb
index c5740a334d7..2b2ea64c00b 100644
--- a/app/graphql/types/base_field.rb
+++ b/app/graphql/types/base_field.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Types
class BaseField < GraphQL::Schema::Field
prepend Gitlab::Graphql::Authorize
diff --git a/app/graphql/types/base_input_object.rb b/app/graphql/types/base_input_object.rb
index 309e336e6c8..aebed035d3b 100644
--- a/app/graphql/types/base_input_object.rb
+++ b/app/graphql/types/base_input_object.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Types
class BaseInputObject < GraphQL::Schema::InputObject
end
diff --git a/app/graphql/types/base_interface.rb b/app/graphql/types/base_interface.rb
index 69e72dc5808..3451a195c33 100644
--- a/app/graphql/types/base_interface.rb
+++ b/app/graphql/types/base_interface.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Types
module BaseInterface
include GraphQL::Schema::Interface
diff --git a/app/graphql/types/base_object.rb b/app/graphql/types/base_object.rb
index 754adf4c04d..82b78abd573 100644
--- a/app/graphql/types/base_object.rb
+++ b/app/graphql/types/base_object.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Types
class BaseObject < GraphQL::Schema::Object
prepend Gitlab::Graphql::Present
diff --git a/app/graphql/types/base_scalar.rb b/app/graphql/types/base_scalar.rb
index c0aa38be239..719bc808f47 100644
--- a/app/graphql/types/base_scalar.rb
+++ b/app/graphql/types/base_scalar.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Types
class BaseScalar < GraphQL::Schema::Scalar
end
diff --git a/app/graphql/types/base_union.rb b/app/graphql/types/base_union.rb
index 36337fc6ee5..30a5668c0bb 100644
--- a/app/graphql/types/base_union.rb
+++ b/app/graphql/types/base_union.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Types
class BaseUnion < GraphQL::Schema::Union
end
diff --git a/app/graphql/types/ci/pipeline_status_enum.rb b/app/graphql/types/ci/pipeline_status_enum.rb
index 2c12e5001d8..c19ddf5bb25 100644
--- a/app/graphql/types/ci/pipeline_status_enum.rb
+++ b/app/graphql/types/ci/pipeline_status_enum.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Types
module Ci
class PipelineStatusEnum < BaseEnum
diff --git a/app/graphql/types/ci/pipeline_type.rb b/app/graphql/types/ci/pipeline_type.rb
index bbb7d9354d0..2bbffad4563 100644
--- a/app/graphql/types/ci/pipeline_type.rb
+++ b/app/graphql/types/ci/pipeline_type.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Types
module Ci
class PipelineType < BaseObject
diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb
index 88cd2adc6dc..fb740b6fb1c 100644
--- a/app/graphql/types/merge_request_type.rb
+++ b/app/graphql/types/merge_request_type.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Types
class MergeRequestType < BaseObject
expose_permissions Types::PermissionTypes::MergeRequest
diff --git a/app/graphql/types/permission_types/base_permission_type.rb b/app/graphql/types/permission_types/base_permission_type.rb
index 934ed572e56..26a71e2bfbb 100644
--- a/app/graphql/types/permission_types/base_permission_type.rb
+++ b/app/graphql/types/permission_types/base_permission_type.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Types
module PermissionTypes
class BasePermissionType < BaseObject
diff --git a/app/graphql/types/permission_types/ci/pipeline.rb b/app/graphql/types/permission_types/ci/pipeline.rb
index 942539c7cf7..73e44a33eba 100644
--- a/app/graphql/types/permission_types/ci/pipeline.rb
+++ b/app/graphql/types/permission_types/ci/pipeline.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Types
module PermissionTypes
module Ci
diff --git a/app/graphql/types/permission_types/merge_request.rb b/app/graphql/types/permission_types/merge_request.rb
index 5c21f6ee9c6..13995d3ea8f 100644
--- a/app/graphql/types/permission_types/merge_request.rb
+++ b/app/graphql/types/permission_types/merge_request.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Types
module PermissionTypes
class MergeRequest < BasePermissionType
diff --git a/app/graphql/types/permission_types/project.rb b/app/graphql/types/permission_types/project.rb
index 755699a4415..066ce64a254 100644
--- a/app/graphql/types/permission_types/project.rb
+++ b/app/graphql/types/permission_types/project.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Types
module PermissionTypes
class Project < BasePermissionType
diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb
index 97707215b4e..7b879608b34 100644
--- a/app/graphql/types/project_type.rb
+++ b/app/graphql/types/project_type.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Types
class ProjectType < BaseObject
expose_permissions Types::PermissionTypes::Project
diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb
index 010ec2d7942..7c41716b82a 100644
--- a/app/graphql/types/query_type.rb
+++ b/app/graphql/types/query_type.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Types
class QueryType < BaseObject
graphql_name 'Query'
diff --git a/app/graphql/types/time_type.rb b/app/graphql/types/time_type.rb
index 2333d82ad1e..f045a50e672 100644
--- a/app/graphql/types/time_type.rb
+++ b/app/graphql/types/time_type.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Types
class TimeType < BaseScalar
graphql_name 'Time'
diff --git a/app/helpers/accounts_helper.rb b/app/helpers/accounts_helper.rb
index 5d27d30eaa3..a4f19480539 100644
--- a/app/helpers/accounts_helper.rb
+++ b/app/helpers/accounts_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module AccountsHelper
def incoming_email_token_enabled?
current_user.incoming_email_token && Gitlab::IncomingEmail.supports_issue_creation?
diff --git a/app/helpers/active_sessions_helper.rb b/app/helpers/active_sessions_helper.rb
index 97b6dac67c5..84aa1160f12 100644
--- a/app/helpers/active_sessions_helper.rb
+++ b/app/helpers/active_sessions_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActiveSessionsHelper
# Maps a device type as defined in `ActiveSession` to an svg icon name and
# outputs the icon html.
diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb
index f48db024e3f..ed13c5cfdd6 100644
--- a/app/helpers/appearances_helper.rb
+++ b/app/helpers/appearances_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module AppearancesHelper
def brand_title
current_appearance&.title.presence || 'GitLab Community Edition'
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 0190aa90763..32fc8e5e9ce 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -1,15 +1,27 @@
+# frozen_string_literal: true
+
require 'digest/md5'
require 'uri'
module ApplicationHelper
# See https://docs.gitlab.com/ee/development/ee_features.html#code-in-app-views
+ # rubocop: disable CodeReuse/ActiveRecord
def render_if_exists(partial, locals = {})
- render(partial, locals) if lookup_context.exists?(partial, [], true)
+ render(partial, locals) if partial_exists?(partial)
+ end
+
+ def partial_exists?(partial)
+ lookup_context.exists?(partial, [], true)
end
+ def template_exists?(template)
+ lookup_context.exists?(template, [], false)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
# Check if a particular controller is the current one
#
- # args - One or more controller names to check
+ # args - One or more controller names to check (using path notation when inside namespaces)
#
# Examples
#
@@ -17,6 +29,11 @@ module ApplicationHelper
# current_controller?(:tree) # => true
# current_controller?(:commits) # => false
# current_controller?(:commits, :tree) # => true
+ #
+ # # On Admin::ApplicationController
+ # current_controller?(:application) # => true
+ # current_controller?('admin/application') # => true
+ # current_controller?('gitlab/application') # => false
def current_controller?(*args)
args.any? do |v|
v.to_s.downcase == controller.controller_name || v.to_s.downcase == controller.controller_path
@@ -49,6 +66,7 @@ module ApplicationHelper
# Define whenever show last push event
# with suggestion to create MR
+ # rubocop: disable CodeReuse/ActiveRecord
def show_last_push_widget?(event)
# Skip if event is not about added or modified non-master branch
return false unless event && event.last_push_to_non_root? && !event.rm_ref?
@@ -66,6 +84,7 @@ module ApplicationHelper
true
end
+ # rubocop: enable CodeReuse/ActiveRecord
def hexdigest(string)
Digest::SHA1.hexdigest string
@@ -106,11 +125,11 @@ module ApplicationHelper
#
# Returns an HTML-safe String
def time_ago_with_tooltip(time, placement: 'top', html_class: '', short_format: false)
- css_classes = short_format ? 'js-short-timeago' : 'js-timeago'
- css_classes << " #{html_class}" unless html_class.blank?
+ css_classes = [short_format ? 'js-short-timeago' : 'js-timeago']
+ css_classes << html_class unless html_class.blank?
element = content_tag :time, l(time, format: "%b %d, %Y"),
- class: css_classes,
+ class: css_classes.join(' '),
title: l(time.to_time.in_time_zone, format: :timeago_tooltip),
datetime: time.to_time.getutc.iso8601,
data: {
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 684c84c3006..dc393968786 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ApplicationSettingsHelper
extend self
@@ -73,12 +75,12 @@ module ApplicationSettingsHelper
def oauth_providers_checkboxes
button_based_providers.map do |source|
disabled = Gitlab::CurrentSettings.disabled_oauth_sign_in_sources.include?(source.to_s)
- css_class = 'btn'
- css_class << ' active' unless disabled
+ css_class = ['btn']
+ css_class << 'active' unless disabled
checkbox_name = 'application_setting[enabled_oauth_sign_in_sources][]'
name = Gitlab::Auth::OAuth::Provider.label_for(source)
- label_tag(checkbox_name, class: css_class) do
+ label_tag(checkbox_name, class: css_class.join(' ')) do
check_box_tag(checkbox_name, source, !disabled,
autocomplete: 'off',
id: name.tr(' ', '_')) + name
@@ -220,6 +222,7 @@ module ApplicationSettingsHelper
:recaptcha_enabled,
:recaptcha_private_key,
:recaptcha_site_key,
+ :receive_max_input_size,
:repository_checks_enabled,
:repository_storages,
:require_two_factor_authentication,
@@ -261,4 +264,8 @@ module ApplicationSettingsHelper
:web_ide_clientside_preview_enabled
]
end
+
+ def expanded_by_default?
+ Rails.env.test?
+ end
end
diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb
index 18f0979fc86..c158cf20dd6 100644
--- a/app/helpers/auth_helper.rb
+++ b/app/helpers/auth_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module AuthHelper
PROVIDERS_WITH_ICONS = %w(twitter github gitlab bitbucket google_oauth2 facebook azure_oauth2 authentiq).freeze
LDAP_PROVIDER = /\Aldap/
@@ -64,9 +66,11 @@ module AuthHelper
end
end
+ # rubocop: disable CodeReuse/ActiveRecord
def auth_active?(provider)
current_user.identities.exists?(provider: provider.to_s)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def unlink_allowed?(provider)
%w(saml cas3).exclude?(provider.to_s)
diff --git a/app/helpers/auto_devops_helper.rb b/app/helpers/auto_devops_helper.rb
index 7b076728685..516c8a353ea 100644
--- a/app/helpers/auto_devops_helper.rb
+++ b/app/helpers/auto_devops_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module AutoDevopsHelper
def show_auto_devops_callout?(project)
Feature.get(:auto_devops_banner_disabled).off? &&
@@ -24,6 +26,7 @@ module AutoDevopsHelper
end
end
+ # rubocop: disable CodeReuse/ActiveRecord
def cluster_ingress_ip(project)
project
.cluster_ingresses
@@ -32,6 +35,7 @@ module AutoDevopsHelper
.pluck(:external_ip)
.first
end
+ # rubocop: enable CodeReuse/ActiveRecord
private
diff --git a/app/helpers/avatars_helper.rb b/app/helpers/avatars_helper.rb
index 494f785e305..321811a3ca3 100644
--- a/app/helpers/avatars_helper.rb
+++ b/app/helpers/avatars_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module AvatarsHelper
def project_icon(project_id, options = {})
source_icon(Project, project_id, options)
@@ -125,9 +127,9 @@ module AvatarsHelper
def source_identicon(source, options = {})
bg_key = (source.id % 7) + 1
- options[:class] ||= ''
- options[:class] << ' identicon'
- options[:class] << " bg#{bg_key}"
+
+ options[:class] =
+ [*options[:class], "identicon bg#{bg_key}"].join(' ')
content_tag(:div, class: options[:class].strip) do
source.name[0, 1].upcase
diff --git a/app/helpers/award_emoji_helper.rb b/app/helpers/award_emoji_helper.rb
index 86b19368cfd..b97a95629f7 100644
--- a/app/helpers/award_emoji_helper.rb
+++ b/app/helpers/award_emoji_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module AwardEmojiHelper
def toggle_award_url(awardable)
return url_for([:toggle_award_emoji, awardable]) unless @project || awardable.is_a?(Note)
diff --git a/app/helpers/blame_helper.rb b/app/helpers/blame_helper.rb
index 089d9e3e387..82c74e2416d 100644
--- a/app/helpers/blame_helper.rb
+++ b/app/helpers/blame_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module BlameHelper
def age_map_duration(blame_groups, project)
now = Time.zone.now
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 96f7415ae98..9cbd5b5f785 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module BlobHelper
def highlight(blob_name, blob_content, repository: nil, plain: false)
plain ||= blob_content.length > Blob::MAXIMUM_TEXT_HIGHLIGHT_SIZE
diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb
index af878bcf9a0..e3b74f443f7 100644
--- a/app/helpers/boards_helper.rb
+++ b/app/helpers/boards_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module BoardsHelper
def board
@board ||= @board || @boards.first
diff --git a/app/helpers/branches_helper.rb b/app/helpers/branches_helper.rb
index 07b1fc3d7cf..eadf48205fc 100644
--- a/app/helpers/branches_helper.rb
+++ b/app/helpers/branches_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module BranchesHelper
def project_branches
options_for_select(@project.repository.branch_names, @project.default_branch)
diff --git a/app/helpers/breadcrumbs_helper.rb b/app/helpers/breadcrumbs_helper.rb
index e88fe6bcd7e..b067376cea0 100644
--- a/app/helpers/breadcrumbs_helper.rb
+++ b/app/helpers/breadcrumbs_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module BreadcrumbsHelper
def add_to_breadcrumbs(text, link)
@breadcrumbs_extra_links ||= []
diff --git a/app/helpers/broadcast_messages_helper.rb b/app/helpers/broadcast_messages_helper.rb
index 0a15c29cfb5..289cb44f1e8 100644
--- a/app/helpers/broadcast_messages_helper.rb
+++ b/app/helpers/broadcast_messages_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module BroadcastMessagesHelper
def broadcast_message(message)
return unless message.present?
@@ -8,18 +10,17 @@ module BroadcastMessagesHelper
end
def broadcast_message_style(broadcast_message)
- style = ''
+ style = []
if broadcast_message.color.present?
style << "background-color: #{broadcast_message.color}"
- style << '; ' if broadcast_message.font.present?
end
if broadcast_message.font.present?
style << "color: #{broadcast_message.font}"
end
- style
+ style.join('; ')
end
def broadcast_message_status(broadcast_message)
diff --git a/app/helpers/builds_helper.rb b/app/helpers/builds_helper.rb
index 4ec63fdaffc..3c8caec3fe5 100644
--- a/app/helpers/builds_helper.rb
+++ b/app/helpers/builds_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module BuildsHelper
def build_summary(build, skip: false)
if build.has_trace?
@@ -12,10 +14,10 @@ module BuildsHelper
end
def sidebar_build_class(build, current_build)
- build_class = ''
- build_class += ' active' if build.id === current_build.id
- build_class += ' retried' if build.retried?
- build_class
+ build_class = []
+ build_class << 'active' if build.id === current_build.id
+ build_class << 'retried' if build.retried?
+ build_class.join(' ')
end
def javascript_build_options
diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb
index 26e3850a540..7f071d55a6b 100644
--- a/app/helpers/button_helper.rb
+++ b/app/helpers/button_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ButtonHelper
# Output a "Copy to Clipboard" button
#
@@ -61,7 +63,7 @@ module ButtonHelper
dropdown_description = http_dropdown_description(protocol)
append_url = project.http_url_to_repo if append_link
- dropdown_item_with_description(protocol, dropdown_description, href: append_url)
+ dropdown_item_with_description(protocol, dropdown_description, href: append_url, data: { clone_type: 'http' })
end
def http_dropdown_description(protocol)
@@ -80,16 +82,17 @@ module ButtonHelper
append_url = project.ssh_url_to_repo if append_link
- dropdown_item_with_description('SSH', dropdown_description, href: append_url)
+ dropdown_item_with_description('SSH', dropdown_description, href: append_url, data: { clone_type: 'ssh' })
end
- def dropdown_item_with_description(title, description, href: nil)
+ def dropdown_item_with_description(title, description, href: nil, data: nil)
button_content = content_tag(:strong, title, class: 'dropdown-menu-inner-title')
button_content << content_tag(:span, description, class: 'dropdown-menu-inner-content') if description
content_tag (href ? :a : :span),
(href ? button_content : title),
class: "#{title.downcase}-selector",
- href: (href if href)
+ href: (href if href),
+ data: (data if data)
end
end
diff --git a/app/helpers/calendar_helper.rb b/app/helpers/calendar_helper.rb
index c54b91b0ce5..ad4116fc3da 100644
--- a/app/helpers/calendar_helper.rb
+++ b/app/helpers/calendar_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module CalendarHelper
def calendar_url_options
{ format: :ics,
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index 330959e536d..136772e1ec3 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
##
# DEPRECATED
#
@@ -121,11 +123,6 @@ module CiStatusHelper
render_status_with_link('pipeline', pipeline.status, path, tooltip_placement: tooltip_placement)
end
- def no_runners_for_project?(project)
- project.runners.blank? &&
- Ci::Runner.instance_type.blank?
- end
-
def render_status_with_link(type, status, path = nil, tooltip_placement: 'left', cssclass: '', container: 'body', icon_size: 16)
klass = "ci-status-link ci-status-icon-#{status.dasherize} #{cssclass}"
title = "#{type.titleize}: #{ci_label_for_status(status)}"
diff --git a/app/helpers/clusters_helper.rb b/app/helpers/clusters_helper.rb
index 8fd0b6f14c6..a67c91b21d7 100644
--- a/app/helpers/clusters_helper.rb
+++ b/app/helpers/clusters_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ClustersHelper
def has_multiple_clusters?(project)
false
@@ -11,4 +13,8 @@ module ClustersHelper
render 'projects/clusters/gcp_signup_offer_banner'
end
end
+
+ def rbac_clusters_feature_enabled?
+ Feature.enabled?(:rbac_clusters)
+ end
end
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index 7a942c44ac4..d52cfd6e37a 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module CommitsHelper
# Returns a link to the commit author. If the author has a matching user and
# is a member of the current @project it will link to the team member page.
diff --git a/app/helpers/compare_helper.rb b/app/helpers/compare_helper.rb
index 2df5b5d1695..9ece8b0bc5b 100644
--- a/app/helpers/compare_helper.rb
+++ b/app/helpers/compare_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module CompareHelper
def create_mr_button?(from = params[:from], to = params[:to], project = @project)
from.present? &&
diff --git a/app/helpers/components_helper.rb b/app/helpers/components_helper.rb
index 8893209b314..d0ef86851ad 100644
--- a/app/helpers/components_helper.rb
+++ b/app/helpers/components_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ComponentsHelper
def gitlab_workhorse_version
if request.headers['Gitlab-Workhorse'].present?
diff --git a/app/helpers/conversational_development_index_helper.rb b/app/helpers/conversational_development_index_helper.rb
index 1ff54415811..37e5bb325fb 100644
--- a/app/helpers/conversational_development_index_helper.rb
+++ b/app/helpers/conversational_development_index_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ConversationalDevelopmentIndexHelper
def score_level(score)
if score < 33.33
diff --git a/app/helpers/count_helper.rb b/app/helpers/count_helper.rb
index 5cd98f40f78..e16223a82c9 100644
--- a/app/helpers/count_helper.rb
+++ b/app/helpers/count_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module CountHelper
def approximate_count_with_delimiters(count_data, model)
count = count_data[model]
diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb
index 31551fba862..33c53021c11 100644
--- a/app/helpers/dashboard_helper.rb
+++ b/app/helpers/dashboard_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module DashboardHelper
def assigned_issues_dashboard_path
issues_dashboard_path(assignee_id: current_user.id)
diff --git a/app/helpers/defer_script_tag_helper.rb b/app/helpers/defer_script_tag_helper.rb
index e1567556e5e..d91c6d52683 100644
--- a/app/helpers/defer_script_tag_helper.rb
+++ b/app/helpers/defer_script_tag_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module DeferScriptTagHelper
# Override the default ActionView `javascript_include_tag` helper to support page specific deferred loading
def javascript_include_tag(*sources)
diff --git a/app/helpers/deploy_tokens_helper.rb b/app/helpers/deploy_tokens_helper.rb
index bd921322476..80a5bb44c69 100644
--- a/app/helpers/deploy_tokens_helper.rb
+++ b/app/helpers/deploy_tokens_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module DeployTokensHelper
def expand_deploy_tokens_section?(deploy_token)
deploy_token.persisted? ||
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index 1bb82fd8150..b6844d36052 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module DiffHelper
def mark_inline_diffs(old_line, new_line)
old_diffs, new_diffs = Gitlab::Diff::InlineDiff.new(old_line, new_line).inline_diffs
@@ -39,7 +41,8 @@ module DiffHelper
line_num_class = %w[diff-line-num unfold js-unfold]
line_num_class << 'js-unfold-bottom' if bottom
- html = ''
+ html = []
+
if old_pos
html << content_tag(:td, '...', class: [*line_num_class, 'old_line'], data: { linenumber: old_pos })
html << content_tag(:td, text, class: [*content_line_class, 'left-side']) if view == :parallel
@@ -50,7 +53,7 @@ module DiffHelper
html << content_tag(:td, text, class: [*content_line_class, ('right-side' if view == :parallel)])
end
- html.html_safe
+ html.join.html_safe
end
def diff_line_content(line)
@@ -210,14 +213,14 @@ module DiffHelper
params[:w] == '1'
end
+ # rubocop: disable CodeReuse/ActiveRecord
def params_with_whitespace
hide_whitespace? ? request.query_parameters.except(:w) : request.query_parameters.merge(w: 1)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def toggle_whitespace_link(url, options)
- options[:class] ||= ''
- options[:class] << ' btn btn-default'
-
+ options[:class] = [*options[:class], 'btn btn-default'].join(' ')
link_to "#{hide_whitespace? ? 'Show' : 'Hide'} whitespace changes", url, class: options[:class]
end
diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb
index 5a2360b4661..4b6c5b215e8 100644
--- a/app/helpers/dropdowns_helper.rb
+++ b/app/helpers/dropdowns_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module DropdownsHelper
def dropdown_tag(toggle_text, options: {}, &block)
content_tag :div, class: "dropdown #{options[:wrapper_class] if options.key?(:wrapper_class)}" do
@@ -10,7 +12,7 @@ module DropdownsHelper
dropdown_output = dropdown_toggle(toggle_text, data_attr, options)
dropdown_output << content_tag(:div, class: "dropdown-menu dropdown-select #{options[:dropdown_class] if options.key?(:dropdown_class)}") do
- output = ""
+ output = []
if options.key?(:title)
output << dropdown_title(options[:title])
@@ -31,8 +33,7 @@ module DropdownsHelper
end
output << dropdown_loading
-
- output.html_safe
+ output.join.html_safe
end
dropdown_output.html_safe
@@ -50,7 +51,7 @@ module DropdownsHelper
def dropdown_title(title, options: {})
content_tag :div, class: "dropdown-title" do
- title_output = ""
+ title_output = []
if options.fetch(:back, false)
title_output << content_tag(:button, class: "dropdown-title-button dropdown-menu-back", aria: { label: "Go back" }, type: "button") do
@@ -66,7 +67,7 @@ module DropdownsHelper
end
end
- title_output.html_safe
+ title_output.join.html_safe
end
end
diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb
index c86a26ac30f..2d2e89a2a50 100644
--- a/app/helpers/emails_helper.rb
+++ b/app/helpers/emails_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module EmailsHelper
include AppearancesHelper
@@ -49,8 +51,8 @@ module EmailsHelper
def reset_token_expire_message
link_tag = link_to('request a new one', new_user_password_url(user_email: @user.email))
- msg = "This link is valid for #{password_reset_token_valid_time}. "
- msg << "After it expires, you can #{link_tag}."
+ "This link is valid for #{password_reset_token_valid_time}. " \
+ "After it expires, you can #{link_tag}."
end
def header_logo
diff --git a/app/helpers/emoji_helper.rb b/app/helpers/emoji_helper.rb
index 482f68f412b..51b7fd7f352 100644
--- a/app/helpers/emoji_helper.rb
+++ b/app/helpers/emoji_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module EmojiHelper
def emoji_icon(*args)
raw Gitlab::Emoji.gl_emoji_tag(*args)
diff --git a/app/helpers/environment_helper.rb b/app/helpers/environment_helper.rb
index 1e78a189c08..2b7320817ed 100644
--- a/app/helpers/environment_helper.rb
+++ b/app/helpers/environment_helper.rb
@@ -1,9 +1,13 @@
+# frozen_string_literal: true
+
module EnvironmentHelper
+ # rubocop: disable CodeReuse/ActiveRecord
def environment_for_build(project, build)
return unless build.environment
project.environments.find_by(name: build.expanded_environment_name)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def environment_link_for_build(project, build)
environment = environment_for_build(project, build)
diff --git a/app/helpers/environments_helper.rb b/app/helpers/environments_helper.rb
index c005ecbb56b..7b22bc8f98f 100644
--- a/app/helpers/environments_helper.rb
+++ b/app/helpers/environments_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module EnvironmentsHelper
def environments_list_data
{
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index 269acf5b2e2..c94946a04e7 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module EventsHelper
ICON_NAMES_BY_EVENT_TYPE = {
'pushed to' => 'commit',
@@ -19,7 +21,7 @@ module EventsHelper
name = self_added ? 'You' : author.name
link_to name, user_path(author.username), title: name
else
- event.author_name
+ escape_once(event.author_name)
end
end
diff --git a/app/helpers/explore_helper.rb b/app/helpers/explore_helper.rb
index f062a91a166..62be591ec47 100644
--- a/app/helpers/explore_helper.rb
+++ b/app/helpers/explore_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ExploreHelper
def filter_projects_path(options = {})
exist_opts = {
diff --git a/app/helpers/external_wiki_helper.rb b/app/helpers/external_wiki_helper.rb
index 8cf890b74a8..e36d63b2946 100644
--- a/app/helpers/external_wiki_helper.rb
+++ b/app/helpers/external_wiki_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ExternalWikiHelper
def get_project_wiki_path(project)
external_wiki_service = project.external_wiki
diff --git a/app/helpers/favicon_helper.rb b/app/helpers/favicon_helper.rb
index 3a5342a8d9d..4a809731d97 100644
--- a/app/helpers/favicon_helper.rb
+++ b/app/helpers/favicon_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module FaviconHelper
def favicon_extension_whitelist
FaviconUploader::EXTENSION_WHITELIST
diff --git a/app/helpers/form_helper.rb b/app/helpers/form_helper.rb
index 905e2002592..5705ee54cee 100644
--- a/app/helpers/form_helper.rb
+++ b/app/helpers/form_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module FormHelper
def form_errors(model, type: 'form')
return unless model.errors.any?
diff --git a/app/helpers/git_helper.rb b/app/helpers/git_helper.rb
index 8ab394384f3..5edc6dcf454 100644
--- a/app/helpers/git_helper.rb
+++ b/app/helpers/git_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module GitHelper
def strip_gpg_signature(text)
text.gsub(/-----BEGIN PGP SIGNATURE-----(.*)-----END PGP SIGNATURE-----/m, "")
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index 61e12b0f31e..04cf43be452 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Shorter routing method for some project items
module GitlabRoutingHelper
extend ActiveSupport::Concern
diff --git a/app/helpers/graph_helper.rb b/app/helpers/graph_helper.rb
index 1022070ab6f..49b15cde009 100644
--- a/app/helpers/graph_helper.rb
+++ b/app/helpers/graph_helper.rb
@@ -1,12 +1,14 @@
+# frozen_string_literal: true
+
module GraphHelper
def refs(repo, commit)
- refs = commit.ref_names(repo).join(' ')
+ refs = [commit.ref_names(repo).join(' ')]
# append note count
notes_count = @graph.notes[commit.id]
refs << "[#{pluralize(notes_count, 'note')}]" if notes_count > 0
- refs
+ refs.join
end
def parents_zip_spaces(parents, parent_spaces)
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index 5b51d2f2425..f573fd399a5 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module GroupsHelper
def group_nav_link_paths
%w[groups#projects groups#edit badges#index ci_cd#show ldap_group_links#index hooks#index audit_events#index pipeline_quota#index]
@@ -43,22 +45,22 @@ module GroupsHelper
def group_title(group, name = nil, url = nil)
@has_group_title = true
- full_title = ''
+ full_title = []
group.ancestors.reverse.each_with_index do |parent, index|
if index > 0
add_to_breadcrumb_dropdown(group_title_link(parent, hidable: false, show_avatar: true, for_dropdown: true), location: :before)
else
- full_title += breadcrumb_list_item group_title_link(parent, hidable: false)
+ full_title << breadcrumb_list_item(group_title_link(parent, hidable: false))
end
end
- full_title += render "layouts/nav/breadcrumbs/collapsed_dropdown", location: :before, title: _("Show parent subgroups")
+ full_title << render("layouts/nav/breadcrumbs/collapsed_dropdown", location: :before, title: _("Show parent subgroups"))
- full_title += breadcrumb_list_item group_title_link(group)
- full_title += ' &middot; '.html_safe + link_to(simple_sanitize(name), url, class: 'group-path breadcrumb-item-text js-breadcrumb-item-text') if name
+ full_title << breadcrumb_list_item(group_title_link(group))
+ full_title << ' &middot; '.html_safe + link_to(simple_sanitize(name), url, class: 'group-path breadcrumb-item-text js-breadcrumb-item-text') if name
- full_title.html_safe
+ full_title.join.html_safe
end
def projects_lfs_status(group)
@@ -138,15 +140,8 @@ module GroupsHelper
def group_title_link(group, hidable: false, show_avatar: false, for_dropdown: false)
link_to(group_path(group), class: "group-path #{'breadcrumb-item-text' unless for_dropdown} js-breadcrumb-item-text #{'hidable' if hidable}") do
- output =
- if (group.try(:avatar_url) || show_avatar) && !Rails.env.test?
- group_icon(group, class: "avatar-tile", width: 15, height: 15)
- else
- ""
- end
-
- output << simple_sanitize(group.name)
- output.html_safe
+ icon = group_icon(group, class: "avatar-tile", width: 15, height: 15) if (group.try(:avatar_url) || show_avatar) && !Rails.env.test?
+ [icon, simple_sanitize(group.name)].join.html_safe
end
end
diff --git a/app/helpers/hooks_helper.rb b/app/helpers/hooks_helper.rb
index 0a356ba55d2..c4b39939192 100644
--- a/app/helpers/hooks_helper.rb
+++ b/app/helpers/hooks_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module HooksHelper
def link_to_test_hook(hook, trigger)
path = case hook
diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb
index a8a10c98d69..037004327b9 100644
--- a/app/helpers/icons_helper.rb
+++ b/app/helpers/icons_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'json'
module IconsHelper
@@ -47,9 +49,10 @@ module IconsHelper
end
end
- css_classes = size ? "s#{size}" : ""
- css_classes << " #{css_class}" unless css_class.blank?
- content_tag(:svg, content_tag(:use, "", { "xlink:href" => "#{sprite_icon_path}##{icon_name}" } ), class: css_classes.empty? ? nil : css_classes)
+ css_classes = []
+ css_classes << "s#{size}" if size
+ css_classes << "#{css_class}" unless css_class.blank?
+ content_tag(:svg, content_tag(:use, "", { "xlink:href" => "#{sprite_icon_path}##{icon_name}" } ), class: css_classes.empty? ? nil : css_classes.join(' '))
end
def external_snippet_icon(name)
@@ -70,10 +73,10 @@ module IconsHelper
end
def spinner(text = nil, visible = false)
- css_class = 'loading'
- css_class << ' hide' unless visible
+ css_class = ['loading']
+ css_class << 'hide' unless visible
- content_tag :div, class: css_class do
+ content_tag :div, class: css_class.join(' ') do
icon('spinner spin') + text
end
end
@@ -86,7 +89,7 @@ module IconsHelper
end
end
- def visibility_level_icon(level, fw: true)
+ def visibility_level_icon(level, fw: true, options: {})
name =
case level
when Gitlab::VisibilityLevel::PRIVATE
@@ -97,9 +100,10 @@ module IconsHelper
'globe'
end
- name << " fw" if fw
+ name = [name]
+ name << "fw" if fw
- icon(name)
+ icon(name.join(' '), options)
end
def file_type_icon_class(type, mode, name)
diff --git a/app/helpers/import_helper.rb b/app/helpers/import_helper.rb
index c65f1565425..3d0eb3d0d51 100644
--- a/app/helpers/import_helper.rb
+++ b/app/helpers/import_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ImportHelper
include ::Gitlab::Utils::StrongMemoize
diff --git a/app/helpers/instance_configuration_helper.rb b/app/helpers/instance_configuration_helper.rb
index cee319f20bc..f695be32743 100644
--- a/app/helpers/instance_configuration_helper.rb
+++ b/app/helpers/instance_configuration_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module InstanceConfigurationHelper
def instance_configuration_cell_html(value, &block)
return '-' unless value.to_s.presence
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index c84ed8091c3..56f6686da57 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module IssuablesHelper
include GitlabRoutingHelper
@@ -105,6 +107,7 @@ module IssuablesHelper
end
end
+ # rubocop: disable CodeReuse/ActiveRecord
def user_dropdown_label(user_id, default_label)
return default_label if user_id.nil?
return "Unassigned" if user_id == "0"
@@ -117,7 +120,9 @@ module IssuablesHelper
default_label
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def project_dropdown_label(project_id, default_label)
return default_label if project_id.nil?
return "Any project" if project_id == "0"
@@ -130,7 +135,9 @@ module IssuablesHelper
default_label
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def group_dropdown_label(group_id, default_label)
return default_label if group_id.nil?
return "Any group" if group_id == "0"
@@ -143,6 +150,7 @@ module IssuablesHelper
default_label
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def milestone_dropdown_label(milestone_title, default_label = "Milestone")
title =
@@ -167,33 +175,35 @@ module IssuablesHelper
end
def issuable_meta(issuable, project, text)
- output = ""
+ output = []
output << "Opened #{time_ago_with_tooltip(issuable.created_at)} by ".html_safe
+
output << content_tag(:strong) do
author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "d-none d-sm-inline", tooltip: true)
author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "d-block d-sm-none")
if status = user_status(issuable.author)
- author_output << "&ensp; #{status}".html_safe
+ author_output << "#{status}".html_safe
end
author_output
end
- output << "&ensp;".html_safe
output << content_tag(:span, (issuable_first_contribution_icon if issuable.first_contribution?), class: 'has-tooltip', title: _('1st contribution!'))
- output << content_tag(:span, (issuable.task_status if issuable.tasks?), id: "task_status", class: "d-none d-sm-none d-md-inline-block")
+ output << content_tag(:span, (issuable.task_status if issuable.tasks?), id: "task_status", class: "d-none d-sm-none d-md-inline-block prepend-left-8")
output << content_tag(:span, (issuable.task_status_short if issuable.tasks?), id: "task_status_short", class: "d-md-none")
- output.html_safe
+ output.join.html_safe
end
+ # rubocop: disable CodeReuse/ActiveRecord
def issuable_todo(issuable)
if current_user
current_user.todos.find_by(target: issuable, state: :pending)
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def issuable_labels_tooltip(labels, limit: 5)
first, last = labels.partition.with_index { |_, i| i < limit }
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 5b27d1d9404..f7d448ea3a7 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -1,9 +1,11 @@
+# frozen_string_literal: true
+
module IssuesHelper
def issue_css_classes(issue)
- classes = "issue"
- classes << " closed" if issue.closed?
- classes << " today" if issue.today?
- classes
+ classes = ["issue"]
+ classes << "closed" if issue.closed?
+ classes << "today" if issue.today?
+ classes.join(' ')
end
# Returns an OpenStruct object suitable for use by <tt>options_from_collection_for_select</tt>
@@ -105,8 +107,8 @@ module IssuesHelper
end
def link_to_discussions_to_resolve(merge_request, single_discussion = nil)
- link_text = merge_request.to_reference
- link_text += " (discussion #{single_discussion.first_note.id})" if single_discussion
+ link_text = [merge_request.to_reference]
+ link_text << "(discussion #{single_discussion.first_note.id})" if single_discussion
path = if single_discussion
Gitlab::UrlBuilder.build(single_discussion.first_note)
@@ -115,7 +117,7 @@ module IssuesHelper
project_merge_request_path(project, merge_request)
end
- link_to link_text, path
+ link_to link_text.join(' '), path
end
def show_new_issue_link?(project)
diff --git a/app/helpers/javascript_helper.rb b/app/helpers/javascript_helper.rb
index cd4075b340d..7cb6da26236 100644
--- a/app/helpers/javascript_helper.rb
+++ b/app/helpers/javascript_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module JavascriptHelper
def page_specific_javascript_tag(js)
javascript_include_tag asset_path(js)
diff --git a/app/helpers/kerberos_spnego_helper.rb b/app/helpers/kerberos_spnego_helper.rb
index f5b0aa7549a..c0eb8f83f56 100644
--- a/app/helpers/kerberos_spnego_helper.rb
+++ b/app/helpers/kerberos_spnego_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module KerberosSpnegoHelper
def allow_basic_auth?
true # different behavior in GitLab Enterprise Edition
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index c7df25cecef..6c51739ba1a 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module LabelsHelper
extend self
include ActionView::Helpers::TagHelper
diff --git a/app/helpers/lazy_image_tag_helper.rb b/app/helpers/lazy_image_tag_helper.rb
index 603b9438e35..ac987a04895 100644
--- a/app/helpers/lazy_image_tag_helper.rb
+++ b/app/helpers/lazy_image_tag_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module LazyImageTagHelper
def placeholder_image
"data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
@@ -11,9 +13,11 @@ module LazyImageTagHelper
options[:data] ||= {}
options[:data][:src] = path_to_image(source)
- options[:class] ||= ""
- options[:class] << " lazy"
+ # options[:class] can be either String or Array.
+ klass_opts = Array.wrap(options[:class])
+ klass_opts << "lazy"
+ options[:class] = klass_opts.join(' ')
source = placeholder_image
end
diff --git a/app/helpers/markup_helper.rb b/app/helpers/markup_helper.rb
index cbb971cf8b7..0d638b850b4 100644
--- a/app/helpers/markup_helper.rb
+++ b/app/helpers/markup_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'nokogiri'
module MarkupHelper
@@ -72,14 +74,21 @@ module MarkupHelper
# the tag contents are truncated without removing the closing tag.
def first_line_in_markdown(object, attribute, max_chars = nil, options = {})
md = markdown_field(object, attribute, options)
+ return nil unless md.present?
- text = truncate_visible(md, max_chars || md.length) if md.present?
+ tags = %w(a gl-emoji b pre code p span)
+ tags << 'img' if options[:allow_images]
- sanitize(
+ text = truncate_visible(md, max_chars || md.length)
+ text = sanitize(
text,
- tags: %w(a img gl-emoji b pre code p span),
+ tags: tags,
attributes: Rails::Html::WhiteListSanitizer.allowed_attributes + ['style', 'data-src', 'data-name', 'data-unicode-version']
)
+
+ # since <img> tags are stripped, this can leave empty <a> tags hanging around
+ # (as our markdown wraps images in links)
+ options[:allow_images] ? text : strip_empty_link_tags(text).html_safe
end
def markdown(text, context = {})
@@ -107,23 +116,23 @@ module MarkupHelper
def markup(file_name, text, context = {})
context[:project] ||= @project
- context[:markdown_engine] ||= :redcarpet
+ context[:markdown_engine] ||= :redcarpet unless commonmark_for_repositories_enabled?
html = context.delete(:rendered) || markup_unsafe(file_name, text, context)
prepare_for_rendering(html, context)
end
- def render_wiki_content(wiki_page)
+ def render_wiki_content(wiki_page, context = {})
text = wiki_page.content
return '' unless text.present?
- context = {
+ context.merge!(
pipeline: :wiki,
project: @project,
project_wiki: @project_wiki,
page_slug: wiki_page.slug,
- issuable_state_filter_enabled: true,
- markdown_engine: :redcarpet
- }
+ issuable_state_filter_enabled: true
+ )
+ context[:markdown_engine] ||= :redcarpet unless commonmark_for_repositories_enabled?
html =
case wiki_page.format
@@ -178,6 +187,10 @@ module MarkupHelper
end
end
+ def commonmark_for_repositories_enabled?
+ Feature.enabled?(:commonmark_for_repositories, default_enabled: true)
+ end
+
private
# Return +text+, truncated to +max_chars+ characters, excluding any HTML
@@ -229,6 +242,16 @@ module MarkupHelper
end
end
+ def strip_empty_link_tags(text)
+ scrubber = Loofah::Scrubber.new do |node|
+ node.remove if node.name == 'a' && node.content.blank?
+ end
+
+ # Use `Loofah` directly instead of `sanitize`
+ # as we still use the `rails-deprecated_sanitizer` gem
+ Loofah.fragment(text).scrub!(scrubber).to_s
+ end
+
def markdown_toolbar_button(options = {})
data = options[:data].merge({ container: 'body' })
content_tag :button,
diff --git a/app/helpers/mattermost_helper.rb b/app/helpers/mattermost_helper.rb
index 27ff4051c8d..b211fe5076a 100644
--- a/app/helpers/mattermost_helper.rb
+++ b/app/helpers/mattermost_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module MattermostHelper
def mattermost_teams_options(teams)
teams.map do |team|
diff --git a/app/helpers/members_helper.rb b/app/helpers/members_helper.rb
index a3129cac2b1..5a21403bc5e 100644
--- a/app/helpers/members_helper.rb
+++ b/app/helpers/members_helper.rb
@@ -1,8 +1,10 @@
+# frozen_string_literal: true
+
module MembersHelper
def remove_member_message(member, user: nil)
user = current_user if defined?(current_user)
+ text = 'Are you sure you want to'
- text = 'Are you sure you want to '
action =
if member.request?
if member.user == user
@@ -16,13 +18,12 @@ module MembersHelper
"remove #{member.user.name} from"
end
- text << action << " the #{member.source.human_name} #{member.real_source_type.humanize(capitalize: false)}?"
+ "#{text} #{action} the #{member.source.human_name} #{member.real_source_type.humanize(capitalize: false)}?"
end
def remove_member_title(member)
- text = " from #{member.real_source_type.humanize(capitalize: false)}"
-
- text.prepend(member.request? ? 'Deny access request' : 'Remove user')
+ action = member.request? ? 'Deny access request' : 'Remove user'
+ "#{action} from #{member.real_source_type.humanize(capitalize: false)}"
end
def leave_confirmation_message(member_source)
@@ -32,9 +33,6 @@ module MembersHelper
def filter_group_project_member_path(options = {})
options = params.slice(:search, :sort).merge(options)
-
- path = request.path
- path << "?#{options.to_param}"
- path
+ "#{request.path}?#{options.to_param}"
end
end
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index 097be8a0643..87af6fb08f0 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module MergeRequestsHelper
def new_mr_path_from_push_event(event)
target_project = event.project.default_merge_request_target
@@ -19,10 +21,10 @@ module MergeRequestsHelper
end
def mr_css_classes(mr)
- classes = "merge-request"
- classes << " closed" if mr.closed?
- classes << " merged" if mr.merged?
- classes
+ classes = ["merge-request"]
+ classes << "closed" if mr.closed?
+ classes << "merged" if mr.merged?
+ classes.join(' ')
end
def ci_build_details_path(merge_request)
diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb
index 95da8f00aff..94a030d9d57 100644
--- a/app/helpers/milestones_helper.rb
+++ b/app/helpers/milestones_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module MilestonesHelper
include EntityDateHelper
@@ -51,6 +53,7 @@ module MilestonesHelper
# Returns count of milestones for different states
# Uses explicit hash keys as the 'opened' state URL params differs from the db value
# and we need to add the total
+ # rubocop: disable CodeReuse/ActiveRecord
def milestone_counts(milestones)
counts = milestones.reorder(nil).group(:state).count
@@ -60,6 +63,7 @@ module MilestonesHelper
all: counts.values.sum || 0
}
end
+ # rubocop: enable CodeReuse/ActiveRecord
# Show 'active' class if provided GET param matches check
# `or_blank` allows the function to return 'active' when given an empty param
@@ -119,20 +123,18 @@ module MilestonesHelper
title = date_type == :start ? "Start date" : "End date"
if date
- time_ago = time_ago_in_words(date)
- time_ago.slice!("about ")
-
- time_ago << if date.past?
- " ago"
- else
- " remaining"
- end
+ time_ago = time_ago_in_words(date).sub("about ", "")
+ state = if date.past?
+ "ago"
+ else
+ "remaining"
+ end
content = [
title,
"<br />",
date.to_s(:medium),
- "(#{time_ago})"
+ "(#{time_ago} #{state})"
].join(" ")
content.html_safe
diff --git a/app/helpers/milestones_routing_helper.rb b/app/helpers/milestones_routing_helper.rb
index a0b2616f224..a49b561533a 100644
--- a/app/helpers/milestones_routing_helper.rb
+++ b/app/helpers/milestones_routing_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module MilestonesRoutingHelper
def milestone_path(milestone, *args)
if milestone.group_milestone?
diff --git a/app/helpers/mirror_helper.rb b/app/helpers/mirror_helper.rb
index 93ed22513ac..a4025730397 100644
--- a/app/helpers/mirror_helper.rb
+++ b/app/helpers/mirror_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module MirrorHelper
def mirrors_form_data_attributes
{ project_mirror_endpoint: project_mirror_path(@project) }
diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb
index 6535afb6425..6c65e573307 100644
--- a/app/helpers/namespaces_helper.rb
+++ b/app/helpers/namespaces_helper.rb
@@ -1,8 +1,11 @@
+# frozen_string_literal: true
+
module NamespacesHelper
def namespace_id_from(params)
params.dig(:project, :namespace_id) || params[:namespace_id]
end
+ # rubocop: disable CodeReuse/ActiveRecord
def namespaces_options(selected = :current_user, display_path: false, groups: nil, extra_group: nil, groups_only: false)
groups ||= current_user.manageable_groups
.eager_load(:route)
@@ -40,6 +43,7 @@ module NamespacesHelper
grouped_options_for_select(options, selected_id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def namespace_icon(namespace, size = 40)
if namespace.is_a?(Group)
@@ -53,6 +57,7 @@ module NamespacesHelper
# Many importers create a temporary Group, so use the real
# group if one exists by that name to prevent duplicates.
+ # rubocop: disable CodeReuse/ActiveRecord
def dedup_extra_group(extra_group)
unless extra_group.persisted?
existing_group = Group.find_by(path: extra_group.path)
@@ -61,6 +66,7 @@ module NamespacesHelper
extra_group
end
+ # rubocop: enable CodeReuse/ActiveRecord
def options_for_group(namespaces, display_path:, type:)
group_label = type.pluralize
diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb
index a84a39235d8..761f42f2f0f 100644
--- a/app/helpers/nav_helper.rb
+++ b/app/helpers/nav_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module NavHelper
def header_links
@header_links ||= get_header_links
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index 5404ead44f3..a80c8f273a8 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module NotesHelper
def note_target_fields(note)
if note.noteable
@@ -108,7 +110,7 @@ module NotesHelper
end
def noteable_note_url(note)
- Gitlab::UrlBuilder.build(note)
+ Gitlab::UrlBuilder.build(note) if note.id
end
def form_resources
diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb
index a185f2916d4..5318ab4ddef 100644
--- a/app/helpers/notifications_helper.rb
+++ b/app/helpers/notifications_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module NotificationsHelper
include IconsHelper
diff --git a/app/helpers/numbers_helper.rb b/app/helpers/numbers_helper.rb
index 45bd3606076..3c0b11c4d32 100644
--- a/app/helpers/numbers_helper.rb
+++ b/app/helpers/numbers_helper.rb
@@ -1,4 +1,7 @@
+# frozen_string_literal: true
+
module NumbersHelper
+ # rubocop: disable CodeReuse/ActiveRecord
def limited_counter_with_delimiter(resource, **options)
limit = options.fetch(:limit, 1000).to_i
count = resource.limit(limit + 1).count(:all)
@@ -8,4 +11,5 @@ module NumbersHelper
number_with_delimiter(count, options)
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb
index 68d892393ef..b33c074d1af 100644
--- a/app/helpers/page_layout_helper.rb
+++ b/app/helpers/page_layout_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module PageLayoutHelper
def page_title(*titles)
@page_title ||= []
@@ -65,14 +67,14 @@ module PageLayoutHelper
end
def page_card_meta_tags
- tags = ''
+ tags = []
page_card_attributes.each_with_index do |pair, i|
tags << tag(:meta, property: "twitter:label#{i + 1}", content: pair[0])
tags << tag(:meta, property: "twitter:data#{i + 1}", content: pair[1])
end
- tags.html_safe
+ tags.join.html_safe
end
def header_title(title = nil, title_url = nil)
@@ -115,16 +117,16 @@ module PageLayoutHelper
end
def container_class
- css_class = "container-fluid"
+ css_class = ["container-fluid"]
unless fluid_layout
- css_class += " container-limited"
+ css_class << "container-limited"
end
if blank_container
- css_class += " container-blank"
+ css_class << "container-blank"
end
- css_class
+ css_class.join(' ')
end
end
diff --git a/app/helpers/pagination_helper.rb b/app/helpers/pagination_helper.rb
index 83dd76a01dd..d05153c9d4b 100644
--- a/app/helpers/pagination_helper.rb
+++ b/app/helpers/pagination_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module PaginationHelper
def paginate_collection(collection, remote: nil)
if collection.is_a?(Kaminari::PaginatableWithoutCount)
diff --git a/app/helpers/performance_bar_helper.rb b/app/helpers/performance_bar_helper.rb
index d24efe37f5f..7518cec160c 100644
--- a/app/helpers/performance_bar_helper.rb
+++ b/app/helpers/performance_bar_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module PerformanceBarHelper
# This is a hack since using `alias_method :performance_bar_enabled?, :peek_enabled?`
# in WithPerformanceBar breaks tests (but works in the browser).
diff --git a/app/helpers/pipeline_schedules_helper.rb b/app/helpers/pipeline_schedules_helper.rb
index 4b9f6bd2caf..0e166106b32 100644
--- a/app/helpers/pipeline_schedules_helper.rb
+++ b/app/helpers/pipeline_schedules_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module PipelineSchedulesHelper
def timezone_data
ActiveSupport::TimeZone.all.map do |timezone|
diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb
index fb523cb865b..ff9842d4cd9 100644
--- a/app/helpers/preferences_helper.rb
+++ b/app/helpers/preferences_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Helper methods for per-User preferences
module PreferencesHelper
def layout_choices
diff --git a/app/helpers/profiles_helper.rb b/app/helpers/profiles_helper.rb
index e7aa92e6e5c..55674e37a34 100644
--- a/app/helpers/profiles_helper.rb
+++ b/app/helpers/profiles_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ProfilesHelper
def attribute_provider_label(attribute)
user_synced_attributes_metadata = current_user.user_synced_attributes_metadata
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 18b3badda8d..8b17e6ef75d 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ProjectsHelper
def link_to_project(project)
link_to [project.namespace.becomes(Namespace), project], title: h(project.name) do
@@ -50,7 +52,7 @@ module ProjectsHelper
return "(deleted)" unless author
- author_html = ""
+ author_html = []
# Build avatar image tag
author_html << link_to_member_avatar(author, opts) if opts[:avatar]
@@ -60,7 +62,7 @@ module ProjectsHelper
author_html << capture(&block) if block
- author_html = author_html.html_safe
+ author_html = author_html.join.html_safe
if opts[:name]
link_to(author_html, user_path(author), class: "author-link #{"#{opts[:extra_class]}" if opts[:extra_class]} #{"#{opts[:mobile_classes]}" if opts[:mobile_classes]}").html_safe
@@ -80,15 +82,8 @@ module ProjectsHelper
end
project_link = link_to project_path(project) do
- output =
- if project.avatar_url && !Rails.env.test?
- project_icon(project, alt: project.name, class: 'avatar-tile', width: 15, height: 15)
- else
- ""
- end
-
- output << content_tag("span", simple_sanitize(project.name), class: "breadcrumb-item-text js-breadcrumb-item-text")
- output.html_safe
+ icon = project_icon(project, alt: project.name, class: 'avatar-tile', width: 15, height: 15) if project.avatar_url && !Rails.env.test?
+ [icon, content_tag("span", simple_sanitize(project.name), class: "breadcrumb-item-text js-breadcrumb-item-text")].join.html_safe
end
namespace_link = breadcrumb_list_item(namespace_link) unless project.group
@@ -203,6 +198,14 @@ module ProjectsHelper
current_user.require_extra_setup_for_git_auth?
end
+ def show_auto_devops_implicitly_enabled_banner?(project)
+ cookie_key = "hide_auto_devops_implicitly_enabled_banner_#{project.id}"
+
+ project.has_auto_devops_implicitly_enabled? &&
+ cookies[cookie_key.to_sym].blank? &&
+ (project.owner == current_user || project.team.maintainer?(current_user))
+ end
+
def link_to_set_password
if current_user.require_password_creation_for_git?
link_to s_('SetPasswordToCloneLink|set a password'), edit_profile_password_path
@@ -219,6 +222,7 @@ module ProjectsHelper
#
# If no limit is applied we'll just issue a COUNT since the result set could
# be too large to load into memory.
+ # rubocop: disable CodeReuse/ActiveRecord
def any_projects?(projects)
return projects.any? if projects.is_a?(Array)
@@ -228,6 +232,7 @@ module ProjectsHelper
projects.except(:offset).any?
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def show_projects?(projects, params)
!!(params[:personal] || params[:name] || any_projects?(projects))
@@ -252,6 +257,10 @@ module ProjectsHelper
"xcode://clone?repo=#{CGI.escape(default_url_to_repo(project))}"
end
+ def legacy_render_context(params)
+ params[:legacy_render] ? { markdown_engine: :redcarpet } : {}
+ end
+
private
def get_project_nav_tabs(project, current_user)
@@ -351,6 +360,10 @@ module ProjectsHelper
end
end
+ def default_clone_label
+ _("Copy %{protocol} clone URL") % { protocol: default_clone_protocol.upcase }
+ end
+
def default_clone_protocol
if allowed_protocols_present?
enabled_protocol
diff --git a/app/helpers/repository_languages_helper.rb b/app/helpers/repository_languages_helper.rb
index 9a842cf5ce0..c1505b52808 100644
--- a/app/helpers/repository_languages_helper.rb
+++ b/app/helpers/repository_languages_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module RepositoryLanguagesHelper
def repository_languages_bar(languages)
return if languages.none?
diff --git a/app/helpers/rss_helper.rb b/app/helpers/rss_helper.rb
index 7d4fa83a67a..67c7d244f11 100644
--- a/app/helpers/rss_helper.rb
+++ b/app/helpers/rss_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module RssHelper
def rss_url_options
{ format: :atom, feed_token: current_user.try(:feed_token) }
diff --git a/app/helpers/runners_helper.rb b/app/helpers/runners_helper.rb
index 9fb42487a75..cb21f922401 100644
--- a/app/helpers/runners_helper.rb
+++ b/app/helpers/runners_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module RunnersHelper
def runner_status_icon(runner)
status = runner.status
diff --git a/app/helpers/safe_params_helper.rb b/app/helpers/safe_params_helper.rb
index b568e8810cc..18bbf3347a8 100644
--- a/app/helpers/safe_params_helper.rb
+++ b/app/helpers/safe_params_helper.rb
@@ -1,6 +1,9 @@
+# frozen_string_literal: true
+
module SafeParamsHelper
# Rails 5.0 requires to permit `params` if they're used in url helpers.
# Use this helper when generating links with `params.merge(...)`
+ # rubocop: disable CodeReuse/ActiveRecord
def safe_params
if params.respond_to?(:permit!)
params.except(:host, :port, :protocol).permit!
@@ -8,4 +11,5 @@ module SafeParamsHelper
params
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 98074a4c0c5..4f9e1322b56 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SearchHelper
def search_autocomplete_opts(term)
return unless current_user
@@ -99,6 +101,7 @@ module SearchHelper
end
# Autocomplete results for the current user's groups
+ # rubocop: disable CodeReuse/ActiveRecord
def groups_autocomplete(term, limit = 5)
current_user.authorized_groups.order_id_desc.search(term).limit(limit).map do |group|
{
@@ -110,8 +113,10 @@ module SearchHelper
}
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
# Autocomplete results for the current user's projects
+ # rubocop: disable CodeReuse/ActiveRecord
def projects_autocomplete(term, limit = 5)
current_user.authorized_projects.order_id_desc.search_by_title(term)
.sorted_by_stars.non_archived.limit(limit).map do |p|
@@ -125,6 +130,7 @@ module SearchHelper
}
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def search_result_sanitize(str)
Sanitize.clean(str)
diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb
index 6cefcde558a..cf60696ef39 100644
--- a/app/helpers/selects_helper.rb
+++ b/app/helpers/selects_helper.rb
@@ -1,12 +1,14 @@
+# frozen_string_literal: true
+
module SelectsHelper
def users_select_tag(id, opts = {})
- css_class = "ajax-users-select "
- css_class << "multiselect " if opts[:multiple]
- css_class << "skip_ldap " if opts[:skip_ldap]
+ css_class = ["ajax-users-select"]
+ css_class << "multiselect" if opts[:multiple]
+ css_class << "skip_ldap" if opts[:skip_ldap]
css_class << (opts[:class] || '')
value = opts[:selected] || ''
html = {
- class: css_class,
+ class: css_class.join(' '),
data: users_select_data_attributes(opts)
}
@@ -24,20 +26,21 @@ module SelectsHelper
end
def groups_select_tag(id, opts = {})
- opts[:class] ||= ''
- opts[:class] << ' ajax-groups-select'
+ classes = Array.wrap(opts[:class])
+ classes << 'ajax-groups-select'
+
+ opts[:class] = classes.join(' ')
+
select2_tag(id, opts)
end
def namespace_select_tag(id, opts = {})
- opts[:class] ||= ''
- opts[:class] << ' ajax-namespace-select'
+ opts[:class] = [*opts[:class], 'ajax-namespace-select'].join(' ')
select2_tag(id, opts)
end
def project_select_tag(id, opts = {})
- opts[:class] ||= ''
- opts[:class] << ' ajax-project-select'
+ opts[:class] = [*opts[:class], 'ajax-project-select'].join(' ')
unless opts.delete(:scope) == :all
if @group
@@ -57,7 +60,10 @@ module SelectsHelper
end
def select2_tag(id, opts = {})
- opts[:class] << ' multiselect' if opts[:multiple]
+ klass_opts = [opts[:class]]
+ klass_opts << 'multiselect' if opts[:multiple]
+
+ opts[:class] = klass_opts.join(' ')
value = opts[:selected] || ''
hidden_field_tag(id, value, opts)
diff --git a/app/helpers/sentry_helper.rb b/app/helpers/sentry_helper.rb
index 3d255df66a0..d53eaef9952 100644
--- a/app/helpers/sentry_helper.rb
+++ b/app/helpers/sentry_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SentryHelper
def sentry_enabled?
Gitlab::Sentry.enabled?
diff --git a/app/helpers/services_helper.rb b/app/helpers/services_helper.rb
index f872990122e..d4b50b7ecfb 100644
--- a/app/helpers/services_helper.rb
+++ b/app/helpers/services_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ServicesHelper
def service_event_description(event)
case event
@@ -30,7 +32,7 @@ module ServicesHelper
end
def service_save_button(service)
- button_tag(class: 'btn btn-save', type: 'submit', disabled: service.deprecated?) do
+ button_tag(class: 'btn btn-success', type: 'submit', disabled: service.deprecated?) do
icon('spinner spin', class: 'hidden js-btn-spinner') +
content_tag(:span, 'Save changes', class: 'js-btn-label')
end
diff --git a/app/helpers/sidekiq_helper.rb b/app/helpers/sidekiq_helper.rb
index 50aeb7f4b82..32bf3526571 100644
--- a/app/helpers/sidekiq_helper.rb
+++ b/app/helpers/sidekiq_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SidekiqHelper
SIDEKIQ_PS_REGEXP = %r{\A
(?<pid>\d+)\s+
diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb
index a05640773ad..c7d31f3469d 100644
--- a/app/helpers/snippets_helper.rb
+++ b/app/helpers/snippets_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SnippetsHelper
def reliable_snippet_path(snippet, opts = nil)
if snippet.project_id?
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index 36a311dfa8a..53bd43d4861 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SortingHelper
def sort_options_hash
{
@@ -22,7 +24,8 @@ module SortingHelper
sort_value_recently_updated => sort_title_recently_updated,
sort_value_popularity => sort_title_popularity,
sort_value_priority => sort_title_priority,
- sort_value_upvotes => sort_title_upvotes
+ sort_value_upvotes => sort_title_upvotes,
+ sort_value_contacted_date => sort_title_contacted_date
}
end
@@ -32,7 +35,8 @@ module SortingHelper
sort_value_name => sort_title_name,
sort_value_oldest_activity => sort_title_oldest_activity,
sort_value_oldest_created => sort_title_oldest_created,
- sort_value_recently_created => sort_title_recently_created
+ sort_value_recently_created => sort_title_recently_created,
+ sort_value_most_stars => sort_title_most_stars
}
if current_controller?('admin/projects')
@@ -99,6 +103,17 @@ module SortingHelper
}
end
+ def label_sort_options_hash
+ {
+ sort_value_name => sort_title_name,
+ sort_value_name_desc => sort_title_name_desc,
+ sort_value_recently_created => sort_title_recently_created,
+ sort_value_oldest_created => sort_title_oldest_created,
+ sort_value_recently_updated => sort_title_recently_updated,
+ sort_value_oldest_updated => sort_title_oldest_updated
+ }
+ end
+
def sortable_item(item, path, sorted_by)
link_to item, path, class: sorted_by == item ? 'is-active' : ''
end
@@ -228,6 +243,14 @@ module SortingHelper
s_('SortOptions|Most popular')
end
+ def sort_title_contacted_date
+ s_('SortOptions|Last Contact')
+ end
+
+ def sort_title_most_stars
+ s_('SortOptions|Most stars')
+ end
+
# Values.
def sort_value_access_level_asc
'access_level_asc'
@@ -348,4 +371,12 @@ module SortingHelper
def sort_value_upvotes
'upvotes_desc'
end
+
+ def sort_value_contacted_date
+ 'contacted_asc'
+ end
+
+ def sort_value_most_stars
+ 'stars_desc'
+ end
end
diff --git a/app/helpers/storage_health_helper.rb b/app/helpers/storage_health_helper.rb
index b76c1228220..182e8e6641b 100644
--- a/app/helpers/storage_health_helper.rb
+++ b/app/helpers/storage_health_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module StorageHealthHelper
def failing_storage_health_message(storage_health)
storage_name = content_tag(:strong, h(storage_health.storage_name))
diff --git a/app/helpers/storage_helper.rb b/app/helpers/storage_helper.rb
index e19c67a37ca..be8761db562 100644
--- a/app/helpers/storage_helper.rb
+++ b/app/helpers/storage_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module StorageHelper
def storage_counter(size_in_bytes)
precision = size_in_bytes < 1.megabyte ? 0 : 1
diff --git a/app/helpers/submodule_helper.rb b/app/helpers/submodule_helper.rb
index ec2cf2b16c0..164c69ca50b 100644
--- a/app/helpers/submodule_helper.rb
+++ b/app/helpers/submodule_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SubmoduleHelper
extend self
diff --git a/app/helpers/system_note_helper.rb b/app/helpers/system_note_helper.rb
index 5b4a141dbcf..ac4e8f54260 100644
--- a/app/helpers/system_note_helper.rb
+++ b/app/helpers/system_note_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SystemNoteHelper
ICON_NAMES_BY_ACTION = {
'commit' => 'commit',
@@ -21,7 +23,8 @@ module SystemNoteHelper
'outdated' => 'pencil-square',
'duplicate' => 'issue-duplicate',
'locked' => 'lock',
- 'unlocked' => 'lock-open'
+ 'unlocked' => 'lock-open',
+ 'due_date' => 'calendar'
}.freeze
def system_note_icon_name(note)
diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb
index ee701076a14..d91f0f78db7 100644
--- a/app/helpers/tab_helper.rb
+++ b/app/helpers/tab_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module TabHelper
# Navigation link helper
#
@@ -6,7 +8,7 @@ module TabHelper
# element is the value passed to the block.
#
# options - The options hash used to determine if the element is "active" (default: {})
- # :controller - One or more controller names to check (optional).
+ # :controller - One or more controller names to check, use path notation when namespaced (optional).
# :action - One or more action names to check (optional).
# :path - A shorthand path, such as 'dashboard#index', to check (optional).
# :html_options - Extra options to be passed to the list element (optional).
@@ -40,6 +42,20 @@ module TabHelper
# nav_link(controller: :tree, html_options: {class: 'home'}) { "Hello" }
# # => '<li class="home active">Hello</li>'
#
+ # # For namespaced controllers like Admin::AppearancesController#show
+ #
+ # # Controller and namespace matches
+ # nav_link(controller: 'admin/appearances') { "Hello" }
+ # # => '<li class="active">Hello</li>'
+ #
+ # # Controller and namespace matches but action doesn't
+ # nav_link(controller: 'admin/appearances', action: :edit) { "Hello" }
+ # # => '<li>Hello</li>'
+ #
+ # # Shorthand path with namespace
+ # nav_link(path: 'admin/appearances#show') { "Hello"}
+ # # => '<li class="active">Hello</li>'
+ #
# Returns a list item element String
def nav_link(options = {}, &block)
klass = active_nav_link?(options) ? 'active' : ''
@@ -47,9 +63,7 @@ module TabHelper
# Add our custom class into the html_options, which may or may not exist
# and which may or may not already have a :class key
o = options.delete(:html_options) || {}
- o[:class] ||= ''
- o[:class] += ' ' + klass
- o[:class].strip!
+ o[:class] = [*o[:class], klass].join(' ').strip
if block_given?
content_tag(:li, capture(&block), o)
diff --git a/app/helpers/tags_helper.rb b/app/helpers/tags_helper.rb
index d000d6b1c0a..de0b92b6fd7 100644
--- a/app/helpers/tags_helper.rb
+++ b/app/helpers/tags_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module TagsHelper
def tag_path(tag)
"/tags/#{tag}"
@@ -14,12 +16,13 @@ module TagsHelper
end
def tag_list(project)
- html = ''
+ html = []
+
project.tag_list.each do |tag|
html << link_to(tag, tag_path(tag))
end
- html.html_safe
+ html.join.html_safe
end
def protected_tag?(project, tag)
diff --git a/app/helpers/time_helper.rb b/app/helpers/time_helper.rb
index 336385f6798..94044d7b85e 100644
--- a/app/helpers/time_helper.rb
+++ b/app/helpers/time_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module TimeHelper
def time_interval_in_words(interval_in_seconds)
interval_in_seconds = interval_in_seconds.to_i
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index 7cd74358168..6bd78336ed3 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module TodosHelper
def todos_pending_count
@todos_pending_count ||= current_user.todos_pending_count
@@ -94,9 +96,7 @@ module TodosHelper
end
end
- path = request.path
- path << "?#{options.to_param}"
- path
+ "#{request.path}?#{options.to_param}"
end
def todo_actions_options
@@ -152,10 +152,11 @@ module TodosHelper
''
end
- html = "&middot; ".html_safe
- html << content_tag(:span, class: css_class) do
+ content = content_tag(:span, class: css_class) do
"Due #{is_due_today ? "today" : todo.target.due_date.to_s(:medium)}"
end
+
+ "&middot; #{content}".html_safe
end
private
diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb
index dc42caa70e5..6d2da5699fb 100644
--- a/app/helpers/tree_helper.rb
+++ b/app/helpers/tree_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module TreeHelper
FILE_LIMIT = 1_000
@@ -5,10 +7,11 @@ module TreeHelper
# their corresponding partials
#
# tree - A `Tree` object for the current tree
+ # rubocop: disable CodeReuse/ActiveRecord
def render_tree(tree)
# Sort submodules and folders together by name ahead of files
folders, files, submodules = tree.trees, tree.blobs, tree.submodules
- tree = ''
+ tree = []
items = (folders + submodules).sort_by(&:name) + files
if items.size > FILE_LIMIT
@@ -18,8 +21,9 @@ module TreeHelper
end
tree << render(partial: 'projects/tree/tree_row', collection: items) if items.present?
- tree.html_safe
+ tree.join.html_safe
end
+ # rubocop: enable CodeReuse/ActiveRecord
# Return an image icon depending on the file type and mode
#
@@ -122,6 +126,7 @@ module TreeHelper
end
# returns the relative path of the first subdir that doesn't have only one directory descendant
+ # rubocop: disable CodeReuse/ActiveRecord
def flatten_tree(root_path, tree)
return tree.flat_path.sub(%r{\A#{Regexp.escape(root_path)}/}, '') if tree.flat_path.present?
@@ -132,6 +137,7 @@ module TreeHelper
return tree.name
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def selected_branch
@branch_name || tree_edit_branch
diff --git a/app/helpers/triggers_helper.rb b/app/helpers/triggers_helper.rb
index ce435ca2241..5cfdc0971f0 100644
--- a/app/helpers/triggers_helper.rb
+++ b/app/helpers/triggers_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module TriggersHelper
def builds_trigger_url(project_id, ref: nil)
if ref.nil?
diff --git a/app/helpers/user_callouts_helper.rb b/app/helpers/user_callouts_helper.rb
index da5fe25c07d..bae01d476df 100644
--- a/app/helpers/user_callouts_helper.rb
+++ b/app/helpers/user_callouts_helper.rb
@@ -1,6 +1,9 @@
+# frozen_string_literal: true
+
module UserCalloutsHelper
GKE_CLUSTER_INTEGRATION = 'gke_cluster_integration'.freeze
GCP_SIGNUP_OFFER = 'gcp_signup_offer'.freeze
+ CLUSTER_SECURITY_WARNING = 'cluster_security_warning'.freeze
def show_gke_cluster_integration_callout?(project)
can?(current_user, :create_cluster, project) &&
@@ -11,6 +14,10 @@ module UserCalloutsHelper
!user_dismissed?(GCP_SIGNUP_OFFER)
end
+ def show_cluster_security_warning?
+ !user_dismissed?(CLUSTER_SECURITY_WARNING)
+ end
+
private
def user_dismissed?(feature_name)
diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb
index 2c0c4254a0c..bcd91f619c8 100644
--- a/app/helpers/users_helper.rb
+++ b/app/helpers/users_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module UsersHelper
def user_link(user)
link_to(user.name, user_path(user),
diff --git a/app/helpers/version_check_helper.rb b/app/helpers/version_check_helper.rb
index c20753ece72..75637eb0676 100644
--- a/app/helpers/version_check_helper.rb
+++ b/app/helpers/version_check_helper.rb
@@ -1,8 +1,12 @@
+# frozen_string_literal: true
+
module VersionCheckHelper
def version_status_badge
- if Rails.env.production? && Gitlab::CurrentSettings.version_check_enabled
- image_url = VersionCheck.new.url
- image_tag image_url, class: 'js-version-status-badge'
- end
+ return unless Rails.env.production?
+ return unless Gitlab::CurrentSettings.version_check_enabled
+ return if User.single_user&.requires_usage_stats_consent?
+
+ image_url = VersionCheck.new.url
+ image_tag image_url, class: 'js-version-status-badge'
end
end
diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb
index cf2fe5a2019..e690350a0d1 100644
--- a/app/helpers/visibility_level_helper.rb
+++ b/app/helpers/visibility_level_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module VisibilityLevelHelper
def visibility_level_color(level)
case level
@@ -82,7 +84,7 @@ module VisibilityLevelHelper
def disallowed_project_visibility_level_description(level, project)
level_name = Gitlab::VisibilityLevel.level_name(level).downcase
reasons = []
- instructions = ''
+ instructions = []
unless project.visibility_level_allowed_as_fork?(level)
reasons << "the fork source project has lower visibility"
@@ -96,7 +98,7 @@ module VisibilityLevelHelper
end
reasons = reasons.any? ? ' because ' + reasons.to_sentence : ''
- "This project cannot be #{level_name}#{reasons}.#{instructions}".html_safe
+ "This project cannot be #{level_name}#{reasons}.#{instructions.join}".html_safe
end
# Note: these messages closely mirror the form validation strings found in the group
@@ -104,7 +106,7 @@ module VisibilityLevelHelper
def disallowed_group_visibility_level_description(level, group)
level_name = Gitlab::VisibilityLevel.level_name(level).downcase
reasons = []
- instructions = ''
+ instructions = []
unless group.visibility_level_allowed_by_projects?(level)
reasons << "it contains projects with higher visibility"
@@ -122,7 +124,7 @@ module VisibilityLevelHelper
end
reasons = reasons.any? ? ' because ' + reasons.to_sentence : ''
- "This group cannot be #{level_name}#{reasons}.#{instructions}".html_safe
+ "This group cannot be #{level_name}#{reasons}.#{instructions.join}".html_safe
end
def visibility_icon_description(form_model)
@@ -138,7 +140,7 @@ module VisibilityLevelHelper
end
def project_visibility_icon_description(level)
- "#{visibility_level_label(level)} - #{project_visibility_level_description(level)}"
+ "#{project_visibility_level_description(level)}"
end
def visibility_level_label(level)
diff --git a/app/helpers/webpack_helper.rb b/app/helpers/webpack_helper.rb
index 72f6b397046..345ddcf023a 100644
--- a/app/helpers/webpack_helper.rb
+++ b/app/helpers/webpack_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module WebpackHelper
def webpack_bundle_tag(bundle)
javascript_include_tag(*webpack_entrypoint_paths(bundle))
diff --git a/app/helpers/wiki_helper.rb b/app/helpers/wiki_helper.rb
index 17940aeb900..647f34e57ed 100644
--- a/app/helpers/wiki_helper.rb
+++ b/app/helpers/wiki_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module WikiHelper
include API::Helpers::RelatedResourcesHelpers
diff --git a/app/helpers/workhorse_helper.rb b/app/helpers/workhorse_helper.rb
index fd1d78bd9b8..f19445fca1a 100644
--- a/app/helpers/workhorse_helper.rb
+++ b/app/helpers/workhorse_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Helpers to send Git blobs, diffs, patches or archives through Workhorse.
# Workhorse will also serve files when using `send_file`.
module WorkhorseHelper
diff --git a/app/mailers/emails/auto_devops.rb b/app/mailers/emails/auto_devops.rb
new file mode 100644
index 00000000000..9705a3052d4
--- /dev/null
+++ b/app/mailers/emails/auto_devops.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Emails
+ module AutoDevops
+ def autodevops_disabled_email(pipeline, recipient)
+ @pipeline = pipeline
+ @project = pipeline.project
+
+ add_project_headers
+
+ mail(to: recipient,
+ subject: auto_devops_disabled_subject(@project.name)) do |format|
+ format.html { render layout: 'mailer' }
+ format.text { render layout: 'mailer' }
+ end
+ end
+
+ private
+
+ def auto_devops_disabled_subject(project_name)
+ subject("Auto DevOps pipeline was disabled for #{project_name}")
+ end
+ end
+end
diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb
index c8b1ab5033a..602e5afe26b 100644
--- a/app/mailers/emails/issues.rb
+++ b/app/mailers/emails/issues.rb
@@ -19,6 +19,7 @@ module Emails
mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id, reason))
end
+ # rubocop: disable CodeReuse/ActiveRecord
def reassigned_issue_email(recipient_id, issue_id, previous_assignee_ids, updated_by_user_id, reason = nil)
setup_issue_mail(issue_id, recipient_id)
@@ -27,6 +28,7 @@ module Emails
mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id, reason))
end
+ # rubocop: enable CodeReuse/ActiveRecord
def closed_issue_email(recipient_id, issue_id, updated_by_user_id, reason = nil)
setup_issue_mail(issue_id, recipient_id)
diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb
index 70f65d4e58d..67af0a4eb98 100644
--- a/app/mailers/emails/merge_requests.rb
+++ b/app/mailers/emails/merge_requests.rb
@@ -22,12 +22,14 @@ module Emails
mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason))
end
+ # rubocop: disable CodeReuse/ActiveRecord
def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id, updated_by_user_id, reason = nil)
setup_merge_request_mail(merge_request_id, recipient_id)
@previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id
mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason))
end
+ # rubocop: enable CodeReuse/ActiveRecord
def relabeled_merge_request_email(recipient_id, merge_request_id, label_names, updated_by_user_id, reason = nil)
setup_merge_request_mail(merge_request_id, recipient_id)
diff --git a/app/mailers/emails/profile.rb b/app/mailers/emails/profile.rb
index 40d7b9ccd7a..2ea1aea1f51 100644
--- a/app/mailers/emails/profile.rb
+++ b/app/mailers/emails/profile.rb
@@ -9,6 +9,7 @@ module Emails
mail(to: @user.notification_email, subject: subject("Account was created for you"))
end
+ # rubocop: disable CodeReuse/ActiveRecord
def new_ssh_key_email(key_id)
@key = Key.find_by(id: key_id)
@@ -18,7 +19,9 @@ module Emails
@target_url = user_url(@user)
mail(to: @user.notification_email, subject: subject("SSH key was added to your account"))
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def new_gpg_key_email(gpg_key_id)
@gpg_key = GpgKey.find_by(id: gpg_key_id)
@@ -28,5 +31,6 @@ module Emails
@target_url = user_url(@user)
mail(to: @user.notification_email, subject: subject("GPG key was added to your account"))
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index f4eeb85270e..f7347ee61b4 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -12,6 +12,7 @@ class Notify < BaseMailer
include Emails::Profile
include Emails::Pipelines
include Emails::Members
+ include Emails::AutoDevops
helper MergeRequestsHelper
helper DiffHelper
diff --git a/app/mailers/previews/notify_preview.rb b/app/mailers/previews/notify_preview.rb
index df470930e9e..2f5b5483e9d 100644
--- a/app/mailers/previews/notify_preview.rb
+++ b/app/mailers/previews/notify_preview.rb
@@ -9,7 +9,7 @@ class NotifyPreview < ActionMailer::Preview
In this notification email, we expect to see:
- The note contents (that's what you're looking at)
- - A link to view this note on Gitlab
+ - A link to view this note on GitLab
- An explanation for why the user is receiving this notification
MD
@@ -26,7 +26,7 @@ class NotifyPreview < ActionMailer::Preview
- A line saying who started this discussion
- The note contents (that's what you're looking at)
- - A link to view this discussion on Gitlab
+ - A link to view this discussion on GitLab
- An explanation for why the user is receiving this notification
MD
@@ -44,7 +44,7 @@ class NotifyPreview < ActionMailer::Preview
- A line saying who started this discussion and on what file
- The diff
- The note contents (that's what you're looking at)
- - A link to view this discussion on Gitlab
+ - A link to view this discussion on GitLab
- An explanation for why the user is receiving this notification
MD
@@ -125,6 +125,10 @@ class NotifyPreview < ActionMailer::Preview
Notify.pipeline_failed_email(pipeline, pipeline.user.try(:email))
end
+ def autodevops_disabled_email
+ Notify.autodevops_disabled_email(pipeline, user.email).message
+ end
+
private
def project
diff --git a/app/mailers/repository_check_mailer.rb b/app/mailers/repository_check_mailer.rb
index 4bcf371cfc0..145169be8a6 100644
--- a/app/mailers/repository_check_mailer.rb
+++ b/app/mailers/repository_check_mailer.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
class RepositoryCheckMailer < BaseMailer
+ # rubocop: disable CodeReuse/ActiveRecord
def notify(failed_count)
@message =
if failed_count == 1
@@ -14,4 +15,5 @@ class RepositoryCheckMailer < BaseMailer
subject: "GitLab Admin | #{@message}"
)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 03bd7fa016e..645adddb000 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -219,6 +219,7 @@ class ApplicationSetting < ActiveRecord::Base
validate :terms_exist, if: :enforce_terms?
before_validation :ensure_uuid!
+ before_validation :strip_sentry_values
before_save :ensure_runners_registration_token
before_save :ensure_health_check_access_token
@@ -302,7 +303,8 @@ class ApplicationSetting < ActiveRecord::Base
instance_statistics_visibility_private: false,
user_default_external: false,
user_default_internal_regex: nil,
- user_show_add_ssh_key_message: true
+ user_show_add_ssh_key_message: true,
+ usage_stats_set_by_user_id: nil
}
end
@@ -381,6 +383,11 @@ class ApplicationSetting < ActiveRecord::Base
super(levels.map { |level| Gitlab::VisibilityLevel.level_value(level) })
end
+ def strip_sentry_values
+ sentry_dsn.strip! if sentry_dsn.present?
+ clientside_sentry_dsn.strip! if clientside_sentry_dsn.present?
+ end
+
def performance_bar_allowed_group
Group.find_by_id(performance_bar_allowed_group_id)
end
diff --git a/app/models/badge.rb b/app/models/badge.rb
index 7e3b6b659e4..f016654206b 100644
--- a/app/models/badge.rb
+++ b/app/models/badge.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class Badge < ActiveRecord::Base
+ include FromUnion
+
# This structure sets the placeholders that the urls
# can have. This hash also sets which action to ask when
# the placeholder is found.
diff --git a/app/models/blob_viewer/gitlab_ci_yml.rb b/app/models/blob_viewer/gitlab_ci_yml.rb
index 1a86f04b1b9..655241c2808 100644
--- a/app/models/blob_viewer/gitlab_ci_yml.rb
+++ b/app/models/blob_viewer/gitlab_ci_yml.rb
@@ -10,16 +10,16 @@ module BlobViewer
self.file_types = %i(gitlab_ci)
self.binary = false
- def validation_message
+ def validation_message(project, sha)
return @validation_message if defined?(@validation_message)
prepare!
- @validation_message = Gitlab::Ci::YamlProcessor.validation_message(blob.data)
+ @validation_message = Gitlab::Ci::YamlProcessor.validation_message(blob.data, { project: project, sha: sha })
end
- def valid?
- validation_message.blank?
+ def valid?(project, sha)
+ validation_message(project, sha).blank?
end
end
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index faa160ad6ba..63aaa0f7bcc 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -40,6 +40,7 @@ module Ci
delegate :url, to: :runner_session, prefix: true, allow_nil: true
delegate :terminal_specification, to: :runner_session, allow_nil: true
delegate :gitlab_deploy_token, to: :project
+ delegate :trigger_short_token, to: :trigger_request, allow_nil: true
##
# The "environment" field for builds is a String, and is the unexpanded name!
@@ -139,9 +140,11 @@ module Ci
end
def retry(build, current_user)
+ # rubocop: disable CodeReuse/ServiceClass
Ci::RetryBuildService
.new(build.project, current_user)
.execute(build)
+ # rubocop: enable CodeReuse/ServiceClass
end
end
@@ -224,11 +227,13 @@ module Ci
self.when == 'manual'
end
+ # rubocop: disable CodeReuse/ServiceClass
def play(current_user)
Ci::PlayBuildService
.new(project, current_user)
.execute(self)
end
+ # rubocop: enable CodeReuse/ServiceClass
def cancelable?
active? || created?
@@ -385,9 +390,11 @@ module Ci
update(coverage: coverage) if coverage.present?
end
+ # rubocop: disable CodeReuse/ServiceClass
def parse_trace_sections!
ExtractSectionsFromBuildTraceService.new(project, user).execute(self)
end
+ # rubocop: enable CodeReuse/ServiceClass
def trace
Gitlab::Ci::Trace.new(self)
@@ -647,8 +654,31 @@ module Ci
end
end
+ # Virtual deployment status depending on the environment status.
+ def deployment_status
+ return nil unless starts_environment?
+
+ if success?
+ return successful_deployment_status
+ elsif complete? && !success?
+ return :failed
+ end
+
+ :creating
+ end
+
private
+ def successful_deployment_status
+ if success? && last_deployment&.last?
+ return :last
+ elsif success? && last_deployment.present?
+ return :out_of_date
+ end
+
+ :creating
+ end
+
def each_test_report
Ci::JobArtifact::TEST_REPORT_FILE_TYPES.each do |file_type|
public_send("job_artifacts_#{file_type}").each_blob do |blob| # rubocop:disable GitlabSecurity/PublicSend
diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb
index 4853b23513c..93fc1b145b2 100644
--- a/app/models/ci/job_artifact.rb
+++ b/app/models/ci/job_artifact.rb
@@ -87,7 +87,9 @@ module Ci
end
def hashed_path?
- super || self.try(:file_location).nil?
+ return true if trace? # ArchiveLegacyTraces background migration might not have `file_location` column
+
+ super || self.file_location.nil?
end
def expire_in
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 526bf7af99b..6dac577c514 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -161,6 +161,12 @@ module Ci
PipelineNotificationWorker.perform_async(pipeline.id)
end
end
+
+ after_transition any => [:failed] do |pipeline|
+ next unless pipeline.auto_devops_source?
+
+ pipeline.run_after_commit { AutoDevops::DisableWorker.perform_async(pipeline.id) }
+ end
end
scope :internal, -> { where(source: internal_sources) }
@@ -381,10 +387,12 @@ module Ci
end
end
+ # rubocop: disable CodeReuse/ServiceClass
def retry_failed(current_user)
Ci::RetryPipelineService.new(project, current_user)
.execute(self)
end
+ # rubocop: enable CodeReuse/ServiceClass
def mark_as_processable_after_stage(stage_idx)
builds.skipped.after_stage(stage_idx).find_each(&:process)
@@ -458,7 +466,7 @@ module Ci
return @config_processor if defined?(@config_processor)
@config_processor ||= begin
- Gitlab::Ci::YamlProcessor.new(ci_yaml_file)
+ ::Gitlab::Ci::YamlProcessor.new(ci_yaml_file, { project: project, sha: sha })
rescue Gitlab::Ci::YamlProcessor::ValidationError => e
self.yaml_errors = e.message
nil
@@ -519,9 +527,11 @@ module Ci
project.notes.for_commit_id(sha)
end
+ # rubocop: disable CodeReuse/ServiceClass
def process!
Ci::ProcessPipelineService.new(project, user).execute(self)
end
+ # rubocop: enable CodeReuse/ServiceClass
def update_status
retry_optimistic_lock(self) do
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index f41955f43e7..3e815937f4b 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -7,11 +7,14 @@ module Ci
include IgnorableColumn
include RedisCacheable
include ChronicDurationAttribute
+ include FromUnion
RUNNER_QUEUE_EXPIRY_TIME = 60.minutes
ONLINE_CONTACT_TIMEOUT = 1.hour
UPDATE_DB_RUNNER_INFO_EVERY = 40.minutes
- AVAILABLE_SCOPES = %w[specific shared active paused online].freeze
+ AVAILABLE_TYPES = %w[specific shared].freeze
+ AVAILABLE_STATUSES = %w[active paused online offline].freeze
+ AVAILABLE_SCOPES = (AVAILABLE_TYPES + AVAILABLE_STATUSES).freeze
FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level maximum_timeout_human_readable].freeze
ignore_column :is_shared
@@ -29,6 +32,13 @@ module Ci
scope :active, -> { where(active: true) }
scope :paused, -> { where(active: false) }
scope :online, -> { where('contacted_at > ?', contact_time_deadline) }
+ # The following query using negation is cheaper than using `contacted_at <= ?`
+ # because there are less runners online than have been created. The
+ # resulting query is quickly finding online ones and then uses the regular
+ # indexed search and rejects the ones that are in the previous set. If we
+ # did `contacted_at <= ?` the query would effectively have to do a seq
+ # scan.
+ scope :offline, -> { where.not(id: online) }
scope :ordered, -> { order(id: :desc) }
# BACKWARD COMPATIBILITY: There are needed to maintain compatibility with `AVAILABLE_SCOPES` used by `lib/api/runners.rb`
@@ -48,21 +58,32 @@ module Ci
}
scope :owned_or_instance_wide, -> (project_id) do
- union = Gitlab::SQL::Union.new(
- [belonging_to_project(project_id), belonging_to_parent_group_of_project(project_id), instance_type],
+ from_union(
+ [
+ belonging_to_project(project_id),
+ belonging_to_parent_group_of_project(project_id),
+ instance_type
+ ],
remove_duplicates: false
)
- from("(#{union.to_sql}) ci_runners")
end
scope :assignable_for, ->(project) do
# FIXME: That `to_sql` is needed to workaround a weird Rails bug.
# Without that, placeholders would miss one and couldn't match.
+ #
+ # We use "unscoped" here so that any current Ci::Runner filters don't
+ # apply to the inner query, which is not necessary.
+ exclude_runners = unscoped { project.runners.select(:id) }.to_sql
+
where(locked: false)
- .where.not("ci_runners.id IN (#{project.runners.select(:id).to_sql})")
+ .where.not("ci_runners.id IN (#{exclude_runners})")
.project_type
end
+ scope :order_contacted_at_asc, -> { order(contacted_at: :asc) }
+ scope :order_created_at_desc, -> { order(created_at: :desc) }
+
validate :tag_constraints
validates :access_level, presence: true
validates :runner_type, presence: true
@@ -115,6 +136,14 @@ module Ci
ONLINE_CONTACT_TIMEOUT.ago
end
+ def self.order_by(order)
+ if order == 'contacted_asc'
+ order_contacted_at_asc
+ else
+ order_created_at_desc
+ end
+ end
+
def set_default_values
self.token = SecureRandom.hex(15) if self.token.blank?
end
diff --git a/app/models/ci/trigger_request.rb b/app/models/ci/trigger_request.rb
index 913936a0bcb..0b52c690e93 100644
--- a/app/models/ci/trigger_request.rb
+++ b/app/models/ci/trigger_request.rb
@@ -8,6 +8,8 @@ module Ci
belongs_to :pipeline, foreign_key: :commit_id
has_many :builds
+ delegate :short_token, to: :trigger, prefix: true, allow_nil: true
+
# We switched to Ci::PipelineVariable from Ci::TriggerRequest.variables.
# Ci::TriggerRequest doesn't save variables anymore.
validates :variables, absence: true
diff --git a/app/models/clusters/applications/helm.rb b/app/models/clusters/applications/helm.rb
index 55bbf7cae7e..423071ec024 100644
--- a/app/models/clusters/applications/helm.rb
+++ b/app/models/clusters/applications/helm.rb
@@ -32,7 +32,8 @@ module Clusters
def install_command
Gitlab::Kubernetes::Helm::InitCommand.new(
name: name,
- files: files
+ files: files,
+ rbac: cluster.platform_kubernetes_rbac?
)
end
diff --git a/app/models/clusters/applications/ingress.rb b/app/models/clusters/applications/ingress.rb
index 93f654e0638..bd0286ee3f9 100644
--- a/app/models/clusters/applications/ingress.rb
+++ b/app/models/clusters/applications/ingress.rb
@@ -39,6 +39,7 @@ module Clusters
Gitlab::Kubernetes::Helm::InstallCommand.new(
name: name,
version: VERSION,
+ rbac: cluster.platform_kubernetes_rbac?,
chart: chart,
files: files
)
diff --git a/app/models/clusters/applications/jupyter.rb b/app/models/clusters/applications/jupyter.rb
index ef1c76c03bd..3d84eeed5a8 100644
--- a/app/models/clusters/applications/jupyter.rb
+++ b/app/models/clusters/applications/jupyter.rb
@@ -40,6 +40,7 @@ module Clusters
Gitlab::Kubernetes::Helm::InstallCommand.new(
name: name,
version: VERSION,
+ rbac: cluster.platform_kubernetes_rbac?,
chart: chart,
files: files,
repository: repository
diff --git a/app/models/clusters/applications/prometheus.rb b/app/models/clusters/applications/prometheus.rb
index 88399dbbb95..46d0388a464 100644
--- a/app/models/clusters/applications/prometheus.rb
+++ b/app/models/clusters/applications/prometheus.rb
@@ -48,6 +48,7 @@ module Clusters
Gitlab::Kubernetes::Helm::InstallCommand.new(
name: name,
version: VERSION,
+ rbac: cluster.platform_kubernetes_rbac?,
chart: chart,
files: files
)
@@ -71,7 +72,7 @@ module Clusters
private
def kube_client
- cluster&.kubeclient
+ cluster&.kubeclient&.core_client
end
end
end
diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb
index bde255723c8..a4a2e2b79a6 100644
--- a/app/models/clusters/applications/runner.rb
+++ b/app/models/clusters/applications/runner.rb
@@ -33,6 +33,7 @@ module Clusters
Gitlab::Kubernetes::Helm::InstallCommand.new(
name: name,
version: VERSION,
+ rbac: cluster.platform_kubernetes_rbac?,
chart: chart,
files: files,
repository: repository
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index 7cf75403ab6..d7011ef447a 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -42,6 +42,7 @@ module Clusters
delegate :on_creation?, to: :provider, allow_nil: true
delegate :active?, to: :platform_kubernetes, prefix: true, allow_nil: true
+ delegate :rbac?, to: :platform_kubernetes, prefix: true, allow_nil: true
delegate :installed?, to: :application_helm, prefix: true, allow_nil: true
delegate :installed?, to: :application_ingress, prefix: true, allow_nil: true
diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb
index e6ddca0d5d0..3a335909101 100644
--- a/app/models/clusters/platforms/kubernetes.rb
+++ b/app/models/clusters/platforms/kubernetes.rb
@@ -5,6 +5,7 @@ module Clusters
class Kubernetes < ActiveRecord::Base
include Gitlab::Kubernetes
include ReactiveCaching
+ include EnumWithNil
self.table_name = 'cluster_platforms_kubernetes'
self.reactive_cache_key = ->(kubernetes) { [kubernetes.class.model_name.singular, kubernetes.id] }
@@ -47,6 +48,12 @@ module Clusters
alias_method :active?, :enabled?
+ enum_with_nil authorization_type: {
+ unknown_authorization: nil,
+ rbac: 1,
+ abac: 2
+ }
+
def actual_namespace
if namespace.present?
namespace
@@ -95,7 +102,7 @@ module Clusters
end
def kubeclient
- @kubeclient ||= build_kubeclient!
+ @kubeclient ||= build_kube_client!(api_groups: ['api', 'apis/rbac.authorization.k8s.io'])
end
private
@@ -115,15 +122,16 @@ module Clusters
slug.gsub(/[^-a-z0-9]/, '-').gsub(/^-+/, '')
end
- def build_kubeclient!(api_path: 'api', api_version: 'v1')
+ def build_kube_client!(api_groups: ['api'], api_version: 'v1')
raise "Incomplete settings" unless api_url && actual_namespace
unless (username && password) || token
raise "Either username/password or token is required to access API"
end
- ::Kubeclient::Client.new(
- join_api_url(api_path),
+ Gitlab::Kubernetes::KubeClient.new(
+ api_url,
+ api_groups,
api_version,
auth_options: kubeclient_auth_options,
ssl_options: kubeclient_ssl_options,
@@ -133,7 +141,7 @@ module Clusters
# Returns a hash of all pods in the namespace
def read_pods
- kubeclient = build_kubeclient!
+ kubeclient = build_kube_client!
kubeclient.get_pods(namespace: actual_namespace).as_json
rescue Kubeclient::HttpError => err
@@ -157,15 +165,6 @@ module Clusters
{ bearer_token: token }
end
- def join_api_url(api_path)
- url = URI.parse(api_url)
- prefix = url.path.sub(%r{/+\z}, '')
-
- url.path = [prefix, api_path].join("/")
-
- url.to_s
- end
-
def terminal_auth
{
token: token,
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 594972ad344..49c36ad9d3f 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -22,6 +22,7 @@ class Commit
attr_accessor :project, :author
attr_accessor :redacted_description_html
attr_accessor :redacted_title_html
+ attr_accessor :redacted_full_title_html
attr_reader :gpg_commit
DIFF_SAFE_LINES = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines]
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index b65d7672973..fe2f144ef03 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -58,9 +58,11 @@ class CommitStatus < ActiveRecord::Base
# These are pages deployments and external statuses.
#
before_create unless: :importing? do
+ # rubocop: disable CodeReuse/ServiceClass
Ci::EnsureStageService.new(project, user).execute(self) do |stage|
self.run_after_commit { StageUpdateWorker.perform_async(stage.id) }
end
+ # rubocop: enable CodeReuse/ServiceClass
end
state_machine :status do
@@ -130,10 +132,12 @@ class CommitStatus < ActiveRecord::Base
after_transition any => :failed do |commit_status|
next unless commit_status.project
+ # rubocop: disable CodeReuse/ServiceClass
commit_status.run_after_commit do
MergeRequests::AddTodoWhenBuildFailsService
.new(project, nil).execute(self)
end
+ # rubocop: enable CodeReuse/ServiceClass
end
end
diff --git a/app/models/concerns/case_sensitivity.rb b/app/models/concerns/case_sensitivity.rb
index 6e80365ee5b..c93b6589ee7 100644
--- a/app/models/concerns/case_sensitivity.rb
+++ b/app/models/concerns/case_sensitivity.rb
@@ -9,23 +9,46 @@ module CaseSensitivity
#
# Unlike other ActiveRecord methods this method only operates on a Hash.
def iwhere(params)
- criteria = self
- cast_lower = Gitlab::Database.postgresql?
+ criteria = self
params.each do |key, value|
- column = ActiveRecord::Base.connection.quote_table_name(key)
+ criteria = case value
+ when Array
+ criteria.where(value_in(key, value))
+ else
+ criteria.where(value_equal(key, value))
+ end
+ end
+
+ criteria
+ end
- condition =
- if cast_lower
- "LOWER(#{column}) = LOWER(:value)"
- else
- "#{column} = :value"
- end
+ private
+
+ def value_equal(column, value)
+ lower_value = lower_value(value)
+
+ lower_column(arel_table[column]).eq(lower_value).to_sql
+ end
- criteria = criteria.where(condition, value: value)
+ def value_in(column, values)
+ lower_values = values.map do |value|
+ lower_value(value)
end
- criteria
+ lower_column(arel_table[column]).in(lower_values).to_sql
+ end
+
+ def lower_value(value)
+ return value if Gitlab::Database.mysql?
+
+ Arel::Nodes::NamedFunction.new('LOWER', [Arel::Nodes.build_quoted(value)])
+ end
+
+ def lower_column(column)
+ return column if Gitlab::Database.mysql?
+
+ column.lower
end
end
end
diff --git a/app/models/concerns/from_union.rb b/app/models/concerns/from_union.rb
new file mode 100644
index 00000000000..9b8595b1211
--- /dev/null
+++ b/app/models/concerns/from_union.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+module FromUnion
+ extend ActiveSupport::Concern
+
+ class_methods do
+ # Produces a query that uses a FROM to select data using a UNION.
+ #
+ # Using a FROM for a UNION has in the past lead to better query plans. As
+ # such, we generally recommend this pattern instead of using a WHERE IN.
+ #
+ # Example:
+ # users = User.from_union([User.where(id: 1), User.where(id: 2)])
+ #
+ # This would produce the following SQL query:
+ #
+ # SELECT *
+ # FROM (
+ # SELECT *
+ # FROM users
+ # WHERE id = 1
+ #
+ # UNION
+ #
+ # SELECT *
+ # FROM users
+ # WHERE id = 2
+ # ) users;
+ #
+ # members - An Array of ActiveRecord::Relation objects to use in the UNION.
+ #
+ # remove_duplicates - A boolean indicating if duplicate entries should be
+ # removed. Defaults to true.
+ #
+ # alias_as - The alias to use for the sub query. Defaults to the name of the
+ # table of the current model.
+ # rubocop: disable Gitlab/Union
+ def from_union(members, remove_duplicates: true, alias_as: table_name)
+ union = Gitlab::SQL::Union
+ .new(members, remove_duplicates: remove_duplicates)
+ .to_sql
+
+ # This pattern is necessary as a bug in Rails 4 can cause the use of
+ # `from("string here").includes(:foo)` to break ActiveRecord. This is
+ # fixed in https://github.com/rails/rails/pull/25374, which is released as
+ # part of Rails 5.
+ from([Arel.sql("(#{union}) #{alias_as}")])
+ end
+ # rubocop: enable Gitlab/Union
+ end
+end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index f881ce2321c..5f65fceb7af 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -49,7 +49,7 @@ module Issuable
end
end
- has_many :label_links, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_many :label_links, as: :target, dependent: :destroy, inverse_of: :target # rubocop:disable Cop/ActiveRecordDependent
has_many :labels, through: :label_links
has_many :todos, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
@@ -109,10 +109,6 @@ module Issuable
false
end
- def etag_caching_enabled?
- false
- end
-
def has_multiple_assignees?
assignees.count > 1
end
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index 393607e82c4..298d0d42d90 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -61,7 +61,7 @@ module Mentionable
cache_key: [self, attr],
author: author,
skip_project_check: skip_project_check?
- )
+ ).merge(mentionable_params)
extractor.analyze(text, options)
end
@@ -86,12 +86,11 @@ module Mentionable
return [] unless matches_cross_reference_regex?
refs = all_references(current_user)
- refs = (refs.issues + refs.merge_requests + refs.commits)
# We're using this method instead of Array diffing because that requires
# both of the object's `hash` values to be the same, which may not be the
# case for otherwise identical Commit objects.
- refs.reject { |ref| ref == local_reference }
+ extracted_mentionables(refs).reject { |ref| ref == local_reference }
end
# Uses regex to quickly determine if mentionables might be referenced
@@ -134,6 +133,10 @@ module Mentionable
private
+ def extracted_mentionables(refs)
+ refs.issues + refs.merge_requests + refs.commits
+ end
+
# Returns a Hash of changed mentionable fields
#
# Preference is given to the `changes` Hash, but falls back to
@@ -161,4 +164,8 @@ module Mentionable
def skip_project_check?
false
end
+
+ def mentionable_params
+ {}
+ end
end
diff --git a/app/models/concerns/mentionable/reference_regexes.rb b/app/models/concerns/mentionable/reference_regexes.rb
index f6fd28bac33..fe8fbb71184 100644
--- a/app/models/concerns/mentionable/reference_regexes.rb
+++ b/app/models/concerns/mentionable/reference_regexes.rb
@@ -5,13 +5,19 @@ module Mentionable
def self.reference_pattern(link_patterns, issue_pattern)
Regexp.union(link_patterns,
issue_pattern,
- Commit.reference_pattern,
- MergeRequest.reference_pattern)
+ *other_patterns)
+ end
+
+ def self.other_patterns
+ [
+ Commit.reference_pattern,
+ MergeRequest.reference_pattern
+ ]
end
DEFAULT_PATTERN = begin
issue_pattern = Issue.reference_pattern
- link_patterns = Regexp.union([Issue, Commit, MergeRequest].map(&:link_reference_pattern))
+ link_patterns = Regexp.union([Issue, Commit, MergeRequest, Epic].map(&:link_reference_pattern).compact)
reference_pattern(link_patterns, issue_pattern)
end
diff --git a/app/models/concerns/noteable.rb b/app/models/concerns/noteable.rb
index ce778eae271..098eed137ba 100644
--- a/app/models/concerns/noteable.rb
+++ b/app/models/concerns/noteable.rb
@@ -82,4 +82,23 @@ module Noteable
def lockable?
[MergeRequest, Issue].include?(self.class)
end
+
+ def etag_caching_enabled?
+ false
+ end
+
+ def expire_note_etag_cache
+ return unless discussions_rendered_on_frontend?
+ return unless etag_caching_enabled?
+
+ Gitlab::EtagCaching::Store.new.touch(note_etag_key)
+ end
+
+ def note_etag_key
+ Gitlab::Routing.url_helpers.project_noteable_notes_path(
+ project,
+ target_type: self.class.name.underscore,
+ target_id: id
+ )
+ end
end
diff --git a/app/models/concerns/project_services_loggable.rb b/app/models/concerns/project_services_loggable.rb
new file mode 100644
index 00000000000..fecd77cdc98
--- /dev/null
+++ b/app/models/concerns/project_services_loggable.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module ProjectServicesLoggable
+ def log_info(message, params = {})
+ message = build_message(message, params)
+
+ logger.info(message)
+ end
+
+ def log_error(message, params = {})
+ message = build_message(message, params)
+
+ logger.error(message)
+ end
+
+ def build_message(message, params = {})
+ {
+ service_class: self.class.name,
+ project_id: project.id,
+ project_path: project.full_path,
+ message: message
+ }.merge(params)
+ end
+
+ def logger
+ Gitlab::ProjectServiceLogger
+ end
+end
diff --git a/app/models/concerns/protected_branch_access.rb b/app/models/concerns/protected_branch_access.rb
index 744f7f48dc8..58761fce952 100644
--- a/app/models/concerns/protected_branch_access.rb
+++ b/app/models/concerns/protected_branch_access.rb
@@ -2,18 +2,17 @@
module ProtectedBranchAccess
extend ActiveSupport::Concern
+ include ProtectedRefAccess
included do
- include ProtectedRefAccess
-
belongs_to :protected_branch
delegate :project, to: :protected_branch
+ end
- def check_access(user)
- return false if access_level == Gitlab::Access::NO_ACCESS
+ def check_access(user)
+ return false if access_level == Gitlab::Access::NO_ACCESS
- super
- end
+ super
end
end
diff --git a/app/models/concerns/protected_ref_access.rb b/app/models/concerns/protected_ref_access.rb
index efa666fb3f2..583751ea6ac 100644
--- a/app/models/concerns/protected_ref_access.rb
+++ b/app/models/concerns/protected_ref_access.rb
@@ -3,18 +3,22 @@
module ProtectedRefAccess
extend ActiveSupport::Concern
- ALLOWED_ACCESS_LEVELS = [
- Gitlab::Access::MAINTAINER,
- Gitlab::Access::DEVELOPER,
- Gitlab::Access::NO_ACCESS
- ].freeze
-
HUMAN_ACCESS_LEVELS = {
Gitlab::Access::MAINTAINER => "Maintainers".freeze,
Gitlab::Access::DEVELOPER => "Developers + Maintainers".freeze,
Gitlab::Access::NO_ACCESS => "No one".freeze
}.freeze
+ class_methods do
+ def allowed_access_levels
+ [
+ Gitlab::Access::MAINTAINER,
+ Gitlab::Access::DEVELOPER,
+ Gitlab::Access::NO_ACCESS
+ ]
+ end
+ end
+
included do
scope :master, -> { maintainer } # @deprecated
scope :maintainer, -> { where(access_level: Gitlab::Access::MAINTAINER) }
@@ -26,7 +30,7 @@ module ProtectedRefAccess
scope :for_group, -> { where.not(group_id: nil) }
validates :access_level, presence: true, if: :role?, inclusion: {
- in: ALLOWED_ACCESS_LEVELS
+ in: self.allowed_access_levels
}
end
diff --git a/app/models/concerns/protected_tag_access.rb b/app/models/concerns/protected_tag_access.rb
index 04bd54d6b1c..3f5696c0749 100644
--- a/app/models/concerns/protected_tag_access.rb
+++ b/app/models/concerns/protected_tag_access.rb
@@ -2,10 +2,9 @@
module ProtectedTagAccess
extend ActiveSupport::Concern
+ include ProtectedRefAccess
included do
- include ProtectedRefAccess
-
belongs_to :protected_tag
delegate :project, to: :protected_tag
diff --git a/app/models/concerns/storage/legacy_namespace.rb b/app/models/concerns/storage/legacy_namespace.rb
index 3b745657a9e..9785011720a 100644
--- a/app/models/concerns/storage/legacy_namespace.rb
+++ b/app/models/concerns/storage/legacy_namespace.rb
@@ -25,8 +25,6 @@ module Storage
Gitlab::PagesTransfer.new.rename_namespace(full_path_was, full_path)
end
- remove_exports!
-
# If repositories moved successfully we need to
# send update instructions to users.
# However we cannot allow rollback since we moved namespace dir
@@ -101,8 +99,6 @@ module Storage
end
end
end
-
- remove_exports!
end
def remove_legacy_exports!
diff --git a/app/models/container_repository.rb b/app/models/container_repository.rb
index 41413854d5c..2c08a8e1acf 100644
--- a/app/models/container_repository.rb
+++ b/app/models/container_repository.rb
@@ -8,8 +8,7 @@ class ContainerRepository < ActiveRecord::Base
delegate :client, to: :registry
- before_destroy :delete_tags!
-
+ # rubocop: disable CodeReuse/ServiceClass
def registry
@registry ||= begin
token = Auth::ContainerRegistryAuthenticationService.full_access_token(path)
@@ -20,6 +19,7 @@ class ContainerRepository < ActiveRecord::Base
ContainerRegistry::Registry.new(url, token: token, path: host_port)
end
end
+ # rubocop: enable CodeReuse/ServiceClass
def path
@path ||= [project.full_path, name]
diff --git a/app/models/dashboard_group_milestone.rb b/app/models/dashboard_group_milestone.rb
index 13807d43265..32e8104125c 100644
--- a/app/models/dashboard_group_milestone.rb
+++ b/app/models/dashboard_group_milestone.rb
@@ -13,7 +13,11 @@ class DashboardGroupMilestone < GlobalMilestone
end
def self.build_collection(groups)
- MilestonesFinder.new(group_ids: groups.pluck(:id)).execute.map { |m| new(m) }
+ Milestone.of_groups(groups.select(:id))
+ .reorder_by_due_date_asc
+ .order_by_name_asc
+ .active
+ .map { |m| new(m) }
end
override :group_milestone?
diff --git a/app/models/deploy_key.rb b/app/models/deploy_key.rb
index fd5d7726fb6..db501b4b506 100644
--- a/app/models/deploy_key.rb
+++ b/app/models/deploy_key.rb
@@ -18,7 +18,7 @@ class DeployKey < Key
end
def orphaned?
- self.deploy_keys_projects.length == 0
+ self.deploy_keys_projects.empty?
end
def almost_orphaned?
diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb
index 716cf6574d3..047d353b4b5 100644
--- a/app/models/diff_note.rb
+++ b/app/models/diff_note.rb
@@ -131,7 +131,7 @@ class DiffNote < Note
# As an extra benefit, the returned `diff_file` already
# has `highlighted_diff_lines` data set from Redis on
# `Diff::FileCollection::MergeRequestDiff`.
- noteable.diffs(paths: original_position.paths, expanded: true).diff_files.first
+ noteable.diffs(original_position.diff_options).diff_files.first
else
original_position.diff_file(self.project.repository)
end
diff --git a/app/models/environment.rb b/app/models/environment.rb
index c8d1d378ae0..309bd4f37c9 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -158,9 +158,11 @@ class Environment < ActiveRecord::Base
prometheus_adapter.query(:additional_metrics_environment, self) if has_metrics?
end
+ # rubocop: disable CodeReuse/ServiceClass
def prometheus_adapter
@prometheus_adapter ||= Prometheus::AdapterService.new(project, deployment_platform).prometheus_adapter
end
+ # rubocop: enable CodeReuse/ServiceClass
def slug
super.presence || generate_slug
diff --git a/app/models/epic.rb b/app/models/epic.rb
index f027993376c..ccd10593434 100644
--- a/app/models/epic.rb
+++ b/app/models/epic.rb
@@ -3,6 +3,10 @@
# Placeholder class for model that is implemented in EE
# It reserves '&' as a reference prefix, but the table does not exists in CE
class Epic < ActiveRecord::Base
+ def self.link_reference_pattern
+ nil
+ end
+
def self.reference_prefix
'&'
end
diff --git a/app/models/event.rb b/app/models/event.rb
index ba28866e8e6..596155a9525 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -3,6 +3,7 @@
class Event < ActiveRecord::Base
include Sortable
include IgnorableColumn
+ include FromUnion
default_scope { reorder(nil) }
CREATED = 1
@@ -151,15 +152,17 @@ class Event < ActiveRecord::Base
if push? || commit_note?
Ability.allowed?(user, :download_code, project)
elsif membership_changed?
- true
+ Ability.allowed?(user, :read_project, project)
elsif created_project?
- true
+ Ability.allowed?(user, :read_project, project)
elsif issue? || issue_note?
Ability.allowed?(user, :read_issue, note? ? note_target : target)
elsif merge_request? || merge_request_note?
Ability.allowed?(user, :read_merge_request, note? ? note_target : target)
+ elsif milestone?
+ Ability.allowed?(user, :read_project, project)
else
- milestone?
+ false # No other event types are visible
end
end
diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb
index 6e23e811b0e..a6cebabe089 100644
--- a/app/models/global_milestone.rb
+++ b/app/models/global_milestone.rb
@@ -17,7 +17,7 @@ class GlobalMilestone
params =
{ project_ids: projects.map(&:id), state: params[:state] }
- child_milestones = MilestonesFinder.new(params).execute
+ child_milestones = MilestonesFinder.new(params).execute # rubocop: disable CodeReuse/Finder
milestones = child_milestones.select(:id, :title).group_by(&:title).map do |title, grouped|
milestones_relation = Milestone.where(id: grouped.map(&:id))
@@ -48,7 +48,7 @@ class GlobalMilestone
params = { group_ids: [group.id], state: 'all' }
- relation = MilestonesFinder.new(params).execute
+ relation = MilestonesFinder.new(params).execute # rubocop: disable CodeReuse/Finder
grouped_by_state = relation.reorder(nil).group(:state).count
{
@@ -64,7 +64,7 @@ class GlobalMilestone
params = { project_ids: projects.map(&:id), state: 'all' }
- relation = MilestonesFinder.new(params).execute
+ relation = MilestonesFinder.new(params).execute # rubocop: disable CodeReuse/Finder
project_milestones_by_state_and_title = relation.reorder(nil).group(:state, :title).count
opened = count_by_state(project_milestones_by_state_and_title, 'active')
diff --git a/app/models/group.rb b/app/models/group.rb
index 106a1f4a94c..62af20d2142 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -236,14 +236,18 @@ class Group < Namespace
system_hook_service.execute_hooks_for(self, :destroy)
end
+ # rubocop: disable CodeReuse/ServiceClass
def system_hook_service
SystemHooksService.new
end
+ # rubocop: enable CodeReuse/ServiceClass
+ # rubocop: disable CodeReuse/ServiceClass
def refresh_members_authorized_projects(blocking: true)
UserProjectAccessChangedService.new(user_ids_for_project_authorizations)
.execute(blocking: blocking)
end
+ # rubocop: enable CodeReuse/ServiceClass
def user_ids_for_project_authorizations
members_with_parents.pluck(:user_id)
@@ -300,14 +304,12 @@ class Group < Namespace
# 3. They belong to a sub-group or project in such sub-group
# 4. They belong to an ancestor group
def direct_and_indirect_users
- union = Gitlab::SQL::Union.new([
+ User.from_union([
User
.where(id: direct_and_indirect_members.select(:user_id))
.reorder(nil),
project_users_with_descendants
])
-
- User.from("(#{union.to_sql}) #{User.table_name}")
end
# Returns all users that are members of projects
diff --git a/app/models/hooks/active_hook_filter.rb b/app/models/hooks/active_hook_filter.rb
index ea046bea368..283e2d680f4 100644
--- a/app/models/hooks/active_hook_filter.rb
+++ b/app/models/hooks/active_hook_filter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ActiveHookFilter
def initialize(hook)
@hook = hook
diff --git a/app/models/hooks/service_hook.rb b/app/models/hooks/service_hook.rb
index bda82a116a1..7d9f6d89d44 100644
--- a/app/models/hooks/service_hook.rb
+++ b/app/models/hooks/service_hook.rb
@@ -4,7 +4,9 @@ class ServiceHook < WebHook
belongs_to :service
validates :service, presence: true
+ # rubocop: disable CodeReuse/ServiceClass
def execute(data)
WebHookService.new(self, data, 'service_hook').execute
end
+ # rubocop: enable CodeReuse/ServiceClass
end
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index 20f15c15277..771a61b090f 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -11,13 +11,17 @@ class WebHook < ActiveRecord::Base
validates :token, format: { without: /\n/ }
validates :push_events_branch_filter, branch_filter: true
+ # rubocop: disable CodeReuse/ServiceClass
def execute(data, hook_name)
WebHookService.new(self, data, hook_name).execute
end
+ # rubocop: enable CodeReuse/ServiceClass
+ # rubocop: disable CodeReuse/ServiceClass
def async_execute(data, hook_name)
WebHookService.new(self, data, hook_name).async_execute
end
+ # rubocop: enable CodeReuse/ServiceClass
# Allow urls pointing localhost and the local network
def allow_local_requests?
diff --git a/app/models/issue.rb b/app/models/issue.rb
index d0cd7461daa..d13fbcf002c 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -172,6 +172,7 @@ class Issue < ActiveRecord::Base
# All branches containing the current issue's ID, except for
# those with a merge request open referencing the current issue.
+ # rubocop: disable CodeReuse/ServiceClass
def related_branches(current_user)
branches_with_iid = project.repository.branch_names.select do |branch|
branch =~ /\A#{iid}-(?!\d+-stable)/i
@@ -185,6 +186,7 @@ class Issue < ActiveRecord::Base
branches_with_iid - branches_with_merge_request
end
+ # rubocop: enable CodeReuse/ServiceClass
def suggested_branch_name
return to_branch_name unless project.repository.branch_exists?(to_branch_name)
@@ -278,9 +280,11 @@ class Issue < ActiveRecord::Base
true
end
+ # rubocop: disable CodeReuse/ServiceClass
def update_project_counter_caches
Projects::OpenIssuesCountService.new(project).refresh_cache
end
+ # rubocop: enable CodeReuse/ServiceClass
private
diff --git a/app/models/key.rb b/app/models/key.rb
index 3bb0d2f6f9c..bdb83e12793 100644
--- a/app/models/key.rb
+++ b/app/models/key.rb
@@ -55,9 +55,11 @@ class Key < ActiveRecord::Base
"key-#{id}"
end
+ # rubocop: disable CodeReuse/ServiceClass
def update_last_used_at
Keys::LastUsedService.new(self).execute
end
+ # rubocop: enable CodeReuse/ServiceClass
def add_to_shell
GitlabShellWorker.perform_async(
@@ -67,9 +69,11 @@ class Key < ActiveRecord::Base
)
end
+ # rubocop: disable CodeReuse/ServiceClass
def post_create_hook
SystemHooksService.new.execute_hooks_for(self, :create)
end
+ # rubocop: enable CodeReuse/ServiceClass
def remove_from_shell
GitlabShellWorker.perform_async(
@@ -79,15 +83,19 @@ class Key < ActiveRecord::Base
)
end
+ # rubocop: disable CodeReuse/ServiceClass
def refresh_user_cache
return unless user
Users::KeysCountService.new(user).refresh_cache
end
+ # rubocop: enable CodeReuse/ServiceClass
+ # rubocop: disable CodeReuse/ServiceClass
def post_destroy_hook
SystemHooksService.new.execute_hooks_for(self, :destroy)
end
+ # rubocop: enable CodeReuse/ServiceClass
def public_key
@public_key ||= Gitlab::SSHPublicKey.new(key)
diff --git a/app/models/label.rb b/app/models/label.rb
index 96c1515b41a..9ef57a05b3e 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -5,6 +5,9 @@ class Label < ActiveRecord::Base
include Referable
include Subscribable
include Gitlab::SQL::Pattern
+ include OptionallySearch
+ include Sortable
+ include FromUnion
# Represents a "No Label" state used for filtering Issues and Merge
# Requests that have no label assigned.
@@ -40,6 +43,8 @@ class Label < ActiveRecord::Base
scope :with_lists_and_board, -> { joins(lists: :board).merge(List.movable) }
scope :on_group_boards, ->(group_id) { with_lists_and_board.where(boards: { group_id: group_id }) }
scope :on_project_boards, ->(project_id) { with_lists_and_board.where(boards: { project_id: project_id }) }
+ scope :order_name_asc, -> { reorder(title: :asc) }
+ scope :order_name_desc, -> { reorder(title: :desc) }
def self.prioritized(project)
joins(:priorities)
diff --git a/app/models/label_link.rb b/app/models/label_link.rb
index 779657b25d5..1d93a55e8e9 100644
--- a/app/models/label_link.rb
+++ b/app/models/label_link.rb
@@ -3,7 +3,7 @@
class LabelLink < ActiveRecord::Base
include Importable
- belongs_to :target, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
+ belongs_to :target, polymorphic: true, inverse_of: :label_links # rubocop:disable Cop/PolymorphicAssociations
belongs_to :label
validates :target, presence: true, unless: :importing?
diff --git a/app/models/label_note.rb b/app/models/label_note.rb
new file mode 100644
index 00000000000..680952cf421
--- /dev/null
+++ b/app/models/label_note.rb
@@ -0,0 +1,97 @@
+# frozen_string_literal: true
+
+class LabelNote < Note
+ attr_accessor :resource_parent
+ attr_reader :events
+
+ def self.from_events(events, resource: nil, resource_parent: nil)
+ resource ||= events.first.issuable
+
+ attrs = {
+ system: true,
+ author: events.first.user,
+ created_at: events.first.created_at,
+ discussion_id: events.first.discussion_id,
+ noteable: resource,
+ system_note_metadata: SystemNoteMetadata.new(action: 'label'),
+ events: events,
+ resource_parent: resource_parent
+ }
+
+ if resource_parent.is_a?(Project)
+ attrs[:project_id] = resource_parent.id
+ end
+
+ LabelNote.new(attrs)
+ end
+
+ def events=(events)
+ @events = events
+
+ update_outdated_markdown
+ end
+
+ def cached_html_up_to_date?(markdown_field)
+ true
+ end
+
+ def note
+ @note ||= note_text
+ end
+
+ def note_html
+ @note_html ||= "<p dir=\"auto\">#{note_text(html: true)}</p>"
+ end
+
+ def project
+ resource_parent if resource_parent.is_a?(Project)
+ end
+
+ def group
+ resource_parent if resource_parent.is_a?(Group)
+ end
+
+ private
+
+ def update_outdated_markdown
+ events.each do |event|
+ if event.outdated_markdown?
+ event.refresh_invalid_reference
+ end
+ end
+ end
+
+ def note_text(html: false)
+ added = labels_str('added', label_refs_by_action('add', html))
+ removed = labels_str('removed', label_refs_by_action('remove', html))
+
+ [added, removed].compact.join(' and ')
+ end
+
+ # returns string containing added/removed labels including
+ # count of deleted labels:
+ #
+ # added ~1 ~2 + 1 deleted label
+ # added 3 deleted labels
+ # added ~1 ~2 labels
+ def labels_str(prefix, label_refs)
+ existing_refs = label_refs.select { |ref| ref.present? }.sort
+ refs_str = existing_refs.empty? ? nil : existing_refs.join(' ')
+
+ deleted = label_refs.count - existing_refs.count
+ deleted_str = deleted == 0 ? nil : "#{deleted} deleted"
+
+ return nil unless refs_str || deleted_str
+
+ label_list_str = [refs_str, deleted_str].compact.join(' + ')
+ suffix = 'label'.pluralize(deleted > 0 ? deleted : existing_refs.count)
+
+ "#{prefix} #{label_list_str} #{suffix}"
+ end
+
+ def label_refs_by_action(action, html)
+ field = html ? :reference_html : :reference
+
+ events.select { |e| e.action == action }.map(&field)
+ end
+end
diff --git a/app/models/license_template.rb b/app/models/license_template.rb
index 0ad75b27827..693a6a89fd2 100644
--- a/app/models/license_template.rb
+++ b/app/models/license_template.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class LicenseTemplate
PROJECT_TEMPLATE_REGEX =
%r{[\<\{\[]
diff --git a/app/models/member.rb b/app/models/member.rb
index d9b4e8d2ac6..0696ea46c8b 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -145,6 +145,7 @@ class Member < ActiveRecord::Base
end
def add_user(source, user, access_level, existing_members: nil, current_user: nil, expires_at: nil, ldap: false)
+ # rubocop: disable CodeReuse/ServiceClass
# `user` can be either a User object, User ID or an email to be invited
member = retrieve_member(source, user, existing_members)
access_level = retrieve_access_level(access_level)
@@ -171,6 +172,7 @@ class Member < ActiveRecord::Base
end
member
+ # rubocop: enable CodeReuse/ServiceClass
end
def add_users(source, users, access_level, current_user: nil, expires_at: nil)
@@ -339,12 +341,14 @@ class Member < ActiveRecord::Base
@notification_setting ||= user&.notification_settings_for(source)
end
+ # rubocop: disable CodeReuse/ServiceClass
def notifiable?(type, opts = {})
# always notify when there isn't a user yet
return true if user.blank?
NotificationRecipientService.notifiable?(user, type, notifiable_options.merge(opts))
end
+ # rubocop: enable CodeReuse/ServiceClass
private
@@ -374,6 +378,7 @@ class Member < ActiveRecord::Base
# in a transaction. Doing so can lead to the job running before the
# transaction has been committed, resulting in the job either throwing an
# error or not doing any meaningful work.
+ # rubocop: disable CodeReuse/ServiceClass
def refresh_member_authorized_projects
# If user/source is being destroyed, project access are going to be
# destroyed eventually because of DB foreign keys, so we shouldn't bother
@@ -382,6 +387,7 @@ class Member < ActiveRecord::Base
UserProjectAccessChangedService.new(user_id).execute
end
+ # rubocop: enable CodeReuse/ServiceClass
def after_accept_invite
post_create_hook
@@ -395,13 +401,17 @@ class Member < ActiveRecord::Base
post_create_hook
end
+ # rubocop: disable CodeReuse/ServiceClass
def system_hook_service
SystemHooksService.new
end
+ # rubocop: enable CodeReuse/ServiceClass
+ # rubocop: disable CodeReuse/ServiceClass
def notification_service
NotificationService.new
end
+ # rubocop: enable CodeReuse/ServiceClass
def notifiable_options
{}
diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb
index 0154fe5aeba..537f2a3a231 100644
--- a/app/models/members/project_member.rb
+++ b/app/models/members/project_member.rb
@@ -138,7 +138,9 @@ class ProjectMember < Member
super
end
+ # rubocop: disable CodeReuse/ServiceClass
def event_service
EventCreateService.new
end
+ # rubocop: enable CodeReuse/ServiceClass
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 396647a14ae..dd5d494997d 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -14,6 +14,7 @@ class MergeRequest < ActiveRecord::Base
include Gitlab::Utils::StrongMemoize
include LabelEventable
include ReactiveCaching
+ include FromUnion
self.reactive_cache_key = ->(model) { [model.project.id, model.iid] }
self.reactive_cache_refresh_interval = 10.minutes
@@ -137,12 +138,14 @@ class MergeRequest < ActiveRecord::Base
Gitlab::Timeless.timeless(merge_request, &block)
end
+ # rubocop: disable CodeReuse/ServiceClass
after_transition unchecked: :cannot_be_merged do |merge_request, transition|
if merge_request.notify_conflict?
NotificationService.new.merge_request_unmergeable(merge_request)
TodoService.new.merge_request_became_unmergeable(merge_request)
end
end
+ # rubocop: enable CodeReuse/ServiceClass
def check_state?(merge_status)
[:unchecked, :cannot_be_merged_recheck].include?(merge_status.to_sym)
@@ -235,11 +238,10 @@ class MergeRequest < ActiveRecord::Base
def self.in_projects(relation)
# unscoping unnecessary conditions that'll be applied
# when executing `where("merge_requests.id IN (#{union.to_sql})")`
- source = unscoped.where(source_project_id: relation).select(:id)
- target = unscoped.where(target_project_id: relation).select(:id)
- union = Gitlab::SQL::Union.new([source, target])
+ source = unscoped.where(source_project_id: relation)
+ target = unscoped.where(target_project_id: relation)
- where("merge_requests.id IN (#{union.to_sql})") # rubocop:disable GitlabSecurity/SqlInjection
+ from_union([source, target])
end
# This is used after project import, to reset the IDs to the correct
@@ -623,11 +625,13 @@ class MergeRequest < ActiveRecord::Base
end
end
+ # rubocop: disable CodeReuse/ServiceClass
def reload_diff(current_user = nil)
return unless open?
MergeRequests::ReloadDiffsService.new(self, current_user).execute
end
+ # rubocop: enable CodeReuse/ServiceClass
def check_if_can_be_merged
return unless self.class.state_machines[:merge_status].check_state?(merge_status) && Gitlab::Database.read_write?
@@ -736,11 +740,8 @@ class MergeRequest < ActiveRecord::Base
# compared to using OR statements. We're using UNION ALL since the queries
# used won't produce any duplicates (e.g. a note for a commit can't also be
# a note for an MR).
- union = Gitlab::SQL::Union
- .new([notes, commit_notes], remove_duplicates: false)
- .to_sql
-
- Note.from("(#{union}) #{Note.table_name}")
+ Note
+ .from_union([notes, commit_notes], remove_duplicates: false)
.includes(:noteable)
end
@@ -1036,6 +1037,7 @@ class MergeRequest < ActiveRecord::Base
actual_head_pipeline&.has_test_reports?
end
+ # rubocop: disable CodeReuse/ServiceClass
def compare_test_reports
unless has_test_reports?
return { status: :error, status_reason: 'This merge request does not have test reports' }
@@ -1050,7 +1052,9 @@ class MergeRequest < ActiveRecord::Base
data
end || { status: :parsing }
end
+ # rubocop: enable CodeReuse/ServiceClass
+ # rubocop: disable CodeReuse/ServiceClass
def calculate_reactive_cache(identifier, *args)
case identifier.to_sym
when :compare_test_results
@@ -1060,6 +1064,7 @@ class MergeRequest < ActiveRecord::Base
raise NotImplementedError, "Unknown identifier: #{identifier}"
end
end
+ # rubocop: enable CodeReuse/ServiceClass
def all_commits
# MySQL doesn't support LIMIT in a subquery.
@@ -1125,6 +1130,7 @@ class MergeRequest < ActiveRecord::Base
diff_refs && diff_refs.complete?
end
+ # rubocop: disable CodeReuse/ServiceClass
def update_diff_discussion_positions(old_diff_refs:, new_diff_refs:, current_user: nil)
return unless has_complete_diff_refs?
return if new_diff_refs == old_diff_refs
@@ -1154,6 +1160,7 @@ class MergeRequest < ActiveRecord::Base
.execute(self)
end
end
+ # rubocop: enable CodeReuse/ServiceClass
def keep_around_commit
project.repository.keep_around(self.merge_commit_sha)
@@ -1189,9 +1196,11 @@ class MergeRequest < ActiveRecord::Base
true
end
+ # rubocop: disable CodeReuse/ServiceClass
def update_project_counter_caches
Projects::OpenMergeRequestsCountService.new(target_project).refresh_cache
end
+ # rubocop: enable CodeReuse/ServiceClass
def first_contribution?
return false if project.team.max_member_access(author_id) > Gitlab::Access::GUEST
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index bbe4f6f7969..02c6b650f33 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -219,12 +219,14 @@ class MergeRequestDiff < ActiveRecord::Base
self.id == merge_request.latest_merge_request_diff_id
end
+ # rubocop: disable CodeReuse/ServiceClass
def compare_with(sha)
# When compare merge request versions we want diff A..B instead of A...B
# so we handle cases when user does squash and rebase of the commits between versions.
# For this reason we set straight to true by default.
CompareService.new(project, head_commit_sha).execute(project, sha, straight: true)
end
+ # rubocop: enable CodeReuse/ServiceClass
private
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index cb1def1b422..892a680f221 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -46,6 +46,9 @@ class Milestone < ActiveRecord::Base
where(conditions.reduce(:or))
end
+ scope :order_by_name_asc, -> { order(Arel::Nodes::Ascending.new(arel_table[:title].lower)) }
+ scope :reorder_by_due_date_asc, -> { reorder(Gitlab::Database.nulls_last_order('due_date', 'ASC')) }
+
validates :group, presence: true, unless: :project
validates :project, presence: true, unless: :group
@@ -149,7 +152,7 @@ class Milestone < ActiveRecord::Base
sorted =
case method.to_s
when 'due_date_asc'
- reorder(Gitlab::Database.nulls_last_order('due_date', 'ASC'))
+ reorder_by_due_date_asc
when 'due_date_desc'
reorder(Gitlab::Database.nulls_last_order('due_date', 'DESC'))
when 'name_asc'
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 0deb44d7916..0289f29211d 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -11,6 +11,7 @@ class Namespace < ActiveRecord::Base
include Gitlab::SQL::Pattern
include IgnorableColumn
include FeatureGate
+ include FromUnion
ignore_column :deleted_at
@@ -253,18 +254,6 @@ class Namespace < ActiveRecord::Base
end
end
- # Exports belonging to projects with legacy storage are placed in a common
- # subdirectory of the namespace, so a simple `rm -rf` is sufficient to remove
- # them.
- #
- # Exports of projects using hashed storage are placed in a location defined
- # only by the project ID, so each must be removed individually.
- def remove_exports!
- remove_legacy_exports!
-
- all_projects.with_storage_feature(:repository).find_each(&:remove_exports)
- end
-
def refresh_project_authorizations
owner.refresh_authorized_projects
end
diff --git a/app/models/network/commit.rb b/app/models/network/commit.rb
index 6c5a4c56377..1b2369aab18 100644
--- a/app/models/network/commit.rb
+++ b/app/models/network/commit.rb
@@ -18,7 +18,7 @@ module Network
end
def space
- if @spaces.size > 0
+ if @spaces.present?
@spaces.first
else
0
diff --git a/app/models/network/graph.rb b/app/models/network/graph.rb
index 1431dfefc55..6da3bb7bfb7 100644
--- a/app/models/network/graph.rb
+++ b/app/models/network/graph.rb
@@ -81,7 +81,7 @@ module Network
skip = 0
while offset == -1
tmp_commits = find_commits(skip)
- if tmp_commits.size > 0
+ if tmp_commits.present?
index = tmp_commits.index do |c|
c.id == @commit.id
end
@@ -218,7 +218,7 @@ module Network
def get_space_base(leaves)
space_base = 1
parents = leaves.last.parents(@map)
- if parents.size > 0
+ if parents.present?
if parents.first.space > 0
space_base = parents.first.space
end
diff --git a/app/models/note.rb b/app/models/note.rb
index 2e343b8f9f8..bea02d69b65 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -17,6 +17,7 @@ class Note < ActiveRecord::Base
include Editable
include Gitlab::SQL::Pattern
include ThrottledTouch
+ include FromUnion
module SpecialRole
FIRST_TIME_CONTRIBUTOR = :first_time_contributor
@@ -181,6 +182,7 @@ class Note < ActiveRecord::Base
end
end
+ # rubocop: disable CodeReuse/ServiceClass
def cross_reference?
return unless system?
@@ -190,6 +192,7 @@ class Note < ActiveRecord::Base
SystemNoteService.cross_reference?(note)
end
end
+ # rubocop: enable CodeReuse/ServiceClass
def diff_note?
false
@@ -389,18 +392,7 @@ class Note < ActiveRecord::Base
end
def expire_etag_cache
- return unless noteable&.discussions_rendered_on_frontend?
- return unless noteable&.etag_caching_enabled?
-
- Gitlab::EtagCaching::Store.new.touch(etag_key)
- end
-
- def etag_key
- Gitlab::Routing.url_helpers.project_noteable_notes_path(
- project,
- target_type: noteable_type.underscore,
- target_id: noteable_id
- )
+ noteable&.expire_note_etag_cache
end
def touch(*args)
diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb
index 7739a3894d3..7a33ade826b 100644
--- a/app/models/pages_domain.rb
+++ b/app/models/pages_domain.rb
@@ -140,9 +140,11 @@ class PagesDomain < ActiveRecord::Base
self.verification_code = SecureRandom.hex(16)
end
+ # rubocop: disable CodeReuse/ServiceClass
def update_daemon
::Projects::UpdatePagesConfigurationService.new(project).execute
end
+ # rubocop: enable CodeReuse/ServiceClass
def pages_config_changed?
project_id_changed? ||
diff --git a/app/models/project.rb b/app/models/project.rb
index 97d9fa355ef..0a5099b27b1 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -29,6 +29,7 @@ class Project < ActiveRecord::Base
include BatchDestroyDependentAssociations
include FeatureGate
include OptionallySearch
+ include FromUnion
extend Gitlab::Cache::RequestCache
extend Gitlab::ConfigHelper
@@ -232,6 +233,8 @@ class Project < ActiveRecord::Base
has_many :clusters, through: :cluster_project, class_name: 'Clusters::Cluster'
has_many :cluster_ingresses, through: :clusters, source: :application_ingress, class_name: 'Clusters::Applications::Ingress'
+ has_many :prometheus_metrics
+
# Container repositories need to remove data from the container registry,
# which is not managed by the DB. Hence we're still using dependent: :destroy
# here.
@@ -328,7 +331,7 @@ class Project < ActiveRecord::Base
# last_activity_at is throttled every minute, but last_repository_updated_at is updated with every push
scope :sorted_by_activity, -> { reorder("GREATEST(COALESCE(last_activity_at, '1970-01-01'), COALESCE(last_repository_updated_at, '1970-01-01')) DESC") }
- scope :sorted_by_stars, -> { reorder('projects.star_count DESC') }
+ scope :sorted_by_stars, -> { reorder(star_count: :desc) }
scope :in_namespace, ->(namespace_ids) { where(namespace_id: namespace_ids) }
scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
@@ -478,6 +481,8 @@ class Project < ActiveRecord::Base
reorder(last_activity_at: :desc)
when 'latest_activity_asc'
reorder(last_activity_at: :asc)
+ when 'stars_desc'
+ sorted_by_stars
else
order_by(method)
end
@@ -567,7 +572,6 @@ class Project < ActiveRecord::Base
end
def cleanup
- @repository&.cleanup
@repository = nil
end
@@ -1113,12 +1117,14 @@ class Project < ActiveRecord::Base
find_or_initialize_services.find { |service| service.to_param == name }
end
+ # rubocop: disable CodeReuse/ServiceClass
def create_labels
Label.templates.each do |label|
params = label.attributes.except('id', 'template', 'created_at', 'updated_at')
Labels::FindOrCreateService.new(nil, self, params).execute(skip_authorization: true)
end
end
+ # rubocop: enable CodeReuse/ServiceClass
def find_service(list, name)
list.find { |service| service.to_param == name }
@@ -1166,6 +1172,7 @@ class Project < ActiveRecord::Base
end
end
+ # rubocop: disable CodeReuse/ServiceClass
def send_move_instructions(old_path_with_namespace)
# New project path needs to be committed to the DB or notification will
# retrieve stale information
@@ -1173,6 +1180,7 @@ class Project < ActiveRecord::Base
NotificationService.new.project_was_moved(self, old_path_with_namespace)
end
end
+ # rubocop: enable CodeReuse/ServiceClass
def owner
if group
@@ -1182,6 +1190,7 @@ class Project < ActiveRecord::Base
end
end
+ # rubocop: disable CodeReuse/ServiceClass
def execute_hooks(data, hooks_scope = :push_hooks)
run_after_commit_or_now do
hooks.hooks_for(hooks_scope).select_active(hooks_scope, data).each do |hook|
@@ -1190,6 +1199,7 @@ class Project < ActiveRecord::Base
SystemHooksService.new.execute_hooks(data, hooks_scope)
end
end
+ # rubocop: enable CodeReuse/ServiceClass
def execute_services(data, hooks_scope = :push_hooks)
# Call only service hooks that are active for this scope
@@ -1486,8 +1496,7 @@ class Project < ActiveRecord::Base
end
def all_runners
- union = Gitlab::SQL::Union.new([runners, group_runners, shared_runners])
- Ci::Runner.from("(#{union.to_sql}) ci_runners")
+ Ci::Runner.from_union([runners, group_runners, shared_runners])
end
def active_runners
@@ -1504,13 +1513,17 @@ class Project < ActiveRecord::Base
self.runners_token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.runners_token)
end
+ # rubocop: disable CodeReuse/ServiceClass
def open_issues_count(current_user = nil)
Projects::OpenIssuesCountService.new(self, current_user).count
end
+ # rubocop: enable CodeReuse/ServiceClass
+ # rubocop: disable CodeReuse/ServiceClass
def open_merge_requests_count
Projects::OpenMergeRequestsCountService.new(self).count
end
+ # rubocop: enable CodeReuse/ServiceClass
def visibility_level_allowed_as_fork?(level = self.visibility_level)
return true unless forked?
@@ -1591,6 +1604,7 @@ class Project < ActiveRecord::Base
end
# TODO: what to do here when not using Legacy Storage? Do we still need to rename and delay removal?
+ # rubocop: disable CodeReuse/ServiceClass
def remove_pages
# Projects with a missing namespace cannot have their pages removed
return unless namespace
@@ -1606,6 +1620,7 @@ class Project < ActiveRecord::Base
PagesWorker.perform_in(5.minutes, :remove, namespace.full_path, temp_path)
end
end
+ # rubocop: enable CodeReuse/ServiceClass
def rename_repo
path_before = previous_changes['path'].first
@@ -1666,6 +1681,7 @@ class Project < ActiveRecord::Base
end
end
+ # rubocop: disable CodeReuse/ServiceClass
def after_create_default_branch
return unless default_branch
@@ -1686,6 +1702,7 @@ class Project < ActiveRecord::Base
ProtectedBranches::CreateService.new(self, creator, params).execute(skip_authorization: true)
end
end
+ # rubocop: enable CodeReuse/ServiceClass
def remove_import_jid
return unless import_jid
@@ -1733,16 +1750,12 @@ class Project < ActiveRecord::Base
import_export_shared.archive_path
end
- def export_project_path
- Dir.glob("#{export_path}/*export.tar.gz").max_by { |f| File.ctime(f) }
- end
-
def export_status
if export_in_progress?
:started
elsif after_export_in_progress?
:after_export_action
- elsif export_project_path || export_project_object_exists?
+ elsif export_file_exists?
:finished
else
:none
@@ -1757,21 +1770,19 @@ class Project < ActiveRecord::Base
import_export_shared.after_export_in_progress?
end
- def remove_exports(path = export_path)
- if path.present?
- FileUtils.rm_rf(path)
- elsif export_project_object_exists?
- import_export_upload.remove_export_file!
- import_export_upload.save
- end
+ def remove_exports
+ return unless export_file_exists?
+
+ import_export_upload.remove_export_file!
+ import_export_upload.save
end
- def remove_exported_project_file
- remove_exports(export_project_path)
+ def export_file_exists?
+ export_file&.file
end
- def export_project_object_exists?
- Gitlab::ImportExport.object_storage? && import_export_upload&.export_file&.file
+ def export_file
+ import_export_upload&.export_file
end
def full_path_slug
@@ -1922,9 +1933,11 @@ class Project < ActiveRecord::Base
# @deprecated cannot remove yet because it has an index with its name in elasticsearch
alias_method :path_with_namespace, :full_path
+ # rubocop: disable CodeReuse/ServiceClass
def forks_count
Projects::ForksCountService.new(self).count
end
+ # rubocop: enable CodeReuse/ServiceClass
def legacy_storage?
[nil, 0].include?(self.storage_version)
@@ -2011,12 +2024,10 @@ class Project < ActiveRecord::Base
def badges
return project_badges unless group
- group_badges_rel = GroupBadge.where(group: group.self_and_ancestors)
-
- union = Gitlab::SQL::Union.new([project_badges.select(:id),
- group_badges_rel.select(:id)])
-
- Badge.where("id IN (#{union.to_sql})") # rubocop:disable GitlabSecurity/SqlInjection
+ Badge.from_union([
+ project_badges,
+ GroupBadge.where(group: group.self_and_ancestors)
+ ])
end
def merge_requests_allowing_push_to_user(user)
@@ -2069,6 +2080,7 @@ class Project < ActiveRecord::Base
private
+ # rubocop: disable CodeReuse/ServiceClass
def rename_or_migrate_repository!
if Gitlab::CurrentSettings.hashed_storage_enabled? &&
storage_upgradable? &&
@@ -2078,6 +2090,7 @@ class Project < ActiveRecord::Base
storage.rename_repo
end
end
+ # rubocop: enable CodeReuse/ServiceClass
def storage_upgradable?
storage_version != LATEST_STORAGE_VERSION
@@ -2102,6 +2115,7 @@ class Project < ActiveRecord::Base
self.project_feature.untrack_statistics_for_deletion!
end
+ # rubocop: disable CodeReuse/ServiceClass
def execute_rename_repository_hooks!(full_path_before)
# When we import a project overwriting the original project, there
# is a move operation. In that case we don't want to send the instructions.
@@ -2112,6 +2126,7 @@ class Project < ActiveRecord::Base
reload_repository!
end
+ # rubocop: enable CodeReuse/ServiceClass
def storage
@storage ||=
diff --git a/app/models/project_authorization.rb b/app/models/project_authorization.rb
index 746bb4584c9..2c590008db2 100644
--- a/app/models/project_authorization.rb
+++ b/app/models/project_authorization.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class ProjectAuthorization < ActiveRecord::Base
+ include FromUnion
+
belongs_to :user
belongs_to :project
@@ -8,9 +10,9 @@ class ProjectAuthorization < ActiveRecord::Base
validates :access_level, inclusion: { in: Gitlab::Access.all_values }, presence: true
validates :user, uniqueness: { scope: [:project, :access_level] }, presence: true
- def self.select_from_union(union)
- select(['project_id', 'MAX(access_level) AS access_level'])
- .from("(#{union.to_sql}) #{ProjectAuthorization.table_name}")
+ def self.select_from_union(relations)
+ from_union(relations)
+ .select(['project_id', 'MAX(access_level) AS access_level'])
.group(:project_id)
end
diff --git a/app/models/project_import_state.rb b/app/models/project_import_state.rb
index 89ed09af96a..d59cb43dea4 100644
--- a/app/models/project_import_state.rb
+++ b/app/models/project_import_state.rb
@@ -48,9 +48,11 @@ class ProjectImportState < ActiveRecord::Base
project.reset_cache_and_import_attrs
if Gitlab::ImportSources.importer_names.include?(project.import_type) && project.repo_exists?
+ # rubocop: disable CodeReuse/ServiceClass
state.run_after_commit do
Projects::AfterImportService.new(project).execute
end
+ # rubocop: enable CodeReuse/ServiceClass
end
end
end
diff --git a/app/models/project_services/asana_service.rb b/app/models/project_services/asana_service.rb
index 35c19049c04..cc5f1207653 100644
--- a/app/models/project_services/asana_service.rb
+++ b/app/models/project_services/asana_service.rb
@@ -65,7 +65,7 @@ http://app.asana.com/-/account_api'
# check the branch restriction is poplulated and branch is not included
branch = Gitlab::Git.ref_name(data[:ref])
branch_restriction = restrict_to_branch.to_s
- if branch_restriction.length > 0 && branch_restriction.index(branch).nil?
+ if branch_restriction.present? && branch_restriction.index(branch).nil?
return
end
@@ -101,7 +101,7 @@ http://app.asana.com/-/account_api'
task.update(completed: true)
end
rescue => e
- Rails.logger.error(e.message)
+ log_error(e.message)
next
end
end
diff --git a/app/models/project_services/chat_message/merge_message.rb b/app/models/project_services/chat_message/merge_message.rb
index 58631e09538..6b7a35aaa75 100644
--- a/app/models/project_services/chat_message/merge_message.rb
+++ b/app/models/project_services/chat_message/merge_message.rb
@@ -26,7 +26,7 @@ module ChatMessage
def activity
{
- title: "Merge Request #{state} by #{user_combined_name}",
+ title: "Merge Request #{state_or_action_text} by #{user_combined_name}",
subtitle: "in #{project_link}",
text: merge_request_link,
image: user_avatar
@@ -48,7 +48,7 @@ module ChatMessage
end
def merge_request_message
- "#{user_combined_name} #{state} #{merge_request_link} in #{project_link}: #{title}"
+ "#{user_combined_name} #{state_or_action_text} #{merge_request_link} in #{project_link}"
end
def merge_request_link
@@ -62,5 +62,10 @@ module ChatMessage
def merge_request_url
"#{project_url}/merge_requests/#{merge_request_iid}"
end
+
+ # overridden in EE
+ def state_or_action_text
+ state
+ end
end
end
diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb
index a783a314071..a15780c14f9 100644
--- a/app/models/project_services/irker_service.rb
+++ b/app/models/project_services/irker_service.rb
@@ -104,7 +104,7 @@ class IrkerService < Service
new_recipient = URI.join(default_irc_uri, '/', recipient).to_s
uri = consider_uri(URI.parse(new_recipient))
rescue
- Rails.logger.error("Unable to create a valid URL from #{default_irc_uri} and #{recipient}")
+ log_error("Unable to create a valid URL", default_irc_uri: default_irc_uri, recipient: recipient)
end
end
diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb
index c7520d766a8..e1d342be188 100644
--- a/app/models/project_services/issue_tracker_service.rb
+++ b/app/models/project_services/issue_tracker_service.rb
@@ -88,7 +88,7 @@ class IssueTrackerService < Service
rescue Gitlab::HTTP::Error, Timeout::Error, SocketError, Errno::ECONNRESET, Errno::ECONNREFUSED, OpenSSL::SSL::SSLError => error
message = "#{self.type} had an error when trying to connect to #{self.project_url}: #{error.message}"
end
- Rails.logger.info(message)
+ log_info(message)
result
end
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index cc98b3f5a41..ba7fcb0cf93 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -205,7 +205,7 @@ class JiraService < IssueTrackerService
begin
issue.transitions.build.save!(transition: { id: transition_id })
rescue => error
- Rails.logger.info "#{self.class.name} Issue Transition failed message ERROR: #{client_url} - #{error.message}"
+ log_error("Issue transition failed", error: error.message, client_url: client_url)
return false
end
end
@@ -257,9 +257,8 @@ class JiraService < IssueTrackerService
new_remote_link.save!(remote_link_props)
end
- result_message = "#{self.class.name} SUCCESS: Successfully posted to #{client_url}."
- Rails.logger.info(result_message)
- result_message
+ log_info("Successfully posted", client_url: client_url)
+ "SUCCESS: Successfully posted to http://jira.example.net."
end
end
@@ -317,7 +316,7 @@ class JiraService < IssueTrackerService
rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, Errno::ECONNREFUSED, URI::InvalidURIError, JIRA::HTTPError, OpenSSL::SSL::SSLError => e
@error = e.message
- Rails.logger.info "#{self.class.name} Send message ERROR: #{client_url} - #{@error}"
+ log_error("Error sending message", client_url: client_url, error: @error)
nil
end
diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb
index bda1f67b8ff..f119555f16b 100644
--- a/app/models/project_services/kubernetes_service.rb
+++ b/app/models/project_services/kubernetes_service.rb
@@ -96,10 +96,10 @@ class KubernetesService < DeploymentService
# Check we can connect to the Kubernetes API
def test(*args)
- kubeclient = build_kubeclient!
+ kubeclient = build_kube_client!
- kubeclient.discover
- { success: kubeclient.discovered, result: "Checked API discovery endpoint" }
+ kubeclient.core_client.discover
+ { success: kubeclient.core_client.discovered, result: "Checked API discovery endpoint" }
rescue => err
{ success: false, result: err }
end
@@ -144,7 +144,7 @@ class KubernetesService < DeploymentService
end
def kubeclient
- @kubeclient ||= build_kubeclient!
+ @kubeclient ||= build_kube_client!(api_groups: ['api', 'apis/rbac.authorization.k8s.io'])
end
def deprecated?
@@ -182,11 +182,12 @@ class KubernetesService < DeploymentService
slug.gsub(/[^-a-z0-9]/, '-').gsub(/^-+/, '')
end
- def build_kubeclient!(api_path: 'api', api_version: 'v1')
+ def build_kube_client!(api_groups: ['api'], api_version: 'v1')
raise "Incomplete settings" unless api_url && actual_namespace && token
- ::Kubeclient::Client.new(
- join_api_url(api_path),
+ Gitlab::Kubernetes::KubeClient.new(
+ api_url,
+ api_groups,
api_version,
auth_options: kubeclient_auth_options,
ssl_options: kubeclient_ssl_options,
@@ -196,7 +197,7 @@ class KubernetesService < DeploymentService
# Returns a hash of all pods in the namespace
def read_pods
- kubeclient = build_kubeclient!
+ kubeclient = build_kube_client!
kubeclient.get_pods(namespace: actual_namespace).as_json
rescue Kubeclient::HttpError => err
@@ -220,15 +221,6 @@ class KubernetesService < DeploymentService
{ bearer_token: token }
end
- def join_api_url(api_path)
- url = URI.parse(api_url)
- prefix = url.path.sub(%r{/+\z}, '')
-
- url.path = [prefix, api_path].join("/")
-
- url.to_s
- end
-
def terminal_auth
{
token: token,
diff --git a/app/models/project_services/slash_commands_service.rb b/app/models/project_services/slash_commands_service.rb
index e3ab60adefd..bfabc6d262c 100644
--- a/app/models/project_services/slash_commands_service.rb
+++ b/app/models/project_services/slash_commands_service.rb
@@ -44,11 +44,15 @@ class SlashCommandsService < Service
private
+ # rubocop: disable CodeReuse/ServiceClass
def find_chat_user(params)
ChatNames::FindUserService.new(self, params).execute
end
+ # rubocop: enable CodeReuse/ServiceClass
+ # rubocop: disable CodeReuse/ServiceClass
def authorize_chat_name_url(params)
ChatNames::AuthorizeUserService.new(self, params).execute
end
+ # rubocop: enable CodeReuse/ServiceClass
end
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index f4b3421f04b..559e4f99294 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -80,7 +80,7 @@ class ProjectWiki
pages(limit: 1).empty?
end
- # Returns an Array of Gitlab WikiPage instances or an
+ # Returns an Array of GitLab WikiPage instances or an
# empty Array if this Wiki has no pages.
def pages(limit: 0)
wiki.pages(limit: limit).map { |page| WikiPage.new(self, page, true) }
@@ -184,11 +184,12 @@ class ProjectWiki
def commit_details(action, message = nil, title = nil)
commit_message = message || default_message(action, title)
+ git_user = Gitlab::Git::User.from_gitlab(@user)
Gitlab::Git::Wiki::CommitDetails.new(@user.id,
- @user.username,
- @user.name,
- @user.email,
+ git_user.username,
+ git_user.name,
+ git_user.email,
commit_message)
end
diff --git a/app/models/prometheus_metric.rb b/app/models/prometheus_metric.rb
new file mode 100644
index 00000000000..ce2db9cb44c
--- /dev/null
+++ b/app/models/prometheus_metric.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+class PrometheusMetric < ActiveRecord::Base
+ belongs_to :project, validate: true, inverse_of: :prometheus_metrics
+
+ enum group: {
+ # built-in groups
+ nginx_ingress: -1,
+ ha_proxy: -2,
+ aws_elb: -3,
+ nginx: -4,
+ kubernetes: -5,
+
+ # custom/user groups
+ business: 0,
+ response: 1,
+ system: 2
+ }
+
+ validates :title, presence: true
+ validates :query, presence: true
+ validates :group, presence: true
+ validates :y_label, presence: true
+ validates :unit, presence: true
+
+ validates :project, presence: true, unless: :common?
+ validates :project, absence: true, if: :common?
+
+ scope :common, -> { where(common: true) }
+
+ GROUP_TITLES = {
+ # built-in groups
+ nginx_ingress: _('Response metrics (NGINX Ingress)'),
+ ha_proxy: _('Response metrics (HA Proxy)'),
+ aws_elb: _('Response metrics (AWS ELB)'),
+ nginx: _('Response metrics (NGINX)'),
+ kubernetes: _('System metrics (Kubernetes)'),
+
+ # custom/user groups
+ business: _('Business metrics (Custom)'),
+ response: _('Response metrics (Custom)'),
+ system: _('System metrics (Custom)')
+ }.freeze
+
+ REQUIRED_METRICS = {
+ nginx_ingress: %w(nginx_upstream_responses_total nginx_upstream_response_msecs_avg),
+ ha_proxy: %w(haproxy_frontend_http_requests_total haproxy_frontend_http_responses_total),
+ aws_elb: %w(aws_elb_request_count_sum aws_elb_latency_average aws_elb_httpcode_backend_5_xx_sum),
+ nginx: %w(nginx_server_requests nginx_server_requestMsec),
+ kubernetes: %w(container_memory_usage_bytes container_cpu_usage_seconds_total)
+ }.freeze
+
+ def group_title
+ GROUP_TITLES[group.to_sym]
+ end
+
+ def required_metrics
+ REQUIRED_METRICS[group.to_sym].to_a.map(&:to_s)
+ end
+
+ def to_query_metric
+ Gitlab::Prometheus::Metric.new(id: id, title: title, required_metrics: required_metrics, weight: 0, y_label: y_label, queries: queries)
+ end
+
+ def queries
+ [
+ {
+ query_range: query,
+ unit: unit,
+ label: legend,
+ series: query_series
+ }.compact
+ ]
+ end
+
+ def query_series
+ case legend
+ when 'Status Code'
+ [{
+ label: 'status_code',
+ when: [
+ { value: '2xx', color: 'green' },
+ { value: '4xx', color: 'orange' },
+ { value: '5xx', color: 'red' }
+ ]
+ }]
+ end
+ end
+end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index cf255c8951f..12fbf7d5d1d 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -81,10 +81,6 @@ class Repository
alias_method :raw, :raw_repository
- def cleanup
- @raw_repository&.cleanup
- end
-
# Don't use this! It's going away. Use Gitaly to read or write from repos.
def path_to_repo
@path_to_repo ||=
@@ -580,7 +576,12 @@ class Repository
end
def rendered_readme
- MarkupHelper.markup_unsafe(readme.name, readme.data, project: project, markdown_engine: :redcarpet) if readme
+ return unless readme
+
+ context = { project: project }
+ context[:markdown_engine] = :redcarpet unless MarkupHelper.commonmark_for_repositories_enabled?
+
+ MarkupHelper.markup_unsafe(readme.name, readme.data, context)
end
cache_method :rendered_readme
@@ -994,14 +995,6 @@ class Repository
remote_branch: merge_request.target_branch)
end
- def blob_data_at(sha, path)
- blob = blob_at(sha, path)
- return unless blob
-
- blob.load_all_data!
- blob.data
- end
-
def squash(user, merge_request)
raw.squash(user, merge_request.id, branch: merge_request.target_branch,
start_sha: merge_request.diff_start_sha,
@@ -1010,6 +1003,14 @@ class Repository
message: merge_request.title)
end
+ def blob_data_at(sha, path)
+ blob = blob_at(sha, path)
+ return unless blob
+
+ blob.load_all_data!
+ blob.data
+ end
+
private
# TODO Generice finder, later split this on finders by Ref or Oid
diff --git a/app/models/resource_label_event.rb b/app/models/resource_label_event.rb
index 42c255fcd1e..3fd96b9dc18 100644
--- a/app/models/resource_label_event.rb
+++ b/app/models/resource_label_event.rb
@@ -3,33 +3,122 @@
# This model is not used yet, it will be used for:
# https://gitlab.com/gitlab-org/gitlab-ce/issues/48483
class ResourceLabelEvent < ActiveRecord::Base
+ include Importable
+ include Gitlab::Utils::StrongMemoize
+ include CacheMarkdownField
+
+ cache_markdown_field :reference
+
belongs_to :user
belongs_to :issue
belongs_to :merge_request
belongs_to :label
- validates :user, presence: true, on: :create
- validates :label, presence: true, on: :create
+ scope :created_after, ->(time) { where('created_at > ?', time) }
+
+ validates :user, presence: { unless: :importing? }, on: :create
+ validates :label, presence: { unless: :importing? }, on: :create
validate :exactly_one_issuable
+ after_save :expire_etag_cache
+ after_destroy :expire_etag_cache
+
enum action: {
add: 1,
remove: 2
}
- def self.issuable_columns
- %i(issue_id merge_request_id).freeze
+ def self.issuable_attrs
+ %i(issue merge_request).freeze
end
def issuable
issue || merge_request
end
+ # create same discussion id for all actions with the same user and time
+ def discussion_id(resource = nil)
+ strong_memoize(:discussion_id) do
+ Digest::SHA1.hexdigest([self.class.name, created_at, user_id].join("-"))
+ end
+ end
+
+ def project
+ issuable.project
+ end
+
+ def group
+ issuable.group if issuable.respond_to?(:group)
+ end
+
+ def outdated_markdown?
+ return true if label_id.nil? && reference.present?
+
+ reference.nil? || latest_cached_markdown_version != cached_markdown_version
+ end
+
+ def banzai_render_context(field)
+ super.merge(pipeline: 'label', only_path: true)
+ end
+
+ def refresh_invalid_reference
+ # label_id could be nullified on label delete
+ self.reference = '' if label_id.nil?
+
+ # reference is not set for events which were not rendered yet
+ self.reference ||= label_reference
+
+ if changed?
+ save
+ elsif invalidated_markdown_cache?
+ refresh_markdown_cache!
+ end
+ end
+
private
+ def label_reference
+ if local_label?
+ label.to_reference(format: :id)
+ elsif label.is_a?(GroupLabel)
+ label.to_reference(label.group, target_project: resource_parent, format: :id)
+ else
+ label.to_reference(resource_parent, format: :id)
+ end
+ end
+
def exactly_one_issuable
- if self.class.issuable_columns.count { |attr| self[attr] } != 1
- errors.add(:base, "Exactly one of #{self.class.issuable_columns.join(', ')} is required")
+ issuable_count = self.class.issuable_attrs.count { |attr| self["#{attr}_id"] }
+
+ return true if issuable_count == 1
+
+ # if none of issuable IDs is set, check explicitly if nested issuable
+ # object is set, this is used during project import
+ if issuable_count == 0 && importing?
+ issuable_count = self.class.issuable_attrs.count { |attr| self.public_send(attr) } # rubocop:disable GitlabSecurity/PublicSend
+
+ return true if issuable_count == 1
end
+
+ errors.add(:base, "Exactly one of #{self.class.issuable_attrs.join(', ')} is required")
+ end
+
+ def expire_etag_cache
+ issuable.expire_note_etag_cache
+ end
+
+ def local_label?
+ params = { include_ancestor_groups: true }
+ if resource_parent.is_a?(Project)
+ params[:project_id] = resource_parent.id
+ else
+ params[:group_id] = resource_parent.id
+ end
+
+ LabelsFinder.new(nil, params).execute(skip_authorization: true).where(id: label.id).any?
+ end
+
+ def resource_parent
+ issuable.project || issuable.group
end
end
diff --git a/app/models/service.rb b/app/models/service.rb
index 140058771ee..4dbda7acab6 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -5,6 +5,7 @@
class Service < ActiveRecord::Base
include Sortable
include Importable
+ include ProjectServicesLoggable
serialize :properties, JSON # rubocop:disable Cop/ActiveRecordSerialize
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index 5b394e3fa79..e9533ee7c77 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -12,6 +12,7 @@ class Snippet < ActiveRecord::Base
include Spammable
include Editable
include Gitlab::SQL::Pattern
+ include FromUnion
cache_markdown_field :title, pipeline: :single_line
cache_markdown_field :description
diff --git a/app/models/system_note_metadata.rb b/app/models/system_note_metadata.rb
index 376ef673ca8..6fadbcefa53 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 tag
+ outdated tag due_date
].freeze
validates :note, presence: true
diff --git a/app/models/todo.rb b/app/models/todo.rb
index 48d92ad04b3..265fb932f7c 100644
--- a/app/models/todo.rb
+++ b/app/models/todo.rb
@@ -2,6 +2,7 @@
class Todo < ActiveRecord::Base
include Sortable
+ include FromUnion
ASSIGNED = 1
MENTIONED = 2
diff --git a/app/models/user.rb b/app/models/user.rb
index f21ca1c569f..eeac87e2e52 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -20,6 +20,7 @@ class User < ActiveRecord::Base
include BlocksJsonSerialization
include WithUploads
include OptionallySearch
+ include FromUnion
DEFAULT_NOTIFICATION_LEVEL = :participating
@@ -61,6 +62,7 @@ class User < ActiveRecord::Base
# Override Devise::Models::Trackable#update_tracked_fields!
# to limit database writes to at most once every hour
+ # rubocop: disable CodeReuse/ServiceClass
def update_tracked_fields!(request)
return if Gitlab::Database.read_only?
@@ -71,6 +73,7 @@ class User < ActiveRecord::Base
Users::UpdateService.new(self, user: self).execute(validate: false)
end
+ # rubocop: enable CodeReuse/ServiceClass
attr_accessor :force_random_password
@@ -159,6 +162,7 @@ class User < ActiveRecord::Base
validates :notification_email, presence: true
validates :notification_email, email: true, if: ->(user) { user.notification_email != user.email }
validates :public_email, presence: true, uniqueness: true, email: true, allow_blank: true
+ validates :commit_email, email: true, allow_nil: true, if: ->(user) { user.commit_email != user.email }
validates :bio, length: { maximum: 255 }, allow_blank: true
validates :projects_limit,
presence: true,
@@ -171,12 +175,15 @@ class User < ActiveRecord::Base
validate :unique_email, if: :email_changed?
validate :owns_notification_email, if: :notification_email_changed?
validate :owns_public_email, if: :public_email_changed?
+ validate :owns_commit_email, if: :commit_email_changed?
validate :signup_domain_valid?, on: :create, if: ->(user) { !user.created_by_id }
before_validation :sanitize_attrs
before_validation :set_notification_email, if: :new_record?
before_validation :set_public_email, if: :public_email_changed?
+ before_validation :set_commit_email, if: :commit_email_changed?
before_save :set_public_email, if: :public_email_changed? # in case validation is skipped
+ before_save :set_commit_email, if: :commit_email_changed? # in case validation is skipped
before_save :ensure_incoming_email_token
before_save :ensure_user_rights_and_limits, if: ->(user) { user.new_record? || user.external_changed? }
before_save :skip_reconfirmation!, if: ->(user) { user.email_changed? && user.read_only_attribute?(:email) }
@@ -257,6 +264,7 @@ class User < ActiveRecord::Base
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) }
+ scope :by_username, -> (usernames) { iwhere(username: usernames) }
# Limits the users to those that have TODOs, optionally in the given state.
#
@@ -279,11 +287,9 @@ class User < ActiveRecord::Base
# 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}")
+ User.unscoped.from_union([all, User.unscoped.where(id: user_id)])
else
all
end
@@ -347,9 +353,8 @@ class User < ActiveRecord::Base
emails = joins(:emails).where(emails: { email: email })
emails = emails.confirmed if confirmed
- union = Gitlab::SQL::Union.new([users, emails])
- from("(#{union.to_sql}) #{table_name}")
+ from_union([users, emails])
end
def filter(filter_name)
@@ -444,17 +449,17 @@ class User < ActiveRecord::Base
end
def find_by_username(username)
- iwhere(username: username).take
+ by_username(username).take
end
def find_by_username!(username)
- iwhere(username: username).take!
+ by_username(username).take!
end
def find_by_personal_access_token(token_string)
return unless token_string
- PersonalAccessTokensFinder.new(state: 'active').find_by(token: token_string)&.user
+ PersonalAccessTokensFinder.new(state: 'active').find_by(token: token_string)&.user # rubocop: disable CodeReuse/Finder
end
# Returns a user for the given SSH key.
@@ -489,6 +494,16 @@ class User < ActiveRecord::Base
u.name = 'Ghost User'
end
end
+
+ # Return true if there is only single non-internal user in the deployment,
+ # ghost user is ignored.
+ def single_user?
+ User.non_internal.limit(2).count == 1
+ end
+
+ def single_user
+ User.non_internal.first if single_user?
+ end
end
def full_path
@@ -606,6 +621,32 @@ class User < ActiveRecord::Base
errors.add(:public_email, "is not an email you own") unless all_emails.include?(public_email)
end
+ def owns_commit_email
+ return if read_attribute(:commit_email).blank?
+
+ errors.add(:commit_email, "is not an email you own") unless verified_emails.include?(commit_email)
+ end
+
+ # Define commit_email-related attribute methods explicitly instead of relying
+ # on ActiveRecord to provide them. Some of the specs use the current state of
+ # the model code but an older database schema, so we need to guard against the
+ # possibility of the commit_email column not existing.
+
+ def commit_email
+ return self.email unless has_attribute?(:commit_email)
+
+ # The commit email is the same as the primary email if undefined
+ super.presence || self.email
+ end
+
+ def commit_email=(email)
+ super if has_attribute?(:commit_email)
+ end
+
+ def commit_email_changed?
+ has_attribute?(:commit_email) && super
+ end
+
# see if the new email is already a verified secondary email
def check_for_verified_email
skip_reconfirmation! if emails.confirmed.where(email: self.email).any?
@@ -616,6 +657,7 @@ class User < ActiveRecord::Base
# hash and `_was` variables getting munged.
# By using an `after_commit` instead of `after_update`, we avoid the recursive callback
# scenario, though it then requires us to use the `previous_changes` hash
+ # rubocop: disable CodeReuse/ServiceClass
def update_emails_with_primary_email(previous_email)
primary_email_record = emails.find_by(email: email)
Emails::DestroyService.new(self, user: self).execute(primary_email_record) if primary_email_record
@@ -624,6 +666,7 @@ class User < ActiveRecord::Base
# have access to the original confirmation values at this point, so just set confirmed_at
Emails::CreateService.new(self, user: self, email: previous_email).execute(confirmed_at: confirmed_at)
end
+ # rubocop: enable CodeReuse/ServiceClass
def update_invalid_gpg_signatures
gpg_keys.each(&:update_invalid_gpg_signatures)
@@ -631,10 +674,10 @@ class User < ActiveRecord::Base
# Returns the groups a user has access to, either through a membership or a project authorization
def authorized_groups
- union = Gitlab::SQL::Union
- .new([groups.select(:id), authorized_projects.select(:namespace_id)])
-
- Group.where("namespaces.id IN (#{union.to_sql})") # rubocop:disable GitlabSecurity/SqlInjection
+ Group.from_union([
+ groups,
+ authorized_projects.joins(:namespace).select('namespaces.*')
+ ])
end
# Returns the groups a user is a member of, either directly or through a parent group
@@ -652,9 +695,11 @@ class User < ActiveRecord::Base
all_expanded_groups.where(require_two_factor_authentication: true)
end
+ # rubocop: disable CodeReuse/ServiceClass
def refresh_authorized_projects
Users::RefreshAuthorizedProjectsService.new(self).execute
end
+ # rubocop: enable CodeReuse/ServiceClass
def remove_project_authorizations(project_ids)
project_authorizations.where(project_id: project_ids).delete_all
@@ -697,7 +742,15 @@ class User < ActiveRecord::Base
end
def owned_projects
- @owned_projects ||= Project.from("(#{owned_projects_union.to_sql}) AS projects")
+ @owned_projects ||= Project.from_union(
+ [
+ Project.where(namespace: namespace),
+ Project.joins(:project_authorizations)
+ .where("projects.namespace_id <> ?", namespace.id)
+ .where(project_authorizations: { user_id: id, access_level: Gitlab::Access::OWNER })
+ ],
+ remove_duplicates: false
+ )
end
# Returns projects which user can admin issues on (for example to move an issue to that project).
@@ -707,11 +760,13 @@ class User < ActiveRecord::Base
authorized_projects(Gitlab::Access::REPORTER).non_archived.with_issues_enabled
end
+ # rubocop: disable CodeReuse/ServiceClass
def require_ssh_key?
count = Users::KeysCountService.new(self).count
count.zero? && Gitlab::ProtocolAccess.allowed?('ssh')
end
+ # rubocop: enable CodeReuse/ServiceClass
def require_password_creation_for_web?
allow_password_authentication_for_web? && password_automatically_set?
@@ -775,6 +830,7 @@ class User < ActiveRecord::Base
projects_limit - personal_projects_count
end
+ # rubocop: disable CodeReuse/ServiceClass
def recent_push(project = nil)
service = Users::LastPushEventService.new(self)
@@ -784,6 +840,7 @@ class User < ActiveRecord::Base
service.last_event_for_user
end
end
+ # rubocop: enable CodeReuse/ServiceClass
def several_namespaces?
owned_groups.any? || maintainers_groups.any?
@@ -852,10 +909,17 @@ class User < ActiveRecord::Base
end
end
+ def set_commit_email
+ if commit_email.blank? || verified_emails.exclude?(commit_email)
+ self.commit_email = nil
+ end
+ end
+
def update_secondary_emails!
set_notification_email
set_public_email
- save if notification_email_changed? || public_email_changed?
+ set_commit_email
+ save if notification_email_changed? || public_email_changed? || commit_email_changed?
end
def set_projects_limit
@@ -921,9 +985,11 @@ class User < ActiveRecord::Base
email.start_with?('temp-email-for-oauth')
end
+ # rubocop: disable CodeReuse/ServiceClass
def avatar_url(size: nil, scale: 2, **args)
GravatarService.new.execute(email, size, scale, username: username)
end
+ # rubocop: enable CodeReuse/ServiceClass
def primary_email_verified?
confirmed? && !temp_oauth_email?
@@ -989,26 +1055,32 @@ class User < ActiveRecord::Base
system_hook_service.execute_hooks_for(self, :destroy)
end
+ # rubocop: disable CodeReuse/ServiceClass
def remove_key_cache
Users::KeysCountService.new(self).delete_cache
end
+ # rubocop: enable CodeReuse/ServiceClass
def delete_async(deleted_by:, params: {})
block if params[:hard_delete]
DeleteUserWorker.perform_async(deleted_by.id, id, params.to_h)
end
+ # rubocop: disable CodeReuse/ServiceClass
def notification_service
NotificationService.new
end
+ # rubocop: enable CodeReuse/ServiceClass
def log_info(message)
Gitlab::AppLogger.info message
end
+ # rubocop: disable CodeReuse/ServiceClass
def system_hook_service
SystemHooksService.new
end
+ # rubocop: enable CodeReuse/ServiceClass
def starred?(project)
starred_projects.exists?(project.id)
@@ -1072,17 +1144,17 @@ class User < ActiveRecord::Base
def ci_owned_runners
@ci_owned_runners ||= begin
- project_runner_ids = Ci::RunnerProject
+ project_runners = Ci::RunnerProject
.where(project: authorized_projects(Gitlab::Access::MAINTAINER))
- .select(:runner_id)
+ .joins(:runner)
+ .select('ci_runners.*')
- group_runner_ids = Ci::RunnerNamespace
+ group_runners = Ci::RunnerNamespace
.where(namespace_id: owned_or_maintainers_groups.select(:id))
- .select(:runner_id)
-
- union = Gitlab::SQL::Union.new([project_runner_ids, group_runner_ids])
+ .joins(:runner)
+ .select('ci_runners.*')
- Ci::Runner.where("ci_runners.id IN (#{union.to_sql})") # rubocop:disable GitlabSecurity/SqlInjection
+ Ci::Runner.from_union([project_runners, group_runners])
end
end
@@ -1110,13 +1182,13 @@ class User < ActiveRecord::Base
def assigned_open_merge_requests_count(force: false)
Rails.cache.fetch(['users', id, 'assigned_open_merge_requests_count'], force: force, expires_in: 20.minutes) do
- MergeRequestsFinder.new(self, assignee_id: self.id, state: 'opened').execute.count
+ MergeRequestsFinder.new(self, assignee_id: self.id, state: 'opened', non_archived: true).execute.count
end
end
def assigned_open_issues_count(force: false)
Rails.cache.fetch(['users', id, 'assigned_open_issues_count'], force: force, expires_in: 20.minutes) do
- IssuesFinder.new(self, assignee_id: self.id, state: 'opened').execute.count
+ IssuesFinder.new(self, assignee_id: self.id, state: 'opened', non_archived: true).execute.count
end
end
@@ -1177,6 +1249,7 @@ class User < ActiveRecord::Base
# See:
# <https://github.com/plataformatec/devise/blob/v4.0.0/lib/devise/models/lockable.rb#L92>
#
+ # rubocop: disable CodeReuse/ServiceClass
def increment_failed_attempts!
return if ::Gitlab::Database.read_only?
@@ -1189,6 +1262,7 @@ class User < ActiveRecord::Base
Users::UpdateService.new(self, user: self).execute(validate: false)
end
end
+ # rubocop: enable CodeReuse/ServiceClass
def access_level
if admin?
@@ -1286,6 +1360,10 @@ class User < ActiveRecord::Base
!terms_accepted?
end
+ def requires_usage_stats_consent?
+ !consented_usage_stats? && 7.days.ago > self.created_at && !has_current_license? && User.single_user?
+ end
+
# @deprecated
alias_method :owned_or_masters_groups, :owned_or_maintainers_groups
@@ -1300,13 +1378,12 @@ class User < ActiveRecord::Base
private
- def owned_projects_union
- Gitlab::SQL::Union.new([
- Project.where(namespace: namespace),
- Project.joins(:project_authorizations)
- .where("projects.namespace_id <> ?", namespace.id)
- .where(project_authorizations: { user_id: id, access_level: Gitlab::Access::OWNER })
- ], remove_duplicates: false)
+ def has_current_license?
+ false
+ end
+
+ def consented_usage_stats?
+ Gitlab::CurrentSettings.usage_stats_set_by_user_id == self.id
end
# Added according to https://github.com/plataformatec/devise/blob/7df57d5081f9884849ca15e4fde179ef164a575f/README.md#activejob-integration
@@ -1417,7 +1494,7 @@ class User < ActiveRecord::Base
&creation_block
)
- Users::UpdateService.new(user, user: user).execute(validate: false)
+ Users::UpdateService.new(user, user: user).execute(validate: false) # rubocop: disable CodeReuse/ServiceClass
user
ensure
Gitlab::ExclusiveLease.cancel(lease_key, uuid)
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
index 33790afc35e..102907a8bd3 100644
--- a/app/models/wiki_page.rb
+++ b/app/models/wiki_page.rb
@@ -51,7 +51,7 @@ class WikiPage
validates :title, presence: true
validates :content, presence: true
- # The Gitlab ProjectWiki instance.
+ # The GitLab ProjectWiki instance.
attr_reader :wiki
# The raw Gitlab::Git::WikiPage instance.
@@ -127,7 +127,7 @@ class WikiPage
version.try(:message)
end
- # The Gitlab Commit instance for this page.
+ # The GitLab Commit instance for this page.
def version
return nil unless persisted?
diff --git a/app/policies/application_setting/term_policy.rb b/app/policies/application_setting/term_policy.rb
index 17f00f33d35..c0d2ceaa349 100644
--- a/app/policies/application_setting/term_policy.rb
+++ b/app/policies/application_setting/term_policy.rb
@@ -19,6 +19,7 @@ class ApplicationSetting
rule { terms_accepted }.prevent :accept_terms
+ # rubocop: disable CodeReuse/ActiveRecord
def agreement
strong_memoize(:agreement) do
next nil if @user.nil? || @subject.nil?
@@ -26,5 +27,6 @@ class ApplicationSetting
@user.term_agreements.find_by(term: @subject)
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/app/policies/ci/runner_policy.rb b/app/policies/ci/runner_policy.rb
index c44f22b6ad3..de76b7b2b5b 100644
--- a/app/policies/ci/runner_policy.rb
+++ b/app/policies/ci/runner_policy.rb
@@ -5,7 +5,9 @@ module Ci
with_options scope: :subject, score: 0
condition(:locked, scope: :subject) { @subject.locked? }
+ # rubocop: disable CodeReuse/ActiveRecord
condition(:owned_runner) { @user.ci_owned_runners.exists?(@subject.id) }
+ # rubocop: enable CodeReuse/ActiveRecord
rule { anonymous }.prevent_all
diff --git a/app/policies/deploy_key_policy.rb b/app/policies/deploy_key_policy.rb
index 204c54a5b20..7f0ec011e79 100644
--- a/app/policies/deploy_key_policy.rb
+++ b/app/policies/deploy_key_policy.rb
@@ -4,7 +4,9 @@ class DeployKeyPolicy < BasePolicy
with_options scope: :subject, score: 0
condition(:private_deploy_key) { @subject.private? }
+ # rubocop: disable CodeReuse/ActiveRecord
condition(:has_deploy_key) { @user.project_deploy_keys.exists?(id: @subject.id) }
+ # rubocop: enable CodeReuse/ActiveRecord
rule { anonymous }.prevent_all
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 273a93a1423..d0e84b1aa38 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -398,6 +398,7 @@ class ProjectPolicy < BasePolicy
end
end
+ # rubocop: disable CodeReuse/ActiveRecord
def project_group_member?
return false if @user.nil?
@@ -407,6 +408,7 @@ class ProjectPolicy < BasePolicy
project.group.requesters.exists?(user_id: @user.id)
)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def team_access_level
return -1 if @user.nil?
diff --git a/app/presenters/commit_status_presenter.rb b/app/presenters/commit_status_presenter.rb
index a08f34e2335..65e77ea3f92 100644
--- a/app/presenters/commit_status_presenter.rb
+++ b/app/presenters/commit_status_presenter.rb
@@ -11,10 +11,16 @@ class CommitStatusPresenter < Gitlab::View::Presenter::Delegated
runner_unsupported: 'Your runner is outdated, please upgrade your runner'
}.freeze
+ private_constant :CALLOUT_FAILURE_MESSAGES
+
presents :build
+ def self.callout_failure_messages
+ CALLOUT_FAILURE_MESSAGES
+ end
+
def callout_failure_message
- CALLOUT_FAILURE_MESSAGES.fetch(failure_reason.to_sym)
+ self.class.callout_failure_messages.fetch(failure_reason.to_sym)
end
def recoverable?
diff --git a/app/presenters/conversational_development_index/metric_presenter.rb b/app/presenters/conversational_development_index/metric_presenter.rb
index e0312c6f431..9639b84cf56 100644
--- a/app/presenters/conversational_development_index/metric_presenter.rb
+++ b/app/presenters/conversational_development_index/metric_presenter.rb
@@ -139,8 +139,10 @@ module ConversationalDevelopmentIndex
]
end
+ # rubocop: disable CodeReuse/ActiveRecord
def average_percentage_score
cards.sum(&:percentage_score) / cards.size.to_f
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/app/presenters/merge_request_presenter.rb b/app/presenters/merge_request_presenter.rb
index 8c4eac3c31d..3f565b826dd 100644
--- a/app/presenters/merge_request_presenter.rb
+++ b/app/presenters/merge_request_presenter.rb
@@ -142,6 +142,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
end
def assign_to_closing_issues_link
+ # rubocop: disable CodeReuse/ServiceClass
issues = MergeRequests::AssignIssuesService.new(project,
current_user,
merge_request: merge_request,
@@ -152,6 +153,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
pluralize_this_issue = issues.count > 1 ? "these issues" : "this issue"
link_to "Assign yourself to #{pluralize_this_issue}", path, method: :post
end
+ # rubocop: enable CodeReuse/ServiceClass
end
def can_revert_on_current_merge_request?
@@ -202,7 +204,9 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
end
def conflicts
+ # rubocop: disable CodeReuse/ServiceClass
@conflicts ||= MergeRequests::Conflicts::ListService.new(merge_request)
+ # rubocop: enable CodeReuse/ServiceClass
end
def closing_issues
diff --git a/app/presenters/project_presenter.rb b/app/presenters/project_presenter.rb
index 4c2f33213d6..d2434d96fd7 100644
--- a/app/presenters/project_presenter.rb
+++ b/app/presenters/project_presenter.rb
@@ -11,16 +11,18 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
presents :project
+ AnchorData = Struct.new(:enabled, :label, :link, :class_modifier)
+ MAX_TAGS_TO_SHOW = 3
+
def statistics_anchors(show_auto_devops_callout:)
[
+ readme_anchor_data,
+ changelog_anchor_data,
+ contribution_guide_anchor_data,
files_anchor_data,
commits_anchor_data,
branches_anchor_data,
tags_anchor_data,
- readme_anchor_data,
- changelog_anchor_data,
- license_anchor_data,
- contribution_guide_anchor_data,
gitlab_ci_anchor_data,
autodevops_anchor_data(show_auto_devops_callout: show_auto_devops_callout),
kubernetes_cluster_anchor_data
@@ -31,7 +33,6 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
[
readme_anchor_data,
changelog_anchor_data,
- license_anchor_data,
contribution_guide_anchor_data,
autodevops_anchor_data(show_auto_devops_callout: show_auto_devops_callout),
kubernetes_cluster_anchor_data,
@@ -42,6 +43,10 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
def empty_repo_statistics_anchors
[
+ files_anchor_data,
+ commits_anchor_data,
+ branches_anchor_data,
+ tags_anchor_data,
autodevops_anchor_data,
kubernetes_cluster_anchor_data
].compact.select { |item| item.enabled }
@@ -51,7 +56,6 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
[
new_file_anchor_data,
readme_anchor_data,
- license_anchor_data,
autodevops_anchor_data,
kubernetes_cluster_anchor_data
].compact.reject { |item| item.enabled }
@@ -182,95 +186,101 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
end
def files_anchor_data
- OpenStruct.new(enabled: true,
- label: _('Files (%{human_size})') % { human_size: storage_counter(statistics.total_repository_size) },
- link: project_tree_path(project))
+ AnchorData.new(true,
+ _('Files (%{human_size})') % { human_size: storage_counter(statistics.total_repository_size) },
+ empty_repo? ? nil : project_tree_path(project))
end
def commits_anchor_data
- OpenStruct.new(enabled: true,
- label: n_('Commit (%{commit_count})', 'Commits (%{commit_count})', statistics.commit_count) % { commit_count: number_with_delimiter(statistics.commit_count) },
- link: project_commits_path(project, repository.root_ref))
+ AnchorData.new(true,
+ n_('Commit (%{commit_count})', 'Commits (%{commit_count})', statistics.commit_count) % { commit_count: number_with_delimiter(statistics.commit_count) },
+ empty_repo? ? nil : project_commits_path(project, repository.root_ref))
end
def branches_anchor_data
- OpenStruct.new(enabled: true,
- label: n_('Branch (%{branch_count})', 'Branches (%{branch_count})', repository.branch_count) % { branch_count: number_with_delimiter(repository.branch_count) },
- link: project_branches_path(project))
+ AnchorData.new(true,
+ n_('Branch (%{branch_count})', 'Branches (%{branch_count})', repository.branch_count) % { branch_count: number_with_delimiter(repository.branch_count) },
+ empty_repo? ? nil : project_branches_path(project))
end
def tags_anchor_data
- OpenStruct.new(enabled: true,
- label: n_('Tag (%{tag_count})', 'Tags (%{tag_count})', repository.tag_count) % { tag_count: number_with_delimiter(repository.tag_count) },
- link: project_tags_path(project))
+ AnchorData.new(true,
+ n_('Tag (%{tag_count})', 'Tags (%{tag_count})', repository.tag_count) % { tag_count: number_with_delimiter(repository.tag_count) },
+ empty_repo? ? nil : project_tags_path(project))
end
def new_file_anchor_data
if current_user && can_current_user_push_to_default_branch?
- OpenStruct.new(enabled: false,
- label: _('New file'),
- link: project_new_blob_path(project, default_branch || 'master'),
- class_modifier: 'new')
+ AnchorData.new(false,
+ _('New file'),
+ project_new_blob_path(project, default_branch || 'master'),
+ 'new')
end
end
def readme_anchor_data
if current_user && can_current_user_push_to_default_branch? && repository.readme.nil?
- OpenStruct.new(enabled: false,
- label: _('Add Readme'),
- link: add_readme_path)
+ AnchorData.new(false,
+ _('Add Readme'),
+ add_readme_path)
elsif repository.readme
- OpenStruct.new(enabled: true,
- label: _('Readme'),
- link: default_view != 'readme' ? readme_path : '#readme')
+ AnchorData.new(true,
+ _('Readme'),
+ default_view != 'readme' ? readme_path : '#readme')
end
end
def changelog_anchor_data
if current_user && can_current_user_push_to_default_branch? && repository.changelog.blank?
- OpenStruct.new(enabled: false,
- label: _('Add Changelog'),
- link: add_changelog_path)
+ AnchorData.new(false,
+ _('Add Changelog'),
+ add_changelog_path)
elsif repository.changelog.present?
- OpenStruct.new(enabled: true,
- label: _('Changelog'),
- link: changelog_path)
+ AnchorData.new(true,
+ _('Changelog'),
+ changelog_path)
end
end
def license_anchor_data
- if current_user && can_current_user_push_to_default_branch? && repository.license_blob.blank?
- OpenStruct.new(enabled: false,
- label: _('Add License'),
- link: add_license_path)
- elsif repository.license_blob.present?
- OpenStruct.new(enabled: true,
- label: license_short_name,
- link: license_path)
+ if repository.license_blob.present?
+ AnchorData.new(true,
+ license_short_name,
+ license_path)
+ else
+ if current_user && can_current_user_push_to_default_branch?
+ AnchorData.new(false,
+ _('Add license'),
+ add_license_path)
+ else
+ AnchorData.new(false,
+ _('No license. All rights reserved'),
+ nil)
+ end
end
end
def contribution_guide_anchor_data
if current_user && can_current_user_push_to_default_branch? && repository.contribution_guide.blank?
- OpenStruct.new(enabled: false,
- label: _('Add Contribution guide'),
- link: add_contribution_guide_path)
+ AnchorData.new(false,
+ _('Add Contribution guide'),
+ add_contribution_guide_path)
elsif repository.contribution_guide.present?
- OpenStruct.new(enabled: true,
- label: _('Contribution guide'),
- link: contribution_guide_path)
+ AnchorData.new(true,
+ _('Contribution guide'),
+ contribution_guide_path)
end
end
def autodevops_anchor_data(show_auto_devops_callout: false)
if current_user && can?(current_user, :admin_pipeline, project) && repository.gitlab_ci_yml.blank? && !show_auto_devops_callout
- OpenStruct.new(enabled: auto_devops_enabled?,
- label: auto_devops_enabled? ? _('Auto DevOps enabled') : _('Enable Auto DevOps'),
- link: project_settings_ci_cd_path(project, anchor: 'autodevops-settings'))
+ AnchorData.new(auto_devops_enabled?,
+ auto_devops_enabled? ? _('Auto DevOps enabled') : _('Enable Auto DevOps'),
+ project_settings_ci_cd_path(project, anchor: 'autodevops-settings'))
elsif auto_devops_enabled?
- OpenStruct.new(enabled: true,
- label: _('Auto DevOps enabled'),
- link: nil)
+ AnchorData.new(true,
+ _('Auto DevOps enabled'),
+ nil)
end
end
@@ -282,32 +292,48 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
cluster_link = new_project_cluster_path(project)
end
- OpenStruct.new(enabled: !clusters.empty?,
- label: clusters.empty? ? _('Add Kubernetes cluster') : _('Kubernetes configured'),
- link: cluster_link)
+ AnchorData.new(!clusters.empty?,
+ clusters.empty? ? _('Add Kubernetes cluster') : _('Kubernetes configured'),
+ cluster_link)
end
end
def gitlab_ci_anchor_data
if current_user && can_current_user_push_code? && repository.gitlab_ci_yml.blank? && !auto_devops_enabled?
- OpenStruct.new(enabled: false,
- label: _('Set up CI/CD'),
- link: add_ci_yml_path)
+ AnchorData.new(false,
+ _('Set up CI/CD'),
+ add_ci_yml_path)
elsif repository.gitlab_ci_yml.present?
- OpenStruct.new(enabled: true,
- label: _('CI/CD configuration'),
- link: ci_configuration_path)
+ AnchorData.new(true,
+ _('CI/CD configuration'),
+ ci_configuration_path)
end
end
def koding_anchor_data
if current_user && can_current_user_push_code? && koding_enabled? && repository.koding_yml.blank?
- OpenStruct.new(enabled: false,
- label: _('Set up Koding'),
- link: add_koding_stack_path)
+ AnchorData.new(false,
+ _('Set up Koding'),
+ add_koding_stack_path)
end
end
+ def tags_to_show
+ project.tag_list.take(MAX_TAGS_TO_SHOW) # rubocop: disable CodeReuse/ActiveRecord
+ end
+
+ def count_of_extra_tags_not_shown
+ if project.tag_list.count > MAX_TAGS_TO_SHOW
+ project.tag_list.count - MAX_TAGS_TO_SHOW
+ else
+ 0
+ end
+ end
+
+ def has_extra_tags?
+ count_of_extra_tags_not_shown > 0
+ end
+
private
def filename_path(filename)
diff --git a/app/presenters/projects/settings/deploy_keys_presenter.rb b/app/presenters/projects/settings/deploy_keys_presenter.rb
index 28eaef00a12..85518c9a3a4 100644
--- a/app/presenters/projects/settings/deploy_keys_presenter.rb
+++ b/app/presenters/projects/settings/deploy_keys_presenter.rb
@@ -12,9 +12,11 @@ module Projects
@key ||= DeployKey.new.tap { |dk| dk.deploy_keys_projects.build }
end
+ # rubocop: disable CodeReuse/ActiveRecord
def enabled_keys
@enabled_keys ||= project.deploy_keys.includes(:projects)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def any_keys_enabled?
enabled_keys.any?
@@ -24,14 +26,17 @@ module Projects
@available_keys ||= current_user.accessible_deploy_keys - enabled_keys
end
+ # rubocop: disable CodeReuse/ActiveRecord
def available_project_keys
@available_project_keys ||= current_user.project_deploy_keys.includes(:projects) - enabled_keys
end
+ # rubocop: enable CodeReuse/ActiveRecord
def key_available?(deploy_key)
available_keys.include?(deploy_key)
end
+ # rubocop: disable CodeReuse/ActiveRecord
def available_public_keys
return @available_public_keys if defined?(@available_public_keys)
@@ -41,9 +46,10 @@ module Projects
# in @available_project_keys.
@available_public_keys -= available_project_keys
end
+ # rubocop: enable CodeReuse/ActiveRecord
def as_json
- serializer = DeployKeySerializer.new
+ serializer = DeployKeySerializer.new # rubocop: disable CodeReuse/Serializer
opts = { user: current_user }
{
diff --git a/app/serializers/build_details_entity.rb b/app/serializers/build_details_entity.rb
index 271ff668eda..00a441a9a1e 100644
--- a/app/serializers/build_details_entity.rb
+++ b/app/serializers/build_details_entity.rb
@@ -1,12 +1,26 @@
# frozen_string_literal: true
class BuildDetailsEntity < JobEntity
+ include EnvironmentHelper
+ include RequestAwareEntity
+ include CiStatusHelper
+
expose :coverage, :erased_at, :duration
expose :tag_list, as: :tags
expose :user, using: UserEntity
expose :runner, using: RunnerEntity
expose :pipeline, using: PipelineEntity
+ expose :deployment_status, if: -> (*) { build.has_environment? } do
+ expose :deployment_status, as: :status
+
+ expose :icon do |build|
+ ci_label_for_status(build.status)
+ end
+
+ expose :persisted_environment, as: :environment, with: EnvironmentEntity
+ end
+
expose :metadata, using: BuildMetadataEntity
expose :artifact, if: -> (*) { can?(current_user, :read_build, build) } do
@@ -36,6 +50,10 @@ class BuildDetailsEntity < JobEntity
erase_project_job_path(project, build)
end
+ expose :terminal_path, if: -> (*) { can_create_build_terminal? } do |build|
+ terminal_project_job_path(project, build)
+ end
+
expose :merge_request, if: -> (*) { can?(current_user, :read_merge_request, build.merge_request) } do
expose :iid do |build|
build.merge_request.iid
@@ -55,6 +73,26 @@ class BuildDetailsEntity < JobEntity
raw_project_job_path(project, build)
end
+ expose :trigger, if: -> (*) { build.trigger_request } do
+ expose :trigger_short_token, as: :short_token
+
+ expose :trigger_variables, as: :variables, using: TriggerVariableEntity
+ end
+
+ expose :runners do
+ expose :online do |build|
+ build.any_runners_online?
+ end
+
+ expose :available do |build|
+ project.any_runners?
+ end
+
+ expose :settings_path, if: -> (*) { can_admin_build? } do |build|
+ project_runners_path(project)
+ end
+ end
+
private
def build_failed_issue_options
@@ -69,4 +107,12 @@ class BuildDetailsEntity < JobEntity
def project
build.project
end
+
+ def can_create_build_terminal?
+ can?(current_user, :create_build_terminal, build) && build.has_terminal?
+ end
+
+ def can_admin_build?
+ can?(request.current_user, :admin_build, project)
+ end
end
diff --git a/app/serializers/commit_entity.rb b/app/serializers/commit_entity.rb
index b3287c66554..ce76659fa46 100644
--- a/app/serializers/commit_entity.rb
+++ b/app/serializers/commit_entity.rb
@@ -6,7 +6,7 @@ class CommitEntity < API::Entities::Commit
expose :author, using: UserEntity
expose :author_gravatar_url do |commit|
- GravatarService.new.execute(commit.author_email)
+ GravatarService.new.execute(commit.author_email) # rubocop: disable CodeReuse/ServiceClass
end
expose :commit_url do |commit|
diff --git a/app/serializers/status_entity.rb b/app/serializers/detailed_status_entity.rb
index 306c30f0323..c772c807f76 100644
--- a/app/serializers/status_entity.rb
+++ b/app/serializers/detailed_status_entity.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class StatusEntity < Grape::Entity
+class DetailedStatusEntity < Grape::Entity
include RequestAwareEntity
expose :icon, :text, :label, :group
@@ -8,6 +8,14 @@ class StatusEntity < Grape::Entity
expose :has_details?, as: :has_details
expose :details_path
+ expose :illustration do |status|
+ begin
+ status.illustration
+ rescue NotImplementedError
+ # ignored
+ end
+ end
+
expose :favicon do |status|
Gitlab::Favicon.status_overlay(status.favicon)
end
diff --git a/app/serializers/diffs_entity.rb b/app/serializers/diffs_entity.rb
index f75ace14d9c..878cc5290bd 100644
--- a/app/serializers/diffs_entity.rb
+++ b/app/serializers/diffs_entity.rb
@@ -35,13 +35,17 @@ class DiffsEntity < Grape::Entity
diffs_project_merge_request_path(merge_request&.project, merge_request)
end
+ # rubocop: disable CodeReuse/ActiveRecord
expose :added_lines do |diffs|
diffs.diff_files.sum(&:added_lines)
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
expose :removed_lines do |diffs|
diffs.diff_files.sum(&:removed_lines)
end
+ # rubocop: enable CodeReuse/ActiveRecord
expose :render_overflow_warning do |diffs|
render_overflow_warning?(diffs.diff_files)
diff --git a/app/serializers/discussion_entity.rb b/app/serializers/discussion_entity.rb
index ed09db0f3f4..ebe76c9fcda 100644
--- a/app/serializers/discussion_entity.rb
+++ b/app/serializers/discussion_entity.rb
@@ -6,6 +6,7 @@ class DiscussionEntity < Grape::Entity
expose :id, :reply_id
expose :position, if: -> (d, _) { d.diff_discussion? && !d.legacy_diff_discussion? }
+ expose :original_position, if: -> (d, _) { d.diff_discussion? && !d.legacy_diff_discussion? }
expose :line_code, if: -> (d, _) { d.diff_discussion? }
expose :expanded?, as: :expanded
expose :active?, as: :active, if: -> (d, _) { d.diff_discussion? }
diff --git a/app/serializers/environment_serializer.rb b/app/serializers/environment_serializer.rb
index dc1686c30c4..598ce5f9e4f 100644
--- a/app/serializers/environment_serializer.rb
+++ b/app/serializers/environment_serializer.rb
@@ -29,6 +29,7 @@ class EnvironmentSerializer < BaseSerializer
private
+ # rubocop: disable CodeReuse/ActiveRecord
def itemize(resource)
items = resource.order('folder ASC')
.group('COALESCE(environment_type, name)')
@@ -46,4 +47,5 @@ class EnvironmentSerializer < BaseSerializer
Item.new(item.folder, item.size, environments[item.last_id])
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/serializers/group_child_entity.rb b/app/serializers/group_child_entity.rb
index f6804fe7f6a..20d7032c970 100644
--- a/app/serializers/group_child_entity.rb
+++ b/app/serializers/group_child_entity.rb
@@ -66,11 +66,13 @@ class GroupChildEntity < Grape::Entity
private
+ # rubocop: disable CodeReuse/ActiveRecord
def membership
return unless request.current_user
@membership ||= request.current_user.members.find_by(source: object)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def project?
object.is_a?(Project)
diff --git a/app/serializers/group_entity.rb b/app/serializers/group_entity.rb
index c46c342ee5d..0e1bc9a6b3d 100644
--- a/app/serializers/group_entity.rb
+++ b/app/serializers/group_entity.rb
@@ -17,9 +17,11 @@ class GroupEntity < Grape::Entity
end
expose :permissions do
+ # rubocop: disable CodeReuse/ActiveRecord
expose :human_group_access do |group, options|
group.group_members.find_by(user_id: request.current_user)&.human_access
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
expose :edit_path do |group|
diff --git a/app/serializers/job_entity.rb b/app/serializers/job_entity.rb
index 7bc1d87dea5..26b29993fec 100644
--- a/app/serializers/job_entity.rb
+++ b/app/serializers/job_entity.rb
@@ -27,7 +27,7 @@ class JobEntity < Grape::Entity
expose :playable?, as: :playable
expose :created_at
expose :updated_at
- expose :detailed_status, as: :status, with: StatusEntity
+ expose :detailed_status, as: :status, with: DetailedStatusEntity
expose :callout_message, if: -> (*) { failed? && !build.script_failure? }
expose :recoverable, if: -> (*) { failed? }
diff --git a/app/serializers/job_group_entity.rb b/app/serializers/job_group_entity.rb
index 0941a9d36be..0db7624b3f7 100644
--- a/app/serializers/job_group_entity.rb
+++ b/app/serializers/job_group_entity.rb
@@ -5,7 +5,7 @@ class JobGroupEntity < Grape::Entity
expose :name
expose :size
- expose :detailed_status, as: :status, with: StatusEntity
+ expose :detailed_status, as: :status, with: DetailedStatusEntity
expose :jobs, with: JobEntity
private
diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb
index f55d448235a..380e8804f51 100644
--- a/app/serializers/merge_request_widget_entity.rb
+++ b/app/serializers/merge_request_widget_entity.rb
@@ -243,7 +243,7 @@ class MergeRequestWidgetEntity < IssuableEntity
def presenter(merge_request)
@presenters ||= {}
- @presenters[merge_request] ||= MergeRequestPresenter.new(merge_request, current_user: current_user)
+ @presenters[merge_request] ||= MergeRequestPresenter.new(merge_request, current_user: current_user) # rubocop: disable CodeReuse/Presenter
end
# Once SchedulePopulateMergeRequestMetricsWithEventsData fully runs,
diff --git a/app/serializers/note_entity.rb b/app/serializers/note_entity.rb
index daa5c24d0f5..c6d27817411 100644
--- a/app/serializers/note_entity.rb
+++ b/app/serializers/note_entity.rb
@@ -4,6 +4,12 @@ class NoteEntity < API::Entities::Note
include RequestAwareEntity
include NotesHelper
+ expose :id do |note|
+ # resource events are represented as notes too, but don't
+ # have ID, discussion ID is used for them instead
+ note.id ? note.id.to_s : note.discussion_id
+ end
+
expose :type
expose :author, using: NoteUserEntity
@@ -46,8 +52,8 @@ class NoteEntity < API::Entities::Note
expose :emoji_awardable?, as: :emoji_awardable
expose :award_emoji, if: -> (note, _) { note.emoji_awardable? }, using: AwardEmojiEntity
- expose :report_abuse_path do |note|
- new_abuse_report_path(user_id: note.author.id, ref_url: Gitlab::UrlBuilder.build(note))
+ expose :report_abuse_path, if: -> (note, _) { note.author_id } do |note|
+ new_abuse_report_path(user_id: note.author_id, ref_url: Gitlab::UrlBuilder.build(note))
end
expose :noteable_note_url do |note|
diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb
index 6cf1925adda..aef838409e0 100644
--- a/app/serializers/pipeline_entity.rb
+++ b/app/serializers/pipeline_entity.rb
@@ -30,7 +30,7 @@ class PipelineEntity < Grape::Entity
end
expose :details do
- expose :detailed_status, as: :status, with: StatusEntity
+ expose :detailed_status, as: :status, with: DetailedStatusEntity
expose :duration
expose :finished_at
end
diff --git a/app/serializers/pipeline_serializer.rb b/app/serializers/pipeline_serializer.rb
index 3205578b83e..4f31af3c46d 100644
--- a/app/serializers/pipeline_serializer.rb
+++ b/app/serializers/pipeline_serializer.rb
@@ -4,6 +4,7 @@ class PipelineSerializer < BaseSerializer
include WithPagination
entity PipelineDetailsEntity
+ # rubocop: disable CodeReuse/ActiveRecord
def represent(resource, opts = {})
if resource.is_a?(ActiveRecord::Relation)
resource = resource.preload([
@@ -33,6 +34,7 @@ class PipelineSerializer < BaseSerializer
super(resource, opts)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def represent_status(resource)
return {} unless resource.present?
diff --git a/app/serializers/project_note_entity.rb b/app/serializers/project_note_entity.rb
index d7c4d0aacc6..f6cdea1d8b5 100644
--- a/app/serializers/project_note_entity.rb
+++ b/app/serializers/project_note_entity.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class ProjectNoteEntity < NoteEntity
- expose :human_access do |note|
+ expose :human_access, if: -> (note, _) { note.project.present? } do |note|
note.project.team.human_max_access(note.author_id)
end
@@ -9,7 +9,7 @@ class ProjectNoteEntity < NoteEntity
toggle_award_emoji_project_note_path(note.project, note.id)
end
- expose :path do |note|
+ expose :path, if: -> (note, _) { note.id } do |note|
project_note_path(note.project, note)
end
diff --git a/app/serializers/runner_entity.rb b/app/serializers/runner_entity.rb
index 04ec80e0efa..97e5b336a35 100644
--- a/app/serializers/runner_entity.rb
+++ b/app/serializers/runner_entity.rb
@@ -5,8 +5,7 @@ class RunnerEntity < Grape::Entity
expose :id, :description
- expose :edit_path,
- if: -> (*) { can?(request.current_user, :admin_build, project) && runner.project_type? } do |runner|
+ expose :edit_path, if: -> (*) { can_edit_runner? } do |runner|
edit_project_runner_path(project, runner)
end
@@ -17,4 +16,8 @@ class RunnerEntity < Grape::Entity
def project
request.project
end
+
+ def can_edit_runner?
+ can?(request.current_user, :update_runner, runner) && runner.project_type?
+ end
end
diff --git a/app/serializers/stage_entity.rb b/app/serializers/stage_entity.rb
index 00e6d32ee3a..029dd3d0684 100644
--- a/app/serializers/stage_entity.rb
+++ b/app/serializers/stage_entity.rb
@@ -19,7 +19,13 @@ class StageEntity < Grape::Entity
latest_statuses
end
- expose :detailed_status, as: :status, with: StatusEntity
+ expose :retried,
+ if: -> (_, opts) { opts[:retried] },
+ with: JobEntity do |stage|
+ retried_statuses
+ end
+
+ expose :detailed_status, as: :status, with: DetailedStatusEntity
expose :path do |stage|
project_pipeline_path(
@@ -48,9 +54,19 @@ class StageEntity < Grape::Entity
@grouped_statuses ||= stage.statuses.latest_ordered.group_by(&:status)
end
+ def grouped_retried_statuses
+ @grouped_retried_statuses ||= stage.statuses.retried_ordered.group_by(&:status)
+ end
+
def latest_statuses
HasStatus::ORDERED_STATUSES.map do |ordered_status|
grouped_statuses.fetch(ordered_status, [])
end.flatten
end
+
+ def retried_statuses
+ HasStatus::ORDERED_STATUSES.map do |ordered_status|
+ grouped_retried_statuses.fetch(ordered_status, [])
+ end.flatten
+ end
end
diff --git a/app/serializers/trigger_variable_entity.rb b/app/serializers/trigger_variable_entity.rb
new file mode 100644
index 00000000000..56203113631
--- /dev/null
+++ b/app/serializers/trigger_variable_entity.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class TriggerVariableEntity < Grape::Entity
+ include RequestAwareEntity
+
+ expose :key, :value, :public
+end
diff --git a/app/services/application_settings/update_service.rb b/app/services/application_settings/update_service.rb
index 19cf34e2ac4..2e4643ed668 100644
--- a/app/services/application_settings/update_service.rb
+++ b/app/services/application_settings/update_service.rb
@@ -11,11 +11,19 @@ module ApplicationSettings
params[:performance_bar_allowed_group_id] = performance_bar_allowed_group_id
end
+ if usage_stats_updated? && !params.delete(:skip_usage_stats_user)
+ params[:usage_stats_set_by_user_id] = current_user.id
+ end
+
@application_setting.update(@params)
end
private
+ def usage_stats_updated?
+ params.key?(:usage_ping_enabled) || params.key?(:version_check_enabled)
+ end
+
def update_terms(terms)
return unless terms.present?
diff --git a/app/services/applications/create_service.rb b/app/services/applications/create_service.rb
index 7db90c0b3c6..3d88c4f064e 100644
--- a/app/services/applications/create_service.rb
+++ b/app/services/applications/create_service.rb
@@ -2,10 +2,12 @@
module Applications
class CreateService
+ # rubocop: disable CodeReuse/ActiveRecord
def initialize(current_user, params)
@current_user = current_user
@params = params.except(:ip_address)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def execute(request)
Doorkeeper::Application.create(@params)
diff --git a/app/services/boards/create_service.rb b/app/services/boards/create_service.rb
index 4caf5ffa3cb..1b796cef3e2 100644
--- a/app/services/boards/create_service.rb
+++ b/app/services/boards/create_service.rb
@@ -9,7 +9,7 @@ module Boards
private
def can_create_board?
- parent.boards.size == 0
+ parent.boards.empty?
end
def create_board!
diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb
index 0db1418b37a..0b69661bbd0 100644
--- a/app/services/boards/issues/list_service.rb
+++ b/app/services/boards/issues/list_service.rb
@@ -9,6 +9,7 @@ module Boards
fetch_issues.order_by_position_and_priority
end
+ # rubocop: disable CodeReuse/ActiveRecord
def metadata
keys = metadata_fields.keys
columns = metadata_fields.values_at(*keys).join(', ')
@@ -16,6 +17,7 @@ module Boards
Hash[keys.zip(results.flatten)]
end
+ # rubocop: enable CodeReuse/ActiveRecord
private
@@ -24,6 +26,7 @@ module Boards
end
# We memoize the query here since the finder methods we use are quite complex. This does not memoize the result of the query.
+ # rubocop: disable CodeReuse/ActiveRecord
def fetch_issues
strong_memoize(:fetch_issues) do
issues = IssuesFinder.new(current_user, filter_params).execute
@@ -31,6 +34,7 @@ module Boards
filter(issues).reorder(nil)
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def filter(issues)
issues = without_board_labels(issues) unless list&.movable? || list&.closed?
@@ -52,6 +56,7 @@ module Boards
set_parent
set_state
set_scope
+ set_non_archived
params
end
@@ -72,24 +77,36 @@ module Boards
params[:include_subgroups] = board.group_board?
end
+ def set_non_archived
+ params[:non_archived] = parent.is_a?(Group)
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
def board_label_ids
@board_label_ids ||= board.lists.movable.pluck(:label_id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def without_board_labels(issues)
return issues unless board_label_ids.any?
issues.where.not('EXISTS (?)', issues_label_links.limit(1))
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def issues_label_links
LabelLink.where("label_links.target_type = 'Issue' AND label_links.target_id = issues.id").where(label_id: board_label_ids)
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def with_list_label(issues)
issues.where('EXISTS (?)', LabelLink.where("label_links.target_type = 'Issue' AND label_links.target_id = issues.id")
.where("label_links.label_id = ?", list.label_id).limit(1))
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
end
diff --git a/app/services/boards/issues/move_service.rb b/app/services/boards/issues/move_service.rb
index 6fd8a23b2a1..7dd87034410 100644
--- a/app/services/boards/issues/move_service.rb
+++ b/app/services/boards/issues/move_service.rb
@@ -21,13 +21,17 @@ module Boards
moving_from_list != moving_to_list
end
+ # rubocop: disable CodeReuse/ActiveRecord
def moving_from_list
@moving_from_list ||= board.lists.find_by(id: params[:from_list_id])
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def moving_to_list
@moving_to_list ||= board.lists.find_by(id: params[:to_list_id])
end
+ # rubocop: enable CodeReuse/ActiveRecord
def update(issue)
::Issues::UpdateService.new(issue.project, current_user, issue_params(issue)).execute(issue)
@@ -61,6 +65,7 @@ module Boards
[moving_to_list.label_id].compact
end
+ # rubocop: disable CodeReuse/ActiveRecord
def remove_label_ids
label_ids =
if moving_to_list.movable?
@@ -73,6 +78,7 @@ module Boards
Array(label_ids).compact
end
+ # rubocop: enable CodeReuse/ActiveRecord
def move_between_ids
return unless params[:move_after_id] || params[:move_before_id]
diff --git a/app/services/boards/lists/destroy_service.rb b/app/services/boards/lists/destroy_service.rb
index e12d4f46e19..609c430caed 100644
--- a/app/services/boards/lists/destroy_service.rb
+++ b/app/services/boards/lists/destroy_service.rb
@@ -18,10 +18,12 @@ module Boards
attr_reader :board
+ # rubocop: disable CodeReuse/ActiveRecord
def decrement_higher_lists(list)
board.lists.movable.where('position > ?', list.position)
.update_all('position = position - 1')
end
+ # rubocop: enable CodeReuse/ActiveRecord
def remove_list(list)
list.destroy
diff --git a/app/services/boards/lists/move_service.rb b/app/services/boards/lists/move_service.rb
index 27a36051662..93f81837d1a 100644
--- a/app/services/boards/lists/move_service.rb
+++ b/app/services/boards/lists/move_service.rb
@@ -34,17 +34,21 @@ module Boards
end
end
+ # rubocop: disable CodeReuse/ActiveRecord
def decrement_intermediate_lists
board.lists.movable.where('position > ?', old_position)
.where('position <= ?', new_position)
.update_all('position = position - 1')
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def increment_intermediate_lists
board.lists.movable.where('position >= ?', new_position)
.where('position < ?', old_position)
.update_all('position = position + 1')
end
+ # rubocop: enable CodeReuse/ActiveRecord
def update_list_position(list)
list.update_attribute(:position, new_position)
diff --git a/app/services/chat_names/find_user_service.rb b/app/services/chat_names/find_user_service.rb
index 854b191c45c..c91738fa4c7 100644
--- a/app/services/chat_names/find_user_service.rb
+++ b/app/services/chat_names/find_user_service.rb
@@ -17,6 +17,7 @@ module ChatNames
private
+ # rubocop: disable CodeReuse/ActiveRecord
def find_chat_name
ChatName.find_by(
service: @service,
@@ -24,5 +25,6 @@ module ChatNames
chat_id: @params[:user_id]
)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/app/services/ci/compare_test_reports_service.rb b/app/services/ci/compare_test_reports_service.rb
index ec25e934a27..2293f95f56b 100644
--- a/app/services/ci/compare_test_reports_service.rb
+++ b/app/services/ci/compare_test_reports_service.rb
@@ -3,6 +3,7 @@
module Ci
class CompareTestReportsService < ::BaseService
def execute(base_pipeline, head_pipeline)
+ # rubocop: disable CodeReuse/Serializer
comparer = Gitlab::Ci::Reports::TestReportsComparer
.new(base_pipeline&.test_reports, head_pipeline.test_reports)
@@ -19,6 +20,7 @@ module Ci
key: key(base_pipeline, head_pipeline),
status_reason: e.message
}
+ # rubocop: enable CodeReuse/Serializer
end
def latest?(base_pipeline, head_pipeline, data)
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index 85df8bcff8c..92a8438ab2f 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -65,6 +65,7 @@ module Ci
end
end
+ # rubocop: disable CodeReuse/ActiveRecord
def auto_cancelable_pipelines
project.pipelines
.where(ref: pipeline.ref)
@@ -72,6 +73,7 @@ module Ci
.where.not(sha: project.commit(pipeline.ref).try(:id))
.created_or_pending
end
+ # rubocop: enable CodeReuse/ActiveRecord
def pipeline_created_counter
@pipeline_created_counter ||= Gitlab::Metrics
@@ -84,8 +86,10 @@ module Ci
end
end
+ # rubocop: disable CodeReuse/ActiveRecord
def related_merge_requests
pipeline.project.source_of_merge_requests.opened.where(source_branch: pipeline.ref)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/app/services/ci/ensure_stage_service.rb b/app/services/ci/ensure_stage_service.rb
index 3d0e39d1b9f..cbb3a2e4709 100644
--- a/app/services/ci/ensure_stage_service.rb
+++ b/app/services/ci/ensure_stage_service.rb
@@ -38,9 +38,11 @@ module Ci
EOS
end
+ # rubocop: disable CodeReuse/ActiveRecord
def find_stage
@build.pipeline.stages.find_by(name: @build.stage)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def create_stage
Ci::Stage.create!(name: @build.stage,
diff --git a/app/services/ci/extract_sections_from_build_trace_service.rb b/app/services/ci/extract_sections_from_build_trace_service.rb
index 693f6d55be3..97f9918fdb7 100644
--- a/app/services/ci/extract_sections_from_build_trace_service.rb
+++ b/app/services/ci/extract_sections_from_build_trace_service.rb
@@ -11,11 +11,13 @@ module Ci
private
+ # rubocop: disable CodeReuse/ActiveRecord
def find_or_create_name(name)
project.build_trace_section_names.find_or_create_by!(name: name)
rescue ActiveRecord::RecordInvalid
project.build_trace_section_names.find_by!(name: name)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def extract_sections(build)
build.trace.extract_sections.map do |attr|
diff --git a/app/services/ci/fetch_kubernetes_token_service.rb b/app/services/ci/fetch_kubernetes_token_service.rb
deleted file mode 100644
index 15eda56cac6..00000000000
--- a/app/services/ci/fetch_kubernetes_token_service.rb
+++ /dev/null
@@ -1,75 +0,0 @@
-# frozen_string_literal: true
-
-##
-# TODO:
-# Almost components in this class were copied from app/models/project_services/kubernetes_service.rb
-# We should dry up those classes not to repeat the same code.
-# Maybe we should have a special facility (e.g. lib/kubernetes_api) to maintain all Kubernetes API caller.
-module Ci
- class FetchKubernetesTokenService
- attr_reader :api_url, :ca_pem, :username, :password
-
- def initialize(api_url, ca_pem, username, password)
- @api_url = api_url
- @ca_pem = ca_pem
- @username = username
- @password = password
- end
-
- def execute
- read_secrets.each do |secret|
- name = secret.dig('metadata', 'name')
- if /default-token/ =~ name
- token_base64 = secret.dig('data', 'token')
- return Base64.decode64(token_base64) if token_base64
- end
- end
-
- nil
- end
-
- private
-
- def read_secrets
- kubeclient = build_kubeclient!
-
- kubeclient.get_secrets.as_json
- rescue Kubeclient::HttpError => err
- raise err unless err.error_code == 404
-
- []
- end
-
- def build_kubeclient!(api_path: 'api', api_version: 'v1')
- raise "Incomplete settings" unless api_url && username && password
-
- ::Kubeclient::Client.new(
- join_api_url(api_path),
- api_version,
- auth_options: { username: username, password: password },
- ssl_options: kubeclient_ssl_options,
- http_proxy_uri: ENV['http_proxy']
- )
- end
-
- def join_api_url(api_path)
- url = URI.parse(api_url)
- prefix = url.path.sub(%r{/+\z}, '')
-
- url.path = [prefix, api_path].join("/")
-
- url.to_s
- end
-
- def kubeclient_ssl_options
- opts = { verify_ssl: OpenSSL::SSL::VERIFY_PEER }
-
- if ca_pem.present?
- opts[:cert_store] = OpenSSL::X509::Store.new
- opts[:cert_store].add_cert(OpenSSL::X509::Certificate.new(ca_pem))
- end
-
- opts
- end
- end
-end
diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb
index cafee76a33c..69341a6c263 100644
--- a/app/services/ci/process_pipeline_service.rb
+++ b/app/services/ci/process_pipeline_service.rb
@@ -60,17 +60,23 @@ module Ci
end
end
+ # rubocop: disable CodeReuse/ActiveRecord
def status_for_prior_stages(index)
pipeline.builds.where('stage_idx < ?', index).latest.status || 'success'
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def stage_indexes_of_created_builds
created_builds.order(:stage_idx).pluck('distinct stage_idx')
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def created_builds_in_stage(index)
created_builds.where(stage_idx: index)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def created_builds
pipeline.builds.created
@@ -80,6 +86,7 @@ module Ci
# This replicates what is db/post_migrate/20170416103934_upate_retried_for_ci_build.rb
# and ensures that functionality will not be broken before migration is run
# this updates only when there are data that needs to be updated, there are two groups with no retried flag
+ # rubocop: disable CodeReuse/ActiveRecord
def update_retried
# find the latest builds for each name
latest_statuses = pipeline.statuses.latest
@@ -93,6 +100,7 @@ module Ci
.where.not(id: latest_statuses.map(&:first))
.update_all(retried: true) if latest_statuses.any?
end
+ # rubocop: enable CodeReuse/ActiveRecord
def enqueue_build(build)
Ci::EnqueueBuildService.new(project, @user).execute(build)
diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb
index 11f85627faf..5a7be921389 100644
--- a/app/services/ci/register_job_service.rb
+++ b/app/services/ci/register_job_service.rb
@@ -15,6 +15,7 @@ module Ci
@runner = runner
end
+ # rubocop: disable CodeReuse/ActiveRecord
def execute(params = {})
builds =
if runner.instance_type?
@@ -63,6 +64,7 @@ module Ci
register_failure
Result.new(nil, valid)
end
+ # rubocop: enable CodeReuse/ActiveRecord
private
@@ -84,6 +86,7 @@ module Ci
true
end
+ # rubocop: disable CodeReuse/ActiveRecord
def builds_for_shared_runner
new_builds.
# don't run projects which have not enabled shared runners and builds
@@ -97,11 +100,15 @@ module Ci
joins("LEFT JOIN (#{running_builds_for_shared_runners.to_sql}) AS project_builds ON ci_builds.project_id=project_builds.project_id")
.order('COALESCE(project_builds.running_builds, 0) ASC', 'ci_builds.id ASC')
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def builds_for_project_runner
new_builds.where(project: runner.projects.without_deleted.with_builds_enabled).order('id ASC')
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def builds_for_group_runner
# Workaround for weird Rails bug, that makes `runner.groups.to_sql` to return `runner_id = NULL`
groups = ::Group.joins(:runner_namespaces).merge(runner.runner_namespaces)
@@ -113,11 +120,14 @@ module Ci
.without_deleted
new_builds.where(project: projects).order('id ASC')
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def running_builds_for_shared_runners
Ci::Build.running.where(runner: Ci::Runner.instance_type)
.group(:project_id).select(:project_id, 'count(*) AS running_builds')
end
+ # rubocop: enable CodeReuse/ActiveRecord
def new_builds
builds = Ci::Build.pending.unstarted
@@ -138,6 +148,7 @@ module Ci
attempt_counter.increment
end
+ # rubocop: disable CodeReuse/ActiveRecord
def jobs_running_for_project(job)
return '+Inf' unless runner.instance_type?
@@ -146,6 +157,7 @@ module Ci
.limit(JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET + 1).count - 1
running_jobs_count < JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET ? running_jobs_count : "#{JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET}+"
end
+ # rubocop: enable CodeReuse/ActiveRecord
def failed_attempt_counter
@failed_attempt_counter ||= Gitlab::Metrics.counter(:job_register_attempts_failed_total, "Counts the times a runner tries to register a job")
diff --git a/app/services/ci/retry_build_service.rb b/app/services/ci/retry_build_service.rb
index 6ceb59e4780..218f1e63d08 100644
--- a/app/services/ci/retry_build_service.rb
+++ b/app/services/ci/retry_build_service.rb
@@ -19,6 +19,7 @@ module Ci
end
end
+ # rubocop: disable CodeReuse/ActiveRecord
def reprocess!(build)
unless can?(current_user, :update_build, build)
raise Gitlab::Access::AccessDeniedError
@@ -41,5 +42,6 @@ module Ci
project.builds.create!(Hash[attributes])
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/app/services/clusters/gcp/finalize_creation_service.rb b/app/services/clusters/gcp/finalize_creation_service.rb
index 264419501dc..3ae0a4a19d0 100644
--- a/app/services/clusters/gcp/finalize_creation_service.rb
+++ b/app/services/clusters/gcp/finalize_creation_service.rb
@@ -9,17 +9,24 @@ module Clusters
@provider = provider
configure_provider
+ create_gitlab_service_account!
configure_kubernetes
cluster.save!
rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e
provider.make_errored!("Failed to request to CloudPlatform; #{e.message}")
+ rescue Kubeclient::HttpError => e
+ provider.make_errored!("Failed to run Kubeclient: #{e.message}")
rescue ActiveRecord::RecordInvalid => e
provider.make_errored!("Failed to configure Google Kubernetes Engine Cluster: #{e.message}")
end
private
+ def create_gitlab_service_account!
+ Clusters::Gcp::Kubernetes::CreateServiceAccountService.new(kube_client, rbac: create_rbac_cluster?).execute
+ end
+
def configure_provider
provider.endpoint = gke_cluster.endpoint
provider.status_event = :make_created
@@ -32,15 +39,54 @@ module Clusters
ca_cert: Base64.decode64(gke_cluster.master_auth.cluster_ca_certificate),
username: gke_cluster.master_auth.username,
password: gke_cluster.master_auth.password,
+ authorization_type: authorization_type,
token: request_kubernetes_token)
end
def request_kubernetes_token
- Ci::FetchKubernetesTokenService.new(
+ Clusters::Gcp::Kubernetes::FetchKubernetesTokenService.new(kube_client).execute
+ end
+
+ def authorization_type
+ create_rbac_cluster? ? 'rbac' : 'abac'
+ end
+
+ def create_rbac_cluster?
+ !provider.legacy_abac?
+ end
+
+ def kube_client
+ @kube_client ||= build_kube_client!(
'https://' + gke_cluster.endpoint,
Base64.decode64(gke_cluster.master_auth.cluster_ca_certificate),
gke_cluster.master_auth.username,
- gke_cluster.master_auth.password).execute
+ gke_cluster.master_auth.password,
+ api_groups: ['api', 'apis/rbac.authorization.k8s.io']
+ )
+ end
+
+ def build_kube_client!(api_url, ca_pem, username, password, api_groups: ['api'], api_version: 'v1')
+ raise "Incomplete settings" unless api_url && username && password
+
+ Gitlab::Kubernetes::KubeClient.new(
+ api_url,
+ api_groups,
+ api_version,
+ auth_options: { username: username, password: password },
+ ssl_options: kubeclient_ssl_options(ca_pem),
+ http_proxy_uri: ENV['http_proxy']
+ )
+ end
+
+ def kubeclient_ssl_options(ca_pem)
+ opts = { verify_ssl: OpenSSL::SSL::VERIFY_PEER }
+
+ if ca_pem.present?
+ opts[:cert_store] = OpenSSL::X509::Store.new
+ opts[:cert_store].add_cert(OpenSSL::X509::Certificate.new(ca_pem))
+ end
+
+ opts
end
def gke_cluster
diff --git a/app/services/clusters/gcp/kubernetes.rb b/app/services/clusters/gcp/kubernetes.rb
new file mode 100644
index 00000000000..d014d73b3e8
--- /dev/null
+++ b/app/services/clusters/gcp/kubernetes.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Gcp
+ module Kubernetes
+ SERVICE_ACCOUNT_NAME = 'gitlab'
+ SERVICE_ACCOUNT_NAMESPACE = 'default'
+ SERVICE_ACCOUNT_TOKEN_NAME = 'gitlab-token'
+ CLUSTER_ROLE_BINDING_NAME = 'gitlab-admin'
+ CLUSTER_ROLE_NAME = 'cluster-admin'
+ end
+ end
+end
diff --git a/app/services/clusters/gcp/kubernetes/create_service_account_service.rb b/app/services/clusters/gcp/kubernetes/create_service_account_service.rb
new file mode 100644
index 00000000000..d17744591e6
--- /dev/null
+++ b/app/services/clusters/gcp/kubernetes/create_service_account_service.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Gcp
+ module Kubernetes
+ class CreateServiceAccountService
+ attr_reader :kubeclient, :rbac
+
+ def initialize(kubeclient, rbac:)
+ @kubeclient = kubeclient
+ @rbac = rbac
+ end
+
+ def execute
+ kubeclient.create_service_account(service_account_resource)
+ kubeclient.create_secret(service_account_token_resource)
+ kubeclient.create_cluster_role_binding(cluster_role_binding_resource) if rbac
+ end
+
+ private
+
+ def service_account_resource
+ Gitlab::Kubernetes::ServiceAccount.new(service_account_name, service_account_namespace).generate
+ end
+
+ def service_account_token_resource
+ Gitlab::Kubernetes::ServiceAccountToken.new(
+ SERVICE_ACCOUNT_TOKEN_NAME, service_account_name, service_account_namespace).generate
+ end
+
+ def cluster_role_binding_resource
+ subjects = [{ kind: 'ServiceAccount', name: service_account_name, namespace: service_account_namespace }]
+
+ Gitlab::Kubernetes::ClusterRoleBinding.new(
+ CLUSTER_ROLE_BINDING_NAME,
+ CLUSTER_ROLE_NAME,
+ subjects
+ ).generate
+ end
+
+ def service_account_name
+ SERVICE_ACCOUNT_NAME
+ end
+
+ def service_account_namespace
+ SERVICE_ACCOUNT_NAMESPACE
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb b/app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb
new file mode 100644
index 00000000000..9e09345c8dc
--- /dev/null
+++ b/app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Gcp
+ module Kubernetes
+ class FetchKubernetesTokenService
+ attr_reader :kubeclient
+
+ def initialize(kubeclient)
+ @kubeclient = kubeclient
+ end
+
+ def execute
+ token_base64 = get_secret&.dig('data', 'token')
+ Base64.decode64(token_base64) if token_base64
+ end
+
+ private
+
+ def get_secret
+ kubeclient.get_secret(SERVICE_ACCOUNT_TOKEN_NAME, SERVICE_ACCOUNT_NAMESPACE).as_json
+ rescue Kubeclient::HttpError => err
+ raise err unless err.error_code == 404
+
+ nil
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/clusters/gcp/provision_service.rb b/app/services/clusters/gcp/provision_service.rb
index ab1bf9c64f6..80040511ec2 100644
--- a/app/services/clusters/gcp/provision_service.rb
+++ b/app/services/clusters/gcp/provision_service.rb
@@ -27,7 +27,9 @@ module Clusters
provider.zone,
provider.cluster.name,
provider.num_nodes,
- machine_type: provider.machine_type)
+ machine_type: provider.machine_type,
+ legacy_abac: provider.legacy_abac
+ )
unless operation.status == 'PENDING' || operation.status == 'RUNNING'
return provider.make_errored!("Operation status is unexpected; #{operation.status_message}")
diff --git a/app/services/cohorts_service.rb b/app/services/cohorts_service.rb
index 7a14e97f749..6d466c2fc9c 100644
--- a/app/services/cohorts_service.rb
+++ b/app/services/cohorts_service.rb
@@ -78,6 +78,7 @@ class CohortsService
# created_at_month can never be nil, but last_activity_on_month can (when a
# user has never logged in, just been created). This covers the last
# MONTHS_INCLUDED months.
+ # rubocop: disable CodeReuse/ActiveRecord
def counts_by_month
@counts_by_month ||=
begin
@@ -91,6 +92,7 @@ class CohortsService
.count
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def column_to_date(column)
if Gitlab::Database.postgresql?
diff --git a/app/services/concerns/issues/resolve_discussions.rb b/app/services/concerns/issues/resolve_discussions.rb
index 1563ed965df..f0e9862ca30 100644
--- a/app/services/concerns/issues/resolve_discussions.rb
+++ b/app/services/concerns/issues/resolve_discussions.rb
@@ -13,12 +13,14 @@ module Issues
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
+ # rubocop: disable CodeReuse/ActiveRecord
def merge_request_to_resolve_discussions_of
strong_memoize(:merge_request_to_resolve_discussions_of) do
MergeRequestsFinder.new(current_user, project_id: project.id)
.find_by(iid: merge_request_to_resolve_discussions_of_iid)
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def discussions_to_resolve
return [] unless merge_request_to_resolve_discussions_of
diff --git a/app/services/create_release_service.rb b/app/services/create_release_service.rb
index 09c68390007..8d1fdbe11c3 100644
--- a/app/services/create_release_service.rb
+++ b/app/services/create_release_service.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
class CreateReleaseService < BaseService
+ # rubocop: disable CodeReuse/ActiveRecord
def execute(tag_name, release_description)
repository = project.repository
existing_tag = repository.find_tag(tag_name)
@@ -21,6 +22,7 @@ class CreateReleaseService < BaseService
error('Tag does not exist', 404)
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def success(release)
super().merge(release: release)
diff --git a/app/services/delete_merged_branches_service.rb b/app/services/delete_merged_branches_service.rb
index ff3e4783fe3..ced87a1c37a 100644
--- a/app/services/delete_merged_branches_service.rb
+++ b/app/services/delete_merged_branches_service.rb
@@ -21,10 +21,12 @@ class DeleteMergedBranchesService < BaseService
private
+ # rubocop: disable CodeReuse/ActiveRecord
def merge_request_branch_names
# reorder(nil) is necessary for SELECT DISTINCT because default scope adds an ORDER BY
source_names = project.origin_merge_requests.opened.reorder(nil).uniq.pluck(:source_branch)
target_names = project.merge_requests.opened.reorder(nil).uniq.pluck(:target_branch)
(source_names + target_names).uniq
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/services/emails/base_service.rb b/app/services/emails/base_service.rb
index ba7b689a9af..988215ffc78 100644
--- a/app/services/emails/base_service.rb
+++ b/app/services/emails/base_service.rb
@@ -2,6 +2,8 @@
module Emails
class BaseService
+ attr_reader :current_user
+
def initialize(current_user, params = {})
@current_user, @params = current_user, params.dup
@user = params.delete(:user)
diff --git a/app/services/emails/create_service.rb b/app/services/emails/create_service.rb
index acf575e24e5..56925a724fe 100644
--- a/app/services/emails/create_service.rb
+++ b/app/services/emails/create_service.rb
@@ -3,7 +3,12 @@
module Emails
class CreateService < ::Emails::BaseService
def execute(extra_params = {})
- @user.emails.create(@params.merge(extra_params))
+ skip_confirmation = @params.delete(:skip_confirmation)
+
+ email = @user.emails.create(@params.merge(extra_params))
+
+ email&.confirm if skip_confirmation && current_user.admin?
+ email
end
end
end
diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb
index fc7b236f7da..39e614d6569 100644
--- a/app/services/files/base_service.rb
+++ b/app/services/files/base_service.rb
@@ -7,8 +7,10 @@ module Files
def initialize(*args)
super
- @author_email = params[:author_email] || current_user&.email
- @author_name = params[:author_name] || current_user&.name
+ git_user = Gitlab::Git::User.from_gitlab(current_user) if current_user.present?
+
+ @author_email = params[:author_email] || git_user&.email
+ @author_name = params[:author_name] || git_user&.name
@commit_message = params[:commit_message]
@last_commit_sha = params[:last_commit_sha]
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index 26e90e8cf8c..f1883877d56 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -94,6 +94,7 @@ class GitPushService < BaseService
ProjectCacheWorker.perform_async(project.id, types, [:commit_count, :repository_size])
end
+ # rubocop: disable CodeReuse/ActiveRecord
def update_signatures
commit_shas = last_pushed_commits.map(&:sha)
@@ -108,6 +109,7 @@ class GitPushService < BaseService
CreateGpgSignatureWorker.perform_async(commit_shas, project.id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
# Schedules processing of commit messages.
def process_commit_messages
diff --git a/app/services/groups/destroy_service.rb b/app/services/groups/destroy_service.rb
index 93d84bd8a9c..641111aeadc 100644
--- a/app/services/groups/destroy_service.rb
+++ b/app/services/groups/destroy_service.rb
@@ -9,6 +9,7 @@ module Groups
Rails.logger.info("User #{current_user.id} scheduled a deletion of group ID #{group.id} with job ID #{job_id}")
end
+ # rubocop: disable CodeReuse/ActiveRecord
def execute
group.prepare_for_destroy
@@ -30,5 +31,6 @@ module Groups
group.destroy
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/app/services/groups/transfer_service.rb b/app/services/groups/transfer_service.rb
index ea7576077f3..5efa746dfb9 100644
--- a/app/services/groups/transfer_service.rb
+++ b/app/services/groups/transfer_service.rb
@@ -64,9 +64,11 @@ module Groups
end
end
+ # rubocop: disable CodeReuse/ActiveRecord
def namespace_with_same_path?
Namespace.exists?(path: @group.path, parent: @new_parent_group)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def update_group_attributes
if @new_parent_group && @new_parent_group.visibility_level < @group.visibility_level
@@ -78,6 +80,7 @@ module Groups
@group.save!
end
+ # rubocop: disable CodeReuse/ActiveRecord
def update_children_and_projects_visibility
descendants = @group.descendants.where("visibility_level > ?", @new_parent_group.visibility_level)
@@ -90,6 +93,7 @@ module Groups
.where("visibility_level > ?", @new_parent_group.visibility_level)
.update_all(visibility_level: @new_parent_group.visibility_level)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def raise_transfer_error(message)
raise TransferError, ERROR_MESSAGES[message]
diff --git a/app/services/import_export_clean_up_service.rb b/app/services/import_export_clean_up_service.rb
index e75a951944e..3ecb51b60d0 100644
--- a/app/services/import_export_clean_up_service.rb
+++ b/app/services/import_export_clean_up_service.rb
@@ -26,10 +26,12 @@ class ImportExportCleanUpService
Gitlab::Popen.popen(%W(find #{path} -not -path #{path} -mmin +#{mmin} -delete))
end
+ # rubocop: disable CodeReuse/ActiveRecord
def clean_up_export_object_files
ImportExportUpload.where('updated_at < ?', mmin.minutes.ago).each do |upload|
upload.remove_export_file!
upload.save!
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/services/issuable/bulk_update_service.rb b/app/services/issuable/bulk_update_service.rb
index 051d5ba881d..c4beddf2294 100644
--- a/app/services/issuable/bulk_update_service.rb
+++ b/app/services/issuable/bulk_update_service.rb
@@ -2,6 +2,7 @@
module Issuable
class BulkUpdateService < IssuableBaseService
+ # rubocop: disable CodeReuse/ActiveRecord
def execute(type)
model_class = type.classify.constantize
update_class = type.classify.pluralize.constantize::UpdateService
@@ -28,6 +29,7 @@ module Issuable
success: !items.count.zero?
}
end
+ # rubocop: enable CodeReuse/ActiveRecord
private
diff --git a/app/services/issuable/common_system_notes_service.rb b/app/services/issuable/common_system_notes_service.rb
index 028b350ca07..765de9c66b0 100644
--- a/app/services/issuable/common_system_notes_service.rb
+++ b/app/services/issuable/common_system_notes_service.rb
@@ -17,6 +17,7 @@ module Issuable
create_labels_note(old_labels) if issuable.labels != old_labels
create_discussion_lock_note if issuable.previous_changes.include?('discussion_locked')
create_milestone_note if issuable.previous_changes.include?('milestone_id')
+ create_due_date_note if issuable.previous_changes.include?('due_date')
end
private
@@ -55,7 +56,9 @@ module Issuable
added_labels = issuable.labels - old_labels
removed_labels = old_labels - issuable.labels
- SystemNoteService.change_label(issuable, issuable.project, current_user, added_labels, removed_labels)
+ ResourceEvents::ChangeLabelsService
+ .new(issuable, current_user)
+ .execute(added_labels: added_labels, removed_labels: removed_labels)
end
def create_title_change_note(old_title)
@@ -88,6 +91,10 @@ module Issuable
SystemNoteService.change_milestone(issuable, issuable.project, current_user, issuable.milestone)
end
+ def create_due_date_note
+ SystemNoteService.change_due_date(issuable, issuable.project, current_user, issuable.due_date)
+ end
+
def create_discussion_lock_note
SystemNoteService.discussion_lock(issuable, current_user)
end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 7d60c65bb79..3e8b9f84042 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -68,11 +68,13 @@ class IssuableBaseService < BaseService
find_or_create_label_ids
end
+ # rubocop: disable CodeReuse/ActiveRecord
def filter_labels_in_param(key)
return if params[key].to_a.empty?
params[key] = available_labels.where(id: params[key]).pluck(:id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def find_or_create_label_ids
labels = params.delete(:labels)
@@ -129,28 +131,19 @@ class IssuableBaseService < BaseService
params.merge!(command_params)
end
- def create_issuable(issuable, attributes, label_ids:)
- issuable.with_transaction_returning_status do
- if issuable.save
- issuable.update(label_ids: label_ids)
- end
- end
- end
-
def create(issuable)
handle_quick_actions_on_create(issuable)
filter_params(issuable)
params.delete(:state_event)
params[:author] ||= current_user
-
- label_ids = process_label_ids(params)
+ params[:label_ids] = issuable.label_ids.to_a + process_label_ids(params)
issuable.assign_attributes(params)
before_create(issuable)
- if params.present? && create_issuable(issuable, params, label_ids: label_ids)
+ if issuable.save
after_create(issuable)
execute_hooks(issuable)
invalidate_cache_counts(issuable, users: issuable.assignees)
@@ -256,6 +249,7 @@ class IssuableBaseService < BaseService
end
end
+ # rubocop: disable CodeReuse/ActiveRecord
def change_todo(issuable)
case params.delete(:todo_event)
when 'add'
@@ -265,6 +259,7 @@ class IssuableBaseService < BaseService
todo_service.mark_todos_as_done_by_ids(todo, current_user) if todo
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def toggle_award(issuable)
award = params.delete(:emoji_award)
diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb
index 25389a946bb..ef08adf4f92 100644
--- a/app/services/issues/base_service.rb
+++ b/app/services/issues/base_service.rb
@@ -31,6 +31,7 @@ module Issues
issue.project.execute_services(issue_data, hooks_scope)
end
+ # rubocop: disable CodeReuse/ActiveRecord
def filter_assignee(issuable)
return if params[:assignee_ids].blank?
@@ -48,6 +49,7 @@ module Issues
params.delete(:assignee_ids)
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def update_project_counter_caches?(issue)
super || issue.confidential_changed?
diff --git a/app/services/issues/move_service.rb b/app/services/issues/move_service.rb
index 841bce9949e..d2bdba1e627 100644
--- a/app/services/issues/move_service.rb
+++ b/app/services/issues/move_service.rb
@@ -36,6 +36,7 @@ module Issues
def update_new_issue
rewrite_notes
+ copy_resource_label_events
rewrite_issue_award_emoji
add_note_moved_from
end
@@ -57,6 +58,7 @@ module Issues
CreateService.new(@new_project, @current_user, new_params).execute
end
+ # rubocop: disable CodeReuse/ActiveRecord
def cloneable_label_ids
params = {
project_id: @new_project.id,
@@ -66,6 +68,7 @@ module Issues
LabelsFinder.new(current_user, params).execute.pluck(:id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def cloneable_milestone_id
title = @old_issue.milestone&.title
@@ -96,6 +99,20 @@ module Issues
end
end
+ # rubocop: disable CodeReuse/ActiveRecord
+ def copy_resource_label_events
+ @old_issue.resource_label_events.find_in_batches do |batch|
+ events = batch.map do |event|
+ event.attributes
+ .except('id', 'reference', 'reference_html')
+ .merge('issue_id' => @new_issue.id, 'action' => ResourceLabelEvent.actions[event.action])
+ end
+
+ Gitlab::Database.bulk_insert(ResourceLabelEvent.table_name, events)
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
def rewrite_issue_award_emoji
rewrite_award_emoji(@old_issue, @new_issue)
end
diff --git a/app/services/issues/referenced_merge_requests_service.rb b/app/services/issues/referenced_merge_requests_service.rb
index 40d78502697..a69cd324b1e 100644
--- a/app/services/issues/referenced_merge_requests_service.rb
+++ b/app/services/issues/referenced_merge_requests_service.rb
@@ -2,6 +2,7 @@
module Issues
class ReferencedMergeRequestsService < Issues::BaseService
+ # rubocop: disable CodeReuse/ActiveRecord
def execute(issue)
referenced = referenced_merge_requests(issue)
closed_by = closed_by_merge_requests(issue)
@@ -12,6 +13,7 @@ module Issues
[sort_by_iid(referenced), sort_by_iid(closed_by)]
end
+ # rubocop: enable CodeReuse/ActiveRecord
def referenced_merge_requests(issue)
merge_requests = extract_merge_requests(issue)
@@ -29,6 +31,7 @@ module Issues
)
end
+ # rubocop: disable CodeReuse/ActiveRecord
def closed_by_merge_requests(issue)
return [] unless issue.open?
@@ -39,6 +42,7 @@ module Issues
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
+ # rubocop: enable CodeReuse/ActiveRecord
private
@@ -54,10 +58,12 @@ module Issues
ext.merge_requests
end
+ # rubocop: disable CodeReuse/ActiveRecord
def issue_notes(issue)
@issue_notes ||= {}
@issue_notes[issue] ||= issue.notes.includes(:author)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def sort_by_iid(merge_requests)
Gitlab::IssuableSorter.sort(project, merge_requests) { |mr| mr.iid.to_s }
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index faa4c8a5a4f..b54b0bf6ef6 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -67,6 +67,7 @@ module Issues
issue.move_between(issue_before, issue_after)
end
+ # rubocop: disable CodeReuse/ActiveRecord
def change_issue_duplicate(issue)
canonical_issue_id = params.delete(:canonical_issue_id)
canonical_issue = IssuesFinder.new(current_user).find_by(id: canonical_issue_id)
@@ -75,6 +76,7 @@ module Issues
Issues::DuplicateService.new(project, current_user).execute(issue, canonical_issue)
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def move_issue_to_new_project(issue)
target_project = params.delete(:target_project)
@@ -89,6 +91,7 @@ module Issues
private
+ # rubocop: disable CodeReuse/ActiveRecord
def get_issue_if_allowed(id, board_group_id = nil)
return unless id
@@ -101,6 +104,7 @@ module Issues
issue if can?(current_user, :update_issue, issue)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def create_confidentiality_note(issue)
SystemNoteService.change_issue_confidentiality(issue, issue.project, current_user)
diff --git a/app/services/labels/find_or_create_service.rb b/app/services/labels/find_or_create_service.rb
index e4486764a4d..628873519d7 100644
--- a/app/services/labels/find_or_create_service.rb
+++ b/app/services/labels/find_or_create_service.rb
@@ -29,6 +29,7 @@ module Labels
# Only creates the label if current_user can do so, if the label does not exist
# and the user can not create the label, nil is returned
+ # rubocop: disable CodeReuse/ActiveRecord
def find_or_create_label
new_label = available_labels.find_by(title: title)
@@ -39,6 +40,7 @@ module Labels
new_label
end
+ # rubocop: enable CodeReuse/ActiveRecord
def title
params[:title] || params[:name]
diff --git a/app/services/labels/promote_service.rb b/app/services/labels/promote_service.rb
index 623a5f0950e..f30ad706c63 100644
--- a/app/services/labels/promote_service.rb
+++ b/app/services/labels/promote_service.rb
@@ -4,6 +4,7 @@ module Labels
class PromoteService < BaseService
BATCH_SIZE = 1000
+ # rubocop: disable CodeReuse/ActiveRecord
def execute(label)
return unless project.group &&
label.is_a?(ProjectLabel)
@@ -13,6 +14,7 @@ module Labels
label_ids_for_merge(new_label).find_in_batches(batch_size: BATCH_SIZE) do |batched_ids|
update_issuables(new_label, batched_ids)
+ update_resource_label_events(new_label, batched_ids)
update_issue_board_lists(new_label, batched_ids)
update_priorities(new_label, batched_ids)
subscribe_users(new_label, batched_ids)
@@ -26,9 +28,11 @@ module Labels
new_label
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
private
+ # rubocop: disable CodeReuse/ActiveRecord
def subscribe_users(new_label, label_ids)
# users can be subscribed to multiple labels that will be merged into the group one
# we want to keep only one subscription / user
@@ -37,7 +41,9 @@ module Labels
.pluck('MAX(id)')
Subscription.where(id: ids_to_update).update_all(subscribable_id: new_label.id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def label_ids_for_merge(new_label)
LabelsFinder
.new(current_user, title: new_label.title, group_id: project.group.id)
@@ -45,28 +51,45 @@ module Labels
.where.not(id: new_label)
.select(:id) # Can't use pluck() to avoid object-creation because of the batching
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def update_issuables(new_label, label_ids)
LabelLink
.where(label: label_ids)
.update_all(label_id: new_label)
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
+ def update_resource_label_events(new_label, label_ids)
+ ResourceLabelEvent
+ .where(label: label_ids)
+ .update_all(label_id: new_label)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ # rubocop: disable CodeReuse/ActiveRecord
def update_issue_board_lists(new_label, label_ids)
List
.where(label: label_ids)
.update_all(label_id: new_label)
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def update_priorities(new_label, label_ids)
LabelPriority
.where(label: label_ids)
.update_all(label_id: new_label)
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def update_project_labels(label_ids)
Label.where(id: label_ids).destroy_all # rubocop: disable DestroyAll
end
+ # rubocop: enable CodeReuse/ActiveRecord
def clone_label_to_group_label(label)
params = label.attributes.slice('title', 'description', 'color')
diff --git a/app/services/labels/transfer_service.rb b/app/services/labels/transfer_service.rb
index 1bd8d9fc325..52360f775dc 100644
--- a/app/services/labels/transfer_service.rb
+++ b/app/services/labels/transfer_service.rb
@@ -32,16 +32,19 @@ module Labels
attr_reader :current_user, :old_group, :project
+ # rubocop: disable CodeReuse/ActiveRecord
def labels_to_transfer
- label_ids = []
- label_ids << group_labels_applied_to_issues.select(:id)
- label_ids << group_labels_applied_to_merge_requests.select(:id)
-
- union = Gitlab::SQL::Union.new(label_ids)
-
- Label.where("labels.id IN (#{union.to_sql})").reorder(nil).uniq # rubocop:disable GitlabSecurity/SqlInjection
+ Label
+ .from_union([
+ group_labels_applied_to_issues,
+ group_labels_applied_to_merge_requests
+ ])
+ .reorder(nil)
+ .uniq
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def group_labels_applied_to_issues
Label.joins(:issues)
.where(
@@ -49,7 +52,9 @@ module Labels
labels: { type: 'GroupLabel', group_id: old_group.id }
)
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def group_labels_applied_to_merge_requests
Label.joins(:merge_requests)
.where(
@@ -57,6 +62,7 @@ module Labels
labels: { type: 'GroupLabel', group_id: old_group.id }
)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def find_or_create_label!(label)
params = label.attributes.slice('title', 'description', 'color')
@@ -65,6 +71,7 @@ module Labels
new_label.id
end
+ # rubocop: disable CodeReuse/ActiveRecord
def update_label_links(labels, old_label_id:, new_label_id:)
# use 'labels' relation to get label_link ids only of issues/MRs
# in the project being transferred.
@@ -76,10 +83,13 @@ module Labels
LabelLink.where(id: link_ids, label_id: old_label_id)
.update_all(label_id: new_label_id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def update_label_priorities(old_label_id:, new_label_id:)
LabelPriority.where(project_id: project.id, label_id: old_label_id)
.update_all(label_id: new_label_id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/app/services/lfs/file_transformer.rb b/app/services/lfs/file_transformer.rb
index c8eccb8e6cd..6ecf583cb6a 100644
--- a/app/services/lfs/file_transformer.rb
+++ b/app/services/lfs/file_transformer.rb
@@ -55,11 +55,13 @@ module Lfs
@cached_attributes ||= Gitlab::Git::AttributesAtRefParser.new(repository, branch_name)
end
+ # rubocop: disable CodeReuse/ActiveRecord
def create_lfs_object!(lfs_pointer_file, file_content)
LfsObject.find_or_create_by(oid: lfs_pointer_file.sha256, size: lfs_pointer_file.size) do |lfs_object|
lfs_object.file = CarrierWaveStringFile.new(file_content)
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def link_lfs_object!(lfs_object)
project.lfs_objects << lfs_object
diff --git a/app/services/lfs/lock_file_service.rb b/app/services/lfs/lock_file_service.rb
index 78434909d68..c7730d24bdc 100644
--- a/app/services/lfs/lock_file_service.rb
+++ b/app/services/lfs/lock_file_service.rb
@@ -18,9 +18,11 @@ module Lfs
private
+ # rubocop: disable CodeReuse/ActiveRecord
def current_lock
project.lfs_file_locks.find_by(path: params[:path])
end
+ # rubocop: enable CodeReuse/ActiveRecord
def create_lock!
lock = project.lfs_file_locks.create!(user: current_user,
diff --git a/app/services/lfs/locks_finder_service.rb b/app/services/lfs/locks_finder_service.rb
index d52cf0e3cc4..4a5b2a52921 100644
--- a/app/services/lfs/locks_finder_service.rb
+++ b/app/services/lfs/locks_finder_service.rb
@@ -10,10 +10,12 @@ module Lfs
private
+ # rubocop: disable CodeReuse/ActiveRecord
def find_locks
options = params.slice(:id, :path).compact.symbolize_keys
project.lfs_file_locks.where(options)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/app/services/lfs/unlock_file_service.rb b/app/services/lfs/unlock_file_service.rb
index 4d1443bf772..a42916d86bb 100644
--- a/app/services/lfs/unlock_file_service.rb
+++ b/app/services/lfs/unlock_file_service.rb
@@ -32,6 +32,7 @@ module Lfs
end
end
+ # rubocop: disable CodeReuse/ActiveRecord
def lock
return @lock if defined?(@lock)
@@ -41,5 +42,6 @@ module Lfs
project.lfs_file_locks.find_by!(path: params[:path])
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index e6dd0e12a3a..aa5d8406d0f 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -55,6 +55,7 @@ module MergeRequests
end
# Returns all origin and fork merge requests from `@project` satisfying passed arguments.
+ # rubocop: disable CodeReuse/ActiveRecord
def merge_requests_for(source_branch, mr_states: [:opened])
MergeRequest
.with_state(mr_states)
@@ -62,6 +63,7 @@ module MergeRequests
.preload(:source_project) # we don't need a #includes since we're just preloading for the #select
.select(&:source_project)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def pipeline_merge_requests(pipeline)
merge_requests_for(pipeline.ref).each do |merge_request|
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index 55750269bb4..0e76d2cc3ab 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -20,6 +20,8 @@ module MergeRequests
if merge_request.can_be_created
compare_branches
assign_title_and_description
+ assign_labels
+ assign_milestone
end
merge_request
@@ -135,6 +137,20 @@ module MergeRequests
append_closes_description
end
+ def assign_labels
+ return unless target_project.issues_enabled? && issue
+ return if merge_request.label_ids&.any?
+
+ merge_request.label_ids = issue.try(:label_ids)
+ end
+
+ def assign_milestone
+ return unless target_project.issues_enabled? && issue
+ return if merge_request.milestone_id.present?
+
+ merge_request.milestone_id = issue.try(:milestone_id)
+ end
+
def append_closes_description
return unless issue&.to_reference.present?
@@ -185,7 +201,9 @@ module MergeRequests
end
def issue
- @issue ||= target_project.get_issue(issue_iid, current_user)
+ strong_memoize(:issue) do
+ target_project.get_issue(issue_iid, current_user)
+ end
end
end
end
diff --git a/app/services/merge_requests/create_from_issue_service.rb b/app/services/merge_requests/create_from_issue_service.rb
index fd91dc4acd0..020af0bb950 100644
--- a/app/services/merge_requests/create_from_issue_service.rb
+++ b/app/services/merge_requests/create_from_issue_service.rb
@@ -16,8 +16,6 @@ module MergeRequests
def execute
return error('Invalid issue iid') unless @issue_iid.present? && issue.present?
- params[:label_ids] = issue.label_ids if issue.label_ids.any?
-
result = CreateBranchService.new(project, current_user).execute(branch_name, ref)
return result if result[:status] == :error
@@ -34,9 +32,11 @@ module MergeRequests
private
+ # rubocop: disable CodeReuse/ActiveRecord
def issue
@issue ||= IssuesFinder.new(current_user, project_id: project.id).find_by(iid: @issue_iid)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def branch_name
@branch ||= @branch_name || issue.to_branch_name
@@ -58,8 +58,7 @@ module MergeRequests
source_project_id: project.id,
source_branch: branch_name,
target_project_id: project.id,
- target_branch: ref,
- milestone_id: issue.milestone_id
+ target_branch: ref
}
end
diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb
index c36a2ecbfe3..6081a7d1de0 100644
--- a/app/services/merge_requests/create_service.rb
+++ b/app/services/merge_requests/create_service.rb
@@ -49,6 +49,7 @@ module MergeRequests
merge_request.update(head_pipeline_id: pipeline.id) if pipeline
end
+ # rubocop: disable CodeReuse/ActiveRecord
def head_pipeline_for(merge_request)
return unless merge_request.source_project
@@ -59,6 +60,7 @@ module MergeRequests
pipelines.order(id: :desc).first
end
+ # rubocop: enable CodeReuse/ActiveRecord
def set_projects!
# @project is used to determine whether the user can set the merge request's
diff --git a/app/services/merge_requests/delete_non_latest_diffs_service.rb b/app/services/merge_requests/delete_non_latest_diffs_service.rb
index 2a8ea316921..d5929446122 100644
--- a/app/services/merge_requests/delete_non_latest_diffs_service.rb
+++ b/app/services/merge_requests/delete_non_latest_diffs_service.rb
@@ -8,6 +8,7 @@ module MergeRequests
@merge_request = merge_request
end
+ # rubocop: disable CodeReuse/ActiveRecord
def execute
diffs = @merge_request.non_latest_diffs.with_files
@@ -16,5 +17,6 @@ module MergeRequests
DeleteDiffFilesWorker.bulk_perform_in(index * 5.minutes, ids)
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index 48da796505f..bcdd752ddc4 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -51,6 +51,7 @@ module MergeRequests
# and close if push to master include last commit from merge request
# We need this to close(as merged) merge requests that were merged into
# target branch manually
+ # rubocop: disable CodeReuse/ActiveRecord
def post_merge_manually_merged
commit_ids = @commits.map(&:id)
merge_requests = @project.merge_requests.preload(:latest_merge_request_diff).opened.where(target_branch: @branch_name).to_a
@@ -67,6 +68,7 @@ module MergeRequests
.execute(merge_request)
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def force_push?
Gitlab::Checks::ForcePush.force_push?(@project, @oldrev, @newrev)
@@ -74,6 +76,7 @@ module MergeRequests
# Refresh merge request diff if we push to source or target branch of merge request
# Note: we should update merge requests from forks too
+ # rubocop: disable CodeReuse/ActiveRecord
def reload_merge_requests
merge_requests = @project.merge_requests.opened
.by_source_or_target_branch(@branch_name).to_a
@@ -101,6 +104,7 @@ module MergeRequests
# @source_merge_requests diffs (for MergeRequest#commit_shas for instance).
merge_requests_for_source_branch(reload: true)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def reset_merge_when_pipeline_succeeds
merge_requests_for_source_branch.each(&:reset_merge_when_pipeline_succeeds)
@@ -197,11 +201,13 @@ module MergeRequests
# If the merge requests closes any issues, save this information in the
# `MergeRequestsClosingIssues` model (as a performance optimization).
+ # rubocop: disable CodeReuse/ActiveRecord
def cache_merge_requests_closing_issues
@project.merge_requests.where(source_branch: @branch_name).each do |merge_request|
merge_request.cache_merge_request_closes_issues!(@current_user)
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def filter_merge_requests(merge_requests)
merge_requests.uniq.select(&:source_project)
diff --git a/app/services/merge_requests/reload_diffs_service.rb b/app/services/merge_requests/reload_diffs_service.rb
index 8d85dc9eb5f..b4d48fe92ad 100644
--- a/app/services/merge_requests/reload_diffs_service.rb
+++ b/app/services/merge_requests/reload_diffs_service.rb
@@ -27,10 +27,11 @@ module MergeRequests
current_user: current_user)
end
+ # rubocop: disable CodeReuse/ActiveRecord
def clear_cache(new_diff)
# Executing the iteration we cache highlighted diffs for each diff file of
# MergeRequestDiff.
- new_diff.diffs_collection.diff_files.to_a
+ cacheable_collection(new_diff).write_cache
# Remove cache for all diffs on this MR. Do not use the association on the
# model, as that will interfere with other actions happening when
@@ -38,8 +39,15 @@ module MergeRequests
MergeRequestDiff.where(merge_request: merge_request).each do |merge_request_diff|
next if merge_request_diff == new_diff
- merge_request_diff.diffs_collection.clear_cache!
+ cacheable_collection(merge_request_diff).clear_cache
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ def cacheable_collection(diff)
+ # There are scenarios where we don't need to request Diff Stats.
+ # Mainly when clearing / writing diff caches.
+ diff.diffs(include_stats: false)
+ end
end
end
diff --git a/app/services/milestones/promote_service.rb b/app/services/milestones/promote_service.rb
index 660b4faaec0..39071b5dc14 100644
--- a/app/services/milestones/promote_service.rb
+++ b/app/services/milestones/promote_service.rb
@@ -26,6 +26,7 @@ module Milestones
private
+ # rubocop: disable CodeReuse/ActiveRecord
def milestone_ids_for_merge(group_milestone)
# Pluck need to be used here instead of select so the array of ids
# is persistent after old milestones gets deleted.
@@ -35,6 +36,7 @@ module Milestones
milestones.pluck(:id)
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def move_children_to_group_milestone(group_milestone)
milestone_ids_for_merge(group_milestone).in_groups_of(100, false) do |milestone_ids|
@@ -59,6 +61,7 @@ module Milestones
milestone
end
+ # rubocop: disable CodeReuse/ActiveRecord
def update_children(group_milestone, milestone_ids)
issues = Issue.where(project_id: group_project_ids, milestone_id: milestone_ids)
merge_requests = MergeRequest.where(source_project_id: group_project_ids, milestone_id: milestone_ids)
@@ -67,18 +70,23 @@ module Milestones
issuable_collection.update_all(milestone_id: group_milestone.id)
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def group
@group ||= parent.group || raise_error('Project does not belong to a group.')
end
+ # rubocop: disable CodeReuse/ActiveRecord
def destroy_old_milestones(milestone)
Milestone.where(id: milestone_ids_for_merge(milestone)).destroy_all # rubocop: disable DestroyAll
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def group_project_ids
@group_project_ids ||= group.projects.pluck(:id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def raise_error(message)
raise PromoteMilestoneError, "Promotion failed - #{message}"
diff --git a/app/services/milestones/update_service.rb b/app/services/milestones/update_service.rb
index 81b20943bab..01ab8b37bac 100644
--- a/app/services/milestones/update_service.rb
+++ b/app/services/milestones/update_service.rb
@@ -2,6 +2,7 @@
module Milestones
class UpdateService < Milestones::BaseService
+ # rubocop: disable CodeReuse/ActiveRecord
def execute(milestone)
state = params[:state_event]
@@ -18,5 +19,6 @@ module Milestones
milestone
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/app/services/notification_recipient_service.rb b/app/services/notification_recipient_service.rb
index 5c0e8a35cb0..9c236d7f41d 100644
--- a/app/services/notification_recipient_service.rb
+++ b/app/services/notification_recipient_service.rb
@@ -58,6 +58,7 @@ module NotificationRecipientService
@recipients ||= []
end
+ # rubocop: disable CodeReuse/ActiveRecord
def add_recipients(users, type, reason)
if users.is_a?(ActiveRecord::Relation)
users = users.includes(:notification_settings)
@@ -66,10 +67,13 @@ module NotificationRecipientService
users = Array(users).compact
recipients.concat(users.map { |u| make_recipient(u, type, reason) })
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def user_scope
User.includes(:notification_settings)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def make_recipient(user, type, reason)
NotificationRecipient.new(
@@ -112,6 +116,7 @@ module NotificationRecipientService
end
# Get project/group users with CUSTOM notification level
+ # rubocop: disable CodeReuse/ActiveRecord
def add_custom_notifications
user_ids = []
@@ -128,6 +133,7 @@ module NotificationRecipientService
add_recipients(user_scope.where(id: user_ids), :watch, nil)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def add_project_watchers
add_recipients(project_watchers, :watch, nil) if project
@@ -138,6 +144,7 @@ module NotificationRecipientService
end
# Get project users with WATCH notification level
+ # rubocop: disable CodeReuse/ActiveRecord
def project_watchers
project_members_ids = user_ids_notifiable_on(project)
@@ -151,7 +158,9 @@ module NotificationRecipientService
user_scope.where(id: user_ids_with_project_setting.concat(user_ids_with_group_setting).uniq)
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def group_watchers
user_ids_with_group_global = user_ids_notifiable_on(group, :global)
user_ids = user_ids_with_global_level_watch(user_ids_with_group_global)
@@ -159,6 +168,7 @@ module NotificationRecipientService
user_scope.where(id: user_ids_with_group_setting)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def add_subscribed_users
return unless target.respond_to? :subscribers
@@ -166,6 +176,7 @@ module NotificationRecipientService
add_recipients(target.subscribers(project), :subscription, nil)
end
+ # rubocop: disable CodeReuse/ActiveRecord
def user_ids_notifiable_on(resource, notification_level = nil)
return [] unless resource
@@ -177,6 +188,7 @@ module NotificationRecipientService
scope.pluck(:user_id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
# Build a list of user_ids based on project notification settings
def select_project_members_ids(global_setting, user_ids_global_level_watch)
@@ -194,14 +206,19 @@ module NotificationRecipientService
uids + (global_setting & user_ids_global_level_watch) - project_members
end
+ # rubocop: disable CodeReuse/ActiveRecord
def user_ids_with_global_level_watch(ids)
settings_with_global_level_of(:watch, ids).pluck(:user_id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def user_ids_with_global_level_custom(ids, action)
settings_with_global_level_of(:custom, ids).pluck(:user_id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def settings_with_global_level_of(level, ids)
NotificationSetting.where(
user_id: ids,
@@ -209,6 +226,7 @@ module NotificationRecipientService
level: NotificationSetting.levels[level]
)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def add_labels_subscribers(labels: nil)
return unless target.respond_to? :labels
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index 4511c500fca..50fa373025b 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -407,6 +407,12 @@ class NotificationService
end
end
+ def autodevops_disabled(pipeline, recipients)
+ recipients.each do |recipient|
+ mailer.autodevops_disabled_email(pipeline, recipient).deliver_later
+ end
+ end
+
def pages_domain_verification_succeeded(domain)
recipients_for_pages_domain(domain).each do |user|
mailer.pages_domain_verification_succeeded_email(domain, user).deliver_later
diff --git a/app/services/preview_markdown_service.rb b/app/services/preview_markdown_service.rb
index 11b996ed4b6..de8757006f1 100644
--- a/app/services/preview_markdown_service.rb
+++ b/app/services/preview_markdown_service.rb
@@ -43,6 +43,10 @@ class PreviewMarkdownService < BaseService
end
def markdown_engine
- CacheMarkdownField::MarkdownEngine.from_version(params[:markdown_version].to_i)
+ if params[:legacy_render]
+ :redcarpet
+ else
+ CacheMarkdownField::MarkdownEngine.from_version(params[:markdown_version].to_i)
+ end
end
end
diff --git a/app/services/projects/auto_devops/disable_service.rb b/app/services/projects/auto_devops/disable_service.rb
new file mode 100644
index 00000000000..1b578a3c5ce
--- /dev/null
+++ b/app/services/projects/auto_devops/disable_service.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module Projects
+ module AutoDevops
+ class DisableService < BaseService
+ def execute
+ return false unless implicitly_enabled_and_first_pipeline_failure?
+
+ disable_auto_devops
+ end
+
+ private
+
+ def implicitly_enabled_and_first_pipeline_failure?
+ project.has_auto_devops_implicitly_enabled? &&
+ first_pipeline_failure?
+ end
+
+ # We're using `limit` to optimize `auto_devops pipeline` query,
+ # since we only care about the first element, and using only `.count`
+ # is an expensive operation. See
+ # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21172#note_99037378
+ # for more context.
+ # rubocop: disable CodeReuse/ActiveRecord
+ def first_pipeline_failure?
+ auto_devops_pipelines.success.limit(1).count.zero? &&
+ auto_devops_pipelines.failed.limit(1).count.nonzero?
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ def disable_auto_devops
+ project.auto_devops_attributes = { enabled: false }
+ project.save!
+ end
+
+ def auto_devops_pipelines
+ @auto_devops_pipelines ||= project.pipelines.auto_devops_source
+ end
+ end
+ end
+end
diff --git a/app/services/projects/base_move_relations_service.rb b/app/services/projects/base_move_relations_service.rb
index 78cc2869b72..24dec1f3a45 100644
--- a/app/services/projects/base_move_relations_service.rb
+++ b/app/services/projects/base_move_relations_service.rb
@@ -13,6 +13,7 @@ module Projects
private
+ # rubocop: disable CodeReuse/ActiveRecord
def prepare_relation(relation, id_param = :id)
if Gitlab::Database.postgresql?
relation
@@ -20,5 +21,6 @@ module Projects
relation.model.where("#{id_param}": relation.pluck(id_param))
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/app/services/projects/batch_forks_count_service.rb b/app/services/projects/batch_forks_count_service.rb
index 9bf369df999..6467744a435 100644
--- a/app/services/projects/batch_forks_count_service.rb
+++ b/app/services/projects/batch_forks_count_service.rb
@@ -5,6 +5,7 @@
# because the service use maps to retrieve the project ids
module Projects
class BatchForksCountService < Projects::BatchCountService
+ # rubocop: disable CodeReuse/ActiveRecord
def global_count
@global_count ||= begin
count_service.query(project_ids)
@@ -12,6 +13,7 @@ module Projects
.count
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def count_service
::Projects::ForksCountService
diff --git a/app/services/projects/batch_open_issues_count_service.rb b/app/services/projects/batch_open_issues_count_service.rb
index d375fcf9dbd..d6ff2291af8 100644
--- a/app/services/projects/batch_open_issues_count_service.rb
+++ b/app/services/projects/batch_open_issues_count_service.rb
@@ -5,11 +5,13 @@
# because the service use maps to retrieve the project ids
module Projects
class BatchOpenIssuesCountService < Projects::BatchCountService
+ # rubocop: disable CodeReuse/ActiveRecord
def global_count
@global_count ||= begin
count_service.query(project_ids).group(:project_id).count
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def count_service
::Projects::OpenIssuesCountService
diff --git a/app/services/projects/container_repository/destroy_service.rb b/app/services/projects/container_repository/destroy_service.rb
new file mode 100644
index 00000000000..1f5af7970d6
--- /dev/null
+++ b/app/services/projects/container_repository/destroy_service.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Projects
+ module ContainerRepository
+ class DestroyService < BaseService
+ def execute(container_repository)
+ return false unless can?(current_user, :update_container_image, project)
+
+ # Delete tags outside of the transaction to avoid hitting an idle-in-transaction timeout
+ container_repository.delete_tags!
+ container_repository.destroy
+ end
+ end
+ end
+end
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index 02a3a3eb096..0e6a7e8da54 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -79,17 +79,21 @@ module Projects
@project.errors.add(:namespace, "is not valid")
end
+ # rubocop: disable CodeReuse/ActiveRecord
def allowed_fork?(source_project_id)
return true if source_project_id.nil?
source_project = Project.find_by(id: source_project_id)
current_user.can?(:fork_project, source_project)
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def allowed_namespace?(user, namespace_id)
namespace = Namespace.find_by(id: namespace_id)
current_user.can?(:create_projects, namespace)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def after_create_actions
log_info("#{@project.owner.name} created a new project \"#{@project.full_name}\"")
@@ -167,12 +171,14 @@ module Projects
@project
end
+ # rubocop: disable CodeReuse/ActiveRecord
def create_services_from_active_templates(project)
Service.where(template: true, active: true).each do |template|
service = Service.build_from_template(project.id, template)
service.save!
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def set_project_name_from_path
# Set project name from path
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index 76e22507698..210571b6b4e 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -107,15 +107,19 @@ module Projects
mv_repository(old_path, new_path)
end
+ # rubocop: disable CodeReuse/ActiveRecord
def repo_exists?(path)
gitlab_shell.exists?(project.repository_storage, path + '.git')
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def mv_repository(from_path, to_path)
return true unless gitlab_shell.exists?(project.repository_storage, from_path + '.git')
gitlab_shell.mv_repository(project.repository_storage, from_path, to_path)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def attempt_rollback(project, message)
return unless project
@@ -129,11 +133,11 @@ module Projects
end
def attempt_destroy_transaction(project)
- Project.transaction do
- unless remove_legacy_registry_tags
- raise_error('Failed to remove some tags in project container registry. Please try again or contact administrator.')
- end
+ unless remove_registry_tags
+ raise_error('Failed to remove some tags in project container registry. Please try again or contact administrator.')
+ end
+ Project.transaction do
log_destroy_event
trash_repositories!
@@ -152,6 +156,17 @@ module Projects
log_info("Attempting to destroy #{project.full_path} (#{project.id})")
end
+ def remove_registry_tags
+ return false unless remove_legacy_registry_tags
+
+ project.container_repositories.find_each do |container_repository|
+ service = Projects::ContainerRepository::DestroyService.new(project, current_user)
+ service.execute(container_repository)
+ end
+
+ true
+ end
+
##
# This method makes sure that we correctly remove registry tags
# for legacy image repository (when repository path equals project path).
@@ -159,7 +174,7 @@ module Projects
def remove_legacy_registry_tags
return true unless Gitlab.config.registry.enabled
- ContainerRepository.build_root_repository(project).tap do |repository|
+ ::ContainerRepository.build_root_repository(project).tap do |repository|
break repository.has_tags? ? repository.delete_tags! : true
end
end
diff --git a/app/services/projects/detect_repository_languages_service.rb b/app/services/projects/detect_repository_languages_service.rb
index 3488b9ce47e..4a837a4fb6a 100644
--- a/app/services/projects/detect_repository_languages_service.rb
+++ b/app/services/projects/detect_repository_languages_service.rb
@@ -4,6 +4,7 @@ module Projects
class DetectRepositoryLanguagesService < BaseService
attr_reader :detected_repository_languages, :programming_languages
+ # rubocop: disable CodeReuse/ActiveRecord
def execute
repository_languages = project.repository_languages
detection = Gitlab::LanguageDetection.new(repository, repository_languages)
@@ -28,9 +29,11 @@ module Projects
project.repository_languages.reload
end
+ # rubocop: enable CodeReuse/ActiveRecord
private
+ # rubocop: disable CodeReuse/ActiveRecord
def ensure_programming_languages(detection)
existing_languages = ProgrammingLanguage.where(name: detection.languages)
return existing_languages if detection.languages.size == existing_languages.size
@@ -42,7 +45,9 @@ module Projects
existing_languages + created_languages
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def create_language(name, color)
ProgrammingLanguage.transaction do
ProgrammingLanguage.where(name: name).first_or_create(color: color)
@@ -50,5 +55,6 @@ module Projects
rescue ActiveRecord::RecordNotUnique
retry
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/app/services/projects/enable_deploy_key_service.rb b/app/services/projects/enable_deploy_key_service.rb
index b7c172028e9..102088e9557 100644
--- a/app/services/projects/enable_deploy_key_service.rb
+++ b/app/services/projects/enable_deploy_key_service.rb
@@ -2,6 +2,7 @@
module Projects
class EnableDeployKeyService < BaseService
+ # rubocop: disable CodeReuse/ActiveRecord
def execute
key = accessible_keys.find_by(id: params[:key_id] || params[:id])
return unless key
@@ -12,6 +13,7 @@ module Projects
key
end
+ # rubocop: enable CodeReuse/ActiveRecord
private
diff --git a/app/services/projects/forks_count_service.rb b/app/services/projects/forks_count_service.rb
index b570c6d4754..00e73148358 100644
--- a/app/services/projects/forks_count_service.rb
+++ b/app/services/projects/forks_count_service.rb
@@ -7,11 +7,13 @@ module Projects
'forks_count'
end
+ # rubocop: disable CodeReuse/ActiveRecord
def self.query(project_ids)
# We can't directly change ForkedProjectLink to ForkNetworkMember here
# Nowadays, when a call using v3 to projects/:id/fork is made,
# the relationship to ForkNetworkMember is not updated
ForkedProjectLink.where(forked_from_project: project_ids)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/app/services/projects/gitlab_projects_import_service.rb b/app/services/projects/gitlab_projects_import_service.rb
index 044afa1d5e1..a315adf42f0 100644
--- a/app/services/projects/gitlab_projects_import_service.rb
+++ b/app/services/projects/gitlab_projects_import_service.rb
@@ -32,11 +32,13 @@ module Projects
Project.find_by_full_path("#{current_namespace.full_path}/#{params[:path]}").present?
end
+ # rubocop: disable CodeReuse/ActiveRecord
def current_namespace
strong_memoize(:current_namespace) do
Namespace.find_by(id: params[:namespace_id])
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def overwrite?
strong_memoize(:overwrite) do
diff --git a/app/services/projects/hashed_storage/migrate_repository_service.rb b/app/services/projects/hashed_storage/migrate_repository_service.rb
index 641d46e6591..4462d504071 100644
--- a/app/services/projects/hashed_storage/migrate_repository_service.rb
+++ b/app/services/projects/hashed_storage/migrate_repository_service.rb
@@ -47,10 +47,13 @@ module Projects
private
+ # rubocop: disable CodeReuse/ActiveRecord
def has_wiki?
gitlab_shell.exists?(project.repository_storage, "#{old_wiki_disk_path}.git")
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def move_repository(from_name, to_name)
from_exists = gitlab_shell.exists?(project.repository_storage, "#{from_name}.git")
to_exists = gitlab_shell.exists?(project.repository_storage, "#{to_name}.git")
@@ -67,6 +70,7 @@ module Projects
gitlab_shell.mv_repository(project.repository_storage, from_name, to_name)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def rollback_folder_move
move_repository(new_disk_path, old_disk_path)
diff --git a/app/services/projects/lfs_pointers/lfs_download_service.rb b/app/services/projects/lfs_pointers/lfs_download_service.rb
index 7d4fa4e08df..1c4a8d05be6 100644
--- a/app/services/projects/lfs_pointers/lfs_download_service.rb
+++ b/app/services/projects/lfs_pointers/lfs_download_service.rb
@@ -4,6 +4,7 @@
module Projects
module LfsPointers
class LfsDownloadService < BaseService
+ # rubocop: disable CodeReuse/ActiveRecord
def execute(oid, url)
return unless project&.lfs_enabled? && oid.present? && url.present?
@@ -20,6 +21,7 @@ module Projects
rescue StandardError => e
Rails.logger.error("LFS file with oid #{oid} could't be downloaded from #{sanitized_uri.sanitized_url}: #{e.message}")
end
+ # rubocop: enable CodeReuse/ActiveRecord
private
diff --git a/app/services/projects/lfs_pointers/lfs_import_service.rb b/app/services/projects/lfs_pointers/lfs_import_service.rb
index 97ce681a911..9215fa0a7bf 100644
--- a/app/services/projects/lfs_pointers/lfs_import_service.rb
+++ b/app/services/projects/lfs_pointers/lfs_import_service.rb
@@ -41,6 +41,7 @@ module Projects
project.update(lfs_enabled: false)
end
+ # rubocop: disable CodeReuse/ActiveRecord
def get_download_links
existent_lfs = LfsListService.new(project).execute
linked_oids = LfsLinkService.new(project).execute(existent_lfs.keys)
@@ -50,6 +51,7 @@ module Projects
LfsDownloadLinkListService.new(project, remote_uri: current_endpoint_uri).execute(not_linked_lfs)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def lfsconfig_endpoint_uri
strong_memoize(:lfsconfig_endpoint_uri) do
diff --git a/app/services/projects/lfs_pointers/lfs_link_service.rb b/app/services/projects/lfs_pointers/lfs_link_service.rb
index a2eba8e124e..8401f3d1d89 100644
--- a/app/services/projects/lfs_pointers/lfs_link_service.rb
+++ b/app/services/projects/lfs_pointers/lfs_link_service.rb
@@ -16,6 +16,7 @@ module Projects
private
+ # rubocop: disable CodeReuse/ActiveRecord
def link_existing_lfs_objects(oids)
existent_lfs_objects = LfsObject.where(oid: oids)
@@ -26,6 +27,7 @@ module Projects
existent_lfs_objects.pluck(:oid)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
end
diff --git a/app/services/projects/move_deploy_keys_projects_service.rb b/app/services/projects/move_deploy_keys_projects_service.rb
index 9f3f44f30ea..b6a3af8c7b8 100644
--- a/app/services/projects/move_deploy_keys_projects_service.rb
+++ b/app/services/projects/move_deploy_keys_projects_service.rb
@@ -20,11 +20,13 @@ module Projects
.update_all(project_id: @project.id)
end
+ # rubocop: disable CodeReuse/ActiveRecord
def non_existent_deploy_keys_projects
source_project.deploy_keys_projects
.joins(:deploy_key)
.where.not(keys: { fingerprint: @project.deploy_keys.select(:fingerprint) })
end
+ # rubocop: enable CodeReuse/ActiveRecord
def remove_remaining_deploy_keys_projects
source_project.deploy_keys_projects.destroy_all # rubocop: disable DestroyAll
diff --git a/app/services/projects/move_forks_service.rb b/app/services/projects/move_forks_service.rb
index 076a7a50aa9..2948555a17c 100644
--- a/app/services/projects/move_forks_service.rb
+++ b/app/services/projects/move_forks_service.rb
@@ -17,6 +17,7 @@ module Projects
private
+ # rubocop: disable CodeReuse/ActiveRecord
def move_forked_project_links
# Update ancestor
ForkedProjectLink.where(forked_to_project: source_project)
@@ -26,16 +27,21 @@ module Projects
ForkedProjectLink.where(forked_from_project: source_project)
.update_all(forked_from_project_id: @project.id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def move_fork_network_members
ForkNetworkMember.where(project: source_project).update_all(project_id: @project.id)
ForkNetworkMember.where(forked_from_project: source_project).update_all(forked_from_project_id: @project.id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def update_root_project
# Update root network project
ForkNetwork.where(root_project: source_project).update_all(root_project_id: @project.id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def refresh_forks_count
Projects::ForksCountService.new(@project).refresh_cache
diff --git a/app/services/projects/move_lfs_objects_projects_service.rb b/app/services/projects/move_lfs_objects_projects_service.rb
index f78546a1e9c..308a54ad06e 100644
--- a/app/services/projects/move_lfs_objects_projects_service.rb
+++ b/app/services/projects/move_lfs_objects_projects_service.rb
@@ -24,8 +24,10 @@ module Projects
source_project.lfs_objects_projects.destroy_all # rubocop: disable DestroyAll
end
+ # rubocop: disable CodeReuse/ActiveRecord
def non_existent_lfs_objects_projects
source_project.lfs_objects_projects.where.not(lfs_object: @project.lfs_objects)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/app/services/projects/move_notification_settings_service.rb b/app/services/projects/move_notification_settings_service.rb
index 109a00dd6d9..e740c44bd26 100644
--- a/app/services/projects/move_notification_settings_service.rb
+++ b/app/services/projects/move_notification_settings_service.rb
@@ -31,10 +31,12 @@ module Projects
end
# Look for notification_settings in source_project that are not in the target project
+ # rubocop: disable CodeReuse/ActiveRecord
def non_existent_notifications
source_project.notification_settings
.select(:id)
.where.not(user_id: users_in_target_project)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/app/services/projects/move_project_authorizations_service.rb b/app/services/projects/move_project_authorizations_service.rb
index 60f2af88e99..2060a263751 100644
--- a/app/services/projects/move_project_authorizations_service.rb
+++ b/app/services/projects/move_project_authorizations_service.rb
@@ -33,10 +33,12 @@ module Projects
end
# Look for authorizations in source_project that are not in the target project
+ # rubocop: disable CodeReuse/ActiveRecord
def non_existent_authorization
source_project.project_authorizations
.select(:user_id)
.where.not(user: @project.authorized_users)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/app/services/projects/move_project_group_links_service.rb b/app/services/projects/move_project_group_links_service.rb
index 1efafdce36d..fb395ecb9a1 100644
--- a/app/services/projects/move_project_group_links_service.rb
+++ b/app/services/projects/move_project_group_links_service.rb
@@ -34,9 +34,11 @@ module Projects
end
# Look for groups in source_project that are not in the target project
+ # rubocop: disable CodeReuse/ActiveRecord
def non_existent_group_links
source_project.project_group_links
.where.not(group_id: group_links_in_target_project)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/app/services/projects/move_project_members_service.rb b/app/services/projects/move_project_members_service.rb
index ec983582d94..f28f44adc03 100644
--- a/app/services/projects/move_project_members_service.rb
+++ b/app/services/projects/move_project_members_service.rb
@@ -33,10 +33,12 @@ module Projects
end
# Look for members in source_project that are not in the target project
+ # rubocop: disable CodeReuse/ActiveRecord
def non_existent_members
source_project.members
.select(:id)
.where.not(user_id: @project.project_members.select(:user_id))
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/app/services/projects/open_issues_count_service.rb b/app/services/projects/open_issues_count_service.rb
index 5d6620c3c54..ee9884e9042 100644
--- a/app/services/projects/open_issues_count_service.rb
+++ b/app/services/projects/open_issues_count_service.rb
@@ -42,6 +42,7 @@ module Projects
cache_key(TOTAL_COUNT_KEY)
end
+ # rubocop: disable CodeReuse/ActiveRecord
def refresh_cache(&block)
if block_given?
super(&block)
@@ -59,11 +60,13 @@ module Projects
end
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
# We only show total issues count for reporters
# which are allowed to view confidential issues
# This will still show a discrepancy on issues number but should be less than before.
# Check https://gitlab.com/gitlab-org/gitlab-ce/issues/38418 description.
+ # rubocop: disable CodeReuse/ActiveRecord
def self.query(projects, public_only: true)
if public_only
Issue.opened.public_only.where(project: projects)
@@ -71,5 +74,6 @@ module Projects
Issue.opened.where(project: projects)
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/app/services/projects/propagate_service_template.rb b/app/services/projects/propagate_service_template.rb
index fdfa91801ab..633a263af7b 100644
--- a/app/services/projects/propagate_service_template.rb
+++ b/app/services/projects/propagate_service_template.rb
@@ -70,6 +70,7 @@ module Projects
)
end
+ # rubocop: disable CodeReuse/ActiveRecord
def service_hash
@service_hash ||=
begin
@@ -83,7 +84,9 @@ module Projects
end
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def run_callbacks(batch)
if active_external_issue_tracker?
Project.where(id: batch).update_all(has_external_issue_tracker: true)
@@ -93,6 +96,7 @@ module Projects
Project.where(id: batch).update_all(has_external_wiki: true)
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def active_external_issue_tracker?
@template.issue_tracker? && !@template.default
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index 3746cfef702..9d40ab166ff 100644
--- a/app/services/projects/transfer_service.rb
+++ b/app/services/projects/transfer_service.rb
@@ -37,6 +37,7 @@ module Projects
private
+ # rubocop: disable CodeReuse/ActiveRecord
def transfer(project)
@old_path = project.full_path
@old_group = project.group
@@ -54,6 +55,7 @@ module Projects
attempt_transfer_transaction
end
+ # rubocop: enable CodeReuse/ActiveRecord
def attempt_transfer_transaction
Project.transaction do
diff --git a/app/services/projects/unlink_fork_service.rb b/app/services/projects/unlink_fork_service.rb
index 2c0d91fe34f..a8b7c7f136a 100644
--- a/app/services/projects/unlink_fork_service.rb
+++ b/app/services/projects/unlink_fork_service.rb
@@ -2,6 +2,7 @@
module Projects
class UnlinkForkService < BaseService
+ # rubocop: disable CodeReuse/ActiveRecord
def execute
return unless @project.forked?
@@ -26,6 +27,7 @@ module Projects
@project.fork_network_member.destroy
@project.forked_project_link.destroy
end
+ # rubocop: enable CodeReuse/ActiveRecord
def refresh_forks_count(project)
Projects::ForksCountService.new(project).refresh_cache
diff --git a/app/services/projects/update_remote_mirror_service.rb b/app/services/projects/update_remote_mirror_service.rb
index 591b38b8151..9d0877d1ab2 100644
--- a/app/services/projects/update_remote_mirror_service.rb
+++ b/app/services/projects/update_remote_mirror_service.rb
@@ -5,10 +5,10 @@ module Projects
attr_reader :errors
def execute(remote_mirror)
- @errors = []
-
return success unless remote_mirror.enabled?
+ errors = []
+
begin
remote_mirror.ensure_remote!
repository.fetch_remote(remote_mirror.remote_name, no_tags: true)
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index e390d7a04c3..d6d9bacf232 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -6,6 +6,7 @@ module Projects
ValidationError = Class.new(StandardError)
+ # rubocop: disable CodeReuse/ActiveRecord
def execute
validate!
@@ -26,6 +27,7 @@ module Projects
rescue ValidationError => e
error(e.message)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def run_auto_devops_pipeline?
return false if project.repository.gitlab_ci_yml || !project.auto_devops&.previous_changes&.include?('enabled')
diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb
index a4c4c9e4812..02d68c3add3 100644
--- a/app/services/quick_actions/interpret_service.rb
+++ b/app/services/quick_actions/interpret_service.rb
@@ -111,10 +111,12 @@ module QuickActions
end
desc 'Assign'
+ # rubocop: disable CodeReuse/ActiveRecord
explanation do |users|
users = issuable.allows_multiple_assignees? ? users : users.take(1)
"Assigns #{users.map(&:to_reference).to_sentence}."
end
+ # rubocop: enable CodeReuse/ActiveRecord
params do
issuable.allows_multiple_assignees? ? '@user1 @user2' : '@user'
end
@@ -124,6 +126,7 @@ module QuickActions
parse_params do |assignee_param|
extract_users(assignee_param)
end
+ # rubocop: disable CodeReuse/ActiveRecord
command :assign do |users|
next if users.empty?
@@ -134,6 +137,7 @@ module QuickActions
[users.first.id]
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
desc do
if issuable.allows_multiple_assignees?
@@ -160,6 +164,7 @@ module QuickActions
# When multiple users are assigned, all will be unassigned if multiple assignees are no longer allowed
extract_users(unassign_param) if issuable.allows_multiple_assignees?
end
+ # rubocop: disable CodeReuse/ActiveRecord
command :unassign do |users = nil|
@updates[:assignee_ids] =
if users&.any?
@@ -168,6 +173,7 @@ module QuickActions
[]
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
desc 'Set milestone'
explanation do |milestone|
@@ -489,6 +495,30 @@ module QuickActions
"#{comment} #{TABLEFLIP}"
end
+ desc "Lock the discussion"
+ explanation "Locks the discussion"
+ condition do
+ issuable.is_a?(Issuable) &&
+ issuable.persisted? &&
+ !issuable.discussion_locked? &&
+ current_user.can?(:"admin_#{issuable.to_ability_name}", issuable)
+ end
+ command :lock do
+ @updates[:discussion_locked] = true
+ end
+
+ desc "Unlock the discussion"
+ explanation "Unlocks the discussion"
+ condition do
+ issuable.is_a?(Issuable) &&
+ issuable.persisted? &&
+ issuable.discussion_locked? &&
+ current_user.can?(:"admin_#{issuable.to_ability_name}", issuable)
+ end
+ command :unlock do
+ @updates[:discussion_locked] = false
+ end
+
# This is a dummy command, so that it appears in the autocomplete commands
desc 'CC'
params '@user'
@@ -522,6 +552,7 @@ module QuickActions
current_user.can?(:"update_#{issuable.to_ability_name}", issuable) &&
issuable.project.boards.count == 1
end
+ # rubocop: disable CodeReuse/ActiveRecord
command :board_move do |target_list_name|
label_ids = find_label_ids(target_list_name)
@@ -536,6 +567,7 @@ module QuickActions
@updates[:add_label_ids] = [label_id]
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
desc 'Mark this issue as a duplicate of another issue'
explanation do |duplicate_reference|
@@ -601,6 +633,7 @@ module QuickActions
@updates[:tag_message] = message
end
+ # rubocop: disable CodeReuse/ActiveRecord
def extract_users(params)
return [] if params.nil?
@@ -617,6 +650,7 @@ module QuickActions
users
end
+ # rubocop: enable CodeReuse/ActiveRecord
def find_milestones(project, params = {})
MilestonesFinder.new(params.merge(project_ids: [project.id], group_ids: [project.group&.id])).execute
@@ -653,6 +687,7 @@ module QuickActions
end
end
+ # rubocop: disable CodeReuse/ActiveRecord
def extract_references(arg, type)
ext = Gitlab::ReferenceExtractor.new(project, current_user)
@@ -660,5 +695,6 @@ module QuickActions
ext.references(type)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/app/services/quick_actions/target_service.rb b/app/services/quick_actions/target_service.rb
index d8ba52c6e50..69464c3c1ae 100644
--- a/app/services/quick_actions/target_service.rb
+++ b/app/services/quick_actions/target_service.rb
@@ -15,13 +15,17 @@ module QuickActions
private
+ # rubocop: disable CodeReuse/ActiveRecord
def issue(type_id)
IssuesFinder.new(current_user, project_id: project.id).find_by(iid: type_id) || project.issues.build
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def merge_request(type_id)
MergeRequestsFinder.new(current_user, project_id: project.id).find_by(iid: type_id) || project.merge_requests.build
end
+ # rubocop: enable CodeReuse/ActiveRecord
def commit(type_id)
project.commit(type_id)
diff --git a/app/services/resource_events/change_labels_service.rb b/app/services/resource_events/change_labels_service.rb
index 8edb0ddb3ed..039d6e2ebad 100644
--- a/app/services/resource_events/change_labels_service.rb
+++ b/app/services/resource_events/change_labels_service.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-# This service is not used yet, it will be used for:
-# https://gitlab.com/gitlab-org/gitlab-ce/issues/48483
module ResourceEvents
class ChangeLabelsService
attr_reader :resource, :user
@@ -25,6 +23,7 @@ module ResourceEvents
end
Gitlab::Database.bulk_insert(ResourceLabelEvent.table_name, labels)
+ resource.expire_note_etag_cache
end
private
diff --git a/app/services/resource_events/merge_into_notes_service.rb b/app/services/resource_events/merge_into_notes_service.rb
new file mode 100644
index 00000000000..596c0105ea0
--- /dev/null
+++ b/app/services/resource_events/merge_into_notes_service.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+# We store events about issuable label changes in a separate table (not as
+# other system notes), but we still want to display notes about label changes
+# as classic system notes in UI. This service generates "synthetic" notes for
+# label event changes and merges them with classic notes and sorts them by
+# creation time.
+
+module ResourceEvents
+ class MergeIntoNotesService
+ include Gitlab::Utils::StrongMemoize
+
+ attr_reader :resource, :current_user, :params
+
+ def initialize(resource, current_user, params = {})
+ @resource = resource
+ @current_user = current_user
+ @params = params
+ end
+
+ def execute(notes = [])
+ (notes + label_notes).sort_by { |n| n.created_at }
+ end
+
+ private
+
+ def label_notes
+ label_events_by_discussion_id.map do |discussion_id, events|
+ LabelNote.from_events(events, resource: resource, resource_parent: resource_parent)
+ end
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def label_events_by_discussion_id
+ return [] unless resource.respond_to?(:resource_label_events)
+
+ events = resource.resource_label_events.includes(:label, :user)
+ events = since_fetch_at(events)
+
+ events.group_by { |event| event.discussion_id }
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ def since_fetch_at(events)
+ return events unless params[:last_fetched_at].present?
+
+ last_fetched_at = Time.at(params.fetch(:last_fetched_at).to_i)
+ events.created_after(last_fetched_at - NotesFinder::FETCH_OVERLAP)
+ end
+
+ def resource_parent
+ strong_memoize(:resource_parent) do
+ resource.project || resource.group
+ end
+ end
+ end
+end
diff --git a/app/services/search/group_service.rb b/app/services/search/group_service.rb
index 34803d005e3..00372887985 100644
--- a/app/services/search/group_service.rb
+++ b/app/services/search/group_service.rb
@@ -11,11 +11,13 @@ module Search
@group = group
end
+ # rubocop: disable CodeReuse/ActiveRecord
def projects
return Project.none unless group
return @projects if defined? @projects
@projects = super.inside_path(group.full_path)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/app/services/search_service.rb b/app/services/search_service.rb
index 1b707d79b43..e0cbfac2420 100644
--- a/app/services/search_service.rb
+++ b/app/services/search_service.rb
@@ -8,6 +8,7 @@ class SearchService
@params = params.dup
end
+ # rubocop: disable CodeReuse/ActiveRecord
def project
return @project if defined?(@project)
@@ -19,7 +20,9 @@ class SearchService
nil
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def group
return @group if defined?(@group)
@@ -31,6 +34,7 @@ class SearchService
nil
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def show_snippets?
return @show_snippets if defined?(@show_snippets)
diff --git a/app/services/spam_check_service.rb b/app/services/spam_check_service.rb
index 895261925ba..51d300d4f1d 100644
--- a/app/services/spam_check_service.rb
+++ b/app/services/spam_check_service.rb
@@ -22,6 +22,7 @@ module SpamCheckService
# a dirty instance, which means it should be already assigned with the new
# attribute values.
# rubocop:disable Gitlab/ModuleWithInstanceVariables
+ # rubocop: disable CodeReuse/ActiveRecord
def spam_check(spammable, user)
spam_service = SpamService.new(spammable, @request)
@@ -29,5 +30,6 @@ module SpamCheckService
user.spam_logs.find_by(id: @spam_log_id)&.update!(recaptcha_verified: true)
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
# rubocop:enable Gitlab/ModuleWithInstanceVariables
end
diff --git a/app/services/submit_usage_ping_service.rb b/app/services/submit_usage_ping_service.rb
index 93c2e222963..62222d3fd2a 100644
--- a/app/services/submit_usage_ping_service.rb
+++ b/app/services/submit_usage_ping_service.rb
@@ -15,6 +15,7 @@ class SubmitUsagePingService
def execute
return false unless Gitlab::CurrentSettings.usage_ping_enabled?
+ return false if User.single_user&.requires_usage_stats_consent?
response = Gitlab::HTTP.post(
URL,
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index dda89830179..575678da1fa 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -98,66 +98,45 @@ module SystemNoteService
create_note(NoteSummary.new(issue, project, author, body, action: 'assignee'))
end
- # Called when one or more labels on a Noteable are added and/or removed
+ # Called when the milestone of a Noteable is changed
#
- # noteable - Noteable object
- # project - Project owning noteable
- # author - User performing the change
- # added_labels - Array of Labels added
- # removed_labels - Array of Labels removed
+ # noteable - Noteable object
+ # project - Project owning noteable
+ # author - User performing the change
+ # milestone - Milestone being assigned, or nil
#
# Example Note text:
#
- # "added ~1 and removed ~2 ~3 labels"
- #
- # "added ~4 label"
+ # "removed milestone"
#
- # "removed ~5 label"
+ # "changed milestone to 7.11"
#
# Returns the created Note object
- def change_label(noteable, project, author, added_labels, removed_labels)
- labels_count = added_labels.count + removed_labels.count
-
- references = ->(label) { label.to_reference(format: :id) }
- added_labels = added_labels.map(&references).join(' ')
- removed_labels = removed_labels.map(&references).join(' ')
-
- text_parts = []
-
- if added_labels.present?
- text_parts << "added #{added_labels}"
- text_parts << 'and' if removed_labels.present?
- end
-
- if removed_labels.present?
- text_parts << "removed #{removed_labels}"
- end
-
- text_parts << 'label'.pluralize(labels_count)
- body = text_parts.join(' ')
+ def change_milestone(noteable, project, author, milestone)
+ format = milestone&.group_milestone? ? :name : :iid
+ body = milestone.nil? ? 'removed milestone' : "changed milestone to #{milestone.to_reference(project, format: format)}"
- create_note(NoteSummary.new(noteable, project, author, body, action: 'label'))
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'milestone'))
end
- # Called when the milestone of a Noteable is changed
+ # Called when the due_date of a Noteable is changed
#
# noteable - Noteable object
# project - Project owning noteable
# author - User performing the change
- # milestone - Milestone being assigned, or nil
+ # due_date - Due date being assigned, or nil
#
# Example Note text:
#
- # "removed milestone"
+ # "removed due date"
#
- # "changed milestone to 7.11"
+ # "changed due date to September 20, 2018"
#
# Returns the created Note object
- def change_milestone(noteable, project, author, milestone)
- format = milestone&.group_milestone? ? :name : :iid
- body = milestone.nil? ? 'removed milestone' : "changed milestone to #{milestone.to_reference(project, format: format)}"
+ def change_due_date(noteable, project, author, due_date)
+ body = due_date ? "changed due date to #{due_date.to_s(:long)}" : 'removed due date'
- create_note(NoteSummary.new(noteable, project, author, body, action: 'milestone'))
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'due_date'))
end
# Called when the estimated time of a Noteable is changed
@@ -601,6 +580,7 @@ module SystemNoteService
private
+ # rubocop: disable CodeReuse/ActiveRecord
def notes_for_mentioner(mentioner, noteable, notes)
if mentioner.is_a?(Commit)
text = "#{cross_reference_note_prefix}%#{mentioner.to_reference(nil)}"
@@ -611,6 +591,7 @@ module SystemNoteService
notes.where(note: [text, text.capitalize])
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def create_note(note_summary)
note = Note.create(note_summary.note.merge(system: true))
diff --git a/app/services/tags/destroy_service.rb b/app/services/tags/destroy_service.rb
index 800268485a4..6bfef09ac54 100644
--- a/app/services/tags/destroy_service.rb
+++ b/app/services/tags/destroy_service.rb
@@ -2,6 +2,7 @@
module Tags
class DestroyService < BaseService
+ # rubocop: disable CodeReuse/ActiveRecord
def execute(tag_name)
repository = project.repository
tag = repository.find_tag(tag_name)
@@ -26,6 +27,7 @@ module Tags
rescue Gitlab::Git::PreReceiveError => ex
error(ex.message)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def error(message, return_code = 400)
super(message).merge(return_code: return_code)
diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb
index 0df61ad3bce..4fe6c1ec986 100644
--- a/app/services/todo_service.rb
+++ b/app/services/todo_service.rb
@@ -41,6 +41,7 @@ class TodoService
# collects the todo users before the todos themselves are deleted, then
# updates the todo counts for those users.
#
+ # rubocop: disable CodeReuse/ActiveRecord
def destroy_target(target)
todo_users = User.where(id: target.todos.pending.select(:user_id)).to_a
@@ -48,6 +49,7 @@ class TodoService
todo_users.each(&:update_todos_count_cache)
end
+ # rubocop: enable CodeReuse/ActiveRecord
# When we reassign an issue we should:
#
@@ -198,16 +200,21 @@ class TodoService
create_todos(current_user, attributes)
end
+ # rubocop: disable CodeReuse/ActiveRecord
def todo_exist?(issuable, current_user)
TodosFinder.new(current_user).execute.exists?(target: issuable)
end
+ # rubocop: enable CodeReuse/ActiveRecord
private
+ # rubocop: disable CodeReuse/ActiveRecord
def todos_by_ids(ids, current_user)
current_user.todos.where(id: Array(ids))
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def update_todos_state(todos, current_user, state)
# Only update those that are not really on that state
todos = todos.where.not(state: state)
@@ -216,6 +223,7 @@ class TodoService
current_user.update_todos_count_cache
todos_ids
end
+ # rubocop: enable CodeReuse/ActiveRecord
def create_todos(users, attributes)
Array(users).map do |user|
@@ -340,8 +348,10 @@ class TodoService
end
end
+ # rubocop: disable CodeReuse/ActiveRecord
def pending_todos(user, criteria = {})
valid_keys = [:project_id, :target_id, :target_type, :commit_id]
user.todos.pending.where(criteria.slice(*valid_keys))
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/services/todos/destroy/base_service.rb b/app/services/todos/destroy/base_service.rb
index aeb60e50c64..f3f1dbb5698 100644
--- a/app/services/todos/destroy/base_service.rb
+++ b/app/services/todos/destroy/base_service.rb
@@ -11,13 +11,17 @@ module Todos
private
+ # rubocop: disable CodeReuse/ActiveRecord
def without_authorized(items)
items.where('user_id NOT IN (?)', authorized_users)
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def authorized_users
ProjectAuthorization.select(:user_id).where(project_id: project_ids)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def todos
raise NotImplementedError
diff --git a/app/services/todos/destroy/confidential_issue_service.rb b/app/services/todos/destroy/confidential_issue_service.rb
index efec0f22da5..6276e332448 100644
--- a/app/services/todos/destroy/confidential_issue_service.rb
+++ b/app/services/todos/destroy/confidential_issue_service.rb
@@ -7,18 +7,22 @@ module Todos
attr_reader :issue
+ # rubocop: disable CodeReuse/ActiveRecord
def initialize(issue_id)
@issue = Issue.find_by(id: issue_id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
private
override :todos
+ # rubocop: disable CodeReuse/ActiveRecord
def todos
Todo.where(target: issue)
.where('user_id != ?', issue.author_id)
.where('user_id NOT IN (?)', issue.assignees.select(:id))
end
+ # rubocop: enable CodeReuse/ActiveRecord
override :todos_to_remove?
def todos_to_remove?
@@ -31,11 +35,13 @@ module Todos
end
override :authorized_users
+ # rubocop: disable CodeReuse/ActiveRecord
def authorized_users
ProjectAuthorization.select(:user_id)
.where(project_id: project_ids)
.where('access_level >= ?', Gitlab::Access::REPORTER)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
end
diff --git a/app/services/todos/destroy/entity_leave_service.rb b/app/services/todos/destroy/entity_leave_service.rb
index 4cb9d08713d..e8d1bcdd142 100644
--- a/app/services/todos/destroy/entity_leave_service.rb
+++ b/app/services/todos/destroy/entity_leave_service.rb
@@ -7,6 +7,7 @@ module Todos
attr_reader :user, :entity
+ # rubocop: disable CodeReuse/ActiveRecord
def initialize(user_id, entity_id, entity_type)
unless %w(Group Project).include?(entity_type)
raise ArgumentError.new("#{entity_type} is not an entity user can leave")
@@ -15,6 +16,7 @@ module Todos
@user = User.find_by(id: user_id)
@entity = entity_type.constantize.find_by(id: entity_id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def execute
return unless entity && user
@@ -40,21 +42,28 @@ module Todos
end
end
+ # rubocop: disable CodeReuse/ActiveRecord
def remove_confidential_issue_todos
Todo.where(
target_id: confidential_issues.select(:id), target_type: Issue, user_id: user.id
).delete_all
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def remove_project_todos
Todo.where(project_id: non_authorized_projects, user_id: user.id).delete_all
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def remove_group_todos
Todo.where(group_id: non_authorized_groups, user_id: user.id).delete_all
end
+ # rubocop: enable CodeReuse/ActiveRecord
override :project_ids
+ # rubocop: disable CodeReuse/ActiveRecord
def project_ids
condition = case entity
when Project
@@ -65,22 +74,29 @@ module Todos
Project.where(condition).select(:id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def non_authorized_projects
project_ids.where('id NOT IN (?)', user.authorized_projects.select(:id))
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def non_authorized_groups
return [] unless entity.is_a?(Namespace)
entity.self_and_descendants.select(:id)
.where('id NOT IN (?)', GroupsFinder.new(user).execute.select(:id))
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def non_member_groups
entity.self_and_descendants.select(:id)
.where('id NOT IN (?)', user.membership_groups.select(:id))
end
+ # rubocop: enable CodeReuse/ActiveRecord
def user_has_reporter_access?
return unless entity.is_a?(Namespace)
@@ -88,6 +104,7 @@ module Todos
entity.member?(User.find(user.id), Gitlab::Access::REPORTER)
end
+ # rubocop: disable CodeReuse/ActiveRecord
def confidential_issues
assigned_ids = IssueAssignee.select(:issue_id).where(user_id: user.id)
authorized_reporter_projects = user
@@ -98,6 +115,7 @@ module Todos
.where('author_id != ?', user.id)
.where('id NOT IN (?)', assigned_ids)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
end
diff --git a/app/services/todos/destroy/group_private_service.rb b/app/services/todos/destroy/group_private_service.rb
index f67f1d40597..d7ecbb952aa 100644
--- a/app/services/todos/destroy/group_private_service.rb
+++ b/app/services/todos/destroy/group_private_service.rb
@@ -7,16 +7,20 @@ module Todos
attr_reader :group
+ # rubocop: disable CodeReuse/ActiveRecord
def initialize(group_id)
@group = Group.find_by(id: group_id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
private
override :todos
+ # rubocop: disable CodeReuse/ActiveRecord
def todos
Todo.where(group_id: group.id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
override :authorized_users
def authorized_users
diff --git a/app/services/todos/destroy/private_features_service.rb b/app/services/todos/destroy/private_features_service.rb
index 7e204885b31..a8c3fe0ef5a 100644
--- a/app/services/todos/destroy/private_features_service.rb
+++ b/app/services/todos/destroy/private_features_service.rb
@@ -10,6 +10,7 @@ module Todos
@user_id = user_id
end
+ # rubocop: disable CodeReuse/ActiveRecord
def execute
ProjectFeature.where(project_id: project_ids).each do |project_features|
target_types = []
@@ -22,6 +23,7 @@ module Todos
remove_todos(project_features.project_id, target_types)
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
private
@@ -29,6 +31,7 @@ module Todos
feature_level == ProjectFeature::PRIVATE
end
+ # rubocop: disable CodeReuse/ActiveRecord
def remove_todos(project_id, target_types)
items = Todo.where(project_id: project_id)
items = items.where(user_id: user_id) if user_id
@@ -37,6 +40,7 @@ module Todos
.where(target_type: target_types)
.delete_all
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
end
diff --git a/app/services/todos/destroy/project_private_service.rb b/app/services/todos/destroy/project_private_service.rb
index ae8fab3ffca..e00d10c3780 100644
--- a/app/services/todos/destroy/project_private_service.rb
+++ b/app/services/todos/destroy/project_private_service.rb
@@ -7,16 +7,20 @@ module Todos
attr_reader :project
+ # rubocop: disable CodeReuse/ActiveRecord
def initialize(project_id)
@project = Project.find_by(id: project_id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
private
override :todos
+ # rubocop: disable CodeReuse/ActiveRecord
def todos
Todo.where(project_id: project.id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
override :project_ids
def project_ids
diff --git a/app/services/update_release_service.rb b/app/services/update_release_service.rb
index 422ba668e35..e2228ca026c 100644
--- a/app/services/update_release_service.rb
+++ b/app/services/update_release_service.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
class UpdateReleaseService < BaseService
+ # rubocop: disable CodeReuse/ActiveRecord
def execute(tag_name, release_description)
repository = project.repository
existing_tag = repository.find_tag(tag_name)
@@ -19,6 +20,7 @@ class UpdateReleaseService < BaseService
error('Tag does not exist', 404)
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def success(release)
super().merge(release: release)
diff --git a/app/services/users/last_push_event_service.rb b/app/services/users/last_push_event_service.rb
index a9c9497520b..b3980b8e32c 100644
--- a/app/services/users/last_push_event_service.rb
+++ b/app/services/users/last_push_event_service.rb
@@ -58,11 +58,13 @@ module Users
private
+ # rubocop: disable CodeReuse/ActiveRecord
def find_event_in_database(id)
PushEvent
.without_existing_merge_requests
.find_by(id: id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def user_cache_key
"last-push-event/#{@user.id}"
diff --git a/app/services/users/migrate_to_ghost_user_service.rb b/app/services/users/migrate_to_ghost_user_service.rb
index 4d47078bf43..04fd6e37501 100644
--- a/app/services/users/migrate_to_ghost_user_service.rb
+++ b/app/services/users/migrate_to_ghost_user_service.rb
@@ -54,15 +54,19 @@ module Users
migrate_award_emoji
end
+ # rubocop: disable CodeReuse/ActiveRecord
def migrate_issues
user.issues.update_all(author_id: ghost_user.id)
Issue.where(last_edited_by_id: user.id).update_all(last_edited_by_id: ghost_user.id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def migrate_merge_requests
user.merge_requests.update_all(author_id: ghost_user.id)
MergeRequest.where(merge_user_id: user.id).update_all(merge_user_id: ghost_user.id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def migrate_notes
user.notes.update_all(author_id: ghost_user.id)
diff --git a/app/services/users/respond_to_terms_service.rb b/app/services/users/respond_to_terms_service.rb
index 9efa3b285a8..254480304f9 100644
--- a/app/services/users/respond_to_terms_service.rb
+++ b/app/services/users/respond_to_terms_service.rb
@@ -6,6 +6,7 @@ module Users
@user, @term = user, term
end
+ # rubocop: disable CodeReuse/ActiveRecord
def execute(accepted:)
agreement = @user.term_agreements.find_or_initialize_by(term: @term)
agreement.accepted = accepted
@@ -16,6 +17,7 @@ module Users
agreement
end
+ # rubocop: enable CodeReuse/ActiveRecord
private
diff --git a/app/services/wikis/create_attachment_service.rb b/app/services/wikis/create_attachment_service.rb
index 30fe0e371a6..df31ad7c8ea 100644
--- a/app/services/wikis/create_attachment_service.rb
+++ b/app/services/wikis/create_attachment_service.rb
@@ -11,7 +11,7 @@ module Wikis
def initialize(*args)
super
- @file_name = truncate_file_name(params[:file_name])
+ @file_name = clean_file_name(params[:file_name])
@file_path = File.join(ATTACHMENT_PATH, SecureRandom.hex, @file_name) if @file_name
@commit_message ||= "Upload attachment #{@file_name}"
@branch_name ||= wiki.default_branch
@@ -23,8 +23,16 @@ module Wikis
private
- def truncate_file_name(file_name)
+ def clean_file_name(file_name)
return unless file_name.present?
+
+ file_name = truncate_file_name(file_name)
+ # CommonMark does not allow Urls with whitespaces, so we have to replace them
+ # Using the same regex Carrierwave use to replace invalid characters
+ file_name.gsub(CarrierWave::SanitizedFile.sanitize_regexp, '_')
+ end
+
+ def truncate_file_name(file_name)
return file_name if file_name.length <= MAX_FILENAME_LENGTH
extension = File.extname(file_name)
diff --git a/app/uploaders/avatar_uploader.rb b/app/uploaders/avatar_uploader.rb
index b29ef57b071..c0165759203 100644
--- a/app/uploaders/avatar_uploader.rb
+++ b/app/uploaders/avatar_uploader.rb
@@ -18,6 +18,10 @@ class AvatarUploader < GitlabUploader
false
end
+ def absolute_path
+ self.class.absolute_path(model.avatar.upload)
+ end
+
private
def dynamic_segment
diff --git a/app/uploaders/namespace_file_uploader.rb b/app/uploaders/namespace_file_uploader.rb
index 52969762b7d..4965bd7f057 100644
--- a/app/uploaders/namespace_file_uploader.rb
+++ b/app/uploaders/namespace_file_uploader.rb
@@ -6,23 +6,27 @@ class NamespaceFileUploader < FileUploader
options.storage_path
end
- def self.base_dir(model, _store = nil)
- File.join(options.base_dir, 'namespace', model_path_segment(model))
+ def self.base_dir(model, store = nil)
+ base_dirs(model)[store || Store::LOCAL]
+ end
+
+ def self.base_dirs(model)
+ {
+ Store::LOCAL => File.join(options.base_dir, 'namespace', model_path_segment(model)),
+ Store::REMOTE => File.join('namespace', model_path_segment(model))
+ }
end
def self.model_path_segment(model)
File.join(model.id.to_s)
end
+ def self.workhorse_local_upload_path
+ File.join(options.storage_path, 'uploads', TMP_UPLOAD_PATH)
+ end
+
# Re-Override
def store_dir
store_dirs[object_store]
end
-
- def store_dirs
- {
- Store::LOCAL => File.join(base_dir, dynamic_segment),
- Store::REMOTE => File.join('namespace', self.class.model_path_segment(model), dynamic_segment)
- }
- end
end
diff --git a/app/uploaders/records_uploads.rb b/app/uploaders/records_uploads.rb
index 5795065ae11..0efca895a50 100644
--- a/app/uploaders/records_uploads.rb
+++ b/app/uploaders/records_uploads.rb
@@ -18,6 +18,7 @@ module RecordsUploads
# `Tempfile` object the callback gets.
#
# Called `after :store`
+ # rubocop: disable CodeReuse/ActiveRecord
def record_upload(_tempfile = nil)
return unless model
return unless file && file.exists?
@@ -29,6 +30,7 @@ module RecordsUploads
self.upload = build_upload.tap(&:save!)
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def upload_path
File.join(store_dir, filename.to_s)
@@ -36,9 +38,11 @@ module RecordsUploads
private
+ # rubocop: disable CodeReuse/ActiveRecord
def uploads
Upload.order(id: :desc).where(uploader: self.class.to_s)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def build_upload
Upload.new(
@@ -53,11 +57,13 @@ module RecordsUploads
# Before removing an attachment, destroy any Upload records at the same path
#
# Called `before :remove`
+ # rubocop: disable CodeReuse/ActiveRecord
def destroy_upload(*args)
return unless file && file.exists?
self.upload = nil
uploads.where(path: upload_path).delete_all
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/app/validators/branch_filter_validator.rb b/app/validators/branch_filter_validator.rb
index ef482aaaa63..6a0899be850 100644
--- a/app/validators/branch_filter_validator.rb
+++ b/app/validators/branch_filter_validator.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# BranchFilterValidator
#
# Custom validator for branch names. Squishes whitespace and ignores empty
diff --git a/app/validators/js_regex_validator.rb b/app/validators/js_regex_validator.rb
index a515af7b919..be715967b4a 100644
--- a/app/validators/js_regex_validator.rb
+++ b/app/validators/js_regex_validator.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class JsRegexValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return true if value.blank?
diff --git a/app/validators/url_validator.rb b/app/validators/url_validator.rb
index faaf1283078..216acf79cbd 100644
--- a/app/validators/url_validator.rb
+++ b/app/validators/url_validator.rb
@@ -41,12 +41,13 @@ class UrlValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
@record = record
- if value.present?
- value.strip!
- else
+ unless value.present?
record.errors.add(attribute, 'must be a valid URL')
+ return
end
+ value = strip_value!(record, attribute, value)
+
Gitlab::UrlBlocker.validate!(value, blocker_args)
rescue Gitlab::UrlBlocker::BlockedUrlError => e
record.errors.add(attribute, "is blocked: #{e.message}")
@@ -54,6 +55,13 @@ class UrlValidator < ActiveModel::EachValidator
private
+ def strip_value!(record, attribute, value)
+ new_value = value.strip
+ return value if new_value == value
+
+ record.public_send("#{attribute}=", new_value) # rubocop:disable GitlabSecurity/PublicSend
+ end
+
def default_options
# By default the validator doesn't block any url based on the ip address
{
diff --git a/app/validators/variable_duplicates_validator.rb b/app/validators/variable_duplicates_validator.rb
index 90193e85f2a..d36a56e81b9 100644
--- a/app/validators/variable_duplicates_validator.rb
+++ b/app/validators/variable_duplicates_validator.rb
@@ -21,6 +21,7 @@ class VariableDuplicatesValidator < ActiveModel::EachValidator
private
+ # rubocop: disable CodeReuse/ActiveRecord
def validate_duplicates(record, attribute, values)
duplicates = values.reject(&:marked_for_destruction?).group_by(&:key).select { |_, v| v.many? }.map(&:first)
if duplicates.any?
@@ -29,4 +30,5 @@ class VariableDuplicatesValidator < ActiveModel::EachValidator
record.errors.add(attribute, error_message)
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/views/abuse_reports/new.html.haml b/app/views/abuse_reports/new.html.haml
index 278ad210543..391115a67b5 100644
--- a/app/views/abuse_reports/new.html.haml
+++ b/app/views/abuse_reports/new.html.haml
@@ -19,4 +19,4 @@
Explain the problem with this user. If appropriate, provide a link to the relevant issue or comment.
.form-actions
- = f.submit "Send report", class: "btn btn-create"
+ = f.submit "Send report", class: "btn btn-success"
diff --git a/app/views/admin/appearances/_form.html.haml b/app/views/admin/appearances/_form.html.haml
index a0861870ba4..cb67079853e 100644
--- a/app/views/admin/appearances/_form.html.haml
+++ b/app/views/admin/appearances/_form.html.haml
@@ -5,7 +5,7 @@
%legend
Navigation bar:
.form-group.row
- = f.label :header_logo, 'Header logo', class: 'col-sm-2 col-form-label'
+ = f.label :header_logo, 'Header logo', class: 'col-sm-2 col-form-label pt-0'
.col-sm-10
- if @appearance.header_logo?
= image_tag @appearance.header_logo_url, class: 'appearance-light-logo-preview'
@@ -22,7 +22,7 @@
%legend
Favicon:
.form-group.row
- = f.label :favicon, 'Favicon', class: 'col-sm-2 col-form-label'
+ = f.label :favicon, 'Favicon', class: 'col-sm-2 col-form-label pt-0'
.col-sm-10
- if @appearance.favicon?
= image_tag @appearance.favicon_url, class: 'appearance-light-logo-preview'
@@ -51,7 +51,7 @@
.hint
Description parsed with #{link_to "GitLab Flavored Markdown", help_page_path('user/markdown'), target: '_blank'}.
.form-group.row
- = f.label :logo, class: 'col-sm-2 col-form-label'
+ = f.label :logo, class: 'col-sm-2 col-form-label pt-0'
.col-sm-10
- if @appearance.logo?
= image_tag @appearance.logo_url, class: 'appearance-logo-preview'
@@ -75,7 +75,7 @@
Guidelines parsed with #{link_to "GitLab Flavored Markdown", help_page_path('user/markdown'), target: '_blank'}.
.form-actions
- = f.submit 'Save', class: 'btn btn-save append-right-10'
+ = f.submit 'Save', class: 'btn btn-success append-right-10'
- if @appearance.persisted?
Preview last save:
= link_to 'Sign-in page', preview_sign_in_admin_appearances_path, class: 'btn', target: '_blank', rel: 'noopener noreferrer'
diff --git a/app/views/admin/appearances/preview_sign_in.html.haml b/app/views/admin/appearances/preview_sign_in.html.haml
index 1af7dd5bb67..2cd95071c73 100644
--- a/app/views/admin/appearances/preview_sign_in.html.haml
+++ b/app/views/admin/appearances/preview_sign_in.html.haml
@@ -8,5 +8,5 @@
= label_tag :password
= password_field_tag :password, nil, class: "form-control bottom", title: 'This field is required.'
.form-group
- = button_tag "Sign in", class: "btn-create btn"
+ = button_tag "Sign in", class: "btn-success btn"
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 9121e44d31b..10bc3452d8b 100644
--- a/app/views/admin/application_settings/_account_and_limit.html.haml
+++ b/app/views/admin/application_settings/_account_and_limit.html.haml
@@ -14,7 +14,10 @@
= f.label :max_attachment_size, 'Maximum attachment size (MB)', class: 'label-bold'
= f.number_field :max_attachment_size, class: 'form-control'
.form-group
- = f.label :session_expire_delay, 'Session duration (minutes)', class: 'label-bold'
+ = f.label :receive_max_input_size, 'Maximum push size (MB)', class: 'label-light'
+ = f.number_field :receive_max_input_size, class: 'form-control'
+ .form-group
+ = f.label :session_expire_delay, 'Session duration (minutes)', class: 'label-light'
= f.number_field :session_expire_delay, class: 'form-control'
%span.form-text.text-muted#session_expire_delay_help_block GitLab restart is required to apply changes
.form-group
diff --git a/app/views/admin/application_settings/_influx.html.haml b/app/views/admin/application_settings/_influx.html.haml
index a1eeacd8290..dc5cbb8fa94 100644
--- a/app/views/admin/application_settings/_influx.html.haml
+++ b/app/views/admin/application_settings/_influx.html.haml
@@ -3,7 +3,7 @@
%fieldset
%p
- Setup InfluxDB to measure a wide variety of statistics like the time spent
+ Set up InfluxDB to measure a wide variety of statistics like the time spent
in running SQL queries. These settings require a
= link_to 'restart', help_page_path('administration/restart_gitlab')
to take effect.
diff --git a/app/views/admin/application_settings/_repository_mirrors_form.html.haml b/app/views/admin/application_settings/_repository_mirrors_form.html.haml
index c94f4c74820..615aa6317b0 100644
--- a/app/views/admin/application_settings/_repository_mirrors_form.html.haml
+++ b/app/views/admin/application_settings/_repository_mirrors_form.html.haml
@@ -7,9 +7,9 @@
.form-check
= f.check_box :mirror_available, class: 'form-check-input'
= f.label :mirror_available, class: 'form-check-label' do
- Allow mirrors to be setup for projects
+ Allow mirrors to be set up for projects
%span.form-text.text-muted
- If disabled, only admins will be able to setup mirrors in projects.
+ If disabled, only admins will be able to set up mirrors in projects.
= link_to icon('question-circle'), help_page_path('workflow/repository_mirroring')
= f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_signin.html.haml b/app/views/admin/application_settings/_signin.html.haml
index 635a6751e5b..5f36358f599 100644
--- a/app/views/admin/application_settings/_signin.html.haml
+++ b/app/views/admin/application_settings/_signin.html.haml
@@ -31,7 +31,7 @@
.form-check
= f.check_box :require_two_factor_authentication, class: 'form-check-input'
= f.label :require_two_factor_authentication, class: 'form-check-label' do
- Require all users to setup Two-factor authentication
+ Require all users to set up Two-factor authentication
.form-group
= f.label :two_factor_authentication, 'Two-factor grace period (hours)', class: 'label-bold'
= f.number_field :two_factor_grace_period, min: 0, class: 'form-control', placeholder: '0'
diff --git a/app/views/admin/application_settings/_usage.html.haml b/app/views/admin/application_settings/_usage.html.haml
index 2495defb6a7..788595877ea 100644
--- a/app/views/admin/application_settings/_usage.html.haml
+++ b/app/views/admin/application_settings/_usage.html.haml
@@ -2,7 +2,7 @@
= form_errors(@application_setting)
%fieldset
- .form-group
+ .form-group.mb-2
.form-check
= f.check_box :version_check_enabled, class: 'form-check-input'
= f.label :version_check_enabled, class: 'form-check-label' do
@@ -16,23 +16,26 @@
.form-check
= f.check_box :usage_ping_enabled, disabled: !can_be_configured, class: 'form-check-input'
= f.label :usage_ping_enabled, class: 'form-check-label' do
- Enable usage ping
+ = _('Enable usage ping')
.form-text.text-muted
- if can_be_configured
- To help improve GitLab and its user experience, GitLab will
- periodically collect usage information.
- = link_to 'Learn more', help_page_path("user/admin_area/settings/usage_statistics", anchor: "usage-ping")
- about what information is shared with GitLab Inc. Visit
- = link_to _('Cohorts'), instance_statistics_cohorts_path(anchor: 'usage-ping')
- to see the JSON payload sent.
+ %p.mb-2= _('To help improve GitLab and its user experience, GitLab will periodically collect usage information.')
+
+ - usage_ping_path = help_page_path('user/admin_area/settings/usage_statistics', anchor: 'usage-ping')
+ - usage_ping_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: usage_ping_path }
+ %p.mb-2= s_('%{usage_ping_link_start}Learn more%{usage_ping_link_end} about what information is shared with GitLab Inc.').html_safe % { usage_ping_link_start: usage_ping_link_start, usage_ping_link_end: '</a>'.html_safe }
+
+ %button.btn.js-usage-ping-payload-trigger{ type: 'button' }
+ .js-spinner.d-none= icon('spinner spin')
+ .js-text.d-inline= _('Preview payload')
+ %pre.usage-data.js-usage-ping-payload.js-syntax-highlight.code.highlight.mt-2.d-none{ data: { endpoint: usage_data_admin_application_settings_path(format: :html) } }
- else
- The usage ping is disabled, and cannot be configured through this
- form. For more information, see the documentation on
- = succeed '.' do
- = link_to 'deactivating the usage ping', help_page_path('user/admin_area/settings/usage_statistics', anchor: 'deactivate-the-usage-ping')
- .form-group
+ = _('The usage ping is disabled, and cannot be configured through this form.')
+ - deactivating_usage_ping_path = help_page_path('user/admin_area/settings/usage_statistics', anchor: 'deactivate-the-usage-ping')
+ - deactivating_usage_ping_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: deactivating_usage_ping_path }
+ = s_('For more information, see the documentation on %{deactivating_usage_ping_link_start}deactivating the usage ping%{deactivating_usage_ping_link_end}.').html_safe % { deactivating_usage_ping_link_start: deactivating_usage_ping_link_start, deactivating_usage_ping_link_end: '</a>'.html_safe }
+ .form-group.mt-3
= f.label :instance_statistics_visibility_private, _('Instance Statistics visibility')
= f.select :instance_statistics_visibility_private, options_for_select({_('All users') => false, _('Only admins') => true}, Gitlab::CurrentSettings.instance_statistics_visibility_private?), {}, class: 'form-control'
= 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
new file mode 100644
index 00000000000..db24c9982f7
--- /dev/null
+++ b/app/views/admin/application_settings/ci_cd.html.haml
@@ -0,0 +1,26 @@
+- breadcrumb_title _("CI/CD")
+- page_title _("CI/CD")
+- @content_class = "limit-container-width" unless fluid_layout
+
+%section.settings.as-ci-cd.no-animate#js-ci-cd-settings{ class: ('expanded' if expanded_by_default?) }
+ .settings-header
+ %h4
+ = _('Continuous Integration and Deployment')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded_by_default? ? _('Collapse') : _('Expand')
+ %p
+ = _('Auto DevOps, runners and job artifacts')
+ .settings-content
+ = render 'ci_cd'
+
+- if Gitlab.config.registry.enabled
+ %section.settings.as-registry.no-animate#js-registry-settings{ class: ('expanded' if expanded_by_default?) }
+ .settings-header
+ %h4
+ = _('Container Registry')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded_by_default? ? _('Collapse') : _('Expand')
+ %p
+ = _('Various container registry settings.')
+ .settings-content
+ = render 'registry'
diff --git a/app/views/admin/application_settings/integrations.html.haml b/app/views/admin/application_settings/integrations.html.haml
new file mode 100644
index 00000000000..310e86b1377
--- /dev/null
+++ b/app/views/admin/application_settings/integrations.html.haml
@@ -0,0 +1,31 @@
+- breadcrumb_title _("Integrations")
+- page_title _("Integrations")
+- @content_class = "limit-container-width" unless fluid_layout
+
+= render_if_exists 'admin/application_settings/elasticsearch_form', expanded: expanded_by_default?
+
+%section.settings.as-plantuml.no-animate#js-plantuml-settings{ class: ('expanded' if expanded_by_default?) }
+ .settings-header
+ %h4
+ = _('PlantUML')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded_by_default? ? _('Collapse') : _('Expand')
+ %p
+ = _('Allow rendering of PlantUML diagrams in Asciidoc documents.')
+ .settings-content
+ = render 'plantuml'
+
+= render_if_exists 'admin/application_settings/slack', expanded: expanded_by_default?
+
+%section.settings.as-third-party-offers.no-animate#js-third-party-offers-settings{ class: ('expanded' if expanded_by_default?) }
+ .settings-header
+ %h4
+ = _('Third party offers')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded_by_default? ? _('Collapse') : _('Expand')
+ %p
+ = _('Control the display of third party offers.')
+ .settings-content
+ = render 'third_party_offers', application_setting: @application_setting
+
+= render_if_exists 'admin/application_settings/snowplow', expanded: expanded_by_default?
diff --git a/app/views/admin/application_settings/metrics_and_profiling.html.haml b/app/views/admin/application_settings/metrics_and_profiling.html.haml
new file mode 100644
index 00000000000..f50aca32bdf
--- /dev/null
+++ b/app/views/admin/application_settings/metrics_and_profiling.html.haml
@@ -0,0 +1,50 @@
+- breadcrumb_title _("Metrics and profiling")
+- page_title _("Metrics and profiling")
+- @content_class = "limit-container-width" unless fluid_layout
+
+%section.settings.as-influx.no-animate#js-influx-settings{ class: ('expanded' if expanded_by_default?) }
+ .settings-header
+ %h4
+ = _('Metrics - Influx')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded_by_default? ? _('Collapse') : _('Expand')
+ %p
+ = _('Enable and configure InfluxDB metrics.')
+ .settings-content
+ = render 'influx'
+
+%section.settings.as-prometheus.no-animate#js-prometheus-settings{ class: ('expanded' if expanded_by_default?) }
+ .settings-header
+ %h4
+ = _('Metrics - Prometheus')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded_by_default? ? _('Collapse') : _('Expand')
+ %p
+ = _('Enable and configure Prometheus metrics.')
+ .settings-content
+ = render 'prometheus'
+
+%section.settings.as-performance-bar.no-animate#js-performance-bar-settings{ class: ('expanded' if expanded_by_default?) }
+ .settings-header
+ %h4
+ = _('Profiling - Performance bar')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded_by_default? ? _('Collapse') : _('Expand')
+ %p
+ = _('Enable the Performance Bar for a given group.')
+ = link_to icon('question-circle'), help_page_path('administration/monitoring/performance/performance_bar')
+ .settings-content
+ = render 'performance_bar'
+
+%section.settings.as-usage.no-animate#js-usage-settings{ class: ('expanded' if expanded_by_default?) }
+ .settings-header#usage-statistics
+ %h4
+ = _('Usage statistics')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded_by_default? ? _('Collapse') : _('Expand')
+ %p
+ = _('Enable or disable version check and usage ping.')
+ .settings-content
+ = render 'usage'
+
+= render_if_exists 'admin/application_settings/pseudonymizer_settings', expanded: expanded_by_default?
diff --git a/app/views/admin/application_settings/network.html.haml b/app/views/admin/application_settings/network.html.haml
new file mode 100644
index 00000000000..26fd745f45f
--- /dev/null
+++ b/app/views/admin/application_settings/network.html.haml
@@ -0,0 +1,36 @@
+- breadcrumb_title _("Network")
+- page_title _("Network")
+- @content_class = "limit-container-width" unless fluid_layout
+
+%section.settings.as-performance.no-animate#js-performance-settings{ class: ('expanded' if expanded_by_default?) }
+ .settings-header
+ %h4
+ = _('Performance optimization')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded_by_default? ? _('Collapse') : _('Expand')
+ %p
+ = _('Various settings that affect GitLab performance.')
+ .settings-content
+ = render 'performance'
+
+%section.settings.as-ip-limits.no-animate#js-ip-limits-settings{ class: ('expanded' if expanded_by_default?) }
+ .settings-header
+ %h4
+ = _('User and IP Rate Limits')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded_by_default? ? _('Collapse') : _('Expand')
+ %p
+ = _('Configure limits for web and API requests.')
+ .settings-content
+ = render 'ip_limits'
+
+%section.settings.as-outbound.no-animate#js-outbound-settings{ class: ('expanded' if expanded_by_default?) }
+ .settings-header
+ %h4
+ = _('Outbound requests')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded_by_default? ? _('Collapse') : _('Expand')
+ %p
+ = _('Allow requests to the local network from hooks and services.')
+ .settings-content
+ = render 'outbound'
diff --git a/app/views/admin/application_settings/preferences.html.haml b/app/views/admin/application_settings/preferences.html.haml
new file mode 100644
index 00000000000..75f76eea3b4
--- /dev/null
+++ b/app/views/admin/application_settings/preferences.html.haml
@@ -0,0 +1,69 @@
+- breadcrumb_title _("Preferences")
+- page_title _("Preferences")
+- @content_class = "limit-container-width" unless fluid_layout
+
+%section.settings.as-email.no-animate#js-email-settings{ class: ('expanded' if expanded_by_default?) }
+ .settings-header
+ %h4
+ = _('Email')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded_by_default? ? _('Collapse') : _('Expand')
+ %p
+ = _('Various email settings.')
+ .settings-content
+ = render 'email'
+
+%section.settings.as-help-page.no-animate#js-help-settings{ class: ('expanded' if expanded_by_default?) }
+ .settings-header
+ %h4
+ = _('Help page')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded_by_default? ? _('Collapse') : _('Expand')
+ %p
+ = _('Help page text and support page url.')
+ .settings-content
+ = render 'help_page'
+
+%section.settings.as-pages.no-animate#js-pages-settings{ class: ('expanded' if expanded_by_default?) }
+ .settings-header
+ %h4
+ = _('Pages')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded_by_default? ? _('Collapse') : _('Expand')
+ %p
+ = _('Size and domain settings for static websites')
+ .settings-content
+ = render 'pages'
+
+%section.settings.as-realtime.no-animate#js-realtime-settings{ class: ('expanded' if expanded_by_default?) }
+ .settings-header
+ %h4
+ = _('Real-time features')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded_by_default? ? _('Collapse') : _('Expand')
+ %p
+ = _('Change this value to influence how frequently the GitLab UI polls for updates.')
+ .settings-content
+ = render 'realtime'
+
+%section.settings.as-background.no-animate#js-background-settings{ class: ('expanded' if expanded_by_default?) }
+ .settings-header
+ %h4
+ = _('Background jobs')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded_by_default? ? _('Collapse') : _('Expand')
+ %p
+ = _('Configure Sidekiq job throttling.')
+ .settings-content
+ = render 'background_jobs'
+
+%section.settings.as-gitaly.no-animate#js-gitaly-settings{ class: ('expanded' if expanded_by_default?) }
+ .settings-header
+ %h4
+ = _('Gitaly')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded_by_default? ? _('Collapse') : _('Expand')
+ %p
+ = _('Configure Gitaly timeouts.')
+ .settings-content
+ = render 'gitaly'
diff --git a/app/views/admin/application_settings/reporting.html.haml b/app/views/admin/application_settings/reporting.html.haml
new file mode 100644
index 00000000000..1c2d9ccdb2d
--- /dev/null
+++ b/app/views/admin/application_settings/reporting.html.haml
@@ -0,0 +1,36 @@
+- breadcrumb_title _("Reporting")
+- page_title _("Reporting")
+- @content_class = "limit-container-width" unless fluid_layout
+
+%section.settings.as-spam.no-animate#js-spam-settings{ class: ('expanded' if expanded_by_default?) }
+ .settings-header
+ %h4
+ = _('Spam and Anti-bot Protection')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded_by_default? ? _('Collapse') : _('Expand')
+ %p
+ = _('Enable reCAPTCHA or Akismet and set IP limits.')
+ .settings-content
+ = render 'spam'
+
+%section.settings.as-abuse.no-animate#js-abuse-settings{ class: ('expanded' if expanded_by_default?) }
+ .settings-header
+ %h4
+ = _('Abuse reports')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded_by_default? ? _('Collapse') : _('Expand')
+ %p
+ = _('Set notification email for abuse reports.')
+ .settings-content
+ = render 'abuse'
+
+%section.settings.as-logging.no-animate#js-logging-settings{ class: ('expanded' if expanded_by_default?) }
+ .settings-header
+ %h4
+ = _('Error Reporting and Logging')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded_by_default? ? _('Collapse') : _('Expand')
+ %p
+ = _('Enable Sentry for error reporting and logging.')
+ .settings-content
+ = render 'logging'
diff --git a/app/views/admin/application_settings/repository.html.haml b/app/views/admin/application_settings/repository.html.haml
new file mode 100644
index 00000000000..d8029e0c54a
--- /dev/null
+++ b/app/views/admin/application_settings/repository.html.haml
@@ -0,0 +1,36 @@
+- breadcrumb_title _("Repository")
+- page_title _("Repository")
+- @content_class = "limit-container-width" unless fluid_layout
+
+%section.settings.as-mirror.no-animate#js-mirror-settings{ class: ('expanded' if expanded_by_default?) }
+ .settings-header
+ %h4
+ = _('Repository mirror')
+ %button.btn.js-settings-toggle{ type: 'button' }
+ = expanded_by_default? ? 'Collapse' : 'Expand'
+ %p
+ = _('Configure push mirrors.')
+ .settings-content
+ = render partial: 'repository_mirrors_form'
+
+%section.settings.as-repository-storage.no-animate#js-repository-storage-settings{ class: ('expanded' if expanded_by_default?) }
+ .settings-header
+ %h4
+ = _('Repository storage')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded_by_default? ? _('Collapse') : _('Expand')
+ %p
+ = _('Configure storage path and circuit breaker settings.')
+ .settings-content
+ = render 'repository_storage'
+
+%section.settings.as-repository-check.no-animate#js-repository-check-settings{ class: ('expanded' if expanded_by_default?) }
+ .settings-header
+ %h4
+ = _('Repository maintenance')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded_by_default? ? _('Collapse') : _('Expand')
+ %p
+ = _('Configure automatic git checks and housekeeping on repositories.')
+ .settings-content
+ = render 'repository_check'
diff --git a/app/views/admin/application_settings/show.html.haml b/app/views/admin/application_settings/show.html.haml
index 194a8157013..e2043183a97 100644
--- a/app/views/admin/application_settings/show.html.haml
+++ b/app/views/admin/application_settings/show.html.haml
@@ -1,359 +1,93 @@
-- breadcrumb_title "Settings"
-- page_title "Settings"
+- breadcrumb_title _("Settings")
+- page_title _("Settings")
- @content_class = "limit-container-width" unless fluid_layout
-- expanded = Rails.env.test?
-%section.settings.as-visibility-access.no-animate#js-visibility-settings{ class: ('expanded' if expanded) }
+%section.settings.as-visibility-access.no-animate#js-visibility-settings{ class: ('expanded' if expanded_by_default?) }
.settings-header
%h4
= _('Visibility and access controls')
%button.btn.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
+ = expanded_by_default? ? _('Collapse') : _('Expand')
%p
= _('Set default and restrict visibility levels. Configure import sources and git access protocol.')
.settings-content
= render 'visibility_and_access'
-%section.settings.as-account-limit.no-animate#js-account-settings{ class: ('expanded' if expanded) }
+%section.settings.as-account-limit.no-animate#js-account-settings{ class: ('expanded' if expanded_by_default?) }
.settings-header
%h4
= _('Account and limit')
%button.btn.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
+ = expanded_by_default? ? _('Collapse') : _('Expand')
%p
= _('Session expiration, projects limit and attachment size.')
.settings-content
= render 'account_and_limit'
-%section.settings.as-signup.no-animate#js-signup-settings{ class: ('expanded' if expanded) }
+%section.settings.as-signup.no-animate#js-signup-settings{ class: ('expanded' if expanded_by_default?) }
.settings-header
%h4
= _('Sign-up restrictions')
%button.btn.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
+ = expanded_by_default? ? _('Collapse') : _('Expand')
%p
= _('Configure the way a user creates a new account.')
.settings-content
= render 'signup'
-%section.settings.as-signin.no-animate#js-signin-settings{ class: ('expanded' if expanded) }
+%section.settings.as-signin.no-animate#js-signin-settings{ class: ('expanded' if expanded_by_default?) }
.settings-header
%h4
= _('Sign-in restrictions')
%button.btn.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
+ = expanded_by_default? ? _('Collapse') : _('Expand')
%p
= _('Set requirements for a user to sign-in. Enable mandatory two-factor authentication.')
.settings-content
= render 'signin'
-%section.settings.as-terms.no-animate#js-terms-settings{ class: ('expanded' if expanded) }
+%section.qa-terms-settings.settings.as-terms.no-animate#js-terms-settings{ class: ('expanded' if expanded_by_default?) }
.settings-header
%h4
= _('Terms of Service and Privacy Policy')
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
+ = expanded_by_default? ? _('Collapse') : _('Expand')
%p
= _('Include a Terms of Service agreement and Privacy Policy that all users must accept.')
.settings-content
= render 'terms'
-%section.settings.as-help-page.no-animate#js-help-settings{ class: ('expanded' if expanded) }
- .settings-header
- %h4
- = _('Help page')
- %button.btn.btn-default.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
- %p
- = _('Help page text and support page url.')
- .settings-content
- = render 'help_page'
-
-%section.settings.as-pages.no-animate#js-pages-settings{ class: ('expanded' if expanded) }
- .settings-header
- %h4
- = _('Pages')
- %button.btn.btn-default.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
- %p
- = _('Size and domain settings for static websites')
- .settings-content
- = render 'pages'
-
-%section.settings.as-ci-cd.no-animate#js-ci-cd-settings{ class: ('expanded' if expanded) }
- .settings-header
- %h4
- = _('Continuous Integration and Deployment')
- %button.btn.btn-default.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
- %p
- = _('Auto DevOps, runners and job artifacts')
- .settings-content
- = render 'ci_cd'
-
-%section.settings.as-influx.no-animate#js-influx-settings{ class: ('expanded' if expanded) }
- .settings-header
- %h4
- = _('Metrics - Influx')
- %button.btn.btn-default.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
- %p
- = _('Enable and configure InfluxDB metrics.')
- .settings-content
- = render 'influx'
-
-%section.settings.as-prometheus.no-animate#js-prometheus-settings{ class: ('expanded' if expanded) }
- .settings-header
- %h4
- = _('Metrics - Prometheus')
- %button.btn.btn-default.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
- %p
- = _('Enable and configure Prometheus metrics.')
- .settings-content
- = render 'prometheus'
-
-%section.settings.as-performance-bar.no-animate#js-performance-bar-settings{ class: ('expanded' if expanded) }
- .settings-header
- %h4
- = _('Profiling - Performance bar')
- %button.btn.btn-default.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
- %p
- = _('Enable the Performance Bar for a given group.')
- = link_to icon('question-circle'), help_page_path('administration/monitoring/performance/performance_bar')
- .settings-content
- = render 'performance_bar'
-
-%section.settings.as-background.no-animate#js-background-settings{ class: ('expanded' if expanded) }
- .settings-header
- %h4
- = _('Background jobs')
- %button.btn.btn-default.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
- %p
- = _('Configure Sidekiq job throttling.')
- .settings-content
- = render 'background_jobs'
-
-%section.settings.as-spam.no-animate#js-spam-settings{ class: ('expanded' if expanded) }
- .settings-header
- %h4
- = _('Spam and Anti-bot Protection')
- %button.btn.btn-default.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
- %p
- = _('Enable reCAPTCHA or Akismet and set IP limits.')
- .settings-content
- = render 'spam'
-
-%section.settings.as-abuse.no-animate#js-abuse-settings{ class: ('expanded' if expanded) }
- .settings-header
- %h4
- = _('Abuse reports')
- %button.btn.btn-default.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
- %p
- = _('Set notification email for abuse reports.')
- .settings-content
- = render 'abuse'
-
-%section.settings.as-logging.no-animate#js-logging-settings{ class: ('expanded' if expanded) }
- .settings-header
- %h4
- = _('Error Reporting and Logging')
- %button.btn.btn-default.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
- %p
- = _('Enable Sentry for error reporting and logging.')
- .settings-content
- = render 'logging'
-
-%section.qa-repository-storage-settings.settings.as-repository-storage.no-animate#js-repository-storage-settings{ class: ('expanded' if expanded) }
- .settings-header
- %h4
- = _('Repository storage')
- %button.btn.btn-default.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
- %p
- = _('Configure storage path and circuit breaker settings.')
- .settings-content
- = render 'repository_storage'
-
-%section.settings.as-repository-check.no-animate#js-repository-check-settings{ class: ('expanded' if expanded) }
- .settings-header
- %h4
- = _('Repository maintenance')
- %button.btn.btn-default.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
- %p
- = _('Configure automatic git checks and housekeeping on repositories.')
- .settings-content
- = render 'repository_check'
-
-- if Gitlab.config.registry.enabled
- %section.settings.as-registry.no-animate#js-registry-settings{ class: ('expanded' if expanded) }
- .settings-header
- %h4
- = _('Container Registry')
- %button.btn.btn-default.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
- %p
- = _('Various container registry settings.')
- .settings-content
- = render 'registry'
-
- if koding_enabled?
- %section.settings.as-koding.no-animate#js-koding-settings{ class: ('expanded' if expanded) }
+ %section.settings.as-koding.no-animate#js-koding-settings{ class: ('expanded' if expanded_by_default?) }
.settings-header
%h4
= _('Koding')
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
+ = expanded_by_default? ? _('Collapse') : _('Expand')
%p
= _('Online IDE integration settings.')
.settings-content
= render 'koding'
-%section.settings.as-plantuml.no-animate#js-plantuml-settings{ class: ('expanded' if expanded) }
- .settings-header
- %h4
- = _('PlantUML')
- %button.btn.btn-default.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
- %p
- = _('Allow rendering of PlantUML diagrams in Asciidoc documents.')
- .settings-content
- = render 'plantuml'
-
-%section.settings.as-usage.no-animate#js-usage-settings{ class: ('expanded' if expanded) }
- .settings-header#usage-statistics
- %h4
- = _('Usage statistics')
- %button.btn.btn-default.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
- %p
- = _('Enable or disable version check and usage ping.')
- .settings-content
- = render 'usage'
-
-%section.settings.as-email.no-animate#js-email-settings{ class: ('expanded' if expanded) }
- .settings-header
- %h4
- = _('Email')
- %button.btn.btn-default.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
- %p
- = _('Various email settings.')
- .settings-content
- = render 'email'
-
-%section.settings.as-gitaly.no-animate#js-gitaly-settings{ class: ('expanded' if expanded) }
- .settings-header
- %h4
- = _('Gitaly')
- %button.btn.btn-default.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
- %p
- = _('Configure Gitaly timeouts.')
- .settings-content
- = render 'gitaly'
+= render_if_exists 'admin/application_settings/external_authorization_service_form', expanded: expanded_by_default?
-%section.settings.as-terminal.no-animate#js-terminal-settings{ class: ('expanded' if expanded) }
+%section.settings.as-terminal.no-animate#js-terminal-settings{ class: ('expanded' if expanded_by_default?) }
.settings-header
%h4
= _('Web terminal')
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
+ = expanded_by_default? ? _('Collapse') : _('Expand')
%p
= _('Set max session time for web terminal.')
.settings-content
= render 'terminal'
-%section.settings.as-realtime.no-animate#js-realtime-settings{ class: ('expanded' if expanded) }
- .settings-header
- %h4
- = _('Real-time features')
- %button.btn.btn-default.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
- %p
- = _('Change this value to influence how frequently the GitLab UI polls for updates.')
- .settings-content
- = render 'realtime'
-
-%section.settings.as-performance.no-animate#js-performance-settings{ class: ('expanded' if expanded) }
- .settings-header
- %h4
- = _('Performance optimization')
- %button.btn.btn-default.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
- %p
- = _('Various settings that affect GitLab performance.')
- .settings-content
- = render 'performance'
-
-%section.settings.as-ip-limits.no-animate#js-ip-limits-settings{ class: ('expanded' if expanded) }
- .settings-header
- %h4
- = _('User and IP Rate Limits')
- %button.btn.btn-default.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
- %p
- = _('Configure limits for web and API requests.')
- .settings-content
- = render 'ip_limits'
-
-%section.settings.as-outbound.no-animate#js-outbound-settings{ class: ('expanded' if expanded) }
- .settings-header
- %h4
- = _('Outbound requests')
- %button.btn.btn-default.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
- %p
- = _('Allow requests to the local network from hooks and services.')
- .settings-content
- = render 'outbound'
-
-%section.settings.as-mirror.no-animate#js-mirror-settings{ class: ('expanded' if expanded) }
- .settings-header
- %h4
- = _('Repository mirror')
- %button.btn.js-settings-toggle{ type: 'button' }
- = expanded ? 'Collapse' : 'Expand'
- %p
- = _('Configure push mirrors.')
- .settings-content
- = render partial: 'repository_mirrors_form'
-
-= render_if_exists 'admin/application_settings/geo', expanded: expanded
-
-= render_if_exists 'admin/application_settings/external_authorization_service_form', expanded: expanded
-
-= render_if_exists 'admin/application_settings/elasticsearch_form', expanded: expanded
-
-= render_if_exists 'admin/application_settings/slack', expanded: expanded
-
-= 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
- = _('Third party offers')
- %button.btn.btn-default.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
- %p
- = _('Control the display of third party offers.')
- .settings-content
- = render 'third_party_offers', application_setting: @application_setting
-
-= render_if_exists 'admin/application_settings/custom_templates_form', expanded: expanded
-
-%section.settings.no-animate#js-web-ide-settings{ class: ('expanded' if expanded) }
+%section.settings.no-animate#js-web-ide-settings{ class: ('expanded' if expanded_by_default?) }
.settings-header
%h4
= _('Web IDE')
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
+ = expanded_by_default? ? _('Collapse') : _('Expand')
%p
= _('Manage Web IDE features')
.settings-content
@@ -370,5 +104,3 @@
= s_('IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox client side evaluation.')
= f.submit _('Save changes'), class: "btn btn-success"
-
-= render_if_exists 'admin/application_settings/pseudonymizer_settings', expanded: expanded
diff --git a/app/views/admin/applications/_form.html.haml b/app/views/admin/applications/_form.html.haml
index 7f14cddebd8..12690343f6e 100644
--- a/app/views/admin/applications/_form.html.haml
+++ b/app/views/admin/applications/_form.html.haml
@@ -21,17 +21,17 @@
for local tests
= content_tag :div, class: 'form-group row' do
- = f.label :trusted, class: 'col-sm-2 col-form-label'
+ = f.label :trusted, class: 'col-sm-2 col-form-label pt-0'
.col-sm-10
= f.check_box :trusted
%span.form-text.text-muted
Trusted applications are automatically authorized on GitLab OAuth flow.
.form-group.row
- = f.label :scopes, class: 'col-sm-2 col-form-label'
+ = f.label :scopes, class: 'col-sm-2 col-form-label pt-0'
.col-sm-10
= render 'shared/tokens/scopes_form', prefix: 'doorkeeper_application', token: application, scopes: @scopes
.form-actions
- = f.submit 'Submit', class: "btn btn-save wide"
+ = f.submit 'Submit', class: "btn btn-success wide"
= link_to "Cancel", admin_applications_path, class: "btn btn-cancel"
diff --git a/app/views/admin/applications/index.html.haml b/app/views/admin/applications/index.html.haml
index 94d33fa6489..2cdf98075d1 100644
--- a/app/views/admin/applications/index.html.haml
+++ b/app/views/admin/applications/index.html.haml
@@ -5,7 +5,7 @@
System OAuth applications don't belong to any user and can only be managed by admins
%hr
%p= link_to 'New application', new_admin_application_path, class: 'btn btn-success'
-%table.table.table-striped
+%table.table
%thead
%tr
%th Name
diff --git a/app/views/admin/broadcast_messages/_form.html.haml b/app/views/admin/broadcast_messages/_form.html.haml
index 7f34357f147..c465d9f51d6 100644
--- a/app/views/admin/broadcast_messages/_form.html.haml
+++ b/app/views/admin/broadcast_messages/_form.html.haml
@@ -36,6 +36,6 @@
= f.datetime_select :ends_at, {}, class: 'form-control form-control-inline'
.form-actions
- if @broadcast_message.persisted?
- = f.submit "Update broadcast message", class: "btn btn-create"
+ = f.submit "Update broadcast message", class: "btn btn-success"
- else
- = f.submit "Add broadcast message", class: "btn btn-create"
+ = f.submit "Add broadcast message", class: "btn btn-success"
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index fac61f9d249..85c04f8a01d 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -14,7 +14,7 @@
Projects:
= approximate_count_with_delimiters(@counts, Project)
%hr
- = link_to('New project', new_project_path, class: "btn btn-new")
+ = link_to('New project', new_project_path, class: "btn btn-success")
.col-sm-4
.info-well.dark-well
.well-segment.well-centered
@@ -24,7 +24,7 @@
= approximate_count_with_delimiters(@counts, User)
= render_if_exists 'admin/dashboard/users_statistics'
%hr
- = link_to 'New user', new_admin_user_path, class: "btn btn-new"
+ = link_to 'New user', new_admin_user_path, class: "btn btn-success"
.col-sm-4
.info-well.dark-well
.well-segment.well-centered
@@ -33,7 +33,7 @@
Groups:
= approximate_count_with_delimiters(@counts, Group)
%hr
- = link_to 'New group', new_admin_group_path, class: "btn btn-new"
+ = link_to 'New group', new_admin_group_path, class: "btn btn-success"
.row
.col-md-4
.info-well
diff --git a/app/views/admin/deploy_keys/edit.html.haml b/app/views/admin/deploy_keys/edit.html.haml
index b50adef362f..7c04ef03947 100644
--- a/app/views/admin/deploy_keys/edit.html.haml
+++ b/app/views/admin/deploy_keys/edit.html.haml
@@ -6,5 +6,5 @@
= form_for [:admin, @deploy_key], html: { class: 'deploy-key-form' } do |f|
= render partial: 'shared/deploy_keys/form', locals: { form: f, deploy_key: @deploy_key }
.form-actions
- = f.submit 'Save changes', class: 'btn-save btn'
+ = f.submit 'Save changes', class: 'btn-success btn'
= link_to 'Cancel', admin_deploy_keys_path, class: 'btn btn-cancel'
diff --git a/app/views/admin/deploy_keys/index.html.haml b/app/views/admin/deploy_keys/index.html.haml
index 52ab8bae119..01013be06d6 100644
--- a/app/views/admin/deploy_keys/index.html.haml
+++ b/app/views/admin/deploy_keys/index.html.haml
@@ -3,7 +3,7 @@
%h3.page-title.deploy-keys-title
Public deploy keys (#{@deploy_keys.count})
.float-right
- = link_to 'New deploy key', new_admin_deploy_key_path, class: 'btn btn-new btn-sm btn-inverted'
+ = link_to 'New deploy key', new_admin_deploy_key_path, class: 'btn btn-success btn-sm btn-inverted'
- if @deploy_keys.any?
.table-holder.deploy-keys-list
diff --git a/app/views/admin/deploy_keys/new.html.haml b/app/views/admin/deploy_keys/new.html.haml
index d4f8e340b69..9a563a5bc78 100644
--- a/app/views/admin/deploy_keys/new.html.haml
+++ b/app/views/admin/deploy_keys/new.html.haml
@@ -6,5 +6,5 @@
= form_for [:admin, @deploy_key], html: { class: 'deploy-key-form' } do |f|
= render partial: 'shared/deploy_keys/form', locals: { form: f, deploy_key: @deploy_key }
.form-actions
- = f.submit 'Create', class: 'btn-create btn'
+ = f.submit 'Create', class: 'btn-success btn'
= link_to 'Cancel', admin_deploy_keys_path, class: 'btn btn-cancel'
diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml
index a3773e90cfb..2a117c1414e 100644
--- a/app/views/admin/groups/_form.html.haml
+++ b/app/views/admin/groups/_form.html.haml
@@ -26,12 +26,12 @@
.alert.alert-info
= render 'shared/group_tips'
.form-actions
- = f.submit _('Create group'), class: "btn btn-create"
+ = f.submit _('Create group'), class: "btn btn-success"
= link_to _('Cancel'), admin_groups_path, class: "btn btn-cancel"
- else
.form-actions
- = f.submit _('Save changes'), class: "btn btn-save"
+ = f.submit _('Save changes'), class: "btn btn-success"
= link_to _('Cancel'), admin_group_path(@group), class: "btn btn-cancel"
= render_if_exists 'ldap_group_links/ldap_syncrhonizations', group: @group
diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml
index 6a9b85b4109..cb833ffd9ac 100644
--- a/app/views/admin/groups/index.html.haml
+++ b/app/views/admin/groups/index.html.haml
@@ -12,7 +12,7 @@
= search_field_tag :name, project_name, class: "form-control search-text-input js-search-input", autofocus: true, spellcheck: false, placeholder: 'Search by name'
= icon("search", class: "search-icon")
= render "shared/groups/dropdown", options_hash: admin_groups_sort_options_hash
- = link_to new_admin_group_path, class: "btn btn-new" do
+ = link_to new_admin_group_path, class: "btn btn-success" do
= _('New group')
%ul.content-list
= render @groups
diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml
index 72b068ea6b5..0c683f86252 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -111,7 +111,7 @@
.prepend-top-10
= select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2"
%hr
- = button_tag _('Add users to group'), class: "btn btn-create"
+ = button_tag _('Add users to group'), class: "btn btn-success"
= render 'shared/members/requests', membership_source: @group, requesters: @requesters, force_mobile_view: true
.card
diff --git a/app/views/admin/hooks/edit.html.haml b/app/views/admin/hooks/edit.html.haml
index b9a650e1f1f..486d0477f20 100644
--- a/app/views/admin/hooks/edit.html.haml
+++ b/app/views/admin/hooks/edit.html.haml
@@ -12,7 +12,7 @@
= form_for @hook, as: :hook, url: admin_hook_path do |f|
= render partial: 'form', locals: { form: f, hook: @hook }
.form-actions
- = f.submit 'Save changes', class: 'btn btn-create'
+ = f.submit 'Save changes', class: 'btn btn-success'
= render 'shared/web_hooks/test_button', triggers: SystemHook.triggers, hook: @hook
= link_to 'Remove', admin_hook_path(@hook), method: :delete, class: 'btn btn-remove float-right', data: { confirm: 'Are you sure?' }
diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml
index 87f9b0e86a7..5d462d7b732 100644
--- a/app/views/admin/hooks/index.html.haml
+++ b/app/views/admin/hooks/index.html.haml
@@ -10,7 +10,7 @@
.col-lg-8.append-bottom-default
= form_for @hook, as: :hook, url: admin_hooks_path do |f|
= render partial: 'form', locals: { form: f, hook: @hook }
- = f.submit 'Add system hook', class: 'btn btn-create'
+ = f.submit 'Add system hook', class: 'btn btn-success'
%hr
diff --git a/app/views/admin/identities/_form.html.haml b/app/views/admin/identities/_form.html.haml
index 946d868da01..3ab7990d9e2 100644
--- a/app/views/admin/identities/_form.html.haml
+++ b/app/views/admin/identities/_form.html.haml
@@ -12,5 +12,5 @@
= f.text_field :extern_uid, class: 'form-control', required: true
.form-actions
- = f.submit _('Save changes'), class: "btn btn-save"
+ = f.submit _('Save changes'), class: "btn btn-success"
diff --git a/app/views/admin/identities/index.html.haml b/app/views/admin/identities/index.html.haml
index df3df159947..9543bbcf977 100644
--- a/app/views/admin/identities/index.html.haml
+++ b/app/views/admin/identities/index.html.haml
@@ -3,7 +3,7 @@
- page_title _("Identities"), @user.name, _("Users")
= render 'admin/users/head'
-= link_to _('New identity'), new_admin_user_identity_path, class: 'float-right btn btn-new'
+= link_to _('New identity'), new_admin_user_identity_path, class: 'float-right btn btn-success'
- if @identities.present?
.table-holder
%table.table
diff --git a/app/views/admin/labels/_form.html.haml b/app/views/admin/labels/_form.html.haml
index ee2d4c8430a..5e7b4817461 100644
--- a/app/views/admin/labels/_form.html.haml
+++ b/app/views/admin/labels/_form.html.haml
@@ -27,5 +27,5 @@
&nbsp;
.form-actions
- = f.submit _('Save'), class: 'btn btn-save js-save-button'
+ = f.submit _('Save'), class: 'btn btn-success js-save-button'
= link_to _("Cancel"), admin_labels_path, class: 'btn btn-cancel'
diff --git a/app/views/admin/labels/index.html.haml b/app/views/admin/labels/index.html.haml
index f1b8658f84e..5a5b3d18c5f 100644
--- a/app/views/admin/labels/index.html.haml
+++ b/app/views/admin/labels/index.html.haml
@@ -1,7 +1,7 @@
- page_title _("Labels")
%div
- = link_to new_admin_label_path, class: "float-right btn btn-nr btn-new" do
+ = link_to new_admin_label_path, class: "float-right btn btn-nr btn-success" do
= _('New label')
%h3.page-title
= _('Labels')
diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml
index 57de792f92d..46bb57c78a8 100644
--- a/app/views/admin/projects/index.html.haml
+++ b/app/views/admin/projects/index.html.haml
@@ -21,7 +21,7 @@
= dropdown_content
= dropdown_loading
= render 'shared/projects/dropdown'
- = link_to new_project_path, class: 'btn btn-new' do
+ = link_to new_project_path, class: 'btn btn-success' do
New Project
= button_tag "Search", class: "btn btn-primary btn-search hide"
diff --git a/app/views/admin/runners/_runner.html.haml b/app/views/admin/runners/_runner.html.haml
index 43937b01339..e4fc2985087 100644
--- a/app/views/admin/runners/_runner.html.haml
+++ b/app/views/admin/runners/_runner.html.haml
@@ -1,51 +1,78 @@
-%tr{ id: dom_id(runner) }
- %td
- - if runner.instance_type?
- %span.badge.badge-success shared
- - elsif runner.group_type?
- %span.badge.badge-success group
- - else
- %span.badge.badge-info specific
- - if runner.locked?
- %span.badge.badge-warning locked
- - unless runner.active?
- %span.badge.badge-danger paused
-
- %td
- = link_to admin_runner_path(runner) do
- = runner.short_sha
- %td
- = runner.description
- %td
- = runner.version
- %td
- = runner.ip_address
- %td
- - if runner.instance_type? || runner.group_type?
- n/a
- - else
- = runner.projects.count(:all)
- %td
- #{runner.builds.count(:all)}
- %td
- - runner.tag_list.sort.each do |tag|
- %span.badge.badge-primary
- = tag
- %td
- - if runner.contacted_at
- = time_ago_with_tooltip runner.contacted_at
- - else
- Never
- %td.admin-runner-btn-group-cell
- .float-right.btn-group
- = link_to admin_runner_path(runner), class: 'btn btn-sm btn-default has-tooltip', title: 'Edit', ref: 'tooltip', aria: { label: 'Edit' }, data: { placement: 'top', container: 'body'} do
- = icon('pencil')
- &nbsp;
- - if runner.active?
- = link_to [:pause, :admin, runner], method: :get, class: 'btn btn-sm btn-default has-tooltip', title: 'Pause', ref: 'tooltip', aria: { label: 'Pause' }, data: { placement: 'top', container: 'body', confirm: "Are you sure?" } do
- = icon('pause')
+.gl-responsive-table-row{ id: dom_id(runner) }
+ .table-section.section-10.section-wrap
+ .table-mobile-header{ role: 'rowheader' }= _('Type')
+ .table-mobile-content
+ - if runner.instance_type?
+ %span.badge.badge-success shared
+ - elsif runner.group_type?
+ %span.badge.badge-success group
- else
- = link_to [:resume, :admin, runner], method: :get, class: 'btn btn-default btn-sm has-tooltip', title: 'Resume', ref: 'tooltip', aria: { label: 'Resume' }, data: { placement: 'top', container: 'body'} do
- = icon('play')
- = link_to [:admin, runner], method: :delete, class: 'btn btn-danger btn-sm has-tooltip', title: 'Remove', ref: 'tooltip', aria: { label: 'Remove' }, data: { placement: 'top', container: 'body', confirm: "Are you sure?" } do
- = icon('remove')
+ %span.badge.badge-info specific
+ - if runner.locked?
+ %span.badge.badge-warning locked
+ - unless runner.active?
+ %span.badge.badge-danger paused
+
+ .table-section.section-10
+ .table-mobile-header{ role: 'rowheader' }= _('Runner token')
+ .table-mobile-content
+ = link_to runner.short_sha, admin_runner_path(runner)
+
+ .table-section.section-15
+ .table-mobile-header{ role: 'rowheader' }= _('Description')
+ .table-mobile-content.str-truncated.has-tooltip{ title: runner.description }
+ = runner.description
+
+ .table-section.section-15
+ .table-mobile-header{ role: 'rowheader' }= _('Version')
+ .table-mobile-content.str-truncated.has-tooltip{ title: runner.version }
+ = runner.version
+
+ .table-section.section-10
+ .table-mobile-header{ role: 'rowheader' }= _('IP Address')
+ .table-mobile-content
+ = runner.ip_address
+
+ .table-section.section-5
+ .table-mobile-header{ role: 'rowheader' }= _('Projects')
+ .table-mobile-content
+ - if runner.instance_type? || runner.group_type?
+ = _('n/a')
+ - else
+ = runner.projects.count(:all)
+
+ .table-section.section-5
+ .table-mobile-header{ role: 'rowheader' }= _('Jobs')
+ .table-mobile-content
+ = runner.builds.count(:all)
+
+ .table-section.section-10.section-wrap
+ .table-mobile-header{ role: 'rowheader' }= _('Tags')
+ .table-mobile-content
+ - runner.tag_list.sort.each do |tag|
+ %span.badge.badge-primary
+ = tag
+
+ .table-section.section-10
+ .table-mobile-header{ role: 'rowheader' }= _('Last contact')
+ .table-mobile-content
+ - if runner.contacted_at
+ = time_ago_with_tooltip runner.contacted_at
+ - else
+ = _('Never')
+
+ .table-section.table-button-footer.section-10
+ .btn-group.table-action-buttons
+ .btn-group
+ = link_to admin_runner_path(runner), class: 'btn btn-default has-tooltip', title: _('Edit'), ref: 'tooltip', aria: { label: _('Edit') }, data: { placement: 'top', container: 'body'} do
+ = icon('pencil')
+ .btn-group
+ - if runner.active?
+ = link_to [:pause, :admin, runner], method: :get, class: 'btn btn-default has-tooltip', title: _('Pause'), ref: 'tooltip', aria: { label: _('Pause') }, data: { placement: 'top', container: 'body', confirm: _('Are you sure?') } do
+ = icon('pause')
+ - else
+ = link_to [:resume, :admin, runner], method: :get, class: 'btn btn-default has-tooltip', title: _('Resume'), ref: 'tooltip', aria: { label: _('Resume') }, data: { placement: 'top', container: 'body'} do
+ = icon('play')
+ .btn-group
+ = link_to [:admin, runner], method: :delete, class: 'btn btn-danger has-tooltip', title: _('Remove'), ref: 'tooltip', aria: { label: _('Remove') }, data: { placement: 'top', container: 'body', confirm: _('Are you sure?') } do
+ = icon('remove')
diff --git a/app/views/admin/runners/_sort_dropdown.html.haml b/app/views/admin/runners/_sort_dropdown.html.haml
new file mode 100644
index 00000000000..b201e6bf10e
--- /dev/null
+++ b/app/views/admin/runners/_sort_dropdown.html.haml
@@ -0,0 +1,11 @@
+- sorted_by = sort_options_hash[@sort]
+
+.dropdown.inline.prepend-left-10
+ %button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' } }
+ = sorted_by
+ = icon('chevron-down')
+ %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort
+ %li
+ = sortable_item(sort_title_created_date, page_filter_path(sort: sort_value_created_date, label: true), sorted_by)
+ = sortable_item(sort_title_contacted_date, page_filter_path(sort: sort_value_contacted_date, label: true), sorted_by)
+
diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml
index 9280ff4d478..ee2e1703fdb 100644
--- a/app/views/admin/runners/index.html.haml
+++ b/app/views/admin/runners/index.html.haml
@@ -1,77 +1,116 @@
-- breadcrumb_title "Runners"
+- breadcrumb_title _('Runners')
- @no_container = true
%div{ class: container_class }
.bs-callout
%p
- A 'Runner' is a process which runs a job.
- You can setup as many Runners as you need.
+ = (_"A 'Runner' is a process which runs a job. You can set up as many Runners as you need.")
%br
- Runners can be placed on separate users, servers, even on your local machine.
+ = _('Runners can be placed on separate users, servers, even on your local machine.')
%br
%div
- %span Each Runner can be in one of the following states:
+ %span= _('Each Runner can be in one of the following states:')
%ul
%li
%span.badge.badge-success shared
- \- Runner runs jobs from all unassigned projects
+ \-
+ = _('Runner runs jobs from all unassigned projects')
%li
%span.badge.badge-success group
- \- Runner runs jobs from all unassigned projects in its group
+ \-
+ = _('Runner runs jobs from all unassigned projects in its group')
%li
%span.badge.badge-info specific
- \- Runner runs jobs from assigned projects
+ \-
+ = _('Runner runs jobs from assigned projects')
%li
%span.badge.badge-warning locked
- \- Runner cannot be assigned to other projects
+ \-
+ = _('Runner cannot be assigned to other projects')
%li
%span.badge.badge-danger paused
- \- Runner will not receive any new jobs
+ \-
+ = _('Runner will not receive any new jobs')
.bs-callout.clearfix
.float-left
%p
- You can reset runners registration token by pressing a button below.
+ = _('You can reset runners registration token by pressing a button below.')
.prepend-top-10
- = button_to _("Reset runners registration token"), reset_runners_token_admin_application_settings_path,
+ = button_to _('Reset runners registration token'), reset_runners_token_admin_application_settings_path,
method: :put, class: 'btn btn-default',
- data: { confirm: _("Are you sure you want to reset registration token?") }
+ data: { confirm: _('Are you sure you want to reset registration token?') }
= render partial: 'ci/runner/how_to_setup_shared_runner',
locals: { registration_token: Gitlab::CurrentSettings.runners_registration_token }
- .append-bottom-20.clearfix
- .float-left
- = form_tag admin_runners_path, id: 'runners-search', class: 'form-inline', method: :get do
- .form-group
- = search_field_tag :search, params[:search], class: 'form-control input-short', placeholder: 'Runner description or token', spellcheck: false
- = submit_tag 'Search', class: 'btn'
-
- .float-right.light
- Runners currently online: #{@active_runners_cnt}
+ .bs-callout
+ %p
+ = _('Runners currently online: %{active_runners_count}') % { active_runners_count: @active_runners_count }
- %br
+ .row-content-block.second-block
+ = form_tag admin_runners_path, id: 'runners-search', method: :get, class: 'filter-form js-filter-form' do
+ .filtered-search-wrapper
+ .filtered-search-box
+ = dropdown_tag(custom_icon('icon_history'),
+ options: { wrapper_class: 'filtered-search-history-dropdown-wrapper',
+ toggle_class: 'filtered-search-history-dropdown-toggle-button',
+ dropdown_class: 'filtered-search-history-dropdown',
+ content_class: 'filtered-search-history-dropdown-content',
+ title: _('Recent searches') }) do
+ .js-filtered-search-history-dropdown{ data: { full_path: admin_runners_path } }
+ .filtered-search-box-input-container.droplab-dropdown
+ .scroll-container
+ %ul.tokens-container.list-unstyled
+ %li.input-token
+ %input.form-control.filtered-search{ { id: 'filtered-search-runners', placeholder: _('Search or filter results...') } }
+ #js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown
+ %ul{ data: { dropdown: true } }
+ %li.filter-dropdown-item{ data: { action: 'submit' } }
+ = button_tag class: %w[btn btn-link] do
+ = sprite_icon('search')
+ %span
+ = _('Press Enter or click to search')
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
+ %li.filter-dropdown-item
+ = button_tag class: %w[btn btn-link] do
+ -# Encapsulate static class name `{{icon}}` inside #{} to bypass
+ -# haml lint's ClassAttributeWithStaticValue
+ %svg
+ %use{ 'xlink:href': "#{'{{icon}}'}" }
+ %span.js-filter-hint
+ {{hint}}
+ %span.js-filter-tag.dropdown-light-content
+ {{tag}}
+ #js-dropdown-admin-runner-status.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul{ data: { dropdown: true } }
+ - Ci::Runner::AVAILABLE_STATUSES.each do |status|
+ %li.filter-dropdown-item{ data: { value: status } }
+ = button_tag class: %w[btn btn-link] do
+ = status.titleize
+ = button_tag class: %w[clear-search hidden] do
+ = icon('times')
+ .filter-dropdown-container
+ = render 'sort_dropdown'
- if @runners.any?
- .runners-content
+ .runners-content.content-list
.table-holder
- %table.table
- %thead
- %tr
- %th Type
- %th Runner token
- %th Description
- %th Version
- %th IP Address
- %th Projects
- %th Jobs
- %th Tags
- %th= link_to 'Last contact', admin_runners_path(safe_params.slice(:search).merge(sort: 'contacted_asc'))
- %th
+ .gl-responsive-table-row.table-row-header{ role: 'row' }
+ .table-section.section-10{ role: 'rowheader' }= _('Type')
+ .table-section.section-10{ role: 'rowheader' }= _('Runner token')
+ .table-section.section-15{ role: 'rowheader' }= _('Description')
+ .table-section.section-15{ role: 'rowheader' }= _('Version')
+ .table-section.section-10{ role: 'rowheader' }= _('IP Address')
+ .table-section.section-5{ role: 'rowheader' }= _('Projects')
+ .table-section.section-5{ role: 'rowheader' }= _('Jobs')
+ .table-section.section-10{ role: 'rowheader' }= _('Tags')
+ .table-section.section-10{ role: 'rowheader' }= _('Last contact')
+ .table-section.section-10{ role: 'rowheader' }
- - @runners.each do |runner|
- = render "admin/runners/runner", runner: runner
- = paginate @runners, theme: "gitlab"
+ - @runners.each do |runner|
+ = render 'admin/runners/runner', runner: runner
+ = paginate @runners, theme: 'gitlab'
- else
- .nothing-here-block No runners found
+ .nothing-here-block= _('No runners found')
diff --git a/app/views/admin/services/_form.html.haml b/app/views/admin/services/_form.html.haml
index 993006e8745..1798b44bbb7 100644
--- a/app/views/admin/services/_form.html.haml
+++ b/app/views/admin/services/_form.html.haml
@@ -7,4 +7,4 @@
= render 'shared/service_settings', form: form, subject: @service
.footer-block.row-content-block
- = form.submit 'Save', class: 'btn btn-save'
+ = form.submit 'Save', class: 'btn btn-success'
diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml
index 7f21bdb91c8..296ef073144 100644
--- a/app/views/admin/users/_form.html.haml
+++ b/app/views/admin/users/_form.html.haml
@@ -75,8 +75,8 @@
.form-actions
- if @user.new_record?
- = f.submit 'Create user', class: "btn btn-create"
+ = f.submit 'Create user', class: "btn btn-success"
= link_to 'Cancel', admin_users_path, class: "btn btn-cancel"
- else
- = f.submit 'Save changes', class: "btn btn-save"
+ = f.submit 'Save changes', class: "btn btn-success"
= link_to 'Cancel', admin_user_path(@user), class: "btn btn-cancel"
diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml
index faeb82656ba..f910e90d6ca 100644
--- a/app/views/admin/users/index.html.haml
+++ b/app/views/admin/users/index.html.haml
@@ -31,7 +31,7 @@
= sort_title_recently_updated
= link_to admin_users_path(sort: sort_value_oldest_updated, filter: params[:filter]) do
= sort_title_oldest_updated
- = link_to 'New user', new_admin_user_path, class: 'btn btn-new btn-search'
+ = link_to 'New user', new_admin_user_path, class: 'btn btn-success btn-search'
.top-area.scrolling-tabs-container.inner-page-scroll-tabs
.fade-left
diff --git a/app/views/admin/users/projects.html.haml b/app/views/admin/users/projects.html.haml
index 3d39c1da408..e6da81831ab 100644
--- a/app/views/admin/users/projects.html.haml
+++ b/app/views/admin/users/projects.html.haml
@@ -7,7 +7,7 @@
.card
.card-header Group projects
%ul.hover-list
- - @user.group_members.includes(:source).each do |group_member|
+ - @user.group_members.includes(:source).each do |group_member| # rubocop: disable CodeReuse/ActiveRecord
- group = group_member.group
%li.group_member
%strong= link_to group.name, admin_group_path(group)
diff --git a/app/views/ci/runner/_how_to_setup_runner.html.haml b/app/views/ci/runner/_how_to_setup_runner.html.haml
index c26eb873718..b1b142460b0 100644
--- a/app/views/ci/runner/_how_to_setup_runner.html.haml
+++ b/app/views/ci/runner/_how_to_setup_runner.html.haml
@@ -1,6 +1,6 @@
- link = link_to _("Install GitLab Runner"), 'https://docs.gitlab.com/runner/install/', target: '_blank'
.append-bottom-10
- %h4= _("Setup a %{type} Runner manually") % { type: type }
+ %h4= _("Set up a %{type} Runner manually") % { type: type }
%ol
%li
diff --git a/app/views/ci/runner/_how_to_setup_specific_runner.html.haml b/app/views/ci/runner/_how_to_setup_specific_runner.html.haml
index e765a353fe4..afe57bdfa01 100644
--- a/app/views/ci/runner/_how_to_setup_specific_runner.html.haml
+++ b/app/views/ci/runner/_how_to_setup_specific_runner.html.haml
@@ -1,6 +1,6 @@
.bs-callout.help-callout
.append-bottom-10
- %h4= _('Setup a specific Runner automatically')
+ %h4= _('Set up a specific Runner automatically')
%p
- link_to_help_page = link_to(_('Learn more about Kubernetes'),
diff --git a/app/views/dashboard/_groups_head.html.haml b/app/views/dashboard/_groups_head.html.haml
index d8f1e50544c..727784141bb 100644
--- a/app/views/dashboard/_groups_head.html.haml
+++ b/app/views/dashboard/_groups_head.html.haml
@@ -10,4 +10,4 @@
= render 'shared/groups/search_form'
= render 'shared/groups/dropdown'
- if current_user.can_create_group?
- = link_to _("New group"), new_group_path, class: "btn btn-new"
+ = link_to _("New group"), new_group_path, class: "btn btn-success"
diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml
index 9b1d9b659f9..69a2e408073 100644
--- a/app/views/dashboard/_projects_head.html.haml
+++ b/app/views/dashboard/_projects_head.html.haml
@@ -19,4 +19,4 @@
= render 'shared/projects/search_form'
= render 'shared/projects/dropdown'
- if current_user.can_create_project?
- = link_to "New project", new_project_path, class: "btn btn-new"
+ = link_to "New project", new_project_path, class: "btn btn-success"
diff --git a/app/views/dashboard/_snippets_head.html.haml b/app/views/dashboard/_snippets_head.html.haml
index e7e323a8683..4f38339b87a 100644
--- a/app/views/dashboard/_snippets_head.html.haml
+++ b/app/views/dashboard/_snippets_head.html.haml
@@ -9,4 +9,4 @@
- if current_user
.nav-controls.d-none.d-sm-block
- = link_to "New snippet", new_snippet_path, class: "btn btn-new", title: "New snippet"
+ = link_to "New snippet", new_snippet_path, class: "btn btn-success", title: "New snippet"
diff --git a/app/views/dashboard/issues.atom.builder b/app/views/dashboard/issues.atom.builder
index d7b6fb9a4a1..6034389b897 100644
--- a/app/views/dashboard/issues.atom.builder
+++ b/app/views/dashboard/issues.atom.builder
@@ -1,3 +1,4 @@
+# rubocop: disable CodeReuse/ActiveRecord
xml.title "#{current_user.name} issues"
xml.link href: url_for(safe_params), rel: "self", type: "application/atom+xml"
xml.link href: issues_dashboard_url, rel: "alternate", type: "text/html"
@@ -5,3 +6,4 @@ xml.id issues_dashboard_url
xml.updated @issues.first.updated_at.xmlschema if @issues.reorder(nil).any?
xml << render(partial: 'issues/issue', collection: @issues) if @issues.reorder(nil).any?
+# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/views/dashboard/snippets/index.html.haml b/app/views/dashboard/snippets/index.html.haml
index 4391624196b..b11dc2c8e9b 100644
--- a/app/views/dashboard/snippets/index.html.haml
+++ b/app/views/dashboard/snippets/index.html.haml
@@ -7,7 +7,7 @@
.d-block.d-sm-none
&nbsp;
- = link_to new_snippet_path, class: "btn btn-new btn-block", title: "New snippet" do
+ = link_to new_snippet_path, class: "btn btn-success btn-block", title: "New snippet" do
New snippet
= render partial: 'snippets/snippets', locals: { link_project: true }
diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml
index 35dafb3e980..4b8ad5acd5b 100644
--- a/app/views/devise/passwords/edit.html.haml
+++ b/app/views/devise/passwords/edit.html.haml
@@ -7,12 +7,12 @@
= f.hidden_field :reset_password_token
.form-group
= f.label 'New password', for: "user_password"
- = f.password_field :password, class: "form-control top", required: true, title: 'This field is required'
+ = f.password_field :password, class: "form-control top qa-password-field", required: true, title: 'This field is required'
.form-group
= f.label 'Confirm new password', for: "user_password_confirmation"
- = f.password_field :password_confirmation, class: "form-control bottom", title: 'This field is required', required: true
+ = f.password_field :password_confirmation, class: "form-control bottom qa-password-confirmation", title: 'This field is required', required: true
.clearfix
- = f.submit "Change your password", class: "btn btn-primary"
+ = f.submit "Change your password", class: "btn btn-primary qa-change-password-button"
.clearfix.prepend-top-20
%p
diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml
index 0ee563ac066..7dacd0b1d72 100644
--- a/app/views/devise/sessions/_new_base.html.haml
+++ b/app/views/devise/sessions/_new_base.html.haml
@@ -1,10 +1,10 @@
= form_for(resource, as: resource_name, url: session_path(resource_name), html: { class: 'new_user gl-show-field-errors', 'aria-live' => 'assertive'}) do |f|
.form-group
- = f.label "Username or email", for: "user_login"
- = f.text_field :login, class: "form-control top", autofocus: "autofocus", autocapitalize: "off", autocorrect: "off", required: true, title: "This field is required."
+ = f.label "Username or email", for: "user_login", class: 'label-bold'
+ = f.text_field :login, class: "form-control top qa-login-field", autofocus: "autofocus", autocapitalize: "off", autocorrect: "off", required: true, title: "This field is required."
.form-group
- = f.label :password
- = f.password_field :password, class: "form-control bottom", required: true, title: "This field is required."
+ = f.label :password, class: 'label-bold'
+ = f.password_field :password, class: "form-control bottom qa-password-field", required: true, title: "This field is required."
- if devise_mapping.rememberable?
.remember-me
%label{ for: "user_remember_me" }
@@ -17,4 +17,4 @@
= recaptcha_tags
.submit-container.move-submit-down
- = f.submit "Sign in", class: "btn btn-save"
+ = f.submit "Sign in", class: "btn btn-success qa-sign-in-button"
diff --git a/app/views/devise/sessions/_new_crowd.html.haml b/app/views/devise/sessions/_new_crowd.html.haml
index 36ff42090be..131544ac0c0 100644
--- a/app/views/devise/sessions/_new_crowd.html.haml
+++ b/app/views/devise/sessions/_new_crowd.html.haml
@@ -10,4 +10,4 @@
%label{ for: "remember_me" }
= check_box_tag :remember_me, '1', false, id: 'remember_me'
%span Remember me
- = submit_tag "Sign in", class: "btn-save btn"
+ = submit_tag "Sign in", class: "btn-success btn"
diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml
index 6bf7349f602..796c0cadda8 100644
--- a/app/views/devise/sessions/_new_ldap.html.haml
+++ b/app/views/devise/sessions/_new_ldap.html.haml
@@ -1,13 +1,13 @@
= form_tag(omniauth_callback_path(:user, server['provider_name']), id: 'new_ldap_user', class: "gl-show-field-errors") do
.form-group
= label_tag :username, "#{server['label']} Username"
- = text_field_tag :username, nil, { class: "form-control top", title: "This field is required.", autofocus: "autofocus", required: true }
+ = text_field_tag :username, nil, { class: "form-control top qa-username-field", title: "This field is required.", autofocus: "autofocus", required: true }
.form-group
= label_tag :password
- = password_field_tag :password, nil, { class: "form-control bottom", title: "This field is required.", required: true }
+ = password_field_tag :password, nil, { class: "form-control bottom qa-password-field", title: "This field is required.", required: true }
- if devise_mapping.rememberable?
.remember-me
%label{ for: "remember_me" }
= check_box_tag :remember_me, '1', false, id: 'remember_me'
%span Remember me
- = submit_tag "Sign in", class: "btn-save btn"
+ = submit_tag "Sign in", class: "btn-success btn qa-sign-in-button"
diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml
index ba168c4eab8..fefdf5f9531 100644
--- a/app/views/devise/sessions/two_factor.html.haml
+++ b/app/views/devise/sessions/two_factor.html.haml
@@ -11,7 +11,7 @@
= f.text_field :otp_attempt, class: 'form-control', required: true, autofocus: true, autocomplete: 'off', title: 'This field is required.'
%p.form-text.text-muted.hint Enter the code from the two-factor app on your mobile device. If you've lost your device, you may enter one of your recovery codes.
.prepend-top-20
- = f.submit "Verify code", class: "btn btn-save"
+ = f.submit "Verify code", class: "btn btn-success"
- if @user.two_factor_u2f_enabled?
= render "u2f/authenticate", locals: { params: params, resource: resource, resource_name: resource_name }
diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml
index 3723814debe..269a3721e06 100644
--- a/app/views/devise/shared/_omniauth_box.html.haml
+++ b/app/views/devise/shared/_omniauth_box.html.haml
@@ -1,14 +1,17 @@
-.omniauth-container
- %p
- %span.light
- Sign in with &nbsp;
- - providers = enabled_button_based_providers
+.omniauth-container.prepend-top-15
+ %label.label-bold.d-block
+ Sign in with
+ - providers = enabled_button_based_providers
+ .d-flex.justify-content-between.flex-wrap
- providers.each do |provider|
- %span.light
- - has_icon = provider_has_icon?(provider)
- = link_to provider_image_tag(provider), omniauth_authorize_path(:user, provider), method: :post, class: 'oauth-login' + (has_icon ? ' oauth-image-link' : ' btn'), id: "oauth-login-#{provider}"
- %fieldset.prepend-top-10.remember-me
- %label
- = check_box_tag :remember_me, nil, false, class: 'remember-me-checkbox'
+ - has_icon = provider_has_icon?(provider)
+ = link_to omniauth_authorize_path(:user, provider), method: :post, class: 'btn d-flex align-items-center omniauth-btn text-left oauth-login', id: "oauth-login-#{provider}" do
+ - if has_icon
+ = provider_image_tag(provider)
%span
- Remember me
+ = label_for_provider(provider)
+ %fieldset.remember-me
+ %label
+ = check_box_tag :remember_me, nil, false, class: 'remember-me-checkbox'
+ %span
+ Remember me
diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml
index ee7369f54a9..90ed20404c5 100644
--- a/app/views/devise/shared/_signup_box.html.haml
+++ b/app/views/devise/shared/_signup_box.html.haml
@@ -4,24 +4,24 @@
.devise-errors
= devise_error_messages!
.form-group
- = f.label :name, 'Full name'
+ = f.label :name, 'Full name', class: 'label-bold'
= f.text_field :name, class: "form-control top", required: true, title: "This field is required."
.username.form-group
- = f.label :username
+ = f.label :username, class: 'label-bold'
= f.text_field :username, class: "form-control middle", pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: 'Please create a username with only alphanumeric characters.'
%p.validation-error.hide Username is already taken.
%p.validation-success.hide Username is available.
%p.validation-pending.hide Checking username availability...
.form-group
- = f.label :email
+ = f.label :email, class: 'label-bold'
= f.email_field :email, class: "form-control middle", required: true, title: "Please provide a valid email address."
.form-group
- = f.label :email_confirmation
+ = f.label :email_confirmation, class: 'label-bold'
= f.email_field :email_confirmation, class: "form-control middle", required: true, title: "Please retype the email address."
.form-group.append-bottom-20#password-strength
- = f.label :password
+ = f.label :password, class: 'label-bold'
= f.password_field :password, class: "form-control bottom", required: true, pattern: ".{#{@minimum_password_length},}", title: "Minimum length is #{@minimum_password_length} characters."
- %p.gl-field-hint Minimum length is #{@minimum_password_length} characters
+ %p.gl-field-hint.text-secondary Minimum length is #{@minimum_password_length} characters
- if Gitlab::CurrentSettings.current_application_settings.enforce_terms?
.form-group
= check_box_tag :terms_opt_in, '1', false, required: true
@@ -34,8 +34,3 @@
= recaptcha_tags
.submit-container
= f.submit "Register", class: "btn-register btn"
-.clearfix.submit-container
- %p
- %span.light Didn't receive a confirmation email?
- = succeed '.' do
- = link_to "Request a new one", new_confirmation_path(:user)
diff --git a/app/views/devise/shared/_tabs_ldap.html.haml b/app/views/devise/shared/_tabs_ldap.html.haml
index 58c585a29ff..3764e86dd8b 100644
--- a/app/views/devise/shared/_tabs_ldap.html.haml
+++ b/app/views/devise/shared/_tabs_ldap.html.haml
@@ -4,10 +4,10 @@
= link_to "Crowd", "#crowd", class: 'nav-link active', 'data-toggle' => 'tab'
- @ldap_servers.each_with_index do |server, i|
%li.nav-item
- = link_to server['label'], "##{server['provider_name']}", class: "nav-link #{active_when(i.zero? && !crowd_enabled?)}", 'data-toggle' => 'tab'
+ = link_to server['label'], "##{server['provider_name']}", class: "nav-link #{active_when(i.zero? && !crowd_enabled?)} qa-ldap-tab", 'data-toggle' => 'tab'
- if password_authentication_enabled_for_web?
%li.nav-item
- = link_to 'Standard', '#login-pane', class: 'nav-link', 'data-toggle' => 'tab'
+ = link_to 'Standard', '#login-pane', class: 'nav-link qa-standard-tab', 'data-toggle' => 'tab'
- if allow_signup?
%li.nav-item
= link_to 'Register', '#register-pane', class: 'nav-link', 'data-toggle' => 'tab'
diff --git a/app/views/devise/shared/_tabs_normal.html.haml b/app/views/devise/shared/_tabs_normal.html.haml
index 284d4fa1b89..8745a4e9d3e 100644
--- a/app/views/devise/shared/_tabs_normal.html.haml
+++ b/app/views/devise/shared/_tabs_normal.html.haml
@@ -1,6 +1,6 @@
%ul.nav-links.new-session-tabs.nav-tabs.nav{ role: 'tablist' }
%li.nav-item{ role: 'presentation' }
- %a.nav-link.active{ href: '#login-pane', data: { toggle: 'tab' }, role: 'tab' } Sign in
+ %a.nav-link.qa-sign-in-tab.active{ href: '#login-pane', data: { toggle: 'tab' }, role: 'tab' } Sign in
- if allow_signup?
%li.nav-item{ role: 'presentation' }
- %a.nav-link{ href: '#register-pane', data: { toggle: 'tab' }, role: 'tab' } Register
+ %a.nav-link.qa-register-tab{ href: '#register-pane', data: { toggle: 'tab' }, role: 'tab' } Register
diff --git a/app/views/discussions/_diff_discussion.html.haml b/app/views/discussions/_diff_discussion.html.haml
index 4b6c4581eb3..6b8dd156874 100644
--- a/app/views/discussions/_diff_discussion.html.haml
+++ b/app/views/discussions/_diff_discussion.html.haml
@@ -4,7 +4,6 @@
-# Text diff discussions
- expanded = local_assigns.fetch(:expanded, true)
%tr.notes_holder{ class: ('hide' unless expanded) }
- %td.notes_line{ colspan: 2 }
- %td.notes_content
+ %td.notes_content{ colspan: 3 }
.content{ class: ('hide' unless expanded) }
= render partial: "discussions/notes", collection: discussions, as: :discussion, locals: { disable_collapse_class: true }
diff --git a/app/views/discussions/_parallel_diff_discussion.html.haml b/app/views/discussions/_parallel_diff_discussion.html.haml
index 079d9083dff..2e621c4082d 100644
--- a/app/views/discussions/_parallel_diff_discussion.html.haml
+++ b/app/views/discussions/_parallel_diff_discussion.html.haml
@@ -1,21 +1,17 @@
- expanded = [*discussions_left, *discussions_right].any?(&:expanded?)
%tr.notes_holder{ class: ('hide' unless expanded) }
- if discussions_left
- %td.notes_line.old
- %td.notes_content.parallel.old
+ %td.notes_content.parallel.old{ colspan: 2 }
.content{ class: ('hide' unless discussions_left.any?(&:expanded?)) }
= render partial: "discussions/notes", collection: discussions_left, as: :discussion, line_type: 'old', locals: { disable_collapse_class: true }
- else
- %td.notes_line.old= ("")
- %td.notes_content.parallel.old
+ %td.notes_content.parallel.old{ colspan: 2 }
.content
- if discussions_right
- %td.notes_line.new
- %td.notes_content.parallel.new
+ %td.notes_content.parallel.new{ colspan: 2 }
.content{ class: ('hide' unless discussions_right.any?(&:expanded?)) }
= render partial: "discussions/notes", collection: discussions_right, as: :discussion, line_type: 'new', locals: { disable_collapse_class: true }
- else
- %td.notes_line.new= ("")
- %td.notes_content.parallel.new
+ %td.notes_content.parallel.new{ colspan: 2 }
.content
diff --git a/app/views/doorkeeper/applications/_form.html.haml b/app/views/doorkeeper/applications/_form.html.haml
index 0bc057a8864..78904f550c7 100644
--- a/app/views/doorkeeper/applications/_form.html.haml
+++ b/app/views/doorkeeper/applications/_form.html.haml
@@ -20,4 +20,4 @@
= render 'shared/tokens/scopes_form', prefix: 'doorkeeper_application', token: application, scopes: @scopes
.prepend-top-default
- = f.submit _('Save application'), class: "btn btn-create"
+ = f.submit _('Save application'), class: "btn btn-success"
diff --git a/app/views/doorkeeper/applications/index.html.haml b/app/views/doorkeeper/applications/index.html.haml
index ab3a1b100ce..1f5c70a6c6e 100644
--- a/app/views/doorkeeper/applications/index.html.haml
+++ b/app/views/doorkeeper/applications/index.html.haml
@@ -16,6 +16,9 @@
= _('Add new application')
= render 'form', application: @application
%hr
+ - else
+ .bs-callout.bs-callout-disabled
+ = _('Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission')
- if user_oauth_applications?
.oauth-applications
%h5
@@ -62,7 +65,7 @@
%th
%tbody
- @authorized_apps.each do |app|
- - token = app.authorized_tokens.order('created_at desc').first
+ - token = app.authorized_tokens.order('created_at desc').first # rubocop: disable CodeReuse/ActiveRecord
%tr{ id: "application_#{app.id}" }
%td= app.name
%td= token.created_at
diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml
index 53a33adc14d..78a1d1a0553 100644
--- a/app/views/events/_event.html.haml
+++ b/app/views/events/_event.html.haml
@@ -11,3 +11,5 @@
= render "events/event/note", event: event
- else
= render "events/event/common", event: event
+- elsif @user&.include_private_contributions?
+ = render "events/event/private", event: event
diff --git a/app/views/events/_event_scope.html.haml b/app/views/events/_event_scope.html.haml
index 8f7da7d8c4f..98941722434 100644
--- a/app/views/events/_event_scope.html.haml
+++ b/app/views/events/_event_scope.html.haml
@@ -1,7 +1,7 @@
%span.event-scope
= event_preposition(event)
- if event.project
- = link_to_project event.project
+ = link_to_project(event.project)
- else
= event.project_name
diff --git a/app/views/events/event/_common.html.haml b/app/views/events/event/_common.html.haml
index 01e72862114..829a3da1558 100644
--- a/app/views/events/event/_common.html.haml
+++ b/app/views/events/event/_common.html.haml
@@ -1,7 +1,7 @@
= icon_for_profile_event(event)
.event-title
- %span.author_name= link_to_author event
+ %span.author_name= link_to_author(event)
%span{ class: event.action_name }
- if event.target
= event.action_name
diff --git a/app/views/events/event/_created_project.html.haml b/app/views/events/event/_created_project.html.haml
index d8e59be57bb..6ad7e157131 100644
--- a/app/views/events/event/_created_project.html.haml
+++ b/app/views/events/event/_created_project.html.haml
@@ -1,11 +1,11 @@
= icon_for_profile_event(event)
.event-title
- %span.author_name= link_to_author event
+ %span.author_name= link_to_author(event)
%span{ class: event.action_name }
= event_action_name(event)
- if event.project
- = link_to_project event.project
+ = link_to_project(event.project)
- else
= event.project_name
diff --git a/app/views/events/event/_note.html.haml b/app/views/events/event/_note.html.haml
index de6383e4097..cdacd998a69 100644
--- a/app/views/events/event/_note.html.haml
+++ b/app/views/events/event/_note.html.haml
@@ -1,7 +1,7 @@
= icon_for_profile_event(event)
.event-title
- %span.author_name= link_to_author event
+ %span.author_name= link_to_author(event)
= event.action_name
= event_note_title_html(event)
diff --git a/app/views/events/event/_private.html.haml b/app/views/events/event/_private.html.haml
new file mode 100644
index 00000000000..ccd2aacb4ea
--- /dev/null
+++ b/app/views/events/event/_private.html.haml
@@ -0,0 +1,10 @@
+.event-inline.event-item
+ .event-item-timestamp
+ = time_ago_with_tooltip(event.created_at)
+
+ .system-note-image= sprite_icon('eye-slash', size: 16, css_class: 'icon')
+
+ .event-title
+ - author_name = capture do
+ %span.author_name= link_to_author(event)
+ = s_('Profiles|%{author_name} made a private contribution').html_safe % { author_name: author_name }
diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml
index 85f2d00bde3..5f0ee79cd9b 100644
--- a/app/views/events/event/_push.html.haml
+++ b/app/views/events/event/_push.html.haml
@@ -3,7 +3,7 @@
= icon_for_profile_event(event)
.event-title
- %span.author_name= link_to_author event
+ %span.author_name= link_to_author(event)
%span.pushed #{event.action_name} #{event.ref_type}
%strong
- commits_link = project_commits_path(project, event.ref_name)
diff --git a/app/views/groups/_archived_projects.html.haml b/app/views/groups/_archived_projects.html.haml
new file mode 100644
index 00000000000..ed79f5790f0
--- /dev/null
+++ b/app/views/groups/_archived_projects.html.haml
@@ -0,0 +1,8 @@
+#js-groups-archived-tree
+ .empty-state.text-center.hidden
+ %p= _("There are no archived projects yet")
+
+ %ul.content-list{ data: { hide_projects: 'false', group_id: group.id, path: group_path(group) } }
+ .js-groups-list-holder
+ .loading-container.text-center
+ = icon('spinner spin 2x', class: 'loading-animation prepend-top-20')
diff --git a/app/views/groups/_children.html.haml b/app/views/groups/_children.html.haml
deleted file mode 100644
index 742b40784d3..00000000000
--- a/app/views/groups/_children.html.haml
+++ /dev/null
@@ -1,4 +0,0 @@
-.js-groups-list-holder
- #js-groups-tree{ data: { hide_projects: 'false', group_id: group.id, endpoint: group_children_path(group, format: :json), path: group_path(group), form_sel: 'form#group-filter-form', filter_sel: '.js-groups-list-filter', holder_sel: '.js-groups-list-holder', dropdown_sel: '.js-group-filter-dropdown-wrap' } }
- .loading-container.text-center
- = icon('spinner spin 2x', class: 'loading-animation prepend-top-20')
diff --git a/app/views/groups/_group_admin_settings.html.haml b/app/views/groups/_group_admin_settings.html.haml
index f7cc62c6929..ff59013ed67 100644
--- a/app/views/groups/_group_admin_settings.html.haml
+++ b/app/views/groups/_group_admin_settings.html.haml
@@ -1,5 +1,5 @@
.form-group.row
- = f.label :lfs_enabled, 'Large File Storage', class: 'col-form-label col-sm-2'
+ = f.label :lfs_enabled, 'Large File Storage', class: 'col-form-label col-sm-2 pt-0'
.col-sm-10
.form-check
= f.check_box :lfs_enabled, checked: @group.lfs_enabled?, class: 'form-check-input'
@@ -11,13 +11,13 @@
%span.descr This setting can be overridden in each project.
.form-group.row
- = f.label :require_two_factor_authentication, 'Two-factor authentication', class: 'col-form-label col-sm-2'
+ = f.label :require_two_factor_authentication, 'Two-factor authentication', class: 'col-form-label col-sm-2 pt-0'
.col-sm-10
.form-check
= f.check_box :require_two_factor_authentication, class: 'form-check-input'
= f.label :require_two_factor_authentication, class: 'form-check-label' do
%strong
- Require all users in this group to setup Two-factor authentication
+ Require all users in this group to set up Two-factor authentication
= link_to icon('question-circle'), help_page_path('security/two_factor_authentication', anchor: 'enforcing-2fa-for-all-users-in-a-group')
.form-group.row
.offset-sm-2.col-sm-10
diff --git a/app/views/groups/_shared_projects.html.haml b/app/views/groups/_shared_projects.html.haml
new file mode 100644
index 00000000000..4eb8367f633
--- /dev/null
+++ b/app/views/groups/_shared_projects.html.haml
@@ -0,0 +1,8 @@
+#js-groups-shared-tree
+ .empty-state.text-center.hidden
+ %p= _("There are no projects shared with this group yet")
+
+ %ul.content-list{ data: { hide_projects: 'false', group_id: group.id, path: group_path(group) } }
+ .js-groups-list-holder
+ .loading-container.text-center
+ = icon('spinner spin 2x', class: 'loading-animation prepend-top-20')
diff --git a/app/views/groups/_subgroups_and_projects.html.haml b/app/views/groups/_subgroups_and_projects.html.haml
new file mode 100644
index 00000000000..d53c8026df8
--- /dev/null
+++ b/app/views/groups/_subgroups_and_projects.html.haml
@@ -0,0 +1,8 @@
+#js-groups-subgroups_and_projects-tree
+ .empty-state.hidden
+ = render "shared/groups/empty_state"
+
+ %ul.content-list{ data: { hide_projects: 'false', group_id: group.id, path: group_path(group) } }
+ .js-groups-list-holder
+ .loading-container.text-center
+ = icon('spinner spin 2x', class: 'loading-animation prepend-top-20')
diff --git a/app/views/groups/group_members/_new_group_member.html.haml b/app/views/groups/group_members/_new_group_member.html.haml
index aa03f8365f9..04683ec5a9a 100644
--- a/app/views/groups/group_members/_new_group_member.html.haml
+++ b/app/views/groups/group_members/_new_group_member.html.haml
@@ -19,4 +19,4 @@
On this date, the member(s) will automatically lose access to this group and all of its projects.
.col-md-2
- = f.submit 'Add to group', class: "btn btn-create btn-block"
+ = f.submit 'Add to group', class: "btn btn-success btn-block"
diff --git a/app/views/groups/issues.atom.builder b/app/views/groups/issues.atom.builder
index 2a385b661e5..2fd96c9d158 100644
--- a/app/views/groups/issues.atom.builder
+++ b/app/views/groups/issues.atom.builder
@@ -1,3 +1,4 @@
+# rubocop: disable CodeReuse/ActiveRecord
xml.title "#{@group.name} issues"
xml.link href: url_for(safe_params), rel: "self", type: "application/atom+xml"
xml.link href: issues_group_url, rel: "alternate", type: "text/html"
@@ -5,3 +6,4 @@ xml.id issues_group_url
xml.updated @issues.first.updated_at.xmlschema if @issues.reorder(nil).any?
xml << render(partial: 'issues/issue', collection: @issues) if @issues.reorder(nil).any?
+# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml
index e6821009d03..003bd25dd06 100644
--- a/app/views/groups/labels/index.html.haml
+++ b/app/views/groups/labels/index.html.haml
@@ -7,7 +7,7 @@
- if can_admin_label
- content_for(:header_content) do
.nav-controls
- = link_to _('New label'), new_group_label_path(@group), class: "btn btn-new"
+ = link_to _('New label'), new_group_label_path(@group), class: "btn btn-success"
- if @labels.exists? || search.present?
#promote-label-modal
@@ -22,6 +22,7 @@
%span.input-group-append
%button.btn.btn-default{ type: "submit", "aria-label" => _('Submit search') }
= icon("search")
+ = render 'shared/labels/sort_dropdown'
.labels-container.prepend-top-5
- if @labels.any?
diff --git a/app/views/groups/milestones/_form.html.haml b/app/views/groups/milestones/_form.html.haml
index 6d35457a0ec..39e3af5f6d2 100644
--- a/app/views/groups/milestones/_form.html.haml
+++ b/app/views/groups/milestones/_form.html.haml
@@ -19,9 +19,9 @@
.form-actions
- if @milestone.new_record?
- = f.submit 'Create milestone', class: "btn-create btn"
+ = f.submit 'Create milestone', class: "btn-success btn"
= link_to "Cancel", group_milestones_path(@group), class: "btn btn-cancel"
- else
- = f.submit 'Update milestone', class: "btn-create btn"
+ = f.submit 'Update milestone', class: "btn-success btn"
= link_to "Cancel", group_milestone_path(@group, @milestone), class: "btn btn-cancel"
diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml
index b6424df55cd..af4fe8f2ef8 100644
--- a/app/views/groups/milestones/index.html.haml
+++ b/app/views/groups/milestones/index.html.haml
@@ -6,7 +6,7 @@
.nav-controls
= render 'shared/milestones_sort_dropdown'
- if can?(current_user, :admin_milestone, @group)
- = link_to "New milestone", new_group_milestone_path(@group), class: "btn btn-new"
+ = link_to "New milestone", new_group_milestone_path(@group), class: "btn btn-success"
.milestones
%ul.content-list
diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml
index 53f54db1ddf..683129fdf6e 100644
--- a/app/views/groups/new.html.haml
+++ b/app/views/groups/new.html.haml
@@ -36,5 +36,5 @@
= render 'shared/group_tips'
.form-actions
- = f.submit 'Create group', class: "btn btn-create"
+ = f.submit 'Create group', class: "btn btn-success"
= link_to 'Cancel', dashboard_groups_path, class: 'btn btn-cancel'
diff --git a/app/views/groups/settings/_permissions.html.haml b/app/views/groups/settings/_permissions.html.haml
index ffce2d4b14f..8dc88ec446c 100644
--- a/app/views/groups/settings/_permissions.html.haml
+++ b/app/views/groups/settings/_permissions.html.haml
@@ -10,7 +10,7 @@
= render 'shared/allow_request_access', form: f
.form-group.row
- %label.col-form-label.col-sm-2
+ %label.col-form-label.col-sm-2.pt-0
= s_('GroupSettings|Share with group lock')
.col-sm-10
.form-check
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index 5a88619f769..f1bd817f17a 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -7,11 +7,10 @@
= render 'groups/home_panel'
-.groups-header{ class: container_class }
- .group-nav-container
- .nav-controls.clearfix
+.groups-listing{ class: container_class, data: { endpoints: { default: group_children_path(@group, format: :json), shared: group_shared_projects_path(@group, format: :json) } } }
+ .top-area.group-nav-container
+ .group-search
= render "shared/groups/search_form"
- = render "shared/groups/dropdown", show_archive_options: true
- if can? current_user, :create_projects, @group
- new_project_label = _("New project")
- new_subgroup_label = _("New subgroup")
@@ -39,7 +38,29 @@
- else
= link_to new_project_label, new_project_path(namespace_id: @group.id), class: "btn btn-success"
- - if params[:filter].blank? && !@has_children
- = render "shared/groups/empty_state"
- - else
- = render "children", children: @children, group: @group
+ .scrolling-tabs-container.inner-page-scroll-tabs
+ .fade-left= icon('angle-left')
+ .fade-right= icon('angle-right')
+ %ul.nav-links.scrolling-tabs.mobile-separator.nav.nav-tabs
+ %li.js-subgroups_and_projects-tab
+ = link_to group_path, data: { target: 'div#subgroups_and_projects', action: 'subgroups_and_projects', toggle: 'tab'} do
+ = _("Subgroups and projects")
+ %li.js-shared-tab
+ = link_to group_shared_path, data: { target: 'div#shared', action: 'shared', toggle: 'tab'} do
+ = _("Shared projects")
+ %li.js-archived-tab
+ = link_to group_archived_path, data: { target: 'div#archived', action: 'archived', toggle: 'tab'} do
+ = _("Archived projects")
+
+ .nav-controls
+ = render "shared/groups/dropdown"
+
+ .tab-content
+ #subgroups_and_projects.tab-pane
+ = render "subgroups_and_projects", group: @group
+
+ #shared.tab-pane
+ = render "shared_projects", group: @group
+
+ #archived.tab-pane
+ = render "archived_projects", group: @group
diff --git a/app/views/help/instance_configuration/_gitlab_pages.html.haml b/app/views/help/instance_configuration/_gitlab_pages.html.haml
index bdd77730dcc..94c25edaf82 100644
--- a/app/views/help/instance_configuration/_gitlab_pages.html.haml
+++ b/app/views/help/instance_configuration/_gitlab_pages.html.haml
@@ -8,7 +8,7 @@
%p
Below are the settings for
- = succeed('.') { link_to('Gitlab Pages', gitlab_pages[:url], target: '_blank') }
+ = succeed('.') { link_to('GitLab Pages', gitlab_pages[:url], target: '_blank') }
.table-responsive
%table
%thead
diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml
index b32b602ceb3..506f580b246 100644
--- a/app/views/help/ui.html.haml
+++ b/app/views/help/ui.html.haml
@@ -189,7 +189,7 @@
%li
= link_to 'Sort by date', '#'
- = link_to 'New issue', '#', class: 'btn btn-new btn-inverted'
+ = link_to 'New issue', '#', class: 'btn btn-success btn-inverted'
.lead
Only nav links without button and search
diff --git a/app/views/import/fogbugz/new.html.haml b/app/views/import/fogbugz/new.html.haml
index b54b1af1e0c..626080c284b 100644
--- a/app/views/import/fogbugz/new.html.haml
+++ b/app/views/import/fogbugz/new.html.haml
@@ -21,4 +21,4 @@
.col-md-4
= password_field_tag :password, nil, class: 'form-control'
.form-actions
- = submit_tag _('Continue to the next step'), class: 'btn btn-create'
+ = submit_tag _('Continue to the next step'), class: 'btn btn-success'
diff --git a/app/views/import/fogbugz/new_user_map.html.haml b/app/views/import/fogbugz/new_user_map.html.haml
index ff2f989c509..8ed9dc68bb3 100644
--- a/app/views/import/fogbugz/new_user_map.html.haml
+++ b/app/views/import/fogbugz/new_user_map.html.haml
@@ -39,4 +39,4 @@
scope: :all, email_user: true, selected: user[:gitlab_user])
.form-actions
- = submit_tag _('Continue to the next step'), class: 'btn btn-create'
+ = submit_tag _('Continue to the next step'), class: 'btn btn-success'
diff --git a/app/views/import/gitea/new.html.haml b/app/views/import/gitea/new.html.haml
index 2b3102f9af9..a88b04eccbb 100644
--- a/app/views/import/gitea/new.html.haml
+++ b/app/views/import/gitea/new.html.haml
@@ -19,4 +19,4 @@
.col-sm-4
= text_field_tag :personal_access_token, nil, class: 'form-control'
.form-actions
- = submit_tag _('List Your Gitea Repositories'), class: 'btn btn-create'
+ = submit_tag _('List Your Gitea Repositories'), class: 'btn btn-success'
diff --git a/app/views/import/gitlab_projects/new.html.haml b/app/views/import/gitlab_projects/new.html.haml
index 4225ee19217..877d945a09b 100644
--- a/app/views/import/gitlab_projects/new.html.haml
+++ b/app/views/import/gitlab_projects/new.html.haml
@@ -8,8 +8,11 @@
= form_tag import_gitlab_project_path, class: 'new_project', multipart: true do
.row
+ .form-group.project-name.col-sm-12
+ = label_tag :name, _('Project name'), class: 'label-bold'
+ = text_field_tag :name, @name, placeholder: "My awesome project", class: "js-project-name form-control input-lg", autofocus: true, required: true
.form-group.col-12.col-sm-6
- = label_tag :namespace_id, 'Project path', class: 'label-bold'
+ = label_tag :namespace_id, _('Project URL'), class: 'label-bold'
.form-group
.input-group
- if current_user.can_select_namespace?
@@ -24,8 +27,8 @@
#{user_url(current_user.username)}/
= hidden_field_tag :namespace_id, value: current_user.namespace_id
.form-group.col-12.col-sm-6.project-path
- = label_tag :path, _('Project name'), class: 'label-bold'
- = text_field_tag :path, @path, placeholder: "my-awesome-project", class: "js-path-name form-control", tabindex: 2, autofocus: true, required: true
+ = label_tag :path, _('Project slug'), class: 'label-bold'
+ = text_field_tag :path, @path, placeholder: "my-awesome-project", class: "js-path-name form-control", tabindex: 2, required: true
.row
.form-group.col-md-12
@@ -38,5 +41,5 @@
= file_field_tag :file, class: ''
.row
.form-actions.col-sm-12
- = submit_tag _('Import project'), class: 'btn btn-create'
+ = submit_tag _('Import project'), class: 'btn btn-success'
= link_to _('Cancel'), new_project_path, class: 'btn btn-cancel'
diff --git a/app/views/import/google_code/new.html.haml b/app/views/import/google_code/new.html.haml
index fd6e4726fc5..7a6ad28f0aa 100644
--- a/app/views/import/google_code/new.html.haml
+++ b/app/views/import/google_code/new.html.haml
@@ -59,4 +59,4 @@
= _('Yes, let me map Google Code users to full names or GitLab users.')
%li
%p
- = submit_tag _('Continue to the next step'), class: "btn btn-create"
+ = submit_tag _('Continue to the next step'), class: "btn btn-success"
diff --git a/app/views/import/google_code/new_user_map.html.haml b/app/views/import/google_code/new_user_map.html.haml
index baaaf6bdc63..f523b993aa7 100644
--- a/app/views/import/google_code/new_user_map.html.haml
+++ b/app/views/import/google_code/new_user_map.html.haml
@@ -33,4 +33,4 @@
= text_area_tag :user_map, JSON.pretty_generate(@user_map), class: 'form-control', rows: 15
.form-actions
- = submit_tag _('Continue to the next step'), class: "btn btn-create"
+ = submit_tag _('Continue to the next step'), class: "btn btn-success"
diff --git a/app/views/instance_statistics/cohorts/index.html.haml b/app/views/instance_statistics/cohorts/index.html.haml
index 5e9a8c083af..e135bab10d8 100644
--- a/app/views/instance_statistics/cohorts/index.html.haml
+++ b/app/views/instance_statistics/cohorts/index.html.haml
@@ -1,16 +1,16 @@
-- breadcrumb_title "Cohorts"
+- breadcrumb_title _("Cohorts")
- @no_container = true
%div{ class: container_class }
- if @cohorts
= render 'cohorts_table'
- = render 'usage_ping'
- else
.bs-callout.bs-callout-warning.clearfix
%p
- User cohorts are only shown when the
- = link_to 'usage ping', help_page_path('user/admin_area/settings/usage_statistics', anchor: 'usage-ping'), target: '_blank'
- is enabled. To enable it and see user cohorts,
- visit
- = succeed '.' do
- = link_to 'application settings', admin_application_settings_path(anchor: 'usage-statistics')
+ - usage_ping_path = help_page_path('user/admin_area/settings/usage_statistics', anchor: 'usage-ping')
+ - usage_ping_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: usage_ping_path }
+ = s_('User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled.').html_safe % { usage_ping_link_start: usage_ping_link_start, usage_ping_link_end: '</a>'.html_safe }
+ - if current_user.admin?
+ - application_settings_path = admin_application_settings_path(anchor: 'usage-statistics')
+ - application_settings_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: application_settings_path }
+ = s_('To enable it and see User Cohorts, visit %{application_settings_link_start}application settings%{application_settings_link_end}.').html_safe % { application_settings_link_start: application_settings_link_start, application_settings_link_end: '</a>'.html_safe }
diff --git a/app/views/instance_statistics/conversational_development_index/_disabled.html.haml b/app/views/instance_statistics/conversational_development_index/_disabled.html.haml
index 0a741b50960..0a5717f75e1 100644
--- a/app/views/instance_statistics/conversational_development_index/_disabled.html.haml
+++ b/app/views/instance_statistics/conversational_development_index/_disabled.html.haml
@@ -1,9 +1,14 @@
.container.convdev-empty
.col-sm-12.justify-content-center.text-center
= custom_icon('convdev_no_index')
- %h4 Usage ping is not enabled
- %p
- ConvDev is only shown when the
- = link_to 'usage ping', help_page_path('user/admin_area/settings/usage_statistics'), target: '_blank'
- is enabled. Enable usage ping to get an overview of how you are using GitLab from a feature perspective
- = link_to 'Enable usage ping', admin_application_settings_path(anchor: 'usage-statistics'), class: 'btn btn-primary'
+ %h4= _('Usage ping is not enabled')
+ - if !current_user.admin?
+ %p
+ - usage_ping_path = help_page_path('user/admin_area/settings/usage_statistics', anchor: 'usage-ping')
+ - usage_ping_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: usage_ping_path }
+ = s_('In order to enable instance-level analytics, please ask an admin to enable %{usage_ping_link_start}usage ping%{usage_ping_link_end}.').html_safe % { usage_ping_link_start: usage_ping_link_start, usage_ping_link_end: '</a>'.html_safe }
+ - if current_user.admin?
+ %p
+ = _('Enable usage ping to get an overview of how you are using GitLab from a feature perspective.')
+ - if current_user.admin?
+ = link_to _('Enable usage ping'), admin_application_settings_path(anchor: 'usage-statistics'), class: 'btn btn-primary'
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 dd63b98376f..1e7db4982d6 100644
--- a/app/views/instance_statistics/conversational_development_index/index.html.haml
+++ b/app/views/instance_statistics/conversational_development_index/index.html.haml
@@ -1,12 +1,13 @@
- @no_container = true
-- page_title 'ConvDev Index'
+- page_title _('ConvDev Index')
+- usage_ping_enabled = Gitlab::CurrentSettings.usage_ping_enabled
.container
- - if show_callout?('convdev_intro_callout_dismissed')
+ - if usage_ping_enabled && show_callout?('convdev_intro_callout_dismissed')
= render 'callout'
.prepend-top-default
- - if !Gitlab::CurrentSettings.usage_ping_enabled
+ - if !usage_ping_enabled
= render 'disabled'
- elsif @metric.blank?
= render 'no_data'
diff --git a/app/views/issues/_issues_calendar.ics.ruby b/app/views/issues/_issues_calendar.ics.ruby
index 3563635d33d..73ab8489e0c 100644
--- a/app/views/issues/_issues_calendar.ics.ruby
+++ b/app/views/issues/_issues_calendar.ics.ruby
@@ -2,6 +2,7 @@ cal = Icalendar::Calendar.new
cal.prodid = '-//GitLab//NONSGML GitLab//EN'
cal.x_wr_calname = 'GitLab Issues'
+# rubocop: disable CodeReuse/ActiveRecord
@issues.includes(project: :namespace).each do |issue|
cal.event do |event|
event.dtstart = Icalendar::Values::Date.new(issue.due_date)
@@ -11,5 +12,6 @@ cal.x_wr_calname = 'GitLab Issues'
event.transp = 'TRANSPARENT'
end
end
+# rubocop: enable CodeReuse/ActiveRecord
cal.to_ical
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index f67a8878c80..a41d30da450 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -8,6 +8,7 @@
= render "layouts/broadcast"
= render 'layouts/header/read_only_banner'
= yield :flash_message
+ = render "shared/ping_consent"
- unless @hide_breadcrumbs
= render "layouts/nav/breadcrumbs"
= render "layouts/flash"
diff --git a/app/views/layouts/nav/sidebar/_admin.html.haml b/app/views/layouts/nav/sidebar/_admin.html.haml
index ff25b040913..f912a32ee1a 100644
--- a/app/views/layouts/nav/sidebar/_admin.html.haml
+++ b/app/views/layouts/nav/sidebar/_admin.html.haml
@@ -7,14 +7,14 @@
.sidebar-context-title
= _('Admin Area')
%ul.sidebar-top-level-items
- = nav_link(controller: %w(dashboard admin projects users groups jobs runners gitaly_servers), html_options: {class: 'home'}) do
+ = nav_link(controller: %w(dashboard admin admin/projects users groups jobs runners gitaly_servers), html_options: {class: 'home'}) do
= link_to admin_root_path, class: 'shortcuts-tree' do
.nav-icon-container
= sprite_icon('overview')
%span.nav-item-name
= _('Overview')
%ul.sidebar-sub-level-items
- = nav_link(controller: %w(dashboard admin projects users groups jobs runners gitaly_servers), html_options: { class: "fly-out-top-item" } ) do
+ = nav_link(controller: %w(dashboard admin admin/projects users groups jobs runners gitaly_servers), html_options: { class: "fly-out-top-item" } ) do
= link_to admin_root_path do
%strong.fly-out-top-item-name
= _('Overview')
@@ -23,7 +23,7 @@
= link_to admin_root_path, title: _('Overview') do
%span
= _('Dashboard')
- = nav_link(controller: [:admin, :projects]) do
+ = nav_link(controller: [:admin, 'admin/projects']) do
= link_to admin_projects_path, title: _('Projects') do
%span
= _('Projects')
@@ -199,10 +199,54 @@
= sprite_icon('settings')
%span.nav-item-name
= _('Settings')
- %ul.sidebar-sub-level-items.is-fly-out-only
+
+ %ul.sidebar-sub-level-items
= nav_link(controller: :application_settings, html_options: { class: "fly-out-top-item" } ) do
= link_to admin_application_settings_path do
%strong.fly-out-top-item-name
= _('Settings')
+ %li.divider.fly-out-top-item
+ = nav_link(path: 'application_settings#show') do
+ = link_to admin_application_settings_path, title: _('General') do
+ %span
+ = _('General')
+ = nav_link(path: 'application_settings#integrations') do
+ = link_to integrations_admin_application_settings_path, title: _('Integrations') do
+ %span
+ = _('Integrations')
+ = nav_link(path: 'application_settings#repository') do
+ = link_to repository_admin_application_settings_path, title: _('Repository') do
+ %span
+ = _('Repository')
+ - if template_exists?('admin/application_settings/templates')
+ = nav_link(path: 'application_settings#templates') do
+ = link_to templates_admin_application_settings_path, title: _('Templates') do
+ %span
+ = _('Templates')
+ = nav_link(path: 'application_settings#ci_cd') do
+ = link_to ci_cd_admin_application_settings_path, title: _('CI/CD') do
+ %span
+ = _('CI/CD')
+ = nav_link(path: 'application_settings#reporting') do
+ = link_to reporting_admin_application_settings_path, title: _('Reporting') do
+ %span
+ = _('Reporting')
+ = nav_link(path: 'application_settings#metrics_and_profiling') do
+ = link_to metrics_and_profiling_admin_application_settings_path, title: _('Metrics and profiling') do
+ %span
+ = _('Metrics and profiling')
+ = nav_link(path: 'application_settings#network') do
+ = link_to network_admin_application_settings_path, title: _('Network') do
+ %span
+ = _('Network')
+ - if template_exists?('admin/application_settings/geo')
+ = nav_link(path: 'application_settings#geo') do
+ = link_to geo_admin_application_settings_path, title: _('Geo') do
+ %span
+ = _('Geo')
+ = nav_link(path: 'application_settings#preferences') do
+ = link_to preferences_admin_application_settings_path, title: _('Preferences') do
+ %span
+ = _('Preferences')
= render 'shared/sidebar_toggle_button'
diff --git a/app/views/layouts/nav/sidebar/_instance_statistics.html.haml b/app/views/layouts/nav/sidebar/_instance_statistics.html.haml
index b8ff448f261..57180f27146 100644
--- a/app/views/layouts/nav/sidebar/_instance_statistics.html.haml
+++ b/app/views/layouts/nav/sidebar/_instance_statistics.html.haml
@@ -18,16 +18,17 @@
%strong.fly-out-top-item-name
= _('ConvDev Index')
- = nav_link(controller: :cohorts) do
- = link_to instance_statistics_cohorts_path do
- .nav-icon-container
- = sprite_icon('users')
- %span.nav-item-name
- = _('Cohorts')
- %ul.sidebar-sub-level-items.is-fly-out-only
- = nav_link(controller: :cohorts, html_options: { class: "fly-out-top-item" } ) do
- = link_to instance_statistics_cohorts_path do
- %strong.fly-out-top-item-name
- = _('Cohorts')
+ - if Gitlab::CurrentSettings.usage_ping_enabled
+ = nav_link(controller: :cohorts) do
+ = link_to instance_statistics_cohorts_path do
+ .nav-icon-container
+ = sprite_icon('users')
+ %span.nav-item-name
+ = _('Cohorts')
+ %ul.sidebar-sub-level-items.is-fly-out-only
+ = nav_link(controller: :cohorts, html_options: { class: "fly-out-top-item" } ) do
+ = link_to instance_statistics_cohorts_path do
+ %strong.fly-out-top-item-name
+ = _('Cohorts')
= render 'shared/sidebar_toggle_button'
diff --git a/app/views/layouts/nav/sidebar/_profile.html.haml b/app/views/layouts/nav/sidebar/_profile.html.haml
index d65f153b451..69167edb1df 100644
--- a/app/views/layouts/nav/sidebar/_profile.html.haml
+++ b/app/views/layouts/nav/sidebar/_profile.html.haml
@@ -28,18 +28,17 @@
= link_to profile_account_path do
%strong.fly-out-top-item-name
= _('Account')
- - if Gitlab::CurrentSettings.user_oauth_applications?
- = nav_link(controller: 'oauth/applications') do
- = link_to applications_profile_path do
- .nav-icon-container
- = sprite_icon('applications')
- %span.nav-item-name
- = _('Applications')
- %ul.sidebar-sub-level-items.is-fly-out-only
- = nav_link(controller: 'oauth/applications', html_options: { class: "fly-out-top-item" } ) do
- = link_to applications_profile_path do
- %strong.fly-out-top-item-name
- = _('Applications')
+ = nav_link(controller: 'oauth/applications') do
+ = link_to applications_profile_path do
+ .nav-icon-container
+ = sprite_icon('applications')
+ %span.nav-item-name
+ = _('Applications')
+ %ul.sidebar-sub-level-items.is-fly-out-only
+ = nav_link(controller: 'oauth/applications', html_options: { class: "fly-out-top-item" } ) do
+ = link_to applications_profile_path do
+ %strong.fly-out-top-item-name
+ = _('Applications')
= nav_link(controller: :chat_names) do
= link_to profile_chat_names_path do
.nav-icon-container
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index 30e0e9fca27..25cd53b378a 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -158,7 +158,7 @@
- if project_nav_tab? :pipelines
= nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :artifacts]) do
- = link_to project_pipelines_path(@project), class: 'shortcuts-pipelines' do
+ = link_to project_pipelines_path(@project), class: 'shortcuts-pipelines qa-link-pipelines' do
.nav-icon-container
= sprite_icon('rocket')
%span.nav-item-name
@@ -245,7 +245,7 @@
= link_to _('Auto DevOps'), help_page_path('topics/autodevops/index.md')
%span= _('uses Kubernetes clusters to deploy your code!')
%hr
- %button.btn.btn-create.btn-sm.dismiss-feature-highlight{ type: 'button' }
+ %button.btn.btn-success.btn-sm.dismiss-feature-highlight{ type: 'button' }
%span= _("Got it!")
= sprite_icon('thumb-up')
diff --git a/app/views/notify/_failed_builds.html.haml b/app/views/notify/_failed_builds.html.haml
new file mode 100644
index 00000000000..7c563bb016c
--- /dev/null
+++ b/app/views/notify/_failed_builds.html.haml
@@ -0,0 +1,32 @@
+%tr
+ %td{ colspan: 2, style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; color: #333333; font-size: 14px; font-weight: 400; line-height: 1.4; padding: 0 8px 16px; text-align: center;" }
+ had
+ = failed.size
+ failed
+ #{'build'.pluralize(failed.size)}.
+%tr.table-warning
+ %td{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; border: 1px solid #ededed; border-bottom: 0; border-radius: 4px 4px 0 0; overflow: hidden; background-color: #fdf4f6; color: #d22852; font-size: 14px; line-height: 1.4; text-align: center; padding: 8px 16px;" }
+ Logs may contain sensitive data. Please consider before forwarding this email.
+%tr.section
+ %td{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; padding: 0 16px; border: 1px solid #ededed; border-radius: 4px; overflow: hidden; border-top: 0; border-radius: 0 0 4px 4px;" }
+ %table.builds{ border: "0", cellpadding: "0", cellspacing: "0", style: "width: 100%; border-collapse: collapse;" }
+ %tbody
+ - failed.each do |build|
+ %tr.build-state
+ %td{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; padding: 16px 0; color: #8c8c8c; font-weight: 500; font-size: 14px;" }
+ %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse: collapse;" }
+ %tbody
+ %tr
+ %td{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; color: #d22f57; font-weight: 500; font-size: 16px; vertical-align: middle; padding-right: 8px; line-height: 10px" }
+ %img{ alt: "✖", height: "10", src: image_url('mailers/ci_pipeline_notif_v1/icon-x-red.gif'), style: "display: block;", width: "10" }/
+ %td{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; color: #8c8c8c; font-weight: 500; font-size: 14px; vertical-align: middle;" }
+ = build.stage
+ %td{ align: "right", style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; padding: 16px 0; color: #8c8c8c; font-weight: 500; font-size: 14px;" }
+ = render "notify/links/#{build.to_partial_path}", pipeline: pipeline, build: build
+ %tr.build-log
+ - if build.has_trace?
+ %td{ colspan: "2", style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; padding: 0 0 16px;" }
+ %pre{ style: "font-family: Monaco,'Lucida Console','Courier New',Courier,monospace; background-color: #fafafa; border-radius: 4px; overflow: hidden; white-space: pre-wrap; word-break: break-all; font-size:13px; line-height: 1.4; padding: 16px 8px; color: #333333; margin: 0;" }
+ = build.trace.html(last_lines: 10).html_safe
+ - else
+ %td{ colspan: "2" }
diff --git a/app/views/notify/autodevops_disabled_email.html.haml b/app/views/notify/autodevops_disabled_email.html.haml
new file mode 100644
index 00000000000..65a2f75a3e2
--- /dev/null
+++ b/app/views/notify/autodevops_disabled_email.html.haml
@@ -0,0 +1,49 @@
+%tr.alert
+ %td{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; padding: 8px 16px; border-radius: 4px; font-size: 14px; line-height: 1.3; text-align: center; overflow: hidden; background-color: #d22f57; color: #ffffff;" }
+ %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse: collapse; margin: 0 auto;" }
+ %tbody
+ %tr
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif; vertical-align: middle; color: #ffffff; text-align: center;" }
+ Auto DevOps pipeline was disabled for #{@project.name}
+
+%tr.pre-section
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif; color: #333333; font-size: 14px; font-weight: 400; line-height: 1.7; padding: 16px 8px 0;" }
+ The Auto DevOps pipeline failed for pipeline
+ %a{ href: pipeline_url(@pipeline), style: "color: #1b69b6; text-decoration:none;" }
+ = "\##{@pipeline.iid}"
+ and has been disabled for
+ %a{ href: project_url(@project), style: "color: #1b69b6; text-decoration: none;" }
+ = @project.name + "."
+ In order to use the Auto DevOps pipeline with your project, please review the
+ %a{ href: 'https://docs.gitlab.com/ee/topics/autodevops/#currently-supported-languages', style: "color:#1b69b6;text-decoration:none;" } currently supported languages,
+ adjust your project accordingly, and turn on the Auto DevOps pipeline within your
+ %a{ href: project_settings_ci_cd_url(@project), style: "color: #1b69b6; text-decoration: none;" }
+ CI/CD project settings.
+
+%tr.pre-section
+ %td{ style: 'text-align: center;border-bottom:1px solid #ededed' }
+ %a{ href: 'https://docs.gitlab.com/ee/topics/autodevops/', style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;" }
+ %button{ type: 'button', style: 'border-color: #dfdfdf; border-style: solid; border-width: 1px; border-radius: 4px; font-size: 14px; padding: 8px 16px; background-color:#fff; margin: 8px 0; cursor: pointer;' }
+ Learn more about Auto DevOps
+
+%tr.pre-section
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif; color: #333333; font-size: 14px; font-weight: 400; line-height: 1.4; padding: 16px 8px; text-align: center;" }
+ %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;margin:0 auto;" }
+ %tbody
+ %tr
+ %td{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; font-size:14px; font-weight:500;line-height: 1.4; vertical-align: baseline;" }
+ Pipeline
+ %a{ href: pipeline_url(@pipeline), style: "color: #1b69b6; text-decoration: none;" }
+ = "\##{@pipeline.id}"
+ triggered by
+ - if @pipeline.user
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif; font-size: 15px; line-height: 1.4; vertical-align: middle; padding-right: 8px; padding-left:8px", width: "24" }
+ %img.avatar{ height: "24", src: avatar_icon_for_user(@pipeline.user, 24, only_path: false), style: "display: block; border-radius: 12px; margin: -2px 0;", width: "24", alt: "" }/
+ %td{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; font-size: 14px; font-weight: 500; line-height: 1.4; vertical-align: baseline;" }
+ %a.muted{ href: user_url(@pipeline.user), style: "color: #333333; text-decoration: none;" }
+ = @pipeline.user.name
+ - else
+ %td{ style: "font-family: 'Menlo','Liberation Mono','Consolas','DejaVu Sans Mono','Ubuntu Mono','Courier New','andale mono','lucida console',monospace; font-size: 14px; line-height: 1.4; vertical-align: baseline; padding:0 8px;" }
+ API
+
+= render 'notify/failed_builds', pipeline: @pipeline, failed: @pipeline.statuses.latest.failed
diff --git a/app/views/notify/autodevops_disabled_email.text.erb b/app/views/notify/autodevops_disabled_email.text.erb
new file mode 100644
index 00000000000..695780c3145
--- /dev/null
+++ b/app/views/notify/autodevops_disabled_email.text.erb
@@ -0,0 +1,20 @@
+Auto DevOps pipeline was disabled for <%= @project.name %>
+
+The Auto DevOps pipeline failed for pipeline <%= @pipeline.iid %> (<%= pipeline_url(@pipeline) %>) and has been disabled for <%= @project.name %>. In order to use the Auto DevOps pipeline with your project, please review the currently supported languagues (https://docs.gitlab.com/ee/topics/autodevops/#currently-supported-languages), adjust your project accordingly, and turn on the Auto DevOps pipeline within your CI/CD project settings (<%= project_settings_ci_cd_url(@project) %>).
+
+<% if @pipeline.user -%>
+ Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) triggered by <%= @pipeline.user.name %> ( <%= user_url(@pipeline.user) %> )
+<% else -%>
+ Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) triggered by API
+<% end -%>
+<% failed = @pipeline.statuses.latest.failed -%>
+had <%= failed.size %> failed <%= 'build'.pluralize(failed.size) %>.
+
+<% failed.each do |build| -%>
+ <%= render "notify/links/#{build.to_partial_path}", pipeline: @pipeline, build: build %>
+ Stage: <%= build.stage %>
+ Name: <%= build.name %>
+ <% if build.has_trace? -%>
+ Trace: <%= build.trace.raw(last_lines: 10) %>
+ <% end -%>
+<% end -%>
diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml
index baafaa6e3a0..86dcca4a447 100644
--- a/app/views/notify/pipeline_failed_email.html.haml
+++ b/app/views/notify/pipeline_failed_email.html.haml
@@ -107,36 +107,5 @@
- else
%td{ style: "font-family:'Menlo','Liberation Mono','Consolas','DejaVu Sans Mono','Ubuntu Mono','Courier New','andale mono','lucida console',monospace;font-size:14px;line-height:1.4;vertical-align:baseline;padding:0 5px;" }
API
-- failed = @pipeline.statuses.latest.failed
-%tr
- %td{ colspan: 2, style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:300;line-height:1.4;padding:15px 5px;text-align:center;" }
- had
- = failed.size
- failed
- #{'build'.pluralize(failed.size)}.
-%tr.table-warning
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;border:1px solid #ededed;border-bottom:0;border-radius:3px 3px 0 0;overflow:hidden;background-color:#fdf4f6;color:#d22852;font-size:14px;line-height:1.4;text-align:center;padding:8px 15px;" }
- Logs may contain sensitive data. Please consider before forwarding this email.
-%tr.section
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;border-top:0;border-radius:0 0 3px 3px;" }
- %table.builds{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;border-collapse:collapse;" }
- %tbody
- - failed.each do |build|
- %tr.build-state
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:20px 0;color:#8c8c8c;font-weight:500;font-size:15px;" }
- %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
- %tbody
- %tr
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#d22f57;font-weight:500;font-size:15px;vertical-align:middle;padding-right:5px;line-height:10px" }
- %img{ alt: "✖", height: "10", src: image_url('mailers/ci_pipeline_notif_v1/icon-x-red.gif'), style: "display:block;", width: "10" }/
- %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#8c8c8c;font-weight:500;font-size:15px;vertical-align:middle;" }
- = build.stage
- %td{ align: "right", style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:20px 0;color:#8c8c8c;font-weight:500;font-size:15px;" }
- = render "notify/links/#{build.to_partial_path}", pipeline: @pipeline, build: build
- %tr.build-log
- - if build.has_trace?
- %td{ colspan: "2", style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 0 15px;" }
- %pre{ style: "font-family:Monaco,'Lucida Console','Courier New',Courier,monospace;background-color:#fafafa;border-radius:3px;overflow:hidden;white-space:pre-wrap;word-break:break-all;font-size:13px;line-height:1.4;padding:12px;color:#333333;margin:0;" }
- = build.trace.html(last_lines: 10).html_safe
- - else
- %td{ colspan: "2" }
+
+= render 'notify/failed_builds', pipeline: @pipeline, failed: @pipeline.statuses.latest.failed
diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml
index 04a19ab14dd..1823f191fb3 100644
--- a/app/views/profiles/emails/index.html.haml
+++ b/app/views/profiles/emails/index.html.haml
@@ -15,14 +15,16 @@
= f.label :email, class: 'label-bold'
= f.text_field :email, class: 'form-control'
.prepend-top-default
- = f.submit 'Add email address', class: 'btn btn-create'
+ = f.submit 'Add email address', class: 'btn btn-success'
%hr
%h4.prepend-top-0
Linked emails (#{@emails.count + 1})
.account-well.append-bottom-default
%ul
%li
- Your Primary Email will be used for avatar detection and web based operations, such as edits and merges.
+ Your Primary Email will be used for avatar detection.
+ %li
+ Your Commit Email will be used for web based operations, such as edits and merges.
%li
Your Notification Email will be used for account notifications.
%li
@@ -34,6 +36,8 @@
= render partial: 'shared/email_with_badge', locals: { email: @primary_email, verified: current_user.confirmed? }
%span.float-right
%span.badge.badge-success Primary email
+ - if @primary_email === current_user.commit_email
+ %span.badge.badge-info Commit email
- if @primary_email === current_user.public_email
%span.badge.badge-info Public email
- if @primary_email === current_user.notification_email
@@ -42,6 +46,8 @@
%li
= render partial: 'shared/email_with_badge', locals: { email: email.email, verified: email.confirmed? }
%span.float-right
+ - if email.email === current_user.commit_email
+ %span.badge.badge-info Commit email
- if email.email === current_user.public_email
%span.badge.badge-info Public email
- if email.email === current_user.notification_email
diff --git a/app/views/profiles/gpg_keys/_form.html.haml b/app/views/profiles/gpg_keys/_form.html.haml
index aa9b0aad034..6c4cb614a2b 100644
--- a/app/views/profiles/gpg_keys/_form.html.haml
+++ b/app/views/profiles/gpg_keys/_form.html.haml
@@ -7,4 +7,4 @@
= f.text_area :key, class: "form-control", rows: 8, required: true, placeholder: "Don't paste the private part of the GPG key. Paste the public part which begins with '-----BEGIN PGP PUBLIC KEY BLOCK-----'."
.prepend-top-default
- = f.submit 'Add key', class: "btn btn-create"
+ = f.submit 'Add key', class: "btn btn-success"
diff --git a/app/views/profiles/keys/_form.html.haml b/app/views/profiles/keys/_form.html.haml
index 5207921d6fe..21eef08983c 100644
--- a/app/views/profiles/keys/_form.html.haml
+++ b/app/views/profiles/keys/_form.html.haml
@@ -5,10 +5,10 @@
.form-group
= f.label :key, class: 'label-bold'
%p= _("Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key.")
- = f.text_area :key, class: "form-control js-add-ssh-key-validation-input", rows: 8, required: true, placeholder: s_('Profiles|Typically starts with "ssh-rsa …"')
+ = f.text_area :key, class: "form-control js-add-ssh-key-validation-input qa-key-public-key-field", rows: 8, required: true, placeholder: s_('Profiles|Typically starts with "ssh-rsa …"')
.form-group
= f.label :title, class: 'label-bold'
- = f.text_field :title, class: "form-control input-lg", required: true, placeholder: s_('Profiles|e.g. My MacBook key')
+ = f.text_field :title, class: "form-control input-lg qa-key-title-field", required: true, placeholder: s_('Profiles|e.g. My MacBook key')
%p.form-text.text-muted= _('Name your individual key via a title')
.js-add-ssh-key-validation-warning.hide
@@ -16,7 +16,7 @@
%strong= _('Oops, are you sure?')
%p= s_("Profiles|This doesn't look like a public SSH key, are you sure you want to add it?")
- %button.btn.btn-create.js-add-ssh-key-validation-confirm-submit= _("Yes, add it")
+ %button.btn.btn-success.js-add-ssh-key-validation-confirm-submit= _("Yes, add it")
.prepend-top-default
- = f.submit s_('Profiles|Add key'), class: "btn btn-create js-add-ssh-key-validation-original-submit"
+ = f.submit s_('Profiles|Add key'), class: "btn btn-success js-add-ssh-key-validation-original-submit qa-add-key-button"
diff --git a/app/views/profiles/keys/_key_details.html.haml b/app/views/profiles/keys/_key_details.html.haml
index 2ac514d3f6f..88473c7f72d 100644
--- a/app/views/profiles/keys/_key_details.html.haml
+++ b/app/views/profiles/keys/_key_details.html.haml
@@ -24,4 +24,4 @@
= @key.key
.col-md-12
.float-right
- = link_to 'Remove', path_to_key(@key, is_admin), data: {confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove delete-key"
+ = link_to 'Remove', path_to_key(@key, is_admin), data: {confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove delete-key qa-delete-key-button"
diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml
index 9c8cc9c059b..0b4b9841ea1 100644
--- a/app/views/profiles/passwords/edit.html.haml
+++ b/app/views/profiles/passwords/edit.html.haml
@@ -29,6 +29,6 @@
= f.label :password_confirmation, class: 'label-bold'
= f.password_field :password_confirmation, required: true, class: 'form-control'
.prepend-top-default.append-bottom-default
- = f.submit 'Save password', class: "btn btn-create append-right-10"
+ = f.submit 'Save password', class: "btn btn-success append-right-10"
- unless @user.password_automatically_set?
= link_to "I forgot my password", reset_profile_password_path, method: :put, class: "account-btn-link"
diff --git a/app/views/profiles/passwords/new.html.haml b/app/views/profiles/passwords/new.html.haml
index 2176d7f8a31..d265f3c44ba 100644
--- a/app/views/profiles/passwords/new.html.haml
+++ b/app/views/profiles/passwords/new.html.haml
@@ -1,6 +1,6 @@
- page_title "New Password"
- header_title "New Password"
-%h3.page-title Setup new password
+%h3.page-title Set up new password
%hr
= form_for @user, url: profile_password_path, method: :post do |f|
%p.slead
@@ -22,4 +22,4 @@
.col-sm-10
= f.password_field :password_confirmation, required: true, class: 'form-control'
.form-actions
- = f.submit 'Set new password', class: "btn btn-create"
+ = f.submit 'Set new password', class: "btn btn-success"
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index fd6dd74e1c5..156c0d05b02 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -58,4 +58,4 @@
.form-text.text-muted
Choose what content you want to see on a project’s overview page
.form-group
- = f.submit 'Save changes', class: 'btn btn-save'
+ = f.submit 'Save changes', class: 'btn btn-success'
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index 6f08a294c5d..51f5ecf2166 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -1,5 +1,6 @@
-- breadcrumb_title "Edit Profile"
+- breadcrumb_title s_("Profiles|Edit Profile")
- @content_class = "limit-container-width" unless fluid_layout
+- gravatar_link = link_to Gitlab.config.gravatar.host, 'https://' + Gitlab.config.gravatar.host
= bootstrap_form_for @user, url: profile_path, method: :put, html: { multipart: true, class: 'edit-user prepend-top-default js-quick-submit' }, authenticity_token: true do |f|
= form_errors(@user)
@@ -7,34 +8,36 @@
.row
.col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
- Public Avatar
+ = s_("Profiles|Public Avatar")
%p
- if @user.avatar?
- You can change your avatar here
- if gravatar_enabled?
- or remove the current avatar to revert to #{link_to Gitlab.config.gravatar.host, 'https://' + Gitlab.config.gravatar.host}
+ = s_("Profiles|You can change your avatar here or remove the current avatar to revert to %{gravatar_link}").html_safe % { gravatar_link: gravatar_link }
+ - else
+ = s_("Profiles|You can change your avatar here")
- else
- You can upload an avatar here
- if gravatar_enabled?
- or change it at #{link_to Gitlab.config.gravatar.host, 'https://' + Gitlab.config.gravatar.host}
+ = s_("Profiles|You can upload your avatar here or change it at %{gravatar_link}").html_safe % { gravatar_link: gravatar_link }
+ - else
+ = s_("Profiles|You can upload your avatar here")
.col-lg-8
.clearfix.avatar-image.append-bottom-default
= link_to avatar_icon_for_user(@user, 400), target: '_blank', rel: 'noopener noreferrer' do
= image_tag avatar_icon_for_user(@user, 160), alt: '', class: 'avatar s160'
- %h5.prepend-top-0= _("Upload new avatar")
+ %h5.prepend-top-0= s_("Profiles|Upload new avatar")
.prepend-top-5.append-bottom-10
- %button.btn.js-choose-user-avatar-button{ type: 'button' }= _("Choose file...")
- %span.avatar-file-name.prepend-left-default.js-avatar-filename= _("No file chosen")
+ %button.btn.js-choose-user-avatar-button{ type: 'button' }= s_("Profiles|Choose file...")
+ %span.avatar-file-name.prepend-left-default.js-avatar-filename= s_("Profiles|No file chosen")
= f.file_field_without_bootstrap :avatar, class: 'js-user-avatar-input hidden', accept: 'image/*'
- .form-text.text-muted= _("The maximum file size allowed is 200KB.")
+ .form-text.text-muted= s_("Profiles|The maximum file size allowed is 200KB.")
- if @user.avatar?
%hr
- = link_to _('Remove avatar'), profile_avatar_path, data: { confirm: _('Avatar will be removed. Are you sure?') }, method: :delete, class: 'btn btn-danger btn-inverted'
+ = link_to s_("Profiles|Remove avatar"), profile_avatar_path, data: { confirm: s_("Profiles|Avatar will be removed. Are you sure?") }, method: :delete, class: 'btn btn-danger btn-inverted'
%hr
.row
.col-lg-4.profile-settings-sidebar
- %h4.prepend-top-0= s_("User|Current status")
+ %h4.prepend-top-0= s_("Profiles|Current status")
%p= s_("Profiles|This emoji and message will appear on your profile and throughout the interface.")
.col-lg-8
= f.fields_for :status, @user.status do |status_form|
@@ -66,62 +69,69 @@
.row
.col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
- Main settings
+ = s_("Profiles|Main settings")
%p
- This information will appear on your profile.
+ = s_("Profiles|This information will appear on your profile.")
- if current_user.ldap_user?
- Some options are unavailable for LDAP accounts
+ = s_("Profiles|Some options are unavailable for LDAP accounts")
.col-lg-8
.row
- if @user.read_only_attribute?(:name)
= f.text_field :name, required: true, readonly: true, wrapper: { class: 'col-md-9' },
- help: "Your name was automatically set based on your #{ attribute_provider_label(:name) } account, so people you know can recognize you."
+ help: s_("Profiles|Your name was automatically set based on your %{provider_label} account, so people you know can recognize you.") % { provider_label: attribute_provider_label(:name) }
- else
- = f.text_field :name, required: true, wrapper: { class: 'col-md-9' }, help: "Enter your name, so people you know can recognize you."
+ = f.text_field :name, label: 'Full name', required: true, wrapper: { class: 'col-md-9' }, help: "Enter your name, so people you know can recognize you."
= f.text_field :id, readonly: true, label: 'User ID', wrapper: { class: 'col-md-3' }
- if @user.read_only_attribute?(:email)
- = f.text_field :email, required: true, readonly: true, help: "Your email address was automatically set based on your #{ attribute_provider_label(:email) } account."
+ = f.text_field :email, required: true, readonly: true, help: s_("Profiles|Your email address was automatically set based on your %{provider_label} account.") % { provider_label: attribute_provider_label(:email) }
- else
= f.text_field :email, required: true, value: (@user.email unless @user.temp_oauth_email?),
help: user_email_help_text(@user)
= f.select :public_email, options_for_select(@user.all_emails, selected: @user.public_email),
- { help: 'This email will be displayed on your public profile.', include_blank: 'Do not show on profile' },
+ { help: s_("Profiles|This email will be displayed on your public profile."), include_blank: s_("Profiles|Do not show on profile") },
+ control_class: 'select2'
+ = f.select :commit_email, options_for_select(@user.verified_emails, selected: @user.commit_email),
+ { help: 'This email will be used for web based operations, such as edits and merges.' },
control_class: 'select2'
= f.select :preferred_language, Gitlab::I18n::AVAILABLE_LANGUAGES.map { |value, label| [label, value] },
- { help: 'This feature is experimental and translations are not complete yet.' },
+ { help: s_("Profiles|This feature is experimental and translations are not complete yet.") },
control_class: 'select2'
= f.text_field :skype
= f.text_field :linkedin
= f.text_field :twitter
- = f.text_field :website_url, label: 'Website'
+ = f.text_field :website_url, label: s_("Profiles|Website")
- if @user.read_only_attribute?(:location)
- = f.text_field :location, readonly: true, help: "Your location was automatically set based on your #{ attribute_provider_label(:location) } account."
+ = f.text_field :location, readonly: true, help: s_("Profiles|Your location was automatically set based on your %{provider_label} account.") % { provider_label: attribute_provider_label(:location) }
- else
= f.text_field :location
= f.text_field :organization
- = f.text_area :bio, rows: 4, maxlength: 250, help: 'Tell us about yourself in fewer than 250 characters.'
+ = f.text_area :bio, rows: 4, maxlength: 250, help: s_("Profiles|Tell us about yourself in fewer than 250 characters.")
%hr
- %h5 Private profile
+ %h5= ("Private profile")
- private_profile_label = capture do
- Don't display activity-related personal information on your profile
+ = s_("Profiles|Don't display activity-related personal information on your profiles")
= link_to icon('question-circle'), help_page_path('user/profile/index.md', anchor: 'private-profile')
= f.check_box :private_profile, label: private_profile_label
+ %h5= s_("Profiles|Private contributions")
+ = f.check_box :include_private_contributions, label: 'Include private contributions on my profile'
+ .help-block
+ = s_("Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information.")
.prepend-top-default.append-bottom-default
- = f.submit 'Update profile settings', class: 'btn btn-success'
- = link_to 'Cancel', user_path(current_user), class: 'btn btn-cancel'
+ = f.submit s_("Profiles|Update profile settings"), class: 'btn btn-success'
+ = link_to _("Cancel"), user_path(current_user), class: 'btn btn-cancel'
.modal.modal-profile-crop
.modal-dialog
.modal-content
.modal-header
%h4.modal-title
- Position and size your new avatar
- %button.close{ type: "button", "data-dismiss": "modal", "aria-label" => _('Close') }
+ = s_("Profiles|Position and size your new avatar")
+ %button.close{ type: "button", "data-dismiss": "modal", "aria-label" => _("Close") }
%span{ "aria-hidden": true } &times;
.modal-body
.profile-crop-image-container
- %img.modal-profile-crop-image{ alt: 'Avatar cropper' }
+ %img.modal-profile-crop-image{ alt: s_("Profiles|Avatar cropper") }
.crop-controls
.btn-group
%button.btn.btn-primary{ data: { method: 'zoom', option: '0.1' } }
@@ -130,4 +140,4 @@
%span.fa.fa-search-minus
.modal-footer
%button.btn.btn-primary.js-upload-user-avatar{ type: 'button' }
- Set new profile picture
+ = s_("Profiles|Set new profile picture")
diff --git a/app/views/profiles/two_factor_auths/_codes.html.haml b/app/views/profiles/two_factor_auths/_codes.html.haml
index 93722d7b034..fb4fff12027 100644
--- a/app/views/profiles/two_factor_auths/_codes.html.haml
+++ b/app/views/profiles/two_factor_auths/_codes.html.haml
@@ -10,4 +10,6 @@
%li
%span.monospace= code
-= link_to 'Proceed', profile_account_path, class: 'btn btn-success'
+.d-flex
+ = link_to 'Proceed', profile_account_path, class: 'btn btn-success append-right-10'
+ = link_to 'Download codes', "data:text/plain;charset=utf-8,#{CGI.escape(@codes.join("\n"))}", download: "gitlab-recovery-codes.txt", class: 'btn btn-default'
diff --git a/app/views/projects/_commit_button.html.haml b/app/views/projects/_commit_button.html.haml
index b387e38c1a6..1e27c71d20d 100644
--- a/app/views/projects/_commit_button.html.haml
+++ b/app/views/projects/_commit_button.html.haml
@@ -1,5 +1,5 @@
.form-actions
- = button_tag 'Commit changes', class: 'btn commit-btn js-commit-button btn-create'
+ = button_tag 'Commit changes', class: 'btn commit-btn js-commit-button btn-success'
= link_to 'Cancel', cancel_path,
class: 'btn btn-cancel', data: {confirm: leave_edit_message}
diff --git a/app/views/projects/_flash_messages.html.haml b/app/views/projects/_flash_messages.html.haml
index 0175b519867..7a5fff96676 100644
--- a/app/views/projects/_flash_messages.html.haml
+++ b/app/views/projects/_flash_messages.html.haml
@@ -5,3 +5,4 @@
- if current_user && can?(current_user, :download_code, project)
= render 'shared/no_ssh'
= render 'shared/no_password'
+ = render 'shared/auto_devops_implicitly_enabled_banner', project: project
diff --git a/app/views/projects/_fork_suggestion.html.haml b/app/views/projects/_fork_suggestion.html.haml
index c855bfaf067..0b616a0c1ce 100644
--- a/app/views/projects/_fork_suggestion.html.haml
+++ b/app/views/projects/_fork_suggestion.html.haml
@@ -6,6 +6,6 @@
edit
files in this project directly. Please fork this project,
make your changes there, and submit a merge request.
- = link_to 'Fork', nil, method: :post, class: 'js-fork-suggestion-button btn btn-grouped btn-inverted btn-new'
+ = link_to 'Fork', nil, method: :post, class: 'js-fork-suggestion-button btn btn-grouped btn-inverted btn-success'
%button.js-cancel-fork-suggestion-button.btn.btn-grouped{ type: 'button' }
Cancel
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index 1b6c4193c4d..ced6a2a0399 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -1,16 +1,35 @@
- empty_repo = @project.empty_repo?
-.project-home-panel.text-center{ class: ("empty-project" if empty_repo) }
+- license = @project.license_anchor_data
+.project-home-panel{ class: ("empty-project" if empty_repo) }
.limit-container-width{ class: container_class }
- .avatar-container.s70.project-avatar
- = project_icon(@project, alt: @project.name, class: 'avatar s70 avatar-tile', width: 70, height: 70)
- %h1.project-title.qa-project-name
- = @project.name
- %span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@project) }
- = visibility_level_icon(@project.visibility_level, fw: false)
+ .project-header.d-flex.flex-row.flex-wrap.align-items-center.append-bottom-8
+ .project-title-row.d-flex.align-items-center
+ .avatar-container.project-avatar.float-none
+ = project_icon(@project, alt: @project.name, class: 'avatar avatar-tile')
+ %h1.project-title.d-flex.align-items-baseline.qa-project-name
+ = @project.name
+ .project-metadata.d-flex.flex-row.flex-wrap.align-items-baseline
+ .project-visibility.d-inline-flex.align-items-baseline.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@project) }
+ = visibility_level_icon(@project.visibility_level, fw: false, options: {class: 'icon'})
+ = visibility_level_label(@project.visibility_level)
+ - if license.present?
+ .project-license.d-inline-flex.align-items-baseline
+ = link_to_if license.link, sprite_icon('scale', size: 16, css_class: 'icon') + license.label, license.link, class: license.enabled ? 'btn btn-link btn-secondary-hover-link' : 'btn btn-link'
+ - if @project.tag_list.present?
+ .project-tag-list.d-inline-flex.align-items-baseline.has-tooltip{ data: { container: 'body' }, title: @project.has_extra_tags? ? @project.tag_list.join(', ') : nil }
+ = sprite_icon('tag', size: 16, css_class: 'icon')
+ = @project.tags_to_show
+ - if @project.has_extra_tags?
+ = _("+ %{count} more") % { count: @project.count_of_extra_tags_not_shown }
.project-home-desc
- if @project.description.present?
- = markdown_field(@project, :description)
+ .project-description
+ .project-description-markdown.read-more-container
+ = markdown_field(@project, :description)
+ %button.btn.btn-blank.btn-link.text-secondary.js-read-more-trigger.text-secondary.d-lg-none{ type: "button" }
+ = _("Read more")
+
- if can?(current_user, :read_project, @project)
.text-secondary.prepend-top-8
= s_('ProjectPage|Project ID: %{project_id}') % { project_id: @project.id }
@@ -25,34 +44,42 @@
- deleted_message = s_('ForkedFromProjectPath|Forked from %{project_name} (deleted)')
= deleted_message % { project_name: fork_source_name(@project) }
- .project-badges.prepend-top-default.append-bottom-default
- - @project.badges.each do |badge|
- %a.append-right-8{ href: badge.rendered_link_url(@project),
- target: '_blank',
- rel: 'noopener noreferrer' }>
- %img.project-badge{ src: badge.rendered_image_url(@project),
- 'aria-hidden': true,
- alt: '' }>
-
- .project-repo-buttons
- .count-buttons
+ - if @project.badges.present?
+ .project-badges.prepend-top-default.append-bottom-default
+ - @project.badges.each do |badge|
+ %a.append-right-8{ href: badge.rendered_link_url(@project),
+ target: '_blank',
+ rel: 'noopener noreferrer' }>
+ %img.project-badge{ src: badge.rendered_image_url(@project),
+ 'aria-hidden': true,
+ alt: 'Project badge' }>
+
+ .project-repo-buttons.d-inline-flex.flex-wrap
+ .count-buttons.d-inline-flex
= render 'projects/buttons/star'
= render 'projects/buttons/fork'
- %span.d-none.d-sm-inline
- - if can?(current_user, :download_code, @project)
- .project-clone-holder
- = render "shared/clone_panel"
+ - if can?(current_user, :download_code, @project)
+ .project-clone-holder.d-inline-flex.d-sm-none
+ = render "shared/mobile_clone_panel"
- - if show_xcode_link?(@project)
- .project-action-button.project-xcode.inline
- = render "projects/buttons/xcode_link"
+ .project-clone-holder.d-none.d-sm-inline-flex
+ = render "shared/clone_panel"
- - if current_user
- - if can?(current_user, :download_code, @project)
+ - if show_xcode_link?(@project)
+ .project-action-button.project-xcode.inline
+ = render "projects/buttons/xcode_link"
+
+ - if current_user
+ - if can?(current_user, :download_code, @project)
+ .d-none.d-sm-inline-flex
= render 'projects/buttons/download', project: @project, ref: @ref
+ .d-none.d-sm-inline-flex
= render 'projects/buttons/dropdown'
+ .d-none.d-sm-inline-flex
= render 'projects/buttons/koding'
+ .d-none.d-sm-inline-flex
= render 'shared/notifications/button', notification_setting: @notification_setting
+ .d-none.d-sm-inline-flex
= render 'shared/members/access_request_buttons', source: @project
diff --git a/app/views/projects/_import_project_pane.html.haml b/app/views/projects/_import_project_pane.html.haml
index 70e1c557547..32da38f14b9 100644
--- a/app/views/projects/_import_project_pane.html.haml
+++ b/app/views/projects/_import_project_pane.html.haml
@@ -63,4 +63,4 @@
= form_for @project, html: { class: 'new_project' } do |f|
%hr
= render "shared/import_form", f: f
- = render 'new_project_fields', f: f, project_name_id: "import-url-name"
+ = render 'new_project_fields', f: f, project_name_id: "import-url-name", hide_init_with_readme: true
diff --git a/app/views/projects/_new_project_fields.html.haml b/app/views/projects/_new_project_fields.html.haml
index ad8c7911fad..db07c475866 100644
--- a/app/views/projects/_new_project_fields.html.haml
+++ b/app/views/projects/_new_project_fields.html.haml
@@ -1,12 +1,16 @@
- visibility_level = params.dig(:project, :visibility_level) || default_project_visibility
- ci_cd_only = local_assigns.fetch(:ci_cd_only, false)
+- hide_init_with_readme = local_assigns.fetch(:hide_init_with_readme, false)
.row{ id: project_name_id }
= f.hidden_field :ci_cd_only, value: ci_cd_only
+ .form-group.project-name.col-sm-12
+ = f.label :name, class: 'label-bold' do
+ %span= _("Project name")
+ = f.text_field :name, placeholder: "My awesome project", class: "form-control input-lg", autofocus: true, required: true
.form-group.project-path.col-sm-6
= f.label :namespace_id, class: 'label-bold' do
- %span
- Project path
+ %span= s_("Project URL")
.input-group
- if current_user.can_select_namespace?
.input-group-prepend.has-tooltip{ title: root_url }
@@ -27,13 +31,12 @@
= f.hidden_field :namespace_id, value: current_user.namespace_id
.form-group.project-path.col-sm-6
= f.label :path, class: 'label-bold' do
- %span
- Project name
- = f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 2, autofocus: true, required: true
+ %span= _("Project slug")
+ = f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 2, required: true
- if current_user.can_create_group?
.form-text.text-muted
Want to house several dependent projects under the same namespace?
- = link_to "Create a group", new_group_path
+ = link_to "Create a group.", new_group_path
.form-group
= f.label :description, class: 'label-bold' do
@@ -46,15 +49,16 @@
= link_to icon('question-circle'), help_page_path("public_access/public_access"), aria: { label: 'Documentation for Visibility Level' }, target: '_blank', rel: 'noopener noreferrer'
= render 'shared/visibility_level', f: f, visibility_level: visibility_level.to_i, can_change_visibility_level: true, form_model: @project, with_label: false
-.form-group.row.initialize-with-readme-setting
- %div{ :class => "col-sm-12" }
- .form-check
- = check_box_tag 'project[initialize_with_readme]', '1', false, class: 'form-check-input'
- = label_tag 'project[initialize_with_readme]', class: 'form-check-label' do
- .option-title
- %strong Initialize repository with a README
- .option-description
- Allows you to immediately clone this project’s repository. Skip this if you plan to push up an existing repository.
+- if !hide_init_with_readme
+ .form-group.row.initialize-with-readme-setting
+ %div{ :class => "col-sm-12" }
+ .form-check
+ = check_box_tag 'project[initialize_with_readme]', '1', false, class: 'form-check-input'
+ = label_tag 'project[initialize_with_readme]', class: 'form-check-label' do
+ .option-title
+ %strong Initialize repository with a README
+ .option-description
+ Allows you to immediately clone this project’s repository. Skip this if you plan to push up an existing repository.
-= f.submit 'Create project', class: "btn btn-create project-submit", tabindex: 4
+= f.submit 'Create project', class: "btn btn-success project-submit", tabindex: 4
= link_to 'Cancel', dashboard_projects_path, class: 'btn btn-cancel'
diff --git a/app/views/projects/_project_templates.html.haml b/app/views/projects/_project_templates.html.haml
index e90a6355214..0f6f3ad6d5e 100644
--- a/app/views/projects/_project_templates.html.haml
+++ b/app/views/projects/_project_templates.html.haml
@@ -5,4 +5,4 @@
.project-fields-form
= render 'projects/project_templates/project_fields_form'
- = render 'projects/new_project_fields', f: f, project_name_id: "template-project-name"
+ = render 'projects/new_project_fields', f: f, project_name_id: "template-project-name", hide_init_with_readme: true
diff --git a/app/views/projects/_readme.html.haml b/app/views/projects/_readme.html.haml
index 705338c083e..32624ac225b 100644
--- a/app/views/projects/_readme.html.haml
+++ b/app/views/projects/_readme.html.haml
@@ -20,4 +20,4 @@
distributed with computer software, forming part of its documentation.
GitLab will render it here instead of this message.
%p
- = link_to "Add Readme", @project.add_readme_path, class: 'btn btn-new'
+ = link_to "Add Readme", @project.add_readme_path, class: 'btn btn-success'
diff --git a/app/views/projects/_stat_anchor_list.html.haml b/app/views/projects/_stat_anchor_list.html.haml
index 15ec58289e3..4cf49f3cf62 100644
--- a/app/views/projects/_stat_anchor_list.html.haml
+++ b/app/views/projects/_stat_anchor_list.html.haml
@@ -1,7 +1,7 @@
- anchors = local_assigns.fetch(:anchors, [])
- return unless anchors.any?
-%ul.nav.justify-content-center
+%ul.nav
- anchors.each do |anchor|
%li.nav-item
= link_to_if anchor.link, anchor.label, anchor.link, class: anchor.enabled ? 'nav-link stat-link' : "nav-link btn btn-#{anchor.class_modifier || 'missing'}" do
diff --git a/app/views/projects/_wiki.html.haml b/app/views/projects/_wiki.html.haml
index 5646dc464f8..5adca007f7e 100644
--- a/app/views/projects/_wiki.html.haml
+++ b/app/views/projects/_wiki.html.haml
@@ -2,7 +2,7 @@
%div{ class: container_class }
.prepend-top-default.append-bottom-default
.wiki
- = render_wiki_content(@wiki_home)
+ = render_wiki_content(@wiki_home, legacy_render_context(params))
- else
- can_create_wiki = can?(current_user, :create_wiki, @project)
.project-home-empty{ class: [('row-content-block' if can_create_wiki), ('content-block' unless can_create_wiki)] }
diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml
index a4b1b496b69..cf273aab108 100644
--- a/app/views/projects/blob/_blob.html.haml
+++ b/app/views/projects/blob/_blob.html.haml
@@ -5,6 +5,7 @@
%ul.blob-commit-info
= render 'projects/commits/commit', commit: @last_commit, project: @project, ref: @ref
+ = render_if_exists 'projects/blob/owners', blob: blob
= render "projects/blob/auxiliary_viewer", blob: blob
#blob-content-holder.blob-content-holder
diff --git a/app/views/projects/blob/_new_dir.html.haml b/app/views/projects/blob/_new_dir.html.haml
index 6f3a691518b..e9010dc63fc 100644
--- a/app/views/projects/blob/_new_dir.html.haml
+++ b/app/views/projects/blob/_new_dir.html.haml
@@ -15,7 +15,7 @@
= render 'shared/new_commit_form', placeholder: _("Add new directory")
.form-actions
- = submit_tag _("Create directory"), class: 'btn btn-create'
+ = submit_tag _("Create directory"), class: 'btn btn-success'
= link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
= render 'shared/projects/edit_information'
diff --git a/app/views/projects/blob/_upload.html.haml b/app/views/projects/blob/_upload.html.haml
index 0a5c73c9037..d2b3c8ef96b 100644
--- a/app/views/projects/blob/_upload.html.haml
+++ b/app/views/projects/blob/_upload.html.haml
@@ -20,7 +20,7 @@
= render 'shared/new_commit_form', placeholder: placeholder
.form-actions
- = button_tag class: 'btn btn-create btn-upload-file', id: 'submit-all', type: 'button' do
+ = button_tag class: 'btn btn-success btn-upload-file', id: 'submit-all', type: 'button' do
= icon('spin spinner', class: 'js-loading-icon hidden' )
= button_title
= link_to _("Cancel"), '#', class: "btn btn-cancel", "data-dismiss" => "modal"
diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml
index 27cf040da7c..fdab8a53b41 100644
--- a/app/views/projects/blob/edit.html.haml
+++ b/app/views/projects/blob/edit.html.haml
@@ -21,7 +21,7 @@
Write
%li
- = link_to '#preview', 'data-preview-url' => project_preview_blob_path(@project, @id) do
+ = link_to '#preview', 'data-preview-url' => project_preview_blob_path(@project, @id, legacy_render: params[:legacy_render]) do
= editing_preview_title(@blob.name)
= form_tag(project_update_blob_path(@project, @id), method: :put, class: 'js-quick-submit js-requires-input js-edit-blob-form', data: blob_editor_paths) do
diff --git a/app/views/projects/blob/preview.html.haml b/app/views/projects/blob/preview.html.haml
index da2cef17e8a..eb65cd90ea8 100644
--- a/app/views/projects/blob/preview.html.haml
+++ b/app/views/projects/blob/preview.html.haml
@@ -2,7 +2,7 @@
.diff-content
- if markup?(@blob.name)
.file-content.wiki
- = markup(@blob.name, @content)
+ = markup(@blob.name, @content, legacy_render_context(params))
- else
.file-content.code.js-syntax-highlight
- unless @diff_lines.empty?
diff --git a/app/views/projects/blob/viewers/_gitlab_ci_yml.html.haml b/app/views/projects/blob/viewers/_gitlab_ci_yml.html.haml
index 28c5be6ebf3..5be7cc7f25a 100644
--- a/app/views/projects/blob/viewers/_gitlab_ci_yml.html.haml
+++ b/app/views/projects/blob/viewers/_gitlab_ci_yml.html.haml
@@ -1,9 +1,9 @@
-- if viewer.valid?
+- if viewer.valid?(@project, @commit.sha)
= icon('check fw')
This GitLab CI configuration is valid.
- else
= icon('warning fw')
This GitLab CI configuration is invalid:
- = viewer.validation_message
+ = viewer.validation_message(@project, @commit.sha)
= link_to 'Learn more', help_page_path('ci/yaml/README')
diff --git a/app/views/projects/blob/viewers/_markup.html.haml b/app/views/projects/blob/viewers/_markup.html.haml
index 230305b488d..bd12cadf240 100644
--- a/app/views/projects/blob/viewers/_markup.html.haml
+++ b/app/views/projects/blob/viewers/_markup.html.haml
@@ -1,4 +1,6 @@
- blob = viewer.blob
-- rendered_markup = blob.rendered_markup if blob.respond_to?(:rendered_markup)
+- context = legacy_render_context(params)
+- unless context[:markdown_engine] == :redcarpet
+ - context[:rendered] = blob.rendered_markup if blob.respond_to?(:rendered_markup)
.file-content.wiki
- = markup(blob.name, blob.data, rendered: rendered_markup)
+ = markup(blob.name, blob.data, context)
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index d6568c9f64a..ca867961f6b 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -41,7 +41,7 @@
data: { confirm: s_('Branches|Deleting the merged branches cannot be undone. Are you sure?'),
container: 'body' } do
= s_('Branches|Delete merged branches')
- = link_to new_project_branch_path(@project), class: 'btn btn-create' do
+ = link_to new_project_branch_path(@project), class: 'btn btn-success' do
= s_('Branches|New branch')
- if can?(current_user, :admin_project, @project)
diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml
index 65b414c8af2..500536a5dbc 100644
--- a/app/views/projects/branches/new.html.haml
+++ b/app/views/projects/branches/new.html.haml
@@ -26,7 +26,7 @@
= render 'shared/ref_dropdown', dropdown_class: 'wide'
.form-text.text-muted Existing branch name, tag, or commit SHA
.form-actions
- = button_tag 'Create branch', class: 'btn btn-create', tabindex: 3
+ = button_tag 'Create branch', class: 'btn btn-success', tabindex: 3
= link_to 'Cancel', project_branches_path(@project), class: 'btn btn-cancel'
-# haml-lint:disable InlineJavaScript
%script#availableRefs{ type: "application/json" }= @project.repository.ref_names.to_json.html_safe
diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml
index f880556a9f7..8da27ca7cb3 100644
--- a/app/views/projects/buttons/_fork.html.haml
+++ b/app/views/projects/buttons/_fork.html.haml
@@ -1,17 +1,17 @@
- unless @project.empty_repo?
- if current_user && can?(current_user, :fork_project, @project)
- - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
- = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: _('Go to your fork'), class: 'btn has-tooltip' do
- = custom_icon('icon_fork')
- %span= s_('GoToYourFork|Fork')
- - else
- - can_create_fork = current_user.can?(:create_fork)
- = link_to new_project_fork_path(@project),
- class: "btn btn-default #{'has-tooltip disabled' unless can_create_fork}",
- title: (_('You have reached your project limit') unless can_create_fork) do
- = custom_icon('icon_fork')
- %span= s_('CreateNewFork|Fork')
- .count-with-arrow
- %span.arrow
- = link_to project_forks_path(@project), title: n_('Fork', 'Forks', @project.forks_count), class: 'count' do
- = @project.forks_count
+ .count-badge.d-inline-flex.align-item-stretch.append-right-8
+ %span.fork-count.count-badge-count.d-flex.align-items-center
+ = link_to project_forks_path(@project), title: n_(s_('ProjectOverview|Fork'), s_('ProjectOverview|Forks'), @project.forks_count), class: 'count' do
+ = @project.forks_count
+ - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
+ = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: s_('ProjectOverview|Go to your fork'), class: 'btn btn-default has-tooltip count-badge-button d-flex align-items-center fork-btn' do
+ = sprite_icon('fork', { css_class: 'icon' })
+ %span= s_('ProjectOverview|Fork')
+ - else
+ - can_create_fork = current_user.can?(:create_fork)
+ = link_to new_project_fork_path(@project),
+ class: "btn btn-default has-tooltip count-badge-button d-flex align-items-center fork-btn #{'has-tooltip disabled' unless can_create_fork}",
+ title: (s_('ProjectOverview|You have reached your project limit') unless can_create_fork) do
+ = sprite_icon('fork', { css_class: 'icon' })
+ %span= s_('ProjectOverview|Fork')
diff --git a/app/views/projects/buttons/_star.html.haml b/app/views/projects/buttons/_star.html.haml
index a2dc2730ecc..0d04ecb3a58 100644
--- a/app/views/projects/buttons/_star.html.haml
+++ b/app/views/projects/buttons/_star.html.haml
@@ -1,21 +1,19 @@
- if current_user
- %button.btn.btn-default.star-btn.toggle-star{ type: "button", data: { endpoint: toggle_star_project_path(@project, :json) } }>
- - if current_user.starred?(@project)
- = sprite_icon('star')
- %span.starred= _('Unstar')
- - else
- = sprite_icon('star-o')
- %span= s_('StarProject|Star')
- .count-with-arrow
- %span.arrow
- %span.count.star-count
+ .count-badge.d-inline-flex.align-item-stretch.append-right-8
+ %span.star-count.count-badge-count.d-flex.align-items-center
= @project.star_count
+ %button.count-badge-button.btn.btn-default.d-flex.align-items-center.star-btn.toggle-star{ type: "button", data: { endpoint: toggle_star_project_path(@project, :json) } }
+ - if current_user.starred?(@project)
+ = sprite_icon('star', { css_class: 'icon' })
+ %span.starred= s_('ProjectOverview|Unstar')
+ - else
+ = sprite_icon('star-o', { css_class: 'icon' })
+ %span= s_('ProjectOverview|Star')
- else
- = link_to new_user_session_path, class: 'btn has-tooltip star-btn', title: _('You must sign in to star a project') do
- = sprite_icon('star')
- #{ s_('StarProject|Star') }
- .count-with-arrow
- %span.arrow
- %span.count
+ .count-badge.d-inline-flex.align-item-stretch.append-right-8
+ %span.star-count.count-badge-count.d-flex.align-items-center
= @project.star_count
+ = link_to new_user_session_path, class: 'btn btn-default has-tooltip count-badge-button d-flex align-items-center star-btn', title: s_('ProjectOverview|You must sign in to star a project') do
+ = sprite_icon('star-o', { css_class: 'icon' })
+ %span= s_('ProjectOverview|Star')
diff --git a/app/views/projects/clusters/_banner.html.haml b/app/views/projects/clusters/_banner.html.haml
index 84362580a90..4616d1359b4 100644
--- a/app/views/projects/clusters/_banner.html.haml
+++ b/app/views/projects/clusters/_banner.html.haml
@@ -1,12 +1,12 @@
-%h4= s_('ClusterIntegration|Kubernetes cluster integration')
+.hidden.js-cluster-error.bs-callout.bs-callout-danger{ role: 'alert' }
+ = s_('ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine')
+ %p.js-error-reason
-.settings-content
- .hidden.js-cluster-error.alert.alert-danger.alert-block.append-bottom-10{ role: 'alert' }
- = s_('ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine')
- %p.js-error-reason
+.hidden.js-cluster-creating.bs-callout.bs-callout-info{ role: 'alert' }
+ = s_('ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine...')
- .hidden.js-cluster-creating.alert.alert-info.alert-block.append-bottom-10{ role: 'alert' }
- = s_('ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine...')
+.hidden.js-cluster-success.bs-callout.bs-callout-success{ role: 'alert' }
+ = s_("ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details")
- if show_cluster_security_warning?
.js-cluster-security-warning.alert.alert-block.alert-dismissable.bs-callout.bs-callout-warning
diff --git a/app/views/projects/clusters/_integration_form.html.haml b/app/views/projects/clusters/_integration_form.html.haml
index b46b45fea49..d0a553e3414 100644
--- a/app/views/projects/clusters/_integration_form.html.haml
+++ b/app/views/projects/clusters/_integration_form.html.haml
@@ -2,14 +2,6 @@
= form_errors(@cluster)
.form-group
%h5= s_('ClusterIntegration|Integration status')
- %p
- - if @cluster.enabled?
- - if can?(current_user, :update_cluster, @cluster)
- = s_('ClusterIntegration|Kubernetes cluster integration is enabled for this project. Disabling this integration will not affect your Kubernetes cluster, it will only temporarily turn off GitLab\'s connection to it.')
- - else
- = s_('ClusterIntegration|Kubernetes cluster integration is enabled for this project.')
- - else
- = s_('ClusterIntegration|Kubernetes cluster integration is disabled for this project.')
%label.append-bottom-0.js-cluster-enable-toggle-area
%button{ type: 'button',
class: "js-project-feature-toggle project-feature-toggle #{'is-checked' if @cluster.enabled?} #{'is-disabled' unless can?(current_user, :update_cluster, @cluster)}",
@@ -19,14 +11,13 @@
%span.toggle-icon
= sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked')
= sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked')
+ .form-text.text-muted= s_('ClusterIntegration|Enable or disable GitLab\'s connection to your Kubernetes cluster.')
- if has_multiple_clusters?(@project)
.form-group
%h5= s_('ClusterIntegration|Environment scope')
- %p
- = s_("ClusterIntegration|Choose which of your project's environments will use this Kubernetes cluster.")
- = link_to s_("ClusterIntegration|Learn more about environments"), help_page_path('ci/environments')
- = field.text_field :environment_scope, class: 'form-control js-select-on-focus', placeholder: s_('ClusterIntegration|Environment scope')
+ = field.text_field :environment_scope, class: 'col-md-6 form-control js-select-on-focus', placeholder: s_('ClusterIntegration|Environment scope')
+ .form-text.text-muted= s_("ClusterIntegration|Choose which of your environments will use this cluster.")
- if can?(current_user, :update_cluster, @cluster)
.form-group
@@ -38,8 +29,3 @@
%code *
is the default environment scope for this cluster. This means that all jobs, regardless of their environment, will use this cluster.
= link_to 'More information', ('https://docs.gitlab.com/ee/user/project/clusters/#setting-the-environment-scope')
-
- %h5= s_('ClusterIntegration|Security')
- %p
- = s_("ClusterIntegration|The default cluster configuration grants access to a wide set of functionalities needed to successfully build and deploy a containerised application.")
- = link_to s_("ClusterIntegration|Learn more about security configuration"), help_page_path('user/project/clusters/index.md', anchor: 'security-implications')
diff --git a/app/views/projects/clusters/gcp/_form.html.haml b/app/views/projects/clusters/gcp/_form.html.haml
index 9133de6559d..0222bbf7338 100644
--- a/app/views/projects/clusters/gcp/_form.html.haml
+++ b/app/views/projects/clusters/gcp/_form.html.haml
@@ -61,5 +61,15 @@
%p.form-text.text-muted
= s_('ClusterIntegration|Learn more about %{help_link_start_machine_type}machine types%{help_link_end} and %{help_link_start_pricing}pricing%{help_link_end}.').html_safe % { help_link_start_machine_type: help_link_start % { url: machine_type_link_url }, help_link_start_pricing: help_link_start % { url: pricing_link_url }, help_link_end: help_link_end }
+ - if rbac_clusters_feature_enabled?
+ .form-group
+ .form-check
+ = provider_gcp_field.check_box :legacy_abac, { class: 'form-check-input' }, false, true
+ = provider_gcp_field.label :legacy_abac, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold'
+ .form-text.text-muted
+ = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).')
+ = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.')
+ = link_to _('More information'), help_page_path('user/project/clusters/index.md', anchor: 'role-based-access-control-rbac-experimental-support'), target: '_blank'
+
.form-group
= field.submit s_('ClusterIntegration|Create Kubernetes cluster'), class: 'js-gke-cluster-creation-submit btn btn-success', disabled: true
diff --git a/app/views/projects/clusters/gcp/_show.html.haml b/app/views/projects/clusters/gcp/_show.html.haml
index 877e0cc876c..be84f2ae67c 100644
--- a/app/views/projects/clusters/gcp/_show.html.haml
+++ b/app/views/projects/clusters/gcp/_show.html.haml
@@ -37,5 +37,14 @@
= platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)')
= platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace')
+ - if rbac_clusters_feature_enabled?
+ .form-group
+ .form-check
+ = platform_kubernetes_field.check_box :authorization_type, { class: 'form-check-input', disabled: true }, 'rbac', 'abac'
+ = platform_kubernetes_field.label :authorization_type, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold'
+ .form-text.text-muted
+ = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).')
+ = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.')
+
.form-group
= field.submit s_('ClusterIntegration|Save changes'), class: 'btn btn-success'
diff --git a/app/views/projects/clusters/show.html.haml b/app/views/projects/clusters/show.html.haml
index 08d2deff6f8..eddd3613c5f 100644
--- a/app/views/projects/clusters/show.html.haml
+++ b/app/views/projects/clusters/show.html.haml
@@ -23,7 +23,8 @@
.js-cluster-application-notice
.flash-container
- %section.settings.no-animate.expanded#cluster-integration
+ %section#cluster-integration
+ %h4= @cluster.name
= render 'banner'
= render 'integration_form'
diff --git a/app/views/projects/clusters/user/_form.html.haml b/app/views/projects/clusters/user/_form.html.haml
index e8ef0008802..f497f5b606c 100644
--- a/app/views/projects/clusters/user/_form.html.haml
+++ b/app/views/projects/clusters/user/_form.html.haml
@@ -25,5 +25,15 @@
= platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)'), class: 'label-bold'
= platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace')
+ - if rbac_clusters_feature_enabled?
+ .form-group
+ .form-check
+ = platform_kubernetes_field.check_box :authorization_type, { class: 'form-check-input' }, 'rbac', 'abac'
+ = platform_kubernetes_field.label :authorization_type, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold'
+ .form-text.text-muted
+ = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).')
+ = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.')
+ = link_to _('More information'), help_page_path('user/project/clusters/index.md', anchor: 'role-based-access-control-rbac-experimental-support'), target: '_blank'
+
.form-group
= field.submit s_('ClusterIntegration|Add Kubernetes cluster'), class: 'btn btn-success'
diff --git a/app/views/projects/clusters/user/_show.html.haml b/app/views/projects/clusters/user/_show.html.haml
index 20a07d6695e..56b597d295a 100644
--- a/app/views/projects/clusters/user/_show.html.haml
+++ b/app/views/projects/clusters/user/_show.html.haml
@@ -26,5 +26,14 @@
= platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)'), class: 'label-bold'
= platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace')
+ - if rbac_clusters_feature_enabled?
+ .form-group
+ .form-check
+ = platform_kubernetes_field.check_box :authorization_type, { class: 'form-check-input', disabled: true }, 'rbac', 'abac'
+ = platform_kubernetes_field.label :authorization_type, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold'
+ .form-text.text-muted
+ = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).')
+ = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.')
+
.form-group
= field.submit s_('ClusterIntegration|Save changes'), class: 'btn btn-success'
diff --git a/app/views/projects/commit/_change.html.haml b/app/views/projects/commit/_change.html.haml
index afd70ef5774..e71615dd1c5 100644
--- a/app/views/projects/commit/_change.html.haml
+++ b/app/views/projects/commit/_change.html.haml
@@ -33,7 +33,7 @@
- else
= hidden_field_tag 'create_merge_request', 1, id: nil
.form-actions
- = submit_tag label, class: 'btn btn-create'
+ = submit_tag label, class: 'btn btn-success'
= link_to _("Cancel"), '#', class: "btn btn-cancel", "data-dismiss" => "modal"
= render 'shared/projects/edit_information'
diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml
index 07112c98804..d24ee4a3251 100644
--- a/app/views/projects/compare/_form.html.haml
+++ b/app/views/projects/compare/_form.html.haml
@@ -22,7 +22,7 @@
.dropdown-toggle-text.str-truncated= params[:from] || _("Select branch/tag")
= render 'shared/ref_dropdown'
&nbsp;
- = button_tag s_("CompareBranches|Compare"), class: "btn btn-create commits-compare-btn"
+ = button_tag s_("CompareBranches|Compare"), class: "btn btn-success commits-compare-btn"
- if @merge_request.present?
= link_to _("View open merge request"), project_merge_request_path(@project, @merge_request), class: 'prepend-left-10 btn'
- elsif create_mr_button?
diff --git a/app/views/projects/deploy_keys/_form.html.haml b/app/views/projects/deploy_keys/_form.html.haml
index f8ab0c1ec54..568930595a2 100644
--- a/app/views/projects/deploy_keys/_form.html.haml
+++ b/app/views/projects/deploy_keys/_form.html.haml
@@ -21,4 +21,4 @@
Allow this key to push to repository as well? (Default only allows pull access.)
.form-group.row
- = f.submit "Add key", class: "btn-create btn"
+ = f.submit "Add key", class: "btn-success btn"
diff --git a/app/views/projects/deploy_keys/edit.html.haml b/app/views/projects/deploy_keys/edit.html.haml
index e009b6fef0e..3e7872ebc1c 100644
--- a/app/views/projects/deploy_keys/edit.html.haml
+++ b/app/views/projects/deploy_keys/edit.html.haml
@@ -6,5 +6,5 @@
= form_for [@project.namespace.becomes(Namespace), @project, @deploy_key], html: { class: 'js-requires-input' } do |f|
= render partial: 'shared/deploy_keys/form', locals: { form: f, deploy_key: @deploy_key }
.form-actions
- = f.submit 'Save changes', class: 'btn-save btn'
+ = f.submit 'Save changes', class: 'btn-success btn'
= link_to 'Cancel', project_settings_repository_path(@project), class: 'btn btn-cancel'
diff --git a/app/views/projects/diffs/_stats.html.haml b/app/views/projects/diffs/_stats.html.haml
index aa1112c3313..229a4574eeb 100644
--- a/app/views/projects/diffs/_stats.html.haml
+++ b/app/views/projects/diffs/_stats.html.haml
@@ -1,5 +1,5 @@
-- sum_added_lines = diff_files.sum(&:added_lines)
-- sum_removed_lines = diff_files.sum(&:removed_lines)
+- sum_added_lines = diff_files.sum(&:added_lines) # rubocop: disable CodeReuse/ActiveRecord
+- sum_removed_lines = diff_files.sum(&:removed_lines) # rubocop: disable CodeReuse/ActiveRecord
.commit-stat-summary.dropdown
Showing
%button.diff-stats-summary-toggler.js-diff-stats-dropdown{ type: "button", data: { toggle: "dropdown", display: "static" } }<
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index acdde9e0f70..96ab582b050 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -59,7 +59,7 @@
- if @project.avatar?
%hr
= link_to _('Remove avatar'), project_avatar_path(@project), data: { confirm: _("Avatar will be removed. Are you sure?") }, method: :delete, class: "btn btn-danger btn-inverted"
- = f.submit 'Save changes', class: "btn btn-success js-btn-save-general-project-settings"
+ = f.submit 'Save changes', class: "btn btn-success js-btn-success-general-project-settings"
%section.settings.sharing-permissions.no-animate#js-shared-permissions{ class: ('expanded' if expanded) }
.settings-header
@@ -75,7 +75,7 @@
-# haml-lint:disable InlineJavaScript
%script.js-project-permissions-form-data{ type: "application/json" }= project_permissions_panel_data_json(@project)
.js-project-permissions-form
- = f.submit 'Save changes', class: "btn btn-save"
+ = f.submit 'Save changes', class: "btn btn-success"
= render_if_exists 'projects/issues_settings'
@@ -93,7 +93,7 @@
= form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "merge-request-settings-form" }, authenticity_token: true do |f|
%input{ name: 'update_section', type: 'hidden', value: 'js-merge-request-settings' }
= render 'projects/merge_request_settings', form: f
- = f.submit 'Save changes', class: "btn btn-save qa-save-merge-request-changes"
+ = f.submit 'Save changes', class: "btn btn-success qa-save-merge-request-changes"
= render_if_exists 'projects/service_desk_settings'
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index d47dc3d8143..d104608b2fe 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -32,9 +32,13 @@
= _('Otherwise it is recommended you start with one of the options below.')
.prepend-top-20
-%nav.project-stats{ class: container_class }
- = render 'stat_anchor_list', anchors: @project.empty_repo_statistics_anchors
- = render 'stat_anchor_list', anchors: @project.empty_repo_statistics_buttons
+%nav.project-stats{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
+ .scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
+ .fade-left= icon('angle-left')
+ .fade-right= icon('angle-right')
+ .nav-links.scrolling-tabs
+ = render 'stat_anchor_list', anchors: @project.empty_repo_statistics_anchors
+ = render 'stat_anchor_list', anchors: @project.empty_repo_statistics_buttons
- if can?(current_user, :push_code, @project)
%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
@@ -42,7 +46,7 @@
.empty_wrapper
%h3#repo-command-line-instructions.page-title-empty
Command line instructions
- .git-empty
+ .git-empty.js-git-empty
%fieldset
%h5 Git global setup
%pre.bg-light
@@ -54,7 +58,7 @@
%h5 Create a new repository
%pre.bg-light
:preserve
- git clone #{ content_tag(:span, default_url_to_repo, class: 'clone')}
+ git clone #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
cd #{h @project.path}
touch README.md
git add README.md
@@ -69,7 +73,7 @@
:preserve
cd existing_folder
git init
- git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')}
+ git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
git add .
git commit -m "Initial commit"
- if @project.can_current_user_push_to_default_branch?
@@ -82,7 +86,7 @@
:preserve
cd existing_repo
git remote rename origin old-origin
- git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')}
+ git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
- if @project.can_current_user_push_to_default_branch?
%span><
git push -u origin --all
diff --git a/app/views/projects/environments/_form.html.haml b/app/views/projects/environments/_form.html.haml
index 0586dbdf0e2..f942b936037 100644
--- a/app/views/projects/environments/_form.html.haml
+++ b/app/views/projects/environments/_form.html.haml
@@ -18,5 +18,5 @@
= f.url_field :external_url, class: 'form-control'
.form-actions
- = f.submit 'Save', class: 'btn btn-save'
+ = f.submit 'Save', class: 'btn btn-success'
= link_to 'Cancel', project_environments_path(@project), class: 'btn btn-cancel'
diff --git a/app/views/projects/environments/terminal.html.haml b/app/views/projects/environments/terminal.html.haml
index 5b680189bc8..e40d631a1a1 100644
--- a/app/views/projects/environments/terminal.html.haml
+++ b/app/views/projects/environments/terminal.html.haml
@@ -2,7 +2,7 @@
- page_title "Terminal for environment", @environment.name
- content_for :page_specific_javascripts do
- = stylesheet_link_tag "xterm/xterm"
+ = stylesheet_link_tag "xterm.css"
%div{ class: container_class }
.top-area
diff --git a/app/views/projects/find_file/show.html.haml b/app/views/projects/find_file/show.html.haml
index a966bfb2dd9..996c7b1b960 100644
--- a/app/views/projects/find_file/show.html.haml
+++ b/app/views/projects/find_file/show.html.haml
@@ -13,6 +13,6 @@
.tree-content-holder
.table-holder
- %table.table.files-slider{ class: "table_#{@hex_path} tree-table table-striped" }
+ %table.table.files-slider{ class: "table_#{@hex_path} tree-table" }
%tbody
= spinner nil, true
diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml
index 57afc7ac9c3..b44ea89510b 100644
--- a/app/views/projects/forks/index.html.haml
+++ b/app/views/projects/forks/index.html.haml
@@ -30,11 +30,11 @@
- if current_user && can?(current_user, :fork_project, @project)
- if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
- = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn btn-new' do
+ = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn btn-success' do
= sprite_icon('fork', size: 12)
%span Fork
- else
- = link_to new_project_fork_path(@project), title: "Fork project", class: 'btn btn-new' do
+ = link_to new_project_fork_path(@project), title: "Fork project", class: 'btn btn-success' do
= sprite_icon('fork', size: 12)
%span Fork
diff --git a/app/views/projects/hooks/_index.html.haml b/app/views/projects/hooks/_index.html.haml
index 5990582fd55..0ab7863b77c 100644
--- a/app/views/projects/hooks/_index.html.haml
+++ b/app/views/projects/hooks/_index.html.haml
@@ -9,7 +9,7 @@
.col-lg-8.append-bottom-default
= form_for @hook, as: :hook, url: polymorphic_path([@project.namespace.becomes(Namespace), @project, :hooks]) do |f|
= render partial: 'shared/web_hooks/form', locals: { form: f, hook: @hook }
- = f.submit 'Add webhook', class: 'btn btn-create'
+ = f.submit 'Add webhook', class: 'btn btn-success'
%hr
%h5.prepend-top-default
diff --git a/app/views/projects/hooks/edit.html.haml b/app/views/projects/hooks/edit.html.haml
index c31aef60453..57311284e11 100644
--- a/app/views/projects/hooks/edit.html.haml
+++ b/app/views/projects/hooks/edit.html.haml
@@ -11,7 +11,7 @@
= form_for [@project.namespace.becomes(Namespace), @project, @hook], as: :hook, url: project_hook_path(@project, @hook) do |f|
= render partial: 'shared/web_hooks/form', locals: { form: f, hook: @hook }
- = f.submit 'Save changes', class: 'btn btn-create'
+ = f.submit 'Save changes', class: 'btn btn-success'
= render 'shared/web_hooks/test_button', triggers: ProjectHook.triggers, hook: @hook
= link_to 'Remove', project_hook_path(@project, @hook), method: :delete, class: 'btn btn-remove float-right', data: { confirm: 'Are you sure?' }
diff --git a/app/views/projects/imports/new.html.haml b/app/views/projects/imports/new.html.haml
index 8ce822c43b7..1c50cfbde85 100644
--- a/app/views/projects/imports/new.html.haml
+++ b/app/views/projects/imports/new.html.haml
@@ -16,4 +16,4 @@
= render "shared/import_form", f: f
.form-actions
- = f.submit 'Start import', class: "btn btn-create", tabindex: 4
+ = f.submit 'Start import', class: "btn btn-success", tabindex: 4
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index 8a14146cb87..31c72f2f759 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -2,9 +2,9 @@
.issue-box
- if @can_bulk_update
.issue-check.hidden
- = check_box_tag dom_id(issue, "selected"), nil, false, 'data-id' => issue.id, class: "selected_issue"
- .issue-info-container
- .issue-main-info
+ = check_box_tag dom_id(issue, "selected"), nil, false, 'data-id' => issue.id, class: "selected-issuable"
+ .issuable-info-container
+ .issuable-main-info
.issue-title.title
%span.issue-title-text
- if issue.confidential?
diff --git a/app/views/projects/issues/_nav_btns.html.haml b/app/views/projects/issues/_nav_btns.html.haml
index 0dd2d2e6c5d..e4a0d4b8479 100644
--- a/app/views/projects/issues/_nav_btns.html.haml
+++ b/app/views/projects/issues/_nav_btns.html.haml
@@ -6,6 +6,6 @@
= link_to "New issue", new_project_issue_path(@project,
issue: { assignee_id: finder.assignee.try(:id),
milestone_id: finder.milestones.first.try(:id) }),
- class: "btn btn-new",
+ class: "btn btn-success",
title: "New issue",
id: "new_issue_link"
diff --git a/app/views/projects/issues/index.atom.builder b/app/views/projects/issues/index.atom.builder
index 6330245954e..6566866be82 100644
--- a/app/views/projects/issues/index.atom.builder
+++ b/app/views/projects/issues/index.atom.builder
@@ -1,3 +1,4 @@
+# rubocop: disable CodeReuse/ActiveRecord
xml.title "#{@project.name} issues"
xml.link href: url_for(safe_params), rel: "self", type: "application/atom+xml"
xml.link href: project_issues_url(@project), rel: "alternate", type: "text/html"
@@ -5,3 +6,4 @@ xml.id project_issues_url(@project)
xml.updated @issues.first.updated_at.xmlschema if @issues.reorder(nil).any?
xml << render(partial: 'issues/issue', collection: @issues) if @issues.reorder(nil).any?
+# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index b81d1a188f0..c39fd0063be 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -55,7 +55,7 @@
- if can_report_spam
= link_to 'Submit as spam', mark_as_spam_project_issue_path(@project, @issue), method: :post, class: 'd-none d-sm-none d-md-block btn btn-grouped btn-spam', title: 'Submit as spam'
- if can_create_issue
- = link_to new_project_issue_path(@project), class: 'd-none d-sm-none d-md-block btn btn-grouped new-issue-link btn-new btn-inverted', title: 'New issue', id: 'new_issue_link' do
+ = link_to new_project_issue_path(@project), class: 'd-none d-sm-none d-md-block btn btn-grouped new-issue-link btn-success btn-inverted', title: 'New issue', id: 'new_issue_link' do
New issue
.issue-details.issuable-details
diff --git a/app/views/projects/jobs/_header.html.haml b/app/views/projects/jobs/_header.html.haml
index b83e8dddccb..e7245622b80 100644
--- a/app/views/projects/jobs/_header.html.haml
+++ b/app/views/projects/jobs/_header.html.haml
@@ -24,7 +24,7 @@
- if show_controls
.nav-controls
- if can?(current_user, :create_issue, @project) && @build.failed?
- = link_to "New issue", new_project_issue_path(@project, issue: build_failed_issue_options), class: 'btn btn-new btn-inverted'
+ = link_to "New issue", new_project_issue_path(@project, issue: build_failed_issue_options), class: 'btn btn-success btn-inverted'
- if can?(current_user, :update_build, @build) && @build.retryable?
= link_to "Retry job", retry_project_job_path(@project, @build), class: 'btn btn-inverted-secondary', method: :post
%button.btn.btn-default.float-right.d-block.d-sm-none.d-md-none.build-gutter-toggle.js-sidebar-build-toggle{ role: "button", type: "button" }
diff --git a/app/views/projects/jobs/index.html.haml b/app/views/projects/jobs/index.html.haml
index fe1c338b634..59592abcf6a 100644
--- a/app/views/projects/jobs/index.html.haml
+++ b/app/views/projects/jobs/index.html.haml
@@ -8,7 +8,7 @@
.nav-controls
- if can?(current_user, :update_build, @project)
- - if @all_builds.running_or_pending.limit(1).any?
+ - if @all_builds.running_or_pending.limit(1).any? # rubocop: disable CodeReuse/ActiveRecord
= link_to 'Cancel running', cancel_all_project_jobs_path(@project),
data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml
index 078f40c4477..5321bc46e73 100644
--- a/app/views/projects/jobs/show.html.haml
+++ b/app/views/projects/jobs/show.html.haml
@@ -3,6 +3,9 @@
- breadcrumb_title "##{@build.id}"
- page_title "#{@build.name} (##{@build.id})", "Jobs"
+- content_for :page_specific_javascripts do
+ = stylesheet_link_tag 'page_bundles/xterm'
+
%div{ class: container_class }
.build-page.js-build-page
#js-build-header-vue
@@ -10,7 +13,7 @@
- unless @build.any_runners_online?
.bs-callout.bs-callout-warning.js-build-stuck
%p
- - if no_runners_for_project?(@build.project)
+ - if @project.any_runners?
This job is stuck, because the project doesn't have any runners online assigned to it.
- elsif @build.tags.any?
This job is stuck, because you don't have any active runners online with any of these tags assigned to them:
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index dfac62e7985..683dda4f166 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -6,7 +6,7 @@
- if can_admin_label
- content_for(:header_content) do
.nav-controls
- = link_to _('New label'), new_project_label_path(@project), class: "btn btn-new"
+ = link_to _('New label'), new_project_label_path(@project), class: "btn btn-success"
- if @labels.exists? || @prioritized_labels.exists? || search.present?
#promote-label-modal
@@ -22,6 +22,7 @@
%span.input-group-append
%button.btn.btn-default{ type: "submit", "aria-label" => _('Submit search') }
= icon("search")
+ = render 'shared/labels/sort_dropdown'
.labels-container.prepend-top-10
- if can_admin_label
diff --git a/app/views/projects/mattermosts/_team_selection.html.haml b/app/views/projects/mattermosts/_team_selection.html.haml
index 37c09f12f63..d0a7f89df31 100644
--- a/app/views/projects/mattermosts/_team_selection.html.haml
+++ b/app/views/projects/mattermosts/_team_selection.html.haml
@@ -43,4 +43,4 @@
.clearfix
.float-right
= link_to 'Cancel', edit_project_service_path(@project, @service), class: 'btn btn-lg'
- = f.submit 'Install', class: 'btn btn-save btn-lg'
+ = f.submit 'Install', class: 'btn btn-success btn-lg'
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index cd3d896fff2..faa070d0389 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -1,10 +1,10 @@
%li{ id: dom_id(merge_request), class: mr_css_classes(merge_request), data: { labels: merge_request.label_ids, id: merge_request.id } }
- if @can_bulk_update
.issue-check.hidden
- = check_box_tag dom_id(merge_request, "selected"), nil, false, 'data-id' => merge_request.id, class: "selected_issue"
+ = check_box_tag dom_id(merge_request, "selected"), nil, false, 'data-id' => merge_request.id, class: "selected-issuable"
- .issue-info-container
- .issue-main-info
+ .issuable-info-container
+ .issuable-main-info
.merge-request-title.title
%span.merge-request-title-text
= link_to merge_request.title, merge_request_path(merge_request)
diff --git a/app/views/projects/merge_requests/_nav_btns.html.haml b/app/views/projects/merge_requests/_nav_btns.html.haml
index e73dab8ad4a..b7498216334 100644
--- a/app/views/projects/merge_requests/_nav_btns.html.haml
+++ b/app/views/projects/merge_requests/_nav_btns.html.haml
@@ -1,5 +1,5 @@
- if @can_bulk_update
= button_tag "Edit merge requests", class: "btn append-right-10 js-bulk-update-toggle"
- if merge_project
- = link_to new_merge_request_path, class: "btn btn-new", title: "New merge request" do
+ = link_to new_merge_request_path, class: "btn btn-success", title: "New merge request" do
New merge request
diff --git a/app/views/projects/merge_requests/creations/_new_compare.html.haml b/app/views/projects/merge_requests/creations/_new_compare.html.haml
index afa7eb06cb4..1fd71a38472 100644
--- a/app/views/projects/merge_requests/creations/_new_compare.html.haml
+++ b/app/views/projects/merge_requests/creations/_new_compare.html.haml
@@ -61,4 +61,4 @@
- if @merge_request.errors.any?
= form_errors(@merge_request)
- = f.submit 'Compare branches and continue', class: "btn btn-new mr-compare-btn"
+ = f.submit 'Compare branches and continue', class: "btn btn-success mr-compare-btn"
diff --git a/app/views/projects/merge_requests/diffs/_diffs.html.haml b/app/views/projects/merge_requests/diffs/_diffs.html.haml
index bf3df0abf86..9ebd91dea0b 100644
--- a/app/views/projects/merge_requests/diffs/_diffs.html.haml
+++ b/app/views/projects/merge_requests/diffs/_diffs.html.haml
@@ -14,7 +14,7 @@
%span.ref-name= @merge_request.source_branch
and
%span.ref-name= @merge_request.target_branch
- .text-center= link_to 'Create commit', project_new_blob_path(@project, @merge_request.source_branch), class: 'btn btn-save'
+ .text-center= link_to 'Create commit', project_new_blob_path(@project, @merge_request.source_branch), class: 'btn btn-success'
- else
- diff_viewable = @merge_request_diff ? @merge_request_diff.viewable? : true
- if diff_viewable
diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml
index 28f0a167128..ebd3229e42b 100644
--- a/app/views/projects/milestones/_form.html.haml
+++ b/app/views/projects/milestones/_form.html.haml
@@ -20,8 +20,8 @@
.form-actions
- if @milestone.new_record?
- = f.submit 'Create milestone', class: "btn-create btn qa-milestone-create-button"
+ = f.submit 'Create milestone', class: "btn-success btn qa-milestone-create-button"
= link_to "Cancel", project_milestones_path(@project), class: "btn btn-cancel"
- else
- = f.submit 'Save changes', class: "btn-save btn"
+ = f.submit 'Save changes', class: "btn-success btn"
= link_to "Cancel", project_milestone_path(@project, @milestone), class: "btn btn-cancel"
diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml
index 26d2ea8447b..57f3c640696 100644
--- a/app/views/projects/milestones/index.html.haml
+++ b/app/views/projects/milestones/index.html.haml
@@ -8,7 +8,7 @@
.nav-controls
= render 'shared/milestones_sort_dropdown'
- if can?(current_user, :admin_milestone, @project)
- = link_to new_project_milestone_path(@project), class: "btn btn-new qa-new-project-milestone", title: 'New milestone' do
+ = link_to new_project_milestone_path(@project), class: "btn btn-success qa-new-project-milestone", title: 'New milestone' do
New milestone
.milestones
diff --git a/app/views/projects/mirrors/_mirror_repos.html.haml b/app/views/projects/mirrors/_mirror_repos.html.haml
index c6764c7607a..d523df1cd90 100644
--- a/app/views/projects/mirrors/_mirror_repos.html.haml
+++ b/app/views/projects/mirrors/_mirror_repos.html.haml
@@ -32,7 +32,7 @@
= 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
+ = f.submit _('Mirror repository'), class: 'btn btn-success', name: :update_remote_mirror
.panel.panel-default
.table-responsive
diff --git a/app/views/projects/pages/show.html.haml b/app/views/projects/pages/show.html.haml
index 7e1a3b9bea6..88ab486a248 100644
--- a/app/views/projects/pages/show.html.haml
+++ b/app/views/projects/pages/show.html.haml
@@ -4,7 +4,7 @@
Pages
- if can?(current_user, :update_pages, @project) && (Gitlab.config.pages.external_http || Gitlab.config.pages.external_https)
- = link_to new_project_pages_domain_path(@project), class: 'btn btn-new float-right', title: 'New Domain' do
+ = link_to new_project_pages_domain_path(@project), class: 'btn btn-success float-right', title: 'New Domain' do
New Domain
%p.light
diff --git a/app/views/projects/pages_domains/edit.html.haml b/app/views/projects/pages_domains/edit.html.haml
index ee70de22f13..342b1482df7 100644
--- a/app/views/projects/pages_domains/edit.html.haml
+++ b/app/views/projects/pages_domains/edit.html.haml
@@ -8,4 +8,4 @@
= form_for [@project.namespace.becomes(Namespace), @project, @domain], html: { class: 'fieldset-form' } do |f|
= render 'form', { f: f }
.form-actions
- = f.submit 'Save Changes', class: "btn btn-save"
+ = f.submit 'Save Changes', class: "btn btn-success"
diff --git a/app/views/projects/pages_domains/new.html.haml b/app/views/projects/pages_domains/new.html.haml
index 376ce3f68aa..94ad1470052 100644
--- a/app/views/projects/pages_domains/new.html.haml
+++ b/app/views/projects/pages_domains/new.html.haml
@@ -7,6 +7,6 @@
= form_for [@project.namespace.becomes(Namespace), @project, @domain], html: { class: 'fieldset-form' } do |f|
= render 'form', { f: f }
.form-actions
- = f.submit 'Create New Domain', class: "btn btn-save"
+ = f.submit 'Create New Domain', class: "btn btn-success"
.float-right
= link_to _('Cancel'), project_pages_path(@project), class: 'btn btn-cancel'
diff --git a/app/views/projects/pipeline_schedules/_form.html.haml b/app/views/projects/pipeline_schedules/_form.html.haml
index 9a981d53ab6..259979417e0 100644
--- a/app/views/projects/pipeline_schedules/_form.html.haml
+++ b/app/views/projects/pipeline_schedules/_form.html.haml
@@ -39,5 +39,5 @@
= f.check_box :active, required: false, value: @schedule.active?
= _('Active')
.footer-block.row-content-block
- = f.submit _('Save pipeline schedule'), class: 'btn btn-create', tabindex: 3
+ = f.submit _('Save pipeline schedule'), class: 'btn btn-success', tabindex: 3
= link_to _('Cancel'), pipeline_schedules_path(@project), class: 'btn btn-cancel'
diff --git a/app/views/projects/pipeline_schedules/index.html.haml b/app/views/projects/pipeline_schedules/index.html.haml
index 3677666070e..0580c15ad15 100644
--- a/app/views/projects/pipeline_schedules/index.html.haml
+++ b/app/views/projects/pipeline_schedules/index.html.haml
@@ -11,7 +11,7 @@
- if can?(current_user, :create_pipeline_schedule, @project)
.nav-controls
- = link_to new_project_pipeline_schedule_path(@project), class: 'btn btn-create' do
+ = link_to new_project_pipeline_schedule_path(@project), class: 'btn btn-success' do
%span= _('New schedule')
- if @schedules.present?
diff --git a/app/views/projects/project_members/_new_shared_group.html.haml b/app/views/projects/project_members/_new_project_group.html.haml
index d7227c32833..74570769117 100644
--- a/app/views/projects/project_members/_new_shared_group.html.haml
+++ b/app/views/projects/project_members/_new_project_group.html.haml
@@ -2,19 +2,19 @@
.col-sm-12
= form_tag project_group_links_path(@project), class: 'js-requires-input', method: :post do
.form-group
- = label_tag :link_group_id, "Select a group to share with", class: "label-bold"
+ = label_tag :link_group_id, _("Select a group to invite"), class: "label-bold"
= groups_select_tag(:link_group_id, data: { skip_groups: @skip_groups }, class: "input-clamp", required: true)
.form-group
- = label_tag :link_group_access, "Max access level", class: "label-bold"
+ = label_tag :link_group_access, _("Max access level"), class: "label-bold"
.select-wrapper
= select_tag :link_group_access, options_for_select(ProjectGroupLink.access_options, ProjectGroupLink.default_access), class: "form-control select-control"
= icon('chevron-down')
.form-text.text-muted.append-bottom-10
- = link_to "Read more", help_page_path("user/permissions"), class: "vlink"
+ = link_to _("Read more"), help_page_path("user/permissions"), class: "vlink"
about role permissions
.form-group
- = label_tag :expires_at, 'Access expiration date', class: 'label-bold'
+ = label_tag :expires_at, _('Access expiration date'), class: 'label-bold'
.clearable-input
- = text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date-groups', placeholder: 'Expiration date', id: 'expires_at_groups'
+ = text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date-groups', placeholder: _('Expiration date'), id: 'expires_at_groups'
%i.clear-icon.js-clear-input
- = submit_tag "Share", class: "btn btn-create"
+ = submit_tag _("Invite"), class: "btn btn-success"
diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml
index 6272687be1c..517fd249f6e 100644
--- a/app/views/projects/project_members/_new_project_member.html.haml
+++ b/app/views/projects/project_members/_new_project_member.html.haml
@@ -17,5 +17,5 @@
= label_tag :expires_at, 'Access expiration date', class: 'label-bold'
= text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Expiration date'
%i.clear-icon.js-clear-input
- = f.submit "Add to project", class: "btn btn-create"
+ = f.submit "Add to project", class: "btn btn-success"
= link_to "Import", import_project_project_members_path(@project), class: "btn btn-default", title: "Import members from another project"
diff --git a/app/views/projects/project_members/import.html.haml b/app/views/projects/project_members/import.html.haml
index 6a52e72bfd8..8b93e81cd31 100644
--- a/app/views/projects/project_members/import.html.haml
+++ b/app/views/projects/project_members/import.html.haml
@@ -11,5 +11,5 @@
.col-sm-10= select_tag(:source_project_id, options_from_collection_for_select(@projects, :id, :name_with_namespace), prompt: "Select project", class: "select2 lg", required: true)
.form-actions
- = button_tag 'Import project members', class: "btn btn-create"
+ = button_tag 'Import project members', class: "btn btn-success"
= link_to "Cancel", project_project_members_path(@project), class: "btn btn-cancel"
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
index 9716322f8a1..14ed3345765 100644
--- a/app/views/projects/project_members/index.html.haml
+++ b/app/views/projects/project_members/index.html.haml
@@ -6,9 +6,9 @@
Project members
- if can?(current_user, :admin_project_member, @project)
%p
- You can add a new member to
+ You can invite a new member to
%strong= @project.name
- or share it with another group.
+ or invite another group.
- else
%p
Members can be added by project
@@ -19,16 +19,16 @@
- if can?(current_user, :admin_project_member, @project)
%ul.nav-links.nav.nav-tabs.gitlab-tabs{ role: 'tablist' }
%li.nav-tab{ role: 'presentation' }
- %a.nav-link.active{ href: '#add-member-pane', id: 'add-member-tab', data: { toggle: 'tab' }, role: 'tab' } Add member
+ %a.nav-link.active{ href: '#invite-member-pane', id: 'invite-member-tab', data: { toggle: 'tab' }, role: 'tab' } Invite member
- if @project.allowed_to_share_with_group?
%li.nav-tab{ role: 'presentation' }
- %a.nav-link{ href: '#share-with-group-pane', id: 'share-with-group-tab', data: { toggle: 'tab' }, role: 'tab' } Share with group
+ %a.nav-link{ href: '#invite-group-pane', id: 'invite-group-tab', data: { toggle: 'tab' }, role: 'tab' } Invite group
.tab-content.gitlab-tab-content
- .tab-pane.active{ id: 'add-member-pane', role: 'tabpanel' }
- = render 'projects/project_members/new_project_member', tab_title: 'Add member'
- .tab-pane{ id: 'share-with-group-pane', role: 'tabpanel' }
- = render 'projects/project_members/new_shared_group', tab_title: 'Share with group'
+ .tab-pane.active{ id: 'invite-member-pane', role: 'tabpanel' }
+ = render 'projects/project_members/new_project_member', tab_title: 'Invite member'
+ .tab-pane{ id: 'invite-group-pane', role: 'tabpanel' }
+ = render 'projects/project_members/new_project_group', tab_title: 'Invite group'
= render 'shared/members/requests', membership_source: @project, requesters: @requesters
.clearfix
diff --git a/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml b/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml
index df2dcf19ed4..c3b8f2f8964 100644
--- a/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml
+++ b/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml
@@ -30,4 +30,4 @@
= yield :push_access_levels
.card-footer
- = f.submit 'Protect', class: 'btn-create btn', disabled: true
+ = f.submit 'Protect', class: 'btn-success btn', disabled: true
diff --git a/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml b/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml
index f98781b77f4..b274c73d035 100644
--- a/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml
+++ b/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml
@@ -26,4 +26,4 @@
= yield :create_access_levels
.card-footer
- = f.submit 'Protect', class: 'btn-create btn', disabled: true
+ = f.submit 'Protect', class: 'btn-success btn', disabled: true
diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml
index 8093cc2c2d7..52c6c7ec424 100644
--- a/app/views/projects/releases/edit.html.haml
+++ b/app/views/projects/releases/edit.html.haml
@@ -19,5 +19,5 @@
= render 'shared/notes/hints'
.error-alert
.prepend-top-default
- = f.submit 'Save changes', class: 'btn btn-save'
+ = f.submit 'Save changes', class: 'btn btn-success'
= link_to "Cancel", project_tag_path(@project, @tag.name), class: "btn btn-default btn-cancel"
diff --git a/app/views/projects/runners/_group_runners.html.haml b/app/views/projects/runners/_group_runners.html.haml
index 86de71c732b..a6c16c70313 100644
--- a/app/views/projects/runners/_group_runners.html.haml
+++ b/app/views/projects/runners/_group_runners.html.haml
@@ -28,7 +28,7 @@
- group_link = link_to _('Group CI/CD settings'), group_settings_ci_cd_path(@project.group)
= _('Group maintainers can register group runners in the %{link}').html_safe % { link: group_link }
- else
- = _('Ask your group maintainer to setup a group Runner.')
+ = _('Ask your group maintainer to set up a group Runner.')
- else
%h4.underlined-title
diff --git a/app/views/projects/runners/_runner.html.haml b/app/views/projects/runners/_runner.html.haml
index 6ee83fae25e..548977d6a80 100644
--- a/app/views/projects/runners/_runner.html.haml
+++ b/app/views/projects/runners/_runner.html.haml
@@ -24,7 +24,7 @@
- if runner.belongs_to_one_project?
= link_to _('Remove Runner'), project_runner_path(@project, runner), data: { confirm: _("Are you sure?") }, method: :delete, class: 'btn btn-danger btn-sm'
- else
- - runner_project = @project.runner_projects.find_by(runner_id: runner)
+ - runner_project = @project.runner_projects.find_by(runner_id: runner) # rubocop: disable CodeReuse/ActiveRecord
= link_to _('Disable for this project'), project_runner_project_path(@project, runner_project), data: { confirm: _("Are you sure?") }, method: :delete, class: 'btn btn-danger btn-sm'
- elsif runner.project_type?
= form_for [@project.namespace.becomes(Namespace), @project, @project.runner_projects.new] do |f|
diff --git a/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml b/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml
index 9314804c5dd..9409418bbcc 100644
--- a/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml
+++ b/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml
@@ -1,6 +1,6 @@
- run_actions_text = "Perform common operations on GitLab project: #{@project.full_name}"
-%p To setup this service:
+%p To set up this service:
%ul.list-unstyled.indent-list
%li
1.
diff --git a/app/views/projects/services/slack_slash_commands/_help.html.haml b/app/views/projects/services/slack_slash_commands/_help.html.haml
index f25d2ecdfb1..9a7004f89c0 100644
--- a/app/views/projects/services/slack_slash_commands/_help.html.haml
+++ b/app/views/projects/services/slack_slash_commands/_help.html.haml
@@ -14,7 +14,7 @@
by entering
%kbd.inline /&lt;command&gt; help
- unless @service.template?
- %p To setup this service:
+ %p To set up this service:
%ul.list-unstyled.indent-list
%li
1.
diff --git a/app/views/projects/settings/ci_cd/_form.html.haml b/app/views/projects/settings/ci_cd/_form.html.haml
index 9134257b631..ae923d8e6dc 100644
--- a/app/views/projects/settings/ci_cd/_form.html.haml
+++ b/app/views/projects/settings/ci_cd/_form.html.haml
@@ -121,7 +121,7 @@
go test -cover (Go)
%code coverage: \d+.\d+% of statements
- = f.submit _('Save changes'), class: "btn btn-save"
+ = f.submit _('Save changes'), class: "btn btn-success"
%hr
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index df8a5742450..aba289c790f 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -19,8 +19,13 @@
- if can?(current_user, :download_code, @project)
%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)
+ .scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
+ .fade-left= icon('angle-left')
+ .fade-right= icon('angle-right')
+ .nav-links.scrolling-tabs
+ = 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)
+
= repository_languages_bar(@project.repository_languages)
%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
diff --git a/app/views/projects/snippets/_actions.html.haml b/app/views/projects/snippets/_actions.html.haml
index 4a3aa3dc626..ea963510a68 100644
--- a/app/views/projects/snippets/_actions.html.haml
+++ b/app/views/projects/snippets/_actions.html.haml
@@ -8,7 +8,7 @@
= link_to project_snippet_path(@project, @snippet), method: :delete, data: { confirm: _("Are you sure?") }, class: "btn btn-grouped btn-inverted btn-remove", title: _('Delete Snippet') do
= _('Delete')
- if can?(current_user, :create_project_snippet, @project)
- = link_to new_project_snippet_path(@project), class: 'btn btn-grouped btn-inverted btn-create', title: _("New snippet") do
+ = link_to new_project_snippet_path(@project), class: 'btn btn-grouped btn-inverted btn-success', title: _("New snippet") do
= _('New snippet')
- if @snippet.submittable_as_spam_by?(current_user)
= link_to _('Submit as spam'), mark_as_spam_project_snippet_path(@project, @snippet), method: :post, class: 'btn btn-grouped btn-spam', title: _('Submit as spam')
diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml
index 1c4c73dc776..a4974d89c1a 100644
--- a/app/views/projects/snippets/index.html.haml
+++ b/app/views/projects/snippets/index.html.haml
@@ -7,6 +7,6 @@
.nav-controls
- if can?(current_user, :create_project_snippet, @project)
- = link_to _("New snippet"), new_project_snippet_path(@project), class: "btn btn-new", title: _("New snippet")
+ = link_to _("New snippet"), new_project_snippet_path(@project), class: "btn btn-success", title: _("New snippet")
= render 'snippets/snippets'
diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml
index 20b4705521c..37535370940 100644
--- a/app/views/projects/tags/index.html.haml
+++ b/app/views/projects/tags/index.html.haml
@@ -25,7 +25,7 @@
%li
= link_to title, filter_tags_path(sort: value), class: ("is-active" if @sort == value)
- if can?(current_user, :push_code, @project)
- = link_to new_project_tag_path(@project), class: 'btn btn-create new-tag-btn' do
+ = link_to new_project_tag_path(@project), class: 'btn btn-success new-tag-btn' do
= s_('TagsPage|New tag')
= link_to project_tags_path(@project, rss_url_options), title: _("Tags feed"), class: 'btn rss-btn has-tooltip' do
= icon("rss")
diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml
index da822ac5675..24724394259 100644
--- a/app/views/projects/tags/new.html.haml
+++ b/app/views/projects/tags/new.html.haml
@@ -41,7 +41,7 @@
.form-text.text-muted
= s_('TagsPage|Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page.')
.form-actions
- = button_tag s_('TagsPage|Create tag'), class: 'btn btn-create'
+ = button_tag s_('TagsPage|Create tag'), class: 'btn btn-success'
= link_to s_('TagsPage|Cancel'), project_tags_path(@project), class: 'btn btn-cancel'
-# haml-lint:disable InlineJavaScript
%script#availableRefs{ type: "application/json" }= @project.repository.ref_names.to_json.html_safe
diff --git a/app/views/projects/tree/_tree_commit_column.html.haml b/app/views/projects/tree/_tree_commit_column.html.haml
index abb3e918e87..406dccb74fb 100644
--- a/app/views/projects/tree/_tree_commit_column.html.haml
+++ b/app/views/projects/tree/_tree_commit_column.html.haml
@@ -1,2 +1,2 @@
%span.str-truncated
- = link_to_markdown commit.full_title, project_commit_path(@project, commit.id), class: "tree-commit-link"
+ = link_to_html commit.redacted_full_title_html, project_commit_path(@project, commit.id), class: 'tree-commit-link'
diff --git a/app/views/projects/triggers/_form.html.haml b/app/views/projects/triggers/_form.html.haml
index 1a5fc56f429..a9abfac239c 100644
--- a/app/views/projects/triggers/_form.html.haml
+++ b/app/views/projects/triggers/_form.html.haml
@@ -8,4 +8,4 @@
.form-group
= f.label :key, "Description", class: "label-bold"
= f.text_field :description, class: "form-control", required: true, title: 'Trigger description is required.', placeholder: "Trigger description"
- = f.submit btn_text, class: "btn btn-save"
+ = f.submit btn_text, class: "btn btn-success"
diff --git a/app/views/projects/update.js.haml b/app/views/projects/update.js.haml
index e8681da6528..70f1bf8ef46 100644
--- a/app/views/projects/update.js.haml
+++ b/app/views/projects/update.js.haml
@@ -7,4 +7,4 @@
$(".project-edit-errors").html("#{escape_javascript(render('errors'))}");
$('.save-project-loader').hide();
$('.project-edit-container').show();
- $('.edit-project .js-btn-save-general-project-settings').enable();
+ $('.edit-project .js-btn-success-general-project-settings').enable();
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index de692466fe5..7d8826e540c 100644
--- a/app/views/projects/wikis/_form.html.haml
+++ b/app/views/projects/wikis/_form.html.haml
@@ -1,9 +1,13 @@
- commit_message = @page.persisted? ? s_("WikiPageEdit|Update %{page_title}") : s_("WikiPageCreate|Create %{page_title}")
- commit_message = commit_message % { page_title: @page.title }
+- if params[:legacy_render] || !commonmark_for_repositories_enabled?
+ - markdown_version = CacheMarkdownField::CACHE_REDCARPET_VERSION
+- else
+ - markdown_version = 0
= form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post,
html: { class: 'wiki-form common-note-form prepend-top-default js-quick-submit' },
- data: { markdown_version: CacheMarkdownField::CACHE_REDCARPET_VERSION } do |f|
+ data: { markdown_version: markdown_version, uploads_path: uploads_path } do |f|
= form_errors(@page)
- if @page.persisted?
@@ -47,10 +51,10 @@
.form-actions
- if @page && @page.persisted?
- = f.submit _("Save changes"), class: 'btn-save btn'
+ = f.submit _("Save changes"), class: 'btn-success btn'
.float-right
= link_to _("Cancel"), project_wiki_path(@project, @page), class: 'btn btn-cancel btn-grouped'
- else
- = f.submit s_("Wiki|Create page"), class: 'btn-create btn'
+ = f.submit s_("Wiki|Create page"), class: 'btn-success btn'
.float-right
= link_to _("Cancel"), project_wiki_path(@project, :home), class: 'btn btn-cancel'
diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml
index 8d91f411f89..643b51e01d1 100644
--- a/app/views/projects/wikis/_main_links.html.haml
+++ b/app/views/projects/wikis/_main_links.html.haml
@@ -1,6 +1,6 @@
- if (@page && @page.persisted?)
- if can?(current_user, :create_wiki, @project)
- = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
+ = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-success", "data-toggle" => "modal" do
= s_("Wiki|New page")
= link_to project_wiki_history_path(@project, @page), class: "btn" do
= s_("Wiki|Page history")
diff --git a/app/views/projects/wikis/_new.html.haml b/app/views/projects/wikis/_new.html.haml
index 38382aae67c..dc12e368b35 100644
--- a/app/views/projects/wikis/_new.html.haml
+++ b/app/views/projects/wikis/_new.html.haml
@@ -15,4 +15,4 @@
= icon('lightbulb-o')
= s_("WikiNewPageTip|Tip: You can specify the full path for the new file. We will automatically create any missing directories.")
.form-actions
- = button_tag s_("Wiki|Create page"), class: "build-new-wiki btn btn-create"
+ = button_tag s_("Wiki|Create page"), class: "build-new-wiki btn btn-success"
diff --git a/app/views/projects/wikis/_sidebar.html.haml b/app/views/projects/wikis/_sidebar.html.haml
index 28353927135..02c5a6ea55c 100644
--- a/app/views/projects/wikis/_sidebar.html.haml
+++ b/app/views/projects/wikis/_sidebar.html.haml
@@ -12,7 +12,7 @@
.blocks-container
.block.block-first
- if @sidebar_page
- = render_wiki_content(@sidebar_page)
+ = render_wiki_content(@sidebar_page, legacy_render_context(params))
- else
%ul.wiki-pages
= render @sidebar_wiki_entries, context: 'sidebar'
diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml
index 71359708022..80aa1500d53 100644
--- a/app/views/projects/wikis/edit.html.haml
+++ b/app/views/projects/wikis/edit.html.haml
@@ -22,27 +22,14 @@
.nav-controls
- if can?(current_user, :create_wiki, @project)
- = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
+ = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-success", "data-toggle" => "modal" do
= s_("Wiki|New page")
- if @page.persisted?
= link_to project_wiki_history_path(@project, @page), class: "btn" do
= s_("Wiki|Page history")
- if can?(current_user, :admin_wiki, @project)
- %button.btn.btn-danger{ data: { toggle: 'modal',
- target: '#delete-wiki-modal',
- delete_wiki_url: project_wiki_path(@project, @page),
- page_title: @page.title.capitalize },
- id: 'delete-wiki-button',
- type: 'button' }
- = _('Delete')
+ #delete-wiki-modal-wrapper{ data: { delete_wiki_url: project_wiki_path(@project, @page), page_title: @page.title.capitalize } }
-= render 'form'
+= render 'form', uploads_path: wiki_attachment_upload_url
= render 'sidebar'
-
-#delete-wiki-modal.modal.fade
-
-- content_for :scripts_body do
- -# haml-lint:disable InlineJavaScript
- :javascript
- window.uploads_path = "#{wiki_attachment_upload_url}";
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index a08973c7f32..19b9744b508 100644
--- a/app/views/projects/wikis/show.html.haml
+++ b/app/views/projects/wikis/show.html.haml
@@ -26,6 +26,6 @@
.prepend-top-default.append-bottom-default
.wiki
- = render_wiki_content(@page)
+ = render_wiki_content(@page, legacy_render_context(params))
= render 'sidebar'
diff --git a/app/views/search/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml
index 57a0b64bfd5..8b95bdf9747 100644
--- a/app/views/search/results/_snippet_blob.html.haml
+++ b/app/views/search/results/_snippet_blob.html.haml
@@ -21,7 +21,7 @@
.file-content.wiki
- snippet_chunks.each do |chunk|
- unless chunk[:data].empty?
- = markup(snippet.file_name, chunk[:data])
+ = markup(snippet.file_name, chunk[:data], legacy_render_context(params))
- else
.file-content.code
.nothing-here-block Empty file
diff --git a/app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml b/app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml
new file mode 100644
index 00000000000..6c4607b2f16
--- /dev/null
+++ b/app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml
@@ -0,0 +1,9 @@
+- if show_auto_devops_implicitly_enabled_banner?(project)
+ .auto-devops-implicitly-enabled-banner.alert.alert-warning
+ - more_information_link = link_to _('More information'), 'https://docs.gitlab.com/ee/topics/autodevops/', class: 'alert-link'
+ - auto_devops_message = s_("AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}") % { more_information_link: more_information_link }
+ = auto_devops_message.html_safe
+ .alert-link-group
+ = link_to _('Settings'), project_settings_ci_cd_path(project), class: 'alert-link'
+ |
+ = link_to _('Dismiss'), '#', class: 'hide-auto-devops-implicitly-enabled-banner alert-link', data: { project_id: project.id }
diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml
index 3655c2a1d42..a2df0347fd6 100644
--- a/app/views/shared/_clone_panel.html.haml
+++ b/app/views/shared/_clone_panel.html.haml
@@ -1,14 +1,14 @@
- project = project || @project
-.git-clone-holder.input-group
+.git-clone-holder.js-git-clone-holder.input-group
.input-group-prepend
- if allowed_protocols_present?
.input-group-text.clone-dropdown-btn.btn
- %span
+ %span.js-clone-dropdown-label
= enabled_project_button(project, enabled_protocol)
- else
%a#clone-dropdown.input-group-text.btn.clone-dropdown-btn.qa-clone-dropdown{ href: '#', data: { toggle: 'dropdown' } }
- %span
+ %span.js-clone-dropdown-label
= default_clone_protocol.upcase
= icon('caret-down')
%ul.dropdown-menu.dropdown-menu-selectable.clone-options-dropdown
diff --git a/app/views/shared/_mobile_clone_panel.html.haml b/app/views/shared/_mobile_clone_panel.html.haml
new file mode 100644
index 00000000000..998985cabe1
--- /dev/null
+++ b/app/views/shared/_mobile_clone_panel.html.haml
@@ -0,0 +1,13 @@
+- project = project || @project
+- ssh_copy_label = _("Copy SSH clone URL")
+- http_copy_label = _("Copy HTTPS clone URL")
+
+.btn-group.mobile-git-clone.js-mobile-git-clone
+ = clipboard_button(button_text: default_clone_label, target: '#project_clone', hide_button_icon: true, class: "input-group-text clone-dropdown-btn js-clone-dropdown-label btn btn-default")
+ %button.btn.btn-default.dropdown-toggle.js-dropdown-toggle{ type: "button", data: { toggle: "dropdown" } }
+ = icon("caret-down", class: "dropdown-btn-icon")
+ %ul.dropdown-menu.dropdown-menu-selectable.dropdown-menu-right.clone-options-dropdown{ data: { dropdown: true } }
+ %li
+ = dropdown_item_with_description(ssh_copy_label, project.ssh_url_to_repo, href: project.ssh_url_to_repo, data: { clone_type: 'ssh' })
+ %li
+ = dropdown_item_with_description(http_copy_label, project.http_url_to_repo, href: project.http_url_to_repo, data: { clone_type: 'http' })
diff --git a/app/views/shared/_new_project_item_select.html.haml b/app/views/shared/_new_project_item_select.html.haml
index d38d161047b..9bc67a7c715 100644
--- a/app/views/shared/_new_project_item_select.html.haml
+++ b/app/views/shared/_new_project_item_select.html.haml
@@ -1,7 +1,7 @@
- if any_projects?(@projects)
.project-item-select-holder.btn-group
- %a.btn.btn-new.new-project-item-link.qa-new-project-item-link{ href: '', data: { label: local_assigns[:label], type: local_assigns[:type] } }
+ %a.btn.btn-success.new-project-item-link.qa-new-project-item-link{ href: '', data: { label: local_assigns[:label], type: local_assigns[:type] } }
= icon('spinner spin')
= project_select_tag :project_path, class: "project-item-select", data: { include_groups: local_assigns[:include_groups], order_by: 'last_activity_at', relative_path: local_assigns[:path] }, with_feature_enabled: local_assigns[:with_feature_enabled]
- %button.btn.btn-new.new-project-item-select-button.qa-new-project-item-select-button
+ %button.btn.btn-success.new-project-item-select-button.qa-new-project-item-select-button
= icon('caret-down')
diff --git a/app/views/shared/_personal_access_tokens_form.html.haml b/app/views/shared/_personal_access_tokens_form.html.haml
index 58d310fac16..f4df7bdcd83 100644
--- a/app/views/shared/_personal_access_tokens_form.html.haml
+++ b/app/views/shared/_personal_access_tokens_form.html.haml
@@ -26,4 +26,4 @@
= render 'shared/tokens/scopes_form', prefix: 'personal_access_token', token: token, scopes: scopes
.prepend-top-default
- = f.submit "Create #{type} token", class: "btn btn-create"
+ = f.submit "Create #{type} token", class: "btn btn-success"
diff --git a/app/views/shared/_ping_consent.html.haml b/app/views/shared/_ping_consent.html.haml
new file mode 100644
index 00000000000..f8eb2b2833b
--- /dev/null
+++ b/app/views/shared/_ping_consent.html.haml
@@ -0,0 +1,12 @@
+- if session[:ask_for_usage_stats_consent]
+ .ping-consent-message.alert.alert-warning.flex-alert
+ - settings_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer" class="alert-link">'.html_safe % { url: admin_application_settings_path(anchor: 'js-usage-settings') }
+ - info_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer" class="alert-link">'.html_safe % { url: help_page_path('user/admin_area/settings/usage_statistics.md') }
+ .alert-message
+ = s_('To help improve GitLab, we would like to periodically collect usage information. This can be changed at any time in %{settings_link_start}Settings%{link_end}. %{info_link_start}More Information%{link_end}').html_safe % { settings_link_start: settings_link_start, info_link_start: info_link_start, link_end: '</a>'.html_safe }
+ .alert-link-group
+ - send_usage_data_path = admin_application_settings_path(application_setting: { version_check_enabled: 1, usage_ping_enabled: 1 })
+ - not_now_path = admin_application_settings_path(application_setting: { version_check_enabled: 0, usage_ping_enabled: 0 })
+ = link_to _("Send usage data"), send_usage_data_path, 'data-url' => admin_application_settings_path, method: :put, 'data-check-enabled': true, 'data-ping-enabled': true, class: 'alert-link js-usage-consent-action'
+ |
+ = link_to _('Not now'), not_now_path, 'data-url' => admin_application_settings_path, method: :put, 'data-check-enabled': false, 'data-ping-enabled': false, class: 'hide-ping-consent-message alert-link js-usage-consent-action'
diff --git a/app/views/shared/_recaptcha_form.html.haml b/app/views/shared/_recaptcha_form.html.haml
index a0ba1afc284..10f358402c1 100644
--- a/app/views/shared/_recaptcha_form.html.haml
+++ b/app/views/shared/_recaptcha_form.html.haml
@@ -17,4 +17,4 @@
- if has_submit
.row-content-block.footer-block
- = f.submit "Submit #{humanized_resource_name}", class: 'btn btn-create'
+ = f.submit "Submit #{humanized_resource_name}", class: 'btn btn-success'
diff --git a/app/views/shared/_visibility_level.html.haml b/app/views/shared/_visibility_level.html.haml
index 01ce1225b8d..ba37b37a3b1 100644
--- a/app/views/shared/_visibility_level.html.haml
+++ b/app/views/shared/_visibility_level.html.haml
@@ -2,7 +2,7 @@
.form-group.row.visibility-level-setting
- if with_label
- = f.label :visibility_level, class: 'col-form-label col-sm-2' do
+ = f.label :visibility_level, class: 'col-form-label col-sm-2 pt-0' do
Visibility Level
= link_to icon('question-circle'), help_page_path("public_access/public_access")
%div{ :class => (with_label ? "col-sm-10" : "col-sm-12") }
diff --git a/app/views/shared/empty_states/_labels.html.haml b/app/views/shared/empty_states/_labels.html.haml
index e8749ee3956..b629ceafeb3 100644
--- a/app/views/shared/empty_states/_labels.html.haml
+++ b/app/views/shared/empty_states/_labels.html.haml
@@ -7,5 +7,5 @@
%h4= _("Labels can be applied to issues and merge requests to categorize them.")
%p= _("You can also star a label to make it a priority label.")
- if can?(current_user, :admin_label, @project)
- = link_to _('New label'), new_project_label_path(@project), class: 'btn btn-new', title: _('New label'), id: 'new_label_link'
+ = link_to _('New label'), new_project_label_path(@project), class: 'btn btn-success', title: _('New label'), id: 'new_label_link'
= link_to _('Generate a default set of labels'), generate_project_labels_path(@project), method: :post, class: 'btn btn-success btn-inverted', title: _('Generate a default set of labels'), id: 'generate_labels_link'
diff --git a/app/views/shared/empty_states/_merge_requests.html.haml b/app/views/shared/empty_states/_merge_requests.html.haml
index 186139f3526..421a1b2415b 100644
--- a/app/views/shared/empty_states/_merge_requests.html.haml
+++ b/app/views/shared/empty_states/_merge_requests.html.haml
@@ -17,7 +17,7 @@
- if project_select_button
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: _('New merge request'), type: :merge_requests, with_feature_enabled: 'merge_requests'
- else
- = link_to _('New merge request'), button_path, class: 'btn btn-new', title: _('New merge request'), id: 'new_merge_request_link'
+ = link_to _('New merge request'), button_path, class: 'btn btn-success', title: _('New merge request'), id: 'new_merge_request_link'
- else
%h4.text-center
= _("There are no merge requests to show")
diff --git a/app/views/shared/empty_states/_wikis.html.haml b/app/views/shared/empty_states/_wikis.html.haml
index f1a41074c28..5351c9ce6a4 100644
--- a/app/views/shared/empty_states/_wikis.html.haml
+++ b/app/views/shared/empty_states/_wikis.html.haml
@@ -2,7 +2,7 @@
- if can?(current_user, :create_wiki, @project)
- create_path = project_wiki_path(@project, params[:id], { view: 'create' })
- - create_link = link_to s_('WikiEmpty|Create your first page'), create_path, class: 'btn btn-new', title: s_('WikiEmpty|Create your first page')
+ - create_link = link_to s_('WikiEmpty|Create your first page'), create_path, class: 'btn btn-success', title: s_('WikiEmpty|Create your first page')
= render layout: layout_path, locals: { image_path: 'illustrations/wiki_login_empty.svg' } do
%h4
@@ -13,7 +13,7 @@
- elsif can?(current_user, :read_issue, @project)
- issues_link = link_to s_('WikiEmptyIssueMessage|issue tracker'), project_issues_path(@project)
- - new_issue_link = link_to s_('WikiEmpty|Suggest wiki improvement'), new_project_issue_path(@project), class: 'btn btn-new', title: s_('WikiEmptyIssueMessage|Suggest wiki improvement')
+ - new_issue_link = link_to s_('WikiEmpty|Suggest wiki improvement'), new_project_issue_path(@project), class: 'btn btn-success', title: s_('WikiEmptyIssueMessage|Suggest wiki improvement')
= render layout: layout_path, locals: { image_path: 'illustrations/wiki_logout_empty.svg' } do
%h4
diff --git a/app/views/shared/groups/_empty_state.html.haml b/app/views/shared/groups/_empty_state.html.haml
index a9c78547eae..c35f6f5a3c1 100644
--- a/app/views/shared/groups/_empty_state.html.haml
+++ b/app/views/shared/groups/_empty_state.html.haml
@@ -1,7 +1,8 @@
-.groups-empty-state.qa-groups-empty-state
- = custom_icon("icon_empty_groups")
+.group-empty-state.row.align-items-center.justify-content-center.qa-groups-empty-state
+ .icon.text-center.order-md-2
+ = custom_icon("icon_empty_groups")
- .text-content
+ .text-content.m-0.order-md-1
%h4= s_("GroupsEmptyState|A group is a collection of several projects.")
%p= s_("GroupsEmptyState|If you organize your projects under a group, it works like a folder.")
%p= s_("GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group.")
diff --git a/app/views/shared/groups/_search_form.html.haml b/app/views/shared/groups/_search_form.html.haml
index 3f91263089a..67e1cd0d67b 100644
--- a/app/views/shared/groups/_search_form.html.haml
+++ b/app/views/shared/groups/_search_form.html.haml
@@ -1,2 +1,2 @@
-= form_tag request.path, method: :get, class: 'group-filter-form append-right-10', id: 'group-filter-form' do |f|
- = search_field_tag :filter, params[:filter], placeholder: s_('GroupsTree|Filter by name...'), class: 'group-filter-form-field form-control input-short js-groups-list-filter', spellcheck: false, id: 'group-filter-form-field', tabindex: "2"
+= form_tag request.path, method: :get, class: "group-filter-form js-group-filter-form", id: 'group-filter-form' do |f|
+ = search_field_tag :filter, params[:filter], placeholder: s_('GroupsTree|Search by name'), class: 'group-filter-form-field form-control js-groups-list-filter', spellcheck: false, id: 'group-filter-form-field', tabindex: "2"
diff --git a/app/views/shared/issuable/_assignees.html.haml b/app/views/shared/issuable/_assignees.html.haml
index fc86f855865..ef3d44a9241 100644
--- a/app/views/shared/issuable/_assignees.html.haml
+++ b/app/views/shared/issuable/_assignees.html.haml
@@ -3,7 +3,7 @@
- render_count = assignees_rendering_overflow ? max_render - 1 : max_render
- more_assignees_count = issue.assignees.size - render_count
-- issue.assignees.take(render_count).each do |assignee|
+- issue.assignees.take(render_count).each do |assignee| # rubocop: disable CodeReuse/ActiveRecord
= link_to_member(@project, assignee, name: false, title: "Assigned to :name")
- if more_assignees_count.positive?
diff --git a/app/views/shared/issuable/_board_create_list_dropdown.html.haml b/app/views/shared/issuable/_board_create_list_dropdown.html.haml
index 23b2e1b91e5..4597d9439fa 100644
--- a/app/views/shared/issuable/_board_create_list_dropdown.html.haml
+++ b/app/views/shared/issuable/_board_create_list_dropdown.html.haml
@@ -1,5 +1,5 @@
.dropdown.prepend-left-10#js-add-list
- %button.btn.btn-create.btn-inverted.js-new-board-list{ type: "button", data: board_list_data }
+ %button.btn.btn-success.btn-inverted.js-new-board-list{ type: "button", data: board_list_data }
Add list
.dropdown-menu.dropdown-menu-paging.dropdown-menu-right.dropdown-menu-issues-board-new.dropdown-menu-selectable.js-tab-container-labels
= render partial: "shared/issuable/label_page_default", locals: { show_footer: true, show_create: true, show_boards_content: true, title: "Add list" }
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index b49e47a7266..5b28a43a361 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -69,9 +69,9 @@
%span.append-right-10
- if issuable.new_record?
- = form.submit "Submit #{issuable.class.model_name.human.downcase}", class: 'btn btn-create qa-issuable-create-button'
+ = form.submit "Submit #{issuable.class.model_name.human.downcase}", class: 'btn btn-success qa-issuable-create-button'
- else
- = form.submit 'Save changes', class: 'btn btn-save'
+ = form.submit 'Save changes', class: 'btn btn-success'
- if !issuable.persisted? && !issuable.project.empty_repo? && (guide_url = issuable.project.present.contribution_guide_path)
.inline.prepend-top-10
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index 9ce7f6fe269..659e03fd67d 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -34,7 +34,7 @@
%ul{ data: { dropdown: true } }
%li.filter-dropdown-item{ data: { action: 'submit' } }
%button.btn.btn-link
- = icon('search')
+ = sprite_icon('search')
%span
Press Enter or click to search
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
@@ -42,7 +42,8 @@
%button.btn.btn-link
-# Encapsulate static class name `{{icon}}` inside #{} to bypass
-# haml lint's ClassAttributeWithStaticValue
- %i.fa{ class: "#{'{{icon}}'}" }
+ %svg
+ %use{ 'xlink:href': "#{'{{icon}}'}" }
%span.js-filter-hint
{{hint}}
%span.js-filter-tag.dropdown-light-content
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 0ca35ea1298..32b609eed0d 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -159,7 +159,7 @@
= dropdown_content
= dropdown_loading
= dropdown_footer add_content_class: true do
- %button.btn.btn-new.sidebar-move-issue-confirmation-button.js-move-issue-confirmation-button{ disabled: true }
+ %button.btn.btn-success.sidebar-move-issue-confirmation-button.js-move-issue-confirmation-button{ disabled: true }
= _('Move')
= icon('spinner spin', class: 'sidebar-move-issue-confirmation-loading-icon')
diff --git a/app/views/shared/issuable/form/_metadata.html.haml b/app/views/shared/issuable/form/_metadata.html.haml
index d8580ad8ab4..ac8d58c0bfe 100644
--- a/app/views/shared/issuable/form/_metadata.html.haml
+++ b/app/views/shared/issuable/form/_metadata.html.haml
@@ -34,4 +34,4 @@
= form.label :due_date, "Due date", class: "col-form-label col-md-2 col-lg-4"
.col-8
.issuable-form-select-holder
- = form.text_field :due_date, id: "issuable-due-date", class: "datepicker form-control", placeholder: "Select due date"
+ = form.text_field :due_date, id: "issuable-due-date", class: "datepicker form-control", placeholder: "Select due date", autocomplete: 'off'
diff --git a/app/views/shared/issuable/form/_title.html.haml b/app/views/shared/issuable/form/_title.html.haml
index e49bdec386a..56c4b021eab 100644
--- a/app/views/shared/issuable/form/_title.html.haml
+++ b/app/views/shared/issuable/form/_title.html.haml
@@ -9,7 +9,7 @@
autocomplete: 'off', class: 'form-control pad qa-issuable-form-title', placeholder: _('Title')
- if issuable.respond_to?(:work_in_progress?)
- %p.form-text.text-muted
+ .form-text.text-muted
.js-wip-explanation
%a.js-toggle-wip{ href: '', tabindex: -1 }
Remove the
diff --git a/app/views/shared/labels/_form.html.haml b/app/views/shared/labels/_form.html.haml
index 2bf5efae1e6..335c34a4632 100644
--- a/app/views/shared/labels/_form.html.haml
+++ b/app/views/shared/labels/_form.html.haml
@@ -28,7 +28,7 @@
.form-actions
- if @label.persisted?
- = f.submit 'Save changes', class: 'btn btn-save js-save-button'
+ = f.submit 'Save changes', class: 'btn btn-success js-save-button'
- else
- = f.submit 'Create label', class: 'btn btn-create js-save-button'
+ = f.submit 'Create label', class: 'btn btn-success js-save-button'
= link_to 'Cancel', back_path, class: 'btn btn-cancel'
diff --git a/app/views/shared/labels/_sort_dropdown.html.haml b/app/views/shared/labels/_sort_dropdown.html.haml
new file mode 100644
index 00000000000..ff6e2947ffd
--- /dev/null
+++ b/app/views/shared/labels/_sort_dropdown.html.haml
@@ -0,0 +1,9 @@
+- sort_title = label_sort_options_hash[@sort] || sort_title_name_desc
+.dropdown.inline
+ %button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown' } }
+ = sort_title
+ = icon('chevron-down')
+ %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-sort
+ %li
+ - label_sort_options_hash.each do |value, title|
+ = sortable_item(title, page_filter_path(sort: value, label: true), sort_title)
diff --git a/app/views/shared/members/_access_request_buttons.html.haml b/app/views/shared/members/_access_request_buttons.html.haml
index 40224cec9e8..ebae58f28ba 100644
--- a/app/views/shared/members/_access_request_buttons.html.haml
+++ b/app/views/shared/members/_access_request_buttons.html.haml
@@ -1,13 +1,13 @@
- model_name = source.model_name.to_s.downcase
-- if can?(current_user, :"destroy_#{model_name}_member", source.members.find_by(user_id: current_user.id))
+- if can?(current_user, :"destroy_#{model_name}_member", source.members.find_by(user_id: current_user.id)) # rubocop: disable CodeReuse/ActiveRecord
.project-action-button.inline
- link_text = source.is_a?(Group) ? _('Leave group') : _('Leave project')
= link_to link_text, polymorphic_path([:leave, source, :members]),
method: :delete,
data: { confirm: leave_confirmation_message(source) },
class: 'btn'
-- elsif requester = source.requesters.find_by(user_id: current_user.id)
+- elsif requester = source.requesters.find_by(user_id: current_user.id) # rubocop: disable CodeReuse/ActiveRecord
.project-action-button.inline
= link_to _('Withdraw Access Request'), polymorphic_path([:leave, source, :members]),
method: :delete,
diff --git a/app/views/shared/notes/_comment_button.html.haml b/app/views/shared/notes/_comment_button.html.haml
index ed336df4e9d..0674c822d63 100644
--- a/app/views/shared/notes/_comment_button.html.haml
+++ b/app/views/shared/notes/_comment_button.html.haml
@@ -1,7 +1,7 @@
- noteable_name = @note.noteable.human_class_name
.float-left.btn-group.append-right-10.droplab-dropdown.comment-type-dropdown.js-comment-type-dropdown
- %input.btn.btn-nr.btn-create.comment-btn.js-comment-button.js-comment-submit-button{ type: 'submit', value: 'Comment' }
+ %input.btn.btn-nr.btn-success.comment-btn.js-comment-button.js-comment-submit-button{ type: 'submit', value: 'Comment' }
- if @note.can_be_discussion_note?
= button_tag type: 'button', class: 'btn btn-nr dropdown-toggle comment-btn js-note-new-discussion js-disable-on-submit', data: { 'dropdown-trigger' => '#resolvable-comment-menu' }, 'aria-label' => 'Open comment type dropdown' do
diff --git a/app/views/shared/notes/_edit_form.html.haml b/app/views/shared/notes/_edit_form.html.haml
index 71a5b94e958..fec966069b9 100644
--- a/app/views/shared/notes/_edit_form.html.haml
+++ b/app/views/shared/notes/_edit_form.html.haml
@@ -9,6 +9,6 @@
.note-form-actions.clearfix
.settings-message.note-edit-warning.js-finish-edit-warning
Finish editing this message first!
- = submit_tag 'Save comment', class: 'btn btn-nr btn-save js-comment-save-button'
+ = submit_tag 'Save comment', class: 'btn btn-nr btn-success js-comment-save-button'
%button.btn.btn-nr.btn-cancel.note-edit-cancel{ type: 'button' }
Cancel
diff --git a/app/views/shared/runners/_form.html.haml b/app/views/shared/runners/_form.html.haml
index fa93307be31..daf08d9bb2c 100644
--- a/app/views/shared/runners/_form.html.haml
+++ b/app/views/shared/runners/_form.html.haml
@@ -51,6 +51,6 @@
= _('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 set up jobs to only use Runners with specific tags. Separate tags with commas.')
.form-actions
= f.submit _('Save changes'), class: 'btn btn-success'
diff --git a/app/views/shared/runners/_runner_description.html.haml b/app/views/shared/runners/_runner_description.html.haml
index da5c032add5..5935750ca06 100644
--- a/app/views/shared/runners/_runner_description.html.haml
+++ b/app/views/shared/runners/_runner_description.html.haml
@@ -1,6 +1,6 @@
.light.prepend-top-default
%p
- = _("A 'Runner' is a process which runs a job. You can setup as many Runners as you need.")
+ = _("A 'Runner' is a process which runs a job. You can set up as many Runners as you need.")
%br
= _('Runners can be placed on separate users, servers, and even on your local machine.')
diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml
index 5e5c050d5c3..1b66d3acd40 100644
--- a/app/views/shared/snippets/_form.html.haml
+++ b/app/views/shared/snippets/_form.html.haml
@@ -32,9 +32,9 @@
.form-actions
- if @snippet.new_record?
- = f.submit 'Create snippet', class: "btn-create btn"
+ = f.submit 'Create snippet', class: "btn-success btn"
- else
- = f.submit 'Save changes', class: "btn-save btn"
+ = f.submit 'Save changes', class: "btn-success btn"
- if @snippet.project_id
= link_to "Cancel", project_snippets_path(@project), class: "btn btn-cancel"
diff --git a/app/views/snippets/_actions.html.haml b/app/views/snippets/_actions.html.haml
index ae69d0d07c7..0ce13ee7a53 100644
--- a/app/views/snippets/_actions.html.haml
+++ b/app/views/snippets/_actions.html.haml
@@ -7,7 +7,7 @@
- if can?(current_user, :admin_personal_snippet, @snippet)
= link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-inverted btn-remove", title: 'Delete Snippet' do
Delete
- = link_to new_snippet_path, class: "btn btn-grouped btn-inverted btn-create", title: "New snippet" do
+ = link_to new_snippet_path, class: "btn btn-grouped btn-inverted btn-success", title: "New snippet" do
New snippet
- if @snippet.submittable_as_spam_by?(current_user)
= link_to 'Submit as spam', mark_as_spam_snippet_path(@snippet), method: :post, class: 'btn btn-grouped btn-spam', title: 'Submit as spam'
diff --git a/app/views/u2f/_register.html.haml b/app/views/u2f/_register.html.haml
index cc0e93c0755..39d4d82a77d 100644
--- a/app/views/u2f/_register.html.haml
+++ b/app/views/u2f/_register.html.haml
@@ -8,13 +8,13 @@
- if current_user.two_factor_otp_enabled?
.row.append-bottom-10
.col-md-4
- %button#js-setup-u2f-device.btn.btn-info.btn-block Setup new U2F device
+ %button#js-setup-u2f-device.btn.btn-info.btn-block Set up new U2F device
.col-md-8
%p Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left.
- else
.row.append-bottom-10
.col-md-4
- %button#js-setup-u2f-device.btn.btn-info.btn-block{ disabled: true } Setup new U2F device
+ %button#js-setup-u2f-device.btn.btn-info.btn-block{ disabled: true } Set up new U2F device
.col-md-8
%p.text-warning You need to register a two-factor authentication app before you can set up a U2F device.
diff --git a/app/views/users/calendar_activities.html.haml b/app/views/users/calendar_activities.html.haml
index 2d4656e8608..938cb579e9f 100644
--- a/app/views/users/calendar_activities.html.haml
+++ b/app/views/users/calendar_activities.html.haml
@@ -1,6 +1,5 @@
%h4.prepend-top-20
- Contributions for
- %strong= @calendar_date.to_s(:medium)
+ = _("Contributions for <strong>%{calendar_date}</strong>").html_safe % { calendar_date: @calendar_date.to_s(:medium) }
- if @events.any?
%ul.bordered-list
@@ -9,25 +8,28 @@
%span.light
%i.fa.fa-clock-o
= event.created_at.strftime('%-I:%M%P')
- - if event.push?
- #{event.action_name} #{event.ref_type}
+ - if event.visible_to_user?(current_user)
+ - if event.push?
+ #{event.action_name} #{event.ref_type}
+ %strong
+ - commits_path = project_commits_path(event.project, event.ref_name)
+ = link_to_if event.project.repository.branch_exists?(event.ref_name), event.ref_name, commits_path
+ - else
+ = event_action_name(event)
+ %strong
+ - if event.note?
+ = link_to event.note_target.to_reference, event_note_target_url(event), class: 'has-tooltip', title: event.target_title
+ - elsif event.target
+ = link_to event.target.to_reference, [event.project.namespace.becomes(Namespace), event.project, event.target], class: 'has-tooltip', title: event.target_title
+
+ at
%strong
- - commits_path = project_commits_path(event.project, event.ref_name)
- = link_to_if event.project.repository.branch_exists?(event.ref_name), event.ref_name, commits_path
+ - if event.project
+ = link_to_project(event.project)
+ - else
+ = event.project_name
- else
- = event_action_name(event)
- %strong
- - if event.note?
- = link_to event.note_target.to_reference, event_note_target_url(event), class: 'has-tooltip', title: event.target_title
- - elsif event.target
- = link_to event.target.to_reference, [event.project.namespace.becomes(Namespace), event.project, event.target], class: 'has-tooltip', title: event.target_title
-
- at
- %strong
- - if event.project
- = link_to_project event.project
- - else
- = event.project_name
+ made a private contribution
- else
%p
- No contributions found for #{@calendar_date.to_s(:medium)}
+ = _('No contributions were found')
diff --git a/app/workers/admin_email_worker.rb b/app/workers/admin_email_worker.rb
index 06324575ffc..f69e74b2674 100644
--- a/app/workers/admin_email_worker.rb
+++ b/app/workers/admin_email_worker.rb
@@ -10,10 +10,12 @@ class AdminEmailWorker
private
+ # rubocop: disable CodeReuse/ActiveRecord
def send_repository_check_mail
repository_check_failed_count = Project.where(last_repository_check_failed: true).count
return if repository_check_failed_count.zero?
RepositoryCheckMailer.notify(repository_check_failed_count).deliver_now
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index f95df7ecf03..1eeb972cee9 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -1,4 +1,6 @@
---
+- auto_devops:auto_devops_disable
+
- cronjob:admin_email
- cronjob:expire_build_artifacts
- cronjob:gitlab_usage_ping
@@ -85,6 +87,7 @@
- authorized_projects
- background_migration
- create_gpg_signature
+- delete_container_repository
- delete_merged_branches
- delete_user
- email_receiver
diff --git a/app/workers/archive_trace_worker.rb b/app/workers/archive_trace_worker.rb
index c6f89a17729..c1283e9b2fc 100644
--- a/app/workers/archive_trace_worker.rb
+++ b/app/workers/archive_trace_worker.rb
@@ -4,9 +4,11 @@ class ArchiveTraceWorker
include ApplicationWorker
include PipelineBackgroundQueue
+ # rubocop: disable CodeReuse/ActiveRecord
def perform(job_id)
Ci::Build.without_archived_trace.find_by(id: job_id).try do |job|
job.trace.archive!
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/authorized_projects_worker.rb b/app/workers/authorized_projects_worker.rb
index dd62bb0f33d..c9ddeb08613 100644
--- a/app/workers/authorized_projects_worker.rb
+++ b/app/workers/authorized_projects_worker.rb
@@ -12,9 +12,11 @@ class AuthorizedProjectsWorker
end
end
+ # rubocop: disable CodeReuse/ActiveRecord
def perform(user_id)
user = User.find_by(id: user_id)
user&.refresh_authorized_projects
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/auto_devops/disable_worker.rb b/app/workers/auto_devops/disable_worker.rb
new file mode 100644
index 00000000000..73ddc591505
--- /dev/null
+++ b/app/workers/auto_devops/disable_worker.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module AutoDevops
+ class DisableWorker
+ include ApplicationWorker
+ include AutoDevopsQueue
+
+ def perform(pipeline_id)
+ pipeline = Ci::Pipeline.find(pipeline_id)
+ project = pipeline.project
+
+ send_notification_email(pipeline, project) if disable_service(project).execute
+ end
+
+ private
+
+ def disable_service(project)
+ Projects::AutoDevops::DisableService.new(project)
+ end
+
+ def send_notification_email(pipeline, project)
+ recipients = email_receivers_for(pipeline, project)
+
+ return unless recipients.any?
+
+ NotificationService.new.autodevops_disabled(pipeline, recipients)
+ end
+
+ def email_receivers_for(pipeline, project)
+ recipients = [pipeline.user&.email]
+ recipients << project.owner.email unless project.group
+ recipients.uniq.compact
+ end
+ end
+end
diff --git a/app/workers/build_coverage_worker.rb b/app/workers/build_coverage_worker.rb
index 53d77dc4524..912c53e11f8 100644
--- a/app/workers/build_coverage_worker.rb
+++ b/app/workers/build_coverage_worker.rb
@@ -4,7 +4,9 @@ class BuildCoverageWorker
include ApplicationWorker
include PipelineQueue
+ # rubocop: disable CodeReuse/ActiveRecord
def perform(build_id)
Ci::Build.find_by(id: build_id)&.update_coverage
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/build_finished_worker.rb b/app/workers/build_finished_worker.rb
index 9dc2c7f3601..51cbbe8882e 100644
--- a/app/workers/build_finished_worker.rb
+++ b/app/workers/build_finished_worker.rb
@@ -6,6 +6,7 @@ class BuildFinishedWorker
queue_namespace :pipeline_processing
+ # rubocop: disable CodeReuse/ActiveRecord
def perform(build_id)
Ci::Build.find_by(id: build_id).try do |build|
# We execute that in sync as this access the files in order to access local file, and reduce IO
@@ -17,4 +18,5 @@ class BuildFinishedWorker
ArchiveTraceWorker.perform_async(build.id)
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/build_hooks_worker.rb b/app/workers/build_hooks_worker.rb
index f1f71dc589c..b0c3676714c 100644
--- a/app/workers/build_hooks_worker.rb
+++ b/app/workers/build_hooks_worker.rb
@@ -6,8 +6,10 @@ class BuildHooksWorker
queue_namespace :pipeline_hooks
+ # rubocop: disable CodeReuse/ActiveRecord
def perform(build_id)
Ci::Build.find_by(id: build_id)
.try(:execute_hooks)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/build_queue_worker.rb b/app/workers/build_queue_worker.rb
index 1b3f1fd3c2a..67d5b0f5f5b 100644
--- a/app/workers/build_queue_worker.rb
+++ b/app/workers/build_queue_worker.rb
@@ -6,9 +6,11 @@ class BuildQueueWorker
queue_namespace :pipeline_processing
+ # rubocop: disable CodeReuse/ActiveRecord
def perform(build_id)
Ci::Build.find_by(id: build_id).try do |build|
Ci::UpdateBuildQueueService.new.execute(build)
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/build_success_worker.rb b/app/workers/build_success_worker.rb
index e1c1cc24a94..c17608f7378 100644
--- a/app/workers/build_success_worker.rb
+++ b/app/workers/build_success_worker.rb
@@ -6,11 +6,13 @@ class BuildSuccessWorker
queue_namespace :pipeline_processing
+ # rubocop: disable CodeReuse/ActiveRecord
def perform(build_id)
Ci::Build.find_by(id: build_id).try do |build|
create_deployment(build) if build.has_environment?
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
private
diff --git a/app/workers/build_trace_sections_worker.rb b/app/workers/build_trace_sections_worker.rb
index f4114b3353c..0641130fd64 100644
--- a/app/workers/build_trace_sections_worker.rb
+++ b/app/workers/build_trace_sections_worker.rb
@@ -4,7 +4,9 @@ class BuildTraceSectionsWorker
include ApplicationWorker
include PipelineQueue
+ # rubocop: disable CodeReuse/ActiveRecord
def perform(build_id)
Ci::Build.find_by(id: build_id)&.parse_trace_sections!
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/ci/archive_traces_cron_worker.rb b/app/workers/ci/archive_traces_cron_worker.rb
index 7d4e9660a4e..7443aad1380 100644
--- a/app/workers/ci/archive_traces_cron_worker.rb
+++ b/app/workers/ci/archive_traces_cron_worker.rb
@@ -5,6 +5,7 @@ module Ci
include ApplicationWorker
include CronjobQueue
+ # rubocop: disable CodeReuse/ActiveRecord
def perform
# Archive stale live traces which still resides in redis or database
# This could happen when ArchiveTraceWorker sidekiq jobs were lost by receiving SIGKILL
@@ -19,6 +20,7 @@ module Ci
end
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
private
diff --git a/app/workers/ci/build_trace_chunk_flush_worker.rb b/app/workers/ci/build_trace_chunk_flush_worker.rb
index 9dbf2e5e1ac..23a11c28f9b 100644
--- a/app/workers/ci/build_trace_chunk_flush_worker.rb
+++ b/app/workers/ci/build_trace_chunk_flush_worker.rb
@@ -5,10 +5,12 @@ module Ci
include ApplicationWorker
include PipelineBackgroundQueue
+ # rubocop: disable CodeReuse/ActiveRecord
def perform(build_trace_chunk_id)
::Ci::BuildTraceChunk.find_by(id: build_trace_chunk_id).try do |build_trace_chunk|
build_trace_chunk.persist_data!
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/app/workers/concerns/auto_devops_queue.rb b/app/workers/concerns/auto_devops_queue.rb
new file mode 100644
index 00000000000..aba928ccaab
--- /dev/null
+++ b/app/workers/concerns/auto_devops_queue.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+#
+module AutoDevopsQueue
+ extend ActiveSupport::Concern
+
+ included do
+ queue_namespace :auto_devops
+ end
+end
diff --git a/app/workers/concerns/gitlab/github_import/rescheduling_methods.rb b/app/workers/concerns/gitlab/github_import/rescheduling_methods.rb
index 692ca6b7f42..1c6413674a0 100644
--- a/app/workers/concerns/gitlab/github_import/rescheduling_methods.rb
+++ b/app/workers/concerns/gitlab/github_import/rescheduling_methods.rb
@@ -8,6 +8,7 @@ module Gitlab
# project_id - The ID of the GitLab project to import the note into.
# hash - A Hash containing the details of the GitHub object to imoprt.
# notify_key - The Redis key to notify upon completion, if any.
+ # rubocop: disable CodeReuse/ActiveRecord
def perform(project_id, hash, notify_key = nil)
project = Project.find_by(id: project_id)
@@ -24,6 +25,7 @@ module Gitlab
.perform_in(client.rate_limit_resets_in, project.id, hash, notify_key)
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def try_import(*args)
import(*args)
diff --git a/app/workers/concerns/gitlab/github_import/stage_methods.rb b/app/workers/concerns/gitlab/github_import/stage_methods.rb
index 147c8c8d683..59e6bc2c97d 100644
--- a/app/workers/concerns/gitlab/github_import/stage_methods.rb
+++ b/app/workers/concerns/gitlab/github_import/stage_methods.rb
@@ -20,11 +20,13 @@ module Gitlab
self.class.perform_in(client.rate_limit_resets_in, project.id)
end
+ # rubocop: disable CodeReuse/ActiveRecord
def find_project(id)
# If the project has been marked as failed we want to bail out
# automatically.
Project.import_started.find_by(id: id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
end
diff --git a/app/workers/concerns/new_issuable.rb b/app/workers/concerns/new_issuable.rb
index 7735dec5e6b..a89451a4475 100644
--- a/app/workers/concerns/new_issuable.rb
+++ b/app/workers/concerns/new_issuable.rb
@@ -10,17 +10,21 @@ module NewIssuable
user && issuable
end
+ # rubocop: disable CodeReuse/ActiveRecord
def set_user(user_id)
@user = User.find_by(id: user_id) # rubocop:disable Gitlab/ModuleWithInstanceVariables
log_error(User, user_id) unless @user # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def set_issuable(issuable_id)
@issuable = issuable_class.find_by(id: issuable_id) # rubocop:disable Gitlab/ModuleWithInstanceVariables
log_error(issuable_class, issuable_id) unless @issuable # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
+ # rubocop: enable CodeReuse/ActiveRecord
def log_error(record_class, record_id)
Rails.logger.error("#{self.class}: couldn't find #{record_class} with ID=#{record_id}, skipping job")
diff --git a/app/workers/create_gpg_signature_worker.rb b/app/workers/create_gpg_signature_worker.rb
index a1aeeb7c4fc..49c7a403838 100644
--- a/app/workers/create_gpg_signature_worker.rb
+++ b/app/workers/create_gpg_signature_worker.rb
@@ -3,6 +3,7 @@
class CreateGpgSignatureWorker
include ApplicationWorker
+ # rubocop: disable CodeReuse/ActiveRecord
def perform(commit_shas, project_id)
# Older versions of GitPushService may push a single commit ID on the stack.
# We need this to be backwards compatible.
@@ -26,4 +27,5 @@ class CreateGpgSignatureWorker
end
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/delete_container_repository_worker.rb b/app/workers/delete_container_repository_worker.rb
new file mode 100644
index 00000000000..e8fe9d82797
--- /dev/null
+++ b/app/workers/delete_container_repository_worker.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+class DeleteContainerRepositoryWorker
+ include ApplicationWorker
+ include ExclusiveLeaseGuard
+
+ LEASE_TIMEOUT = 1.hour
+
+ attr_reader :container_repository
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def perform(current_user_id, container_repository_id)
+ current_user = User.find_by(id: current_user_id)
+ @container_repository = ContainerRepository.find_by(id: container_repository_id)
+ project = container_repository&.project
+
+ return unless current_user && container_repository && project
+
+ # If a user accidentally attempts to delete the same container registry in quick succession,
+ # this can lead to orphaned tags.
+ try_obtain_lease do
+ Projects::ContainerRepository::DestroyService.new(project, current_user).execute(container_repository)
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ # For ExclusiveLeaseGuard concern
+ def lease_key
+ @lease_key ||= "container_repository:delete:#{container_repository.id}"
+ end
+
+ # For ExclusiveLeaseGuard concern
+ def lease_timeout
+ LEASE_TIMEOUT
+ end
+end
diff --git a/app/workers/delete_diff_files_worker.rb b/app/workers/delete_diff_files_worker.rb
index 0874a0b75e8..f518dfe871c 100644
--- a/app/workers/delete_diff_files_worker.rb
+++ b/app/workers/delete_diff_files_worker.rb
@@ -3,6 +3,7 @@
class DeleteDiffFilesWorker
include ApplicationWorker
+ # rubocop: disable CodeReuse/ActiveRecord
def perform(merge_request_diff_id)
merge_request_diff = MergeRequestDiff.find(merge_request_diff_id)
@@ -16,4 +17,5 @@ class DeleteDiffFilesWorker
.delete_all
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/detect_repository_languages_worker.rb b/app/workers/detect_repository_languages_worker.rb
index 854b74b884a..64bc9776d48 100644
--- a/app/workers/detect_repository_languages_worker.rb
+++ b/app/workers/detect_repository_languages_worker.rb
@@ -11,6 +11,7 @@ class DetectRepositoryLanguagesWorker
attr_reader :project
+ # rubocop: disable CodeReuse/ActiveRecord
def perform(project_id, user_id)
@project = Project.find_by(id: project_id)
user = User.find_by(id: user_id)
@@ -20,6 +21,7 @@ class DetectRepositoryLanguagesWorker
::Projects::DetectRepositoryLanguagesService.new(project, user).execute
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
private
diff --git a/app/workers/expire_build_artifacts_worker.rb b/app/workers/expire_build_artifacts_worker.rb
index 5d3a9a39b93..dce812d1ae2 100644
--- a/app/workers/expire_build_artifacts_worker.rb
+++ b/app/workers/expire_build_artifacts_worker.rb
@@ -4,6 +4,7 @@ class ExpireBuildArtifactsWorker
include ApplicationWorker
include CronjobQueue
+ # rubocop: disable CodeReuse/ActiveRecord
def perform
Rails.logger.info 'Scheduling removal of build artifacts'
@@ -12,4 +13,5 @@ class ExpireBuildArtifactsWorker
ExpireBuildInstanceArtifactsWorker.bulk_perform_async(build_ids)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/expire_build_instance_artifacts_worker.rb b/app/workers/expire_build_instance_artifacts_worker.rb
index 3b57ecb36e3..4fcd1e5bd24 100644
--- a/app/workers/expire_build_instance_artifacts_worker.rb
+++ b/app/workers/expire_build_instance_artifacts_worker.rb
@@ -3,6 +3,7 @@
class ExpireBuildInstanceArtifactsWorker
include ApplicationWorker
+ # rubocop: disable CodeReuse/ActiveRecord
def perform(build_id)
build = Ci::Build
.with_expired_artifacts
@@ -14,4 +15,5 @@ class ExpireBuildInstanceArtifactsWorker
Rails.logger.info "Removing artifacts for build #{build.id}..."
build.erase_artifacts!
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/expire_job_cache_worker.rb b/app/workers/expire_job_cache_worker.rb
index 14a57b90114..b09d0a5d121 100644
--- a/app/workers/expire_job_cache_worker.rb
+++ b/app/workers/expire_job_cache_worker.rb
@@ -6,6 +6,7 @@ class ExpireJobCacheWorker
queue_namespace :pipeline_cache
+ # rubocop: disable CodeReuse/ActiveRecord
def perform(job_id)
job = CommitStatus.joins(:pipeline, :project).find_by(id: job_id)
return unless job
@@ -18,6 +19,7 @@ class ExpireJobCacheWorker
store.touch(project_job_path(project, job))
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
private
diff --git a/app/workers/expire_pipeline_cache_worker.rb b/app/workers/expire_pipeline_cache_worker.rb
index 992fc63c451..c96e8a0379b 100644
--- a/app/workers/expire_pipeline_cache_worker.rb
+++ b/app/workers/expire_pipeline_cache_worker.rb
@@ -6,6 +6,7 @@ class ExpirePipelineCacheWorker
queue_namespace :pipeline_cache
+ # rubocop: disable CodeReuse/ActiveRecord
def perform(pipeline_id)
pipeline = Ci::Pipeline.find_by(id: pipeline_id)
return unless pipeline
@@ -23,6 +24,7 @@ class ExpirePipelineCacheWorker
Gitlab::Cache::Ci::ProjectPipelineStatus.update_for_pipeline(pipeline)
end
+ # rubocop: enable CodeReuse/ActiveRecord
private
diff --git a/app/workers/gitlab/github_import/advance_stage_worker.rb b/app/workers/gitlab/github_import/advance_stage_worker.rb
index be0b6c180b0..cd2ceb8dcdf 100644
--- a/app/workers/gitlab/github_import/advance_stage_worker.rb
+++ b/app/workers/gitlab/github_import/advance_stage_worker.rb
@@ -63,12 +63,14 @@ module Gitlab
end
end
+ # rubocop: disable CodeReuse/ActiveRecord
def find_project(id)
# TODO: Only select the JID
# This is due to the fact that the JID could be present in either the project record or
# its associated import_state record
Project.import_started.find_by(id: id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
end
diff --git a/app/workers/gitlab/github_import/refresh_import_jid_worker.rb b/app/workers/gitlab/github_import/refresh_import_jid_worker.rb
index 68d2c5c4331..65473026b4c 100644
--- a/app/workers/gitlab/github_import/refresh_import_jid_worker.rb
+++ b/app/workers/gitlab/github_import/refresh_import_jid_worker.rb
@@ -30,12 +30,14 @@ module Gitlab
# stage, if it died there's nothing we can do anyway.
end
+ # rubocop: disable CodeReuse/ActiveRecord
def find_project(id)
# TODO: Only select the JID
# This is due to the fact that the JID could be present in either the project record or
# its associated import_state record
Project.import_started.find_by(id: id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
end
diff --git a/app/workers/invalid_gpg_signature_update_worker.rb b/app/workers/invalid_gpg_signature_update_worker.rb
index 4724ab7ad98..fc8a731b427 100644
--- a/app/workers/invalid_gpg_signature_update_worker.rb
+++ b/app/workers/invalid_gpg_signature_update_worker.rb
@@ -3,6 +3,7 @@
class InvalidGpgSignatureUpdateWorker
include ApplicationWorker
+ # rubocop: disable CodeReuse/ActiveRecord
def perform(gpg_key_id)
gpg_key = GpgKey.find_by(id: gpg_key_id)
@@ -10,4 +11,5 @@ class InvalidGpgSignatureUpdateWorker
Gitlab::Gpg::InvalidGpgSignatureUpdater.new(gpg_key).run
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/issue_due_scheduler_worker.rb b/app/workers/issue_due_scheduler_worker.rb
index c04a2d75e0b..476cba47ad7 100644
--- a/app/workers/issue_due_scheduler_worker.rb
+++ b/app/workers/issue_due_scheduler_worker.rb
@@ -4,9 +4,11 @@ class IssueDueSchedulerWorker
include ApplicationWorker
include CronjobQueue
+ # rubocop: disable CodeReuse/ActiveRecord
def perform
project_ids = Issue.opened.due_tomorrow.group(:project_id).pluck(:project_id).map { |id| [id] }
MailScheduler::IssueDueWorker.bulk_perform_async(project_ids)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/mail_scheduler/issue_due_worker.rb b/app/workers/mail_scheduler/issue_due_worker.rb
index 8794ad7a82c..1e1dde1e829 100644
--- a/app/workers/mail_scheduler/issue_due_worker.rb
+++ b/app/workers/mail_scheduler/issue_due_worker.rb
@@ -5,10 +5,12 @@ module MailScheduler
include ApplicationWorker
include MailSchedulerQueue
+ # rubocop: disable CodeReuse/ActiveRecord
def perform(project_id)
Issue.opened.due_tomorrow.in_projects(project_id).preload(:project).find_each do |issue|
notification_service.issue_due(issue)
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/app/workers/new_merge_request_worker.rb b/app/workers/new_merge_request_worker.rb
index 5d8b8904502..fa48c1b29a8 100644
--- a/app/workers/new_merge_request_worker.rb
+++ b/app/workers/new_merge_request_worker.rb
@@ -9,6 +9,8 @@ class NewMergeRequestWorker
EventCreateService.new.open_mr(issuable, user)
NotificationService.new.new_merge_request(issuable, user)
+
+ issuable.diffs(include_stats: false).write_cache
issuable.create_cross_references!(user)
end
diff --git a/app/workers/new_note_worker.rb b/app/workers/new_note_worker.rb
index 74f34dcf9aa..42f5b945a75 100644
--- a/app/workers/new_note_worker.rb
+++ b/app/workers/new_note_worker.rb
@@ -5,6 +5,7 @@ class NewNoteWorker
# Keep extra parameter to preserve backwards compatibility with
# old `NewNoteWorker` jobs (can remove later)
+ # rubocop: disable CodeReuse/ActiveRecord
def perform(note_id, _params = {})
if note = Note.find_by(id: note_id)
NotificationService.new.new_note(note)
@@ -13,4 +14,5 @@ class NewNoteWorker
Rails.logger.error("NewNoteWorker: couldn't find note with ID=#{note_id}, skipping job")
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/object_storage/migrate_uploads_worker.rb b/app/workers/object_storage/migrate_uploads_worker.rb
index 01d03ec7888..fe5d27b087d 100644
--- a/app/workers/object_storage/migrate_uploads_worker.rb
+++ b/app/workers/object_storage/migrate_uploads_worker.rb
@@ -57,11 +57,13 @@ module ObjectStorage
include Report
+ # rubocop: disable CodeReuse/ActiveRecord
def self.enqueue!(uploads, model_class, mounted_as, to_store)
sanity_check!(uploads, model_class, mounted_as)
perform_async(uploads.ids, model_class.to_s, mounted_as, to_store)
end
+ # rubocop: enable CodeReuse/ActiveRecord
# We need to be sure all the uploads are for the same uploader and model type
# and that the mount point exists if provided.
@@ -78,6 +80,7 @@ module ObjectStorage
raise(SanityCheckError, "Mount point #{mounted_as} not found in #{model_class}.") unless model_has_mount
end
+ # rubocop: disable CodeReuse/ActiveRecord
def perform(*args)
args_check!(args)
@@ -97,6 +100,7 @@ module ObjectStorage
# do not retry: the job is insane
Rails.logger.warn "#{self.class}: Sanity check error (#{e.message})"
end
+ # rubocop: enable CodeReuse/ActiveRecord
def sanity_check!(uploads)
self.class.sanity_check!(uploads, @model_class, @mounted_as)
diff --git a/app/workers/pages_domain_verification_worker.rb b/app/workers/pages_domain_verification_worker.rb
index 4610b688189..b3319ff5a13 100644
--- a/app/workers/pages_domain_verification_worker.rb
+++ b/app/workers/pages_domain_verification_worker.rb
@@ -3,6 +3,7 @@
class PagesDomainVerificationWorker
include ApplicationWorker
+ # rubocop: disable CodeReuse/ActiveRecord
def perform(domain_id)
domain = PagesDomain.find_by(id: domain_id)
@@ -10,4 +11,5 @@ class PagesDomainVerificationWorker
VerifyPagesDomainService.new(domain).execute
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/pages_worker.rb b/app/workers/pages_worker.rb
index 13a6576a301..fa0dfa2ff4b 100644
--- a/app/workers/pages_worker.rb
+++ b/app/workers/pages_worker.rb
@@ -9,6 +9,7 @@ class PagesWorker
send(action, *arg) # rubocop:disable GitlabSecurity/PublicSend
end
+ # rubocop: disable CodeReuse/ActiveRecord
def deploy(build_id)
build = Ci::Build.find_by(id: build_id)
result = Projects::UpdatePagesService.new(build.project, build).execute
@@ -18,6 +19,7 @@ class PagesWorker
result
end
+ # rubocop: enable CodeReuse/ActiveRecord
def remove(namespace_path, project_path)
full_path = File.join(Settings.pages.path, namespace_path, project_path)
diff --git a/app/workers/pipeline_hooks_worker.rb b/app/workers/pipeline_hooks_worker.rb
index 58023e0af1b..eae1115e60c 100644
--- a/app/workers/pipeline_hooks_worker.rb
+++ b/app/workers/pipeline_hooks_worker.rb
@@ -6,8 +6,10 @@ class PipelineHooksWorker
queue_namespace :pipeline_hooks
+ # rubocop: disable CodeReuse/ActiveRecord
def perform(pipeline_id)
Ci::Pipeline.find_by(id: pipeline_id)
.try(:execute_hooks)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/pipeline_metrics_worker.rb b/app/workers/pipeline_metrics_worker.rb
index a97019b100a..c2fbfd2b3a5 100644
--- a/app/workers/pipeline_metrics_worker.rb
+++ b/app/workers/pipeline_metrics_worker.rb
@@ -4,12 +4,14 @@ class PipelineMetricsWorker
include ApplicationWorker
include PipelineQueue
+ # rubocop: disable CodeReuse/ActiveRecord
def perform(pipeline_id)
Ci::Pipeline.find_by(id: pipeline_id).try do |pipeline|
update_metrics_for_active_pipeline(pipeline) if pipeline.active?
update_metrics_for_succeeded_pipeline(pipeline) if pipeline.success?
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
private
@@ -21,9 +23,11 @@ class PipelineMetricsWorker
metrics(pipeline).update_all(latest_build_started_at: pipeline.started_at, latest_build_finished_at: pipeline.finished_at, pipeline_id: pipeline.id)
end
+ # rubocop: disable CodeReuse/ActiveRecord
def metrics(pipeline)
MergeRequest::Metrics.where(merge_request_id: merge_requests(pipeline))
end
+ # rubocop: enable CodeReuse/ActiveRecord
def merge_requests(pipeline)
pipeline.merge_requests.map(&:id)
diff --git a/app/workers/pipeline_notification_worker.rb b/app/workers/pipeline_notification_worker.rb
index 3a8846b3747..e4a18573d20 100644
--- a/app/workers/pipeline_notification_worker.rb
+++ b/app/workers/pipeline_notification_worker.rb
@@ -4,6 +4,7 @@ class PipelineNotificationWorker
include ApplicationWorker
include PipelineQueue
+ # rubocop: disable CodeReuse/ActiveRecord
def perform(pipeline_id, recipients = nil)
pipeline = Ci::Pipeline.find_by(id: pipeline_id)
@@ -11,4 +12,5 @@ class PipelineNotificationWorker
NotificationService.new.pipeline_finished(pipeline, recipients)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/pipeline_process_worker.rb b/app/workers/pipeline_process_worker.rb
index 83744c5338a..f2aa17acb51 100644
--- a/app/workers/pipeline_process_worker.rb
+++ b/app/workers/pipeline_process_worker.rb
@@ -6,8 +6,10 @@ class PipelineProcessWorker
queue_namespace :pipeline_processing
+ # rubocop: disable CodeReuse/ActiveRecord
def perform(pipeline_id)
Ci::Pipeline.find_by(id: pipeline_id)
.try(:process!)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/pipeline_schedule_worker.rb b/app/workers/pipeline_schedule_worker.rb
index a1815757735..85d1ffe0fa9 100644
--- a/app/workers/pipeline_schedule_worker.rb
+++ b/app/workers/pipeline_schedule_worker.rb
@@ -4,6 +4,7 @@ class PipelineScheduleWorker
include ApplicationWorker
include CronjobQueue
+ # rubocop: disable CodeReuse/ActiveRecord
def perform
Ci::PipelineSchedule.active.where("next_run_at < ?", Time.now)
.preload(:owner, :project).find_each do |schedule|
@@ -21,4 +22,5 @@ class PipelineScheduleWorker
end
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/pipeline_success_worker.rb b/app/workers/pipeline_success_worker.rb
index 68e9af6a619..4f349ed922c 100644
--- a/app/workers/pipeline_success_worker.rb
+++ b/app/workers/pipeline_success_worker.rb
@@ -6,6 +6,7 @@ class PipelineSuccessWorker
queue_namespace :pipeline_processing
+ # rubocop: disable CodeReuse/ActiveRecord
def perform(pipeline_id)
Ci::Pipeline.find_by(id: pipeline_id).try do |pipeline|
MergeRequests::MergeWhenPipelineSucceedsService
@@ -13,4 +14,5 @@ class PipelineSuccessWorker
.trigger(pipeline)
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/pipeline_update_worker.rb b/app/workers/pipeline_update_worker.rb
index c33468c1f14..13a748e1551 100644
--- a/app/workers/pipeline_update_worker.rb
+++ b/app/workers/pipeline_update_worker.rb
@@ -6,8 +6,10 @@ class PipelineUpdateWorker
queue_namespace :pipeline_processing
+ # rubocop: disable CodeReuse/ActiveRecord
def perform(pipeline_id)
Ci::Pipeline.find_by(id: pipeline_id)
.try(:update_status)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/process_commit_worker.rb b/app/workers/process_commit_worker.rb
index c9f6df9b56d..7b167c95c29 100644
--- a/app/workers/process_commit_worker.rb
+++ b/app/workers/process_commit_worker.rb
@@ -14,6 +14,7 @@ class ProcessCommitWorker
# commit_hash - Hash containing commit details to use for constructing a
# Commit object without having to use the Git repository.
# default - The data was pushed to the default branch.
+ # rubocop: disable CodeReuse/ActiveRecord
def perform(project_id, user_id, commit_hash, default = false)
project = Project.find_by(id: project_id)
@@ -30,6 +31,7 @@ class ProcessCommitWorker
process_commit_message(project, commit, user, author, default)
update_issue_metrics(commit, author)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def process_commit_message(project, commit, user, author, default = false)
# Ignore closing references from GitLab-generated commit messages.
@@ -50,6 +52,7 @@ class ProcessCommitWorker
end
end
+ # rubocop: disable CodeReuse/ActiveRecord
def update_issue_metrics(commit, author)
mentioned_issues = commit.all_references(author).issues
@@ -58,6 +61,7 @@ class ProcessCommitWorker
Issue::Metrics.where(issue_id: mentioned_issues.map(&:id), first_mentioned_in_commit_at: nil)
.update_all(first_mentioned_in_commit_at: commit.committed_date)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def build_commit(project, hash)
date_suffix = '_date'
diff --git a/app/workers/project_cache_worker.rb b/app/workers/project_cache_worker.rb
index b0e1d8837d9..d27b5e62574 100644
--- a/app/workers/project_cache_worker.rb
+++ b/app/workers/project_cache_worker.rb
@@ -12,6 +12,7 @@ class ProjectCacheWorker
# CHANGELOG.
# statistics - An Array containing columns from ProjectStatistics to
# refresh, if empty all columns will be refreshed
+ # rubocop: disable CodeReuse/ActiveRecord
def perform(project_id, files = [], statistics = [])
project = Project.find_by(id: project_id)
@@ -23,6 +24,7 @@ class ProjectCacheWorker
project.cleanup
end
+ # rubocop: enable CodeReuse/ActiveRecord
def update_statistics(project, statistics = [])
return unless try_obtain_lease_for(project.id, :update_statistics)
diff --git a/app/workers/project_migrate_hashed_storage_worker.rb b/app/workers/project_migrate_hashed_storage_worker.rb
index ad0003e7bff..4c6339f7701 100644
--- a/app/workers/project_migrate_hashed_storage_worker.rb
+++ b/app/workers/project_migrate_hashed_storage_worker.rb
@@ -5,6 +5,7 @@ class ProjectMigrateHashedStorageWorker
LEASE_TIMEOUT = 30.seconds.to_i
+ # rubocop: disable CodeReuse/ActiveRecord
def perform(project_id, old_disk_path = nil)
project = Project.find_by(id: project_id)
return if project.nil? || project.pending_delete?
@@ -19,6 +20,7 @@ class ProjectMigrateHashedStorageWorker
cancel_lease_for(project_id, uuid) if uuid
raise ex
end
+ # rubocop: enable CodeReuse/ActiveRecord
def lease_for(project_id)
Gitlab::ExclusiveLease.new(lease_key(project_id), timeout: LEASE_TIMEOUT)
diff --git a/app/workers/project_service_worker.rb b/app/workers/project_service_worker.rb
index a0bc9288cf0..25567cec08b 100644
--- a/app/workers/project_service_worker.rb
+++ b/app/workers/project_service_worker.rb
@@ -7,6 +7,10 @@ class ProjectServiceWorker
def perform(hook_id, data)
data = data.with_indifferent_access
- Service.find(hook_id).execute(data)
+ service = Service.find(hook_id)
+ service.execute(data)
+ rescue => error
+ service_class = service&.class&.name || "Not Found"
+ logger.error class: self.class.name, service_class: service_class, message: error.message
end
end
diff --git a/app/workers/propagate_service_template_worker.rb b/app/workers/propagate_service_template_worker.rb
index c9da1cae255..3ccd7615697 100644
--- a/app/workers/propagate_service_template_worker.rb
+++ b/app/workers/propagate_service_template_worker.rb
@@ -6,11 +6,13 @@ class PropagateServiceTemplateWorker
LEASE_TIMEOUT = 4.hours.to_i
+ # rubocop: disable CodeReuse/ActiveRecord
def perform(template_id)
return unless try_obtain_lease_for(template_id)
Projects::PropagateServiceTemplate.propagate(Service.find_by(id: template_id))
end
+ # rubocop: enable CodeReuse/ActiveRecord
private
diff --git a/app/workers/prune_old_events_worker.rb b/app/workers/prune_old_events_worker.rb
index c1d05ebbcfd..d44ad0d8030 100644
--- a/app/workers/prune_old_events_worker.rb
+++ b/app/workers/prune_old_events_worker.rb
@@ -4,6 +4,7 @@ class PruneOldEventsWorker
include ApplicationWorker
include CronjobQueue
+ # rubocop: disable CodeReuse/ActiveRecord
def perform
# Contribution calendar shows maximum 12 months of events.
# Double nested query is used because MySQL doesn't allow DELETE subqueries
@@ -17,4 +18,5 @@ class PruneOldEventsWorker
.limit(10_000))
.delete_all
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/prune_web_hook_logs_worker.rb b/app/workers/prune_web_hook_logs_worker.rb
index 45c7d32f7eb..38054069f4e 100644
--- a/app/workers/prune_web_hook_logs_worker.rb
+++ b/app/workers/prune_web_hook_logs_worker.rb
@@ -9,6 +9,7 @@ class PruneWebHookLogsWorker
# The maximum number of rows to remove in a single job.
DELETE_LIMIT = 50_000
+ # rubocop: disable CodeReuse/ActiveRecord
def perform
# MySQL doesn't allow "DELETE FROM ... WHERE id IN ( ... )" if the inner
# query refers to the same table. To work around this we wrap the IN body in
@@ -23,4 +24,5 @@ class PruneWebHookLogsWorker
)
.delete_all
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/reactive_caching_worker.rb b/app/workers/reactive_caching_worker.rb
index 9b331f15dc5..96ff8cd6222 100644
--- a/app/workers/reactive_caching_worker.rb
+++ b/app/workers/reactive_caching_worker.rb
@@ -3,6 +3,7 @@
class ReactiveCachingWorker
include ApplicationWorker
+ # rubocop: disable CodeReuse/ActiveRecord
def perform(class_name, id, *args)
klass = begin
Kernel.const_get(class_name)
@@ -13,4 +14,5 @@ class ReactiveCachingWorker
klass.find_by(id: id).try(:exclusively_update_reactive_cache!, *args)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/repository_check/batch_worker.rb b/app/workers/repository_check/batch_worker.rb
index 07559ea479b..c1bb1adc9cc 100644
--- a/app/workers/repository_check/batch_worker.rb
+++ b/app/workers/repository_check/batch_worker.rb
@@ -59,22 +59,28 @@ module RepositoryCheck
never_checked_project_ids(BATCH_SIZE) + old_checked_project_ids(BATCH_SIZE)
end
+ # rubocop: disable CodeReuse/ActiveRecord
def never_checked_project_ids(batch_size)
projects_on_shard.where(last_repository_check_at: nil)
.where('created_at < ?', 24.hours.ago)
.limit(batch_size).pluck(:id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def old_checked_project_ids(batch_size)
projects_on_shard.where.not(last_repository_check_at: nil)
.where('last_repository_check_at < ?', 1.month.ago)
.reorder(last_repository_check_at: :asc)
.limit(batch_size).pluck(:id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def projects_on_shard
Project.where(repository_storage: shard_name)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def try_obtain_lease_for_project(id)
# Use a 24-hour timeout because on servers/projects where 'git fsck' is
diff --git a/app/workers/repository_check/clear_worker.rb b/app/workers/repository_check/clear_worker.rb
index 81e1a4b63bb..01964c69fb2 100644
--- a/app/workers/repository_check/clear_worker.rb
+++ b/app/workers/repository_check/clear_worker.rb
@@ -5,6 +5,7 @@ module RepositoryCheck
include ApplicationWorker
include RepositoryCheckQueue
+ # rubocop: disable CodeReuse/ActiveRecord
def perform
# Do small batched updates because these updates will be slow and locking
Project.select(:id).find_in_batches(batch_size: 100) do |batch|
@@ -14,5 +15,6 @@ module RepositoryCheck
)
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/app/workers/repository_check/single_repository_worker.rb b/app/workers/repository_check/single_repository_worker.rb
index f44e5693b25..a8097af321f 100644
--- a/app/workers/repository_check/single_repository_worker.rb
+++ b/app/workers/repository_check/single_repository_worker.rb
@@ -48,9 +48,11 @@ module RepositoryCheck
false
end
+ # rubocop: disable CodeReuse/ActiveRecord
def has_changes?(project)
Project.with_push.exists?(project.id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def has_wiki_changes?(project)
return false unless project.wiki_enabled?
diff --git a/app/workers/run_pipeline_schedule_worker.rb b/app/workers/run_pipeline_schedule_worker.rb
index 1f6cb18c812..f72331c003a 100644
--- a/app/workers/run_pipeline_schedule_worker.rb
+++ b/app/workers/run_pipeline_schedule_worker.rb
@@ -6,6 +6,7 @@ class RunPipelineScheduleWorker
queue_namespace :pipeline_creation
+ # rubocop: disable CodeReuse/ActiveRecord
def perform(schedule_id, user_id)
schedule = Ci::PipelineSchedule.find_by(id: schedule_id)
user = User.find_by(id: user_id)
@@ -14,6 +15,7 @@ class RunPipelineScheduleWorker
run_pipeline_schedule(schedule, user)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def run_pipeline_schedule(schedule, user)
Ci::CreatePipelineService.new(schedule.project,
diff --git a/app/workers/stage_update_worker.rb b/app/workers/stage_update_worker.rb
index ec8c8e3689f..ea587789d03 100644
--- a/app/workers/stage_update_worker.rb
+++ b/app/workers/stage_update_worker.rb
@@ -6,9 +6,11 @@ class StageUpdateWorker
queue_namespace :pipeline_processing
+ # rubocop: disable CodeReuse/ActiveRecord
def perform(stage_id)
Ci::Stage.find_by(id: stage_id).try do |stage|
stage.update_status
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/stuck_ci_jobs_worker.rb b/app/workers/stuck_ci_jobs_worker.rb
index c78b7fac589..f6bca1176d1 100644
--- a/app/workers/stuck_ci_jobs_worker.rb
+++ b/app/workers/stuck_ci_jobs_worker.rb
@@ -46,6 +46,7 @@ class StuckCiJobsWorker
end
end
+ # rubocop: disable CodeReuse/ActiveRecord
def search(status, timeout)
loop do
jobs = Ci::Build.where(status: status)
@@ -60,6 +61,7 @@ class StuckCiJobsWorker
end
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
def drop_build(type, build, status, timeout)
Rails.logger.info "#{self.class}: Dropping #{type} build #{build.id} for runner #{build.runner_id} (status: #{status}, timeout: #{timeout})"
diff --git a/app/workers/stuck_import_jobs_worker.rb b/app/workers/stuck_import_jobs_worker.rb
index 79ce06dd66e..de92f3eca6a 100644
--- a/app/workers/stuck_import_jobs_worker.rb
+++ b/app/workers/stuck_import_jobs_worker.rb
@@ -23,6 +23,7 @@ class StuckImportJobsWorker
end.count
end
+ # rubocop: disable CodeReuse/ActiveRecord
def mark_projects_with_jid_as_failed!
# TODO: Rollback this change to use SQL through #pluck
jids_and_ids = enqueued_projects_with_jid.map { |project| [project.import_jid, project.id] }.to_h
@@ -43,18 +44,25 @@ class StuckImportJobsWorker
project.mark_import_as_failed(error_message)
end.count
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def enqueued_projects
Project.joins_import_state.where("(import_state.status = 'scheduled' OR import_state.status = 'started') OR (projects.import_status = 'scheduled' OR projects.import_status = 'started')")
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def enqueued_projects_with_jid
enqueued_projects.where.not("import_state.jid IS NULL AND projects.import_jid IS NULL")
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def enqueued_projects_without_jid
enqueued_projects.where("import_state.jid IS NULL AND projects.import_jid IS NULL")
end
+ # rubocop: enable CodeReuse/ActiveRecord
def error_message
"Import timed out. Import took longer than #{IMPORT_JOBS_EXPIRATION} seconds"
diff --git a/app/workers/stuck_merge_jobs_worker.rb b/app/workers/stuck_merge_jobs_worker.rb
index b0a62f76e94..98c81956cba 100644
--- a/app/workers/stuck_merge_jobs_worker.rb
+++ b/app/workers/stuck_merge_jobs_worker.rb
@@ -4,6 +4,7 @@ class StuckMergeJobsWorker
include ApplicationWorker
include CronjobQueue
+ # rubocop: disable CodeReuse/ActiveRecord
def perform
stuck_merge_requests.find_in_batches(batch_size: 100) do |group|
jids = group.map(&:merge_jid)
@@ -18,9 +19,11 @@ class StuckMergeJobsWorker
end
end
end
+ # rubocop: enable CodeReuse/ActiveRecord
private
+ # rubocop: disable CodeReuse/ActiveRecord
def apply_current_state!(completed_jids, completed_ids)
merge_requests = MergeRequest.where(id: completed_ids)
@@ -34,8 +37,11 @@ class StuckMergeJobsWorker
Rails.logger.info("Updated state of locked merge jobs. JIDs: #{completed_jids.join(', ')}")
end
+ # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
def stuck_merge_requests
MergeRequest.select('id, merge_jid').with_state(:locked).where.not(merge_jid: nil).reorder(nil)
end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/update_head_pipeline_for_merge_request_worker.rb b/app/workers/update_head_pipeline_for_merge_request_worker.rb
index 0487a393566..9ce51662969 100644
--- a/app/workers/update_head_pipeline_for_merge_request_worker.rb
+++ b/app/workers/update_head_pipeline_for_merge_request_worker.rb
@@ -6,6 +6,7 @@ class UpdateHeadPipelineForMergeRequestWorker
queue_namespace :pipeline_processing
+ # rubocop: disable CodeReuse/ActiveRecord
def perform(merge_request_id)
merge_request = MergeRequest.find(merge_request_id)
pipeline = Ci::Pipeline.where(project: merge_request.source_project, ref: merge_request.source_branch).last
@@ -20,6 +21,7 @@ class UpdateHeadPipelineForMergeRequestWorker
merge_request.update_attribute(:head_pipeline_id, pipeline.id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def log_error_message_for(merge_request)
Rails.logger.error(
diff --git a/app/workers/update_merge_requests_worker.rb b/app/workers/update_merge_requests_worker.rb
index 742841219b3..c7213df652a 100644
--- a/app/workers/update_merge_requests_worker.rb
+++ b/app/workers/update_merge_requests_worker.rb
@@ -5,6 +5,7 @@ class UpdateMergeRequestsWorker
LOG_TIME_THRESHOLD = 90 # seconds
+ # rubocop: disable CodeReuse/ActiveRecord
def perform(project_id, user_id, oldrev, newrev, ref)
project = Project.find_by(id: project_id)
return unless project
@@ -28,4 +29,5 @@ class UpdateMergeRequestsWorker
Rails.logger.info("UpdateMergeRequestsWorker#perform #{args_log}") if time.real > LOG_TIME_THRESHOLD
end
+ # rubocop: enable CodeReuse/ActiveRecord
end