summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndreas Kämmerle <andreas.kaemmerle@gmail.com>2018-09-13 12:00:59 +0200
committerAndreas Kämmerle <andreas.kaemmerle@gmail.com>2018-09-13 12:00:59 +0200
commit87d01cc3bfc12c08024881dc8f13101e5430e3e6 (patch)
tree05e82defa51500257f97e3530862da03668895e3
parentfef5c923c009c64183cc43909444a1bdaba218b7 (diff)
parent71f3d48544cc0857c8d470b182f7e809de6a4642 (diff)
downloadgitlab-ce-consolidate-page-widths-part02.tar.gz
Merge branch 'master' of https://gitlab.com/gitlab-org/gitlab-ce into consolidate-page-widths-part02consolidate-page-widths-part02
-rw-r--r--.eslintrc.yml9
-rw-r--r--.gitignore1
-rw-r--r--.gitlab-ci.yml6
-rw-r--r--.gitlab/CODEOWNERS2
-rw-r--r--Dangerfile2
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--Gemfile4
-rw-r--r--Gemfile.lock19
-rw-r--r--Gemfile.rails5.lock17
-rw-r--r--LICENSE18
-rw-r--r--app/assets/javascripts/badges/components/badge.vue4
-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/boards/components/board_list.vue6
-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/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/commit/pipelines/pipelines_table.vue4
-rw-r--r--app/assets/javascripts/commons/gitlab_ui.js14
-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/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/diff_discussions.vue10
-rw-r--r--app/assets/javascripts/diffs/components/diff_file.vue39
-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.vue8
-rw-r--r--app/assets/javascripts/diffs/components/diff_table_cell.vue37
-rw-r--r--app/assets/javascripts/diffs/components/inline_diff_comment_row.vue11
-rw-r--r--app/assets/javascripts/diffs/components/inline_diff_table_row.vue7
-rw-r--r--app/assets/javascripts/diffs/components/inline_diff_view.vue21
-rw-r--r--app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue50
-rw-r--r--app/assets/javascripts/diffs/components/parallel_diff_table_row.vue119
-rw-r--r--app/assets/javascripts/diffs/components/parallel_diff_view.vue41
-rw-r--r--app/assets/javascripts/diffs/store/actions.js61
-rw-r--r--app/assets/javascripts/diffs/store/getters.js55
-rw-r--r--app/assets/javascripts/diffs/store/mutation_types.js2
-rw-r--r--app/assets/javascripts/diffs/store/mutations.js130
-rw-r--r--app/assets/javascripts/diffs/store/utils.js55
-rw-r--r--app/assets/javascripts/dismissable_callout.js (renamed from app/assets/javascripts/clusters/components/gcp_signup_offer.js)4
-rw-r--r--app/assets/javascripts/dropzone_input.js23
-rw-r--r--app/assets/javascripts/environments/components/container.vue6
-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_table.vue4
-rw-r--r--app/assets/javascripts/environments/mixins/environments_mixin.js2
-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/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.vue7
-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.vue6
-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_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/jobs/list.vue6
-rw-r--r--app/assets/javascripts/ide/components/jobs/stage.vue4
-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/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/jobs/components/header.vue6
-rw-r--r--app/assets/javascripts/jobs/components/sidebar_details_block.vue6
-rw-r--r--app/assets/javascripts/jobs/store/mutations.js2
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js47
-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.js2
-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/mixins/monitoring_mixins.js15
-rw-r--r--app/assets/javascripts/monitoring/utils/multiple_time_series.js30
-rw-r--r--app/assets/javascripts/notes.js53
-rw-r--r--app/assets/javascripts/notes/components/note_actions.vue19
-rw-r--r--app/assets/javascripts/notes/components/note_awards_list.vue2
-rw-r--r--app/assets/javascripts/notes/components/note_form.vue9
-rw-r--r--app/assets/javascripts/notes/components/note_header.vue13
-rw-r--r--app/assets/javascripts/notes/components/noteable_discussion.vue17
-rw-r--r--app/assets/javascripts/notes/components/noteable_note.vue1
-rw-r--r--app/assets/javascripts/notes/components/notes_app.vue3
-rw-r--r--app/assets/javascripts/notes/stores/actions.js63
-rw-r--r--app/assets/javascripts/notes/stores/getters.js14
-rw-r--r--app/assets/javascripts/notes/stores/mutations.js27
-rw-r--r--app/assets/javascripts/notes/stores/utils.js21
-rw-r--r--app/assets/javascripts/pages/dashboard/groups/index/index.js4
-rw-r--r--app/assets/javascripts/pages/groups/show/group_tabs.js136
-rw-r--r--app/assets/javascripts/pages/groups/show/index.js12
-rw-r--r--app/assets/javascripts/pages/projects/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/project.js7
-rw-r--r--app/assets/javascripts/pages/projects/wikis/components/delete_wiki_modal.vue69
-rw-r--r--app/assets/javascripts/pages/projects/wikis/index.js12
-rw-r--r--app/assets/javascripts/pipelines/components/graph/graph_component.vue6
-rw-r--r--app/assets/javascripts/pipelines/components/header_component.vue6
-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.vue4
-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/registry/components/app.vue6
-rw-r--r--app/assets/javascripts/registry/components/collapsible_container.vue12
-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/sidebar/components/participants/participants.vue6
-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/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.vue4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue4
-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/header_ci_component.vue2
-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/toggle_button.vue4
-rw-r--r--app/assets/javascripts/vue_shared/vue_resource_interceptor.js2
-rw-r--r--app/assets/stylesheets/framework.scss1
-rw-r--r--app/assets/stylesheets/framework/avatar.scss3
-rw-r--r--app/assets/stylesheets/framework/buttons.scss6
-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.scss4
-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/markdown_area.scss2
-rw-r--r--app/assets/stylesheets/framework/mixins.scss6
-rw-r--r--app/assets/stylesheets/framework/selects.scss4
-rw-r--r--app/assets/stylesheets/framework/toggle.scss4
-rw-r--r--app/assets/stylesheets/framework/typography.scss4
-rw-r--r--app/assets/stylesheets/framework/variables.scss86
-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.scss69
-rw-r--r--app/assets/stylesheets/pages/branches.scss4
-rw-r--r--app/assets/stylesheets/pages/diff.scss5
-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.scss56
-rw-r--r--app/assets/stylesheets/pages/help.scss4
-rw-r--r--app/assets/stylesheets/pages/issuable.scss4
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss2
-rw-r--r--app/assets/stylesheets/pages/notes.scss4
-rw-r--r--app/assets/stylesheets/pages/projects.scss4
-rw-r--r--app/assets/stylesheets/pages/settings.scss2
-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/admin/application_settings_controller.rb24
-rw-r--r--app/controllers/admin/logs_controller.rb3
-rw-r--r--app/controllers/application_controller.rb26
-rw-r--r--app/controllers/concerns/issuable_actions.rb1
-rw-r--r--app/controllers/concerns/notes_actions.rb1
-rw-r--r--app/controllers/groups/labels_controller.rb12
-rw-r--r--app/controllers/groups_controller.rb8
-rw-r--r--app/controllers/profiles_controller.rb1
-rw-r--r--app/controllers/projects/labels_controller.rb7
-rw-r--r--app/controllers/projects/merge_requests/diffs_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb1
-rw-r--r--app/controllers/projects/registry/repositories_controller.rb16
-rw-r--r--app/controllers/projects_controller.rb14
-rw-r--r--app/finders/group_descendants_finder.rb2
-rw-r--r--app/finders/labels_finder.rb6
-rw-r--r--app/finders/projects_finder.rb5
-rw-r--r--app/finders/user_recent_events_finder.rb16
-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.rb8
-rw-r--r--app/helpers/application_settings_helper.rb9
-rw-r--r--app/helpers/auth_helper.rb2
-rw-r--r--app/helpers/auto_devops_helper.rb2
-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.rb2
-rw-r--r--app/helpers/calendar_helper.rb2
-rw-r--r--app/helpers/ci_status_helper.rb2
-rw-r--r--app/helpers/clusters_helper.rb2
-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.rb11
-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.rb2
-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.rb20
-rw-r--r--app/helpers/import_helper.rb2
-rw-r--r--app/helpers/instance_configuration_helper.rb2
-rw-r--r--app/helpers/issuables_helper.rb10
-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.rb2
-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.rb18
-rw-r--r--app/helpers/milestones_routing_helper.rb2
-rw-r--r--app/helpers/mirror_helper.rb2
-rw-r--r--app/helpers/namespaces_helper.rb2
-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.rb2
-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.rb25
-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.rb2
-rw-r--r--app/helpers/search_helper.rb2
-rw-r--r--app/helpers/selects_helper.rb28
-rw-r--r--app/helpers/sentry_helper.rb2
-rw-r--r--app/helpers/services_helper.rb2
-rw-r--r--app/helpers/sidekiq_helper.rb2
-rw-r--r--app/helpers/snippets_helper.rb2
-rw-r--r--app/helpers/sorting_helper.rb13
-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.rb6
-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.rb6
-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.rb10
-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/notify.rb1
-rw-r--r--app/mailers/previews/notify_preview.rb4
-rw-r--r--app/models/application_setting.rb9
-rw-r--r--app/models/blob_viewer/gitlab_ci_yml.rb8
-rw-r--r--app/models/ci/build.rb1
-rw-r--r--app/models/ci/pipeline.rb8
-rw-r--r--app/models/ci/trigger_request.rb2
-rw-r--r--app/models/clusters/applications/jupyter.rb9
-rw-r--r--app/models/concerns/issuable.rb4
-rw-r--r--app/models/concerns/noteable.rb19
-rw-r--r--app/models/concerns/project_services_loggable.rb28
-rw-r--r--app/models/concerns/storage/legacy_namespace.rb4
-rw-r--r--app/models/event.rb8
-rw-r--r--app/models/hooks/active_hook_filter.rb2
-rw-r--r--app/models/label.rb4
-rw-r--r--app/models/label_note.rb97
-rw-r--r--app/models/license_template.rb2
-rw-r--r--app/models/namespace.rb12
-rw-r--r--app/models/note.rb13
-rw-r--r--app/models/project.rb35
-rw-r--r--app/models/project_services/asana_service.rb2
-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/prometheus_metric.rb89
-rw-r--r--app/models/repository.rb21
-rw-r--r--app/models/resource_label_event.rb101
-rw-r--r--app/models/service.rb1
-rw-r--r--app/models/system_note_metadata.rb2
-rw-r--r--app/models/user.rb22
-rw-r--r--app/models/user_callout.rb3
-rw-r--r--app/presenters/commit_status_presenter.rb8
-rw-r--r--app/serializers/build_details_entity.rb6
-rw-r--r--app/serializers/detailed_status_entity.rb (renamed from app/serializers/status_entity.rb)10
-rw-r--r--app/serializers/discussion_entity.rb1
-rw-r--r--app/serializers/job_entity.rb2
-rw-r--r--app/serializers/job_group_entity.rb2
-rw-r--r--app/serializers/note_entity.rb10
-rw-r--r--app/serializers/pipeline_entity.rb2
-rw-r--r--app/serializers/project_note_entity.rb4
-rw-r--r--app/serializers/stage_entity.rb2
-rw-r--r--app/serializers/trigger_variable_entity.rb7
-rw-r--r--app/services/application_settings/update_service.rb8
-rw-r--r--app/services/emails/base_service.rb2
-rw-r--r--app/services/emails/create_service.rb7
-rw-r--r--app/services/issuable/common_system_notes_service.rb9
-rw-r--r--app/services/issues/move_service.rb13
-rw-r--r--app/services/labels/promote_service.rb7
-rw-r--r--app/services/merge_requests/reload_diffs_service.rb4
-rw-r--r--app/services/notification_service.rb6
-rw-r--r--app/services/projects/auto_devops/disable_service.rb39
-rw-r--r--app/services/projects/container_repository/destroy_service.rb13
-rw-r--r--app/services/projects/destroy_service.rb2
-rw-r--r--app/services/projects/update_remote_mirror_service.rb5
-rw-r--r--app/services/quick_actions/interpret_service.rb24
-rw-r--r--app/services/resource_events/change_labels_service.rb3
-rw-r--r--app/services/resource_events/merge_into_notes_service.rb55
-rw-r--r--app/services/submit_usage_ping_service.rb1
-rw-r--r--app/services/system_note_service.rb57
-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/validators/branch_filter_validator.rb2
-rw-r--r--app/validators/js_regex_validator.rb2
-rw-r--r--app/views/admin/application_settings/_account_and_limit.html.haml5
-rw-r--r--app/views/admin/applications/index.html.haml2
-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/_shared_projects.html.haml8
-rw-r--r--app/views/groups/_subgroups_and_projects.html.haml8
-rw-r--r--app/views/groups/labels/index.html.haml1
-rw-r--r--app/views/groups/show.html.haml37
-rw-r--r--app/views/import/gitlab_projects/new.html.haml9
-rw-r--r--app/views/layouts/_page.html.haml1
-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/show.html.haml67
-rw-r--r--app/views/projects/_flash_messages.html.haml1
-rw-r--r--app/views/projects/_new_project_fields.html.haml14
-rw-r--r--app/views/projects/blob/viewers/_gitlab_ci_yml.html.haml4
-rw-r--r--app/views/projects/clusters/_banner.html.haml23
-rw-r--r--app/views/projects/clusters/_integration_form.html.haml20
-rw-r--r--app/views/projects/clusters/show.html.haml3
-rw-r--r--app/views/projects/find_file/show.html.haml2
-rw-r--r--app/views/projects/labels/index.html.haml1
-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/index.html.haml16
-rw-r--r--app/views/projects/wikis/_form.html.haml2
-rw-r--r--app/views/projects/wikis/edit.html.haml17
-rw-r--r--app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml9
-rw-r--r--app/views/shared/_ping_consent.html.haml12
-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/labels/_sort_dropdown.html.haml9
-rw-r--r--app/views/users/calendar_activities.html.haml42
-rw-r--r--app/workers/all_queues.yml3
-rw-r--r--app/workers/auto_devops/disable_worker.rb35
-rw-r--r--app/workers/concerns/auto_devops_queue.rb9
-rw-r--r--app/workers/delete_container_repository_worker.rb34
-rw-r--r--app/workers/new_merge_request_worker.rb2
-rw-r--r--app/workers/project_service_worker.rb6
-rw-r--r--changelogs/unreleased/21371-avatar-fix.yml5
-rw-r--r--changelogs/unreleased/21617-initialize-projects-with-readme.yml5
-rw-r--r--changelogs/unreleased/2934-create-new-project-re-add-project-name-field.yml5
-rw-r--r--changelogs/unreleased/38208-due-dates-system-notes.yml5
-rw-r--r--changelogs/unreleased/39923-automatically-disable-auto-devops-for-project.yml5
-rw-r--r--changelogs/unreleased/42861-move-include-external-files-in-gitlab-ci-yml-from-starter-to-libre.yml5
-rw-r--r--changelogs/unreleased/44005-improve-handling-of-projects-shared-with-a-group.yml5
-rw-r--r--changelogs/unreleased/48167-fix-outdated-discussions-new-datastructure.yml5
-rw-r--r--changelogs/unreleased/48778-remove-old-storage-logic-from-import-export.yml5
-rw-r--r--changelogs/unreleased/48902-fix-diff-vertical-alignment.yml5
-rw-r--r--changelogs/unreleased/49632-clean-up-the-top-section-of-the-cluster-page.yml5
-rw-r--r--changelogs/unreleased/49644-make-margin-of-user-status-emoji-consistent.yml5
-rw-r--r--changelogs/unreleased/49943-resolve-filter-bar-height-changes.yml5
-rw-r--r--changelogs/unreleased/50535-display-banner-to-notify-user-if-project-is-implicitly-opted-ado.yml5
-rw-r--r--changelogs/unreleased/50677-fix-cherry-pick-branch-empty-name.yml5
-rw-r--r--changelogs/unreleased/50678-ignores-project-pending-delete.yml5
-rw-r--r--changelogs/unreleased/50835-add-filtering-sorting-for-labels-on-labels-page.yml5
-rw-r--r--changelogs/unreleased/50989-add-trigger-information-to-job-api.yml5
-rw-r--r--changelogs/unreleased/51112-add-status-illustration-in-job-api.yml5
-rw-r--r--changelogs/unreleased/51180-update-ffi-to-1-9-25.yml5
-rw-r--r--changelogs/unreleased/51281-on-master-diff-view-contains-extra-and-signs.yml5
-rw-r--r--changelogs/unreleased/51318-project-export-broken-when-avatar-is-set.yml5
-rw-r--r--changelogs/unreleased/api-promote-find-branch.yml5
-rw-r--r--changelogs/unreleased/clean-gitlab-git.yml5
-rw-r--r--changelogs/unreleased/da-synchronize-the-default-branch-when-updating-a-remote-mirror.yml5
-rw-r--r--changelogs/unreleased/ee-6381-multiseries.yml5
-rw-r--r--changelogs/unreleased/feat-update-contribution-calendar.yml5
-rw-r--r--changelogs/unreleased/fix-mention-in-edit-mr.yml5
-rw-r--r--changelogs/unreleased/fix-namespace-upload.yml5
-rw-r--r--changelogs/unreleased/fix-namespace-uploader.yml5
-rw-r--r--changelogs/unreleased/fj-51194-fix-wiki-attachments-with-whitespaces.yml5
-rw-r--r--changelogs/unreleased/frozen-string-enable-app-helpers.yml5
-rw-r--r--changelogs/unreleased/frozen-string-enable-vestigial.yml5
-rw-r--r--changelogs/unreleased/ide-commit-panel-improved.yml5
-rw-r--r--changelogs/unreleased/ide-file-templates.yml5
-rw-r--r--changelogs/unreleased/import-all-common-metrics-into-database.yml5
-rw-r--r--changelogs/unreleased/issue_50488.yml5
-rw-r--r--changelogs/unreleased/issue_50528.yml5
-rw-r--r--changelogs/unreleased/label-event.yml6
-rw-r--r--changelogs/unreleased/lock-unlock-quick-actions.yml5
-rw-r--r--changelogs/unreleased/osw-send-max-patch-bytes-to-gitaly.yml5
-rw-r--r--changelogs/unreleased/osw-write-cache-upon-mr-creation-and-cache-refactoring.yml5
-rw-r--r--changelogs/unreleased/rails5-fix-issue-move-service.yml6
-rw-r--r--changelogs/unreleased/rd-26044-new-option-to-prevent-too-big-git-pushes.yml5
-rw-r--r--changelogs/unreleased/remove-zebra-table-background.yml5
-rw-r--r--changelogs/unreleased/sh-allow-key-id-in-params.yml5
-rw-r--r--changelogs/unreleased/sh-delete-container-registry-async.yml5
-rw-r--r--changelogs/unreleased/sh-remove-orphaned-label-links.yml5
-rw-r--r--changelogs/unreleased/sh-support-adding-confirmed-emails.yml5
-rw-r--r--changelogs/unreleased/sh-upgrade-katex-0-9-0.yml5
-rw-r--r--changelogs/unreleased/update-gitlab-shell.yml5
-rw-r--r--changelogs/unreleased/usage_consent.yml5
-rw-r--r--changelogs/unreleased/zj-cleanup-port-gitaly.yml5
-rw-r--r--config/application.rb10
-rw-r--r--config/environments/test.rb4
-rw-r--r--config/initializers/active_record_avoid_type_casting_in_uniqueness_validator.rb2
-rw-r--r--config/initializers/static_files.rb23
-rw-r--r--config/karma.config.js15
-rw-r--r--config/prometheus/common_metrics.yml (renamed from config/prometheus/additional_metrics.yml)74
-rw-r--r--config/routes/group.rb3
-rw-r--r--config/sidekiq_queues.yml2
-rw-r--r--danger/commit_messages/Dangerfile192
-rw-r--r--danger/documentation/Dangerfile37
-rw-r--r--db/fixtures/development/17_cycle_analytics.rb14
-rw-r--r--db/fixtures/development/99_common_metrics.rb5
-rw-r--r--db/fixtures/production/999_common_metrics.rb5
-rw-r--r--db/importers/common_metrics_importer.rb103
-rw-r--r--db/migrate/20180101160629_create_prometheus_metrics.rb18
-rw-r--r--db/migrate/20180101160630_change_project_id_for_prometheus_metrics.rb11
-rw-r--r--db/migrate/20180228172924_add_include_private_contributions_to_users.rb7
-rw-r--r--db/migrate/20180720023512_add_receive_max_input_size_to_application_settings.rb11
-rw-r--r--db/migrate/20180831164904_fix_prometheus_metric_query_limits.rb19
-rw-r--r--db/migrate/20180831164905_add_common_to_prometheus_metrics.rb17
-rw-r--r--db/migrate/20180831164907_add_index_on_common_for_prometheus_metrics.rb17
-rw-r--r--db/migrate/20180831164908_add_identifier_to_prometheus_metric.rb11
-rw-r--r--db/migrate/20180831164909_add_index_for_identifier_to_prometheus_metric.rb17
-rw-r--r--db/migrate/20180831164910_import_common_metrics.rb17
-rw-r--r--db/migrate/20180901171833_add_project_config_source_status_index_to_pipeline.rb17
-rw-r--r--db/migrate/20180901200537_add_resource_label_event_reference_fields.rb11
-rw-r--r--db/migrate/20180906101639_add_user_ping_consent_to_application_settings.rb19
-rw-r--r--db/migrate/prometheus_metrics_limits_to_mysql.rb12
-rw-r--r--db/post_migrate/20180816193530_rename_login_root_namespaces.rb8
-rw-r--r--db/post_migrate/20180906051323_remove_orphaned_label_links.rb43
-rw-r--r--db/schema.rb31
-rw-r--r--doc/administration/logs.md13
-rw-r--r--doc/administration/monitoring/prometheus/index.md8
-rw-r--r--doc/administration/raketasks/project_import_export.md7
-rw-r--r--doc/api/README.md1
-rw-r--r--doc/api/jobs.md36
-rw-r--r--doc/api/projects.md1
-rw-r--r--doc/api/resource_label_events.md175
-rw-r--r--doc/api/users.md1
-rw-r--r--doc/ci/caching/index.md69
-rw-r--r--doc/ci/docker/using_docker_build.md26
-rw-r--r--doc/ci/interactive_web_terminal/index.md6
-rw-r--r--doc/ci/pipelines.md47
-rw-r--r--doc/ci/variables/README.md16
-rw-r--r--doc/ci/variables/where_variables_can_be_used.md27
-rw-r--r--doc/ci/yaml/README.md185
-rw-r--r--doc/development/README.md2
-rw-r--r--doc/development/code_review.md5
-rw-r--r--doc/development/contributing/design.md10
-rw-r--r--doc/development/diffs.md16
-rw-r--r--doc/development/documentation/index.md9
-rw-r--r--doc/development/documentation/styleguide.md80
-rw-r--r--doc/development/performance.md2
-rw-r--r--doc/development/permissions.md63
-rw-r--r--doc/development/prometheus_metrics.md48
-rw-r--r--doc/install/kubernetes/index.md23
-rw-r--r--doc/raketasks/backup_restore.md16
-rw-r--r--doc/topics/autodevops/index.md6
-rw-r--r--doc/user/gitlab_com/index.md2
-rw-r--r--doc/user/profile/index.md12
-rw-r--r--doc/user/project/integrations/prometheus_library/metrics.md6
-rw-r--r--doc/user/project/pipelines/settings.md4
-rw-r--r--doc/user/project/quick_actions.md2
-rw-r--r--doc/user/project/web_ide/index.md9
-rw-r--r--doc/workflow/todos.md2
-rw-r--r--lib/api/api.rb1
-rw-r--r--lib/api/branches.rb18
-rw-r--r--lib/api/commits.rb9
-rw-r--r--lib/api/entities.rb14
-rw-r--r--lib/api/files.rb10
-rw-r--r--lib/api/helpers.rb14
-rw-r--r--lib/api/helpers/projects_helpers.rb1
-rw-r--r--lib/api/internal.rb8
-rw-r--r--lib/api/pipeline_schedules.rb2
-rw-r--r--lib/api/project_export.rb8
-rw-r--r--lib/api/resource_label_events.rb52
-rw-r--r--lib/api/triggers.rb2
-rw-r--r--lib/api/users.rb1
-rw-r--r--lib/banzai/filter/spaced_link_filter.rb53
-rw-r--r--lib/banzai/pipeline/gfm_pipeline.rb1
-rw-r--r--lib/banzai/pipeline/label_pipeline.rb14
-rw-r--r--lib/banzai/pipeline/wiki_pipeline.rb1
-rw-r--r--lib/banzai/renderer/common_mark/html.rb14
-rw-r--r--lib/gitlab/ci/config.rb21
-rw-r--r--lib/gitlab/ci/external/file/base.rb29
-rw-r--r--lib/gitlab/ci/external/file/local.rb34
-rw-r--r--lib/gitlab/ci/external/file/remote.rb30
-rw-r--r--lib/gitlab/ci/external/mapper.rb32
-rw-r--r--lib/gitlab/ci/external/processor.rb52
-rw-r--r--lib/gitlab/ci/status/build/failed.rb8
-rw-r--r--lib/gitlab/contributions_calendar.rb12
-rw-r--r--lib/gitlab/current_settings.rb4
-rw-r--r--lib/gitlab/diff/file_collection/base.rb10
-rw-r--r--lib/gitlab/diff/file_collection/merge_request_diff.rb63
-rw-r--r--lib/gitlab/diff/highlight_cache.rb68
-rw-r--r--lib/gitlab/fake_application_settings.rb24
-rw-r--r--lib/gitlab/git/committer_with_hooks.rb47
-rw-r--r--lib/gitlab/git/diff.rb76
-rw-r--r--lib/gitlab/git/diff_collection.rb5
-rw-r--r--lib/gitlab/git/gitlab_projects.rb253
-rw-r--r--lib/gitlab/git/hook.rb108
-rw-r--r--lib/gitlab/git/hooks_service.rb35
-rw-r--r--lib/gitlab/git/index.rb150
-rw-r--r--lib/gitlab/git/operation_service.rb173
-rw-r--r--lib/gitlab/git/popen.rb112
-rw-r--r--lib/gitlab/git/repository.rb217
-rw-r--r--lib/gitlab/git/tree.rb45
-rw-r--r--lib/gitlab/git/version.rb2
-rw-r--r--lib/gitlab/git/wiki.rb14
-rw-r--r--lib/gitlab/git_access.rb6
-rw-r--r--lib/gitlab/gitaly_client/commit_service.rb2
-rw-r--r--lib/gitlab/gitaly_client/remote_service.rb14
-rw-r--r--lib/gitlab/gitaly_client/storage_service.rb8
-rw-r--r--lib/gitlab/import_export.rb4
-rw-r--r--lib/gitlab/import_export/after_export_strategies/base_after_export_strategy.rb6
-rw-r--r--lib/gitlab/import_export/after_export_strategies/web_upload_strategy.rb16
-rw-r--r--lib/gitlab/import_export/avatar_restorer.rb2
-rw-r--r--lib/gitlab/import_export/avatar_saver.rb11
-rw-r--r--lib/gitlab/import_export/import_export.yml14
-rw-r--r--lib/gitlab/import_export/importer.rb2
-rw-r--r--lib/gitlab/import_export/project_tree_restorer.rb2
-rw-r--r--lib/gitlab/import_export/saver.rb16
-rw-r--r--lib/gitlab/import_export/uploads_manager.rb13
-rw-r--r--lib/gitlab/import_export/uploads_restorer.rb24
-rw-r--r--lib/gitlab/import_export/uploads_saver.rb2
-rw-r--r--lib/gitlab/project_service_logger.rb7
-rw-r--r--lib/gitlab/prometheus/additional_metrics_parser.rb2
-rw-r--r--lib/gitlab/prometheus/metric_group.rb5
-rw-r--r--lib/gitlab/shell.rb9
-rw-r--r--lib/gitlab/template_helper.rb17
-rw-r--r--lib/gitlab/workhorse.rb11
-rw-r--r--lib/tasks/gitlab/cleanup.rake89
-rw-r--r--lib/tasks/gitlab/update_templates.rake4
-rw-r--r--lib/tasks/migrate/add_limits_mysql.rake2
-rw-r--r--locale/gitlab.pot272
-rw-r--r--package.json11
-rw-r--r--public/robots.txt2
-rw-r--r--qa/Gemfile.lock2
-rw-r--r--qa/README.md8
-rw-r--r--qa/qa.rb2
-rw-r--r--qa/qa/git/repository.rb9
-rw-r--r--qa/qa/page/component/groups_filter.rb4
-rw-r--r--qa/qa/page/dashboard/groups.rb5
-rw-r--r--qa/qa/page/group/show.rb4
-rw-r--r--qa/qa/page/main/login.rb25
-rw-r--r--qa/qa/page/project/new.rb3
-rw-r--r--qa/qa/runtime/env.rb22
-rw-r--r--qa/qa/runtime/user.rb18
-rw-r--r--qa/qa/scenario/test/sanity/framework.rb (renamed from qa/qa/scenario/test/sanity/failing.rb)7
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb2
-rw-r--r--qa/qa/specs/features/sanity/failing_spec.rb13
-rw-r--r--qa/qa/specs/features/sanity/framework_spec.rb23
-rw-r--r--qa/qa/specs/runner.rb7
-rw-r--r--qa/spec/runtime/env_spec.rb21
-rw-r--r--qa/spec/scenario/test/sanity/framework_spec.rb5
-rw-r--r--qa/spec/specs/runner_spec.rb60
-rw-r--r--spec/bin/changelog_spec.rb1
-rw-r--r--spec/controllers/admin/application_settings_controller_spec.rb7
-rw-r--r--spec/controllers/groups_controller_spec.rb8
-rw-r--r--spec/controllers/projects/jobs_controller_spec.rb65
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb14
-rw-r--r--spec/controllers/projects/notes_controller_spec.rb2
-rw-r--r--spec/controllers/projects/registry/repositories_controller_spec.rb5
-rw-r--r--spec/controllers/projects_controller_spec.rb30
-rw-r--r--spec/db/development/import_common_metrics_spec.rb15
-rw-r--r--spec/db/importers/common_metrics_importer_spec.rb131
-rw-r--r--spec/db/production/import_common_metrics_spec.rb15
-rw-r--r--spec/factories/ci/pipelines.rb8
-rw-r--r--spec/factories/projects.rb20
-rw-r--r--spec/factories/prometheus_metrics.rb18
-rw-r--r--spec/factories/resource_label_events.rb7
-rw-r--r--spec/features/commits/user_uses_quick_actions_spec.rb (renamed from spec/features/commits/user_uses_slash_commands_spec.rb)0
-rw-r--r--spec/features/dashboard/groups_list_spec.rb4
-rw-r--r--spec/features/explore/new_menu_spec.rb4
-rw-r--r--spec/features/groups/labels/sort_labels_spec.rb48
-rw-r--r--spec/features/instance_statistics/cohorts_spec.rb2
-rw-r--r--spec/features/issues/resource_label_events_spec.rb60
-rw-r--r--spec/features/issues/user_uses_quick_actions_spec.rb (renamed from spec/features/issues/user_uses_slash_commands_spec.rb)0
-rw-r--r--spec/features/merge_request/user_posts_diff_notes_spec.rb7
-rw-r--r--spec/features/merge_request/user_uses_quick_actions_spec.rb (renamed from spec/features/merge_request/user_uses_slash_commands_spec.rb)0
-rw-r--r--spec/features/projects/activity/user_sees_private_activity_spec.rb35
-rw-r--r--spec/features/projects/import_export/export_file_spec.rb1
-rw-r--r--spec/features/projects/import_export/import_file_object_storage_spec.rb103
-rw-r--r--spec/features/projects/import_export/import_file_spec.rb18
-rw-r--r--spec/features/projects/import_export/namespace_export_file_spec.rb68
-rw-r--r--spec/features/projects/import_export/test_project_export.tar.gzbin3368 -> 3360 bytes
-rw-r--r--spec/features/projects/labels/sort_labels_spec.rb48
-rw-r--r--spec/features/projects/members/invite_group_spec.rb (renamed from spec/features/projects/members/share_with_group_spec.rb)21
-rw-r--r--spec/features/projects/new_project_spec.rb5
-rw-r--r--spec/features/projects/settings/user_manages_group_links_spec.rb6
-rw-r--r--spec/features/projects/show/user_interacts_with_auto_devops_banner_spec.rb61
-rw-r--r--spec/features/projects/user_creates_project_spec.rb5
-rw-r--r--spec/features/projects/wiki/user_deletes_wiki_page_spec.rb2
-rw-r--r--spec/features/projects_spec.rb2
-rw-r--r--spec/features/usage_stats_consent_spec.rb45
-rw-r--r--spec/finders/contributed_projects_finder_spec.rb10
-rw-r--r--spec/finders/group_descendants_finder_spec.rb9
-rw-r--r--spec/finders/projects_finder_spec.rb7
-rw-r--r--spec/finders/user_recent_events_finder_spec.rb46
-rw-r--r--spec/fixtures/api/schemas/job/job.json2
-rw-r--r--spec/fixtures/api/schemas/job/job_details.json7
-rw-r--r--spec/fixtures/api/schemas/job/trigger.json28
-rw-r--r--spec/fixtures/api/schemas/pipeline_stage.json2
-rw-r--r--spec/fixtures/api/schemas/status/action.json22
-rw-r--r--spec/fixtures/api/schemas/status/ci_detailed_status.json (renamed from spec/fixtures/api/schemas/ci_detailed_status.json)26
-rw-r--r--spec/fixtures/api/schemas/status/illustration.json19
-rw-r--r--spec/fixtures/gitlab/ci/external_files/.gitlab-ci-template-1.yml10
-rw-r--r--spec/helpers/issuables_helper_spec.rb2
-rw-r--r--spec/javascripts/boards/mock_data.js2
-rw-r--r--spec/javascripts/boards/utils/query_data_spec.js27
-rw-r--r--spec/javascripts/diffs/components/diff_file_spec.js4
-rw-r--r--spec/javascripts/diffs/components/diff_line_gutter_content_spec.js46
-rw-r--r--spec/javascripts/diffs/components/parallel_diff_view_spec.js8
-rw-r--r--spec/javascripts/diffs/mock_data/diff_discussions.js10
-rw-r--r--spec/javascripts/diffs/mock_data/diff_file.js16
-rw-r--r--spec/javascripts/diffs/store/actions_spec.js230
-rw-r--r--spec/javascripts/diffs/store/getters_spec.js78
-rw-r--r--spec/javascripts/diffs/store/mutations_spec.js140
-rw-r--r--spec/javascripts/diffs/store/utils_spec.js106
-rw-r--r--spec/javascripts/dropzone_input_spec.js68
-rw-r--r--spec/javascripts/gfm_auto_complete_spec.js2
-rw-r--r--spec/javascripts/groups/components/app_spec.js105
-rw-r--r--spec/javascripts/helpers/vue_resource_helper.js1
-rw-r--r--spec/javascripts/ide/components/commit_sidebar/list_item_spec.js2
-rw-r--r--spec/javascripts/ide/components/commit_sidebar/stage_button_spec.js4
-rw-r--r--spec/javascripts/ide/components/file_templates/bar_spec.js117
-rw-r--r--spec/javascripts/ide/components/file_templates/dropdown_spec.js201
-rw-r--r--spec/javascripts/ide/components/repo_commit_section_spec.js4
-rw-r--r--spec/javascripts/ide/helpers.js2
-rw-r--r--spec/javascripts/ide/stores/actions/file_spec.js15
-rw-r--r--spec/javascripts/ide/stores/actions_spec.js26
-rw-r--r--spec/javascripts/ide/stores/modules/file_templates/actions_spec.js83
-rw-r--r--spec/javascripts/ide/stores/modules/file_templates/getters_spec.js51
-rw-r--r--spec/javascripts/ide/stores/mutations_spec.js8
-rw-r--r--spec/javascripts/lazy_loader_spec.js12
-rw-r--r--spec/javascripts/lib/utils/common_utils_spec.js40
-rw-r--r--spec/javascripts/lib/utils/mock_data.js2
-rw-r--r--spec/javascripts/lib/utils/text_utility_spec.js6
-rw-r--r--spec/javascripts/monitoring/graph/flag_spec.js2
-rw-r--r--spec/javascripts/monitoring/graph_spec.js3
-rw-r--r--spec/javascripts/notes/components/note_actions_spec.js18
-rw-r--r--spec/javascripts/notes/components/note_awards_list_spec.js4
-rw-r--r--spec/javascripts/notes/components/note_form_spec.js18
-rw-r--r--spec/javascripts/notes/components/note_header_spec.js14
-rw-r--r--spec/javascripts/notes/mock_data.js46
-rw-r--r--spec/javascripts/projects/project_new_spec.js30
-rw-r--r--spec/javascripts/test_bundle.js5
-rw-r--r--spec/javascripts/vue_shared/components/file_icon_spec.js8
-rw-r--r--spec/javascripts/vue_shared/components/loading_icon_spec.js54
-rw-r--r--spec/javascripts/vue_shared/components/notes/system_note_spec.js2
-rw-r--r--spec/javascripts/vue_shared/components/pagination_links_spec.js72
-rw-r--r--spec/lib/api/helpers/pagination_spec.rb2
-rw-r--r--spec/lib/banzai/filter/markdown_filter_spec.rb23
-rw-r--r--spec/lib/banzai/filter/spaced_link_filter_spec.rb86
-rw-r--r--spec/lib/banzai/pipeline/gfm_pipeline_spec.rb18
-rw-r--r--spec/lib/banzai/pipeline/wiki_pipeline_spec.rb21
-rw-r--r--spec/lib/forever_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config_spec.rb233
-rw-r--r--spec/lib/gitlab/ci/external/file/local_spec.rb78
-rw-r--r--spec/lib/gitlab/ci/external/file/remote_spec.rb114
-rw-r--r--spec/lib/gitlab/ci/external/mapper_spec.rb96
-rw-r--r--spec/lib/gitlab/ci/external/processor_spec.rb182
-rw-r--r--spec/lib/gitlab/cleanup/project_uploads_spec.rb2
-rw-r--r--spec/lib/gitlab/contributions_calendar_spec.rb17
-rw-r--r--spec/lib/gitlab/diff/highlight_cache_spec.rb70
-rw-r--r--spec/lib/gitlab/git/committer_with_hooks_spec.rb156
-rw-r--r--spec/lib/gitlab/git/diff_spec.rb82
-rw-r--r--spec/lib/gitlab/git/gitlab_projects_spec.rb321
-rw-r--r--spec/lib/gitlab/git/hook_spec.rb111
-rw-r--r--spec/lib/gitlab/git/hooks_service_spec.rb50
-rw-r--r--spec/lib/gitlab/git/index_spec.rb239
-rw-r--r--spec/lib/gitlab/git/popen_spec.rb179
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb774
-rw-r--r--spec/lib/gitlab/gitaly_client/commit_service_spec.rb18
-rw-r--r--spec/lib/gitlab/gitaly_client/remote_service_spec.rb20
-rw-r--r--spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_object_storage_spec.rb105
-rw-r--r--spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb3
-rw-r--r--spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb31
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml9
-rw-r--r--spec/lib/gitlab/import_export/avatar_restorer_spec.rb33
-rw-r--r--spec/lib/gitlab/import_export/avatar_saver_spec.rb5
-rw-r--r--spec/lib/gitlab/import_export/file_importer_object_storage_spec.rb89
-rw-r--r--spec/lib/gitlab/import_export/file_importer_spec.rb12
-rw-r--r--spec/lib/gitlab/import_export/importer_object_storage_spec.rb115
-rw-r--r--spec/lib/gitlab/import_export/importer_spec.rb15
-rw-r--r--spec/lib/gitlab/import_export/model_configuration_spec.rb42
-rw-r--r--spec/lib/gitlab/import_export/project.json33
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb8
-rw-r--r--spec/lib/gitlab/import_export/project_tree_saver_spec.rb11
-rw-r--r--spec/lib/gitlab/import_export/repo_restorer_spec.rb6
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml21
-rw-r--r--spec/lib/gitlab/import_export/saver_spec.rb24
-rw-r--r--spec/lib/gitlab/import_export/uploads_manager_spec.rb43
-rw-r--r--spec/lib/gitlab/import_export/uploads_restorer_spec.rb10
-rw-r--r--spec/lib/gitlab/import_export/uploads_saver_spec.rb1
-rw-r--r--spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/metric_group_spec.rb41
-rw-r--r--spec/lib/gitlab/shell_spec.rb5
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb16
-rw-r--r--spec/mailers/emails/auto_devops_spec.rb32
-rw-r--r--spec/migrations/import_common_metrics_spec.rb16
-rw-r--r--spec/migrations/remove_orphaned_label_links_spec.rb40
-rw-r--r--spec/models/application_setting_spec.rb30
-rw-r--r--spec/models/blob_viewer/gitlab_ci_yml_spec.rb10
-rw-r--r--spec/models/ci/pipeline_spec.rb26
-rw-r--r--spec/models/clusters/applications/jupyter_spec.rb13
-rw-r--r--spec/models/label_note_spec.rb23
-rw-r--r--spec/models/namespace_spec.rb32
-rw-r--r--spec/models/project_services/jira_service_spec.rb6
-rw-r--r--spec/models/project_spec.rb99
-rw-r--r--spec/models/prometheus_metric_spec.rb98
-rw-r--r--spec/models/repository_spec.rb243
-rw-r--r--spec/models/resource_label_event_spec.rb52
-rw-r--r--spec/models/service_spec.rb27
-rw-r--r--spec/models/user_spec.rb42
-rw-r--r--spec/requests/api/commits_spec.rb8
-rw-r--r--spec/requests/api/internal_spec.rb20
-rw-r--r--spec/requests/api/project_export_spec.rb33
-rw-r--r--spec/requests/api/project_import_spec.rb1
-rw-r--r--spec/requests/api/projects_spec.rb27
-rw-r--r--spec/requests/api/resource_label_events_spec.rb75
-rw-r--r--spec/requests/api/users_spec.rb17
-rw-r--r--spec/serializers/detailed_status_entity_spec.rb (renamed from spec/serializers/status_entity_spec.rb)2
-rw-r--r--spec/services/issuable/common_system_notes_service_spec.rb13
-rw-r--r--spec/services/issues/move_service_spec.rb11
-rw-r--r--spec/services/issues/update_service_spec.rb9
-rw-r--r--spec/services/merge_requests/reload_diffs_service_spec.rb1
-rw-r--r--spec/services/merge_requests/update_service_spec.rb9
-rw-r--r--spec/services/notification_service_spec.rb17
-rw-r--r--spec/services/projects/auto_devops/disable_service_spec.rb100
-rw-r--r--spec/services/projects/container_repository/destroy_service_spec.rb40
-rw-r--r--spec/services/projects/update_remote_mirror_service_spec.rb127
-rw-r--r--spec/services/quick_actions/interpret_service_spec.rb52
-rw-r--r--spec/services/resource_events/change_labels_service_spec.rb8
-rw-r--r--spec/services/resource_events/merge_into_notes_service_spec.rb70
-rw-r--r--spec/services/system_note_service_spec.rb65
-rw-r--r--spec/services/wikis/create_attachment_service_spec.rb24
-rw-r--r--spec/spec_helper.rb4
-rw-r--r--spec/support/features/issuable_quick_actions_shared_examples.rb (renamed from spec/support/features/issuable_slash_commands_shared_examples.rb)84
-rw-r--r--spec/support/helpers/git_helpers.rb11
-rw-r--r--spec/support/helpers/test_env.rb4
-rw-r--r--spec/support/import_export/export_file_helper.rb2
-rw-r--r--spec/support/shared_examples/features/editable_merge_request_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/instance_statistics_controllers_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/label_note_shared_examples.rb109
-rw-r--r--spec/support/sidekiq.rb20
-rw-r--r--spec/tasks/gitlab/cleanup_rake_spec.rb35
-rw-r--r--spec/uploaders/avatar_uploader_spec.rb8
-rw-r--r--spec/uploaders/namespace_file_uploader_spec.rb20
-rw-r--r--spec/workers/auto_devops/disable_worker_spec.rb57
-rw-r--r--spec/workers/delete_container_repository_worker_spec.rb33
-rw-r--r--spec/workers/project_service_worker_spec.rb25
-rw-r--r--vendor/Dockerfile/Node-alpine.Dockerfile18
-rw-r--r--vendor/Dockerfile/OpenJDK.Dockerfile14
-rw-r--r--vendor/Dockerfile/Ruby-alpine.Dockerfile6
-rw-r--r--vendor/gitignore/Global/Diff.gitignore2
-rw-r--r--vendor/gitignore/Global/JetBrains.gitignore3
-rw-r--r--vendor/gitignore/Global/MicrosoftOffice.gitignore3
-rw-r--r--vendor/gitignore/KiCad.gitignore1
-rw-r--r--vendor/gitignore/Processing.gitignore2
-rw-r--r--vendor/gitignore/Python.gitignore7
-rw-r--r--vendor/gitignore/Rails.gitignore12
-rw-r--r--vendor/gitignore/Swift.gitignore7
-rw-r--r--vendor/gitignore/Symfony.gitignore4
-rw-r--r--vendor/gitignore/TeX.gitignore3
-rw-r--r--vendor/gitignore/Terraform.gitignore11
-rw-r--r--vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml16
-rw-r--r--vendor/gitlab-ci-yml/Maven.gitlab-ci.yml2
-rw-r--r--vendor/gitlab-ci-yml/Pages/Middleman.gitlab-ci.yml13
-rw-r--r--vendor/gitlab-ci-yml/Swift.gitlab-ci.yml2
-rw-r--r--vendor/licenses.csv530
-rw-r--r--yarn.lock1166
849 files changed, 11656 insertions, 8697 deletions
diff --git a/.eslintrc.yml b/.eslintrc.yml
index 77b1b72fe68..ebf8048f19c 100644
--- a/.eslintrc.yml
+++ b/.eslintrc.yml
@@ -33,6 +33,15 @@ rules:
- error
- max: 1
promise/catch-or-return: error
+ no-param-reassign:
+ - error
+ - props: true
+ ignorePropertyModificationsFor:
+ - "acc" # for reduce accumulators
+ - "accumulator" # for reduce accumulators
+ - "el" # for DOM elements
+ - "element" # for DOM elements
+ - "state" # for Vuex mutations
no-underscore-dangle:
- error
- allow:
diff --git a/.gitignore b/.gitignore
index eb0875a977f..82b3d08f7a8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -78,3 +78,4 @@ eslint-report.html
/.gitlab_pages_secret
package-lock.json
/junit_rspec.xml
+/junit_karma.xml
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 1b4134282c9..cc27ac3677b 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,4 +1,4 @@
-image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.4.4-golang-1.9-git-2.18-chrome-67.0-node-8.x-yarn-1.2-postgresql-9.6-graphicsmagick-1.3.29"
+image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.4.4-golang-1.9-git-2.18-chrome-69.0-node-8.x-yarn-1.2-postgresql-9.6-graphicsmagick-1.3.29"
.dedicated-runner: &dedicated-runner
retry: 1
@@ -708,7 +708,6 @@ gitlab:assets:compile:
SETUP_DB: "false"
SKIP_STORAGE_VALIDATION: "true"
WEBPACK_REPORT: "true"
- NO_COMPRESSION: "true"
# we override the max_old_space_size to prevent OOM errors
NODE_OPTIONS: --max_old_space_size=3584
script:
@@ -722,6 +721,7 @@ gitlab:assets:compile:
expire_in: 31d
paths:
- webpack-report/
+ - public/assets/
karma:
<<: *dedicated-no-docs-and-no-qa-pull-cache-job
@@ -743,6 +743,8 @@ karma:
paths:
- chrome_debug.log
- coverage-javascript/
+ reports:
+ junit: junit_karma.xml
code_quality:
<<: *dedicated-no-docs-no-db-pull-cache-job
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS
index 5b6e5a719fa..7fd32563696 100644
--- a/.gitlab/CODEOWNERS
+++ b/.gitlab/CODEOWNERS
@@ -13,3 +13,5 @@ db/ @abrandl @NikolayS
# Feature specific owners
/ee/lib/gitlab/code_owners/ @reprazent
+/ee/lib/ee/gitlab/auth/ldap/ @dblessing @mkozono
+/lib/gitlab/auth/ldap/ @dblessing @mkozono
diff --git a/Dangerfile b/Dangerfile
index 9217610da8b..46e53edcac4 100644
--- a/Dangerfile
+++ b/Dangerfile
@@ -4,4 +4,6 @@ danger.import_dangerfile(path: 'danger/changelog')
danger.import_dangerfile(path: 'danger/specs')
danger.import_dangerfile(path: 'danger/gemfile')
danger.import_dangerfile(path: 'danger/database')
+danger.import_dangerfile(path: 'danger/documentation')
danger.import_dangerfile(path: 'danger/frozen_string')
+danger.import_dangerfile(path: 'danger/commit_messages')
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 377d8aca07e..99e0d1ed987 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.117.2
+0.120.0
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index 0e79152459e..9c78b761ea1 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-8.1.1
+8.3.2
diff --git a/Gemfile b/Gemfile
index fe788370640..27e6ba21ff3 100644
--- a/Gemfile
+++ b/Gemfile
@@ -170,7 +170,7 @@ gem 'state_machines-activerecord', '~> 0.5.1'
gem 'acts-as-taggable-on', '~> 5.0'
# Background jobs
-gem 'sidekiq', '~> 5.1'
+gem 'sidekiq', '~> 5.2.1'
gem 'sidekiq-cron', '~> 0.6.0'
gem 'redis-namespace', '~> 1.6.0'
gem 'sidekiq-limit_fetch', '~> 3.4', require: false
@@ -425,7 +425,7 @@ group :ed25519 do
end
# Gitaly GRPC client
-gem 'gitaly-proto', '~> 0.113.0', require: 'gitaly'
+gem 'gitaly-proto', '~> 0.117.0', require: 'gitaly'
gem 'grpc', '~> 1.11.0'
# Locked until https://github.com/google/protobuf/issues/4210 is closed
diff --git a/Gemfile.lock b/Gemfile.lock
index e7ab97fb299..0832fe25711 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -125,7 +125,7 @@ GEM
coderay (1.1.2)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
- commonmarker (0.17.8)
+ commonmarker (0.17.13)
ruby-enum (~> 0.5)
concord (0.1.5)
adamantium (~> 0.2.0)
@@ -133,7 +133,7 @@ GEM
concurrent-ruby (1.0.5)
concurrent-ruby-ext (1.0.5)
concurrent-ruby (= 1.0.5)
- connection_pool (2.2.1)
+ connection_pool (2.2.2)
crack (0.4.3)
safe_yaml (~> 1.0.0)
crass (1.0.4)
@@ -208,7 +208,7 @@ GEM
fast_blank (1.0.0)
fast_gettext (1.6.0)
ffaker (2.4.0)
- ffi (1.9.18)
+ ffi (1.9.25)
flipper (0.13.0)
flipper-active_record (0.13.0)
activerecord (>= 3.2, < 6)
@@ -276,7 +276,7 @@ GEM
gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
- gitaly-proto (0.113.0)
+ gitaly-proto (0.117.0)
google-protobuf (~> 3.1)
grpc (~> 1.10)
github-linguist (5.3.3)
@@ -649,7 +649,7 @@ GEM
httpclient (>= 2.4)
multi_json (>= 1.3.6)
rack (>= 1.1)
- rack-protection (2.0.1)
+ rack-protection (2.0.3)
rack
rack-proxy (0.6.0)
rack
@@ -843,9 +843,8 @@ GEM
rack
shoulda-matchers (3.1.2)
activesupport (>= 4.0.0)
- sidekiq (5.1.3)
- concurrent-ruby (~> 1.0)
- connection_pool (~> 2.2, >= 2.2.0)
+ sidekiq (5.2.1)
+ connection_pool (~> 2.2, >= 2.2.2)
rack-protection (>= 1.5.0)
redis (>= 3.3.5, < 5)
sidekiq-cron (0.6.0)
@@ -1038,7 +1037,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
- gitaly-proto (~> 0.113.0)
+ gitaly-proto (~> 0.117.0)
github-linguist (~> 5.3.3)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-gollum-lib (~> 4.2)
@@ -1166,7 +1165,7 @@ DEPENDENCIES
settingslogic (~> 2.0.9)
sham_rack (~> 1.3.6)
shoulda-matchers (~> 3.1.2)
- sidekiq (~> 5.1)
+ sidekiq (~> 5.2.1)
sidekiq-cron (~> 0.6.0)
sidekiq-limit_fetch (~> 3.4)
simple_po_parser (~> 1.1.2)
diff --git a/Gemfile.rails5.lock b/Gemfile.rails5.lock
index 1a1aac9f439..b384592d035 100644
--- a/Gemfile.rails5.lock
+++ b/Gemfile.rails5.lock
@@ -136,7 +136,7 @@ GEM
concurrent-ruby (1.0.5)
concurrent-ruby-ext (1.0.5)
concurrent-ruby (= 1.0.5)
- connection_pool (2.2.1)
+ connection_pool (2.2.2)
crack (0.4.3)
safe_yaml (~> 1.0.0)
crass (1.0.4)
@@ -211,7 +211,7 @@ GEM
fast_blank (1.0.0)
fast_gettext (1.6.0)
ffaker (2.4.0)
- ffi (1.9.18)
+ ffi (1.9.25)
flipper (0.13.0)
flipper-active_record (0.13.0)
activerecord (>= 3.2, < 6)
@@ -279,7 +279,7 @@ GEM
gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
- gitaly-proto (0.113.0)
+ gitaly-proto (0.117.0)
google-protobuf (~> 3.1)
grpc (~> 1.10)
github-linguist (5.3.3)
@@ -653,7 +653,7 @@ GEM
httpclient (>= 2.4)
multi_json (>= 1.3.6)
rack (>= 1.1)
- rack-protection (2.0.1)
+ rack-protection (2.0.3)
rack
rack-proxy (0.6.0)
rack
@@ -851,9 +851,8 @@ GEM
rack
shoulda-matchers (3.1.2)
activesupport (>= 4.0.0)
- sidekiq (5.1.3)
- concurrent-ruby (~> 1.0)
- connection_pool (~> 2.2, >= 2.2.0)
+ sidekiq (5.2.1)
+ connection_pool (~> 2.2, >= 2.2.2)
rack-protection (>= 1.5.0)
redis (>= 3.3.5, < 5)
sidekiq-cron (0.6.0)
@@ -1047,7 +1046,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
- gitaly-proto (~> 0.113.0)
+ gitaly-proto (~> 0.117.0)
github-linguist (~> 5.3.3)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-gollum-lib (~> 4.2)
@@ -1176,7 +1175,7 @@ DEPENDENCIES
settingslogic (~> 2.0.9)
sham_rack (~> 1.3.6)
shoulda-matchers (~> 3.1.2)
- sidekiq (~> 5.1)
+ sidekiq (~> 5.2.1)
sidekiq-cron (~> 0.6.0)
sidekiq-limit_fetch (~> 3.4)
simple_po_parser (~> 1.1.2)
diff --git a/LICENSE b/LICENSE
index a76372fad2c..a90ea939517 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,7 +1,19 @@
Copyright GitLab B.V.
-Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
-The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/app/assets/javascripts/badges/components/badge.vue b/app/assets/javascripts/badges/components/badge.vue
index 155c348286c..b08dc454d12 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"
/>
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/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue
index bfc8d9b03ad..606c9e81db4 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"
@@ -245,7 +243,7 @@ export default {
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/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/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/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/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/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/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..940e06a75cc 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_gutter_avatars.vue b/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue
index 7e50a0aed84..a02c41f39ab 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-gl-tooltip
v-if="moreText"
: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..a0dc381ccc7 100644
--- a/app/assets/javascripts/diffs/components/diff_line_note_form.vue
+++ b/app/assets/javascripts/diffs/components/diff_line_note_form.vue
@@ -6,6 +6,7 @@ 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';
+import { reduceDiscussionsToLineCodes } from '../../notes/stores/utils';
export default {
components: {
@@ -52,7 +53,7 @@ export default {
}
},
methods: {
- ...mapActions('diffs', ['cancelCommentForm']),
+ ...mapActions('diffs', ['cancelCommentForm', 'assignDiscussionsToDiff']),
...mapActions(['saveNote', 'refetchDiscussionById']),
handleCancelCommentForm(shouldConfirm, isDirty) {
if (shouldConfirm && isDirty) {
@@ -88,7 +89,10 @@ export default {
const endpoint = this.getNotesDataByProp('discussionsPath');
this.refetchDiscussionById({ path: endpoint, discussionId: result.discussion_id })
- .then(() => {
+ .then(selectedDiscussion => {
+ const lineCodeDiscussions = reduceDiscussionsToLineCodes([selectedDiscussion]);
+ this.assignDiscussionsToDiff(lineCodeDiscussions);
+
this.handleCancelCommentForm();
})
.catch(() => {
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 6348f32d36d..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';
},
},
};
@@ -49,8 +44,8 @@ export default {
>
<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..0e306f39a9f 100644
--- a/app/assets/javascripts/diffs/components/inline_diff_table_row.vue
+++ b/app/assets/javascripts/diffs/components/inline_diff_table_row.vue
@@ -33,11 +33,6 @@ export default {
required: false,
default: false,
},
- discussions: {
- type: Array,
- required: false,
- default: () => [],
- },
},
data() {
return {
@@ -94,7 +89,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 +98,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..947e7c98fae 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,7 +40,7 @@ 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
:file-hash="diffFile.fileHash"
@@ -61,7 +48,6 @@ export default {
:line="line"
:is-bottom="index + 1 === diffLinesLength"
:key="line.lineCode"
- :discussions="discussionsList(line)"
/>
<inline-diff-comment-row
v-if="shouldRenderInlineCommentRow(line)"
@@ -69,7 +55,6 @@ export default {
:line="line"
:line-index="index"
:key="index"
- :discussions="discussionsList(line)"
/>
</template>
</tbody>
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..26417c350cb 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
@@ -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
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..fb68d191091 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,5 @@
<script>
import $ from 'jquery';
-import { mapGetters } from 'vuex';
import DiffTableCell from './diff_table_cell.vue';
import {
NEW_LINE_TYPE,
@@ -10,8 +9,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 +34,6 @@ export default {
required: false,
default: false,
},
- leftDiscussions: {
- type: Array,
- required: false,
- default: () => [],
- },
- rightDiscussions: {
- type: Array,
- required: false,
- default: () => [],
- },
},
data() {
return {
@@ -54,29 +42,26 @@ 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;
},
methods: {
@@ -116,47 +101,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..501bd4450d8 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,7 +42,7 @@ export default {
<table>
<tbody>
<template
- v-for="(line, index) in parallelDiffLines"
+ v-for="(line, index) in diffLines"
>
<parallel-diff-table-row
:file-hash="diffFile.fileHash"
@@ -81,8 +50,6 @@ export default {
: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/store/actions.js b/app/assets/javascripts/diffs/store/actions.js
index 4ab6ceb249a..027df2ec841 100644
--- a/app/assets/javascripts/diffs/store/actions.js
+++ b/app/assets/javascripts/diffs/store/actions.js
@@ -3,6 +3,7 @@ import axios from '~/lib/utils/axios_utils';
import Cookies from 'js-cookie';
import { handleLocationHash, historyPushState } from '~/lib/utils/common_utils';
import { mergeUrlParams } from '~/lib/utils/url_utility';
+import { getDiffPositionByLineCode } from './utils';
import * as types from './mutation_types';
import {
PARALLEL_DIFF_VIEW_TYPE,
@@ -29,25 +30,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 }) => {
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/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..6dc5bf16c65 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,93 @@ 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 isResolvable = firstDiscussion.resolvable;
+ const diffPosition = diffPositionByLineCode[firstDiscussion.line_code];
+
+ if (
+ selectedFile &&
+ isDiffDiscussion &&
+ hasLineCode &&
+ isResolvable &&
+ diffPosition &&
+ isDiscussionApplicableToLine(firstDiscussion, diffPosition)
+ ) {
+ 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..b7e52a8f37f 100644
--- a/app/assets/javascripts/diffs/store/utils.js
+++ b/app/assets/javascripts/diffs/store/utils.js
@@ -8,6 +8,8 @@ import {
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) {
@@ -161,6 +163,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 +181,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;
@@ -194,3 +238,12 @@ 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) {
+ const originalRefs = convertObjectPropsToCamelCase(discussion.original_position.formatter);
+ const refs = convertObjectPropsToCamelCase(discussion.position.formatter);
+
+ return _.isEqual(refs, diffPosition) || _.isEqual(originalRefs, diffPosition);
+}
diff --git a/app/assets/javascripts/clusters/components/gcp_signup_offer.js b/app/assets/javascripts/dismissable_callout.js
index 8bc20a1c09f..5185b019376 100644
--- a/app/assets/javascripts/clusters/components/gcp_signup_offer.js
+++ b/app/assets/javascripts/dismissable_callout.js
@@ -3,8 +3,8 @@ import axios from '~/lib/utils/axios_utils';
import { __ } from '~/locale';
import Flash from '~/flash';
-export default function gcpSignupOffer() {
- const alertEl = document.querySelector('.gcp-signup-offer');
+export default function initDismissableCallout(alertSelector) {
+ const alertEl = document.querySelector(alertSelector);
if (!alertEl) {
return;
}
diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js
index 5528ad9f38d..ff969bb94a4 100644
--- a/app/assets/javascripts/dropzone_input.js
+++ b/app/assets/javascripts/dropzone_input.js
@@ -7,6 +7,19 @@ 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/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_table.vue b/app/assets/javascripts/environments/components/environments_table.vue
index 016e9f7c7b3..a9d9d768c06 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: {
@@ -97,7 +95,7 @@ export default {
<div
v-if="model.isLoadingFolderContent"
:key="`loading-item-${i}`">
- <loading-icon size="2" />
+ <gl-loading-icon :size="2" />
</div>
<template v-else>
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/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/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..6e700b8bf8a 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);
},
},
};
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..bf0ff6e35ec 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,
},
@@ -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..3fdd35ad228 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
v-tooltip
- v-show="filesLength"
+ ref="actionBtn"
+ :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-tooltip
+ v-if="!stagedList"
+ :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_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/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..1c474acb4b2 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: {
@@ -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/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/jobs/components/header.vue b/app/assets/javascripts/jobs/components/header.vue
index 1e7f4b2c3f7..3e49b04e44e 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: {
@@ -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/sidebar_details_block.vue b/app/assets/javascripts/jobs/components/sidebar_details_block.vue
index 36d4a3e2bc9..1210ccd038a 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],
@@ -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/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index 3e208764b3e..0849d97bc1a 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -131,16 +131,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('&')
- .map(param => {
- const split = param.split('=');
- return [decodeURI(split[0]), split[1]].join('=');
- });
+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;
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..c5a5f64abac 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -29,6 +29,7 @@ import './milestone_select';
import './frequent_items';
import initBreadcrumbs from './breadcrumb';
import initDispatcher from './dispatcher';
+import initUsagePingConsent from './usage_ping_consent';
// expose jQuery as global (TODO: remove these)
window.jQuery = jQuery;
@@ -78,6 +79,7 @@ document.addEventListener('DOMContentLoaded', () => {
initImporterStatus();
initTodoToggle();
initLogoAnimation();
+ initUsagePingConsent();
// Set the default path for all cookies to GitLab's root directory
Cookies.defaults.path = gon.relative_url_root || '/';
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/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/notes.js b/app/assets/javascripts/notes.js
index 1e168742667..8b1d8f6055e 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;
}
}
@@ -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/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..051b17e9aa9 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: {
diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue
index abcd4422d7c..c41ed070383 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');
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..afe86911230 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"
@@ -344,6 +352,7 @@ Please check your network connection and try again.`;
:is="componentName(note)"
:note="componentData(note)"
:key="note.id"
+ @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..42c87fdf54a 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())
diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js
index 3eefbe11c37..9a2ec15debd 100644
--- a/app/assets/javascripts/notes/stores/actions.js
+++ b/app/assets/javascripts/notes/stores/actions.js
@@ -43,14 +43,23 @@ 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 refetchDiscussionById = ({ commit, state }, { path, discussionId }) =>
+ new Promise(resolve => {
+ service
+ .fetchDiscussions(path)
+ .then(res => res.json())
+ .then(discussions => {
+ const selectedDiscussion = discussions.find(discussion => discussion.id === discussionId);
+ if (selectedDiscussion) {
+ commit(types.UPDATE_DISCUSSION, selectedDiscussion);
+ // We need to refetch as it is now the transformed one in state
+ const discussion = utils.findNoteObjectById(state.discussions, discussionId);
+
+ resolve(discussion);
+ }
+ })
+ .catch(() => {});
+ });
export const deleteNote = ({ commit }, note) =>
service.deleteNote(note.path).then(() => {
@@ -152,26 +161,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 +222,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;
});
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/mutations.js b/app/assets/javascripts/notes/stores/mutations.js
index ab6a95e2601..2c04bfea122 100644
--- a/app/assets/javascripts/notes/stores/mutations.js
+++ b/app/assets/javascripts/notes/stores/mutations.js
@@ -54,13 +54,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 +94,15 @@ 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 });
+ }
+
// To support legacy notes, should be very rare case.
if (discussion.individual_note && discussion.notes.length > 1) {
discussion.notes.forEach(n => {
@@ -168,8 +172,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 +188,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) {
diff --git a/app/assets/javascripts/notes/stores/utils.js b/app/assets/javascripts/notes/stores/utils.js
index a0e096ebfaf..8ccbdb4c130 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 && 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 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/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/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..5b8c2ae7e81 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 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/projects/index.js b/app/assets/javascripts/pages/projects/index.js
index cc0e6553e83..9c074b74c3b 100644
--- a/app/assets/javascripts/pages/projects/index.js
+++ b/app/assets/javascripts/pages/projects/index.js
@@ -1,4 +1,4 @@
-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';
@@ -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/project.js b/app/assets/javascripts/pages/projects/project.js
index fdcbcc236c1..34a13eb3251 100644
--- a/app/assets/javascripts/pages/projects/project.js
+++ b/app/assets/javascripts/pages/projects/project.js
@@ -61,6 +61,13 @@ export default class Project {
.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();
}
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..b0a323a71cd 100644
--- a/app/assets/javascripts/pages/projects/wikis/index.js
+++ b/app/assets/javascripts/pages/projects/wikis/index.js
@@ -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/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
index 1952dd453f4..e27f195c9b0 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>
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/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..3e13bad9a0b 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,
},
@@ -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/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..d4c4d779d44 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));
},
@@ -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/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/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/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/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..2f2394371ef 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,
},
@@ -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_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/header_ci_component.vue b/app/assets/javascripts/vue_shared/components/header_ci_component.vue
index 49fbce75110..18f5ce53bb1 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,
},
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/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/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/framework.scss b/app/assets/stylesheets/framework.scss
index 39ffabb3ea6..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';
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/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index e91e830fcac..ab62ca07573 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -166,6 +166,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);
@@ -365,7 +369,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 a52e6c4f6a7..e9b074236cc 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;
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/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/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/toggle.scss b/app/assets/stylesheets/framework/toggle.scss
index 43aaf198609..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;
@@ -61,7 +61,7 @@
}
.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 f5e7a84d082..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;
@@ -256,14 +258,7 @@ $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);
@@ -276,20 +271,8 @@ $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;
@@ -320,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%);
@@ -342,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);
@@ -420,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;
@@ -437,7 +410,6 @@ $identicon-indigo: #e8eaf6;
$identicon-blue: #e3f2fd;
$identicon-teal: #e0f2f1;
$identicon-orange: #fbe9e7;
-$identicon-fg-color: #555555;
/*
* Calendar
@@ -506,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;
@@ -534,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;
/*
@@ -546,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;
@@ -575,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;
@@ -613,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);
@@ -632,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
@@ -666,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..45df8391f9a 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 {
@@ -567,24 +569,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 +583,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 {
@@ -660,6 +647,8 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
.multi-file-commit-list-path {
cursor: pointer;
+ height: $ide-commit-row-height;
+ padding-right: 0;
&.is-active {
background-color: $white-normal;
@@ -668,6 +657,12 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
&:hover,
&:focus {
outline: 0;
+
+ .multi-file-discard-btn {
+ > .btn {
+ display: flex;
+ }
+ }
}
svg {
@@ -679,6 +674,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 +682,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 +805,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;
@@ -1442,3 +1439,29 @@ $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;
+}
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/diff.scss b/app/assets/stylesheets/pages/diff.scss
index d673b59e1c0..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;
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..394c99268be 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 {
@@ -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..cb29b5d4313 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -701,6 +701,10 @@
align-self: center;
overflow: hidden;
text-overflow: ellipsis;
+
+ .user-status-emoji {
+ margin: 0 $gl-padding-8 0 $gl-padding-4;
+ }
}
.js-issuable-selector-wrap {
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 7b8cad254c7..9d46c2cf4fa 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -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/notes.scss b/app/assets/stylesheets/pages/notes.scss
index b1e33196049..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,
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 9b7051924e6..7c42dcad959 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -371,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 {
@@ -447,7 +447,7 @@
> li + li::before {
padding: 0 3px;
- color: $project-breadcrumb-color;
+ color: $gl-gray-400;
}
a {
diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss
index 5a594920e44..dbf8692d69b 100644
--- a/app/assets/stylesheets/pages/settings.scss
+++ b/app/assets/stylesheets/pages/settings.scss
@@ -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/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 9723e400574..869213d61f1 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -9,11 +9,18 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
.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
+
+ respond_to do |format|
+ if successful
+ format.json { head :ok }
+ format.html { redirect_to admin_application_settings_path, notice: 'Application settings saved successfully' }
+ else
+ format.json { head :bad_request }
+ format.html { render :show }
+ end
end
end
@@ -76,6 +83,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/logs_controller.rb b/app/controllers/admin/logs_controller.rb
index 12a27cede75..7248edb64e1 100644
--- a/app/controllers/admin/logs_controller.rb
+++ b/app/controllers/admin/logs_controller.rb
@@ -12,7 +12,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/application_controller.rb b/app/controllers/application_controller.rb
index 7cd68d6b92a..7e2b2cf3ad3 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -22,6 +22,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
@@ -434,4 +435,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/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb
index 37e03d70b6f..7b6e5bcb5f1 100644
--- a/app/controllers/concerns/issuable_actions.rb
+++ b/app/controllers/concerns/issuable_actions.rb
@@ -95,6 +95,7 @@ module IssuableActions
.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) }
diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb
index 5127db3f5fb..b63f2eb85f0 100644
--- a/app/controllers/concerns/notes_actions.rb
+++ b/app/controllers/concerns/notes_actions.rb
@@ -18,6 +18,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) }
diff --git a/app/controllers/groups/labels_controller.rb b/app/controllers/groups/labels_controller.rb
index 3e0076ac935..059cf160fa2 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,13 @@ class Groups::LabelsController < Groups::ApplicationController
def index
respond_to do |format|
format.html do
- @labels = @available_labels.page(params[:page])
+ @labels = @group.labels
+ .optionally_search(params[:search])
+ .order_by(sort)
+ .page(params[:page])
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 +118,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..1f48c3417d0 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -17,7 +17,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 +53,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
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index 6f50cbb4a36..5671663f81e 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -101,6 +101,7 @@ class ProfilesController < Profiles::ApplicationController
:organization,
:preferred_language,
:private_profile,
+ :include_private_contributions,
status: [:emoji, :message]
)
end
diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb
index 8a2bce6e7b5..69332ee2a0e 100644
--- a/app/controllers/projects/labels_controller.rb
+++ b/app/controllers/projects/labels_controller.rb
@@ -163,7 +163,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/merge_requests/diffs_controller.rb b/app/controllers/projects/merge_requests/diffs_controller.rb
index 48e02581d54..34de554212f 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
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/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_controller.rb b/app/controllers/projects_controller.rb
index 0eaf9f94e37..98076791ab9 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -191,10 +191,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)
else
redirect_to(
edit_project_path(@project, anchor: 'js-export-project'),
@@ -425,12 +423,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/finders/group_descendants_finder.rb b/app/finders/group_descendants_finder.rb
index 051ea108e06..2300b7fd114 100644
--- a/app/finders/group_descendants_finder.rb
+++ b/app/finders/group_descendants_finder.rb
@@ -134,7 +134,7 @@ class GroupDescendantsFinder
end
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
diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb
index 1d05bf28438..8418577dab2 100644
--- a/app/finders/labels_finder.rb
+++ b/app/finders/labels_finder.rb
@@ -54,7 +54,11 @@ class LabelsFinder < UnionFinder
end
def sort(items)
- items.reorder(title: :asc)
+ if params[:sort]
+ items.order_by(params[:sort])
+ else
+ items.reorder(title: :asc)
+ end
end
def with_title(items)
diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb
index cac6643eff3..0398ccce93b 100644
--- a/app/finders/projects_finder.rb
+++ b/app/finders/projects_finder.rb
@@ -49,6 +49,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
@@ -131,6 +132,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/user_recent_events_finder.rb b/app/finders/user_recent_events_finder.rb
index 876f086a3ef..b874f6959c9 100644
--- a/app/finders/user_recent_events_finder.rb
+++ b/app/finders/user_recent_events_finder.rb
@@ -48,20 +48,6 @@ class UserRecentEventsFinder
end
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/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..bb7ae03313c 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'digest/md5'
require 'uri'
@@ -106,11 +108,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..3ebf0162cb6 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,
diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb
index 18f0979fc86..c17a54a6dff 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/
diff --git a/app/helpers/auto_devops_helper.rb b/app/helpers/auto_devops_helper.rb
index 7b076728685..62fc6fb279f 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? &&
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 2b3fe57767c..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
#
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..f8d36dce45d 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
##
# DEPRECATED
#
diff --git a/app/helpers/clusters_helper.rb b/app/helpers/clusters_helper.rb
index 73049c74d80..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
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 19aa55a8d49..463f4145bdd 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..7684734c014 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)
@@ -215,9 +218,7 @@ module DiffHelper
end
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..4b3ef2de701 100644
--- a/app/helpers/environment_helper.rb
+++ b/app/helpers/environment_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module EnvironmentHelper
def environment_for_build(project, build)
return unless build.environment
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 a5612372aa6..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
@@ -97,9 +100,10 @@ module IconsHelper
'globe'
end
- name << " fw" if fw
+ name = [name]
+ name << "fw" if fw
- icon(name, options)
+ 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..8396cfe0ac8 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
@@ -167,26 +169,26 @@ 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_short if issuable.tasks?), id: "task_status_short", class: "d-md-none")
- output.html_safe
+ output.join.html_safe
end
def issuable_todo(issuable)
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 3adaa1366c0..f2cd676bb1b 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
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..999143002bb 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
@@ -119,20 +121,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..e7537ca5733 100644
--- a/app/helpers/namespaces_helper.rb
+++ b/app/helpers/namespaces_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module NamespacesHelper
def namespace_id_from(params)
params.dig(:project, :namespace_id) || params[:namespace_id]
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..f609b6c0cec 100644
--- a/app/helpers/numbers_helper.rb
+++ b/app/helpers/numbers_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module NumbersHelper
def limited_counter_with_delimiter(resource, **options)
limit = options.fetch(:limit, 1000).to_i
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 80b45176a62..89fee06ee77 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
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..72bf1377b02 100644
--- a/app/helpers/safe_params_helper.rb
+++ b/app/helpers/safe_params_helper.rb
@@ -1,3 +1,5 @@
+# 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(...)`
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 98074a4c0c5..c509cd592c4 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
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..8b554e1aaa9 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
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..a6e65d30eda 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
{
@@ -99,6 +101,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
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..e310fda51d7 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
#
@@ -47,9 +49,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..80f61a371fd 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
@@ -8,7 +10,7 @@ module TreeHelper
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,7 +20,7 @@ module TreeHelper
end
tree << render(partial: 'projects/tree/tree_row', collection: items) if items.present?
- tree.html_safe
+ tree.join.html_safe
end
# Return an image icon depending on the file type and mode
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 7b64869c9ea..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)
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/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..c133f4e6dbb 100644
--- a/app/mailers/previews/notify_preview.rb
+++ b/app/mailers/previews/notify_preview.rb
@@ -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/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/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..cc1408cb397 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!
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 526bf7af99b..8c075253400 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) }
@@ -458,7 +464,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
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/jupyter.rb b/app/models/clusters/applications/jupyter.rb
index 3d84eeed5a8..2371b0237d8 100644
--- a/app/models/clusters/applications/jupyter.rb
+++ b/app/models/clusters/applications/jupyter.rb
@@ -73,10 +73,19 @@ module Clusters
"clientSecret" => oauth_application.secret,
"callbackUrl" => callback_url
}
+ },
+ "singleuser" => {
+ "extraEnv" => {
+ "GITLAB_PROJECT_ID" => project_id
+ }
}
}
end
+ def project_id
+ cluster&.project&.id
+ end
+
def gitlab_url
Gitlab.config.gitlab.url
end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 7f14d78e976..5f65fceb7af 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -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/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/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/event.rb b/app/models/event.rb
index ba28866e8e6..041dac6941b 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -151,15 +151,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/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/label.rb b/app/models/label.rb
index 96c1515b41a..8dc7ded53ad 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -5,6 +5,8 @@ class Label < ActiveRecord::Base
include Referable
include Subscribable
include Gitlab::SQL::Pattern
+ include OptionallySearch
+ include Sortable
# Represents a "No Label" state used for filtering Issues and Merge
# Requests that have no label assigned.
@@ -40,6 +42,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_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/namespace.rb b/app/models/namespace.rb
index 0deb44d7916..76920c3c039 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -253,18 +253,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/note.rb b/app/models/note.rb
index 2e343b8f9f8..8f090cc31e6 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -389,18 +389,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/project.rb b/app/models/project.rb
index 97d9fa355ef..f057c63afdf 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -232,6 +232,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.
@@ -567,7 +569,6 @@ class Project < ActiveRecord::Base
end
def cleanup
- @repository&.cleanup
@repository = nil
end
@@ -1733,16 +1734,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 +1754,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
@@ -2067,6 +2062,12 @@ class Project < ActiveRecord::Base
auto_cancel_pending_pipelines == 'enabled'
end
+ # Update the default branch querying the remote to determine its HEAD
+ def update_root_ref(remote_name)
+ root_ref = repository.find_remote_root_ref(remote_name)
+ change_head(root_ref) if root_ref.present? && root_ref != default_branch
+ end
+
private
def rename_or_migrate_repository!
diff --git a/app/models/project_services/asana_service.rb b/app/models/project_services/asana_service.rb
index 35c19049c04..568f870c2db 100644
--- a/app/models/project_services/asana_service.rb
+++ b/app/models/project_services/asana_service.rb
@@ -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/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/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 929d28b9d88..ad65881ff43 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -24,6 +24,7 @@ class Repository
delegate :ref_name_for_sha, to: :raw_repository
delegate :bundle_to_disk, to: :raw_repository
+ delegate :find_remote_root_ref, to: :raw_repository
CreateTreeError = Class.new(StandardError)
@@ -81,10 +82,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 ||=
@@ -999,14 +996,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,
@@ -1015,6 +1004,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/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/user.rb b/app/models/user.rb
index 0fcc952b5cd..568ec101016 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -490,6 +490,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
@@ -1287,6 +1297,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
@@ -1301,6 +1315,14 @@ class User < ActiveRecord::Base
private
+ def has_current_license?
+ false
+ end
+
+ def consented_usage_stats?
+ Gitlab::CurrentSettings.usage_stats_set_by_user_id == self.id
+ end
+
def owned_projects_union
Gitlab::SQL::Union.new([
Project.where(namespace: namespace),
diff --git a/app/models/user_callout.rb b/app/models/user_callout.rb
index 97e955ace36..1cd05cf3aac 100644
--- a/app/models/user_callout.rb
+++ b/app/models/user_callout.rb
@@ -5,7 +5,8 @@ class UserCallout < ActiveRecord::Base
enum feature_name: {
gke_cluster_integration: 1,
- gcp_signup_offer: 2
+ gcp_signup_offer: 2,
+ cluster_security_warning: 3
}
validates :user, presence: true
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/serializers/build_details_entity.rb b/app/serializers/build_details_entity.rb
index b107fc26f18..6f8194d9856 100644
--- a/app/serializers/build_details_entity.rb
+++ b/app/serializers/build_details_entity.rb
@@ -59,6 +59,12 @@ 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
+
private
def build_failed_issue_options
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/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/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/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/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/stage_entity.rb b/app/serializers/stage_entity.rb
index 00e6d32ee3a..ca8fa7e7877 100644
--- a/app/serializers/stage_entity.rb
+++ b/app/serializers/stage_entity.rb
@@ -19,7 +19,7 @@ class StageEntity < Grape::Entity
latest_statuses
end
- expose :detailed_status, as: :status, with: StatusEntity
+ expose :detailed_status, as: :status, with: DetailedStatusEntity
expose :path do |stage|
project_pipeline_path(
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/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/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/issues/move_service.rb b/app/services/issues/move_service.rb
index 841bce9949e..ec9d8944e4e 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
@@ -96,6 +97,18 @@ module Issues
end
end
+ 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
+
def rewrite_issue_award_emoji
rewrite_award_emoji(@old_issue, @new_issue)
end
diff --git a/app/services/labels/promote_service.rb b/app/services/labels/promote_service.rb
index 623a5f0950e..fcdcea2d0ea 100644
--- a/app/services/labels/promote_service.rb
+++ b/app/services/labels/promote_service.rb
@@ -13,6 +13,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)
@@ -52,6 +53,12 @@ module Labels
.update_all(label_id: new_label)
end
+ def update_resource_label_events(new_label, label_ids)
+ ResourceLabelEvent
+ .where(label: label_ids)
+ .update_all(label_id: new_label)
+ end
+
def update_issue_board_lists(new_label, label_ids)
List
.where(label: label_ids)
diff --git a/app/services/merge_requests/reload_diffs_service.rb b/app/services/merge_requests/reload_diffs_service.rb
index 8d85dc9eb5f..1390ae0e199 100644
--- a/app/services/merge_requests/reload_diffs_service.rb
+++ b/app/services/merge_requests/reload_diffs_service.rb
@@ -30,7 +30,7 @@ module MergeRequests
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
+ new_diff.diffs_collection.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,7 +38,7 @@ 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!
+ merge_request_diff.diffs_collection.clear_cache
end
end
end
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/projects/auto_devops/disable_service.rb b/app/services/projects/auto_devops/disable_service.rb
new file mode 100644
index 00000000000..9745ab67dbd
--- /dev/null
+++ b/app/services/projects/auto_devops/disable_service.rb
@@ -0,0 +1,39 @@
+# 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.
+ def first_pipeline_failure?
+ auto_devops_pipelines.success.limit(1).count.zero? &&
+ auto_devops_pipelines.failed.limit(1).count.nonzero?
+ end
+
+ 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/container_repository/destroy_service.rb b/app/services/projects/container_repository/destroy_service.rb
new file mode 100644
index 00000000000..a8e7eab6068
--- /dev/null
+++ b/app/services/projects/container_repository/destroy_service.rb
@@ -0,0 +1,13 @@
+# 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)
+
+ container_repository.destroy
+ end
+ end
+ end
+end
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index 76e22507698..01de6afcd8e 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -159,7 +159,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/update_remote_mirror_service.rb b/app/services/projects/update_remote_mirror_service.rb
index 591b38b8151..85b9eb02803 100644
--- a/app/services/projects/update_remote_mirror_service.rb
+++ b/app/services/projects/update_remote_mirror_service.rb
@@ -5,13 +5,14 @@ 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)
+ project.update_root_ref(remote_mirror.remote_name)
opts = {}
if remote_mirror.only_protected_branches?
diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb
index a4c4c9e4812..be9d1e48435 100644
--- a/app/services/quick_actions/interpret_service.rb
+++ b/app/services/quick_actions/interpret_service.rb
@@ -489,6 +489,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'
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..1b02a1602e2
--- /dev/null
+++ b/app/services/resource_events/merge_into_notes_service.rb
@@ -0,0 +1,55 @@
+# 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
+
+ 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
+
+ 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/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..c5d05992575 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
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/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/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/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/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/_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/labels/index.html.haml b/app/views/groups/labels/index.html.haml
index e6821009d03..86178eb2ffd 100644
--- a/app/views/groups/labels/index.html.haml
+++ b/app/views/groups/labels/index.html.haml
@@ -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/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/import/gitlab_projects/new.html.haml b/app/views/import/gitlab_projects/new.html.haml
index 4225ee19217..c4218f3d787 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
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/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/show.html.haml b/app/views/profiles/show.html.haml
index 9f79feb4ddd..0a1ee648d97 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,66 @@
.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, 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 :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 +137,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/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/_new_project_fields.html.haml b/app/views/projects/_new_project_fields.html.haml
index ad8c7911fad..3b6090211c0 100644
--- a/app/views/projects/_new_project_fields.html.haml
+++ b/app/views/projects/_new_project_fields.html.haml
@@ -3,10 +3,13 @@
.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 +30,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
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/clusters/_banner.html.haml b/app/views/projects/clusters/_banner.html.haml
index f18caa3f4ac..73cfea0ef92 100644
--- a/app/views/projects/clusters/_banner.html.haml
+++ b/app/views/projects/clusters/_banner.html.haml
@@ -1,14 +1,15 @@
-%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")
- .hidden.js-cluster-success.alert.alert-success.alert-block.append-bottom-10{ role: 'alert' }
- = s_("ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details")
-
- %p= s_('ClusterIntegration|Control how your Kubernetes cluster integrates with GitLab')
+- if show_cluster_security_warning?
+ .js-cluster-security-warning.alert.alert-block.alert-dismissable.bs-callout.bs-callout-warning
+ %button.close{ type: "button", data: { feature_id: UserCalloutsHelper::CLUSTER_SECURITY_WARNING, dismiss_endpoint: user_callouts_path } } &times;
+ = s_("ClusterIntegration|The default cluster configuration grants access to many functionalities needed to successfully build and deploy a containerised application.")
+ = link_to s_("More information"), help_page_path('user/project/clusters/index.md', anchor: 'security-implications')
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/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/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/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index dfac62e7985..1bfd8a85f0f 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -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/project_members/_new_shared_group.html.haml b/app/views/projects/project_members/_new_project_group.html.haml
index d7227c32833..7de24ec5ad2 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-create"
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/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index 7fb80450161..35872d70db4 100644
--- a/app/views/projects/wikis/_form.html.haml
+++ b/app/views/projects/wikis/_form.html.haml
@@ -7,7 +7,7 @@
= 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: markdown_version } do |f|
+ data: { markdown_version: markdown_version, uploads_path: uploads_path } do |f|
= form_errors(@page)
- if @page.persisted?
diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml
index 71359708022..e12b8f2858c 100644
--- a/app/views/projects/wikis/edit.html.haml
+++ b/app/views/projects/wikis/edit.html.haml
@@ -28,21 +28,8 @@
= 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/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/_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/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/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/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/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/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/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/delete_container_repository_worker.rb b/app/workers/delete_container_repository_worker.rb
new file mode 100644
index 00000000000..b703530d3a0
--- /dev/null
+++ b/app/workers/delete_container_repository_worker.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+class DeleteContainerRepositoryWorker
+ include ApplicationWorker
+ include ExclusiveLeaseGuard
+
+ LEASE_TIMEOUT = 1.hour
+
+ attr_reader :container_repository
+
+ 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
+
+ # 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/new_merge_request_worker.rb b/app/workers/new_merge_request_worker.rb
index 5d8b8904502..62f9d9b6f57 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.write_cache
issuable.create_cross_references!(user)
end
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/changelogs/unreleased/21371-avatar-fix.yml b/changelogs/unreleased/21371-avatar-fix.yml
new file mode 100644
index 00000000000..6e00a4ba360
--- /dev/null
+++ b/changelogs/unreleased/21371-avatar-fix.yml
@@ -0,0 +1,5 @@
+---
+title: "Vertically centres landscape avatars."
+merge_request: 21371
+author: Vicary Archangel
+type: fixed
diff --git a/changelogs/unreleased/21617-initialize-projects-with-readme.yml b/changelogs/unreleased/21617-initialize-projects-with-readme.yml
new file mode 100644
index 00000000000..168f6af60c5
--- /dev/null
+++ b/changelogs/unreleased/21617-initialize-projects-with-readme.yml
@@ -0,0 +1,5 @@
+---
+title: Adds a initialize_with_readme parameter to POST /projects
+merge_request: 21617
+author: Steve
+type: added
diff --git a/changelogs/unreleased/2934-create-new-project-re-add-project-name-field.yml b/changelogs/unreleased/2934-create-new-project-re-add-project-name-field.yml
new file mode 100644
index 00000000000..ad9136b69c2
--- /dev/null
+++ b/changelogs/unreleased/2934-create-new-project-re-add-project-name-field.yml
@@ -0,0 +1,5 @@
+---
+title: Re-add project name field on "Create new project" page
+merge_request: 21386
+author:
+type: other
diff --git a/changelogs/unreleased/38208-due-dates-system-notes.yml b/changelogs/unreleased/38208-due-dates-system-notes.yml
new file mode 100644
index 00000000000..60e09ff64de
--- /dev/null
+++ b/changelogs/unreleased/38208-due-dates-system-notes.yml
@@ -0,0 +1,5 @@
+---
+title: Add system note when due date is changed
+merge_request:
+author: Eva Kadlecova
+type: added
diff --git a/changelogs/unreleased/39923-automatically-disable-auto-devops-for-project.yml b/changelogs/unreleased/39923-automatically-disable-auto-devops-for-project.yml
new file mode 100644
index 00000000000..76b411e9e8c
--- /dev/null
+++ b/changelogs/unreleased/39923-automatically-disable-auto-devops-for-project.yml
@@ -0,0 +1,5 @@
+---
+title: Disable Auto DevOps for project upon first pipeline failure
+merge_request: 21172
+author:
+type: added
diff --git a/changelogs/unreleased/42861-move-include-external-files-in-gitlab-ci-yml-from-starter-to-libre.yml b/changelogs/unreleased/42861-move-include-external-files-in-gitlab-ci-yml-from-starter-to-libre.yml
new file mode 100644
index 00000000000..171779817c8
--- /dev/null
+++ b/changelogs/unreleased/42861-move-include-external-files-in-gitlab-ci-yml-from-starter-to-libre.yml
@@ -0,0 +1,5 @@
+---
+title: Move including external files in .gitlab-ci.yml from Starter to Libre
+merge_request: 21603
+author:
+type: changed
diff --git a/changelogs/unreleased/44005-improve-handling-of-projects-shared-with-a-group.yml b/changelogs/unreleased/44005-improve-handling-of-projects-shared-with-a-group.yml
new file mode 100644
index 00000000000..a828ee36eb4
--- /dev/null
+++ b/changelogs/unreleased/44005-improve-handling-of-projects-shared-with-a-group.yml
@@ -0,0 +1,5 @@
+---
+title: Overhaul listing of projects in the group overview page
+merge_request: 20262
+author:
+type: added
diff --git a/changelogs/unreleased/48167-fix-outdated-discussions-new-datastructure.yml b/changelogs/unreleased/48167-fix-outdated-discussions-new-datastructure.yml
new file mode 100644
index 00000000000..a8fcce2eeb8
--- /dev/null
+++ b/changelogs/unreleased/48167-fix-outdated-discussions-new-datastructure.yml
@@ -0,0 +1,5 @@
+---
+title: Fix outdated discussions being shown on Merge Request Changes tab
+merge_request: 21543
+author:
+type: fixed
diff --git a/changelogs/unreleased/48778-remove-old-storage-logic-from-import-export.yml b/changelogs/unreleased/48778-remove-old-storage-logic-from-import-export.yml
new file mode 100644
index 00000000000..4ddbc118e86
--- /dev/null
+++ b/changelogs/unreleased/48778-remove-old-storage-logic-from-import-export.yml
@@ -0,0 +1,5 @@
+---
+title: Update Import/Export to only use new storage uploaders logic
+merge_request: 21409
+author:
+type: added
diff --git a/changelogs/unreleased/48902-fix-diff-vertical-alignment.yml b/changelogs/unreleased/48902-fix-diff-vertical-alignment.yml
new file mode 100644
index 00000000000..75018d57e5b
--- /dev/null
+++ b/changelogs/unreleased/48902-fix-diff-vertical-alignment.yml
@@ -0,0 +1,5 @@
+---
+title: Fix vertical alignment of text in diffs
+merge_request: 21573
+author:
+type: fixed
diff --git a/changelogs/unreleased/49632-clean-up-the-top-section-of-the-cluster-page.yml b/changelogs/unreleased/49632-clean-up-the-top-section-of-the-cluster-page.yml
new file mode 100644
index 00000000000..eae5da45524
--- /dev/null
+++ b/changelogs/unreleased/49632-clean-up-the-top-section-of-the-cluster-page.yml
@@ -0,0 +1,5 @@
+---
+title: Make cluster page settings easier to read
+merge_request: 21550
+author:
+type: other
diff --git a/changelogs/unreleased/49644-make-margin-of-user-status-emoji-consistent.yml b/changelogs/unreleased/49644-make-margin-of-user-status-emoji-consistent.yml
new file mode 100644
index 00000000000..a2ae582fb1c
--- /dev/null
+++ b/changelogs/unreleased/49644-make-margin-of-user-status-emoji-consistent.yml
@@ -0,0 +1,5 @@
+---
+title: Make margin of user status emoji consistent
+merge_request: 21268
+author:
+type: other
diff --git a/changelogs/unreleased/49943-resolve-filter-bar-height-changes.yml b/changelogs/unreleased/49943-resolve-filter-bar-height-changes.yml
new file mode 100644
index 00000000000..aa19b816b0b
--- /dev/null
+++ b/changelogs/unreleased/49943-resolve-filter-bar-height-changes.yml
@@ -0,0 +1,5 @@
+---
+title: Fix filter bar height bug when a tag is added
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/50535-display-banner-to-notify-user-if-project-is-implicitly-opted-ado.yml b/changelogs/unreleased/50535-display-banner-to-notify-user-if-project-is-implicitly-opted-ado.yml
new file mode 100644
index 00000000000..23d4d504f04
--- /dev/null
+++ b/changelogs/unreleased/50535-display-banner-to-notify-user-if-project-is-implicitly-opted-ado.yml
@@ -0,0 +1,5 @@
+---
+title: Display banner on project page if AutoDevOps is implicitly enabled
+merge_request: 21503
+author:
+type: added
diff --git a/changelogs/unreleased/50677-fix-cherry-pick-branch-empty-name.yml b/changelogs/unreleased/50677-fix-cherry-pick-branch-empty-name.yml
new file mode 100644
index 00000000000..88a2ab802c8
--- /dev/null
+++ b/changelogs/unreleased/50677-fix-cherry-pick-branch-empty-name.yml
@@ -0,0 +1,5 @@
+---
+title: Fixes 500 for cherry pick API with empty branch name
+merge_request: 21501
+author: Jacopo Beschi @jacopo-beschi
+type: fixed
diff --git a/changelogs/unreleased/50678-ignores-project-pending-delete.yml b/changelogs/unreleased/50678-ignores-project-pending-delete.yml
new file mode 100644
index 00000000000..e4594abba99
--- /dev/null
+++ b/changelogs/unreleased/50678-ignores-project-pending-delete.yml
@@ -0,0 +1,5 @@
+---
+title: Excludes project marked from deletion to projects API
+merge_request: 21542
+author: Jacopo Beschi @jacopo-beschi
+type: changed
diff --git a/changelogs/unreleased/50835-add-filtering-sorting-for-labels-on-labels-page.yml b/changelogs/unreleased/50835-add-filtering-sorting-for-labels-on-labels-page.yml
new file mode 100644
index 00000000000..24e231ed88a
--- /dev/null
+++ b/changelogs/unreleased/50835-add-filtering-sorting-for-labels-on-labels-page.yml
@@ -0,0 +1,5 @@
+---
+title: Add sorting for labels on labels page
+merge_request: 21642
+author:
+type: added
diff --git a/changelogs/unreleased/50989-add-trigger-information-to-job-api.yml b/changelogs/unreleased/50989-add-trigger-information-to-job-api.yml
new file mode 100644
index 00000000000..5c8c78b8de8
--- /dev/null
+++ b/changelogs/unreleased/50989-add-trigger-information-to-job-api.yml
@@ -0,0 +1,5 @@
+---
+title: Add trigger information in job API
+merge_request: 21495
+author:
+type: other
diff --git a/changelogs/unreleased/51112-add-status-illustration-in-job-api.yml b/changelogs/unreleased/51112-add-status-illustration-in-job-api.yml
new file mode 100644
index 00000000000..fdc75e28824
--- /dev/null
+++ b/changelogs/unreleased/51112-add-status-illustration-in-job-api.yml
@@ -0,0 +1,5 @@
+---
+title: Add empty state illustration information in job API
+merge_request: 21532
+author:
+type: other
diff --git a/changelogs/unreleased/51180-update-ffi-to-1-9-25.yml b/changelogs/unreleased/51180-update-ffi-to-1-9-25.yml
new file mode 100644
index 00000000000..67354d6a610
--- /dev/null
+++ b/changelogs/unreleased/51180-update-ffi-to-1-9-25.yml
@@ -0,0 +1,5 @@
+---
+title: Update ffi to 1.9.25
+merge_request: 21561
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/51281-on-master-diff-view-contains-extra-and-signs.yml b/changelogs/unreleased/51281-on-master-diff-view-contains-extra-and-signs.yml
new file mode 100644
index 00000000000..2ca74b4bc48
--- /dev/null
+++ b/changelogs/unreleased/51281-on-master-diff-view-contains-extra-and-signs.yml
@@ -0,0 +1,5 @@
+---
+title: Fixes double +/- on inline diff view
+merge_request: 21634
+author:
+type: fixed
diff --git a/changelogs/unreleased/51318-project-export-broken-when-avatar-is-set.yml b/changelogs/unreleased/51318-project-export-broken-when-avatar-is-set.yml
new file mode 100644
index 00000000000..c0f7e88f2b7
--- /dev/null
+++ b/changelogs/unreleased/51318-project-export-broken-when-avatar-is-set.yml
@@ -0,0 +1,5 @@
+---
+title: Fix broken exports when they include a projet avatar
+merge_request: 21649
+author:
+type: fixed
diff --git a/changelogs/unreleased/api-promote-find-branch.yml b/changelogs/unreleased/api-promote-find-branch.yml
new file mode 100644
index 00000000000..cfa767809b2
--- /dev/null
+++ b/changelogs/unreleased/api-promote-find-branch.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: Use find_branch! in all places'
+merge_request: 21614
+author: Robert Schilling
+type: fixed
diff --git a/changelogs/unreleased/clean-gitlab-git.yml b/changelogs/unreleased/clean-gitlab-git.yml
new file mode 100644
index 00000000000..d7086b8eea0
--- /dev/null
+++ b/changelogs/unreleased/clean-gitlab-git.yml
@@ -0,0 +1,5 @@
+---
+title: Remove Rugged and shell code from Gitlab::Git
+merge_request: 21488
+author:
+type: other
diff --git a/changelogs/unreleased/da-synchronize-the-default-branch-when-updating-a-remote-mirror.yml b/changelogs/unreleased/da-synchronize-the-default-branch-when-updating-a-remote-mirror.yml
new file mode 100644
index 00000000000..723aa3eee8a
--- /dev/null
+++ b/changelogs/unreleased/da-synchronize-the-default-branch-when-updating-a-remote-mirror.yml
@@ -0,0 +1,5 @@
+---
+title: Synchronize the default branch when updating a remote mirror
+merge_request: 21653
+author:
+type: fixed
diff --git a/changelogs/unreleased/ee-6381-multiseries.yml b/changelogs/unreleased/ee-6381-multiseries.yml
new file mode 100644
index 00000000000..a749e31d27c
--- /dev/null
+++ b/changelogs/unreleased/ee-6381-multiseries.yml
@@ -0,0 +1,5 @@
+---
+title: Allow gaps in multiseries metrics charts
+merge_request: 21427
+author:
+type: fixed
diff --git a/changelogs/unreleased/feat-update-contribution-calendar.yml b/changelogs/unreleased/feat-update-contribution-calendar.yml
new file mode 100644
index 00000000000..4ada8b1fcd5
--- /dev/null
+++ b/changelogs/unreleased/feat-update-contribution-calendar.yml
@@ -0,0 +1,5 @@
+---
+title: Include private contributions to contributions calendar
+merge_request: 17296
+author: George Tsiolis
+type: added
diff --git a/changelogs/unreleased/fix-mention-in-edit-mr.yml b/changelogs/unreleased/fix-mention-in-edit-mr.yml
new file mode 100644
index 00000000000..a82b0ba9748
--- /dev/null
+++ b/changelogs/unreleased/fix-mention-in-edit-mr.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed mention autocomplete in edit merge request.
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-namespace-upload.yml b/changelogs/unreleased/fix-namespace-upload.yml
new file mode 100644
index 00000000000..383d79a998f
--- /dev/null
+++ b/changelogs/unreleased/fix-namespace-upload.yml
@@ -0,0 +1,5 @@
+---
+title: Fix workhorse temp path for namespace uploads
+merge_request: 21650
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-namespace-uploader.yml b/changelogs/unreleased/fix-namespace-uploader.yml
new file mode 100644
index 00000000000..081adc9a6f1
--- /dev/null
+++ b/changelogs/unreleased/fix-namespace-uploader.yml
@@ -0,0 +1,5 @@
+---
+title: Fix NamespaceUploader.base_dir for remote uploads
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/fj-51194-fix-wiki-attachments-with-whitespaces.yml b/changelogs/unreleased/fj-51194-fix-wiki-attachments-with-whitespaces.yml
new file mode 100644
index 00000000000..a6464807e01
--- /dev/null
+++ b/changelogs/unreleased/fj-51194-fix-wiki-attachments-with-whitespaces.yml
@@ -0,0 +1,5 @@
+---
+title: Replace white spaces in wiki attachments file names
+merge_request: 21569
+author:
+type: fixed
diff --git a/changelogs/unreleased/frozen-string-enable-app-helpers.yml b/changelogs/unreleased/frozen-string-enable-app-helpers.yml
new file mode 100644
index 00000000000..7f6805ccb5a
--- /dev/null
+++ b/changelogs/unreleased/frozen-string-enable-app-helpers.yml
@@ -0,0 +1,5 @@
+---
+title: Enable frozen string for app/helpers/**/*.rb
+merge_request:
+author: gfyoung
+type: performance
diff --git a/changelogs/unreleased/frozen-string-enable-vestigial.yml b/changelogs/unreleased/frozen-string-enable-vestigial.yml
new file mode 100644
index 00000000000..55313ff0fcc
--- /dev/null
+++ b/changelogs/unreleased/frozen-string-enable-vestigial.yml
@@ -0,0 +1,5 @@
+---
+title: Enable frozen string in vestigial files
+merge_request:
+author: gfyoung
+type: performance
diff --git a/changelogs/unreleased/ide-commit-panel-improved.yml b/changelogs/unreleased/ide-commit-panel-improved.yml
new file mode 100644
index 00000000000..245214185e5
--- /dev/null
+++ b/changelogs/unreleased/ide-commit-panel-improved.yml
@@ -0,0 +1,5 @@
+---
+title: Improved commit panel in Web IDE
+merge_request: 21471
+author:
+type: changed
diff --git a/changelogs/unreleased/ide-file-templates.yml b/changelogs/unreleased/ide-file-templates.yml
new file mode 100644
index 00000000000..68983670b25
--- /dev/null
+++ b/changelogs/unreleased/ide-file-templates.yml
@@ -0,0 +1,5 @@
+---
+title: Added file templates to the Web IDE
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/import-all-common-metrics-into-database.yml b/changelogs/unreleased/import-all-common-metrics-into-database.yml
new file mode 100644
index 00000000000..524112fe115
--- /dev/null
+++ b/changelogs/unreleased/import-all-common-metrics-into-database.yml
@@ -0,0 +1,5 @@
+---
+title: Import all common metrics into database
+merge_request: 21459
+author:
+type: changed
diff --git a/changelogs/unreleased/issue_50488.yml b/changelogs/unreleased/issue_50488.yml
new file mode 100644
index 00000000000..dad7ae55a0d
--- /dev/null
+++ b/changelogs/unreleased/issue_50488.yml
@@ -0,0 +1,5 @@
+---
+title: Move project services log to a separate file
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/issue_50528.yml b/changelogs/unreleased/issue_50528.yml
new file mode 100644
index 00000000000..82d33bfa255
--- /dev/null
+++ b/changelogs/unreleased/issue_50528.yml
@@ -0,0 +1,5 @@
+---
+title: Log project services errors when executing async
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/label-event.yml b/changelogs/unreleased/label-event.yml
new file mode 100644
index 00000000000..e543abe5649
--- /dev/null
+++ b/changelogs/unreleased/label-event.yml
@@ -0,0 +1,6 @@
+---
+title: Use separate model for tracking resource label changes and render label system
+ notes based on data from this model.
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/lock-unlock-quick-actions.yml b/changelogs/unreleased/lock-unlock-quick-actions.yml
new file mode 100644
index 00000000000..9322d60ba52
--- /dev/null
+++ b/changelogs/unreleased/lock-unlock-quick-actions.yml
@@ -0,0 +1,5 @@
+---
+title: Add /lock and /unlock quick actions
+merge_request: 15197
+author: Mehdi Lahmam (@mehlah)
+type: added
diff --git a/changelogs/unreleased/osw-send-max-patch-bytes-to-gitaly.yml b/changelogs/unreleased/osw-send-max-patch-bytes-to-gitaly.yml
new file mode 100644
index 00000000000..3c50448e3ff
--- /dev/null
+++ b/changelogs/unreleased/osw-send-max-patch-bytes-to-gitaly.yml
@@ -0,0 +1,5 @@
+---
+title: Send max_patch_bytes to Gitaly via Gitaly::CommitDiffRequest
+merge_request: 21575
+author:
+type: other
diff --git a/changelogs/unreleased/osw-write-cache-upon-mr-creation-and-cache-refactoring.yml b/changelogs/unreleased/osw-write-cache-upon-mr-creation-and-cache-refactoring.yml
new file mode 100644
index 00000000000..4fba33decfa
--- /dev/null
+++ b/changelogs/unreleased/osw-write-cache-upon-mr-creation-and-cache-refactoring.yml
@@ -0,0 +1,5 @@
+---
+title: Write diff highlighting cache upon MR creation (refactors caching)
+merge_request: 21489
+author:
+type: performance
diff --git a/changelogs/unreleased/rails5-fix-issue-move-service.yml b/changelogs/unreleased/rails5-fix-issue-move-service.yml
new file mode 100644
index 00000000000..1e71544e587
--- /dev/null
+++ b/changelogs/unreleased/rails5-fix-issue-move-service.yml
@@ -0,0 +1,6 @@
+---
+title: 'Rails 5: fix issue move service In rails 5, the attributes method for an enum
+ returns the name instead of the database integer.'
+merge_request: 21616
+author: Jasper Maes
+type: other
diff --git a/changelogs/unreleased/rd-26044-new-option-to-prevent-too-big-git-pushes.yml b/changelogs/unreleased/rd-26044-new-option-to-prevent-too-big-git-pushes.yml
new file mode 100644
index 00000000000..f464b6dda5b
--- /dev/null
+++ b/changelogs/unreleased/rd-26044-new-option-to-prevent-too-big-git-pushes.yml
@@ -0,0 +1,5 @@
+---
+title: Allow admins to configure the maximum Git push size
+merge_request: 20758
+author:
+type: added
diff --git a/changelogs/unreleased/remove-zebra-table-background.yml b/changelogs/unreleased/remove-zebra-table-background.yml
new file mode 100644
index 00000000000..dafba72035d
--- /dev/null
+++ b/changelogs/unreleased/remove-zebra-table-background.yml
@@ -0,0 +1,5 @@
+---
+title: Remove striped table styling of Find files and Admin Area Applications views
+merge_request: 21560
+author: Andreas Kämmerle
+type: other
diff --git a/changelogs/unreleased/sh-allow-key-id-in-params.yml b/changelogs/unreleased/sh-allow-key-id-in-params.yml
new file mode 100644
index 00000000000..2be1cfb0ed3
--- /dev/null
+++ b/changelogs/unreleased/sh-allow-key-id-in-params.yml
@@ -0,0 +1,5 @@
+---
+title: Filter any parameters ending with "key" in logs
+merge_request: 21688
+author:
+type: changed
diff --git a/changelogs/unreleased/sh-delete-container-registry-async.yml b/changelogs/unreleased/sh-delete-container-registry-async.yml
new file mode 100644
index 00000000000..dfe0e812112
--- /dev/null
+++ b/changelogs/unreleased/sh-delete-container-registry-async.yml
@@ -0,0 +1,5 @@
+---
+title: Delete a container registry asynchronously
+merge_request: 21553
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-remove-orphaned-label-links.yml b/changelogs/unreleased/sh-remove-orphaned-label-links.yml
new file mode 100644
index 00000000000..b035b57ff1b
--- /dev/null
+++ b/changelogs/unreleased/sh-remove-orphaned-label-links.yml
@@ -0,0 +1,5 @@
+---
+title: Remove orphaned label links
+merge_request: 21552
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-support-adding-confirmed-emails.yml b/changelogs/unreleased/sh-support-adding-confirmed-emails.yml
new file mode 100644
index 00000000000..1b64a1c62dc
--- /dev/null
+++ b/changelogs/unreleased/sh-support-adding-confirmed-emails.yml
@@ -0,0 +1,5 @@
+---
+title: Add ability to skip user email confirmation with API
+merge_request: 21630
+author:
+type: added
diff --git a/changelogs/unreleased/sh-upgrade-katex-0-9-0.yml b/changelogs/unreleased/sh-upgrade-katex-0-9-0.yml
new file mode 100644
index 00000000000..2a27e37c053
--- /dev/null
+++ b/changelogs/unreleased/sh-upgrade-katex-0-9-0.yml
@@ -0,0 +1,5 @@
+---
+title: Bump KaTeX version to 0.9.0
+merge_request: 21625
+author:
+type: fixed
diff --git a/changelogs/unreleased/update-gitlab-shell.yml b/changelogs/unreleased/update-gitlab-shell.yml
new file mode 100644
index 00000000000..f5d0e30a7be
--- /dev/null
+++ b/changelogs/unreleased/update-gitlab-shell.yml
@@ -0,0 +1,5 @@
+---
+title: Update GitLab Shell to v8.3.2
+merge_request: 21701
+author:
+type: fixed
diff --git a/changelogs/unreleased/usage_consent.yml b/changelogs/unreleased/usage_consent.yml
new file mode 100644
index 00000000000..56e00879b9a
--- /dev/null
+++ b/changelogs/unreleased/usage_consent.yml
@@ -0,0 +1,5 @@
+---
+title: Ask user explicitly about usage stats agreement on single user deployments.
+merge_request: 21423
+author:
+type: added
diff --git a/changelogs/unreleased/zj-cleanup-port-gitaly.yml b/changelogs/unreleased/zj-cleanup-port-gitaly.yml
new file mode 100644
index 00000000000..25e13b0fd50
--- /dev/null
+++ b/changelogs/unreleased/zj-cleanup-port-gitaly.yml
@@ -0,0 +1,5 @@
+---
+title: Administrative cleanup rake tasks now leverage Gitaly
+merge_request: 21588
+author:
+type: changed
diff --git a/config/application.rb b/config/application.rb
index 76a2c47a750..f3c53fa63f3 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -19,6 +19,7 @@ module Gitlab
require_dependency Rails.root.join('lib/gitlab/request_context')
require_dependency Rails.root.join('lib/gitlab/current_settings')
require_dependency Rails.root.join('lib/gitlab/middleware/read_only')
+ require_dependency Rails.root.join('lib/gitlab/middleware/basic_health_check')
# This needs to be loaded before DB connection is made
# to make sure that all connections have NO_ZERO_DATE
@@ -84,6 +85,7 @@ module Gitlab
# - Any parameter ending with `token`
# - Any parameter containing `password`
# - Any parameter containing `secret`
+ # - Any parameter ending with `key`
# - Two-factor tokens (:otp_attempt)
# - Repo/Project Import URLs (:import_url)
# - Build traces (:trace)
@@ -91,15 +93,13 @@ module Gitlab
# - GitLab Pages SSL cert/key info (:certificate, :encrypted_key)
# - Webhook URLs (:hook)
# - Sentry DSN (:sentry_dsn)
- # - Deploy keys (:key)
# - File content from Web Editor (:content)
- config.filter_parameters += [/token$/, /password/, /secret/]
+ config.filter_parameters += [/token$/, /password/, /secret/, /key$/]
config.filter_parameters += %i(
certificate
encrypted_key
hook
import_url
- key
otp_attempt
sentry_dsn
trace
@@ -159,7 +159,7 @@ module Gitlab
# This middleware needs to precede ActiveRecord::QueryCache and other middlewares that
# connect to the database.
- config.middleware.insert_after "Rails::Rack::Logger", "Gitlab::Middleware::BasicHealthCheck"
+ config.middleware.insert_after Rails::Rack::Logger, ::Gitlab::Middleware::BasicHealthCheck
config.middleware.insert_after Warden::Manager, Rack::Attack
@@ -196,7 +196,7 @@ module Gitlab
config.cache_store = :redis_store, caching_config_hash
- config.active_record.raise_in_transactional_callbacks = true
+ config.active_record.raise_in_transactional_callbacks = true unless rails5?
config.active_job.queue_adapter = :sidekiq
diff --git a/config/environments/test.rb b/config/environments/test.rb
index af1011a1ab1..072f93150a3 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -21,12 +21,12 @@ Rails.application.configure do
if Gitlab.rails5?
config.public_file_server.enabled = true
+ config.public_file_server.headers = { 'Cache-Control' => 'public, max-age=3600' }
else
config.serve_static_files = true
+ config.static_cache_control = "public, max-age=3600"
end
- config.static_cache_control = "public, max-age=3600"
-
# Show full error reports and disable caching
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
diff --git a/config/initializers/active_record_avoid_type_casting_in_uniqueness_validator.rb b/config/initializers/active_record_avoid_type_casting_in_uniqueness_validator.rb
index d9418caf68b..ef4abb77bd7 100644
--- a/config/initializers/active_record_avoid_type_casting_in_uniqueness_validator.rb
+++ b/config/initializers/active_record_avoid_type_casting_in_uniqueness_validator.rb
@@ -21,8 +21,6 @@
# This bug was fixed in Rails 5.1 by https://github.com/rails/rails/pull/24745/commits/aa062318c451512035c10898a1af95943b1a3803
if Gitlab.rails5?
- ActiveSupport::Deprecation.warn("#{__FILE__} is a monkey patch which must be removed when upgrading to Rails 5.1")
-
if Rails.version.start_with?("5.1")
raise "Remove this monkey patch: #{__FILE__}"
end
diff --git a/config/initializers/static_files.rb b/config/initializers/static_files.rb
index 6c28686e69a..a0b8b68f3ef 100644
--- a/config/initializers/static_files.rb
+++ b/config/initializers/static_files.rb
@@ -1,17 +1,26 @@
app = Rails.application
-if app.config.serve_static_files
+if (Gitlab.rails5? && app.config.public_file_server.enabled) || app.config.serve_static_files
# The `ActionDispatch::Static` middleware intercepts requests for static files
# by checking if they exist in the `/public` directory.
# We're replacing it with our `Gitlab::Middleware::Static` that does the same,
# except ignoring `/uploads`, letting those go through to the GitLab Rails app.
- app.config.middleware.swap(
- ActionDispatch::Static,
- Gitlab::Middleware::Static,
- app.paths["public"].first,
- app.config.static_cache_control
- )
+ if Gitlab.rails5?
+ app.config.middleware.swap(
+ ActionDispatch::Static,
+ Gitlab::Middleware::Static,
+ app.paths["public"].first,
+ headers: app.config.public_file_server.headers
+ )
+ else
+ app.config.middleware.swap(
+ ActionDispatch::Static,
+ Gitlab::Middleware::Static,
+ app.paths["public"].first,
+ app.config.static_cache_control
+ )
+ end
# If webpack-dev-server is configured, proxy webpack's public directory
# instead of looking for static assets
diff --git a/config/karma.config.js b/config/karma.config.js
index 84810332dc2..c890c670619 100644
--- a/config/karma.config.js
+++ b/config/karma.config.js
@@ -80,11 +80,12 @@ if (specFilters.length) {
module.exports = function(config) {
process.env.TZ = 'Etc/UTC';
- const progressReporter = process.env.CI ? 'mocha' : 'progress';
-
const karmaConfig = {
basePath: ROOT_PATH,
browsers: ['ChromeHeadlessCustom'],
+ client: {
+ isCI: !!process.env.CI
+ },
customLaunchers: {
ChromeHeadlessCustom: {
base: 'ChromeHeadless',
@@ -104,11 +105,19 @@ module.exports = function(config) {
preprocessors: {
'spec/javascripts/**/*.js': ['webpack', 'sourcemap'],
},
- reporters: [progressReporter],
+ reporters: ['progress'],
webpack: webpackConfig,
webpackMiddleware: { stats: 'errors-only' },
};
+ if (process.env.CI) {
+ karmaConfig.reporters = ['mocha', 'junit'];
+ karmaConfig.junitReporter = {
+ outputFile: 'junit_karma.xml',
+ useBrowserName: false,
+ };
+ }
+
if (process.env.BABEL_ENV === 'coverage' || process.env.NODE_ENV === 'coverage') {
karmaConfig.reporters.push('coverage-istanbul');
karmaConfig.coverageIstanbulReporter = {
diff --git a/config/prometheus/additional_metrics.yml b/config/prometheus/common_metrics.yml
index c994bad7865..52023a2e3cb 100644
--- a/config/prometheus/additional_metrics.yml
+++ b/config/prometheus/common_metrics.yml
@@ -7,7 +7,8 @@
- nginx_upstream_responses_total
weight: 1
queries:
- - query_range: 'sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) by (status_code)'
+ - id: response_metrics_nginx_ingress_throughput_status_code
+ query_range: 'sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) by (status_code)'
unit: req / sec
label: Status Code
series:
@@ -25,7 +26,8 @@
- nginx_upstream_response_msecs_avg
weight: 1
queries:
- - query_range: 'avg(nginx_upstream_response_msecs_avg{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"})'
+ - id: response_metrics_nginx_ingress_latency_pod_average
+ query_range: 'avg(nginx_upstream_response_msecs_avg{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"})'
label: Pod average
unit: ms
- title: "HTTP Error Rate"
@@ -34,7 +36,8 @@
- nginx_upstream_responses_total
weight: 1
queries:
- - query_range: 'sum(rate(nginx_upstream_responses_total{status_code="5xx", upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) / sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) * 100'
+ - id: response_metrics_nginx_ingress_http_error_rate
+ query_range: 'sum(rate(nginx_upstream_responses_total{status_code="5xx", upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) / sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) * 100'
label: 5xx Errors
unit: "%"
- group: Response metrics (HA Proxy)
@@ -46,10 +49,12 @@
- haproxy_frontend_http_requests_total
weight: 1
queries:
- - query_range: 'sum(rate(haproxy_frontend_http_requests_total{%{environment_filter}}[2m])) by (code)'
+ - id: response_metrics_ha_proxy_throughput_status_code
+ query_range: 'sum(rate(haproxy_frontend_http_requests_total{%{environment_filter}}[2m])) by (code)'
unit: req / sec
+ label: Status Code
series:
- - label: code
+ - label: status_code
when:
- value: 2xx
color: green
@@ -63,7 +68,8 @@
- haproxy_frontend_http_responses_total
weight: 1
queries:
- - query_range: 'sum(rate(haproxy_frontend_http_responses_total{code="5xx",%{environment_filter}}[2m])) / sum(rate(haproxy_frontend_http_responses_total{%{environment_filter}}[2m]))'
+ - id: response_metrics_ha_proxy_http_error_rate
+ query_range: 'sum(rate(haproxy_frontend_http_responses_total{code="5xx",%{environment_filter}}[2m])) / sum(rate(haproxy_frontend_http_responses_total{%{environment_filter}}[2m]))'
label: HTTP Errors
unit: "%"
- group: Response metrics (AWS ELB)
@@ -75,7 +81,8 @@
- aws_elb_request_count_sum
weight: 1
queries:
- - query_range: 'sum(aws_elb_request_count_sum{%{environment_filter}}) / 60'
+ - id: response_metrics_aws_elb_throughput_requests
+ query_range: 'sum(aws_elb_request_count_sum{%{environment_filter}}) / 60'
label: Total
unit: req / sec
- title: "Latency"
@@ -84,7 +91,8 @@
- aws_elb_latency_average
weight: 1
queries:
- - query_range: 'avg(aws_elb_latency_average{%{environment_filter}}) * 1000'
+ - id: response_metrics_aws_elb_latency_average
+ query_range: 'avg(aws_elb_latency_average{%{environment_filter}}) * 1000'
label: Average
unit: ms
- title: "HTTP Error Rate"
@@ -94,7 +102,8 @@
- aws_elb_httpcode_backend_5_xx_sum
weight: 1
queries:
- - query_range: 'sum(aws_elb_httpcode_backend_5_xx_sum{%{environment_filter}}) / sum(aws_elb_request_count_sum{%{environment_filter}})'
+ - id: response_metrics_aws_elb_http_error_rate
+ query_range: 'sum(aws_elb_httpcode_backend_5_xx_sum{%{environment_filter}}) / sum(aws_elb_request_count_sum{%{environment_filter}})'
label: HTTP Errors
unit: "%"
- group: Response metrics (NGINX)
@@ -106,7 +115,8 @@
- nginx_server_requests
weight: 1
queries:
- - query_range: 'sum(rate(nginx_server_requests{server_zone!="*", server_zone!="_", %{environment_filter}}[2m])) by (code)'
+ - id: response_metrics_nginx_throughput_status_code
+ query_range: 'sum(rate(nginx_server_requests{server_zone!="*", server_zone!="_", %{environment_filter}}[2m])) by (code)'
unit: req / sec
label: Status Code
series:
@@ -124,7 +134,8 @@
- nginx_server_requestMsec
weight: 1
queries:
- - query_range: 'avg(nginx_server_requestMsec{%{environment_filter}})'
+ - id: response_metrics_nginx_latency
+ query_range: 'avg(nginx_server_requestMsec{%{environment_filter}})'
label: Upstream
unit: ms
- title: "HTTP Error Rate"
@@ -133,7 +144,8 @@
- nginx_server_requests
weight: 1
queries:
- - query_range: 'sum(rate(nginx_server_requests{code="5xx", %{environment_filter}}[2m]))'
+ - id: response_metrics_nginx_http_error_rate
+ query_range: 'sum(rate(nginx_server_requests{code="5xx", %{environment_filter}}[2m]))'
label: HTTP Errors
unit: "errors / sec"
- group: System metrics (Kubernetes)
@@ -145,7 +157,8 @@
- container_memory_usage_bytes
weight: 4
queries:
- - query_range: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1024/1024/1024'
+ - id: system_metrics_kubernetes_container_memory_total
+ query_range: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1024/1024/1024'
label: Total
unit: GB
- title: "Core Usage (Total)"
@@ -154,7 +167,8 @@
- container_cpu_usage_seconds_total
weight: 3
queries:
- - query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job)'
+ - id: system_metrics_kubernetes_container_cores_total
+ query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job)'
label: Total
unit: "cores"
- title: "Memory Usage (Pod average)"
@@ -163,15 +177,39 @@
- container_memory_usage_bytes
weight: 2
queries:
- - query_range: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024'
+ - id: system_metrics_kubernetes_container_memory_average
+ query_range: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024'
+ label: Pod average
+ unit: MB
+ - title: "Canary: Memory Usage (Pod Average)"
+ y_label: "Memory Used per Pod"
+ required_metrics:
+ - container_memory_usage_bytes
+ weight: 2
+ queries:
+ - id: system_metrics_kubernetes_container_memory_average_canary
+ query_range: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-canary-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-canary-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024'
label: Pod average
unit: MB
- - title: "Core Usage (Pod average)"
+ track: canary
+ - title: "Core Usage (Pod Average)"
y_label: "Cores per Pod"
required_metrics:
- container_cpu_usage_seconds_total
weight: 1
queries:
- - query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job) / count(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}[15m])) by (pod_name))'
+ - id: system_metrics_kubernetes_container_core_usage
+ query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job) / count(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}[15m])) by (pod_name))'
label: Pod average
- unit: "cores" \ No newline at end of file
+ unit: "cores"
+ - title: "Canary: Core Usage (Pod Average)"
+ y_label: "Cores per Pod"
+ required_metrics:
+ - container_cpu_usage_seconds_total
+ weight: 1
+ queries:
+ - id: system_metrics_kubernetes_container_core_usage_canary
+ query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-canary-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job) / count(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-canary-(.*)",namespace="%{kube_namespace}"}[15m])) by (pod_name))'
+ label: Pod average
+ unit: "cores"
+ track: canary
diff --git a/config/routes/group.rb b/config/routes/group.rb
index 343865cc50c..893ec8a4e58 100644
--- a/config/routes/group.rb
+++ b/config/routes/group.rb
@@ -14,6 +14,9 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
get :projects, as: :projects_group
get :activity, as: :activity_group
put :transfer, as: :transfer_group
+ # TODO: Remove as part of refactor in https://gitlab.com/gitlab-org/gitlab-ce/issues/49693
+ get 'shared', action: :show, as: :group_shared
+ get 'archived', action: :show, as: :group_archived
end
get '/', action: :show, as: :group_canonical
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index fb7738a5536..0e723cdeb9c 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -46,6 +46,7 @@
- [project_service, 1]
- [delete_user, 1]
- [todos_destroyer, 1]
+ - [delete_container_repository, 1]
- [delete_merged_branches, 1]
- [authorized_projects, 1]
- [expire_build_instance_artifacts, 1]
@@ -78,3 +79,4 @@
- [create_note_diff_file, 1]
- [delete_diff_files, 1]
- [detect_repository_languages, 1]
+ - [auto_devops, 2]
diff --git a/danger/commit_messages/Dangerfile b/danger/commit_messages/Dangerfile
new file mode 100644
index 00000000000..0341429d3cc
--- /dev/null
+++ b/danger/commit_messages/Dangerfile
@@ -0,0 +1,192 @@
+# frozen_string_literal: true
+
+# rubocop: disable Style/SignalException
+# rubocop: disable Metrics/CyclomaticComplexity
+# rubocop: disable Metrics/PerceivedComplexity
+
+# Perform various checks against commits. We're not using
+# https://github.com/jonallured/danger-commit_lint because its output is not
+# very helpful, and it doesn't offer the means of ignoring merge commits.
+
+def fail_commit(commit, message)
+ fail("#{commit.sha}: #{message}")
+end
+
+def lines_changed_in_commit(commit)
+ commit.diff_parent.stats[:total][:lines]
+end
+
+def subject_starts_with_capital?(subject)
+ first_char = subject.chars.first
+
+ first_char.upcase == first_char
+end
+
+def ce_upstream?
+ gitlab.mr_labels.any? { |label| label == 'CE upstream' }
+end
+
+def lint_commits(commits)
+ failures = false
+
+ unicode_emoji_regex = %r((
+ [\u{1F300}-\u{1F5FF}] |
+ [\u{1F1E6}-\u{1F1FF}] |
+ [\u{2700}-\u{27BF}] |
+ [\u{1F900}-\u{1F9FF}] |
+ [\u{1F600}-\u{1F64F}] |
+ [\u{1F680}-\u{1F6FF}] |
+ [\u{2600}-\u{26FF}]
+ ))x
+
+ commits.each do |commit|
+ # For now we'll ignore merge commits, as getting rid of those is a problem
+ # separate from enforcing good commit messages.
+ next if commit.message.start_with?('Merge branch')
+
+ subject, separator, details = commit.message.split("\n", 3)
+
+ if subject.split.length < 3
+ fail_commit(
+ commit,
+ 'The commit subject must contain at least three words'
+ )
+
+ failures = true
+ end
+
+ if subject.length > 50
+ fail_commit(
+ commit,
+ 'The commit subject may not be longer than 50 characters'
+ )
+
+ failures = true
+ end
+
+ unless subject_starts_with_capital?(subject)
+ fail_commit(commit, 'The commit subject must start with a capital letter')
+ failures = true
+ end
+
+ if subject.end_with?('.')
+ fail_commit(commit, 'The commit subject must not end with a period')
+ failures = true
+ end
+
+ if separator && !separator.empty?
+ fail_commit(
+ commit,
+ 'The commit subject and body must be separated by a blank line'
+ )
+
+ failures = true
+ end
+
+ details&.each_line do |line|
+ line = line.strip
+
+ next if line.length <= 72
+
+ url_size = line.scan(%r((https?://\S+))).sum { |(url)| url.length }
+
+ # If the line includes a URL, we'll allow it to exceed 72 characters, but
+ # only if the line _without_ the URL does not exceed this limit.
+ next if line.length - url_size <= 72
+
+ fail_commit(
+ commit,
+ 'The commit body should not contain more than 72 characters per line'
+ )
+
+ failures = true
+ end
+
+ if !details && lines_changed_in_commit(commit) >= 20
+ fail_commit(
+ commit,
+ 'Commits that change more than 20 lines ' \
+ 'must describe these changes in the commit body'
+ )
+
+ failures = true
+ end
+
+ if commit.message.match?(/:[\+a-z0-9_\-]+:/)
+ fail_commit(
+ commit,
+ 'Avoid the use of Markdown Emoji such as `:+1:`. ' \
+ 'These add no value to the commit message, ' \
+ 'and are displayed as plain text outside of GitLab'
+ )
+
+ failures = true
+ end
+
+ if commit.message.match?(unicode_emoji_regex)
+ fail_commit(
+ commit,
+ 'Avoid the use of Unicode Emoji. ' \
+ 'These add no value to the commit message, ' \
+ 'and may not be displayed properly everywhere'
+ )
+
+ failures = true
+ end
+
+ if commit.message.match?(%r(([\w\-\/]+)?(#|!|&|%)\d+))
+ fail_commit(
+ commit,
+ 'Use full URLs instead of short references ' \
+ '(`gitlab-org/gitlab-ce#123` or `!123`), as short references are ' \
+ 'displayed as plain text outside of GitLab'
+ )
+
+ failures = true
+ end
+ end
+
+ if failures
+ markdown(<<~MARKDOWN)
+ ## Commit message standards
+
+ One or more commit messages do not meet our Git commit message standards.
+ For more information on how to write a good commit message, take a look at
+ [How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/).
+
+ Here is an example of a good commit message:
+
+ Reject ruby interpolation in externalized strings
+
+ When using ruby interpolation in externalized strings, they can't be
+ detected. Which means they will never be presented to be translated.
+
+ To mix variables into translations we need to use `sprintf`
+ instead.
+
+ Instead of:
+
+ _("Hello \#{subject}")
+
+ Use:
+
+ _("Hello %{subject}) % { subject: 'world' }
+
+ This is an example of a bad commit message:
+
+ updated README.md
+
+ This commit message is bad because although it tells us that README.md is
+ updated, it doesn't tell us why or how it was updated.
+ MARKDOWN
+ end
+end
+
+if git.commits.length > 10 && !ce_upstream?
+ warn(
+ 'This merge request includes more than 10 commits. ' \
+ 'Please rebase these commits into a smaller number of commits.'
+ )
+else
+ lint_commits(git.commits)
+end
diff --git a/danger/documentation/Dangerfile b/danger/documentation/Dangerfile
new file mode 100644
index 00000000000..d65bec123a9
--- /dev/null
+++ b/danger/documentation/Dangerfile
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+# All the files/directories that should be reviewed by the Docs team.
+DOCS_FILES = [
+ 'doc/'
+].freeze
+
+def docs_paths_requiring_review(files)
+ files.select do |file|
+ DOCS_FILES.any? { |pattern| file.start_with?(pattern) }
+ end
+end
+
+all_files = git.added_files + git.modified_files
+
+docs_paths_to_review = docs_paths_requiring_review(all_files)
+
+unless docs_paths_to_review.empty?
+ message 'This merge request adds or changes files that require a ' \
+ 'review from the docs team.'
+
+ markdown(<<~MARKDOWN)
+## Docs Review
+
+The following files require a review from the Documentation team:
+
+* #{docs_paths_to_review.map { |path| "`#{path}`" }.join("\n* ")}
+
+To make sure these changes are reviewed, mention `@gl-docsteam` in a separate
+comment, and explain what needs to be reviewed by the team. Please don't mention
+the team until your changes are ready for review.
+ MARKDOWN
+
+ unless gitlab.mr_labels.include?('Documentation')
+ warn 'This merge request is missing the ~Documentation label.'
+ end
+end
diff --git a/db/fixtures/development/17_cycle_analytics.rb b/db/fixtures/development/17_cycle_analytics.rb
index 7b9a4bad449..285436f4324 100644
--- a/db/fixtures/development/17_cycle_analytics.rb
+++ b/db/fixtures/development/17_cycle_analytics.rb
@@ -6,20 +6,6 @@ class Gitlab::Seeder::CycleAnalytics
@project = project
@user = User.admins.first
@issue_count = perf ? 1000 : 5
- stub_git_pre_receive!
- end
-
- # The GitLab API needn't be running for the fixtures to be
- # created. Since we're performing a number of git actions
- # here (like creating a branch or committing a file), we need
- # to disable the `pre_receive` hook in order to remove this
- # dependency on the GitLab API.
- def stub_git_pre_receive!
- Gitlab::Git::HooksService.class_eval do
- def run_hook(name)
- [true, '']
- end
- end
end
def seed_metrics!
diff --git a/db/fixtures/development/99_common_metrics.rb b/db/fixtures/development/99_common_metrics.rb
new file mode 100644
index 00000000000..1f39c0ce5a0
--- /dev/null
+++ b/db/fixtures/development/99_common_metrics.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+require Rails.root.join('db/importers/common_metrics_importer.rb')
+
+::Importers::CommonMetricsImporter.new.execute
diff --git a/db/fixtures/production/999_common_metrics.rb b/db/fixtures/production/999_common_metrics.rb
new file mode 100644
index 00000000000..1f39c0ce5a0
--- /dev/null
+++ b/db/fixtures/production/999_common_metrics.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+require Rails.root.join('db/importers/common_metrics_importer.rb')
+
+::Importers::CommonMetricsImporter.new.execute
diff --git a/db/importers/common_metrics_importer.rb b/db/importers/common_metrics_importer.rb
new file mode 100644
index 00000000000..6302394d7a6
--- /dev/null
+++ b/db/importers/common_metrics_importer.rb
@@ -0,0 +1,103 @@
+# frozen_string_literal: true
+
+module Importers
+ class PrometheusMetric < ActiveRecord::Base
+ enum group: {
+ # built-in groups
+ nginx_ingress: -1,
+ ha_proxy: -2,
+ aws_elb: -3,
+ nginx: -4,
+ kubernetes: -5,
+
+ # custom groups
+ business: 0,
+ response: 1,
+ system: 2
+ }
+
+ scope :common, -> { where(common: true) }
+
+ GROUP_TITLES = {
+ business: _('Business metrics (Custom)'),
+ response: _('Response metrics (Custom)'),
+ system: _('System metrics (Custom)'),
+ 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)')
+ }.freeze
+ end
+
+ class CommonMetricsImporter
+ MissingQueryId = Class.new(StandardError)
+
+ attr_reader :content
+
+ def initialize(filename = 'common_metrics.yml')
+ @content = YAML.load_file(Rails.root.join('config', 'prometheus', filename))
+ end
+
+ def execute
+ PrometheusMetric.reset_column_information
+
+ process_content do |id, attributes|
+ find_or_build_metric!(id)
+ .update!(**attributes)
+ end
+ end
+
+ private
+
+ def process_content(&blk)
+ content.map do |group|
+ process_group(group, &blk)
+ end
+ end
+
+ def process_group(group, &blk)
+ attributes = {
+ group: find_group_title_key(group['group'])
+ }
+
+ group['metrics'].map do |metric|
+ process_metric(metric, attributes, &blk)
+ end
+ end
+
+ def process_metric(metric, attributes, &blk)
+ attributes = attributes.merge(
+ title: metric['title'],
+ y_label: metric['y_label'])
+
+ metric['queries'].map do |query|
+ process_metric_query(query, attributes, &blk)
+ end
+ end
+
+ def process_metric_query(query, attributes, &blk)
+ attributes = attributes.merge(
+ legend: query['label'],
+ query: query['query_range'],
+ unit: query['unit'])
+
+ yield(query['id'], attributes)
+ end
+
+ def find_or_build_metric!(id)
+ raise MissingQueryId unless id
+
+ PrometheusMetric.common.find_by(identifier: id) ||
+ PrometheusMetric.new(common: true, identifier: id)
+ end
+
+ def find_group_title_key(title)
+ PrometheusMetric.groups[find_group_title(title)]
+ end
+
+ def find_group_title(title)
+ PrometheusMetric::GROUP_TITLES.invert[title]
+ end
+ end
+end
diff --git a/db/migrate/20180101160629_create_prometheus_metrics.rb b/db/migrate/20180101160629_create_prometheus_metrics.rb
new file mode 100644
index 00000000000..c3be0939b17
--- /dev/null
+++ b/db/migrate/20180101160629_create_prometheus_metrics.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class CreatePrometheusMetrics < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def change
+ create_table :prometheus_metrics do |t|
+ t.references :project, index: true, foreign_key: { on_delete: :cascade }, null: false
+ t.string :title, null: false
+ t.string :query, null: false
+ t.string :y_label
+ t.string :unit
+ t.string :legend
+ t.integer :group, null: false, index: true
+ t.timestamps_with_timezone null: false
+ end
+ end
+end
diff --git a/db/migrate/20180101160630_change_project_id_for_prometheus_metrics.rb b/db/migrate/20180101160630_change_project_id_for_prometheus_metrics.rb
new file mode 100644
index 00000000000..66820f13f54
--- /dev/null
+++ b/db/migrate/20180101160630_change_project_id_for_prometheus_metrics.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class ChangeProjectIdForPrometheusMetrics < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ change_column_null :prometheus_metrics, :project_id, true
+ end
+end
diff --git a/db/migrate/20180228172924_add_include_private_contributions_to_users.rb b/db/migrate/20180228172924_add_include_private_contributions_to_users.rb
new file mode 100644
index 00000000000..ea3ebdd83d1
--- /dev/null
+++ b/db/migrate/20180228172924_add_include_private_contributions_to_users.rb
@@ -0,0 +1,7 @@
+class AddIncludePrivateContributionsToUsers < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def change
+ add_column :users, :include_private_contributions, :boolean
+ end
+end
diff --git a/db/migrate/20180720023512_add_receive_max_input_size_to_application_settings.rb b/db/migrate/20180720023512_add_receive_max_input_size_to_application_settings.rb
new file mode 100644
index 00000000000..4ed851a0780
--- /dev/null
+++ b/db/migrate/20180720023512_add_receive_max_input_size_to_application_settings.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddReceiveMaxInputSizeToApplicationSettings < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def change
+ add_column :application_settings, :receive_max_input_size, :integer
+ end
+end
diff --git a/db/migrate/20180831164904_fix_prometheus_metric_query_limits.rb b/db/migrate/20180831164904_fix_prometheus_metric_query_limits.rb
new file mode 100644
index 00000000000..28c92e7c7ac
--- /dev/null
+++ b/db/migrate/20180831164904_fix_prometheus_metric_query_limits.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+require Rails.root.join('db/migrate/prometheus_metrics_limits_to_mysql')
+
+class FixPrometheusMetricQueryLimits < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ PrometheusMetricsLimitsToMysql.new.up
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/migrate/20180831164905_add_common_to_prometheus_metrics.rb b/db/migrate/20180831164905_add_common_to_prometheus_metrics.rb
new file mode 100644
index 00000000000..e21c156fff6
--- /dev/null
+++ b/db/migrate/20180831164905_add_common_to_prometheus_metrics.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddCommonToPrometheusMetrics < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default(:prometheus_metrics, :common, :boolean, default: false)
+ end
+
+ def down
+ remove_column(:prometheus_metrics, :common)
+ end
+end
diff --git a/db/migrate/20180831164907_add_index_on_common_for_prometheus_metrics.rb b/db/migrate/20180831164907_add_index_on_common_for_prometheus_metrics.rb
new file mode 100644
index 00000000000..fdbaaf67b87
--- /dev/null
+++ b/db/migrate/20180831164907_add_index_on_common_for_prometheus_metrics.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddIndexOnCommonForPrometheusMetrics < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :prometheus_metrics, :common
+ end
+
+ def down
+ remove_concurrent_index :prometheus_metrics, :common
+ end
+end
diff --git a/db/migrate/20180831164908_add_identifier_to_prometheus_metric.rb b/db/migrate/20180831164908_add_identifier_to_prometheus_metric.rb
new file mode 100644
index 00000000000..67de990757e
--- /dev/null
+++ b/db/migrate/20180831164908_add_identifier_to_prometheus_metric.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AddIdentifierToPrometheusMetric < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :prometheus_metrics, :identifier, :string
+ end
+end
diff --git a/db/migrate/20180831164909_add_index_for_identifier_to_prometheus_metric.rb b/db/migrate/20180831164909_add_index_for_identifier_to_prometheus_metric.rb
new file mode 100644
index 00000000000..b30c24ccafe
--- /dev/null
+++ b/db/migrate/20180831164909_add_index_for_identifier_to_prometheus_metric.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddIndexForIdentifierToPrometheusMetric < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :prometheus_metrics, :identifier, unique: true
+ end
+
+ def down
+ remove_concurrent_index :prometheus_metrics, :identifier, unique: true
+ end
+end
diff --git a/db/migrate/20180831164910_import_common_metrics.rb b/db/migrate/20180831164910_import_common_metrics.rb
new file mode 100644
index 00000000000..72658c09b8e
--- /dev/null
+++ b/db/migrate/20180831164910_import_common_metrics.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class ImportCommonMetrics < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ require Rails.root.join('db/importers/common_metrics_importer.rb')
+
+ DOWNTIME = false
+
+ def up
+ Importers::CommonMetricsImporter.new.execute
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/migrate/20180901171833_add_project_config_source_status_index_to_pipeline.rb b/db/migrate/20180901171833_add_project_config_source_status_index_to_pipeline.rb
new file mode 100644
index 00000000000..99dfcc94b12
--- /dev/null
+++ b/db/migrate/20180901171833_add_project_config_source_status_index_to_pipeline.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddProjectConfigSourceStatusIndexToPipeline < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :ci_pipelines, [:project_id, :status, :config_source]
+ end
+
+ def down
+ remove_concurrent_index :ci_pipelines, [:project_id, :status, :config_source]
+ end
+end
diff --git a/db/migrate/20180901200537_add_resource_label_event_reference_fields.rb b/db/migrate/20180901200537_add_resource_label_event_reference_fields.rb
new file mode 100644
index 00000000000..264970ceed8
--- /dev/null
+++ b/db/migrate/20180901200537_add_resource_label_event_reference_fields.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AddResourceLabelEventReferenceFields < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def change
+ add_column :resource_label_events, :cached_markdown_version, :integer
+ add_column :resource_label_events, :reference, :text
+ add_column :resource_label_events, :reference_html, :text
+ end
+end
diff --git a/db/migrate/20180906101639_add_user_ping_consent_to_application_settings.rb b/db/migrate/20180906101639_add_user_ping_consent_to_application_settings.rb
new file mode 100644
index 00000000000..5d0e67d2648
--- /dev/null
+++ b/db/migrate/20180906101639_add_user_ping_consent_to_application_settings.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddUserPingConsentToApplicationSettings < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_column :application_settings, :usage_stats_set_by_user_id, :integer
+ add_concurrent_foreign_key :application_settings, :users, column: :usage_stats_set_by_user_id, on_delete: :nullify
+ end
+
+ def down
+ remove_foreign_key :application_settings, column: :usage_stats_set_by_user_id
+ remove_column :application_settings, :usage_stats_set_by_user_id
+ end
+end
diff --git a/db/migrate/prometheus_metrics_limits_to_mysql.rb b/db/migrate/prometheus_metrics_limits_to_mysql.rb
new file mode 100644
index 00000000000..79f4ab9b64b
--- /dev/null
+++ b/db/migrate/prometheus_metrics_limits_to_mysql.rb
@@ -0,0 +1,12 @@
+class PrometheusMetricsLimitsToMysql < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def up
+ return unless Gitlab::Database.mysql?
+
+ change_column :prometheus_metrics, :query, :text, limit: 4096, default: nil
+ end
+
+ def down
+ end
+end
diff --git a/db/post_migrate/20180816193530_rename_login_root_namespaces.rb b/db/post_migrate/20180816193530_rename_login_root_namespaces.rb
index 60cec24eed6..b0c1fb98fa8 100644
--- a/db/post_migrate/20180816193530_rename_login_root_namespaces.rb
+++ b/db/post_migrate/20180816193530_rename_login_root_namespaces.rb
@@ -7,10 +7,14 @@ class RenameLoginRootNamespaces < ActiveRecord::Migration
# We're taking over the /login namespace as part of a fix for the Jira integration
def up
- rename_root_paths 'login'
+ disable_statement_timeout do
+ rename_root_paths 'login'
+ end
end
def down
- revert_renames
+ disable_statement_timeout do
+ revert_renames
+ end
end
end
diff --git a/db/post_migrate/20180906051323_remove_orphaned_label_links.rb b/db/post_migrate/20180906051323_remove_orphaned_label_links.rb
new file mode 100644
index 00000000000..b56b74f483e
--- /dev/null
+++ b/db/post_migrate/20180906051323_remove_orphaned_label_links.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+class RemoveOrphanedLabelLinks < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ class LabelLinks < ActiveRecord::Base
+ self.table_name = 'label_links'
+ include EachBatch
+
+ def self.orphaned
+ where('NOT EXISTS ( SELECT 1 FROM labels WHERE labels.id = label_links.label_id )')
+ end
+ end
+
+ def up
+ # Some of these queries can take up to 10 seconds to run on GitLab.com,
+ # which is pretty close to our 15 second statement timeout. To ensure a
+ # smooth deployment procedure we disable the statement timeouts for this
+ # migration, just in case.
+ disable_statement_timeout do
+ # On GitLab.com there are over 2,000,000 orphaned label links. On
+ # staging, removing 100,000 rows generated a max replication lag of 6.7
+ # MB. In total, removing all these rows will only generate about 136 MB
+ # of data, so it should be safe to do this.
+ LabelLinks.orphaned.each_batch(of: 100_000) do |batch|
+ batch.delete_all
+ end
+ end
+
+ add_concurrent_foreign_key(:label_links, :labels, column: :label_id, on_delete: :cascade)
+ end
+
+ def down
+ # There is no way to restore orphaned label links.
+ if foreign_key_exists?(:label_links, column: :label_id)
+ remove_foreign_key(:label_links, column: :label_id)
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 1d05be0d3e8..13814dd569e 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20180826111825) do
+ActiveRecord::Schema.define(version: 20180906101639) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -172,6 +172,8 @@ ActiveRecord::Schema.define(version: 20180826111825) do
t.boolean "instance_statistics_visibility_private", default: false, null: false
t.boolean "web_ide_clientside_preview_enabled", default: false, null: false
t.boolean "user_show_add_ssh_key_message", default: true, null: false
+ t.integer "usage_stats_set_by_user_id"
+ t.integer "receive_max_input_size"
end
create_table "audit_events", force: :cascade do |t|
@@ -476,6 +478,7 @@ ActiveRecord::Schema.define(version: 20180826111825) do
add_index "ci_pipelines", ["project_id", "iid"], name: "index_ci_pipelines_on_project_id_and_iid", unique: true, where: "(iid IS NOT NULL)", using: :btree
add_index "ci_pipelines", ["project_id", "ref", "status", "id"], name: "index_ci_pipelines_on_project_id_and_ref_and_status_and_id", using: :btree
add_index "ci_pipelines", ["project_id", "sha"], name: "index_ci_pipelines_on_project_id_and_sha", using: :btree
+ add_index "ci_pipelines", ["project_id", "status", "config_source"], name: "index_ci_pipelines_on_project_id_and_status_and_config_source", using: :btree
add_index "ci_pipelines", ["project_id"], name: "index_ci_pipelines_on_project_id", using: :btree
add_index "ci_pipelines", ["status"], name: "index_ci_pipelines_on_status", using: :btree
add_index "ci_pipelines", ["user_id"], name: "index_ci_pipelines_on_user_id", using: :btree
@@ -1699,6 +1702,25 @@ ActiveRecord::Schema.define(version: 20180826111825) do
add_index "projects", ["star_count"], name: "index_projects_on_star_count", using: :btree
add_index "projects", ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree
+ create_table "prometheus_metrics", force: :cascade do |t|
+ t.integer "project_id"
+ t.string "title", null: false
+ t.string "query", null: false
+ t.string "y_label"
+ t.string "unit"
+ t.string "legend"
+ t.integer "group", null: false
+ t.datetime_with_timezone "created_at", null: false
+ t.datetime_with_timezone "updated_at", null: false
+ t.boolean "common", default: false, null: false
+ t.string "identifier"
+ end
+
+ add_index "prometheus_metrics", ["common"], name: "index_prometheus_metrics_on_common", using: :btree
+ add_index "prometheus_metrics", ["group"], name: "index_prometheus_metrics_on_group", using: :btree
+ add_index "prometheus_metrics", ["identifier"], name: "index_prometheus_metrics_on_identifier", unique: true, using: :btree
+ add_index "prometheus_metrics", ["project_id"], name: "index_prometheus_metrics_on_project_id", using: :btree
+
create_table "protected_branch_merge_access_levels", force: :cascade do |t|
t.integer "protected_branch_id", null: false
t.integer "access_level", default: 40, null: false
@@ -1821,6 +1843,9 @@ ActiveRecord::Schema.define(version: 20180826111825) do
t.integer "label_id"
t.integer "user_id"
t.datetime_with_timezone "created_at", null: false
+ t.integer "cached_markdown_version"
+ t.text "reference"
+ t.text "reference_html"
end
add_index "resource_label_events", ["issue_id"], name: "index_resource_label_events_on_issue_id", using: :btree
@@ -2181,6 +2206,7 @@ ActiveRecord::Schema.define(version: 20180826111825) do
t.integer "accepted_term_id"
t.string "feed_token"
t.boolean "private_profile"
+ t.boolean "include_private_contributions"
end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
@@ -2252,6 +2278,7 @@ ActiveRecord::Schema.define(version: 20180826111825) do
add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree
add_index "web_hooks", ["type"], name: "index_web_hooks_on_type", using: :btree
+ add_foreign_key "application_settings", "users", column: "usage_stats_set_by_user_id", name: "fk_964370041d", on_delete: :nullify
add_foreign_key "badges", "namespaces", column: "group_id", on_delete: :cascade
add_foreign_key "badges", "projects", on_delete: :cascade
add_foreign_key "boards", "namespaces", column: "group_id", on_delete: :cascade
@@ -2332,6 +2359,7 @@ ActiveRecord::Schema.define(version: 20180826111825) do
add_foreign_key "issues", "users", column: "author_id", name: "fk_05f1e72feb", on_delete: :nullify
add_foreign_key "issues", "users", column: "closed_by_id", name: "fk_c63cbf6c25", on_delete: :nullify
add_foreign_key "issues", "users", column: "updated_by_id", name: "fk_ffed080f01", on_delete: :nullify
+ add_foreign_key "label_links", "labels", name: "fk_d97dd08678", on_delete: :cascade
add_foreign_key "label_priorities", "labels", on_delete: :cascade
add_foreign_key "label_priorities", "projects", on_delete: :cascade
add_foreign_key "labels", "namespaces", column: "group_id", on_delete: :cascade
@@ -2379,6 +2407,7 @@ ActiveRecord::Schema.define(version: 20180826111825) do
add_foreign_key "project_import_data", "projects", name: "fk_ffb9ee3a10", on_delete: :cascade
add_foreign_key "project_mirror_data", "projects", on_delete: :cascade
add_foreign_key "project_statistics", "projects", on_delete: :cascade
+ add_foreign_key "prometheus_metrics", "projects", on_delete: :cascade
add_foreign_key "protected_branch_merge_access_levels", "protected_branches", name: "fk_8a3072ccb3", on_delete: :cascade
add_foreign_key "protected_branch_push_access_levels", "protected_branches", name: "fk_9ffc86a3d9", on_delete: :cascade
add_foreign_key "protected_branches", "projects", name: "fk_7a9c6d93e7", on_delete: :cascade
diff --git a/doc/administration/logs.md b/doc/administration/logs.md
index 0fbb4481fb8..98134075b94 100644
--- a/doc/administration/logs.md
+++ b/doc/administration/logs.md
@@ -113,6 +113,19 @@ October 07, 2014 11:25: User "Claudie Hodkiewicz" (nasir_stehr@olson.co.uk) was
October 07, 2014 11:25: Project "project133" was removed
```
+## `integrations_json.log`
+
+This file lives in `/var/log/gitlab/gitlab-rails/integrations_json.log` for
+Omnibus GitLab packages or in `/home/git/gitlab/log/integrations_json.log` for
+installations from source.
+
+It contains information about [integrations](../user/project/integrations/project_services.md) activities such as JIRA, Asana and Irker services. It uses JSON format like the example below:
+
+``` json
+{"severity":"ERROR","time":"2018-09-06T14:56:20.439Z","service_class":"JiraService","project_id":8,"project_path":"h5bp/html5-boilerplate","message":"Error sending message","client_url":"http://jira.gitlap.com:8080","error":"execution expired"}
+{"severity":"INFO","time":"2018-09-06T17:15:16.365Z","service_class":"JiraService","project_id":3,"project_path":"namespace2/project2","message":"Successfully posted","client_url":"http://jira.example.net"}
+```
+
## `githost.log`
This file lives in `/var/log/gitlab/gitlab-rails/githost.log` for
diff --git a/doc/administration/monitoring/prometheus/index.md b/doc/administration/monitoring/prometheus/index.md
index 9d525645952..b1b670c3b42 100644
--- a/doc/administration/monitoring/prometheus/index.md
+++ b/doc/administration/monitoring/prometheus/index.md
@@ -97,10 +97,10 @@ For a more fully featured dashboard, Grafana can be used and has
Sample Prometheus queries:
-- **% Memory used:** `(1 - ((node_memory_MemFree + node_memory_Cached) / node_memory_MemTotal)) * 100`
-- **% CPU load:** `1 - rate(node_cpu{mode="idle"}[5m])`
-- **Data transmitted:** `irate(node_network_transmit_bytes[5m])`
-- **Data received:** `irate(node_network_receive_bytes[5m])`
+- **% Memory available:** `((node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) or ((node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes) / node_memory_MemTotal_bytes)) * 100`
+- **% CPU utilization:** `1 - avg without (mode,cpu) (rate(node_cpu_seconds_total{mode="idle"}[5m]))`
+- **Data transmitted:** `rate(node_network_transmit_bytes_total{device!="lo"}[5m])`
+- **Data received:** `rate(node_network_receive_bytes_total{device!="lo"}[5m])`
## Configuring Prometheus to monitor Kubernetes
diff --git a/doc/administration/raketasks/project_import_export.md b/doc/administration/raketasks/project_import_export.md
index 7bd765a35e0..f43bba0a7a7 100644
--- a/doc/administration/raketasks/project_import_export.md
+++ b/doc/administration/raketasks/project_import_export.md
@@ -9,6 +9,7 @@
> application settings (`/admin/application_settings`) under 'Import sources'.
> - The exports are stored in a temporary [shared directory][tmp] and are deleted
> every 24 hours by a specific worker.
+> - ImportExport can use object storage automatically starting from GitLab 11.3
The GitLab Import/Export version can be checked by using:
@@ -30,12 +31,6 @@ sudo gitlab-rake gitlab:import_export:data
bundle exec rake gitlab:import_export:data RAILS_ENV=production
```
-In order to enable Object Storage on the Export, you can use the [feature flag][feature-flags]:
-
-```
-import_export_object_storage
-```
-
[ce-3050]: https://gitlab.com/gitlab-org/gitlab-ce/issues/3050
[feature-flags]: https://docs.gitlab.com/ee/api/features.html
[tmp]: ../../development/shared_files.md
diff --git a/doc/api/README.md b/doc/api/README.md
index e2a6e87a2c3..1738d4fae5c 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -40,6 +40,7 @@ following locations:
- [Namespaces](namespaces.md)
- [Notes](notes.md) (comments)
- [Discussions](discussions.md) (threaded comments)
+- [Resource Label Events](resource_label_events.md)
- [Notification settings](notification_settings.md)
- [Open source license templates](templates/licenses.md)
- [Pages Domains](pages_domains.md)
diff --git a/doc/api/jobs.md b/doc/api/jobs.md
index 5f9556726d1..cf292adf150 100644
--- a/doc/api/jobs.md
+++ b/doc/api/jobs.md
@@ -33,7 +33,9 @@ Example of response
},
"coverage": null,
"created_at": "2015-12-24T15:51:21.727Z",
+ "started_at": "2015-12-24T17:54:24.729Z",
"finished_at": "2015-12-24T17:54:24.921Z",
+ "duration": 0.192,
"artifacts_expire_at": "2016-01-23T17:54:24.921Z",
"id": 6,
"name": "rspec:other",
@@ -47,7 +49,6 @@ Example of response
"artifacts": [],
"runner": null,
"stage": "test",
- "started_at": "2015-12-24T17:54:24.729Z",
"status": "failed",
"tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/6",
@@ -78,6 +79,9 @@ Example of response
},
"coverage": null,
"created_at": "2015-12-24T15:51:21.802Z",
+ "started_at": "2015-12-24T17:54:27.722Z",
+ "finished_at": "2015-12-24T17:54:27.895Z",
+ "duration": 0.173,
"artifacts_file": {
"filename": "artifacts.zip",
"size": 1000
@@ -88,7 +92,6 @@ Example of response
{"file_type": "trace", "size": 1500, "filename": "job.log", "file_format": "raw"},
{"file_type": "junit", "size": 750, "filename": "junit.xml.gz", "file_format": "gzip"}
],
- "finished_at": "2015-12-24T17:54:27.895Z",
"artifacts_expire_at": "2016-01-23T17:54:27.895Z",
"id": 7,
"name": "teaspoon",
@@ -102,7 +105,6 @@ Example of response
"artifacts": [],
"runner": null,
"stage": "test",
- "started_at": "2015-12-24T17:54:27.722Z",
"status": "failed",
"tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/7",
@@ -158,7 +160,9 @@ Example of response
},
"coverage": null,
"created_at": "2015-12-24T15:51:21.727Z",
+ "started_at": "2015-12-24T17:54:24.729Z",
"finished_at": "2015-12-24T17:54:24.921Z",
+ "duration": 0.192,
"artifacts_expire_at": "2016-01-23T17:54:24.921Z",
"id": 6,
"name": "rspec:other",
@@ -172,7 +176,6 @@ Example of response
"artifacts": [],
"runner": null,
"stage": "test",
- "started_at": "2015-12-24T17:54:24.729Z",
"status": "failed",
"tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/6",
@@ -203,6 +206,9 @@ Example of response
},
"coverage": null,
"created_at": "2015-12-24T15:51:21.802Z",
+ "started_at": "2015-12-24T17:54:27.722Z",
+ "finished_at": "2015-12-24T17:54:27.895Z",
+ "duration": 0.173,
"artifacts_file": {
"filename": "artifacts.zip",
"size": 1000
@@ -213,7 +219,6 @@ Example of response
{"file_type": "trace", "size": 1500, "filename": "job.log", "file_format": "raw"},
{"file_type": "junit", "size": 750, "filename": "junit.xml.gz", "file_format": "gzip"}
],
- "finished_at": "2015-12-24T17:54:27.895Z",
"artifacts_expire_at": "2016-01-23T17:54:27.895Z",
"id": 7,
"name": "teaspoon",
@@ -227,7 +232,6 @@ Example of response
"artifacts": [],
"runner": null,
"stage": "test",
- "started_at": "2015-12-24T17:54:27.722Z",
"status": "failed",
"tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/7",
@@ -281,7 +285,9 @@ Example of response
},
"coverage": null,
"created_at": "2015-12-24T15:51:21.880Z",
+ "started_at": "2015-12-24T17:54:30.733Z",
"finished_at": "2015-12-24T17:54:31.198Z",
+ "duration": 0.465,
"artifacts_expire_at": "2016-01-23T17:54:31.198Z",
"id": 8,
"name": "rubocop",
@@ -295,7 +301,6 @@ Example of response
"artifacts": [],
"runner": null,
"stage": "test",
- "started_at": "2015-12-24T17:54:30.733Z",
"status": "failed",
"tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/8",
@@ -319,7 +324,7 @@ Example of response
## Get job artifacts
> **Notes**:
->
+>
> - [Introduced][ce-2893] in GitLab 8.5.
Get job artifacts of a project.
@@ -351,7 +356,7 @@ Response:
## Download the artifacts archive
> **Notes**:
->
+>
> - [Introduced][ce-5347] in GitLab 8.10.
Download the artifacts archive from the given reference name and job provided the
@@ -474,14 +479,15 @@ Example of response
},
"coverage": null,
"created_at": "2016-01-11T10:13:33.506Z",
- "finished_at": "2016-01-11T10:14:09.526Z",
+ "started_at": "2016-01-11T10:14:09.526Z",
+ "finished_at": null,
+ "duration": 8,
"id": 42,
"name": "rubocop",
"ref": "master",
"artifacts": [],
"runner": null,
"stage": "test",
- "started_at": null,
"status": "canceled",
"tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/42",
@@ -521,14 +527,15 @@ Example of response
},
"coverage": null,
"created_at": "2016-01-11T10:13:33.506Z",
+ "started_at": null,
"finished_at": null,
+ "duration": null,
"id": 42,
"name": "rubocop",
"ref": "master",
"artifacts": [],
"runner": null,
"stage": "test",
- "started_at": null,
"status": "pending",
"tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/42",
@@ -581,6 +588,7 @@ Example of response
"created_at": "2016-01-11T10:13:33.506Z",
"started_at": "2016-01-11T10:13:33.506Z",
"finished_at": "2016-01-11T10:15:10.506Z",
+ "duration": 97.0,
"status": "failed",
"tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/42",
@@ -633,6 +641,7 @@ Example response:
"created_at": "2016-01-11T10:13:33.506Z",
"started_at": "2016-01-11T10:13:33.506Z",
"finished_at": "2016-01-11T10:15:10.506Z",
+ "duration": 97.0,
"status": "failed",
"tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/42",
@@ -672,14 +681,15 @@ Example of response
},
"coverage": null,
"created_at": "2016-01-11T10:13:33.506Z",
+ "started_at": null,
"finished_at": null,
+ "duration": null,
"id": 42,
"name": "rubocop",
"ref": "master",
"artifacts": [],
"runner": null,
"stage": "test",
- "started_at": null,
"status": "started",
"tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/42",
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 7e8b7c4b502..947e7db9c52 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -661,6 +661,7 @@ POST /projects
| `avatar` | mixed | no | Image file for avatar of the project |
| `printing_merge_request_link_enabled` | boolean | no | Show link to create/view merge request when pushing from the command line |
| `ci_config_path` | string | no | The path to CI config file |
+| `initialize_with_readme` | boolean | no | `false` by default |
## Create project for user
diff --git a/doc/api/resource_label_events.md b/doc/api/resource_label_events.md
new file mode 100644
index 00000000000..33e4821ccf4
--- /dev/null
+++ b/doc/api/resource_label_events.md
@@ -0,0 +1,175 @@
+# Resource label events API
+
+Resource label events keep track about who, when, and which label was added or removed to an issuable.
+
+## Issues
+
+### List project issue label events
+
+Gets a list of all label events for a single issue.
+
+```
+GET /projects/:id/issues/:issue_iid/resource_label_events
+```
+
+| Attribute | Type | Required | Description |
+| ------------------- | ---------------- | ---------- | ------------ |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
+| `issue_iid` | integer | yes | The IID of an issue |
+
+```json
+[
+ {
+ "id": 142,
+ "user": {
+ "id": 1,
+ "name": "Administrator",
+ "username": "root",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "web_url": "http://gitlab.example.com/root"
+ },
+ "created_at": "2018-08-20T13:38:20.077Z",
+ "resource_type": "Issue",
+ "resource_id": 253,
+ "label": {
+ "id": 73,
+ "name": "a1",
+ "color": "#34495E",
+ "description": ""
+ },
+ "action": "add"
+ },
+ {
+ "id": 143,
+ "user": {
+ "id": 1,
+ "name": "Administrator",
+ "username": "root",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "web_url": "http://gitlab.example.com/root"
+ },
+ "created_at": "2018-08-20T13:38:20.077Z",
+ "resource_type": "Issue",
+ "resource_id": 253,
+ "label": {
+ "id": 74,
+ "name": "p1",
+ "color": "#0033CC",
+ "description": ""
+ },
+ "action": "remove"
+ }
+]
+```
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/11/resource_label_events
+```
+
+### Get single issue label event
+
+Returns a single label event for a specific project issue
+
+```
+GET /projects/:id/issues/:issue_iid/resource_label_events/:resource_label_event_id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------------- | -------------- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
+| `issue_iid` | integer | yes | The IID of an issue |
+| `resource_label_event_id` | integer | yes | The ID of a label event |
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/11/resource_label_events/1
+```
+
+## Merge requests
+
+### List project merge request label events
+
+Gets a list of all label events for a single merge request.
+
+```
+GET /projects/:id/merge_requests/:merge_request_iid/resource_label_events
+```
+
+| Attribute | Type | Required | Description |
+| ------------------- | ---------------- | ---------- | ------------ |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
+| `merge_request_iid` | integer | yes | The IID of a merge request |
+
+```json
+[
+ {
+ "id": 119,
+ "user": {
+ "id": 1,
+ "name": "Administrator",
+ "username": "root",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "web_url": "http://gitlab.example.com/root"
+ },
+ "created_at": "2018-08-20T06:17:28.394Z",
+ "resource_type": "MergeRequest",
+ "resource_id": 28,
+ "label": {
+ "id": 74,
+ "name": "p1",
+ "color": "#0033CC",
+ "description": ""
+ },
+ "action": "add"
+ },
+ {
+ "id": 120,
+ "user": {
+ "id": 1,
+ "name": "Administrator",
+ "username": "root",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "web_url": "http://gitlab.example.com/root"
+ },
+ "created_at": "2018-08-20T06:17:28.394Z",
+ "resource_type": "MergeRequest",
+ "resource_id": 28,
+ "label": {
+ "id": 41,
+ "name": "project",
+ "color": "#D1D100",
+ "description": ""
+ },
+ "action": "add"
+ }
+]
+```
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/resource_label_events
+```
+
+### Get single merge request label event
+
+Returns a single label event for a specific project merge request
+
+```
+GET /projects/:id/merge_requests/:merge_request_iid/resource_label_events/:resource_label_event_id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| ------------------- | -------------- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
+| `merge_request_iid` | integer | yes | The IID of a merge request |
+| `resource_label_event_id` | integer | yes | The ID of a label event |
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/resource_label_events/120
+```
diff --git a/doc/api/users.md b/doc/api/users.md
index a8858468cab..51935280401 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -972,6 +972,7 @@ Parameters:
- `id` (required) - id of specified user
- `email` (required) - email address
+- `skip_confirmation` (optional) - Skip confirmation and assume e-mail is verified - true or false (default)
## Delete email for current user
diff --git a/doc/ci/caching/index.md b/doc/ci/caching/index.md
index 01e95b54fc4..b41101695f6 100644
--- a/doc/ci/caching/index.md
+++ b/doc/ci/caching/index.md
@@ -24,14 +24,14 @@ Don't mix the caching with passing artifacts between stages. Caching is not
designed to pass artifacts between stages. Cache is for runtime dependencies
needed to compile the project:
-- `cache` - **Use for temporary storage for project dependencies.** Not useful
+- `cache`: **Use for temporary storage for project dependencies.** Not useful
for keeping intermediate build results, like `jar` or `apk` files.
Cache was designed to be used to speed up invocations of subsequent runs of a
given job, by keeping things like dependencies (e.g., npm packages, Go vendor
packages, etc.) so they don't have to be re-fetched from the public internet.
While the cache can be abused to pass intermediate build results between stages,
there may be cases where artifacts are a better fit.
-- `artifacts` - **Use for stage results that will be passed between stages.**
+- `artifacts`: **Use for stage results that will be passed between stages.**
Artifacts were designed to upload some compiled/generated bits of the build,
and they can be fetched by any number of concurrent Runners. They are
guaranteed to be available and are there to pass data between jobs. They are
@@ -57,19 +57,20 @@ control exactly where artifacts are passed around.
In summary:
-- Caches are disabled if not defined globally or per job (using `cache:`)
-- Caches are available for all jobs in your `.gitlab-ci.yml` if enabled globally
+- Caches are disabled if not defined globally or per job (using `cache:`).
+- Caches are available for all jobs in your `.gitlab-ci.yml` if enabled globally.
- Caches can be used by subsequent pipelines of that very same job (a script in
a stage) in which the cache was created (if not defined globally).
- Caches are stored where the Runner is installed **and** uploaded to S3 if
- [distributed cache is enabled](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching)
-- Caches defined per job are only used either a) for the next pipeline of that job,
- or b) if that same cache is also defined in a subsequent job of the same pipeline
-- Artifacts are disabled if not defined per job (using `artifacts:`)
-- Artifacts can only be enabled per job, not globally
+ [distributed cache is enabled](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching).
+- Caches defined per job are only used, either:
+ - For the next pipeline of that job.
+ - If that same cache is also defined in a subsequent job of the same pipeline.
+- Artifacts are disabled if not defined per job (using `artifacts:`).
+- Artifacts can only be enabled per job, not globally.
- Artifacts are created during a pipeline and can be used by the subsequent
- jobs of that currently active pipeline
-- Artifacts are always uploaded to GitLab (known as coordinator)
+ jobs of that currently active pipeline.
+- Artifacts are always uploaded to GitLab (known as coordinator).
- Artifacts can have an expiration value for controlling disk usage (30 days by default).
## Good caching practices
@@ -97,13 +98,13 @@ or pipelines in a guaranteed manner.
From the perspective of the Runner, in order for cache to work effectively, one
of the following must be true:
-- Use a single Runner for all your jobs
-- Use multiple Runners (in autoscale mode or not) that use
+- Use a single Runner for all your jobs.
+- Use multiple Runners (in autoscale mode or not) that use.
[distributed caching](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching),
- where the cache is stored in S3 buckets (like shared Runners on GitLab.com)
+ where the cache is stored in S3 buckets (like shared Runners on GitLab.com).
- Use multiple Runners (not in autoscale mode) of the same architecture that
share a common network-mounted directory (using NFS or something similar)
- where the cache will be stored
+ where the cache will be stored.
TIP: **Tip:**
Read about the [availability of the cache](#availability-of-the-cache)
@@ -367,19 +368,19 @@ job B:
Here's what happens behind the scenes:
-1. Pipeline starts
-1. `job A` runs
-1. `before_script` is executed
-1. `script` is executed
-1. `after_script` is executed
+1. Pipeline starts.
+1. `job A` runs.
+1. `before_script` is executed.
+1. `script` is executed.
+1. `after_script` is executed.
1. `cache` runs and the `vendor/` directory is zipped into `cache.zip`.
This file is then saved in the directory based on the
[Runner's setting](#where-the-caches-are-stored) and the `cache: key`.
-1. `job B` runs
-1. The cache is extracted (if found)
-1. `before_script` is executed
-1. `script` is executed
-1. Pipeline finishes
+1. `job B` runs.
+1. The cache is extracted (if found).
+1. `before_script` is executed.
+1. `script` is executed.
+1. Pipeline finishes.
By using a single Runner on a single machine, you'll not have the issue where
`job B` might execute on a Runner different from `job A`, thus guaranteeing the
@@ -451,13 +452,13 @@ job B:
- vendor/
```
-1. `job A` runs
-1. `public/` is cached as cache.zip
-1. `job B` runs
-1. The previous cache, if any, is unzipped
-1. `vendor/` is cached as cache.zip and overwrites the previous one
+1. `job A` runs.
+1. `public/` is cached as cache.zip.
+1. `job B` runs.
+1. The previous cache, if any, is unzipped.
+1. `vendor/` is cached as cache.zip and overwrites the previous one.
1. The next time `job A` runs it will use the cache of `job B` which is different
- and thus will be ineffective
+ and thus will be ineffective.
To fix that, use different `keys` for each job.
@@ -514,12 +515,12 @@ next run of the pipeline, the cache will be stored in a different location.
If you want to avoid editing `.gitlab-ci.yml`, you can easily clear the cache
via GitLab's UI:
-1. Navigate to your project's **CI/CD > Pipelines** page
-1. Click on the **Clear Runner caches** button to clean up the cache
+1. Navigate to your project's **CI/CD > Pipelines** page.
+1. Click on the **Clear Runner caches** button to clean up the cache.
![Clear Runners cache](img/clear_runners_cache.png)
-1. On the next push, your CI/CD job will use a new cache
+1. On the next push, your CI/CD job will use a new cache.
Behind the scenes, this works by increasing a counter in the database, and the
value of that counter is used to create the key for the cache by appending an
diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md
index 54a8cfe7fd8..aa997d15b64 100644
--- a/doc/ci/docker/using_docker_build.md
+++ b/doc/ci/docker/using_docker_build.md
@@ -151,16 +151,16 @@ In order to do that, follow the steps:
DOCKER_DRIVER: overlay2
services:
- - docker:dind
+ - docker:dind
before_script:
- - docker info
+ - docker info
build:
stage: build
script:
- - docker build -t my-docker-image .
- - docker run my-docker-image /script/to/run/tests
+ - docker build -t my-docker-image .
+ - docker run my-docker-image /script/to/run/tests
```
Docker-in-Docker works well, and is the recommended configuration, but it is
@@ -246,13 +246,13 @@ In order to do that, follow the steps:
image: docker:stable
before_script:
- - docker info
+ - docker info
build:
stage: build
script:
- - docker build -t my-docker-image .
- - docker run my-docker-image /script/to/run/tests
+ - docker build -t my-docker-image .
+ - docker run my-docker-image /script/to/run/tests
```
While the above method avoids using Docker in privileged mode, you should be
@@ -403,7 +403,7 @@ could look like:
build:
image: docker:stable
services:
- - docker:dind
+ - docker:dind
variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_DRIVER: overlay2
@@ -456,13 +456,13 @@ an application-specific deploy script:
```yaml
image: docker:stable
services:
-- docker:dind
+ - docker:dind
stages:
-- build
-- test
-- release
-- deploy
+ - build
+ - test
+ - release
+ - deploy
variables:
DOCKER_HOST: tcp://docker:2375
diff --git a/doc/ci/interactive_web_terminal/index.md b/doc/ci/interactive_web_terminal/index.md
index 507aceb27fa..8ce4fe55cec 100644
--- a/doc/ci/interactive_web_terminal/index.md
+++ b/doc/ci/interactive_web_terminal/index.md
@@ -1,10 +1,6 @@
# Getting started with interactive web terminals
-> Introduced in GitLab 11.3.
-
-CAUTION: **Warning:**
-Interactive web terminals are in beta, so they might not work properly and
-lack features. For more information [follow issue #25990](https://gitlab.com/gitlab-org/gitlab-ce/issues/25990).
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/50144) in GitLab 11.3.
Interactive web terminals give the user access to a terminal in GitLab for
running one-of commands for their CI pipeline.
diff --git a/doc/ci/pipelines.md b/doc/ci/pipelines.md
index 4e964af97f5..ea47d676edb 100644
--- a/doc/ci/pipelines.md
+++ b/doc/ci/pipelines.md
@@ -9,7 +9,7 @@ you may need to enable pipeline triggering in your project's
## Pipelines
-A pipeline is a group of [jobs][] that get executed in [stages][](batches).
+A pipeline is a group of [jobs] that get executed in [stages].
All of the jobs in a stage are executed in parallel (if there are enough
concurrent [Runners]), and if they all succeed, the pipeline moves on to the
next stage. If one of the jobs fails, the next stage is not (usually)
@@ -29,17 +29,17 @@ There are three types of pipelines that often use the single shorthand of "pipel
![Types of Pipelines](img/types-of-pipelines.svg)
-1. **CI Pipeline**: Build and test stages defined in `.gitlab-ci.yml`
-2. **Deploy Pipeline**: Deploy stage(s) defined in `.gitlab-ci.yml` The flow of deploying code to servers through various stages: e.g. development to staging to production
-3. **Project Pipeline**: Cross-project CI dependencies [triggered via API][triggers], particularly for micro-services, but also for complicated build dependencies: e.g. api -> front-end, ce/ee -> omnibus.
+1. **CI Pipeline**: Build and test stages defined in `.gitlab-ci.yml`.
+1. **Deploy Pipeline**: Deploy stage(s) defined in `.gitlab-ci.yml` The flow of deploying code to servers through various stages: e.g. development to staging to production.
+1. **Project Pipeline**: Cross-project CI dependencies [triggered via API][triggers], particularly for micro-services, but also for complicated build dependencies: e.g. api -> front-end, ce/ee -> omnibus.
## Development workflows
Pipelines accommodate several development workflows:
-1. **Branch Flow** (e.g. different branch for dev, qa, staging, production)
-2. **Trunk-based Flow** (e.g. feature branches and single master branch, possibly with tags for releases)
-3. **Fork-based Flow** (e.g. merge requests come from forks)
+1. **Branch Flow** (e.g. different branch for dev, qa, staging, production).
+1. **Trunk-based Flow** (e.g. feature branches and single master branch, possibly with tags for releases).
+1. **Fork-based Flow** (e.g. merge requests come from forks).
Example continuous delivery flow:
@@ -57,6 +57,16 @@ Pipelines are defined in `.gitlab-ci.yml` by specifying [jobs] that run in
See the reference [documentation for jobs](yaml/README.md#jobs).
+## Manually executing pipelines
+
+Pipelines can be manually executed, with predefined or manually-specified [variables](variables/README.md).
+
+To execute a pipeline manually:
+
+1. Navigate to your project's **CI/CD > Pipelines**.
+1. Click on the **Run Pipeline** button.
+1. Select the branch to run the pipeline for and enter any environment variables required for the pipeline run.
+
## Seeing pipeline status
You can find the current and historical pipeline runs under your project's
@@ -112,9 +122,9 @@ Then, there is the pipeline mini graph which takes less space and can give you a
quick glance if all jobs pass or something failed. The pipeline mini graph can
be found when you visit:
-- the pipelines index page
-- a single commit page
-- a merge request page
+- The pipelines index page.
+- A single commit page.
+- A merge request page.
That way, you can see all related jobs for a single commit and the net result
of each stage of your pipeline. This allows you to quickly see what failed and
@@ -142,9 +152,9 @@ jobs. Click to expand them.
The basic requirements is that there are two numbers separated with one of
the following (you can even use them interchangeably):
-- a space
-- a slash (`/`)
-- a colon (`:`)
+- A space (` `)
+- A slash (`/`)
+- A colon (`:`)
>**Note:**
More specifically, [it uses][regexp] this regular expression: `\d+[\s:\/\\]+\d+\s*`.
@@ -252,11 +262,12 @@ A strict security model is enforced when pipelines are executed on
The following actions are allowed on protected branches only if the user is
[allowed to merge or push](../user/project/protected_branches.md#using-the-allowed-to-merge-and-allowed-to-push-settings)
on that specific branch:
-- run **manual pipelines** (using Web UI or Pipelines API)
-- run **scheduled pipelines**
-- run pipelines using **triggers**
-- trigger **manual actions** on existing pipelines
-- **retry/cancel** existing jobs (using Web UI or Pipelines API)
+
+- Run **manual pipelines** (using [Web UI](#manually-executing-pipelines) or Pipelines API).
+- Run **scheduled pipelines**.
+- Run pipelines using **triggers**.
+- Trigger **manual actions** on existing pipelines.
+- **Retry/cancel** existing jobs (using Web UI or Pipelines API).
**Variables** marked as **protected** are accessible only to jobs that
run on protected branches, avoiding untrusted users to get unintended access to
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index 115e6e390c9..f11949da64e 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -34,7 +34,7 @@ Some of the predefined environment variables are available only if a minimum
version of [GitLab Runner][runner] is used. Consult the table below to find the
version of Runner required.
->**Note:**
+NOTE: **Note:**
Starting with GitLab 9.0, we have deprecated some variables. Read the
[9.0 Renaming](#9-0-renaming) section to find out their replacements. **You are
strongly advised to use the new variables as we will remove the old ones in
@@ -109,7 +109,7 @@ To follow conventions of naming across GitLab, and to further move away from the
`build` term and toward `job` CI variables have been renamed for the 9.0
release.
->**Note:**
+NOTE: **Note:**
Starting with GitLab 9.0, we have deprecated the `$CI_BUILD_*` variables. **You are
strongly advised to use the new variables as we will remove the old ones in
future GitLab releases.**
@@ -131,7 +131,7 @@ future GitLab releases.**
## `.gitlab-ci.yml` defined variables
->**Note:**
+NOTE **Note:**
This feature requires GitLab Runner 0.5.0 or higher and GitLab CI 7.14 or higher.
GitLab CI allows you to add to `.gitlab-ci.yml` variables that are set in the
@@ -215,9 +215,15 @@ Protected variables can be added by going to your project's
Once you set them, they will be available for all subsequent pipelines.
+### Manually-specified variables
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/44059) in GitLab 10.8.
+
+Variables can be specified for a single pipeline run when a [manual pipeline](../pipelines.md#manually-executing-pipelines) is created.
+
## Deployment variables
->**Note:**
+NOTE: **Note:**
This feature requires GitLab CI 8.15 or higher.
[Project services](../../user/project/integrations/project_services.md) that are
@@ -567,4 +573,4 @@ Below you can find supported syntax reference:
[builds-policies]: ../yaml/README.md#only-and-except-complex
[gitlab-deploy-token]: ../../user/project/deploy_tokens/index.md#gitlab-deploy-token
[registry]: ../../user/project/container_registry.md
-[dependent-repositories]: ../../user/project/new_ci_build_permissions_model.md#dependent-repositories \ No newline at end of file
+[dependent-repositories]: ../../user/project/new_ci_build_permissions_model.md#dependent-repositories
diff --git a/doc/ci/variables/where_variables_can_be_used.md b/doc/ci/variables/where_variables_can_be_used.md
index b2b4a26bdda..b0d2ea6484d 100644
--- a/doc/ci/variables/where_variables_can_be_used.md
+++ b/doc/ci/variables/where_variables_can_be_used.md
@@ -8,10 +8,10 @@ This document describes where and how the different types of variables can be us
## Variables usage
-There are basically two places where you can use any defined variables:
+There are two places defined variables can be used. On the:
-1. On GitLab's side there's `.gitlab-ci.yml`
-1. On the Runner's side there's `config.toml`
+1. GitLab side, in `.gitlab-ci.yml`.
+1. The runner side, in `config.toml`.
### `.gitlab-ci.yml` file
@@ -56,8 +56,8 @@ since the expansion is done in GitLab before any Runner will get the job.
### GitLab Runner internal variable expansion mechanism
- **Supported:** project/group variables, `.gitlab-ci.yml` variables, `config.toml` variables, and
- variables from triggers and pipeline schedules
-- **Not supported:** variables defined inside of scripts (e.g., `export MY_VARIABLE="test"`)
+ variables from triggers, pipeline schedules, and manual pipelines.
+- **Not supported:** variables defined inside of scripts (e.g., `export MY_VARIABLE="test"`).
The Runner uses Go's `os.Expand()` method for variable expansion. It means that it will handle
only variables defined as `$variable` and `${variable}`. What's also important, is that
@@ -80,11 +80,10 @@ are using a different variables syntax.
`.gitlab-ci.yml` variables, `config.toml` variables, and variables from triggers and pipeline schedules).
- The `script` may also use all variables defined in the lines before. So, for example, if you define
a variable `export MY_VARIABLE="test"`:
-
- - in `before_script`, it will work in the following lines of `before_script` and
- all lines of the related `script`
- - in `script`, it will work in the following lines of `script`
- - in `after_script`, it will work in following lines of `after_script`
+ - In `before_script`, it will work in the following lines of `before_script` and
+ all lines of the related `script`.
+ - In `script`, it will work in the following lines of `script`.
+ - In `after_script`, it will work in following lines of `after_script`.
## Persisted variables
@@ -107,7 +106,7 @@ The following variables are known as "persisted":
They are:
-- **supported** for all definitions as [described in the table](#gitlab-ci-yml-file) where the "Expansion place" is "Runner"
-- **not supported:**
- - by the definitions [described in the table](#gitlab-ci-yml-file) where the "Expansion place" is "GitLab"
- - in the `only` and `except` [variables expressions](README.md#variables-expressions)
+- **Supported** for all definitions as [described in the table](#gitlab-ci-yml-file) where the "Expansion place" is "Runner".
+- **Not supported:**
+ - By the definitions [described in the table](#gitlab-ci-yml-file) where the "Expansion place" is "GitLab".
+ - In the `only` and `except` [variables expressions](README.md#variables-expressions).
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index d89705e8ead..d069b94e53b 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -1352,6 +1352,187 @@ test:
retry: 2
```
+## `include`
+
+> Introduced in [GitLab Edition Premium][ee] 10.5.
+> Available for Starter, Premium and Ultimate [versions][gitlab-versions] since 10.6.
+> Behaviour expanded in GitLab 10.8 to allow more flexible overriding.
+> Available for Libre since [11.4](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21603)
+
+Using the `include` keyword, you can allow the inclusion of external YAML files.
+
+In the following example, the content of `.before-script-template.yml` will be
+automatically fetched and evaluated along with the content of `.gitlab-ci.yml`:
+
+```yaml
+# Content of https://gitlab.com/awesome-project/raw/master/.before-script-template.yml
+
+before_script:
+ - apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs
+ - gem install bundler --no-ri --no-rdoc
+ - bundle install --jobs $(nproc) "${FLAGS[@]}"
+```
+
+```yaml
+# Content of .gitlab-ci.yml
+
+include: 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml'
+
+rspec:
+ script:
+ - bundle exec rspec
+```
+
+You can define it either as a single string, or, in case you want to include
+more than one files, an array of different values . The following examples
+are both valid cases:
+
+```yaml
+# Single string
+
+include: '/templates/.after-script-template.yml'
+```
+
+```yaml
+# Array
+
+include:
+ - 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml'
+ - '/templates/.after-script-template.yml'
+```
+
+---
+
+`include` supports two types of files:
+
+- **local** to the same repository, referenced by using full paths in the same
+ repository, with `/` being the root directory. For example:
+
+ ```yaml
+ # Within the repository
+ include: '/templates/.gitlab-ci-template.yml'
+ ```
+
+ NOTE: **Note:**
+ You can only use files that are currently tracked by Git on the same branch
+ your configuration file is. In other words, when using a **local file**, make
+ sure that both `.gitlab-ci.yml` and the local file are on the same branch.
+
+ NOTE: **Note:**
+ We don't support the inclusion of local files through Git submodules paths.
+
+- **remote** in a different location, accessed using HTTP/HTTPS, referenced
+ using the full URL. For example:
+
+ ```yaml
+ include: 'https://gitlab.com/awesome-project/raw/master/.gitlab-ci-template.yml'
+ ```
+
+ NOTE: **Note:**
+ The remote file must be publicly accessible through a simple GET request, as we don't support authentication schemas in the remote URL.
+
+---
+
+
+Since GitLab 10.8 we are now recursively merging the files defined in `include`
+with those in `.gitlab-ci.yml`. Files defined by `include` are always
+evaluated first and recursively merged with the content of `.gitlab-ci.yml`, no
+matter the position of the `include` keyword. You can take advantage of
+recursive merging to customize and override details in included CI
+configurations with local definitions.
+
+The following example shows specific YAML-defined variables and details of the
+`production` job from an include file being customized in `.gitlab-ci.yml`.
+
+```yaml
+# Content of https://company.com/autodevops-template.yml
+
+variables:
+ POSTGRES_USER: user
+ POSTGRES_PASSWORD: testing_password
+ POSTGRES_DB: $CI_ENVIRONMENT_SLUG
+
+production:
+ stage: production
+ script:
+ - install_dependencies
+ - deploy
+ environment:
+ name: production
+ url: https://$CI_PROJECT_PATH_SLUG.$AUTO_DEVOPS_DOMAIN
+ only:
+ - master
+```
+
+```yaml
+# Content of .gitlab-ci.yml
+
+include: 'https://company.com/autodevops-template.yml'
+
+image: alpine:latest
+
+variables:
+ POSTGRES_USER: root
+ POSTGRES_PASSWORD: secure_password
+
+stages:
+ - build
+ - test
+ - production
+
+production:
+ environment:
+ url: https://domain.com
+```
+
+In this case, the variables `POSTGRES_USER` and `POSTGRES_PASSWORD` along
+with the environment url of the `production` job defined in
+`autodevops-template.yml` have been overridden by new values defined in
+`.gitlab-ci.yml`.
+
+NOTE: **Note:**
+Recursive includes are not supported meaning your external files
+should not use the `include` keyword, as it will be ignored.
+
+Recursive merging lets you extend and override dictionary mappings, but
+you cannot add or modify items to an included array. For example, to add
+an additional item to the production job script, you must repeat the
+existing script items.
+
+```yaml
+# Content of https://company.com/autodevops-template.yml
+
+production:
+ stage: production
+ script:
+ - install_dependencies
+ - deploy
+```
+
+```yaml
+# Content of .gitlab-ci.yml
+
+include: 'https://company.com/autodevops-template.yml'
+
+stages:
+ - production
+
+production:
+ script:
+ - install_depedencies
+ - deploy
+ - notify_owner
+```
+
+In this case, if `install_dependencies` and `deploy` were not repeated in
+`.gitlab-ci.yml`, they would not be part of the script for the `production`
+job in the combined CI configuration.
+
+NOTE: **Note:**
+We currently do not support using YAML aliases across different YAML files
+sourced by `include`. You must only refer to aliases in the same file. Instead
+of using YAML anchors you can use [`extends` keyword](#extends).
+
## `variables`
> Introduced in GitLab Runner v0.5.0.
@@ -1456,7 +1637,9 @@ There are three possible values: `none`, `normal`, and `recursive`:
```
- `recursive` means that all submodules (including submodules of submodules)
- will be included. It is equivalent to:
+ will be included. This feature needs Git v1.8.1 and later. When using a
+ GitLab Runner with an executor not based on Docker, make sure the Git version
+ meets that requirement. It is equivalent to:
```
git submodule sync --recursive
diff --git a/doc/development/README.md b/doc/development/README.md
index 20f8fa1d368..b37403552fe 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -47,6 +47,8 @@ description: 'Learn how to contribute to GitLab.'
- [How to dump production data to staging](db_dump.md)
- [Working with the GitHub importer](github_importer.md)
- [Working with Merge Request diffs](diffs.md)
+- [Permissions](permissions.md)
+- [Prometheus metrics](prometheus_metrics.md)
## Performance guides
diff --git a/doc/development/code_review.md b/doc/development/code_review.md
index e50e6370c80..edf0b6f46df 100644
--- a/doc/development/code_review.md
+++ b/doc/development/code_review.md
@@ -27,8 +27,9 @@ There are a few rules to get your merge request accepted:
ask or assign any [reviewers][projects] for a first review.
1. If you need some guidance (e.g. it's your first merge request), feel free
to ask one of the [Merge request coaches][team].
- 1. The reviewer will assign the merge request to a maintainer once the
- reviewer is satisfied with the state of the merge request.
+ 1. It is recommended that you assign a maintainer that is from a different team than your own.
+ This ensures that all code across GitLab is consistent and can be easily understood by all contributors.
+
1. Keep in mind that maintainers are also going to perform a final code review.
The ideal scenario is that the reviewer has already addressed any concerns
the maintainer would have found, and the maintainer only has to perform the
diff --git a/doc/development/contributing/design.md b/doc/development/contributing/design.md
index 57ae318e821..45fe8c26591 100644
--- a/doc/development/contributing/design.md
+++ b/doc/development/contributing/design.md
@@ -25,10 +25,12 @@ There is a special type label called ~"product discovery". It represents a disco
The initial issue should be about the problem we are solving. If a separate [product discovery issue](#product-discovery-issues) is needed for additional research and design work, it will be created by a PM or UX person. Assign the ~UX, ~"product discovery" and ~"Deliverable" labels, add a milestone and use a title that makes it clear that the scheduled issue is product discovery
(e.g. `Product discovery for XYZ`).
-When the ~"product discovery" issue has been completed, the UXer removes the ~UX
-label, adds the ~"UX ready" label and closes the issue. This indicates the
-UX work for the issue is complete. The UXer will also copy any designs to related
-issues for implementation in an upcoming milestone.
+In order to complete a product discovery issue in a release, you must complete the following:
+
+1. UXer removes the ~UX label, adds the ~"UX ready" label.
+1. Modify the issue description in the product discovery issue to contain the final design. If it makes sense, the original information indicating the need for the design can be moved to a lower "Original Information" section.
+1. Copy the design to the description of the delivery issue for which the product discovery issue was created. Do not simply refer to the product discovery issue as a separate source of truth.
+1. In some cases, a product discovery issue also identifies future enhancements that will not go into the issue that originated the product discovery issue. For these items, create new issues containing the designs to ensure they are not lost. Put the issues in the backlog if they are agreed upon as good ideas. Otherwise leave them for triage.
## Style guides
diff --git a/doc/development/diffs.md b/doc/development/diffs.md
index 2738b1b5635..5e8e8cc7541 100644
--- a/doc/development/diffs.md
+++ b/doc/development/diffs.md
@@ -15,9 +15,9 @@ We're constantly moving Rugged calls to Gitaly and the progress can be followed
When refreshing a Merge Request (pushing to a source branch, force-pushing to target branch, or if the target branch now contains any commits from the MR)
we fetch the comparison information using `Gitlab::Git::Compare`, which fetches `base` and `head` data using Gitaly and diff between them through
-`Gitlab::Git::Diff.between` (which uses _Gitaly_ if it's enabled, otherwise _Rugged_).
+`Gitlab::Git::Diff.between`.
The diffs fetching process _limits_ single file diff sizes and the overall size of the whole diff through a series of constant values. Raw diff files are
-then persisted on `merge_request_diff_files` table.
+then persisted on `merge_request_diff_files` table.
Even though diffs higher than 10kb are collapsed (`Gitlab::Git::Diff::COLLAPSE_LIMIT`), we still keep them on Postgres. However, diff files over _safety limits_
(see the [Diff limits section](#diff-limits)) are _not_ persisted.
@@ -63,34 +63,34 @@ File diffs will be collapsed (but be expandable) if 100 files have already been
```ruby
-Gitlab::Git::DiffCollection.collection_limits[:safe_max_lines] = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines] = 5000
+Gitlab::Git::DiffCollection.collection_limits[:safe_max_lines] = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines] = 5000
```
File diffs will be collapsed (but be expandable) if 5000 lines have already been rendered.
```ruby
-Gitlab::Git::DiffCollection.collection_limits[:safe_max_bytes] = Gitlab::Git::DiffCollection.collection_limits[:safe_max_files] * 5.kilobytes = 500.kilobytes
+Gitlab::Git::DiffCollection.collection_limits[:safe_max_bytes] = Gitlab::Git::DiffCollection.collection_limits[:safe_max_files] * 5.kilobytes = 500.kilobytes
```
File diffs will be collapsed (but be expandable) if 500 kilobytes have already been rendered.
```ruby
-Gitlab::Git::DiffCollection.collection_limits[:max_files] = Commit::DIFF_HARD_LIMIT_FILES = 1000
+Gitlab::Git::DiffCollection.collection_limits[:max_files] = Commit::DIFF_HARD_LIMIT_FILES = 1000
```
No more files will be rendered at all if 1000 files have already been rendered.
```ruby
-Gitlab::Git::DiffCollection.collection_limits[:max_lines] = Commit::DIFF_HARD_LIMIT_LINES = 50000
+Gitlab::Git::DiffCollection.collection_limits[:max_lines] = Commit::DIFF_HARD_LIMIT_LINES = 50000
```
No more files will be rendered at all if 50,000 lines have already been rendered.
```ruby
-Gitlab::Git::DiffCollection.collection_limits[:max_bytes] = Gitlab::Git::DiffCollection.collection_limits[:max_files] * 5.kilobytes = 5000.kilobytes
+Gitlab::Git::DiffCollection.collection_limits[:max_bytes] = Gitlab::Git::DiffCollection.collection_limits[:max_files] * 5.kilobytes = 5000.kilobytes
```
No more files will be rendered at all if 5 megabytes have already been rendered.
@@ -131,7 +131,7 @@ File diff will be suppressed (technically different from collapsed, but behaves
## Viewers
Diff Viewers, which can be found on `models/diff_viewer/*` are classes used to map metadata about each type of Diff File. It has information
-whether it's a binary, which partial should be used to render it or which File extensions this class accounts for.
+whether it's a binary, which partial should be used to render it or which File extensions this class accounts for.
`DiffViewer::Base` validates _blobs_ (old and new versions) content, extension and file type in order to check if it can be rendered.
diff --git a/doc/development/documentation/index.md b/doc/development/documentation/index.md
index f46c171d9f1..7ac211ed550 100644
--- a/doc/development/documentation/index.md
+++ b/doc/development/documentation/index.md
@@ -257,6 +257,15 @@ choices:
If your branch name matches any of the above, it will run only the docs
tests. If it doesn't, the whole test suite will run (including docs).
+## Danger bot
+
+GitLab uses [danger bot](https://github.com/danger/danger) for some elements in
+code review. For docs changes in merge requests, the following actions are taken:
+
+1. Whenever a change under `/doc` is made, the bot leaves a comment for the
+ author to mention `@gl-docsteam`, so that the docs can be properly
+ reviewed.
+
## Merge requests for GitLab documentation
Before getting started, make sure you read the introductory section
diff --git a/doc/development/documentation/styleguide.md b/doc/development/documentation/styleguide.md
index e610c7595a8..8083f219d4a 100644
--- a/doc/development/documentation/styleguide.md
+++ b/doc/development/documentation/styleguide.md
@@ -13,10 +13,10 @@ Check the GitLab handbook for the [writing styles guidelines](https://about.gitl
## Files
- [Directory structure](index.md#location-and-naming-documents): place the docs
-in the correct location
-- [Documentation files](index.md#documentation-files): name the files accordingly
+in the correct location.
+- [Documentation files](index.md#documentation-files): name the files accordingly.
- [Markdown](../../user/markdown.md): use the GitLab Flavored Markdown in the
-documentation
+documentation.
NOTE: **Note:**
**Do not** use capital letters, spaces, or special chars in file names,
@@ -30,17 +30,17 @@ a test that will fail if it spots a new `README.md` file.
- Split up long lines (wrap text), this makes it much easier to review and edit. Only
double line breaks are shown as a full line break in [GitLab markdown][gfm].
- 80-100 characters is a good line length
+ 80-100 characters is a good line length.
- Make sure that the documentation is added in the correct
[directory](index.md#documentation-directory-structure) and that
- there's a link to it somewhere useful
-- Do not duplicate information
-- Be brief and clear
-- Unless there's a logical reason not to, add documents in alphabetical order
-- Write in US English
-- Use [single spaces][] instead of double spaces
+ there's a link to it somewhere useful.
+- Do not duplicate information.
+- Be brief and clear.
+- Unless there's a logical reason not to, add documents in alphabetical order.
+- Write in US English.
+- Use [single spaces][] instead of double spaces.
- Jump a line between different markups (e.g., after every paragraph, header, list, etc)
-- Capitalize "G" and "L" in GitLab
+- Capitalize "G" and "L" in GitLab.
- Use sentence case for titles, headings, labels, menu items, and buttons.
- Use title case when referring to [features](https://about.gitlab.com/features/) or
[products](https://about.gitlab.com/pricing/) (e.g., GitLab Runner, Geo,
@@ -50,10 +50,9 @@ some features are also objects (e.g. "Merge Requests" and "merge requests").
## Formatting
-- Use double asterisks (`**`) to mark a word or text in bold (`**bold**`)
-- Use undescore (`_`) for text in italics (`_italic_`)
-- Jump a line between different markups, for example:
-
+- Use double asterisks (`**`) to mark a word or text in bold (`**bold**`).
+- Use undescore (`_`) for text in italics (`_italic_`).
+- Put an empty line between different markups. For example:
```md
## Header
@@ -69,9 +68,16 @@ For punctuation rules, please refer to the [GitLab UX guide](https://design.gitl
### Ordered and unordered lists
-- Use dashes (`-`) for unordered lists instead of asterisks (`*`)
-- Use the number one (`1`) for ordered lists
-- For punctuation in bullet lists, please refer to the [GitLab UX guide](https://design.gitlab.com/content/punctuation/)
+- Use dashes (`-`) for unordered lists instead of asterisks (`*`).
+- Use the number one (`1`) for ordered lists.
+- Separate list items from explanatory text with a colon (`:`). For example:
+ ```md
+ The list is as follows:
+
+ - First item: This explains the first item.
+ - Second item: This explains the second item.
+ ```
+- For further guidance on punctuation in bullet lists, please refer to the [GitLab UX guide](https://design.gitlab.com/content/punctuation/).
## Headings
@@ -82,7 +88,7 @@ For punctuation rules, please refer to the [GitLab UX guide](https://design.gitl
- Avoid putting numbers in headings. Numbers shift, hence documentation anchor
links shift too, which eventually leads to dead links. If you think it is
compelling to add numbers in headings, make sure to at least discuss it with
- someone in the Merge Request
+ someone in the Merge Request.
- [Avoid using symbols and special chars](https://gitlab.com/gitlab-com/gitlab-docs/issues/84)
in headers. Whenever possible, they should be plain and short text.
- Avoid adding things that show ephemeral statuses. For example, if a feature is
@@ -92,8 +98,8 @@ For punctuation rules, please refer to the [GitLab UX guide](https://design.gitl
of the following GitLab members for a review: `@axil` or `@marcia`.
This is to ensure that no document with wrong heading is going
live without an audit, thus preventing dead links and redirection issues when
- corrected
-- Leave exactly one new line after a heading
+ corrected.
+- Leave exactly one new line after a heading.
## Links
@@ -120,11 +126,11 @@ For punctuation rules, please refer to the [GitLab UX guide](https://design.gitl
To indicate the steps of navigation through the UI:
-- Use the exact word as shown in the UI, including any capital letters as-is
+- Use the exact word as shown in the UI, including any capital letters as-is.
- Use bold text for navigation items and the char `>` as separator
-(e.g., `Navigate to your project's **Settings > CI/CD**` )
+(e.g., `Navigate to your project's **Settings > CI/CD**` ).
- If there are any expandable menus, make sure to mention that the user
-needs to expand the tab to find the settings you're referring to
+needs to expand the tab to find the settings you're referring to.
## Images
@@ -149,12 +155,12 @@ Inside the document:
`![Proper description what the image is about](img/document_image_title.png)`
- Always use a proper description for what the image is about. That way, when a
browser fails to show the image, this text will be used as an alternative
- description
+ description.
- If there are consecutive images with little text between them, always add
three dashes (`---`) between the image and the text to create a horizontal
- line for better clarity
+ line for better clarity.
- If a heading is placed right after an image, always add three dashes (`---`)
- between the image and the heading
+ between the image and the heading.
## Alert boxes
@@ -262,18 +268,18 @@ below.
When a feature is available in EE-only tiers, add the corresponding tier according to the
feature availability:
-- For GitLab Starter and GitLab.com Bronze: `**[STARTER]**`
-- For GitLab Premium and GitLab.com Silver: `**[PREMIUM]**`
-- For GitLab Ultimate and GitLab.com Gold: `**[ULTIMATE]**`
-- For GitLab Core and GitLab.com Free: `**[CORE]**`
+- For GitLab Starter and GitLab.com Bronze: `**[STARTER]**`.
+- For GitLab Premium and GitLab.com Silver: `**[PREMIUM]**`.
+- For GitLab Ultimate and GitLab.com Gold: `**[ULTIMATE]**`.
+- For GitLab Core and GitLab.com Free: `**[CORE]**`.
To exclude GitLab.com tiers (when the feature is not available in GitLab.com), add the
keyword "only":
-- For GitLab Starter: `**[STARTER ONLY]**`
-- For GitLab Premium: `**[PREMIUM ONLY]**`
-- For GitLab Ultimate: `**[ULTIMATE ONLY]**`
-- For GitLab Core: `**[CORE ONLY]**`
+- For GitLab Starter: `**[STARTER ONLY]**`.
+- For GitLab Premium: `**[PREMIUM ONLY]**`.
+- For GitLab Ultimate: `**[ULTIMATE ONLY]**`.
+- For GitLab Core: `**[CORE ONLY]**`.
The tier should be ideally added to headers, so that the full badge will be displayed.
But it can be also mentioned from paragraphs, list items, and table cells. For these cases,
@@ -328,8 +334,8 @@ prefer to document it in the CE docs to avoid duplication.
Configuration settings include:
-- settings that touch configuration files in `config/`
-- NGINX settings and settings in `lib/support/` in general
+- Settings that touch configuration files in `config/`.
+- NGINX settings and settings in `lib/support/` in general.
When there is a list of steps to perform, usually that entails editing the
configuration file and reconfiguring/restarting GitLab. In such case, follow
diff --git a/doc/development/performance.md b/doc/development/performance.md
index 6b4cb6d72d1..05caffb150a 100644
--- a/doc/development/performance.md
+++ b/doc/development/performance.md
@@ -43,7 +43,7 @@ GitLab provides built-in tools to aid the process of improving performance:
* [QueryRecoder](query_recorder.md) for preventing `N+1` regressions
GitLab employees can use GitLab.com's performance monitoring systems located at
-<http://performance.gitlab.net>, this requires you to log in using your
+<https://dashboards.gitlab.net>, this requires you to log in using your
`@gitlab.com` Email address. Non-GitLab employees are advised to set up their
own InfluxDB + Grafana stack.
diff --git a/doc/development/permissions.md b/doc/development/permissions.md
new file mode 100644
index 00000000000..5d409c9461e
--- /dev/null
+++ b/doc/development/permissions.md
@@ -0,0 +1,63 @@
+# GitLab permissions guide
+
+There are multiple types of permissions across GitLab, and when implementing
+anything that deals with permissions, all of them should be considered.
+
+## Groups and Projects
+
+### General permissions
+
+Groups and projects can have the following visibility levels:
+
+- public (20) - an entity is visible to everyone
+- internal (10) - an entity is visible to logged in users
+- private (0) - an entity is visible only to the approved members of the entity
+
+The visibility level of a group can be changed only if all subgroups and
+subprojects have the same or lower visibility level. (e.g., a group can be set
+to internal only if all subgroups and projects are internal or private).
+
+Visibility levels can be found in the `Gitlab::VisibilityLevel` module.
+
+### Feature specific permissions
+
+Additionally, the following project features can have different visibility levels:
+
+- Issues
+- Repository
+ - Merge Request
+ - Pipelines
+ - Container Registry
+ - Git Large File Storage
+- Wiki
+- Snippets
+
+These features can be set to "Everyone with Access" or "Only Project Members".
+They make sense only for public or internal projects because private projects
+can be accessed only by project members by default.
+
+### Members
+
+Users can be members of multiple groups and projects. The following access
+levels are available (defined in the `Gitlab::Access` module):
+
+- Guest
+- Reporter
+- Developer
+- Maintainer
+- Owner
+
+If a user is the member of both a project and the project parent group, the
+higher permission is taken into account for the project.
+
+If a user is the member of a project, but not the parent group (or groups), they
+can still view the groups and their entities (like epics).
+
+Project membership (where the group membership is already taken into account)
+is stored in the `project_authorizations` table.
+
+### Confidential issues
+
+Confidential issues can be accessed only by project members who are at least
+reporters (they can't be accessed by guests). Additionally they can be accessed
+by their authors and assignees.
diff --git a/doc/development/prometheus_metrics.md b/doc/development/prometheus_metrics.md
new file mode 100644
index 00000000000..b6b6d9665ea
--- /dev/null
+++ b/doc/development/prometheus_metrics.md
@@ -0,0 +1,48 @@
+# Working with Prometheus Metrics
+
+## Adding to the library
+
+We strive to support the 2-4 most important metrics for each common system service that supports Prometheus. If you are looking for support for a particular exporter which has not yet been added to the library, additions can be made [to the `common_metrics.yml`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/prometheus/common_metrics.yml) file.
+
+### Query identifier
+
+The requirement for adding a new metric is to make each query to have an unique identifier which is used to update the metric later when changed:
+
+```yaml
+- group: Response metrics (NGINX Ingress)
+ metrics:
+ - title: "Throughput"
+ y_label: "Requests / Sec"
+ queries:
+ - id: response_metrics_nginx_ingress_throughput_status_code
+ query_range: 'sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) by (status_code)'
+ unit: req / sec
+ label: Status Code
+```
+
+### Update existing metrics
+
+After you add or change existing _common_ metric you have to create a new database migration that will query and update all existing metrics.
+
+NOTE: **Note:**
+If a query metric (which is identified by `id:`) is removed it will not be removed from database by default.
+You might want to add additional database migration that makes a decision what to do with removed one.
+For example: you might be interested in migrating all dependent data to a different metric.
+
+```ruby
+class ImportCommonMetrics < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ require Rails.root.join('db/importers/common_metrics_importer.rb')
+
+ DOWNTIME = false
+
+ def up
+ Importers::CommonMetricsImporter.new.execute
+ end
+
+ def down
+ # no-op
+ end
+end
+```
diff --git a/doc/install/kubernetes/index.md b/doc/install/kubernetes/index.md
index e67d5ba4d4c..df74d2aeab3 100644
--- a/doc/install/kubernetes/index.md
+++ b/doc/install/kubernetes/index.md
@@ -4,23 +4,12 @@ description: 'Read through the different methods to deploy GitLab on Kubernetes.
# Installing GitLab on Kubernetes
-NOTE: **Note**: These charts have been tested on Google Kubernetes Engine. Other
-Kubernetes installations may work as well, if not please [open an issue](https://gitlab.com/charts/issues).
-
The easiest method to deploy GitLab on [Kubernetes](https://kubernetes.io/) is
to take advantage of GitLab's Helm charts. [Helm] is a package
management tool for Kubernetes, allowing apps to be easily managed via their
Charts. A [Chart] is a detailed description of the application including how it
should be deployed, upgraded, and configured.
-## Chart Overview
-
-- **[GitLab Chart](gitlab_chart.html)**: Deploys GitLab on Kubernetes. Includes all the required components to get started, and can scale to large deployments.
-- **[GitLab Runner Chart](gitlab_runner_chart.md)**: For deploying just the GitLab Runner.
-- Other Charts
- - [GitLab-Omnibus](gitlab_omnibus.md): Chart based on the Omnibus GitLab package, only suitable for small deployments. Deprecated, we strongly recommend using the [gitlab](#gitlab-chart) chart.
- - [Community contributed charts](#community-contributed-charts): Community contributed charts.
-
## GitLab Chart
This chart contains all the required components to get started, and can scale to
@@ -43,13 +32,13 @@ it can be deployed with the GitLab Runner chart.
Learn more about [gitlab-runner chart](gitlab_runner_chart.md).
-## Other Charts
-
-### GitLab-Omnibus Chart
+## Deprecated Charts
CAUTION: **Deprecated:**
-This chart is **deprecated**. We recommend using the [GitLab Chart](gitlab_chart.md)
-instead. A comparison of the two charts is available in [this video](https://youtu.be/Z6jWR8Z8dv8).
+These charts are **deprecated**. We recommend using the [GitLab Chart](gitlab_chart.md)
+instead.
+
+### GitLab-Omnibus Chart
This chart is based on the [GitLab Omnibus Docker images](https://docs.gitlab.com/omnibus/docker/).
It deploys and configures nearly all features of GitLab, including:
@@ -64,7 +53,7 @@ Learn more about the [gitlab-omnibus chart](gitlab_omnibus.md).
### Community Contributed Charts
-The community has also contributed GitLab [CE](https://github.com/kubernetes/charts/tree/master/stable/gitlab-ce) and [EE](https://github.com/kubernetes/charts/tree/master/stable/gitlab-ee) charts to the [Helm Stable Repository](https://github.com/kubernetes/charts#repository-structure). These charts should be considered [deprecated](https://github.com/kubernetes/charts/issues/1138) in favor of the [official Charts](gitlab_omnibus.md).
+The community has also contributed GitLab [CE](https://github.com/kubernetes/charts/tree/master/stable/gitlab-ce) and [EE](https://github.com/kubernetes/charts/tree/master/stable/gitlab-ee) charts to the [Helm Stable Repository](https://github.com/kubernetes/charts#repository-structure). These charts are [deprecated](https://github.com/kubernetes/charts/issues/1138) in favor of the [official Chart](gitlab_chart.md).
[chart]: https://github.com/kubernetes/charts
[helm]: https://github.com/kubernetes/helm/blob/master/README.md
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index c2cf0d54aeb..1d29f6d4e43 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -16,17 +16,27 @@ and is flexible enough to fit your needs.
### Requirements
-If you're using GitLab with the Omnibus package, you're all set. If you
-installed GitLab from source, make sure the following packages are installed:
-
* rsync
+If you're using GitLab with the Omnibus package, you're all set. If you
+installed GitLab from source, make sure you have rsync installed.
+
If you're using Ubuntu, you could run:
```
sudo apt-get install -y rsync
```
+* tar
+
+Backup and restore tasks use `tar` under the hood to create and extract
+archives. Ensure you have version 1.30 or above of `tar` available in your
+system. To check the version, run:
+
+```
+tar --version
+```
+
### Backup timestamp
>**Note:**
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index c0268ce136c..e778f1d83df 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -238,6 +238,12 @@ There is also a feature flag to enable Auto DevOps to a percentage of projects
which can be enabled from the console with
`Feature.get(:force_autodevops_on_by_default).enable_percentage_of_actors(10)`.
+NOTE: **Enabled by default:**
+Starting with GitLab 11.3, the Auto DevOps pipeline will be enabled by default for all
+projects. If it's not explicitly enabled for the project, Auto DevOps will be automatically
+disabled on the first pipeline failure. Your project will continue to use an alternative
+[CI/CD configuration file](../../ci/yaml/README.md) if one is found.
+
### Deployment strategy
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/38542) in GitLab 11.0.
diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md
index de5d7d0a3a0..0b9395914f9 100644
--- a/doc/user/gitlab_com/index.md
+++ b/doc/user/gitlab_com/index.md
@@ -99,7 +99,7 @@ Below are the shared Runners settings.
| Default Docker image | `ruby:2.5` | - |
| `privileged` (run [Docker in Docker]) | `true` | `false` |
-[ci_version_dashboard]: https://monitor.gitlab.net/dashboard/db/ci?from=now-1h&to=now&refresh=5m&orgId=1&panelId=12&fullscreen&theme=light
+[ci_version_dashboard]: https://dashboards.gitlab.com/dashboard/db/ci?from=now-1h&to=now&refresh=5m&orgId=1&panelId=12&fullscreen&theme=light
### `config.toml`
diff --git a/doc/user/profile/index.md b/doc/user/profile/index.md
index b1b822f25bd..6b225147232 100644
--- a/doc/user/profile/index.md
+++ b/doc/user/profile/index.md
@@ -91,6 +91,18 @@ To enable private profile:
NOTE: **Note:**
You and GitLab admins can see your the abovementioned information on your profile even if it is private.
+## Private contributions
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/14078) in GitLab 11.3.
+
+Enabling private contributions will include contributions to private projects, in the user contribution calendar graph and user recent activity.
+
+To enable private contributions:
+
+1. Navigate to your personal [profile settings](#profile-settings).
+2. Check the "Private contributions" option.
+3. Hit **Update profile settings**.
+
## Current status
> Introduced in GitLab 11.2.
diff --git a/doc/user/project/integrations/prometheus_library/metrics.md b/doc/user/project/integrations/prometheus_library/metrics.md
index 96a22316265..ec16902fcc8 100644
--- a/doc/user/project/integrations/prometheus_library/metrics.md
+++ b/doc/user/project/integrations/prometheus_library/metrics.md
@@ -17,9 +17,3 @@ GitLab retrieves performance data from the configured Prometheus server, and att
In order to isolate and only display relevant metrics for a given environment, GitLab needs a method to detect which labels are associated. To do that,
GitLab uses the defined queries and fills in the environment specific variables. Typically this involves looking for the [$CI_ENVIRONMENT_SLUG](../../../../ci/variables/README.md#predefined-variables-environment-variables), but may also include other information such as the project's Kubernetes namespace. Each search query is defined in the [exporter specific documentation](#prometheus-metrics-library).
-
-## Adding to the library
-
-We strive to support the 2-4 most important metrics for each common system service that supports Prometheus. If you are looking for support for a particular exporter which has not yet been added to the library, additions can be made [to the `additional_metrics.yml`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/prometheus/additional_metrics.yml) file.
-
-> Note: The library is only for monitoring public, common, system services which all customers can benefit from. Support for monitoring [customer proprietary metrics](https://gitlab.com/gitlab-org/gitlab-ee/issues/2273) will be added in a subsequent release.
diff --git a/doc/user/project/pipelines/settings.md b/doc/user/project/pipelines/settings.md
index 14f2e522f01..15eacc48dfe 100644
--- a/doc/user/project/pipelines/settings.md
+++ b/doc/user/project/pipelines/settings.md
@@ -157,6 +157,10 @@ into your `README.md`:
![coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)
```
+### Environment Variables
+
+[Environment variables](../../../ci/variables/README.html#variables) can be set in an environment to be available to a runner.
+
[var]: ../../../ci/yaml/README.md#git-strategy
[coverage report]: #test-coverage-parsing
[timeout overriding]: ../../../ci/runners/README.html#setting-maximum-job-timeout-for-a-runner
diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md
index 8fdfd2a6f4d..9ad9155258f 100644
--- a/doc/user/project/quick_actions.md
+++ b/doc/user/project/quick_actions.md
@@ -44,3 +44,5 @@ do.
| `/shrug` | Append the comment with `¯\_(ツ)_/¯` |
| <code>/copy_metadata #issue &#124; !merge_request</code> | Copy labels and milestone from other issue or merge request |
| `/confidential` | Makes the issue confidential |
+| `/lock` | Lock the discussion |
+| `/unlock` | Unlock the discussion |
diff --git a/doc/user/project/web_ide/index.md b/doc/user/project/web_ide/index.md
index 16969b2c527..9429b1268f0 100644
--- a/doc/user/project/web_ide/index.md
+++ b/doc/user/project/web_ide/index.md
@@ -78,13 +78,14 @@ switching to a different branch.
The Web IDE can be used to preview JavaScript projects right in the browser.
This feature uses CodeSandbox to compile and bundle the JavaScript used to
-preview the web application. On public projects, an `Open in CodeSandbox`
-button is visible which will transfer the contents of the project into a
-CodeSandbox project to share with others.
-**Note** this button is not visible on private or internal projects.
+preview the web application.
![Web IDE Client Side Evaluation](img/clientside_evaluation.png)
+Additionally, for public projects an `Open in CodeSandbox` button is available
+to transfer the contents of the project into a public CodeSandbox project to
+quickly share your project with others.
+
### Enabling Client Side Evaluation
The Client Side Evaluation feature needs to be enabled in the GitLab instances
diff --git a/doc/workflow/todos.md b/doc/workflow/todos.md
index dda82352c67..f94d592d0db 100644
--- a/doc/workflow/todos.md
+++ b/doc/workflow/todos.md
@@ -14,7 +14,7 @@ in a simple dashboard.
---
-You can quickly access the Todos dashboard using the bell icon next to the
+You can quickly access the Todos dashboard using the checkmark icon next to the
search bar in the upper right corner. The number in blue is the number of Todos
you still have open if the count is < 100, else it's 99+. The exact number
will still be shown in the body of the _To do_ tab.
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 843f75d3096..e89d9337853 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -118,6 +118,7 @@ module API
mount ::API::Namespaces
mount ::API::Notes
mount ::API::Discussions
+ mount ::API::ResourceLabelEvents
mount ::API::NotificationSettings
mount ::API::PagesDomains
mount ::API::Pipelines
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index 3e445e6b1fa..ac33c2924ca 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -9,14 +9,6 @@ module API
before { authorize! :download_code, user_project }
helpers do
- def find_branch!(branch_name)
- begin
- user_project.repository.find_branch(branch_name) || not_found!('Branch')
- rescue Gitlab::Git::CommandError
- render_api_error!('The branch refname is invalid', 400)
- end
- end
-
params :filter_params do
optional :search, type: String, desc: 'Return list of branches matching the search criteria'
optional :sort, type: String, desc: 'Return list of branches sorted by the given field'
@@ -77,7 +69,7 @@ module API
success Entities::Branch
end
params do
- requires :branch, type: String, desc: 'The name of the branch'
+ requires :branch, type: String, desc: 'The name of the branch', allow_blank: false
optional :developers_can_push, type: Boolean, desc: 'Flag if developers can push to that branch'
optional :developers_can_merge, type: Boolean, desc: 'Flag if developers can merge to that branch'
end
@@ -114,7 +106,7 @@ module API
success Entities::Branch
end
params do
- requires :branch, type: String, desc: 'The name of the branch'
+ requires :branch, type: String, desc: 'The name of the branch', allow_blank: false
end
put ':id/repository/branches/:branch/unprotect', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
authorize_admin_project
@@ -130,8 +122,8 @@ module API
success Entities::Branch
end
params do
- requires :branch, type: String, desc: 'The name of the branch'
- requires :ref, type: String, desc: 'Create branch from commit sha or existing branch'
+ requires :branch, type: String, desc: 'The name of the branch', allow_blank: false
+ requires :ref, type: String, desc: 'Create branch from commit sha or existing branch', allow_blank: false
end
post ':id/repository/branches' do
authorize_push_project
@@ -151,7 +143,7 @@ module API
desc 'Delete a branch'
params do
- requires :branch, type: String, desc: 'The name of the branch'
+ requires :branch, type: String, desc: 'The name of the branch', allow_blank: false
end
delete ':id/repository/branches/:branch', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
authorize_push_project
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 92329465b2c..f86ac60b54b 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -71,7 +71,7 @@ module API
detail 'This feature was introduced in GitLab 8.13'
end
params do
- requires :branch, type: String, desc: 'Name of the branch to commit into. To create a new branch, also provide `start_branch`.'
+ requires :branch, type: String, desc: 'Name of the branch to commit into. To create a new branch, also provide `start_branch`.', allow_blank: false
requires :commit_message, type: String, desc: 'Commit message'
requires :actions, type: Array[Hash], desc: 'Actions to perform in commit'
optional :start_branch, type: String, desc: 'Name of the branch to start the new commit from'
@@ -151,7 +151,7 @@ module API
end
params do
requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag to be cherry picked'
- requires :branch, type: String, desc: 'The name of the branch'
+ requires :branch, type: String, desc: 'The name of the branch', allow_blank: false
end
post ':id/repository/commits/:sha/cherry_pick', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do
authorize_push_to_branch!(params[:branch])
@@ -159,8 +159,7 @@ module API
commit = user_project.commit(params[:sha])
not_found!('Commit') unless commit
- branch = user_project.repository.find_branch(params[:branch])
- not_found!('Branch') unless branch
+ find_branch!(params[:branch])
commit_params = {
commit: commit,
@@ -171,7 +170,7 @@ module API
result = ::Commits::CherryPickService.new(user_project, current_user, commit_params).execute
if result[:status] == :success
- branch = user_project.repository.find_branch(params[:branch])
+ branch = find_branch!(params[:branch])
present user_project.repository.commit(branch.dereferenced_target), with: Entities::Commit
else
render_api_error!(result[:message], 400)
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 90abee94f6a..f0eafbaeb94 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -1437,5 +1437,19 @@ module API
badge.type == 'ProjectBadge' ? 'project' : 'group'
end
end
+
+ class ResourceLabelEvent < Grape::Entity
+ expose :id
+ expose :user, using: Entities::UserBasic
+ expose :created_at
+ expose :resource_type do |event, options|
+ event.issuable.class.name
+ end
+ expose :resource_id do |event, options|
+ event.issuable.id
+ end
+ expose :label, using: Entities::LabelBasic
+ expose :action
+ end
end
end
diff --git a/lib/api/files.rb b/lib/api/files.rb
index ff4f75c12df..ac02488d30c 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -58,7 +58,7 @@ module API
params :simple_file_params do
requires :file_path, type: String, desc: 'The url encoded path to the file. Ex. lib%2Fclass%2Erb'
- requires :branch, type: String, desc: 'Name of the branch to commit into. To create a new branch, also provide `start_branch`.'
+ requires :branch, type: String, desc: 'Name of the branch to commit into. To create a new branch, also provide `start_branch`.', allow_blank: false
requires :commit_message, type: String, allow_blank: false, desc: 'Commit message'
optional :start_branch, type: String, desc: 'Name of the branch to start the new commit from'
optional :author_email, type: String, desc: 'The email of the author'
@@ -80,7 +80,7 @@ module API
desc 'Get raw file metadata from repository'
params do
requires :file_path, type: String, desc: 'The url encoded path to the file. Ex. lib%2Fclass%2Erb'
- requires :ref, type: String, desc: 'The name of branch, tag or commit'
+ requires :ref, type: String, desc: 'The name of branch, tag or commit', allow_blank: false
end
head ":id/repository/files/:file_path/raw", requirements: FILE_ENDPOINT_REQUIREMENTS do
assign_file_vars!
@@ -91,7 +91,7 @@ module API
desc 'Get raw file contents from the repository'
params do
requires :file_path, type: String, desc: 'The url encoded path to the file. Ex. lib%2Fclass%2Erb'
- requires :ref, type: String, desc: 'The name of branch, tag commit'
+ requires :ref, type: String, desc: 'The name of branch, tag commit', allow_blank: false
end
get ":id/repository/files/:file_path/raw", requirements: FILE_ENDPOINT_REQUIREMENTS do
assign_file_vars!
@@ -104,7 +104,7 @@ module API
desc 'Get file metadata from repository'
params do
requires :file_path, type: String, desc: 'The url encoded path to the file. Ex. lib%2Fclass%2Erb'
- requires :ref, type: String, desc: 'The name of branch, tag or commit'
+ requires :ref, type: String, desc: 'The name of branch, tag or commit', allow_blank: false
end
head ":id/repository/files/:file_path", requirements: FILE_ENDPOINT_REQUIREMENTS do
assign_file_vars!
@@ -115,7 +115,7 @@ module API
desc 'Get a file from the repository'
params do
requires :file_path, type: String, desc: 'The url encoded path to the file. Ex. lib%2Fclass%2Erb'
- requires :ref, type: String, desc: 'The name of branch, tag or commit'
+ requires :ref, type: String, desc: 'The name of branch, tag or commit', allow_blank: false
end
get ":id/repository/files/:file_path", requirements: FILE_ENDPOINT_REQUIREMENTS do
assign_file_vars!
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index be17653dbb2..c7ecddeccf0 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -103,10 +103,12 @@ module API
end
def find_project(id)
+ projects = Project.without_deleted
+
if id.is_a?(Integer) || id =~ /^\d+$/
- Project.find_by(id: id)
+ projects.find_by(id: id)
elsif id.include?("/")
- Project.find_by_full_path(id)
+ projects.find_by_full_path(id)
end
end
@@ -156,6 +158,12 @@ module API
end
end
+ def find_branch!(branch_name)
+ user_project.repository.find_branch(branch_name) || not_found!('Branch')
+ rescue Gitlab::Git::CommandError
+ render_api_error!('The branch refname is invalid', 400)
+ end
+
def find_project_label(id)
labels = available_labels_for(user_project)
label = labels.find_by_id(id) || labels.find_by_title(id)
@@ -380,7 +388,7 @@ module API
end
def project_finder_params
- finder_params = {}
+ finder_params = { without_deleted: true }
finder_params[:owned] = true if params[:owned].present?
finder_params[:non_public] = true if params[:membership].present?
finder_params[:starred] = true if params[:starred].present?
diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb
index 381d5e8968c..98672f2f765 100644
--- a/lib/api/helpers/projects_helpers.rb
+++ b/lib/api/helpers/projects_helpers.rb
@@ -26,6 +26,7 @@ module API
optional :avatar, type: File, desc: 'Avatar image for project'
optional :printing_merge_request_link_enabled, type: Boolean, desc: 'Show link to create/view merge request when pushing from the command line'
optional :merge_method, type: String, values: %w(ff rebase_merge merge), desc: 'The merge method used when merging merge requests'
+ optional :initialize_with_readme, type: Boolean, desc: "Initialize a project with a README.md"
end
params :optional_project_params do
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index 0990e2a1fba..e3e8cb71c09 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -74,6 +74,7 @@ module API
gl_repository: gl_repository,
gl_id: Gitlab::GlId.gl_id(user),
gl_username: user&.username,
+ git_config_options: [],
# This repository_path is a bogus value but gitlab-shell still requires
# its presence. https://gitlab.com/gitlab-org/gitlab-shell/issues/135
@@ -81,6 +82,13 @@ module API
gitaly: gitaly_payload(params[:action])
}
+
+ # Custom option for git-receive-pack command
+ receive_max_input_size = Gitlab::CurrentSettings.receive_max_input_size.to_i
+ if receive_max_input_size > 0
+ payload[:git_config_options] << "receive.maxInputSize=#{receive_max_input_size.megabytes}"
+ end
+
response_with_status(**payload)
when ::Gitlab::GitAccessResult::CustomAction
response_with_status(code: 300, message: check_result.message, payload: check_result.payload)
diff --git a/lib/api/pipeline_schedules.rb b/lib/api/pipeline_schedules.rb
index 37f32411296..ae4a7654ec1 100644
--- a/lib/api/pipeline_schedules.rb
+++ b/lib/api/pipeline_schedules.rb
@@ -39,7 +39,7 @@ module API
end
params do
requires :description, type: String, desc: 'The description of pipeline schedule'
- requires :ref, type: String, desc: 'The branch/tag name will be triggered'
+ requires :ref, type: String, desc: 'The branch/tag name will be triggered', allow_blank: false
requires :cron, type: String, desc: 'The cron'
optional :cron_timezone, type: String, default: 'UTC', desc: 'The timezone'
optional :active, type: Boolean, default: true, desc: 'The activation of pipeline schedule'
diff --git a/lib/api/project_export.rb b/lib/api/project_export.rb
index 15c57a2fc02..8562ae6d737 100644
--- a/lib/api/project_export.rb
+++ b/lib/api/project_export.rb
@@ -21,12 +21,8 @@ module API
detail 'This feature was introduced in GitLab 10.6.'
end
get ':id/export/download' do
- path = user_project.export_project_path
-
- if path
- present_disk_file!(path, File.basename(path), 'application/gzip')
- elsif user_project.export_project_object_exists?
- present_carrierwave_file!(user_project.import_export_upload.export_file)
+ if user_project.export_file_exists?
+ present_carrierwave_file!(user_project.export_file)
else
render_api_error!('404 Not found or has expired', 404)
end
diff --git a/lib/api/resource_label_events.rb b/lib/api/resource_label_events.rb
new file mode 100644
index 00000000000..5ac3adeb990
--- /dev/null
+++ b/lib/api/resource_label_events.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+module API
+ class ResourceLabelEvents < Grape::API
+ include PaginationParams
+ helpers ::API::Helpers::NotesHelpers
+
+ before { authenticate! }
+
+ EVENTABLE_TYPES = [Issue, MergeRequest].freeze
+
+ EVENTABLE_TYPES.each do |eventable_type|
+ parent_type = eventable_type.parent_class.to_s.underscore
+ eventables_str = eventable_type.to_s.underscore.pluralize
+
+ params do
+ requires :id, type: String, desc: "The ID of a #{parent_type}"
+ end
+ resource parent_type.pluralize.to_sym, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
+ desc "Get a list of #{eventable_type.to_s.downcase} resource label events" do
+ success Entities::ResourceLabelEvent
+ detail 'This feature was introduced in 11.3'
+ end
+ params do
+ requires :eventable_id, types: [Integer, String], desc: 'The ID of the eventable'
+ use :pagination
+ end
+ get ":id/#{eventables_str}/:eventable_id/resource_label_events" do
+ eventable = find_noteable(parent_type, eventables_str, params[:eventable_id])
+ events = eventable.resource_label_events.includes(:label, :user)
+
+ present paginate(events), with: Entities::ResourceLabelEvent
+ end
+
+ desc "Get a single #{eventable_type.to_s.downcase} resource label event" do
+ success Entities::ResourceLabelEvent
+ detail 'This feature was introduced in 11.3'
+ end
+ params do
+ requires :event_id, type: String, desc: 'The ID of a resource label event'
+ requires :eventable_id, types: [Integer, String], desc: 'The ID of the eventable'
+ end
+ get ":id/#{eventables_str}/:eventable_id/resource_label_events/:event_id" do
+ eventable = find_noteable(parent_type, eventables_str, params[:eventable_id])
+ event = eventable.resource_label_events.find(params[:event_id])
+
+ present event, with: Entities::ResourceLabelEvent
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb
index b29e660c6e0..be95ef9e928 100644
--- a/lib/api/triggers.rb
+++ b/lib/api/triggers.rb
@@ -10,7 +10,7 @@ module API
success Entities::Pipeline
end
params do
- requires :ref, type: String, desc: 'The commit sha or name of a branch or tag'
+ requires :ref, type: String, desc: 'The commit sha or name of a branch or tag', allow_blank: false
requires :token, type: String, desc: 'The unique token of trigger'
optional :variables, type: Hash, desc: 'The list of variables to be injected into build'
end
diff --git a/lib/api/users.rb b/lib/api/users.rb
index b0811bb4aad..a4ae597e252 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -361,6 +361,7 @@ module API
params do
requires :id, type: Integer, desc: 'The ID of the user'
requires :email, type: String, desc: 'The email of the user'
+ optional :skip_confirmation, type: Boolean, desc: 'Skip confirmation of email and assume it is verified'
end
post ":id/emails" do
authenticated_as_admin!
diff --git a/lib/banzai/filter/spaced_link_filter.rb b/lib/banzai/filter/spaced_link_filter.rb
index 574a8a6c7a5..a27f1d46863 100644
--- a/lib/banzai/filter/spaced_link_filter.rb
+++ b/lib/banzai/filter/spaced_link_filter.rb
@@ -8,22 +8,31 @@ module Banzai
#
# Based on Banzai::Filter::AutolinkFilter
#
- # CommonMark does not allow spaces in the url portion of a link.
- # For example, `[example](page slug)` is not valid. However,
+ # CommonMark does not allow spaces in the url portion of a link/url.
+ # For example, `[example](page slug)` is not valid.
+ # Neither is `![example](test image.jpg)`. However, particularly
# in our wikis, we support (via RedCarpet) this type of link, allowing
# wiki pages to be easily linked by their title. This filter adds that functionality.
- # The intent is for this to only be used in Wikis - in general, we want
- # to adhere to CommonMark's spec.
+ #
+ # This is a small extension to the CommonMark spec. If they start allowing
+ # spaces in urls, we could then remove this filter.
#
class SpacedLinkFilter < HTML::Pipeline::Filter
include ActionView::Helpers::TagHelper
# Pattern to match a standard markdown link
#
- # Rubular: http://rubular.com/r/z9EAHxYmKI
- LINK_PATTERN = /\[([^\]]+)\]\(([^)"]+)(?: \"([^\"]+)\")?\)/
-
- # Text matching LINK_PATTERN inside these elements will not be linked
+ # Rubular: http://rubular.com/r/2EXEQ49rg5
+ LINK_OR_IMAGE_PATTERN = %r{
+ (?<preview_operator>!)?
+ \[(?<text>.+?)\]
+ \(
+ (?<new_link>.+?)
+ (?<title>\ ".+?")?
+ \)
+ }x
+
+ # Text matching LINK_OR_IMAGE_PATTERN inside these elements will not be linked
IGNORE_PARENTS = %w(a code kbd pre script style).to_set
# The XPath query to use for finding text nodes to parse.
@@ -38,7 +47,7 @@ module Banzai
doc.xpath(TEXT_QUERY).each do |node|
content = node.to_html
- next unless content.match(LINK_PATTERN)
+ next unless content.match(LINK_OR_IMAGE_PATTERN)
html = spaced_link_filter(content)
@@ -53,25 +62,37 @@ module Banzai
private
def spaced_link_match(link)
- match = LINK_PATTERN.match(link)
- return link unless match && match[1] && match[2]
+ match = LINK_OR_IMAGE_PATTERN.match(link)
+ return link unless match
# escape the spaces in the url so that it's a valid markdown link,
# then run it through the markdown processor again, let it do its magic
- text = match[1]
- new_link = match[2].gsub(' ', '%20')
- title = match[3] ? " \"#{match[3]}\"" : ''
- html = Banzai::Filter::MarkdownFilter.call("[#{text}](#{new_link}#{title})", context)
+ html = Banzai::Filter::MarkdownFilter.call(transform_markdown(match), context)
# link is wrapped in a <p>, so strip that off
html.sub('<p>', '').chomp('</p>')
end
def spaced_link_filter(text)
- Gitlab::StringRegexMarker.new(CGI.unescapeHTML(text), text.html_safe).mark(LINK_PATTERN) do |link, left:, right:|
+ Gitlab::StringRegexMarker.new(CGI.unescapeHTML(text), text.html_safe).mark(LINK_OR_IMAGE_PATTERN) do |link, left:, right:|
spaced_link_match(link)
end
end
+
+ def transform_markdown(match)
+ preview_operator, text, new_link, title = process_match(match)
+
+ "#{preview_operator}[#{text}](#{new_link}#{title})"
+ end
+
+ def process_match(match)
+ [
+ match[:preview_operator],
+ match[:text],
+ match[:new_link].gsub(' ', '%20'),
+ match[:title]
+ ]
+ end
end
end
end
diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb
index e9be05e174e..bd34614f149 100644
--- a/lib/banzai/pipeline/gfm_pipeline.rb
+++ b/lib/banzai/pipeline/gfm_pipeline.rb
@@ -16,6 +16,7 @@ module Banzai
Filter::MathFilter,
Filter::ColorFilter,
Filter::MermaidFilter,
+ Filter::SpacedLinkFilter,
Filter::VideoLinkFilter,
Filter::ImageLazyLoadFilter,
Filter::ImageLinkFilter,
diff --git a/lib/banzai/pipeline/label_pipeline.rb b/lib/banzai/pipeline/label_pipeline.rb
new file mode 100644
index 00000000000..725cccc4b2b
--- /dev/null
+++ b/lib/banzai/pipeline/label_pipeline.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Banzai
+ module Pipeline
+ class LabelPipeline < BasePipeline
+ def self.filters
+ @filters ||= FilterArray[
+ Filter::SanitizationFilter,
+ Filter::LabelReferenceFilter
+ ]
+ end
+ end
+ end
+end
diff --git a/lib/banzai/pipeline/wiki_pipeline.rb b/lib/banzai/pipeline/wiki_pipeline.rb
index 737ff0cc818..c37b8e71cb0 100644
--- a/lib/banzai/pipeline/wiki_pipeline.rb
+++ b/lib/banzai/pipeline/wiki_pipeline.rb
@@ -5,7 +5,6 @@ module Banzai
@filters ||= begin
super.insert_after(Filter::TableOfContentsFilter, Filter::GollumTagsFilter)
.insert_before(Filter::TaskListFilter, Filter::WikiLinkFilter)
- .insert_before(Filter::WikiLinkFilter, Filter::SpacedLinkFilter)
end
end
end
diff --git a/lib/banzai/renderer/common_mark/html.rb b/lib/banzai/renderer/common_mark/html.rb
index 46b609c36b0..0b27316da1b 100644
--- a/lib/banzai/renderer/common_mark/html.rb
+++ b/lib/banzai/renderer/common_mark/html.rb
@@ -4,15 +4,11 @@ module Banzai
class HTML < CommonMarker::HtmlRenderer
def code_block(node)
block do
- code = node.string_content
- lang = node.fence_info
- lang_attr = lang.present? ? %Q{ lang="#{lang}"} : ''
- result =
- "<pre>" \
- "<code#{lang_attr}>#{ERB::Util.html_escape(code)}</code>" \
- "</pre>"
-
- out(result)
+ out("<pre#{sourcepos(node)}><code")
+ out(' lang="', node.fence_info, '"') if node.fence_info.present?
+ out('>')
+ out(escape_html(node.string_content))
+ out('</code></pre>')
end
end
end
diff --git a/lib/gitlab/ci/config.rb b/lib/gitlab/ci/config.rb
index 46dad59eb8c..fe98d25af29 100644
--- a/lib/gitlab/ci/config.rb
+++ b/lib/gitlab/ci/config.rb
@@ -1,6 +1,6 @@
module Gitlab
module Ci
- ##
+ #
# Base GitLab CI Configuration facade
#
class Config
@@ -15,6 +15,8 @@ module Gitlab
@global.compose!
rescue Loader::FormatError, Extendable::ExtensionError => e
raise Config::ConfigError, e.message
+ rescue ::Gitlab::Ci::External::Processor::FileError => e
+ raise ::Gitlab::Ci::YamlProcessor::ValidationError, e.message
end
def valid?
@@ -64,9 +66,22 @@ module Gitlab
@global.jobs_value
end
- # 'opts' argument is used in EE see /ee/lib/ee/gitlab/ci/config.rb
+ private
+
def build_config(config, opts = {})
- Loader.new(config).load!
+ initial_config = Loader.new(config).load!
+ project = opts.fetch(:project, nil)
+
+ if project
+ process_external_files(initial_config, project, opts)
+ else
+ initial_config
+ end
+ end
+
+ def process_external_files(config, project, opts)
+ sha = opts.fetch(:sha) { project.repository.root_ref_sha }
+ ::Gitlab::Ci::External::Processor.new(config, project, sha).perform
end
end
end
diff --git a/lib/gitlab/ci/external/file/base.rb b/lib/gitlab/ci/external/file/base.rb
new file mode 100644
index 00000000000..f4da07b0b02
--- /dev/null
+++ b/lib/gitlab/ci/external/file/base.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module External
+ module File
+ class Base
+ YAML_WHITELIST_EXTENSION = /(yml|yaml)$/i.freeze
+
+ def initialize(location, opts = {})
+ @location = location
+ end
+
+ def valid?
+ location.match(YAML_WHITELIST_EXTENSION) && content
+ end
+
+ def content
+ raise NotImplementedError, 'content must be implemented and return a string or nil'
+ end
+
+ def error_message
+ raise NotImplementedError, 'error_message must be implemented and return a string'
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/external/file/local.rb b/lib/gitlab/ci/external/file/local.rb
new file mode 100644
index 00000000000..1aa7f687507
--- /dev/null
+++ b/lib/gitlab/ci/external/file/local.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module External
+ module File
+ class Local < Base
+ attr_reader :location, :project, :sha
+
+ def initialize(location, opts = {})
+ super
+
+ @project = opts.fetch(:project)
+ @sha = opts.fetch(:sha)
+ end
+
+ def content
+ @content ||= fetch_local_content
+ end
+
+ def error_message
+ "Local file '#{location}' is not valid."
+ end
+
+ private
+
+ def fetch_local_content
+ project.repository.blob_data_at(sha, location)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/external/file/remote.rb b/lib/gitlab/ci/external/file/remote.rb
new file mode 100644
index 00000000000..59bb3e8999e
--- /dev/null
+++ b/lib/gitlab/ci/external/file/remote.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module External
+ module File
+ class Remote < Base
+ include Gitlab::Utils::StrongMemoize
+ attr_reader :location
+
+ def content
+ return @content if defined?(@content)
+
+ @content = strong_memoize(:content) do
+ begin
+ Gitlab::HTTP.get(location)
+ rescue Gitlab::HTTP::Error, Timeout::Error, SocketError, Gitlab::HTTP::BlockedUrlError
+ nil
+ end
+ end
+ end
+
+ def error_message
+ "Remote file '#{location}' is not valid."
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/external/mapper.rb b/lib/gitlab/ci/external/mapper.rb
new file mode 100644
index 00000000000..58bd6a19acf
--- /dev/null
+++ b/lib/gitlab/ci/external/mapper.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module External
+ class Mapper
+ def initialize(values, project, sha)
+ @locations = Array(values.fetch(:include, []))
+ @project = project
+ @sha = sha
+ end
+
+ def process
+ locations.map { |location| build_external_file(location) }
+ end
+
+ private
+
+ attr_reader :locations, :project, :sha
+
+ def build_external_file(location)
+ if ::Gitlab::UrlSanitizer.valid?(location)
+ Gitlab::Ci::External::File::Remote.new(location)
+ else
+ options = { project: project, sha: sha }
+ Gitlab::Ci::External::File::Local.new(location, options)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/external/processor.rb b/lib/gitlab/ci/external/processor.rb
new file mode 100644
index 00000000000..76cf3ce89f9
--- /dev/null
+++ b/lib/gitlab/ci/external/processor.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module External
+ class Processor
+ FileError = Class.new(StandardError)
+
+ def initialize(values, project, sha)
+ @values = values
+ @external_files = Gitlab::Ci::External::Mapper.new(values, project, sha).process
+ @content = {}
+ end
+
+ def perform
+ return values if external_files.empty?
+
+ external_files.each do |external_file|
+ validate_external_file(external_file)
+ @content.deep_merge!(content_of(external_file))
+ end
+
+ append_inline_content
+ remove_include_keyword
+ end
+
+ private
+
+ attr_reader :values, :external_files, :content
+
+ def validate_external_file(external_file)
+ unless external_file.valid?
+ raise FileError, external_file.error_message
+ end
+ end
+
+ def content_of(external_file)
+ Gitlab::Ci::Config::Loader.new(external_file.content).load!
+ end
+
+ def append_inline_content
+ @content.deep_merge!(@values)
+ end
+
+ def remove_include_keyword
+ content.delete(:include)
+ content
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/status/build/failed.rb b/lib/gitlab/ci/status/build/failed.rb
index 508b4814631..2fa9a0d4541 100644
--- a/lib/gitlab/ci/status/build/failed.rb
+++ b/lib/gitlab/ci/status/build/failed.rb
@@ -13,6 +13,8 @@ module Gitlab
runner_unsupported: 'unsupported runner'
}.freeze
+ private_constant :REASONS
+
def status_tooltip
base_message
end
@@ -25,6 +27,10 @@ module Gitlab
build.failed?
end
+ def self.reasons
+ REASONS
+ end
+
private
def base_message
@@ -36,7 +42,7 @@ module Gitlab
end
def failure_reason_message
- REASONS.fetch(subject.failure_reason.to_sym)
+ self.class.reasons.fetch(subject.failure_reason.to_sym)
end
end
end
diff --git a/lib/gitlab/contributions_calendar.rb b/lib/gitlab/contributions_calendar.rb
index 4c28489f45a..58ca077e636 100644
--- a/lib/gitlab/contributions_calendar.rb
+++ b/lib/gitlab/contributions_calendar.rb
@@ -7,7 +7,11 @@ module Gitlab
def initialize(contributor, current_user = nil)
@contributor = contributor
@current_user = current_user
- @projects = ContributedProjectsFinder.new(contributor).execute(current_user)
+ @projects = if @contributor.include_private_contributions?
+ ContributedProjectsFinder.new(@contributor).execute(@contributor)
+ else
+ ContributedProjectsFinder.new(contributor).execute(current_user)
+ end
end
def activity_dates
@@ -36,13 +40,9 @@ module Gitlab
def events_by_date(date)
return Event.none unless can_read_cross_project?
- events = Event.contributions.where(author_id: contributor.id)
+ Event.contributions.where(author_id: contributor.id)
.where(created_at: date.beginning_of_day..date.end_of_day)
.where(project_id: projects)
-
- # Use visible_to_user? instead of the complicated logic in activity_dates
- # because we're only viewing the events for a single day.
- events.select { |event| event.visible_to_user?(current_user) }
end
def starting_year
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index 9147ef401da..c9dbd2d2e5f 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -13,6 +13,10 @@ module Gitlab
Gitlab::FakeApplicationSettings.new(::ApplicationSetting.defaults.merge(attributes || {}))
end
+ def clear_in_memory_application_settings!
+ @in_memory_application_settings = nil
+ end
+
def method_missing(name, *args, &block)
current_application_settings.send(name, *args, &block) # rubocop:disable GitlabSecurity/PublicSend
end
diff --git a/lib/gitlab/diff/file_collection/base.rb b/lib/gitlab/diff/file_collection/base.rb
index c79d8d3cb21..2acb0e43b69 100644
--- a/lib/gitlab/diff/file_collection/base.rb
+++ b/lib/gitlab/diff/file_collection/base.rb
@@ -2,7 +2,7 @@ module Gitlab
module Diff
module FileCollection
class Base
- attr_reader :project, :diff_options, :diff_refs, :fallback_diff_refs
+ attr_reader :project, :diff_options, :diff_refs, :fallback_diff_refs, :diffable
delegate :count, :size, :real_size, to: :diff_files
@@ -33,6 +33,14 @@ module Gitlab
diff_files.find { |diff_file| diff_file.new_path == new_path }
end
+ def clear_cache
+ # No-op
+ end
+
+ def write_cache
+ # No-op
+ end
+
private
def decorate_diff!(diff)
diff --git a/lib/gitlab/diff/file_collection/merge_request_diff.rb b/lib/gitlab/diff/file_collection/merge_request_diff.rb
index be25e1bab21..0dd073a3a8e 100644
--- a/lib/gitlab/diff/file_collection/merge_request_diff.rb
+++ b/lib/gitlab/diff/file_collection/merge_request_diff.rb
@@ -2,6 +2,8 @@ module Gitlab
module Diff
module FileCollection
class MergeRequestDiff < Base
+ extend ::Gitlab::Utils::Override
+
def initialize(merge_request_diff, diff_options:)
@merge_request_diff = merge_request_diff
@@ -13,70 +15,35 @@ module Gitlab
end
def diff_files
- # Make sure to _not_ send any method call to Gitlab::Diff::File
- # _before_ all of them were collected (`super`). Premature method calls will
- # trigger N+1 RPCs to Gitaly through BatchLoader records (Blob.lazy).
- #
diff_files = super
- diff_files.each { |diff_file| cache_highlight!(diff_file) if cacheable?(diff_file) }
- store_highlight_cache
+ diff_files.each { |diff_file| cache.decorate(diff_file) }
diff_files
end
- def real_size
- @merge_request_diff.real_size
+ override :write_cache
+ def write_cache
+ cache.write_if_empty
end
- def clear_cache!
- Rails.cache.delete(cache_key)
+ override :clear_cache
+ def clear_cache
+ cache.clear
end
def cache_key
- [@merge_request_diff, 'highlighted-diff-files', Gitlab::Diff::Line::SERIALIZE_KEYS, diff_options]
- end
-
- private
-
- def highlight_diff_file_from_cache!(diff_file, cache_diff_lines)
- diff_file.highlighted_diff_lines = cache_diff_lines.map do |line|
- Gitlab::Diff::Line.init_from_hash(line)
- end
+ cache.key
end
- #
- # If we find the highlighted diff files lines on the cache we replace existing diff_files lines (no highlighted)
- # for the highlighted ones, so we just skip their execution.
- # If the highlighted diff files lines are not cached we calculate and cache them.
- #
- # The content of the cache is a Hash where the key identifies the file and the values are Arrays of
- # hashes that represent serialized diff lines.
- #
- def cache_highlight!(diff_file)
- item_key = diff_file.file_identifier
-
- if highlight_cache[item_key]
- highlight_diff_file_from_cache!(diff_file, highlight_cache[item_key])
- else
- highlight_cache[item_key] = diff_file.highlighted_diff_lines.map(&:to_hash)
- end
- end
-
- def highlight_cache
- return @highlight_cache if defined?(@highlight_cache)
-
- @highlight_cache = Rails.cache.read(cache_key) || {}
- @highlight_cache_was_empty = @highlight_cache.empty?
- @highlight_cache
+ def real_size
+ @merge_request_diff.real_size
end
- def store_highlight_cache
- Rails.cache.write(cache_key, highlight_cache, expires_in: 1.week) if @highlight_cache_was_empty
- end
+ private
- def cacheable?(diff_file)
- @merge_request_diff.present? && diff_file.text? && diff_file.diffable?
+ def cache
+ @cache ||= Gitlab::Diff::HighlightCache.new(self)
end
end
end
diff --git a/lib/gitlab/diff/highlight_cache.rb b/lib/gitlab/diff/highlight_cache.rb
new file mode 100644
index 00000000000..e4390771db2
--- /dev/null
+++ b/lib/gitlab/diff/highlight_cache.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+#
+module Gitlab
+ module Diff
+ class HighlightCache
+ delegate :diffable, to: :@diff_collection
+ delegate :diff_options, to: :@diff_collection
+
+ def initialize(diff_collection, backend: Rails.cache)
+ @backend = backend
+ @diff_collection = diff_collection
+ end
+
+ # - Reads from cache
+ # - Assigns DiffFile#highlighted_diff_lines for cached files
+ def decorate(diff_file)
+ if content = read_file(diff_file)
+ diff_file.highlighted_diff_lines = content.map do |line|
+ Gitlab::Diff::Line.init_from_hash(line)
+ end
+ end
+ end
+
+ # It populates a Hash in order to submit a single write to the memory
+ # cache. This avoids excessive IO generated by N+1's (1 writing for
+ # each highlighted line or file).
+ def write_if_empty
+ return if cached_content.present?
+
+ @diff_collection.diff_files.each do |diff_file|
+ next unless cacheable?(diff_file)
+
+ diff_file_id = diff_file.file_identifier
+
+ cached_content[diff_file_id] = diff_file.highlighted_diff_lines.map(&:to_hash)
+ end
+
+ cache.write(key, cached_content, expires_in: 1.week)
+ end
+
+ def clear
+ cache.delete(key)
+ end
+
+ def key
+ [diffable, 'highlighted-diff-files', Gitlab::Diff::Line::SERIALIZE_KEYS, diff_options]
+ end
+
+ private
+
+ def read_file(diff_file)
+ cached_content[diff_file.file_identifier]
+ end
+
+ def cache
+ @backend
+ end
+
+ def cached_content
+ @cached_content ||= cache.read(key) || {}
+ end
+
+ def cacheable?(diff_file)
+ diffable.present? && diff_file.text? && diff_file.diffable?
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/fake_application_settings.rb b/lib/gitlab/fake_application_settings.rb
index bb14a8cd9e7..2c827265d8c 100644
--- a/lib/gitlab/fake_application_settings.rb
+++ b/lib/gitlab/fake_application_settings.rb
@@ -5,12 +5,6 @@
# column type without parsing db/schema.rb.
module Gitlab
class FakeApplicationSettings < OpenStruct
- def initialize(options = {})
- super
-
- FakeApplicationSettings.define_predicate_methods(options)
- end
-
# Mimic ActiveRecord predicate methods for boolean values
def self.define_predicate_methods(options)
options.each do |key, value|
@@ -23,5 +17,23 @@ module Gitlab
end
end
end
+
+ def initialize(options = {})
+ super
+
+ FakeApplicationSettings.define_predicate_methods(options)
+ end
+
+ def key_restriction_for(type)
+ 0
+ end
+
+ def allowed_key_types
+ ApplicationSetting::SUPPORTED_KEY_TYPES
+ end
+
+ def pick_repository_storage
+ repository_storages.sample
+ end
end
end
diff --git a/lib/gitlab/git/committer_with_hooks.rb b/lib/gitlab/git/committer_with_hooks.rb
deleted file mode 100644
index 4198be7c9c9..00000000000
--- a/lib/gitlab/git/committer_with_hooks.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-module Gitlab
- module Git
- class CommitterWithHooks < Gollum::Committer
- attr_reader :gl_wiki
-
- def initialize(gl_wiki, options = {})
- @gl_wiki = gl_wiki
- super(gl_wiki.gollum_wiki, options)
- end
-
- def commit
- # TODO: Remove after 10.8
- return super unless allowed_to_run_hooks?
-
- result = Gitlab::Git::OperationService.new(git_user, gl_wiki.repository).with_branch(
- @wiki.ref,
- start_branch_name: @wiki.ref
- ) do |start_commit|
- super(false)
- end
-
- result[:newrev]
- rescue Gitlab::Git::PreReceiveError => e
- message = "Custom Hook failed: #{e.message}"
- raise Gitlab::Git::Wiki::OperationError, message
- end
-
- private
-
- # TODO: Remove after 10.8
- def allowed_to_run_hooks?
- @options[:user_id] != 0 && @options[:username].present?
- end
-
- def git_user
- @git_user ||= Gitlab::Git::User.new(@options[:username],
- @options[:name],
- @options[:email],
- gitlab_id)
- end
-
- def gitlab_id
- Gitlab::GlId.gl_id_from_id_value(@options[:user_id])
- end
- end
- end
-end
diff --git a/lib/gitlab/git/diff.rb b/lib/gitlab/git/diff.rb
index 61ce10ca131..f6b51dc3982 100644
--- a/lib/gitlab/git/diff.rb
+++ b/lib/gitlab/git/diff.rb
@@ -1,6 +1,3 @@
-# Gitaly note: JV: needs RPC for Gitlab::Git::Diff.between.
-
-# Gitlab::Git::Diff is a wrapper around native Rugged::Diff object
module Gitlab
module Git
class Diff
@@ -52,20 +49,31 @@ module Gitlab
repo.diff(common_commit, head, actual_options, *paths)
end
- # Return a copy of the +options+ hash containing only keys that can be
- # passed to Rugged. Allowed options are:
+ # Return a copy of the +options+ hash containing only recognized keys.
+ # Allowed options are:
#
# :ignore_whitespace_change ::
# If true, changes in amount of whitespace will be ignored.
#
- # :disable_pathspec_match ::
- # If true, the given +*paths+ will be applied as exact matches,
- # instead of as fnmatch patterns.
+ # :max_files ::
+ # Limit how many files will patches be allowed for before collapsing
+ #
+ # :max_lines ::
+ # Limit how many patch lines (across all files) will be allowed for
+ # before collapsing
#
+ # :limits ::
+ # A hash with additional limits to check before collapsing patches.
+ # Allowed keys are: `max_bytes`, `safe_max_files`, `safe_max_lines`
+ # and `safe_max_bytes`
+ #
+ # :expanded ::
+ # If true, patch raw data will not be included in the diff after
+ # `max_files`, `max_lines` or any of the limits in `limits` are
+ # exceeded
def filter_diff_options(options, default_options = {})
- allowed_options = [:ignore_whitespace_change,
- :disable_pathspec_match, :paths,
- :max_files, :max_lines, :limits, :expanded]
+ allowed_options = [:ignore_whitespace_change, :max_files, :max_lines,
+ :limits, :expanded]
if default_options
actual_defaults = default_options.dup
@@ -93,7 +101,7 @@ module Gitlab
#
# "Binary files a/file/path and b/file/path differ\n"
# This is used when we detect that a diff is binary
- # using CharlockHolmes when Rugged treats it as text.
+ # using CharlockHolmes.
def binary_message(old_path, new_path)
"Binary files #{old_path} and #{new_path} differ\n"
end
@@ -106,8 +114,6 @@ module Gitlab
when Hash
init_from_hash(raw_diff)
prune_diff_if_eligible
- when Rugged::Patch, Rugged::Diff::Delta
- init_from_rugged(raw_diff)
when Gitlab::GitalyClient::Diff
init_from_gitaly(raw_diff)
prune_diff_if_eligible
@@ -184,31 +190,6 @@ module Gitlab
private
- def init_from_rugged(rugged)
- if rugged.is_a?(Rugged::Patch)
- init_from_rugged_patch(rugged)
- d = rugged.delta
- else
- d = rugged
- end
-
- @new_path = encode!(d.new_file[:path])
- @old_path = encode!(d.old_file[:path])
- @a_mode = d.old_file[:mode].to_s(8)
- @b_mode = d.new_file[:mode].to_s(8)
- @new_file = d.added?
- @renamed_file = d.renamed?
- @deleted_file = d.deleted?
- end
-
- def init_from_rugged_patch(patch)
- # Don't bother initializing diffs that are too large. If a diff is
- # binary we're not going to display anything so we skip the size check.
- return if !patch.delta.binary? && prune_large_patch(patch)
-
- @diff = encode!(strip_diff_headers(patch.to_s))
- end
-
def init_from_hash(hash)
raw_diff = hash.symbolize_keys
@@ -262,23 +243,6 @@ module Gitlab
false
end
-
- # Strip out the information at the beginning of the patch's text to match
- # Grit's output
- def strip_diff_headers(diff_text)
- # Delete everything up to the first line that starts with '---' or
- # 'Binary'
- diff_text.sub!(/\A.*?^(---|Binary)/m, '\1')
-
- if diff_text.start_with?('---', 'Binary')
- diff_text
- else
- # If the diff_text did not contain a line starting with '---' or
- # 'Binary', return the empty string. No idea why; we are just
- # preserving behavior from before the refactor.
- ''
- end
- end
end
end
end
diff --git a/lib/gitlab/git/diff_collection.rb b/lib/gitlab/git/diff_collection.rb
index 219c69893ad..20dce8d0e06 100644
--- a/lib/gitlab/git/diff_collection.rb
+++ b/lib/gitlab/git/diff_collection.rb
@@ -11,7 +11,7 @@ module Gitlab
delegate :max_files, :max_lines, :max_bytes, :safe_max_files, :safe_max_lines, :safe_max_bytes, to: :limits
- def self.collection_limits(options = {})
+ def self.limits(options = {})
limits = {}
limits[:max_files] = options.fetch(:max_files, DEFAULT_LIMITS[:max_files])
limits[:max_lines] = options.fetch(:max_lines, DEFAULT_LIMITS[:max_lines])
@@ -19,13 +19,14 @@ module Gitlab
limits[:safe_max_files] = [limits[:max_files], DEFAULT_LIMITS[:max_files]].min
limits[:safe_max_lines] = [limits[:max_lines], DEFAULT_LIMITS[:max_lines]].min
limits[:safe_max_bytes] = limits[:safe_max_files] * 5.kilobytes # Average 5 KB per file
+ limits[:max_patch_bytes] = Gitlab::Git::Diff::SIZE_LIMIT
OpenStruct.new(limits)
end
def initialize(iterator, options = {})
@iterator = iterator
- @limits = self.class.collection_limits(options)
+ @limits = self.class.limits(options)
@enforce_limits = !!options.fetch(:limits, true)
@expanded = !!options.fetch(:expanded, true)
diff --git a/lib/gitlab/git/gitlab_projects.rb b/lib/gitlab/git/gitlab_projects.rb
deleted file mode 100644
index 5ff15a787f0..00000000000
--- a/lib/gitlab/git/gitlab_projects.rb
+++ /dev/null
@@ -1,253 +0,0 @@
-module Gitlab
- module Git
- class GitlabProjects
- include Gitlab::Git::Popen
- include Gitlab::Utils::StrongMemoize
-
- # Name of shard where repositories are stored.
- # Example: nfs-file06
- attr_reader :shard_name
-
- # Relative path is a directory name for repository with .git at the end.
- # Example: gitlab-org/gitlab-test.git
- attr_reader :repository_relative_path
-
- # This is the path at which the gitlab-shell hooks directory can be found.
- # It's essential for integration between git and GitLab proper. All new
- # repositories should have their hooks directory symlinked here.
- attr_reader :global_hooks_path
-
- attr_reader :logger
-
- def initialize(shard_name, repository_relative_path, global_hooks_path:, logger:)
- @shard_name = shard_name
- @repository_relative_path = repository_relative_path
-
- @logger = logger
- @global_hooks_path = global_hooks_path
- @output = StringIO.new
- end
-
- def output
- io = @output.dup
- io.rewind
- io.read
- end
-
- # Absolute path to the repository.
- # Example: /home/git/repositorities/gitlab-org/gitlab-test.git
- # Probably will be removed when we fully migrate to Gitaly, part of
- # https://gitlab.com/gitlab-org/gitaly/issues/1124.
- def repository_absolute_path
- strong_memoize(:repository_absolute_path) do
- File.join(shard_path, repository_relative_path)
- end
- end
-
- def shard_path
- strong_memoize(:shard_path) do
- Gitlab.config.repositories.storages.fetch(shard_name).legacy_disk_path
- end
- end
-
- # Import project via git clone --bare
- # URL must be publicly cloneable
- def import_project(source, timeout)
- git_import_repository(source, timeout)
- end
-
- def fork_repository(new_shard_name, new_repository_relative_path)
- git_fork_repository(new_shard_name, new_repository_relative_path)
- end
-
- def fetch_remote(name, timeout, force:, tags:, ssh_key: nil, known_hosts: nil, prune: true)
- logger.info "Fetching remote #{name} for repository #{repository_absolute_path}."
- cmd = fetch_remote_command(name, tags, prune, force)
-
- setup_ssh_auth(ssh_key, known_hosts) do |env|
- run_with_timeout(cmd, timeout, repository_absolute_path, env).tap do |success|
- unless success
- logger.error "Fetching remote #{name} for repository #{repository_absolute_path} failed."
- end
- end
- end
- end
-
- def push_branches(remote_name, timeout, force, branch_names)
- logger.info "Pushing branches from #{repository_absolute_path} to remote #{remote_name}: #{branch_names}"
- cmd = %W(#{Gitlab.config.git.bin_path} push)
- cmd << '--force' if force
- cmd += %W(-- #{remote_name}).concat(branch_names)
-
- success = run_with_timeout(cmd, timeout, repository_absolute_path)
-
- unless success
- logger.error("Pushing branches to remote #{remote_name} failed.")
- end
-
- success
- end
-
- def delete_remote_branches(remote_name, branch_names)
- branches = branch_names.map { |branch_name| ":#{branch_name}" }
-
- logger.info "Pushing deleted branches from #{repository_absolute_path} to remote #{remote_name}: #{branch_names}"
- cmd = %W(#{Gitlab.config.git.bin_path} push -- #{remote_name}).concat(branches)
-
- success = run(cmd, repository_absolute_path)
-
- unless success
- logger.error("Pushing deleted branches to remote #{remote_name} failed.")
- end
-
- success
- end
-
- protected
-
- def run(*args)
- output, exitstatus = popen(*args)
- @output << output
-
- exitstatus&.zero?
- end
-
- def run_with_timeout(*args)
- output, exitstatus = popen_with_timeout(*args)
- @output << output
-
- exitstatus&.zero?
- rescue Timeout::Error
- @output.puts('Timed out')
-
- false
- end
-
- def mask_password_in_url(url)
- result = URI(url)
- result.password = "*****" unless result.password.nil?
- result.user = "*****" unless result.user.nil? # it's needed for oauth access_token
- result
- rescue
- url
- end
-
- def remove_origin_in_repo
- cmd = %W(#{Gitlab.config.git.bin_path} remote rm origin)
- run(cmd, repository_absolute_path)
- end
-
- # Builds a small shell script that can be used to execute SSH with a set of
- # custom options.
- #
- # Options are expanded as `'-oKey="Value"'`, so SSH will correctly interpret
- # paths with spaces in them. We trust the user not to embed single or double
- # quotes in the key or value.
- def custom_ssh_script(options = {})
- args = options.map { |k, v| %Q{'-o#{k}="#{v}"'} }.join(' ')
-
- [
- "#!/bin/sh",
- "exec ssh #{args} \"$@\""
- ].join("\n")
- end
-
- # Known hosts data and private keys can be passed to gitlab-shell in the
- # environment. If present, this method puts them into temporary files, writes
- # a script that can substitute as `ssh`, setting the options to respect those
- # files, and yields: { "GIT_SSH" => "/tmp/myScript" }
- def setup_ssh_auth(key, known_hosts)
- options = {}
-
- if key
- key_file = Tempfile.new('gitlab-shell-key-file')
- key_file.chmod(0o400)
- key_file.write(key)
- key_file.close
-
- options['IdentityFile'] = key_file.path
- options['IdentitiesOnly'] = 'yes'
- end
-
- if known_hosts
- known_hosts_file = Tempfile.new('gitlab-shell-known-hosts')
- known_hosts_file.chmod(0o400)
- known_hosts_file.write(known_hosts)
- known_hosts_file.close
-
- options['StrictHostKeyChecking'] = 'yes'
- options['UserKnownHostsFile'] = known_hosts_file.path
- end
-
- return yield({}) if options.empty?
-
- script = Tempfile.new('gitlab-shell-ssh-wrapper')
- script.chmod(0o755)
- script.write(custom_ssh_script(options))
- script.close
-
- yield('GIT_SSH' => script.path)
- ensure
- key_file&.close!
- known_hosts_file&.close!
- script&.close!
- end
-
- private
-
- def fetch_remote_command(name, tags, prune, force)
- %W(#{Gitlab.config.git.bin_path} fetch #{name} --quiet).tap do |cmd|
- cmd << '--prune' if prune
- cmd << '--force' if force
- cmd << (tags ? '--tags' : '--no-tags')
- end
- end
-
- def git_import_repository(source, timeout)
- # Skip import if repo already exists
- return false if File.exist?(repository_absolute_path)
-
- masked_source = mask_password_in_url(source)
-
- logger.info "Importing project from <#{masked_source}> to <#{repository_absolute_path}>."
- cmd = %W(#{Gitlab.config.git.bin_path} clone --bare -- #{source} #{repository_absolute_path})
-
- success = run_with_timeout(cmd, timeout, nil)
-
- unless success
- logger.error("Importing project from <#{masked_source}> to <#{repository_absolute_path}> failed.")
- FileUtils.rm_rf(repository_absolute_path)
- return false
- end
-
- Gitlab::Git::Repository.create_hooks(repository_absolute_path, global_hooks_path)
-
- # The project was imported successfully.
- # Remove the origin URL since it may contain password.
- remove_origin_in_repo
-
- true
- end
-
- def git_fork_repository(new_shard_name, new_repository_relative_path)
- from_path = repository_absolute_path
- new_shard_path = Gitlab.config.repositories.storages.fetch(new_shard_name).legacy_disk_path
- to_path = File.join(new_shard_path, new_repository_relative_path)
-
- # The repository cannot already exist
- if File.exist?(to_path)
- logger.error "fork-repository failed: destination repository <#{to_path}> already exists."
- return false
- end
-
- # Ensure the namepsace / hashed storage directory exists
- FileUtils.mkdir_p(File.dirname(to_path), mode: 0770)
-
- logger.info "Forking repository from <#{from_path}> to <#{to_path}>."
- cmd = %W(#{Gitlab.config.git.bin_path} clone --bare --no-local -- #{from_path} #{to_path})
-
- run(cmd, nil) && Gitlab::Git::Repository.create_hooks(to_path, global_hooks_path)
- end
- end
- end
-end
diff --git a/lib/gitlab/git/hook.rb b/lib/gitlab/git/hook.rb
deleted file mode 100644
index 94ff5b4980a..00000000000
--- a/lib/gitlab/git/hook.rb
+++ /dev/null
@@ -1,108 +0,0 @@
-# Gitaly note: JV: looks like this is only used by Gitlab::Git::HooksService in
-# app/services. We shouldn't bother migrating this until we know how
-# Gitlab::Git::HooksService will be migrated.
-
-module Gitlab
- module Git
- class Hook
- GL_PROTOCOL = 'web'.freeze
- attr_reader :name, :path, :repository
-
- def initialize(name, repository)
- @name = name
- @repository = repository
- @path = File.join(repo_path, 'hooks', name)
- end
-
- def repo_path
- repository.path
- end
-
- def exists?
- File.exist?(path)
- end
-
- def trigger(gl_id, gl_username, oldrev, newrev, ref)
- return [true, nil] unless exists?
-
- Bundler.with_clean_env do
- case name
- when "pre-receive", "post-receive"
- call_receive_hook(gl_id, gl_username, oldrev, newrev, ref)
- when "update"
- call_update_hook(gl_id, gl_username, oldrev, newrev, ref)
- end
- end
- end
-
- private
-
- def call_receive_hook(gl_id, gl_username, oldrev, newrev, ref)
- changes = [oldrev, newrev, ref].join(" ")
-
- exit_status = false
- exit_message = nil
-
- vars = {
- 'GL_ID' => gl_id,
- 'GL_USERNAME' => gl_username,
- 'PWD' => repo_path,
- 'GL_PROTOCOL' => GL_PROTOCOL,
- 'GL_REPOSITORY' => repository.gl_repository
- }
-
- options = {
- chdir: repo_path
- }
-
- Open3.popen3(vars, path, options) do |stdin, stdout, stderr, wait_thr|
- exit_status = true
- stdin.sync = true
-
- # in git, pre- and post- receive hooks may just exit without
- # reading stdin. We catch the exception to avoid a broken pipe
- # warning
- begin
- # inject all the changes as stdin to the hook
- changes.lines do |line|
- stdin.puts line
- end
- rescue Errno::EPIPE
- end
-
- stdin.close
-
- unless wait_thr.value == 0
- exit_status = false
- exit_message = retrieve_error_message(stderr, stdout)
- end
- end
-
- [exit_status, exit_message]
- end
-
- def call_update_hook(gl_id, gl_username, oldrev, newrev, ref)
- env = {
- 'GL_ID' => gl_id,
- 'GL_USERNAME' => gl_username,
- 'PWD' => repo_path
- }
-
- options = {
- chdir: repo_path
- }
-
- args = [ref, oldrev, newrev]
-
- stdout, stderr, status = Open3.capture3(env, path, *args, options)
- [status.success?, stderr.presence || stdout]
- end
-
- def retrieve_error_message(stderr, stdout)
- err_message = stderr.read
- err_message = err_message.blank? ? stdout.read : err_message
- err_message
- end
- end
- end
-end
diff --git a/lib/gitlab/git/hooks_service.rb b/lib/gitlab/git/hooks_service.rb
deleted file mode 100644
index e67cacdb95a..00000000000
--- a/lib/gitlab/git/hooks_service.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-module Gitlab
- module Git
- class HooksService
- attr_accessor :oldrev, :newrev, :ref
-
- def execute(pusher, repository, oldrev, newrev, ref)
- @repository = repository
- @gl_id = pusher.gl_id
- @gl_username = pusher.username
- @oldrev = oldrev
- @newrev = newrev
- @ref = ref
-
- %w(pre-receive update).each do |hook_name|
- status, message = run_hook(hook_name)
-
- unless status
- raise PreReceiveError, message
- end
- end
-
- yield(self).tap do
- run_hook('post-receive')
- end
- end
-
- private
-
- def run_hook(name)
- hook = Gitlab::Git::Hook.new(name, @repository)
- hook.trigger(@gl_id, @gl_username, oldrev, newrev, ref)
- end
- end
- end
-end
diff --git a/lib/gitlab/git/index.rb b/lib/gitlab/git/index.rb
index d94082a3e30..c2e4274e3ee 100644
--- a/lib/gitlab/git/index.rb
+++ b/lib/gitlab/git/index.rb
@@ -1,157 +1,7 @@
-# Gitaly note: JV: When the time comes I think we will want to copy this
-# class into Gitaly. None of its methods look like they should be RPC's.
-# The RPC's will be at a higher level.
-
module Gitlab
module Git
class Index
IndexError = Class.new(StandardError)
-
- DEFAULT_MODE = 0o100644
-
- ACTIONS = %w(create create_dir update move delete).freeze
- ACTION_OPTIONS = %i(file_path previous_path content encoding).freeze
-
- attr_reader :repository, :raw_index
-
- def initialize(repository)
- @repository = repository
- @raw_index = repository.rugged.index
- end
-
- delegate :read_tree, :get, to: :raw_index
-
- def apply(action, options)
- validate_action!(action)
- public_send(action, options.slice(*ACTION_OPTIONS)) # rubocop:disable GitlabSecurity/PublicSend
- end
-
- def write_tree
- raw_index.write_tree(repository.rugged)
- end
-
- def dir_exists?(path)
- raw_index.find { |entry| entry[:path].start_with?("#{path}/") }
- end
-
- def create(options)
- options = normalize_options(options)
-
- if get(options[:file_path])
- raise IndexError, "A file with this name already exists"
- end
-
- add_blob(options)
- end
-
- def create_dir(options)
- options = normalize_options(options)
-
- if get(options[:file_path])
- raise IndexError, "A file with this name already exists"
- end
-
- if dir_exists?(options[:file_path])
- raise IndexError, "A directory with this name already exists"
- end
-
- options = options.dup
- options[:file_path] += '/.gitkeep'
- options[:content] = ''
-
- add_blob(options)
- end
-
- def update(options)
- options = normalize_options(options)
-
- file_entry = get(options[:file_path])
- unless file_entry
- raise IndexError, "A file with this name doesn't exist"
- end
-
- add_blob(options, mode: file_entry[:mode])
- end
-
- def move(options)
- options = normalize_options(options)
-
- file_entry = get(options[:previous_path])
- unless file_entry
- raise IndexError, "A file with this name doesn't exist"
- end
-
- if get(options[:file_path])
- raise IndexError, "A file with this name already exists"
- end
-
- raw_index.remove(options[:previous_path])
-
- add_blob(options, mode: file_entry[:mode])
- end
-
- def delete(options)
- options = normalize_options(options)
-
- unless get(options[:file_path])
- raise IndexError, "A file with this name doesn't exist"
- end
-
- raw_index.remove(options[:file_path])
- end
-
- private
-
- def normalize_options(options)
- options = options.dup
- options[:file_path] = normalize_path(options[:file_path]) if options[:file_path]
- options[:previous_path] = normalize_path(options[:previous_path]) if options[:previous_path]
- options
- end
-
- def normalize_path(path)
- unless path
- raise IndexError, "You must provide a file path"
- end
-
- pathname = Gitlab::Git::PathHelper.normalize_path(path.dup)
-
- pathname.each_filename do |segment|
- if segment == '..'
- raise IndexError, 'Path cannot include directory traversal'
- end
- end
-
- pathname.to_s
- end
-
- def add_blob(options, mode: nil)
- content = options[:content]
- unless content
- raise IndexError, "You must provide content"
- end
-
- content = Base64.decode64(content) if options[:encoding] == 'base64'
-
- detect = CharlockHolmes::EncodingDetector.new.detect(content)
- unless detect && detect[:type] == :binary
- # When writing to the repo directly as we are doing here,
- # the `core.autocrlf` config isn't taken into account.
- content.gsub!("\r\n", "\n") if repository.autocrlf
- end
-
- oid = repository.rugged.write(content, :blob)
-
- raw_index.add(path: options[:file_path], oid: oid, mode: mode || DEFAULT_MODE)
- rescue Rugged::IndexError => e
- raise IndexError, e.message
- end
-
- def validate_action!(action)
- unless ACTIONS.include?(action.to_s)
- raise ArgumentError, "Unknown action '#{action}'"
- end
- end
end
end
end
diff --git a/lib/gitlab/git/operation_service.rb b/lib/gitlab/git/operation_service.rb
index 57d748343be..0584629ac84 100644
--- a/lib/gitlab/git/operation_service.rb
+++ b/lib/gitlab/git/operation_service.rb
@@ -1,8 +1,6 @@
module Gitlab
module Git
class OperationService
- include Gitlab::Git::Popen
-
BranchUpdate = Struct.new(:newrev, :repo_created, :branch_created) do
alias_method :repo_created?, :repo_created
alias_method :branch_created?, :branch_created
@@ -17,177 +15,6 @@ module Gitlab
)
end
end
-
- attr_reader :user, :repository
-
- def initialize(user, new_repository)
- if user
- user = Gitlab::Git::User.from_gitlab(user) unless user.respond_to?(:gl_id)
- @user = user
- end
-
- # Refactoring aid
- Gitlab::Git.check_namespace!(new_repository)
-
- @repository = new_repository
- end
-
- def add_branch(branch_name, newrev)
- ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
- oldrev = Gitlab::Git::BLANK_SHA
-
- update_ref_in_hooks(ref, newrev, oldrev)
- end
-
- def rm_branch(branch)
- ref = Gitlab::Git::BRANCH_REF_PREFIX + branch.name
- oldrev = branch.target
- newrev = Gitlab::Git::BLANK_SHA
-
- update_ref_in_hooks(ref, newrev, oldrev)
- end
-
- def add_tag(tag_name, newrev, options = {})
- ref = Gitlab::Git::TAG_REF_PREFIX + tag_name
- oldrev = Gitlab::Git::BLANK_SHA
-
- with_hooks(ref, newrev, oldrev) do |service|
- # We want to pass the OID of the tag object to the hooks. For an
- # annotated tag we don't know that OID until after the tag object
- # (raw_tag) is created in the repository. That is why we have to
- # update the value after creating the tag object. Only the
- # "post-receive" hook will receive the correct value in this case.
- raw_tag = repository.rugged.tags.create(tag_name, newrev, options)
- service.newrev = raw_tag.target_id
- end
- end
-
- def rm_tag(tag)
- ref = Gitlab::Git::TAG_REF_PREFIX + tag.name
- oldrev = tag.target
- newrev = Gitlab::Git::BLANK_SHA
-
- update_ref_in_hooks(ref, newrev, oldrev) do
- repository.rugged.tags.delete(tag_name)
- end
- end
-
- # Whenever `start_branch_name` is passed, if `branch_name` doesn't exist,
- # it would be created from `start_branch_name`.
- # If `start_repository` is passed, and the branch doesn't exist,
- # it would try to find the commits from it instead of current repository.
- def with_branch(
- branch_name,
- start_branch_name: nil,
- start_repository: repository,
- &block)
-
- Gitlab::Git.check_namespace!(start_repository)
- start_repository = RemoteRepository.new(start_repository) unless start_repository.is_a?(RemoteRepository)
-
- start_branch_name = nil if start_repository.empty?
-
- if start_branch_name && !start_repository.branch_exists?(start_branch_name)
- raise ArgumentError, "Cannot find branch #{start_branch_name} in #{start_repository.relative_path}"
- end
-
- update_branch_with_hooks(branch_name) do
- repository.with_repo_branch_commit(
- start_repository,
- start_branch_name || branch_name,
- &block)
- end
- end
-
- def update_branch(branch_name, newrev, oldrev)
- ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
- update_ref_in_hooks(ref, newrev, oldrev)
- end
-
- private
-
- # Returns [newrev, should_run_after_create, should_run_after_create_branch]
- def update_branch_with_hooks(branch_name)
- update_autocrlf_option
-
- was_empty = repository.empty?
-
- # Make commit
- newrev = yield
-
- unless newrev
- raise Gitlab::Git::CommitError.new('Failed to create commit')
- end
-
- branch = repository.find_branch(branch_name)
- oldrev = find_oldrev_from_branch(newrev, branch)
-
- ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
- update_ref_in_hooks(ref, newrev, oldrev)
-
- BranchUpdate.new(newrev, was_empty, was_empty || Gitlab::Git.blank_ref?(oldrev))
- end
-
- def find_oldrev_from_branch(newrev, branch)
- return Gitlab::Git::BLANK_SHA unless branch
-
- oldrev = branch.target
-
- merge_base = repository.merge_base(newrev, branch.target)
- raise Gitlab::Git::Repository::InvalidRef unless merge_base
-
- if oldrev == merge_base
- oldrev
- else
- raise Gitlab::Git::CommitError.new('Branch diverged')
- end
- end
-
- def update_ref_in_hooks(ref, newrev, oldrev)
- with_hooks(ref, newrev, oldrev) do
- update_ref(ref, newrev, oldrev)
- end
- end
-
- def with_hooks(ref, newrev, oldrev)
- Gitlab::Git::HooksService.new.execute(
- user,
- repository,
- oldrev,
- newrev,
- ref) do |service|
-
- yield(service)
- end
- end
-
- # Gitaly note: JV: wait with migrating #update_ref until we know how to migrate its call sites.
- def update_ref(ref, newrev, oldrev)
- # We use 'git update-ref' because libgit2/rugged currently does not
- # offer 'compare and swap' ref updates. Without compare-and-swap we can
- # (and have!) accidentally reset the ref to an earlier state, clobbering
- # commits. See also https://github.com/libgit2/libgit2/issues/1534.
- command = %W[#{Gitlab.config.git.bin_path} update-ref --stdin -z]
-
- output, status = popen(
- command,
- repository.path) do |stdin|
- stdin.write("update #{ref}\x00#{newrev}\x00#{oldrev}\x00")
- end
-
- unless status.zero?
- Gitlab::GitLogger.error("'git update-ref' in #{repository.path}: #{output}")
- raise Gitlab::Git::CommitError.new(
- "Could not update branch #{Gitlab::Git.branch_name(ref)}." \
- " Please refresh and try again.")
- end
- end
-
- def update_autocrlf_option
- if repository.autocrlf != :input
- repository.autocrlf = :input
- end
- end
end
end
end
diff --git a/lib/gitlab/git/popen.rb b/lib/gitlab/git/popen.rb
deleted file mode 100644
index 7426688fc55..00000000000
--- a/lib/gitlab/git/popen.rb
+++ /dev/null
@@ -1,112 +0,0 @@
-# Gitaly note: JV: no RPC's here.
-
-require 'open3'
-
-module Gitlab
- module Git
- module Popen
- FAST_GIT_PROCESS_TIMEOUT = 15.seconds
-
- def popen(cmd, path, vars = {}, lazy_block: nil)
- unless cmd.is_a?(Array)
- raise "System commands must be given as an array of strings"
- end
-
- path ||= Dir.pwd
- vars['PWD'] = path
- options = { chdir: path }
-
- cmd_output = ""
- cmd_status = 0
- Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
- stdout.set_encoding(Encoding::ASCII_8BIT)
-
- # stderr and stdout pipes can block if stderr/stdout aren't drained: https://bugs.ruby-lang.org/issues/9082
- # Mimic what Ruby does with capture3: https://github.com/ruby/ruby/blob/1ec544695fa02d714180ef9c34e755027b6a2103/lib/open3.rb#L257-L273
- err_reader = Thread.new { stderr.read }
-
- yield(stdin) if block_given?
- stdin.close
-
- if lazy_block
- cmd_output = lazy_block.call(stdout.lazy)
- cmd_status = 0
- break
- else
- cmd_output << stdout.read
- end
-
- cmd_output << err_reader.value
- cmd_status = wait_thr.value.exitstatus
- end
-
- [cmd_output, cmd_status]
- end
-
- def popen_with_timeout(cmd, timeout, path, vars = {})
- unless cmd.is_a?(Array)
- raise "System commands must be given as an array of strings"
- end
-
- path ||= Dir.pwd
- vars['PWD'] = path
-
- unless File.directory?(path)
- FileUtils.mkdir_p(path)
- end
-
- rout, wout = IO.pipe
- rerr, werr = IO.pipe
-
- pid = Process.spawn(vars, *cmd, out: wout, err: werr, chdir: path, pgroup: true)
- # stderr and stdout pipes can block if stderr/stdout aren't drained: https://bugs.ruby-lang.org/issues/9082
- # Mimic what Ruby does with capture3: https://github.com/ruby/ruby/blob/1ec544695fa02d714180ef9c34e755027b6a2103/lib/open3.rb#L257-L273
- out_reader = Thread.new { rout.read }
- err_reader = Thread.new { rerr.read }
-
- begin
- # close write ends so we could read them
- wout.close
- werr.close
-
- status = process_wait_with_timeout(pid, timeout)
-
- cmd_output = out_reader.value
- cmd_output << err_reader.value # Copying the behaviour of `popen` which merges stderr into output
-
- [cmd_output, status.exitstatus]
- rescue Timeout::Error => e
- kill_process_group_for_pid(pid)
-
- raise e
- ensure
- wout.close unless wout.closed?
- werr.close unless werr.closed?
-
- rout.close
- rerr.close
- end
- end
-
- def process_wait_with_timeout(pid, timeout)
- deadline = timeout.seconds.from_now
- wait_time = 0.01
-
- while deadline > Time.now
- sleep(wait_time)
- _, status = Process.wait2(pid, Process::WNOHANG)
-
- return status unless status.nil?
- end
-
- raise Timeout::Error, "Timeout waiting for process ##{pid}"
- end
-
- def kill_process_group_for_pid(pid)
- Process.kill("KILL", -pid)
- Process.wait(pid)
- rescue Errno::ESRCH
- end
- end
- end
-end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 9521a2d63a0..1b8d320ff3b 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -6,7 +6,6 @@ module Gitlab
module Git
class Repository
include Gitlab::Git::RepositoryMirroring
- include Gitlab::Git::Popen
include Gitlab::EncodingHelper
include Gitlab::Utils::StrongMemoize
@@ -73,7 +72,7 @@ module Gitlab
# Relative path of repo
attr_reader :relative_path
- attr_reader :gitlab_projects, :storage, :gl_repository, :relative_path
+ attr_reader :storage, :gl_repository, :relative_path
# This initializer method is only used on the client side (gitlab-ce).
# Gitaly-ruby uses a different initializer.
@@ -82,13 +81,6 @@ module Gitlab
@relative_path = relative_path
@gl_repository = gl_repository
- @gitlab_projects = Gitlab::Git::GitlabProjects.new(
- storage,
- relative_path,
- global_hooks_path: Gitlab.config.gitlab_shell.hooks_path,
- logger: Rails.logger
- )
-
@name = @relative_path.split("/").last
end
@@ -121,10 +113,6 @@ module Gitlab
raise NoRepository.new('no repository for such path')
end
- def cleanup
- @rugged&.close
- end
-
def circuit_breaker
@circuit_breaker ||= Gitlab::Git::Storage::CircuitBreaker.for_storage(storage)
end
@@ -148,10 +136,6 @@ module Gitlab
end
end
- def reload_rugged
- @rugged = nil
- end
-
# Directly find a branch with a simple name (e.g. master)
#
def find_branch(name)
@@ -250,15 +234,6 @@ module Gitlab
end
end
- # Returns an Array of all ref names, except when it's matching pattern
- #
- # regexp - The pattern for ref names we don't want
- def all_ref_names_except(prefixes)
- rugged.references.reject do |ref|
- prefixes.any? { |p| ref.name.start_with?(p) }
- end.map(&:name)
- end
-
def archive_metadata(ref, storage_path, project_path, format = "tar.gz", append_sha:)
ref ||= root_ref
commit = Gitlab::Git::Commit.find(self, ref)
@@ -331,7 +306,7 @@ module Gitlab
(size.to_f / 1024).round(2)
end
- # Use the Rugged Walker API to build an array of commits.
+ # Build an array of commits.
#
# Usage.
# repo.log(
@@ -591,19 +566,6 @@ module Gitlab
end
end
- def check_revert_content(target_commit, source_sha)
- args = [target_commit.sha, source_sha]
- args << { mainline: 1 } if target_commit.merge_commit?
-
- revert_index = rugged.revert_commit(*args)
- return false if revert_index.conflicts?
-
- tree_id = revert_index.write_tree(rugged)
- return false unless diff_exists?(source_sha, tree_id)
-
- tree_id
- end
-
def cherry_pick(user:, commit:, branch_name:, message:, start_branch_name:, start_repository:)
args = {
user: user,
@@ -619,10 +581,6 @@ module Gitlab
end
end
- def diff_exists?(sha1, sha2)
- rugged.diff(sha1, sha2).size > 0
- end
-
def user_to_committer(user)
Gitlab::Git.committer_hash(email: user.email, name: user.name)
end
@@ -666,6 +624,14 @@ module Gitlab
end
end
+ def find_remote_root_ref(remote_name)
+ return unless remote_name.present?
+
+ wrapped_gitaly_errors do
+ gitaly_remote_client.find_remote_root_ref(remote_name)
+ end
+ end
+
AUTOCRLF_VALUES = {
"true" => true,
"false" => false,
@@ -738,48 +704,6 @@ module Gitlab
end
end
- def with_repo_branch_commit(start_repository, start_branch_name)
- Gitlab::Git.check_namespace!(start_repository)
- start_repository = RemoteRepository.new(start_repository) unless start_repository.is_a?(RemoteRepository)
-
- return yield nil if start_repository.empty?
-
- if start_repository.same_repository?(self)
- yield commit(start_branch_name)
- else
- start_commit_id = start_repository.commit_id(start_branch_name)
-
- return yield nil unless start_commit_id
-
- if branch_commit = commit(start_commit_id)
- yield branch_commit
- else
- with_repo_tmp_commit(
- start_repository, start_branch_name, start_commit_id) do |tmp_commit|
- yield tmp_commit
- end
- end
- end
- end
-
- def with_repo_tmp_commit(start_repository, start_branch_name, sha)
- source_ref = start_branch_name
-
- unless Gitlab::Git.branch_ref?(source_ref)
- source_ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}#{source_ref}"
- end
-
- tmp_ref = fetch_ref(
- start_repository,
- source_ref: source_ref,
- target_ref: "refs/tmp/#{SecureRandom.hex}"
- )
-
- yield commit(sha)
- ensure
- delete_refs(tmp_ref) if tmp_ref
- end
-
def fetch_source_branch!(source_repository, source_branch, local_ref)
wrapped_gitaly_errors do
gitaly_repository_client.fetch_source_branch(source_repository, source_branch, local_ref)
@@ -809,21 +733,6 @@ module Gitlab
end
end
- # This method, fetch_ref, is used from within
- # Gitlab::Git::OperationService. OperationService will eventually only
- # exist in gitaly-ruby. When we delete OperationService from gitlab-ce
- # we can also remove fetch_ref.
- def fetch_ref(source_repository, source_ref:, target_ref:)
- Gitlab::Git.check_namespace!(source_repository)
- source_repository = RemoteRepository.new(source_repository) unless source_repository.is_a?(RemoteRepository)
-
- args = %W(fetch --no-tags -f #{GITALY_INTERNAL_URL} #{source_ref}:#{target_ref})
- message, status = run_git(args, env: source_repository.fetch_env)
- raise Gitlab::Git::CommandError, message if status != 0
-
- target_ref
- end
-
# Refactoring aid; allows us to copy code from app/models/repository.rb
def commit(ref = 'HEAD')
Gitlab::Git::Commit.find(self, ref)
@@ -891,24 +800,6 @@ module Gitlab
end
end
- def push_remote_branches(remote_name, branch_names, forced: true)
- success = @gitlab_projects.push_branches(remote_name, GITLAB_PROJECTS_TIMEOUT, forced, branch_names)
-
- success || gitlab_projects_error
- end
-
- def delete_remote_branches(remote_name, branch_names)
- success = @gitlab_projects.delete_remote_branches(remote_name, branch_names)
-
- success || gitlab_projects_error
- end
-
- def delete_remote_branches(remote_name, branch_names)
- success = @gitlab_projects.delete_remote_branches(remote_name, branch_names)
-
- success || gitlab_projects_error
- end
-
def bundle_to_disk(save_path)
wrapped_gitaly_errors do
gitaly_repository_client.create_bundle(save_path)
@@ -1056,37 +947,12 @@ module Gitlab
end
end
- def shell_blame(sha, path)
- output, _status = run_git(%W(blame -p #{sha} -- #{path}))
- output
- end
-
def last_commit_for_path(sha, path)
wrapped_gitaly_errors do
gitaly_commit_client.last_commit_for_path(sha, path)
end
end
- def rev_list(including: [], excluding: [], options: [], objects: false, &block)
- args = ['rev-list']
-
- args.push(*rev_list_param(including))
-
- exclude_param = *rev_list_param(excluding)
- if exclude_param.any?
- args.push('--not')
- args.push(*exclude_param)
- end
-
- args.push('--objects') if objects
-
- if options.any?
- args.push(*options)
- end
-
- run_git!(args, lazy_block: block)
- end
-
def checksum
# The exists? RPC is much cheaper, so we perform this request first
raise NoRepository, "Repository does not exists" unless exists?
@@ -1104,44 +970,6 @@ module Gitlab
end
end
- def run_git(args, chdir: path, env: {}, nice: false, lazy_block: nil, &block)
- cmd = [Gitlab.config.git.bin_path, *args]
- cmd.unshift("nice") if nice
-
- object_directories = alternate_object_directories
- if object_directories.any?
- env['GIT_ALTERNATE_OBJECT_DIRECTORIES'] = object_directories.join(File::PATH_SEPARATOR)
- end
-
- circuit_breaker.perform do
- popen(cmd, chdir, env, lazy_block: lazy_block, &block)
- end
- end
-
- def run_git!(args, chdir: path, env: {}, nice: false, lazy_block: nil, &block)
- output, status = run_git(args, chdir: chdir, env: env, nice: nice, lazy_block: lazy_block, &block)
-
- raise GitError, output unless status.zero?
-
- output
- end
-
- def run_git_with_timeout(args, timeout, env: {})
- circuit_breaker.perform do
- popen_with_timeout([Gitlab.config.git.bin_path, *args], timeout, path, env)
- end
- end
-
- def git_env_for_user(user)
- {
- 'GIT_COMMITTER_NAME' => user.name,
- 'GIT_COMMITTER_EMAIL' => user.email,
- 'GL_ID' => Gitlab::GlId.gl_id(user),
- 'GL_PROTOCOL' => Gitlab::Git::Hook::GL_PROTOCOL,
- 'GL_REPOSITORY' => gl_repository
- }
- end
-
def gitaly_merged_branch_names(branch_names, root_sha)
qualified_branch_names = branch_names.map { |b| "refs/heads/#{b}" }
@@ -1192,23 +1020,6 @@ module Gitlab
Gitlab::Git::HookEnv.all(gl_repository).values_at(*ALLOWED_OBJECT_RELATIVE_DIRECTORIES_VARIABLES).flatten.compact
end
- def sort_branches(branches, sort_by)
- case sort_by
- when 'name'
- branches.sort_by(&:name)
- when 'updated_desc'
- branches.sort do |a, b|
- b.dereferenced_target.committed_date <=> a.dereferenced_target.committed_date
- end
- when 'updated_asc'
- branches.sort do |a, b|
- a.dereferenced_target.committed_date <=> b.dereferenced_target.committed_date
- end
- else
- branches
- end
- end
-
# Returns true if the given ref name exists
#
# Ref names must start with `refs/`.
@@ -1223,14 +1034,6 @@ module Gitlab
def gitaly_delete_refs(*ref_names)
gitaly_ref_client.delete_refs(refs: ref_names) if ref_names.any?
end
-
- def gitlab_projects_error
- raise CommandError, @gitlab_projects.output
- end
-
- def rev_list_param(spec)
- spec == :all ? ['--all'] : spec
- end
end
end
end
diff --git a/lib/gitlab/git/tree.rb b/lib/gitlab/git/tree.rb
index cb851b76a23..e0867aeb5a7 100644
--- a/lib/gitlab/git/tree.rb
+++ b/lib/gitlab/git/tree.rb
@@ -50,51 +50,6 @@ module Gitlab
entry[:oid]
end
end
-
- def tree_entries_from_rugged(repository, sha, path, recursive)
- current_path_entries = get_tree_entries_from_rugged(repository, sha, path)
- ordered_entries = []
-
- current_path_entries.each do |entry|
- ordered_entries << entry
-
- if recursive && entry.dir?
- ordered_entries.concat(tree_entries_from_rugged(repository, sha, entry.path, true))
- end
- end
-
- ordered_entries
- end
-
- def get_tree_entries_from_rugged(repository, sha, path)
- commit = repository.lookup(sha)
- root_tree = commit.tree
-
- tree = if path
- id = find_id_by_path(repository, root_tree.oid, path)
- if id
- repository.lookup(id)
- else
- []
- end
- else
- root_tree
- end
-
- tree.map do |entry|
- new(
- id: entry[:oid],
- root_id: root_tree.oid,
- name: entry[:name],
- type: entry[:type],
- mode: entry[:filemode].to_s(8),
- path: path ? File.join(path, entry[:name]) : entry[:name],
- commit_id: sha
- )
- end
- rescue Rugged::ReferenceError
- []
- end
end
def initialize(options)
diff --git a/lib/gitlab/git/version.rb b/lib/gitlab/git/version.rb
index 1e14e8b652a..4bd91898457 100644
--- a/lib/gitlab/git/version.rb
+++ b/lib/gitlab/git/version.rb
@@ -1,8 +1,6 @@
module Gitlab
module Git
module Version
- extend Gitlab::Git::Popen
-
def self.git_version
Gitlab::VersionInfo.parse(Gitaly::Server.all.first.git_binary_version)
end
diff --git a/lib/gitlab/git/wiki.rb b/lib/gitlab/git/wiki.rb
index 9d992be66eb..ae92a624e05 100644
--- a/lib/gitlab/git/wiki.rb
+++ b/lib/gitlab/git/wiki.rb
@@ -163,20 +163,6 @@ module Gitlab
Gitlab::Git::WikiPage.new(wiki_page, version)
end
end
-
- def committer_with_hooks(commit_details)
- Gitlab::Git::CommitterWithHooks.new(self, commit_details.to_h)
- end
-
- def with_committer_with_hooks(commit_details, &block)
- committer = committer_with_hooks(commit_details)
-
- yield committer
-
- committer.commit
-
- nil
- end
end
end
end
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index 93720500711..30cd09a0ca7 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -241,8 +241,6 @@ module Gitlab
end
elsif user
# User access is verified in check_change_access!
- elsif authed_via_jwt?
- # Authenticated via JWT
else
raise UnauthorizedError, ERROR_MESSAGES[:upload]
end
@@ -331,10 +329,6 @@ module Gitlab
!Gitlab.config.gitlab_shell.receive_pack
end
- def authed_via_jwt?
- false
- end
-
protected
def changes_list
diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb
index 6a97cd8ed17..aa5b4f94090 100644
--- a/lib/gitlab/gitaly_client/commit_service.rb
+++ b/lib/gitlab/gitaly_client/commit_service.rb
@@ -369,7 +369,7 @@ module Gitlab
request_params[:ignore_whitespace_change] = options.fetch(:ignore_whitespace_change, false)
request_params[:enforce_limits] = options.fetch(:limits, true)
request_params[:collapse_diffs] = !options.fetch(:expanded, true)
- request_params.merge!(Gitlab::Git::DiffCollection.collection_limits(options).to_h)
+ request_params.merge!(Gitlab::Git::DiffCollection.limits(options).to_h)
request = Gitaly::CommitDiffRequest.new(request_params)
response = GitalyClient.call(@repository.storage, :diff_service, :commit_diff, request, timeout: GitalyClient.medium_timeout)
diff --git a/lib/gitlab/gitaly_client/remote_service.rb b/lib/gitlab/gitaly_client/remote_service.rb
index 1381e033d4b..4661448621b 100644
--- a/lib/gitlab/gitaly_client/remote_service.rb
+++ b/lib/gitlab/gitaly_client/remote_service.rb
@@ -1,6 +1,8 @@
module Gitlab
module GitalyClient
class RemoteService
+ include Gitlab::EncodingHelper
+
MAX_MSG_SIZE = 128.kilobytes.freeze
def self.exists?(remote_url)
@@ -52,6 +54,18 @@ module Gitlab
response.result
end
+ def find_remote_root_ref(remote_name)
+ request = Gitaly::FindRemoteRootRefRequest.new(
+ repository: @gitaly_repo,
+ remote: remote_name
+ )
+
+ response = GitalyClient.call(@storage, :remote_service,
+ :find_remote_root_ref, request)
+
+ encode_utf8(response.ref)
+ end
+
def update_remote_mirror(ref_name, only_branches_matching)
req_enum = Enumerator.new do |y|
y.yield Gitaly::UpdateRemoteMirrorRequest.new(
diff --git a/lib/gitlab/gitaly_client/storage_service.rb b/lib/gitlab/gitaly_client/storage_service.rb
index eb0e910665b..3a26dd58ff4 100644
--- a/lib/gitlab/gitaly_client/storage_service.rb
+++ b/lib/gitlab/gitaly_client/storage_service.rb
@@ -5,6 +5,14 @@ module Gitlab
@storage = storage
end
+ # Returns all directories in the git storage directory, lexically ordered
+ def list_directories(depth: 1)
+ request = Gitaly::ListDirectoriesRequest.new(storage_name: @storage, depth: depth)
+
+ GitalyClient.call(@storage, :storage_service, :list_directories, request)
+ .flat_map(&:paths)
+ end
+
# Delete all repositories in the storage. This is a slow and VERY DESTRUCTIVE operation.
def delete_all_repositories
request = Gitaly::DeleteAllRepositoriesRequest.new(storage_name: @storage)
diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb
index be3710c5b7f..53fe2f8e436 100644
--- a/lib/gitlab/import_export.rb
+++ b/lib/gitlab/import_export.rb
@@ -40,10 +40,6 @@ module Gitlab
"#{basename[0..FILENAME_LIMIT]}_export.tar.gz"
end
- def object_storage?
- Feature.enabled?(:import_export_object_storage)
- end
-
def version
VERSION
end
diff --git a/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy.rb b/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy.rb
index 83134bb0769..7cbf653dd97 100644
--- a/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy.rb
+++ b/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy.rb
@@ -53,7 +53,7 @@ module Gitlab
end
def self.lock_file_path(project)
- return unless project.export_path || object_storage?
+ return unless project.export_path || export_file_exists?
lock_path = project.import_export_shared.archive_path
@@ -83,8 +83,8 @@ module Gitlab
errors.full_messages.each { |msg| project.import_export_shared.add_error_message(msg) }
end
- def object_storage?
- project.export_project_object_exists?
+ def export_file_exists?
+ project.export_file_exists?
end
end
end
diff --git a/lib/gitlab/import_export/after_export_strategies/web_upload_strategy.rb b/lib/gitlab/import_export/after_export_strategies/web_upload_strategy.rb
index dce8f89c0ab..4f29bdcea2c 100644
--- a/lib/gitlab/import_export/after_export_strategies/web_upload_strategy.rb
+++ b/lib/gitlab/import_export/after_export_strategies/web_upload_strategy.rb
@@ -23,7 +23,7 @@ module Gitlab
def strategy_execute
handle_response_error(send_file)
- project.remove_exported_project_file
+ project.remove_exports
end
def handle_response_error(response)
@@ -40,15 +40,11 @@ module Gitlab
def send_file
Gitlab::HTTP.public_send(http_method.downcase, url, send_file_options) # rubocop:disable GitlabSecurity/PublicSend
ensure
- export_file.close if export_file && !object_storage?
+ export_file.close if export_file
end
def export_file
- if object_storage?
- project.import_export_upload.export_file.file.open
- else
- File.open(project.export_project_path)
- end
+ project.export_file.open
end
def send_file_options
@@ -63,11 +59,7 @@ module Gitlab
end
def export_size
- if object_storage?
- project.import_export_upload.export_file.file.size
- else
- File.size(project.export_project_path)
- end
+ project.export_file.file.size
end
end
end
diff --git a/lib/gitlab/import_export/avatar_restorer.rb b/lib/gitlab/import_export/avatar_restorer.rb
index cfa595629f4..17796430811 100644
--- a/lib/gitlab/import_export/avatar_restorer.rb
+++ b/lib/gitlab/import_export/avatar_restorer.rb
@@ -19,7 +19,7 @@ module Gitlab
private
def avatar_export_file
- @avatar_export_file ||= Dir["#{avatar_export_path}/*"].first
+ @avatar_export_file ||= Dir["#{avatar_export_path}/**/*"].find { |f| File.file?(f) }
end
def avatar_export_path
diff --git a/lib/gitlab/import_export/avatar_saver.rb b/lib/gitlab/import_export/avatar_saver.rb
index 31ef0490cb3..6ffebf83dd2 100644
--- a/lib/gitlab/import_export/avatar_saver.rb
+++ b/lib/gitlab/import_export/avatar_saver.rb
@@ -1,8 +1,6 @@
module Gitlab
module ImportExport
class AvatarSaver
- include Gitlab::ImportExport::CommandLineUtil
-
def initialize(project:, shared:)
@project = project
@shared = shared
@@ -14,19 +12,12 @@ module Gitlab
Gitlab::ImportExport::UploadsManager.new(
project: @project,
shared: @shared,
- relative_export_path: 'avatar',
- from: avatar_path
+ relative_export_path: 'avatar'
).save
rescue => e
@shared.error(e)
false
end
-
- private
-
- def avatar_path
- @project.avatar.path
- end
end
end
end
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index f69f98a78a3..a19b3c88627 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -19,6 +19,9 @@ project_tree:
- milestone:
- events:
- :push_event_payload
+ - resource_label_events:
+ - label:
+ :priorities
- :issue_assignees
- snippets:
- :award_emoji
@@ -45,6 +48,9 @@ project_tree:
- milestone:
- events:
- :push_event_payload
+ - resource_label_events:
+ - label:
+ :priorities
- pipelines:
- notes:
- :author
@@ -64,6 +70,7 @@ project_tree:
- :create_access_levels
- :project_feature
- :custom_attributes
+ - :prometheus_metrics
- :project_badges
- :ci_cd_settings
@@ -108,6 +115,9 @@ excluded_attributes:
- :remote_mirror_available_overridden
- :description_html
- :repository_languages
+ prometheus_metrics:
+ - :common
+ - :identifier
snippets:
- :expired_at
merge_request_diff:
@@ -133,6 +143,10 @@ excluded_attributes:
- :event_id
project_badges:
- :group_id
+ resource_label_events:
+ - :reference
+ - :reference_html
+ - :epic_id
methods:
labels:
diff --git a/lib/gitlab/import_export/importer.rb b/lib/gitlab/import_export/importer.rb
index 4e179f63d8c..72d5b9b830c 100644
--- a/lib/gitlab/import_export/importer.rb
+++ b/lib/gitlab/import_export/importer.rb
@@ -92,8 +92,6 @@ module Gitlab
end
def remove_import_file
- return unless Gitlab::ImportExport.object_storage?
-
upload = @project.import_export_upload
return unless upload&.import_file&.file
diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb
index f4106e03a57..00ea4b833e2 100644
--- a/lib/gitlab/import_export/project_tree_restorer.rb
+++ b/lib/gitlab/import_export/project_tree_restorer.rb
@@ -199,7 +199,7 @@ module Gitlab
end
def excluded_keys_for_relation(relation)
- @reader.attributes_finder.find_excluded_keys(relation)
+ reader.attributes_finder.find_excluded_keys(relation)
end
end
end
diff --git a/lib/gitlab/import_export/saver.rb b/lib/gitlab/import_export/saver.rb
index 3cd153a4fd2..59a74083395 100644
--- a/lib/gitlab/import_export/saver.rb
+++ b/lib/gitlab/import_export/saver.rb
@@ -18,7 +18,7 @@ module Gitlab
Rails.logger.info("Saved project export #{archive_file}")
- save_on_object_storage if use_object_storage?
+ save_upload
else
@shared.error(Gitlab::ImportExport::Error.new(error_message))
false
@@ -27,10 +27,8 @@ module Gitlab
@shared.error(e)
false
ensure
- if use_object_storage?
- remove_archive
- remove_export_path
- end
+ remove_archive
+ remove_export_path
end
private
@@ -51,7 +49,7 @@ module Gitlab
@archive_file ||= File.join(@shared.archive_path, Gitlab::ImportExport.export_filename(project: @project))
end
- def save_on_object_storage
+ def save_upload
upload = ImportExportUpload.find_or_initialize_by(project: @project)
File.open(archive_file) { |file| upload.export_file = file }
@@ -59,12 +57,8 @@ module Gitlab
upload.save!
end
- def use_object_storage?
- Gitlab::ImportExport.object_storage?
- end
-
def error_message
- "Unable to save #{archive_file} into #{@shared.export_path}. Object storage enabled: #{use_object_storage?}"
+ "Unable to save #{archive_file} into #{@shared.export_path}."
end
end
end
diff --git a/lib/gitlab/import_export/uploads_manager.rb b/lib/gitlab/import_export/uploads_manager.rb
index e0d4235e65b..8511319cb1c 100644
--- a/lib/gitlab/import_export/uploads_manager.rb
+++ b/lib/gitlab/import_export/uploads_manager.rb
@@ -5,18 +5,13 @@ module Gitlab
UPLOADS_BATCH_SIZE = 100
- def initialize(project:, shared:, relative_export_path: 'uploads', from: nil)
+ def initialize(project:, shared:, relative_export_path: 'uploads')
@project = project
@shared = shared
@relative_export_path = relative_export_path
- @from = from || default_uploads_path
end
def save
- if File.file?(@from) && @relative_export_path == 'avatar'
- copy_files(@from, File.join(uploads_export_path, @project.avatar.filename))
- end
-
copy_project_uploads
true
@@ -55,17 +50,11 @@ module Gitlab
copy_files(uploader.absolute_path, File.join(uploads_export_path, uploader.upload.path))
else
- next unless Gitlab::ImportExport.object_storage?
-
download_and_copy(uploader)
end
end
end
- def default_uploads_path
- FileUploader.absolute_base_dir(@project)
- end
-
def uploads_export_path
@uploads_export_path ||= File.join(@shared.export_path, @relative_export_path)
end
diff --git a/lib/gitlab/import_export/uploads_restorer.rb b/lib/gitlab/import_export/uploads_restorer.rb
index 25f85936227..b4313ff4cb4 100644
--- a/lib/gitlab/import_export/uploads_restorer.rb
+++ b/lib/gitlab/import_export/uploads_restorer.rb
@@ -2,30 +2,14 @@ module Gitlab
module ImportExport
class UploadsRestorer < UploadsSaver
def restore
- if Gitlab::ImportExport.object_storage?
- Gitlab::ImportExport::UploadsManager.new(
- project: @project,
- shared: @shared
- ).restore
- elsif File.directory?(uploads_export_path)
- copy_files(uploads_export_path, uploads_path)
-
- true
- else
- true # Proceed without uploads
- end
+ Gitlab::ImportExport::UploadsManager.new(
+ project: @project,
+ shared: @shared
+ ).restore
rescue => e
@shared.error(e)
false
end
-
- def uploads_path
- FileUploader.absolute_base_dir(@project)
- end
-
- def uploads_export_path
- @uploads_export_path ||= File.join(@shared.export_path, 'uploads')
- end
end
end
end
diff --git a/lib/gitlab/import_export/uploads_saver.rb b/lib/gitlab/import_export/uploads_saver.rb
index b3f17af5661..0275f686c5e 100644
--- a/lib/gitlab/import_export/uploads_saver.rb
+++ b/lib/gitlab/import_export/uploads_saver.rb
@@ -1,8 +1,6 @@
module Gitlab
module ImportExport
class UploadsSaver
- include Gitlab::ImportExport::CommandLineUtil
-
def initialize(project:, shared:)
@project = project
@shared = shared
diff --git a/lib/gitlab/project_service_logger.rb b/lib/gitlab/project_service_logger.rb
new file mode 100644
index 00000000000..e84dca97962
--- /dev/null
+++ b/lib/gitlab/project_service_logger.rb
@@ -0,0 +1,7 @@
+module Gitlab
+ class ProjectServiceLogger < Gitlab::JsonLogger
+ def self.file_name_noext
+ 'integrations_json'
+ end
+ end
+end
diff --git a/lib/gitlab/prometheus/additional_metrics_parser.rb b/lib/gitlab/prometheus/additional_metrics_parser.rb
index bb1172f82a1..a240d090074 100644
--- a/lib/gitlab/prometheus/additional_metrics_parser.rb
+++ b/lib/gitlab/prometheus/additional_metrics_parser.rb
@@ -5,7 +5,7 @@ module Gitlab
MUTEX = Mutex.new
extend self
- def load_groups_from_yaml(file_name = 'additional_metrics.yml')
+ def load_groups_from_yaml(file_name)
yaml_metrics_raw(file_name).map(&method(:group_from_entry))
end
diff --git a/lib/gitlab/prometheus/metric_group.rb b/lib/gitlab/prometheus/metric_group.rb
index e91c6fb2e27..d696a8fc00c 100644
--- a/lib/gitlab/prometheus/metric_group.rb
+++ b/lib/gitlab/prometheus/metric_group.rb
@@ -4,10 +4,13 @@ module Gitlab
include ActiveModel::Model
attr_accessor :name, :priority, :metrics
+
validates :name, :priority, :metrics, presence: true
def self.common_metrics
- AdditionalMetricsParser.load_groups_from_yaml
+ ::PrometheusMetric.common.group_by(&:group_title).map do |name, metrics|
+ MetricGroup.new(name: name, priority: 0, metrics: metrics.map(&:to_query_metric))
+ end
end
# EE only
diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb
index a17cd27e82d..c8fa50757f6 100644
--- a/lib/gitlab/shell.rb
+++ b/lib/gitlab/shell.rb
@@ -368,15 +368,6 @@ module Gitlab
private
- def gitlab_projects(shard_name, disk_path)
- Gitlab::Git::GitlabProjects.new(
- shard_name,
- disk_path,
- global_hooks_path: Gitlab.config.gitlab_shell.hooks_path,
- logger: Rails.logger
- )
- end
-
def gitlab_shell_fast_execute(cmd)
output, status = gitlab_shell_fast_execute_helper(cmd)
diff --git a/lib/gitlab/template_helper.rb b/lib/gitlab/template_helper.rb
index f24a01e6cf5..fc498dde723 100644
--- a/lib/gitlab/template_helper.rb
+++ b/lib/gitlab/template_helper.rb
@@ -1,24 +1,9 @@
module Gitlab
module TemplateHelper
- include Gitlab::Utils::StrongMemoize
-
def prepare_template_environment(file)
return unless file
- if Gitlab::ImportExport.object_storage?
- params[:import_export_upload] = ImportExportUpload.new(import_file: file)
- else
- FileUtils.mkdir_p(File.dirname(import_upload_path))
- FileUtils.copy_entry(file.path, import_upload_path)
-
- params[:import_source] = import_upload_path
- end
- end
-
- def import_upload_path
- strong_memoize(:import_upload_path) do
- Gitlab::ImportExport.import_upload_path(filename: tmp_filename)
- end
+ params[:import_export_upload] = ImportExportUpload.new(import_file: file)
end
def tmp_filename
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index a9629a92a50..30a8c3578d8 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -22,18 +22,27 @@ module Gitlab
project = repository.project
- {
+ attrs = {
GL_ID: Gitlab::GlId.gl_id(user),
GL_REPOSITORY: Gitlab::GlRepository.gl_repository(project, is_wiki),
GL_USERNAME: user&.username,
ShowAllRefs: show_all_refs,
Repository: repository.gitaly_repository.to_h,
RepoPath: 'ignored but not allowed to be empty in gitlab-workhorse',
+ GitConfigOptions: [],
GitalyServer: {
address: Gitlab::GitalyClient.address(project.repository_storage),
token: Gitlab::GitalyClient.token(project.repository_storage)
}
}
+
+ # Custom option for git-receive-pack command
+ receive_max_input_size = Gitlab::CurrentSettings.receive_max_input_size.to_i
+ if receive_max_input_size > 0
+ attrs[:GitConfigOptions] << "receive.maxInputSize=#{receive_max_input_size.megabytes}"
+ end
+
+ attrs
end
def send_git_blob(repository, blob)
diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake
index c8a8863443e..e8ae5dfa540 100644
--- a/lib/tasks/gitlab/cleanup.rake
+++ b/lib/tasks/gitlab/cleanup.rake
@@ -1,40 +1,29 @@
-# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/954
-#
+# frozen_string_literal: true
+require 'set'
+
namespace :gitlab do
namespace :cleanup do
- HASHED_REPOSITORY_NAME = '@hashed'.freeze
-
desc "GitLab | Cleanup | Clean namespaces"
task dirs: :gitlab_environment do
- warn_user_is_not_gitlab
+ namespaces = Set.new(Namespace.pluck(:path))
+ namespaces << Storage::HashedProject::ROOT_PATH_PREFIX
- namespaces = Namespace.pluck(:path)
- namespaces << HASHED_REPOSITORY_NAME # add so that it will be ignored
- Gitlab.config.repositories.storages.each do |name, repository_storage|
- git_base_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access { repository_storage.legacy_disk_path }
- all_dirs = Dir.glob(git_base_path + '/*')
+ Gitaly::Server.all.each do |server|
+ all_dirs = Gitlab::GitalyClient::StorageService
+ .new(server.storage)
+ .list_directories(depth: 0)
+ .reject { |dir| dir.ends_with?('.git') || namespaces.include?(File.basename(dir)) }
- puts git_base_path.color(:yellow)
puts "Looking for directories to remove... "
-
- all_dirs.reject! do |dir|
- # skip if git repo
- dir =~ /.git$/
- end
-
- all_dirs.reject! do |dir|
- dir_name = File.basename dir
-
- # skip if namespace present
- namespaces.include?(dir_name)
- end
-
all_dirs.each do |dir_path|
if remove?
- if FileUtils.rm_rf dir_path
- puts "Removed...#{dir_path}".color(:red)
- else
- puts "Cannot remove #{dir_path}".color(:red)
+ begin
+ Gitlab::GitalyClient::NamespaceService.new(server.storage)
+ .remove(dir_path)
+
+ puts "Removed...#{dir_path}"
+ rescue StandardError => e
+ puts "Cannot remove #{dir_path}: #{e.message}".color(:red)
end
else
puts "Can be removed: #{dir_path}".color(:red)
@@ -49,29 +38,29 @@ namespace :gitlab do
desc "GitLab | Cleanup | Clean repositories"
task repos: :gitlab_environment do
- warn_user_is_not_gitlab
-
move_suffix = "+orphaned+#{Time.now.to_i}"
- Gitlab.config.repositories.storages.each do |name, repository_storage|
- repo_root = Gitlab::GitalyClient::StorageSettings.allow_disk_access { repository_storage.legacy_disk_path }
-
- # Look for global repos (legacy, depth 1) and normal repos (depth 2)
- IO.popen(%W(find #{repo_root} -mindepth 1 -maxdepth 2 -name *.git)) do |find|
- find.each_line do |path|
- path.chomp!
- repo_with_namespace = path
- .sub(repo_root, '')
- .sub(%r{^/*}, '')
- .chomp('.git')
- .chomp('.wiki')
-
- # TODO ignoring hashed repositories for now. But revisit to fully support
- # possible orphaned hashed repos
- next if repo_with_namespace.start_with?("#{HASHED_REPOSITORY_NAME}/") || Project.find_by_full_path(repo_with_namespace)
-
- new_path = path + move_suffix
- puts path.inspect + ' -> ' + new_path.inspect
- File.rename(path, new_path)
+
+ Gitaly::Server.all.each do |server|
+ Gitlab::GitalyClient::StorageService
+ .new(server.storage)
+ .list_directories
+ .each do |path|
+ repo_with_namespace = path.chomp('.git').chomp('.wiki')
+
+ # TODO ignoring hashed repositories for now. But revisit to fully support
+ # possible orphaned hashed repos
+ next if repo_with_namespace.start_with?(Storage::HashedProject::ROOT_PATH_PREFIX)
+ next if Project.find_by_full_path(repo_with_namespace)
+
+ new_path = path + move_suffix
+ puts path.inspect + ' -> ' + new_path.inspect
+
+ begin
+ Gitlab::GitalyClient::NamespaceService
+ .new(server.storage)
+ .rename(path, new_path)
+ rescue StandardError => e
+ puts "Error occured while moving the repository: #{e.message}".color(:red)
end
end
end
diff --git a/lib/tasks/gitlab/update_templates.rake b/lib/tasks/gitlab/update_templates.rake
index a25f7ce59c7..ef6a32d6730 100644
--- a/lib/tasks/gitlab/update_templates.rake
+++ b/lib/tasks/gitlab/update_templates.rake
@@ -6,6 +6,8 @@ namespace :gitlab do
desc "GitLab | Update project templates"
task :update_project_templates do
+ include Gitlab::ImportExport::CommandLineUtil
+
if Rails.env.production?
puts "This rake task is not meant fo production instances".red
exit(1)
@@ -52,7 +54,7 @@ namespace :gitlab do
end
Projects::ImportExport::ExportService.new(project, admin).execute
- FileUtils.cp(project.export_project_path, template.archive_path)
+ download_or_copy_upload(project.export_file, template.archive_path)
Projects::DestroyService.new(admin, project).execute
puts "Exported #{template.name}".green
end
diff --git a/lib/tasks/migrate/add_limits_mysql.rake b/lib/tasks/migrate/add_limits_mysql.rake
index 9b05876034c..c77fa49d586 100644
--- a/lib/tasks/migrate/add_limits_mysql.rake
+++ b/lib/tasks/migrate/add_limits_mysql.rake
@@ -3,6 +3,7 @@ require Rails.root.join('db/migrate/markdown_cache_limits_to_mysql')
require Rails.root.join('db/migrate/merge_request_diff_file_limits_to_mysql')
require Rails.root.join('db/migrate/limits_ci_build_trace_chunks_raw_data_for_mysql')
require Rails.root.join('db/migrate/gpg_keys_limits_to_mysql')
+require Rails.root.join('db/migrate/prometheus_metrics_limits_to_mysql')
desc "GitLab | Add limits to strings in mysql database"
task add_limits_mysql: :environment do
@@ -12,4 +13,5 @@ task add_limits_mysql: :environment do
MergeRequestDiffFileLimitsToMysql.new.up
LimitsCiBuildTraceChunksRawDataForMysql.new.up
IncreaseMysqlTextLimitForGpgKeys.new.up
+ PrometheusMetricsLimitsToMysql.new.up
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 00f9c6aa95a..7b6c15abd4f 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -259,6 +259,9 @@ msgstr ""
msgid "A default branch cannot be chosen for an empty project."
msgstr ""
+msgid "A deleted user"
+msgstr ""
+
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr ""
@@ -295,6 +298,9 @@ msgstr ""
msgid "Access denied! Please verify you can add deploy keys to this repository."
msgstr ""
+msgid "Access expiration date"
+msgstr ""
+
msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
msgstr ""
@@ -604,6 +610,9 @@ msgstr ""
msgid "Archived project! Repository and other project resources are read-only"
msgstr ""
+msgid "Archived projects"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr ""
@@ -739,6 +748,9 @@ msgstr ""
msgid "AutoDevOps|Learn more in the %{link_to_documentation}"
msgstr ""
+msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
+msgstr ""
+
msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
msgstr ""
@@ -1026,6 +1038,9 @@ msgstr ""
msgid "Browse files"
msgstr ""
+msgid "Business metrics (Custom)"
+msgstr ""
+
msgid "ByAuthor|by"
msgstr ""
@@ -1158,6 +1173,12 @@ msgstr ""
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
msgstr ""
+msgid "Choose a template..."
+msgstr ""
+
+msgid "Choose a type..."
+msgstr ""
+
msgid "Choose any color."
msgstr ""
@@ -1332,10 +1353,7 @@ msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
msgstr ""
-msgid "ClusterIntegration|Choose which of your project's environments will use this Kubernetes cluster."
-msgstr ""
-
-msgid "ClusterIntegration|Control how your Kubernetes cluster integrates with GitLab"
+msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
msgid "ClusterIntegration|Copy API URL"
@@ -1362,6 +1380,9 @@ msgstr ""
msgid "ClusterIntegration|Did you know?"
msgstr ""
+msgid "ClusterIntegration|Enable or disable GitLab's connection to your Kubernetes cluster."
+msgstr ""
+
msgid "ClusterIntegration|Enable this setting if using role-based access control (RBAC)."
msgstr ""
@@ -1455,15 +1476,6 @@ msgstr ""
msgid "ClusterIntegration|Kubernetes cluster integration"
msgstr ""
-msgid "ClusterIntegration|Kubernetes cluster integration is disabled for this project."
-msgstr ""
-
-msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project."
-msgstr ""
-
-msgid "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."
-msgstr ""
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr ""
@@ -1488,12 +1500,6 @@ msgstr ""
msgid "ClusterIntegration|Learn more about %{help_link_start}zones%{help_link_end}."
msgstr ""
-msgid "ClusterIntegration|Learn more about environments"
-msgstr ""
-
-msgid "ClusterIntegration|Learn more about security configuration"
-msgstr ""
-
msgid "ClusterIntegration|Machine type"
msgstr ""
@@ -1581,9 +1587,6 @@ msgstr ""
msgid "ClusterIntegration|Search zones"
msgstr ""
-msgid "ClusterIntegration|Security"
-msgstr ""
-
msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster"
msgstr ""
@@ -1623,7 +1626,7 @@ msgstr ""
msgid "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."
msgstr ""
-msgid "ClusterIntegration|The default cluster configuration grants access to a wide set of functionalities needed to successfully build and deploy a containerised application."
+msgid "ClusterIntegration|The default cluster configuration grants access to many functionalities needed to successfully build and deploy a containerised application."
msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
@@ -1891,6 +1894,9 @@ msgstr ""
msgid "Contribution guide"
msgstr ""
+msgid "Contributions for <strong>%{calendar_date}</strong>"
+msgstr ""
+
msgid "Contributors"
msgstr ""
@@ -2301,9 +2307,21 @@ msgstr ""
msgid "Disable group Runners"
msgstr ""
+msgid "Discard"
+msgstr ""
+
+msgid "Discard all changes"
+msgstr ""
+
+msgid "Discard all unstaged changes?"
+msgstr ""
+
msgid "Discard changes"
msgstr ""
+msgid "Discard changes to %{path}?"
+msgstr ""
+
msgid "Discard draft"
msgstr ""
@@ -2637,6 +2655,9 @@ msgstr ""
msgid "Expand sidebar"
msgstr ""
+msgid "Expiration date"
+msgstr ""
+
msgid "Explore"
msgstr ""
@@ -2697,6 +2718,9 @@ msgstr ""
msgid "Fields on this page are now uneditable, you can configure"
msgstr ""
+msgid "File templates"
+msgstr ""
+
msgid "Files"
msgstr ""
@@ -2709,6 +2733,9 @@ msgstr ""
msgid "Filter by commit message"
msgstr ""
+msgid "Filter..."
+msgstr ""
+
msgid "Find by path"
msgstr ""
@@ -3000,6 +3027,9 @@ msgstr ""
msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group."
msgstr ""
+msgid "GroupsTree|Are you sure you want to leave the \"%{fullName}\" group?"
+msgstr ""
+
msgid "GroupsTree|Create a project in this group."
msgstr ""
@@ -3012,19 +3042,19 @@ msgstr ""
msgid "GroupsTree|Failed to leave the group. Please make sure you are not the only owner."
msgstr ""
-msgid "GroupsTree|Filter by name..."
-msgstr ""
-
msgid "GroupsTree|Leave this group"
msgstr ""
msgid "GroupsTree|Loading groups"
msgstr ""
-msgid "GroupsTree|Sorry, no groups matched your search"
+msgid "GroupsTree|No groups matched your search"
msgstr ""
-msgid "GroupsTree|Sorry, no groups or projects matched your search"
+msgid "GroupsTree|No groups or projects matched your search"
+msgstr ""
+
+msgid "GroupsTree|Search by name"
msgstr ""
msgid "Health Check"
@@ -3260,6 +3290,9 @@ msgstr ""
msgid "Introducing Cycle Analytics"
msgstr ""
+msgid "Invite"
+msgstr ""
+
msgid "Issue Boards"
msgstr ""
@@ -3597,6 +3630,9 @@ msgstr ""
msgid "Markdown enabled"
msgstr ""
+msgid "Max access level"
+msgstr ""
+
msgid "Maximum git storage failures"
msgstr ""
@@ -3759,9 +3795,6 @@ msgstr ""
msgid "More"
msgstr ""
-msgid "More actions"
-msgstr ""
-
msgid "More information"
msgstr ""
@@ -3902,6 +3935,9 @@ msgstr ""
msgid "No container images stored for this project. Add one by following the instructions above."
msgstr ""
+msgid "No contributions were found"
+msgstr ""
+
msgid "No due date"
msgstr ""
@@ -3974,6 +4010,9 @@ msgstr ""
msgid "Not enough data"
msgstr ""
+msgid "Not now"
+msgstr ""
+
msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
msgstr ""
@@ -4438,6 +4477,9 @@ msgstr ""
msgid "Profiles| You are going to change the username %{currentUsernameBold} to %{newUsernameBold}. Profile and projects will be redirected to the %{newUsername} namespace but this redirect will expire once the %{currentUsername} namespace is registered by another user or group. Please update your Git repository remotes as soon as possible."
msgstr ""
+msgid "Profiles|%{author_name} made a private contribution"
+msgstr ""
+
msgid "Profiles|Account scheduled for removal."
msgstr ""
@@ -4447,15 +4489,30 @@ msgstr ""
msgid "Profiles|Add status emoji"
msgstr ""
+msgid "Profiles|Avatar cropper"
+msgstr ""
+
+msgid "Profiles|Avatar will be removed. Are you sure?"
+msgstr ""
+
msgid "Profiles|Change username"
msgstr ""
+msgid "Profiles|Choose file..."
+msgstr ""
+
+msgid "Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information."
+msgstr ""
+
msgid "Profiles|Clear status"
msgstr ""
msgid "Profiles|Current path: %{path}"
msgstr ""
+msgid "Profiles|Current status"
+msgstr ""
+
msgid "Profiles|Delete Account"
msgstr ""
@@ -4468,39 +4525,108 @@ msgstr ""
msgid "Profiles|Deleting an account has the following effects:"
msgstr ""
+msgid "Profiles|Do not show on profile"
+msgstr ""
+
+msgid "Profiles|Don't display activity-related personal information on your profiles"
+msgstr ""
+
+msgid "Profiles|Edit Profile"
+msgstr ""
+
msgid "Profiles|Invalid password"
msgstr ""
msgid "Profiles|Invalid username"
msgstr ""
+msgid "Profiles|Main settings"
+msgstr ""
+
+msgid "Profiles|No file chosen"
+msgstr ""
+
msgid "Profiles|Path"
msgstr ""
+msgid "Profiles|Position and size your new avatar"
+msgstr ""
+
+msgid "Profiles|Private contributions"
+msgstr ""
+
+msgid "Profiles|Public Avatar"
+msgstr ""
+
+msgid "Profiles|Remove avatar"
+msgstr ""
+
+msgid "Profiles|Set new profile picture"
+msgstr ""
+
+msgid "Profiles|Some options are unavailable for LDAP accounts"
+msgstr ""
+
+msgid "Profiles|Tell us about yourself in fewer than 250 characters."
+msgstr ""
+
+msgid "Profiles|The maximum file size allowed is 200KB."
+msgstr ""
+
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr ""
+msgid "Profiles|This email will be displayed on your public profile."
+msgstr ""
+
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
+msgid "Profiles|This feature is experimental and translations are not complete yet."
+msgstr ""
+
+msgid "Profiles|This information will appear on your profile."
+msgstr ""
+
msgid "Profiles|Type your %{confirmationValue} to confirm:"
msgstr ""
msgid "Profiles|Typically starts with \"ssh-rsa …\""
msgstr ""
+msgid "Profiles|Update profile settings"
+msgstr ""
+
msgid "Profiles|Update username"
msgstr ""
+msgid "Profiles|Upload new avatar"
+msgstr ""
+
msgid "Profiles|Username change failed - %{message}"
msgstr ""
msgid "Profiles|Username successfully changed"
msgstr ""
+msgid "Profiles|Website"
+msgstr ""
+
msgid "Profiles|What's your status?"
msgstr ""
+msgid "Profiles|You can change your avatar here"
+msgstr ""
+
+msgid "Profiles|You can change your avatar here or remove the current avatar to revert to %{gravatar_link}"
+msgstr ""
+
+msgid "Profiles|You can upload your avatar here"
+msgstr ""
+
+msgid "Profiles|You can upload your avatar here or change it at %{gravatar_link}"
+msgstr ""
+
msgid "Profiles|You don't have access to delete this user."
msgstr ""
@@ -4510,6 +4636,15 @@ msgstr ""
msgid "Profiles|Your account is currently an owner in these groups:"
msgstr ""
+msgid "Profiles|Your email address was automatically set based on your %{provider_label} account."
+msgstr ""
+
+msgid "Profiles|Your location was automatically set based on your %{provider_label} account."
+msgstr ""
+
+msgid "Profiles|Your name was automatically set based on your %{provider_label} account, so people you know can recognize you."
+msgstr ""
+
msgid "Profiles|Your status"
msgstr ""
@@ -4546,6 +4681,9 @@ msgstr ""
msgid "Project Badges"
msgstr ""
+msgid "Project URL"
+msgstr ""
+
msgid "Project access must be granted explicitly to each user."
msgstr ""
@@ -4573,6 +4711,9 @@ msgstr ""
msgid "Project name"
msgstr ""
+msgid "Project slug"
+msgstr ""
+
msgid "ProjectActivityRSS|Subscribe"
msgstr ""
@@ -4914,6 +5055,21 @@ msgstr ""
msgid "Resolve discussion"
msgstr ""
+msgid "Response metrics (AWS ELB)"
+msgstr ""
+
+msgid "Response metrics (Custom)"
+msgstr ""
+
+msgid "Response metrics (HA Proxy)"
+msgstr ""
+
+msgid "Response metrics (NGINX Ingress)"
+msgstr ""
+
+msgid "Response metrics (NGINX)"
+msgstr ""
+
msgid "Resume"
msgstr ""
@@ -5087,6 +5243,9 @@ msgstr ""
msgid "Select Archive Format"
msgstr ""
+msgid "Select a group to invite"
+msgstr ""
+
msgid "Select a namespace to fork the project"
msgstr ""
@@ -5129,6 +5288,9 @@ msgstr ""
msgid "Send email"
msgstr ""
+msgid "Send usage data"
+msgstr ""
+
msgid "Sep"
msgstr ""
@@ -5186,6 +5348,9 @@ msgstr ""
msgid "Shared Runners"
msgstr ""
+msgid "Shared projects"
+msgstr ""
+
msgid "Sherlock Transactions"
msgstr ""
@@ -5464,6 +5629,9 @@ msgstr ""
msgid "Subgroups"
msgstr ""
+msgid "Subgroups and projects"
+msgstr ""
+
msgid "Submit as spam"
msgstr ""
@@ -5488,6 +5656,12 @@ msgstr ""
msgid "System Info"
msgstr ""
+msgid "System metrics (Custom)"
+msgstr ""
+
+msgid "System metrics (Kubernetes)"
+msgstr ""
+
msgid "Tag (%{tag_count})"
msgid_plural "Tags (%{tag_count})"
msgstr[0] ""
@@ -5706,6 +5880,9 @@ msgstr ""
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
+msgid "There are no archived projects yet"
+msgstr ""
+
msgid "There are no issues to show"
msgstr ""
@@ -5715,6 +5892,15 @@ msgstr ""
msgid "There are no merge requests to show"
msgstr ""
+msgid "There are no projects shared with this group yet"
+msgstr ""
+
+msgid "There are no staged changes"
+msgstr ""
+
+msgid "There are no unstaged changes"
+msgstr ""
+
msgid "There are problems accessing Git storage: "
msgstr ""
@@ -5754,6 +5940,9 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This container registry has been scheduled for deletion."
+msgstr ""
+
msgid "This diff is collapsed."
msgstr ""
@@ -6073,6 +6262,9 @@ msgstr ""
msgid "To help improve GitLab and its user experience, GitLab will periodically collect usage information."
msgstr ""
+msgid "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}"
+msgstr ""
+
msgid "To import GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
msgstr ""
@@ -6157,6 +6349,9 @@ msgstr ""
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Undo"
+msgstr ""
+
msgid "Unlock"
msgstr ""
@@ -6169,6 +6364,9 @@ msgstr ""
msgid "Unresolve discussion"
msgstr ""
+msgid "Unstage"
+msgstr ""
+
msgid "Unstage all changes"
msgstr ""
@@ -6223,9 +6421,6 @@ msgstr ""
msgid "Upload file"
msgstr ""
-msgid "Upload new avatar"
-msgstr ""
-
msgid "UploadLink|click to upload"
msgstr ""
@@ -6271,9 +6466,6 @@ msgstr ""
msgid "Users"
msgstr ""
-msgid "User|Current status"
-msgstr ""
-
msgid "Variables"
msgstr ""
@@ -6586,6 +6778,12 @@ msgstr ""
msgid "You need permission."
msgstr ""
+msgid "You will loose all changes you've made to this file. This action cannot be undone."
+msgstr ""
+
+msgid "You will loose all the unstaged changes you've made in this project. This action cannot be undone."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr ""
diff --git a/package.json b/package.json
index 7e6ddf0fca7..4a479b6fb2a 100644
--- a/package.json
+++ b/package.json
@@ -18,8 +18,8 @@
"webpack-prod": "NODE_ENV=production webpack --config config/webpack.config.js"
},
"dependencies": {
- "@gitlab-org/gitlab-svgs": "^1.28.0",
- "@gitlab-org/gitlab-ui": "1.0.5",
+ "@gitlab-org/gitlab-svgs": "^1.29.0",
+ "@gitlab-org/gitlab-ui": "^1.5.0",
"autosize": "^4.0.0",
"axios": "^0.17.1",
"babel-core": "^6.26.3",
@@ -28,7 +28,7 @@
"babel-preset-latest": "^6.24.1",
"babel-preset-stage-2": "^6.24.1",
"blackst0ne-mermaid": "^7.1.0-fixed",
- "bootstrap": "~4.1.1",
+ "bootstrap": "4.1.1",
"brace-expansion": "^1.1.8",
"cache-loader": "^1.2.2",
"chart.js": "1.0.2",
@@ -67,7 +67,7 @@
"js-cookie": "^2.1.3",
"jszip": "^3.1.3",
"jszip-utils": "^0.0.2",
- "katex": "^0.8.3",
+ "katex": "^0.9.0",
"marked": "^0.3.12",
"monaco-editor": "^0.14.3",
"monaco-editor-webpack-plugin": "^1.5.2",
@@ -133,10 +133,11 @@
"jasmine-core": "^2.9.0",
"jasmine-diff": "^0.1.3",
"jasmine-jquery": "^2.1.1",
- "karma": "^2.0.4",
+ "karma": "^3.0.0",
"karma-chrome-launcher": "^2.2.0",
"karma-coverage-istanbul-reporter": "^1.4.2",
"karma-jasmine": "^1.1.2",
+ "karma-junit-reporter": "^1.2.0",
"karma-mocha-reporter": "^2.2.5",
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^4.0.0-beta.0",
diff --git a/public/robots.txt b/public/robots.txt
index 1f9d42f4adc..ea931e1a223 100644
--- a/public/robots.txt
+++ b/public/robots.txt
@@ -21,6 +21,8 @@ Disallow: /groups/new
Disallow: /groups/*/edit
Disallow: /users
Disallow: /help
+# Only specifically allow the Sign In page to avoid very ugly search results
+Allow: /users/sign_in
# Global snippets
User-Agent: *
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index 8f523e55adc..8d28fcacc05 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -32,7 +32,7 @@ GEM
diff-lcs (1.3)
domain_name (0.5.20170404)
unf (>= 0.0.5, < 1.0.0)
- ffi (1.9.18)
+ ffi (1.9.25)
http-cookie (1.0.3)
domain_name (~> 0.5)
i18n (0.9.1)
diff --git a/qa/README.md b/qa/README.md
index f8a5c00efd4..746bd5cf94b 100644
--- a/qa/README.md
+++ b/qa/README.md
@@ -78,13 +78,7 @@ If your user doesn't have permission to default sandbox group
GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password GITLAB_SANDBOX_NAME=jsmith-qa-sandbox bin/qa Test::Instance::All https://gitlab.example.com
```
-In addition, the `GITLAB_USER_TYPE` can be set to "ldap" to sign in as an LDAP user:
-
-```
-GITLAB_USER_TYPE=ldap GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password GITLAB_SANDBOX_NAME=jsmith-qa-sandbox bin/qa Test::Instance::All https://gitlab.example.com
-```
-
-All [supported environment variables are here](https://gitlab.com/gitlab-org/gitlab-qa#supported-environment-variables).
+All [supported environment variables are here](https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/what_tests_can_be_run.md#supported-environment-variables).
### Building a Docker image to test
diff --git a/qa/qa.rb b/qa/qa.rb
index c21cb3c1929..cb3202c8e1c 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -100,7 +100,7 @@ module QA
end
module Sanity
- autoload :Failing, 'qa/scenario/test/sanity/failing'
+ autoload :Framework, 'qa/scenario/test/sanity/framework'
autoload :Selectors, 'qa/scenario/test/sanity/selectors'
end
end
diff --git a/qa/qa/git/repository.rb b/qa/qa/git/repository.rb
index bdbb18b5045..faecacd45ec 100644
--- a/qa/qa/git/repository.rb
+++ b/qa/qa/git/repository.rb
@@ -28,8 +28,13 @@ module QA
end
def use_default_credentials
- self.username = Runtime::User.username
- self.password = Runtime::User.password
+ if ::QA::Runtime::User.ldap_user?
+ self.username = Runtime::User.ldap_username
+ self.password = Runtime::User.ldap_password
+ else
+ self.username = Runtime::User.username
+ self.password = Runtime::User.password
+ end
end
def clone(opts = '')
diff --git a/qa/qa/page/component/groups_filter.rb b/qa/qa/page/component/groups_filter.rb
index 69d465e8ac7..e647d368f0f 100644
--- a/qa/qa/page/component/groups_filter.rb
+++ b/qa/qa/page/component/groups_filter.rb
@@ -7,7 +7,7 @@ module QA
def self.included(base)
base.view 'app/views/shared/groups/_search_form.html.haml' do
element :groups_filter, 'search_field_tag :filter'
- element :groups_filter_placeholder, 'Filter by name...'
+ element :groups_filter_placeholder, 'Search by name'
end
base.view 'app/views/shared/groups/_empty_state.html.haml' do
@@ -27,7 +27,7 @@ module QA
page.has_css?(element_selector_css(:groups_list_tree_container))
end
- fill_in 'Filter by name...', with: name
+ fill_in 'Search by name', with: name
end
end
end
diff --git a/qa/qa/page/dashboard/groups.rb b/qa/qa/page/dashboard/groups.rb
index 5654cc01e09..70c5f996ff8 100644
--- a/qa/qa/page/dashboard/groups.rb
+++ b/qa/qa/page/dashboard/groups.rb
@@ -4,6 +4,11 @@ module QA
class Groups < Page::Base
include Page::Component::GroupsFilter
+ view 'app/views/shared/groups/_search_form.html.haml' do
+ element :groups_filter, 'search_field_tag :filter'
+ element :groups_filter_placeholder, 'Search by name'
+ end
+
view 'app/views/dashboard/_groups_head.html.haml' do
element :new_group_button, 'link_to _("New group")'
end
diff --git a/qa/qa/page/group/show.rb b/qa/qa/page/group/show.rb
index ac85f16d8af..6747f7f10b6 100644
--- a/qa/qa/page/group/show.rb
+++ b/qa/qa/page/group/show.rb
@@ -16,7 +16,7 @@ module QA
end
view 'app/assets/javascripts/groups/constants.js' do
- element :no_result_text, 'Sorry, no groups or projects matched your search'
+ element :no_result_text, 'No groups or projects matched your search'
end
def go_to_subgroup(name)
@@ -30,7 +30,7 @@ module QA
def has_subgroup?(name)
filter_by_name(name)
- page.has_text?(/#{name}|Sorry, no groups or projects matched your search/, wait: 60)
+ page.has_text?(/#{name}|No groups or projects matched your search/, wait: 60)
page.has_text?(name, wait: 0)
end
diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb
index afc8b66d878..08cf8da34fd 100644
--- a/qa/qa/page/main/login.rb
+++ b/qa/qa/page/main/login.rb
@@ -59,10 +59,31 @@ module QA
Page::Menu::Main.act { has_personal_area? }
end
+ def sign_in_using_admin_credentials
+ admin = QA::Factory::Resource::User.new.tap do |user|
+ user.username = QA::Runtime::User.admin_username
+ user.password = QA::Runtime::User.admin_password
+ end
+
+ using_wait_time 0 do
+ sign_in_using_gitlab_credentials(admin)
+ end
+
+ Page::Menu::Main.act { has_personal_area? }
+ end
+
def self.path
'/users/sign_in'
end
+ def sign_in_tab?
+ page.has_button?('Sign in')
+ end
+
+ def ldap_tab?
+ page.has_link?('LDAP')
+ end
+
def switch_to_sign_in_tab
click_on 'Sign in'
end
@@ -90,8 +111,8 @@ module QA
end
def sign_in_using_gitlab_credentials(user)
- switch_to_sign_in_tab unless page.has_button?('Sign in')
- switch_to_standard_tab if page.has_content?('LDAP')
+ switch_to_sign_in_tab unless sign_in_tab?
+ switch_to_standard_tab if ldap_tab?
fill_in :user_login, with: user.username
fill_in :user_password, with: user.password
diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb
index 1fb569b0f29..0766c98da6f 100644
--- a/qa/qa/page/project/new.rb
+++ b/qa/qa/page/project/new.rb
@@ -11,6 +11,7 @@ module QA
view 'app/views/projects/_new_project_fields.html.haml' do
element :project_namespace_select
element :project_namespace_field, 'namespaces_options'
+ element :project_name, 'text_field :name'
element :project_path, 'text_field :path'
element :project_description, 'text_area :description'
element :project_create_button, "submit 'Create project'"
@@ -32,7 +33,7 @@ module QA
end
def choose_name(name)
- fill_in 'project_path', with: name
+ fill_in 'project_name', with: name
end
def add_description(description)
diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb
index 841c959045f..27ba915961d 100644
--- a/qa/qa/runtime/env.rb
+++ b/qa/qa/runtime/env.rb
@@ -3,8 +3,6 @@ module QA
module Env
extend self
- attr_writer :user_type
-
# set to 'false' to have Chrome run visibly instead of headless
def chrome_headless?
(ENV['CHROME_HEADLESS'] =~ /^(false|no|0)$/i) != 0
@@ -19,18 +17,6 @@ module QA
ENV['PERSONAL_ACCESS_TOKEN']
end
- # By default, "standard" denotes a standard GitLab user login.
- # Set this to "ldap" if the user should be logged in via LDAP.
- def user_type
- return @user_type if defined?(@user_type) # rubocop:disable Gitlab/ModuleWithInstanceVariables
-
- ENV.fetch('GITLAB_USER_TYPE', 'standard').tap do |type|
- unless %w(ldap standard).include?(type)
- raise ArgumentError.new("Invalid user type '#{type}': must be 'ldap' or 'standard'")
- end
- end
- end
-
def user_username
ENV['GITLAB_USERNAME']
end
@@ -39,6 +25,14 @@ module QA
ENV['GITLAB_PASSWORD']
end
+ def admin_username
+ ENV['GITLAB_ADMIN_USERNAME']
+ end
+
+ def admin_password
+ ENV['GITLAB_ADMIN_PASSWORD']
+ end
+
def forker?
forker_username && forker_password
end
diff --git a/qa/qa/runtime/user.rb b/qa/qa/runtime/user.rb
index b016777c987..5eb7a210fce 100644
--- a/qa/qa/runtime/user.rb
+++ b/qa/qa/runtime/user.rb
@@ -7,25 +7,37 @@ module QA
'root'
end
+ def default_password
+ '5iveL!fe'
+ end
+
def username
Runtime::Env.user_username || default_username
end
def password
- Runtime::Env.user_password || '5iveL!fe'
+ Runtime::Env.user_password || default_password
end
def ldap_user?
- Runtime::Env.user_type == 'ldap'
+ Runtime::Env.ldap_username && Runtime::Env.ldap_password
end
def ldap_username
- Runtime::Env.ldap_username || name
+ Runtime::Env.ldap_username || username
end
def ldap_password
Runtime::Env.ldap_password || password
end
+
+ def admin_username
+ Runtime::Env.admin_username || default_username
+ end
+
+ def admin_password
+ Runtime::Env.admin_password || default_password
+ end
end
end
end
diff --git a/qa/qa/scenario/test/sanity/failing.rb b/qa/qa/scenario/test/sanity/framework.rb
index 03452f6693d..7835d2564f0 100644
--- a/qa/qa/scenario/test/sanity/failing.rb
+++ b/qa/qa/scenario/test/sanity/framework.rb
@@ -5,12 +5,13 @@ module QA
module Test
module Sanity
##
- # This scenario exits with a 1 exit code.
+ # This scenario runs 1 passing example, and 1 failing example, and exits
+ # with a 1 exit code.
#
- class Failing < Template
+ class Framework < Template
include Bootable
- tags :failing
+ tags :framework
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb
index c9958917be9..c296296def6 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb
@@ -3,10 +3,6 @@
module QA
context :manage, :orchestrated, :ldap do
describe 'LDAP login' do
- before do
- Runtime::Env.user_type = 'ldap'
- end
-
it 'user logs into GitLab using LDAP credentials' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb
index b19bdd950fa..0dcdc6639d1 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb
@@ -2,7 +2,7 @@
module QA
context :create do
- describe 'Git clone over HTTP' do
+ describe 'Git clone over HTTP', :ldap do
let(:location) do
Page::Project::Show.act do
choose_repository_clone_http
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb
index 40dfd138a1b..bf32569b6cb 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb
@@ -2,7 +2,7 @@
module QA
context :create do
- describe 'Git push over HTTP' do
+ describe 'Git push over HTTP', :ldap do
it 'user pushes code to the repository' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb
index 1d9cc33080d..b2da685c477 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb
@@ -2,7 +2,7 @@
module QA
context :create do
- describe 'Protected branch support' do
+ describe 'Protected branch support', :ldap do
let(:branch_name) { 'protected-branch' }
let(:commit_message) { 'Protected push commit message' }
let(:project) do
diff --git a/qa/qa/specs/features/sanity/failing_spec.rb b/qa/qa/specs/features/sanity/failing_spec.rb
deleted file mode 100644
index 7e0480e9067..00000000000
--- a/qa/qa/specs/features/sanity/failing_spec.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- context 'Sanity checks', :orchestrated, :failing do
- describe 'Failing orchestrated example' do
- it 'always fails' do
- Runtime::Browser.visit(:gitlab, Page::Main::Login)
-
- expect(page).to have_text("These Aren't the Texts You're Looking For", wait: 1)
- end
- end
- end
-end
diff --git a/qa/qa/specs/features/sanity/framework_spec.rb b/qa/qa/specs/features/sanity/framework_spec.rb
new file mode 100644
index 00000000000..ee9d068eb3a
--- /dev/null
+++ b/qa/qa/specs/features/sanity/framework_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Framework sanity checks', :orchestrated, :framework do
+ describe 'Passing orchestrated example' do
+ it 'succeeds' do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+
+ Page::Main::Login.perform do |main_login|
+ expect(main_login.sign_in_tab?).to be(true)
+ end
+ end
+ end
+
+ describe 'Failing orchestrated example' do
+ it 'fails' do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+
+ expect(page).to have_text("These Aren't the Texts You're Looking For", wait: 1)
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/runner.rb b/qa/qa/specs/runner.rb
index 5b5699d8a93..fea0ef94df3 100644
--- a/qa/qa/specs/runner.rb
+++ b/qa/qa/specs/runner.rb
@@ -5,10 +5,12 @@ module QA
class Runner < Scenario::Template
attr_accessor :tty, :tags, :options
+ DEFAULT_TEST_PATH_ARGS = ['--', File.expand_path('./features', __dir__)].freeze
+
def initialize
@tty = false
@tags = []
- @options = [File.expand_path('./features', __dir__)]
+ @options = []
end
def perform
@@ -18,10 +20,11 @@ module QA
if tags.any?
tags.each { |tag| args.push(['--tag', tag.to_s]) }
else
- args.push(%w[--tag ~orchestrated])
+ args.push(%w[--tag ~orchestrated]) unless (%w[-t --tag] & options).any?
end
args.push(options)
+ args.push(DEFAULT_TEST_PATH_ARGS) unless options.any? { |opt| opt =~ %r{/features/} }
Runtime::Browser.configure!
diff --git a/qa/spec/runtime/env_spec.rb b/qa/spec/runtime/env_spec.rb
index 5493a33cd2a..d889d185a45 100644
--- a/qa/spec/runtime/env_spec.rb
+++ b/qa/spec/runtime/env_spec.rb
@@ -56,27 +56,6 @@ describe QA::Runtime::Env do
end
end
- describe '.user_type' do
- it 'returns standard if not defined' do
- expect(described_class.user_type).to eq('standard')
- end
-
- it 'returns standard as defined' do
- stub_env('GITLAB_USER_TYPE', 'standard')
- expect(described_class.user_type).to eq('standard')
- end
-
- it 'returns ldap as defined' do
- stub_env('GITLAB_USER_TYPE', 'ldap')
- expect(described_class.user_type).to eq('ldap')
- end
-
- it 'returns an error if invalid user type' do
- stub_env('GITLAB_USER_TYPE', 'foobar')
- expect { described_class.user_type }.to raise_error(ArgumentError)
- end
- end
-
describe '.forker?' do
it 'returns false if no forker credentials are defined' do
expect(described_class).not_to be_forker
diff --git a/qa/spec/scenario/test/sanity/framework_spec.rb b/qa/spec/scenario/test/sanity/framework_spec.rb
new file mode 100644
index 00000000000..44ac780556e
--- /dev/null
+++ b/qa/spec/scenario/test/sanity/framework_spec.rb
@@ -0,0 +1,5 @@
+describe QA::Scenario::Test::Sanity::Framework do
+ it_behaves_like 'a QA scenario class' do
+ let(:tags) { [:framework] }
+ end
+end
diff --git a/qa/spec/specs/runner_spec.rb b/qa/spec/specs/runner_spec.rb
index b237b954889..cf22d1c9395 100644
--- a/qa/spec/specs/runner_spec.rb
+++ b/qa/spec/specs/runner_spec.rb
@@ -7,43 +7,65 @@ describe QA::Specs::Runner do
end
it 'excludes the orchestrated tag by default' do
- expect(RSpec::Core::Runner).to receive(:run)
- .with(['--tag', '~orchestrated', File.expand_path('../../qa/specs/features', __dir__)], $stderr, $stdout)
- .and_return(0)
+ expect_rspec_runner_arguments(['--tag', '~orchestrated', *described_class::DEFAULT_TEST_PATH_ARGS])
subject.perform
end
context 'when tty is set' do
- subject do
- described_class.new.tap do |runner|
- runner.tty = true
- end
- end
+ subject { described_class.new.tap { |runner| runner.tty = true } }
it 'sets the `--tty` flag' do
- expect(RSpec::Core::Runner).to receive(:run)
- .with(['--tty', '--tag', '~orchestrated', File.expand_path('../../qa/specs/features', __dir__)], $stderr, $stdout)
- .and_return(0)
+ expect_rspec_runner_arguments(['--tty', '--tag', '~orchestrated', *described_class::DEFAULT_TEST_PATH_ARGS])
subject.perform
end
end
context 'when tags are set' do
- subject do
- described_class.new.tap do |runner|
- runner.tags = %i[orchestrated github]
- end
- end
+ subject { described_class.new.tap { |runner| runner.tags = %i[orchestrated github] } }
it 'focuses on the given tags' do
- expect(RSpec::Core::Runner).to receive(:run)
- .with(['--tag', 'orchestrated', '--tag', 'github', File.expand_path('../../qa/specs/features', __dir__)], $stderr, $stdout)
- .and_return(0)
+ expect_rspec_runner_arguments(['--tag', 'orchestrated', '--tag', 'github', *described_class::DEFAULT_TEST_PATH_ARGS])
+
+ subject.perform
+ end
+ end
+
+ context 'when "--tag smoke" is set as options' do
+ subject { described_class.new.tap { |runner| runner.options = %w[--tag smoke] } }
+
+ it 'focuses on the given tag without excluded the orchestrated tag' do
+ expect_rspec_runner_arguments(['--tag', 'smoke', *described_class::DEFAULT_TEST_PATH_ARGS])
+
+ subject.perform
+ end
+ end
+
+ context 'when "qa/specs/features/foo" is set as options' do
+ subject { described_class.new.tap { |runner| runner.options = %w[qa/specs/features/foo] } }
+
+ it 'passes the given tests path and excludes the orchestrated tag' do
+ expect_rspec_runner_arguments(['--tag', '~orchestrated', 'qa/specs/features/foo'])
subject.perform
end
end
+
+ context 'when "-- qa/specs/features/foo" is set as options' do
+ subject { described_class.new.tap { |runner| runner.options = %w[-- qa/specs/features/foo] } }
+
+ it 'passes the given tests path and excludes the orchestrated tag' do
+ expect_rspec_runner_arguments(['--tag', '~orchestrated', '--', 'qa/specs/features/foo'])
+
+ subject.perform
+ end
+ end
+
+ def expect_rspec_runner_arguments(arguments)
+ expect(RSpec::Core::Runner).to receive(:run)
+ .with(arguments, $stderr, $stdout)
+ .and_return(0)
+ end
end
end
diff --git a/spec/bin/changelog_spec.rb b/spec/bin/changelog_spec.rb
index 9dc4edf97d1..c59add88a82 100644
--- a/spec/bin/changelog_spec.rb
+++ b/spec/bin/changelog_spec.rb
@@ -95,6 +95,7 @@ describe 'bin/changelog' do
it 'shows error message and exits the program' do
allow($stdin).to receive(:getc).and_return(type)
+
expect do
expect { described_class.read_type }.to raise_error(
ChangelogHelpers::Abort,
diff --git a/spec/controllers/admin/application_settings_controller_spec.rb b/spec/controllers/admin/application_settings_controller_spec.rb
index 9d10d725ff3..10e1bfc30f9 100644
--- a/spec/controllers/admin/application_settings_controller_spec.rb
+++ b/spec/controllers/admin/application_settings_controller_spec.rb
@@ -78,5 +78,12 @@ describe Admin::ApplicationSettingsController do
expect(response).to redirect_to(admin_application_settings_path)
expect(ApplicationSetting.current.restricted_visibility_levels).to be_empty
end
+
+ it 'updates the receive_max_input_size setting' do
+ put :update, application_setting: { receive_max_input_size: "1024" }
+
+ expect(response).to redirect_to(admin_application_settings_path)
+ expect(ApplicationSetting.current.receive_max_input_size).to eq(1024)
+ end
end
end
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index ae49490f31c..65d6cd1a295 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -38,14 +38,6 @@ describe GroupsController do
project
end
- context 'as html' do
- it 'assigns whether or not a group has children' do
- get :show, id: group.to_param
-
- expect(assigns(:has_children)).to be_truthy
- end
- end
-
context 'as atom' do
it 'assigns events for all the projects in the group' do
create(:event, project: project)
diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index ca7d30fec83..b42f6419922 100644
--- a/spec/controllers/projects/jobs_controller_spec.rb
+++ b/spec/controllers/projects/jobs_controller_spec.rb
@@ -166,8 +166,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
expect(response).to match_response_schema('job/job_details')
expect(json_response['artifact']['download_path']).to match(%r{artifacts/download})
expect(json_response['artifact']['browse_path']).to match(%r{artifacts/browse})
- expect(json_response['artifact']).not_to have_key(:expired)
- expect(json_response['artifact']).not_to have_key(:expired_at)
+ expect(json_response['artifact']).not_to have_key('expired')
+ expect(json_response['artifact']).not_to have_key('expired_at')
end
end
@@ -177,8 +177,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
it 'exposes needed information' do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('job/job_details')
- expect(json_response['artifact']).not_to have_key(:download_path)
- expect(json_response['artifact']).not_to have_key(:browse_path)
+ expect(json_response['artifact']).not_to have_key('download_path')
+ expect(json_response['artifact']).not_to have_key('browse_path')
expect(json_response['artifact']['expired']).to eq(true)
expect(json_response['artifact']['expire_at']).not_to be_empty
end
@@ -194,6 +194,63 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
expect(json_response['terminal_path']).to match(%r{/terminal})
end
end
+
+ context 'when job passed with no trace' do
+ let(:job) { create(:ci_build, :success, :artifacts, pipeline: pipeline) }
+
+ it 'exposes empty state illustrations' do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('job/job_details')
+ expect(json_response['status']['illustration']).to have_key('image')
+ expect(json_response['status']['illustration']).to have_key('size')
+ expect(json_response['status']['illustration']).to have_key('title')
+ end
+ end
+ end
+
+ context 'when requesting JSON job is triggered' do
+ let!(:merge_request) { create(:merge_request, source_project: project) }
+ let(:trigger) { create(:ci_trigger, project: project) }
+ let(:trigger_request) { create(:ci_trigger_request, pipeline: pipeline, trigger: trigger) }
+ let(:job) { create(:ci_build, pipeline: pipeline, trigger_request: trigger_request) }
+
+ before do
+ project.add_developer(user)
+ sign_in(user)
+
+ allow_any_instance_of(Ci::Build).to receive(:merge_request).and_return(merge_request)
+ end
+
+ context 'with no variables' do
+ before do
+ get_show(id: job.id, format: :json)
+ end
+
+ it 'exposes trigger information' do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('job/job_details')
+ expect(json_response['trigger']['short_token']).to eq 'toke'
+ expect(json_response['trigger']['variables'].length).to eq 0
+ end
+ end
+
+ context 'with variables' do
+ before do
+ create(:ci_pipeline_variable, pipeline: pipeline, key: :TRIGGER_KEY_1, value: 'TRIGGER_VALUE_1')
+
+ get_show(id: job.id, format: :json)
+ end
+
+ it 'exposes trigger information and variables' do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('job/job_details')
+ expect(json_response['trigger']['short_token']).to eq 'toke'
+ expect(json_response['trigger']['variables'].length).to eq 1
+ expect(json_response['trigger']['variables'].first['key']).to eq "TRIGGER_KEY_1"
+ expect(json_response['trigger']['variables'].first['value']).to eq "TRIGGER_VALUE_1"
+ expect(json_response['trigger']['variables'].first['public']).to eq false
+ end
+ end
end
def get_show(**extra_params)
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index d9bb3981539..7446e0650f7 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -885,4 +885,18 @@ describe Projects::MergeRequestsController do
end
end
end
+
+ describe 'GET edit' do
+ it 'responds successfully' do
+ get :edit, namespace_id: project.namespace, project_id: project, id: merge_request
+
+ expect(response).to have_gitlab_http_status(:success)
+ end
+
+ it 'assigns the noteable to make sure autocompletes work' do
+ get :edit, namespace_id: project.namespace, project_id: project, id: merge_request
+
+ expect(assigns(:noteable)).not_to be_nil
+ end
+ end
end
diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb
index 1458113b90c..81badaac76b 100644
--- a/spec/controllers/projects/notes_controller_spec.rb
+++ b/spec/controllers/projects/notes_controller_spec.rb
@@ -154,7 +154,7 @@ describe Projects::NotesController do
get :index, request_params
expect(parsed_response[:notes].count).to eq(1)
- expect(note_json[:id]).to eq(note.id)
+ expect(note_json[:id]).to eq(note.id.to_s)
end
it 'does not result in N+1 queries' do
diff --git a/spec/controllers/projects/registry/repositories_controller_spec.rb b/spec/controllers/projects/registry/repositories_controller_spec.rb
index 17769a14def..d11e42b411b 100644
--- a/spec/controllers/projects/registry/repositories_controller_spec.rb
+++ b/spec/controllers/projects/registry/repositories_controller_spec.rb
@@ -86,9 +86,10 @@ describe Projects::Registry::RepositoriesController do
stub_container_registry_tags(repository: :any, tags: [])
end
- it 'deletes a repository' do
- expect { delete_repository(repository) }.to change { ContainerRepository.all.count }.by(-1)
+ it 'schedules a job to delete a repository' do
+ expect(DeleteContainerRepositoryWorker).to receive(:perform_async).with(user.id, repository.id)
+ delete_repository(repository)
expect(response).to have_gitlab_http_status(:no_content)
end
end
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index c3a66477b6a..3bc9cbe64c5 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -803,37 +803,7 @@ describe ProjectsController do
project.add_maintainer(user)
end
- context 'object storage disabled' do
- before do
- stub_feature_flags(import_export_object_storage: false)
- end
-
- context 'when project export is enabled' do
- it 'returns 302' do
- get :download_export, namespace_id: project.namespace, id: project
-
- expect(response).to have_gitlab_http_status(302)
- end
- end
-
- context 'when project export is disabled' do
- before do
- stub_application_setting(project_export_enabled?: false)
- end
-
- it 'returns 404' do
- get :download_export, namespace_id: project.namespace, id: project
-
- expect(response).to have_gitlab_http_status(404)
- end
- end
- end
-
context 'object storage enabled' do
- before do
- stub_feature_flags(import_export_object_storage: true)
- end
-
context 'when project export is enabled' do
it 'returns 302' do
get :download_export, namespace_id: project.namespace, id: project
diff --git a/spec/db/development/import_common_metrics_spec.rb b/spec/db/development/import_common_metrics_spec.rb
new file mode 100644
index 00000000000..25061ef0887
--- /dev/null
+++ b/spec/db/development/import_common_metrics_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Import metrics on development seed' do
+ subject { load Rails.root.join('db', 'fixtures', 'development', '99_common_metrics.rb') }
+
+ it "imports all prometheus metrics" do
+ expect(PrometheusMetric.common).to be_empty
+
+ subject
+
+ expect(PrometheusMetric.common).not_to be_empty
+ end
+end
diff --git a/spec/db/importers/common_metrics_importer_spec.rb b/spec/db/importers/common_metrics_importer_spec.rb
new file mode 100644
index 00000000000..68260820958
--- /dev/null
+++ b/spec/db/importers/common_metrics_importer_spec.rb
@@ -0,0 +1,131 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+require Rails.root.join("db", "importers", "common_metrics_importer.rb")
+
+describe Importers::PrometheusMetric do
+ it 'group enum equals ::PrometheusMetric' do
+ expect(described_class.groups).to eq(::PrometheusMetric.groups)
+ end
+
+ it 'GROUP_TITLES equals ::PrometheusMetric' do
+ expect(described_class::GROUP_TITLES).to eq(::PrometheusMetric::GROUP_TITLES)
+ end
+end
+
+describe Importers::CommonMetricsImporter do
+ subject { described_class.new }
+
+ context "does import common_metrics.yml" do
+ let(:groups) { subject.content }
+ let(:metrics) { groups.map { |group| group['metrics'] }.flatten }
+ let(:queries) { metrics.map { |group| group['queries'] }.flatten }
+ let(:query_ids) { queries.map { |query| query['id'] } }
+
+ before do
+ subject.execute
+ end
+
+ it "has the same amount of groups" do
+ expect(PrometheusMetric.common.group(:group).count.count).to eq(groups.count)
+ end
+
+ it "has the same amount of metrics" do
+ expect(PrometheusMetric.common.group(:group, :title).count.count).to eq(metrics.count)
+ end
+
+ it "has the same amount of queries" do
+ expect(PrometheusMetric.common.count).to eq(queries.count)
+ end
+
+ it "does not have duplicate IDs" do
+ expect(query_ids).to eq(query_ids.uniq)
+ end
+
+ it "imports all IDs" do
+ expect(PrometheusMetric.common.pluck(:identifier)).to contain_exactly(*query_ids)
+ end
+ end
+
+ context "does import common_metrics.yml" do
+ it "when executed from outside of the Rails.root" do
+ Dir.chdir(Dir.tmpdir) do
+ expect { subject.execute }.not_to raise_error
+ end
+
+ expect(PrometheusMetric.common).not_to be_empty
+ end
+ end
+
+ context 'does import properly all fields' do
+ let(:query_identifier) { 'response-metric' }
+ let(:group) do
+ {
+ group: 'Response metrics (NGINX Ingress)',
+ metrics: [{
+ title: "Throughput",
+ y_label: "Requests / Sec",
+ queries: [{
+ id: query_identifier,
+ query_range: 'my-query',
+ unit: 'my-unit',
+ label: 'status code'
+ }]
+ }]
+ }
+ end
+
+ before do
+ expect(subject).to receive(:content) { [group.deep_stringify_keys] }
+ end
+
+ shared_examples 'stores metric' do
+ let(:metric) { PrometheusMetric.find_by(identifier: query_identifier) }
+
+ it 'with all data' do
+ expect(metric.group).to eq('nginx_ingress')
+ expect(metric.title).to eq('Throughput')
+ expect(metric.y_label).to eq('Requests / Sec')
+ expect(metric.unit).to eq('my-unit')
+ expect(metric.legend).to eq('status code')
+ expect(metric.query).to eq('my-query')
+ end
+ end
+
+ context 'if ID is missing' do
+ let(:query_identifier) { }
+
+ it 'raises exception' do
+ expect { subject.execute }.to raise_error(described_class::MissingQueryId)
+ end
+ end
+
+ context 'for existing common metric with different ID' do
+ let!(:existing_metric) { create(:prometheus_metric, :common, identifier: 'my-existing-metric') }
+
+ before do
+ subject.execute
+ end
+
+ it_behaves_like 'stores metric' do
+ it 'and existing metric is not changed' do
+ expect(metric).not_to eq(existing_metric)
+ end
+ end
+ end
+
+ context 'when metric with ID exists ' do
+ let!(:existing_metric) { create(:prometheus_metric, :common, identifier: 'response-metric') }
+
+ before do
+ subject.execute
+ end
+
+ it_behaves_like 'stores metric' do
+ it 'and existing metric is changed' do
+ expect(metric).to eq(existing_metric)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/db/production/import_common_metrics_spec.rb b/spec/db/production/import_common_metrics_spec.rb
new file mode 100644
index 00000000000..1e4ff818a86
--- /dev/null
+++ b/spec/db/production/import_common_metrics_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Import metrics on production seed' do
+ subject { load Rails.root.join('db', 'fixtures', 'production', '999_common_metrics.rb') }
+
+ it "imports all prometheus metrics" do
+ expect(PrometheusMetric.common).to be_empty
+
+ subject
+
+ expect(PrometheusMetric.common).not_to be_empty
+ end
+end
diff --git a/spec/factories/ci/pipelines.rb b/spec/factories/ci/pipelines.rb
index a6ff226fa75..9fef424e425 100644
--- a/spec/factories/ci/pipelines.rb
+++ b/spec/factories/ci/pipelines.rb
@@ -77,6 +77,14 @@ FactoryBot.define do
pipeline.builds << build(:ci_build, :test_reports, pipeline: pipeline, project: pipeline.project)
end
end
+
+ trait :auto_devops_source do
+ config_source { Ci::Pipeline.config_sources[:auto_devops_source] }
+ end
+
+ trait :repository_source do
+ config_source { Ci::Pipeline.config_sources[:repository_source] }
+ end
end
end
end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 17e457c04a5..80801eb1082 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -103,27 +103,11 @@ FactoryBot.define do
end
trait :with_export do
- before(:create) do |_project, _evaluator|
- allow(Feature).to receive(:enabled?).with(:import_export_object_storage) { false }
- allow(Feature).to receive(:enabled?).with('import_export_object_storage') { false }
- end
-
after(:create) do |project, _evaluator|
ProjectExportWorker.new.perform(project.creator.id, project.id)
end
end
- trait :with_object_export do
- before(:create) do |_project, _evaluator|
- allow(Feature).to receive(:enabled?).with(:import_export_object_storage) { true }
- allow(Feature).to receive(:enabled?).with('import_export_object_storage') { true }
- end
-
- after(:create) do |project, evaluator|
- ProjectExportWorker.new.perform(project.creator.id, project.id)
- end
- end
-
trait :broken_storage do
after(:create) do |project|
project.update_column(:repository_storage, 'broken')
@@ -260,6 +244,10 @@ FactoryBot.define do
trait(:repository_enabled) { repository_access_level ProjectFeature::ENABLED }
trait(:repository_disabled) { repository_access_level ProjectFeature::DISABLED }
trait(:repository_private) { repository_access_level ProjectFeature::PRIVATE }
+
+ trait :auto_devops do
+ association :auto_devops, factory: :project_auto_devops
+ end
end
# Project with empty repository
diff --git a/spec/factories/prometheus_metrics.rb b/spec/factories/prometheus_metrics.rb
new file mode 100644
index 00000000000..c56644bfb96
--- /dev/null
+++ b/spec/factories/prometheus_metrics.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :prometheus_metric, class: PrometheusMetric do
+ title 'title'
+ query 'avg(metric)'
+ y_label 'y_label'
+ unit 'm/s'
+ group :business
+ project
+ legend 'legend'
+
+ trait :common do
+ common true
+ project nil
+ end
+ end
+end
diff --git a/spec/factories/resource_label_events.rb b/spec/factories/resource_label_events.rb
index a67ad78c098..739ba901052 100644
--- a/spec/factories/resource_label_events.rb
+++ b/spec/factories/resource_label_events.rb
@@ -2,9 +2,12 @@
FactoryBot.define do
factory :resource_label_event do
- user { issue.project.creator }
action :add
label
- issue
+ user { issuable&.author || create(:user) }
+
+ after(:build) do |event, evaluator|
+ event.issue = create(:issue) unless event.issuable
+ end
end
end
diff --git a/spec/features/commits/user_uses_slash_commands_spec.rb b/spec/features/commits/user_uses_quick_actions_spec.rb
index 9a4b7bd2444..9a4b7bd2444 100644
--- a/spec/features/commits/user_uses_slash_commands_spec.rb
+++ b/spec/features/commits/user_uses_quick_actions_spec.rb
diff --git a/spec/features/dashboard/groups_list_spec.rb b/spec/features/dashboard/groups_list_spec.rb
index eceb12e91cd..e75c43d5338 100644
--- a/spec/features/dashboard/groups_list_spec.rb
+++ b/spec/features/dashboard/groups_list_spec.rb
@@ -125,7 +125,7 @@ describe 'Dashboard Groups page', :js do
end
it 'loads results for next page' do
- expect(page).to have_selector('.gl-pagination .page', count: 2)
+ expect(page).to have_selector('.gl-pagination .page-item a[role=menuitemradio]', count: 2)
# Check first page
expect(page).to have_content(group2.full_name)
@@ -134,7 +134,7 @@ describe 'Dashboard Groups page', :js do
expect(page).not_to have_selector("#group-#{group.id}")
# Go to next page
- find(".gl-pagination .page:not(.active) a").click
+ find('.gl-pagination .page-item:not(.active) a[role=menuitemradio]').click
wait_for_requests
diff --git a/spec/features/explore/new_menu_spec.rb b/spec/features/explore/new_menu_spec.rb
index 0a88988ea09..11f05b6d220 100644
--- a/spec/features/explore/new_menu_spec.rb
+++ b/spec/features/explore/new_menu_spec.rb
@@ -20,7 +20,7 @@ describe 'Top Plus Menu', :js do
click_topmenuitem("New project")
- expect(page).to have_content('Project path')
+ expect(page).to have_content('Project URL')
expect(page).to have_content('Project name')
end
@@ -92,7 +92,7 @@ describe 'Top Plus Menu', :js do
find('.header-new-group-project a').click
end
- expect(page).to have_content('Project path')
+ expect(page).to have_content('Project URL')
expect(page).to have_content('Project name')
end
end
diff --git a/spec/features/groups/labels/sort_labels_spec.rb b/spec/features/groups/labels/sort_labels_spec.rb
new file mode 100644
index 00000000000..2aea4d77675
--- /dev/null
+++ b/spec/features/groups/labels/sort_labels_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Sort labels', :js do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let!(:label1) { create(:group_label, title: 'Foo', description: 'Lorem ipsum', group: group) }
+ let!(:label2) { create(:group_label, title: 'Bar', description: 'Fusce consequat', group: group) }
+
+ before do
+ group.add_maintainer(user)
+ sign_in(user)
+
+ visit group_labels_path(group)
+ end
+
+ it 'sorts by title by default' do
+ expect(page).to have_button('Name')
+
+ # assert default sorting
+ within '.other-labels' do
+ expect(page.all('.label-list-item').first.text).to include('Bar')
+ expect(page.all('.label-list-item').last.text).to include('Foo')
+ end
+ end
+
+ it 'sorts by date' do
+ click_button 'Name'
+
+ sort_options = find('ul.dropdown-menu-sort li').all('a').collect(&:text)
+
+ expect(sort_options[0]).to eq('Name')
+ expect(sort_options[1]).to eq('Name, descending')
+ expect(sort_options[2]).to eq('Last created')
+ expect(sort_options[3]).to eq('Oldest created')
+ expect(sort_options[4]).to eq('Last updated')
+ expect(sort_options[5]).to eq('Oldest updated')
+
+ click_link 'Name, descending'
+
+ # assert default sorting
+ within '.other-labels' do
+ expect(page.all('.label-list-item').first.text).to include('Foo')
+ expect(page.all('.label-list-item').last.text).to include('Bar')
+ end
+ end
+end
diff --git a/spec/features/instance_statistics/cohorts_spec.rb b/spec/features/instance_statistics/cohorts_spec.rb
index 81fc5eff980..40e65515ceb 100644
--- a/spec/features/instance_statistics/cohorts_spec.rb
+++ b/spec/features/instance_statistics/cohorts_spec.rb
@@ -3,6 +3,8 @@ require 'rails_helper'
describe 'Cohorts page' do
before do
sign_in(create(:admin))
+
+ stub_application_setting(usage_ping_enabled: true)
end
it 'See users count per month' do
diff --git a/spec/features/issues/resource_label_events_spec.rb b/spec/features/issues/resource_label_events_spec.rb
new file mode 100644
index 00000000000..40c452c991a
--- /dev/null
+++ b/spec/features/issues/resource_label_events_spec.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe 'List issue resource label events', :js do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public) }
+ let(:issue) { create(:issue, project: project, author: user) }
+ let!(:label) { create(:label, project: project, title: 'foo') }
+
+ context 'when user displays the issue' do
+ let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue, note: 'some note') }
+ let!(:event) { create(:resource_label_event, user: user, issue: issue, label: label) }
+
+ before do
+ visit project_issue_path(project, issue)
+ wait_for_requests
+ end
+
+ it 'shows both notes and resource label events' do
+ page.within('#notes') do
+ expect(find("#note_#{note.id}")).to have_content 'some note'
+ expect(find("#note_#{event.discussion_id}")).to have_content 'added foo label'
+ end
+ end
+ end
+
+ context 'when user adds label to the issue' do
+ def toggle_labels(labels)
+ page.within '.labels' do
+ click_link 'Edit'
+ wait_for_requests
+
+ labels.each { |label| click_link label }
+
+ click_link 'Edit'
+ wait_for_requests
+ end
+ end
+
+ before do
+ create(:label, project: project, title: 'bar')
+ project.add_developer(user)
+
+ sign_in(user)
+ visit project_issue_path(project, issue)
+ wait_for_requests
+ end
+
+ it 'shows add note for newly added labels' do
+ toggle_labels(%w(foo bar))
+ visit project_issue_path(project, issue)
+ wait_for_requests
+
+ page.within('#notes') do
+ expect(page).to have_content 'added bar foo labels'
+ end
+ end
+ end
+end
diff --git a/spec/features/issues/user_uses_slash_commands_spec.rb b/spec/features/issues/user_uses_quick_actions_spec.rb
index 5926e442f24..5926e442f24 100644
--- a/spec/features/issues/user_uses_slash_commands_spec.rb
+++ b/spec/features/issues/user_uses_quick_actions_spec.rb
diff --git a/spec/features/merge_request/user_posts_diff_notes_spec.rb b/spec/features/merge_request/user_posts_diff_notes_spec.rb
index 77261f9375c..b6ed3686de2 100644
--- a/spec/features/merge_request/user_posts_diff_notes_spec.rb
+++ b/spec/features/merge_request/user_posts_diff_notes_spec.rb
@@ -186,11 +186,8 @@ describe 'Merge request > User posts diff notes', :js do
describe 'posting a note' do
it 'adds as discussion' do
- expect(page).to have_css('.js-temp-notes-holder', count: 2)
-
should_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_22_22"]'), asset_form_reset: false)
expect(page).to have_css('.notes_holder .note.note-discussion', count: 1)
- expect(page).to have_css('.js-temp-notes-holder', count: 1)
expect(page).to have_button('Reply...')
end
end
@@ -267,7 +264,7 @@ describe 'Merge request > User posts diff notes', :js do
def assert_comment_persistence(line_holder, asset_form_reset:)
notes_holder_saved = line_holder.find(:xpath, notes_holder_input_xpath)
- expect(notes_holder_saved[:class]).not_to include(notes_holder_input_class)
+ expect(notes_holder_saved[:class]).not_to include('note-edit-form')
expect(notes_holder_saved).to have_content test_note_comment
assert_form_is_reset if asset_form_reset
@@ -281,6 +278,6 @@ describe 'Merge request > User posts diff notes', :js do
end
def assert_form_is_reset
- expect(page).to have_no_css('.js-temp-notes-holder')
+ expect(page).to have_no_css('.note-edit-form')
end
end
diff --git a/spec/features/merge_request/user_uses_slash_commands_spec.rb b/spec/features/merge_request/user_uses_quick_actions_spec.rb
index b81478a481f..b81478a481f 100644
--- a/spec/features/merge_request/user_uses_slash_commands_spec.rb
+++ b/spec/features/merge_request/user_uses_quick_actions_spec.rb
diff --git a/spec/features/projects/activity/user_sees_private_activity_spec.rb b/spec/features/projects/activity/user_sees_private_activity_spec.rb
new file mode 100644
index 00000000000..d7dc0a6712a
--- /dev/null
+++ b/spec/features/projects/activity/user_sees_private_activity_spec.rb
@@ -0,0 +1,35 @@
+require 'spec_helper'
+
+describe 'Project > Activity > User sees private activity', :js do
+ let(:project) { create(:project, :public) }
+ let(:author) { create(:user) }
+ let(:user) { create(:user) }
+ let(:issue) { create(:issue, :confidential, project: project, author: author) }
+ let(:message) { "#{author.name} opened issue #{issue.to_reference}" }
+
+ before do
+ project.add_developer(author)
+
+ create(:event, :created, project: project, target: issue, author: author)
+ end
+
+ it 'shows the activity to a logged-in user with permissions' do
+ sign_in(author)
+ visit activity_project_path(project)
+
+ expect(page).to have_content(message)
+ end
+
+ it 'hides the activity from a logged-in user without permissions' do
+ sign_in(user)
+ visit activity_project_path(project)
+
+ expect(page).not_to have_content(message)
+ end
+
+ it 'hides the activity from an anonymous user' do
+ visit activity_project_path(project)
+
+ expect(page).not_to have_content(message)
+ end
+end
diff --git a/spec/features/projects/import_export/export_file_spec.rb b/spec/features/projects/import_export/export_file_spec.rb
index eb281cd2122..8a418356541 100644
--- a/spec/features/projects/import_export/export_file_spec.rb
+++ b/spec/features/projects/import_export/export_file_spec.rb
@@ -25,7 +25,6 @@ describe 'Import/Export - project export integration test', :js do
before do
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
- stub_feature_flags(import_export_object_storage: false)
end
after do
diff --git a/spec/features/projects/import_export/import_file_object_storage_spec.rb b/spec/features/projects/import_export/import_file_object_storage_spec.rb
deleted file mode 100644
index 0d364543916..00000000000
--- a/spec/features/projects/import_export/import_file_object_storage_spec.rb
+++ /dev/null
@@ -1,103 +0,0 @@
-require 'spec_helper'
-
-describe 'Import/Export - project import integration test', :js do
- include Select2Helper
-
- let(:user) { create(:user) }
- let(:file) { File.join(Rails.root, 'spec', 'features', 'projects', 'import_export', 'test_project_export.tar.gz') }
- let(:export_path) { "#{Dir.tmpdir}/import_file_spec" }
-
- before do
- stub_feature_flags(import_export_object_storage: true)
- stub_uploads_object_storage(FileUploader)
- allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
- gitlab_sign_in(user)
- end
-
- after do
- FileUtils.rm_rf(export_path, secure: true)
- end
-
- context 'when selecting the namespace' do
- let(:user) { create(:admin) }
- let!(:namespace) { user.namespace }
- let(:project_path) { 'test-project-path' + SecureRandom.hex }
-
- context 'prefilled the path' do
- it 'user imports an exported project successfully' do
- visit new_project_path
-
- select2(namespace.id, from: '#project_namespace_id')
- fill_in :project_path, with: project_path, visible: true
- click_import_project_tab
- click_link 'GitLab export'
-
- expect(page).to have_content('Import an exported GitLab project')
- expect(URI.parse(current_url).query).to eq("namespace_id=#{namespace.id}&path=#{project_path}")
-
- attach_file('file', file)
- click_on 'Import project'
-
- expect(Project.count).to eq(1)
-
- project = Project.last
- expect(project).not_to be_nil
- expect(project.description).to eq("Foo Bar")
- expect(project.issues).not_to be_empty
- expect(project.merge_requests).not_to be_empty
- expect(project_hook_exists?(project)).to be true
- expect(wiki_exists?(project)).to be true
- expect(project.import_state.status).to eq('finished')
- end
- end
-
- context 'path is not prefilled' do
- it 'user imports an exported project successfully' do
- visit new_project_path
- click_import_project_tab
- click_link 'GitLab export'
-
- fill_in :path, with: 'test-project-path', visible: true
- attach_file('file', file)
-
- expect { click_on 'Import project' }.to change { Project.count }.by(1)
-
- project = Project.last
- expect(project).not_to be_nil
- expect(page).to have_content("Project 'test-project-path' is being imported")
- end
- end
- end
-
- it 'invalid project' do
- project = create(:project, namespace: user.namespace)
-
- visit new_project_path
-
- select2(user.namespace.id, from: '#project_namespace_id')
- fill_in :project_path, with: project.name, visible: true
- click_import_project_tab
- click_link 'GitLab export'
- attach_file('file', file)
- click_on 'Import project'
-
- page.within('.flash-container') do
- expect(page).to have_content('Project could not be imported')
- end
- end
-
- def wiki_exists?(project)
- wiki = ProjectWiki.new(project)
- wiki.repository.exists? && !wiki.repository.empty?
- end
-
- def project_hook_exists?(project)
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- Gitlab::Git::Hook.new('post-receive', project.repository.raw_repository).exists?
- end
- end
-
- def click_import_project_tab
- find('#import-project-tab').click
- end
-end
diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb
index 2d86115de12..65c68277167 100644
--- a/spec/features/projects/import_export/import_file_spec.rb
+++ b/spec/features/projects/import_export/import_file_spec.rb
@@ -2,13 +2,14 @@ require 'spec_helper'
describe 'Import/Export - project import integration test', :js do
include Select2Helper
+ include GitHelpers
let(:user) { create(:user) }
let(:file) { File.join(Rails.root, 'spec', 'features', 'projects', 'import_export', 'test_project_export.tar.gz') }
let(:export_path) { "#{Dir.tmpdir}/import_file_spec" }
before do
- stub_feature_flags(import_export_object_storage: false)
+ stub_uploads_object_storage(FileUploader)
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
gitlab_sign_in(user)
end
@@ -20,6 +21,7 @@ describe 'Import/Export - project import integration test', :js do
context 'when selecting the namespace' do
let(:user) { create(:admin) }
let!(:namespace) { user.namespace }
+ let(:project_name) { 'Test Project Name' + SecureRandom.hex }
let(:project_path) { 'test-project-path' + SecureRandom.hex }
context 'prefilled the path' do
@@ -27,13 +29,13 @@ describe 'Import/Export - project import integration test', :js do
visit new_project_path
select2(namespace.id, from: '#project_namespace_id')
+ fill_in :project_name, with: project_name, visible: true
fill_in :project_path, with: project_path, visible: true
click_import_project_tab
click_link 'GitLab export'
expect(page).to have_content('Import an exported GitLab project')
- expect(URI.parse(current_url).query).to eq("namespace_id=#{namespace.id}&path=#{project_path}")
- expect(Gitlab::ImportExport).to receive(:import_upload_path).with(filename: /\A\h{32}\z/).and_call_original
+ expect(URI.parse(current_url).query).to eq("namespace_id=#{namespace.id}&name=#{ERB::Util.url_encode(project_name)}&path=#{project_path}")
attach_file('file', file)
click_on 'Import project'
@@ -57,6 +59,7 @@ describe 'Import/Export - project import integration test', :js do
click_import_project_tab
click_link 'GitLab export'
+ fill_in :name, with: 'Test Project Name', visible: true
fill_in :path, with: 'test-project-path', visible: true
attach_file('file', file)
@@ -75,7 +78,8 @@ describe 'Import/Export - project import integration test', :js do
visit new_project_path
select2(user.namespace.id, from: '#project_namespace_id')
- fill_in :project_path, with: project.name, visible: true
+ fill_in :project_name, with: project.name, visible: true
+ fill_in :project_path, with: project.path, visible: true
click_import_project_tab
click_link 'GitLab export'
attach_file('file', file)
@@ -91,12 +95,6 @@ describe 'Import/Export - project import integration test', :js do
wiki.repository.exists? && !wiki.repository.empty?
end
- def project_hook_exists?(project)
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- Gitlab::Git::Hook.new('post-receive', project.repository.raw_repository).exists?
- end
- end
-
def click_import_project_tab
find('#import-project-tab').click
end
diff --git a/spec/features/projects/import_export/namespace_export_file_spec.rb b/spec/features/projects/import_export/namespace_export_file_spec.rb
deleted file mode 100644
index 9bb8a2063b5..00000000000
--- a/spec/features/projects/import_export/namespace_export_file_spec.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-require 'spec_helper'
-
-describe 'Import/Export - Namespace export file cleanup', :js do
- let(:export_path) { Dir.mktmpdir('namespace_export_file_spec') }
-
- before do
- allow(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
- stub_feature_flags(import_export_object_storage: false)
- end
-
- after do
- FileUtils.rm_rf(export_path, secure: true)
- end
-
- shared_examples_for 'handling project exports on namespace change' do
- let!(:old_export_path) { project.export_path }
-
- before do
- sign_in(create(:admin))
-
- setup_export_project
- end
-
- context 'moving the namespace' do
- it 'removes the export file' do
- expect(File).to exist(old_export_path)
-
- project.namespace.update!(path: build(:namespace).path)
-
- expect(File).not_to exist(old_export_path)
- end
- end
-
- context 'deleting the namespace' do
- it 'removes the export file' do
- expect(File).to exist(old_export_path)
-
- project.namespace.destroy
-
- expect(File).not_to exist(old_export_path)
- end
- end
- end
-
- describe 'legacy storage' do
- let(:project) { create(:project, :legacy_storage) }
-
- it_behaves_like 'handling project exports on namespace change'
- end
-
- describe 'hashed storage' do
- let(:project) { create(:project) }
-
- it_behaves_like 'handling project exports on namespace change'
- end
-
- def setup_export_project
- visit edit_project_path(project)
-
- expect(page).to have_content('Export project')
-
- find(:link, 'Export project').send_keys(:return)
-
- visit edit_project_path(project)
-
- expect(page).to have_content('Download export')
- end
-end
diff --git a/spec/features/projects/import_export/test_project_export.tar.gz b/spec/features/projects/import_export/test_project_export.tar.gz
index 3b5df47e0b6..730e586b278 100644
--- a/spec/features/projects/import_export/test_project_export.tar.gz
+++ b/spec/features/projects/import_export/test_project_export.tar.gz
Binary files differ
diff --git a/spec/features/projects/labels/sort_labels_spec.rb b/spec/features/projects/labels/sort_labels_spec.rb
new file mode 100644
index 00000000000..01c3f251173
--- /dev/null
+++ b/spec/features/projects/labels/sort_labels_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Sort labels', :js do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let!(:label1) { create(:label, title: 'Foo', description: 'Lorem ipsum', project: project) }
+ let!(:label2) { create(:label, title: 'Bar', description: 'Fusce consequat', project: project) }
+
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+
+ visit project_labels_path(project)
+ end
+
+ it 'sorts by title by default' do
+ expect(page).to have_button('Name')
+
+ # assert default sorting
+ within '.other-labels' do
+ expect(page.all('.label-list-item').first.text).to include('Bar')
+ expect(page.all('.label-list-item').last.text).to include('Foo')
+ end
+ end
+
+ it 'sorts by date' do
+ click_button 'Name'
+
+ sort_options = find('ul.dropdown-menu-sort li').all('a').collect(&:text)
+
+ expect(sort_options[0]).to eq('Name')
+ expect(sort_options[1]).to eq('Name, descending')
+ expect(sort_options[2]).to eq('Last created')
+ expect(sort_options[3]).to eq('Oldest created')
+ expect(sort_options[4]).to eq('Last updated')
+ expect(sort_options[5]).to eq('Oldest updated')
+
+ click_link 'Name, descending'
+
+ # assert default sorting
+ within '.other-labels' do
+ expect(page.all('.label-list-item').first.text).to include('Foo')
+ expect(page.all('.label-list-item').last.text).to include('Bar')
+ end
+ end
+end
diff --git a/spec/features/projects/members/share_with_group_spec.rb b/spec/features/projects/members/invite_group_spec.rb
index c6d85e5d22f..0fb3eb20b5b 100644
--- a/spec/features/projects/members/share_with_group_spec.rb
+++ b/spec/features/projects/members/invite_group_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Project > Members > Share with Group', :js do
+describe 'Project > Members > Invite group', :js do
include Select2Helper
include ActionView::Helpers::DateHelper
@@ -8,17 +8,16 @@ describe 'Project > Members > Share with Group', :js do
describe 'Share with group lock' do
shared_examples 'the project can be shared with groups' do
- it 'the "Share with group" tab exists' do
+ it 'the "Invite group" tab exists' do
visit project_settings_members_path(project)
- expect(page).to have_selector('#share-with-group-tab')
+ expect(page).to have_selector('#invite-group-tab')
end
end
shared_examples 'the project cannot be shared with groups' do
- it 'the "Share with group" tab does not exist' do
+ it 'the "Invite group" tab does not exist' do
visit project_settings_members_path(project)
- expect(page).to have_selector('#add-member-tab')
- expect(page).not_to have_selector('#share-with-group-tab')
+ expect(page).not_to have_selector('#invite-group-tab')
end
end
@@ -37,7 +36,7 @@ describe 'Project > Members > Share with Group', :js do
it 'the project can be shared with another group' do
visit project_settings_members_path(project)
- click_on 'share-with-group-tab'
+ click_on 'invite-group-tab'
select2 group_to_share_with.id, from: '#link_group_id'
page.find('body').click
@@ -117,12 +116,12 @@ describe 'Project > Members > Share with Group', :js do
visit project_settings_members_path(project)
- click_on 'share-with-group-tab'
+ click_on 'invite-group-tab'
select2 group.id, from: '#link_group_id'
fill_in 'expires_at_groups', with: (Time.now + 4.5.days).strftime('%Y-%m-%d')
- click_on 'share-with-group-tab'
+ click_on 'invite-group-tab'
find('.btn-create').click
end
@@ -150,7 +149,7 @@ describe 'Project > Members > Share with Group', :js do
visit project_settings_members_path(project)
- click_link 'Share with group'
+ click_link 'Invite group'
find('.ajax-groups-select.select2-container')
@@ -183,7 +182,7 @@ describe 'Project > Members > Share with Group', :js do
it 'the groups dropdown does not show ancestors', :nested_groups do
visit project_settings_members_path(project)
- click_on 'share-with-group-tab'
+ click_on 'invite-group-tab'
click_link 'Search for a group'
page.within '.select2-drop' do
diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb
index bbe08ff83ff..0acd5059385 100644
--- a/spec/features/projects/new_project_spec.rb
+++ b/spec/features/projects/new_project_spec.rb
@@ -12,8 +12,9 @@ describe 'New project' do
it 'shows "New project" page', :js do
visit new_project_path
- expect(page).to have_content('Project path')
expect(page).to have_content('Project name')
+ expect(page).to have_content('Project URL')
+ expect(page).to have_content('Project slug')
find('#import-project-tab').click
@@ -187,7 +188,7 @@ describe 'New project' do
collision_project = create(:project, name: 'test-name-collision', namespace: user.namespace)
fill_in 'project_import_url', with: collision_project.http_url_to_repo
- fill_in 'project_path', with: collision_project.path
+ fill_in 'project_name', with: collision_project.name
click_on 'Create project'
diff --git a/spec/features/projects/settings/user_manages_group_links_spec.rb b/spec/features/projects/settings/user_manages_group_links_spec.rb
index 2f1824d7849..676659b90c3 100644
--- a/spec/features/projects/settings/user_manages_group_links_spec.rb
+++ b/spec/features/projects/settings/user_manages_group_links_spec.rb
@@ -26,13 +26,13 @@ describe 'Projects > Settings > User manages group links' do
end
end
- it 'shares a project with a group', :js do
- click_link('Share with group')
+ it 'invites a group to a project', :js do
+ click_link('Invite group')
select2(group_market.id, from: '#link_group_id')
select('Maintainer', from: 'link_group_access')
- click_button('Share')
+ click_button('Invite')
page.within('.project-members-groups') do
expect(page).to have_content('Market')
diff --git a/spec/features/projects/show/user_interacts_with_auto_devops_banner_spec.rb b/spec/features/projects/show/user_interacts_with_auto_devops_banner_spec.rb
new file mode 100644
index 00000000000..baf715000a3
--- /dev/null
+++ b/spec/features/projects/show/user_interacts_with_auto_devops_banner_spec.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Project > Show > User interacts with auto devops implicitly enabled banner' do
+ let(:project) { create(:project, :repository) }
+ let(:user) { create(:user) }
+
+ before do
+ project.add_user(user, role)
+ sign_in(user)
+ end
+
+ context 'when user does not have maintainer access' do
+ let(:role) { :developer }
+
+ context 'when AutoDevOps is implicitly enabled' do
+ it 'does not display AutoDevOps implicitly enabled banner' do
+ expect(page).not_to have_css('.auto-devops-implicitly-enabled-banner')
+ end
+ end
+ end
+
+ context 'when user has mantainer access' do
+ let(:role) { :maintainer }
+
+ context 'when AutoDevOps is implicitly enabled' do
+ before do
+ stub_application_setting(auto_devops_enabled: true)
+
+ visit project_path(project)
+ end
+
+ it 'display AutoDevOps implicitly enabled banner' do
+ expect(page).to have_css('.auto-devops-implicitly-enabled-banner')
+ end
+
+ context 'when user dismisses the banner', :js do
+ it 'does not display AutoDevOps implicitly enabled banner' do
+ find('.hide-auto-devops-implicitly-enabled-banner').click
+ wait_for_requests
+ visit project_path(project)
+
+ expect(page).not_to have_css('.auto-devops-implicitly-enabled-banner')
+ end
+ end
+ end
+
+ context 'when AutoDevOps is not implicitly enabled' do
+ before do
+ stub_application_setting(auto_devops_enabled: false)
+
+ visit project_path(project)
+ end
+
+ it 'does not display AutoDevOps implicitly enabled banner' do
+ expect(page).not_to have_css('.auto-devops-implicitly-enabled-banner')
+ end
+ end
+ end
+end
diff --git a/spec/features/projects/user_creates_project_spec.rb b/spec/features/projects/user_creates_project_spec.rb
index 83d18996f4e..8d7e2883b2a 100644
--- a/spec/features/projects/user_creates_project_spec.rb
+++ b/spec/features/projects/user_creates_project_spec.rb
@@ -11,7 +11,7 @@ describe 'User creates a project', :js do
it 'creates a new project' do
visit(new_project_path)
- fill_in(:project_path, with: 'Empty')
+ fill_in(:project_name, with: 'Empty')
page.within('#content-body') do
click_button('Create project')
@@ -37,6 +37,7 @@ describe 'User creates a project', :js do
it 'creates a new project' do
visit(new_project_path)
+ fill_in :project_name, with: 'A Subgroup Project'
fill_in :project_path, with: 'a-subgroup-project'
page.find('.js-select-namespace').click
@@ -46,7 +47,7 @@ describe 'User creates a project', :js do
click_button('Create project')
end
- expect(page).to have_content("Project 'a-subgroup-project' was successfully created")
+ expect(page).to have_content("Project 'A Subgroup Project' was successfully created")
project = Project.last
diff --git a/spec/features/projects/wiki/user_deletes_wiki_page_spec.rb b/spec/features/projects/wiki/user_deletes_wiki_page_spec.rb
index 5007794cd77..18ccd31f3d0 100644
--- a/spec/features/projects/wiki/user_deletes_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_deletes_wiki_page_spec.rb
@@ -13,7 +13,7 @@ describe 'User deletes wiki page', :js do
it 'deletes a page' do
click_on('Edit')
click_on('Delete')
- find('.js-modal-primary-action').click
+ find('.modal-footer .btn-danger').click
expect(page).to have_content('Page was successfully deleted')
end
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index 22e3a99072f..8e310f38a8c 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -16,7 +16,7 @@ describe 'Project' do
it "allows creation from templates", :js do
find('#create-from-template-tab').click
find("label[for=#{template.name}]").click
- fill_in("project_path", with: template.name)
+ fill_in("project_name", with: template.name)
page.within '#content-body' do
click_button "Create project"
diff --git a/spec/features/usage_stats_consent_spec.rb b/spec/features/usage_stats_consent_spec.rb
new file mode 100644
index 00000000000..dd8f3179895
--- /dev/null
+++ b/spec/features/usage_stats_consent_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Usage stats consent' do
+ context 'when signed in' do
+ let(:user) { create(:admin, created_at: 8.days.ago) }
+ let(:message) { 'To help improve GitLab, we would like to periodically collect usage information.' }
+
+ before do
+ allow(user).to receive(:has_current_license?).and_return false
+
+ gitlab_sign_in(user)
+ end
+
+ it 'hides the banner permanently when sets usage stats' do
+ visit root_dashboard_path
+
+ expect(page).to have_content(message)
+
+ click_link 'Send usage data'
+
+ expect(page).not_to have_content(message)
+ expect(page).to have_content('Application settings saved successfully')
+
+ gitlab_sign_out
+ gitlab_sign_in(user)
+ visit root_dashboard_path
+
+ expect(page).not_to have_content(message)
+ end
+
+ it 'shows banner on next session if user did not set usage stats' do
+ visit root_dashboard_path
+
+ expect(page).to have_content(message)
+
+ gitlab_sign_out
+ gitlab_sign_in(user)
+ visit root_dashboard_path
+
+ expect(page).to have_content(message)
+ end
+ end
+end
diff --git a/spec/finders/contributed_projects_finder_spec.rb b/spec/finders/contributed_projects_finder_spec.rb
index 9155a8d6fe9..81fb4e3561c 100644
--- a/spec/finders/contributed_projects_finder_spec.rb
+++ b/spec/finders/contributed_projects_finder_spec.rb
@@ -8,6 +8,7 @@ describe ContributedProjectsFinder do
let!(:public_project) { create(:project, :public) }
let!(:private_project) { create(:project, :private) }
+ let!(:internal_project) { create(:project, :internal) }
before do
private_project.add_maintainer(source_user)
@@ -16,17 +17,18 @@ describe ContributedProjectsFinder do
create(:push_event, project: public_project, author: source_user)
create(:push_event, project: private_project, author: source_user)
+ create(:push_event, project: internal_project, author: source_user)
end
- describe 'without a current user' do
+ describe 'activity without a current user' do
subject { finder.execute }
- it { is_expected.to eq([public_project]) }
+ it { is_expected.to match_array([public_project]) }
end
- describe 'with a current user' do
+ describe 'activity with a current user' do
subject { finder.execute(current_user) }
- it { is_expected.to eq([private_project, public_project]) }
+ it { is_expected.to match_array([private_project, internal_project, public_project]) }
end
end
diff --git a/spec/finders/group_descendants_finder_spec.rb b/spec/finders/group_descendants_finder_spec.rb
index 796d40cb625..c64abdc3619 100644
--- a/spec/finders/group_descendants_finder_spec.rb
+++ b/spec/finders/group_descendants_finder_spec.rb
@@ -108,6 +108,15 @@ describe GroupDescendantsFinder do
end
end
end
+
+ it 'does not include projects shared with the group' do
+ project = create(:project, namespace: group)
+ other_project = create(:project)
+ other_project.project_group_links.create(group: group,
+ group_access: ProjectGroupLink::MASTER)
+
+ expect(finder.execute).to contain_exactly(project)
+ end
end
context 'with nested groups', :nested_groups do
diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb
index 7931ad9b9f0..590e838f13e 100644
--- a/spec/finders/projects_finder_spec.rb
+++ b/spec/finders/projects_finder_spec.rb
@@ -174,6 +174,13 @@ describe ProjectsFinder do
end
end
+ describe 'filter by without_deleted' do
+ let(:params) { { without_deleted: true } }
+ let!(:pending_delete_project) { create(:project, :public, pending_delete: true) }
+
+ it { is_expected.to match_array([public_project, internal_project]) }
+ end
+
describe 'sorting' do
let(:params) { { sort: 'name_asc' } }
diff --git a/spec/finders/user_recent_events_finder_spec.rb b/spec/finders/user_recent_events_finder_spec.rb
index 58470f4c84d..c5fcd68eb4c 100644
--- a/spec/finders/user_recent_events_finder_spec.rb
+++ b/spec/finders/user_recent_events_finder_spec.rb
@@ -13,49 +13,25 @@ describe UserRecentEventsFinder do
subject(:finder) { described_class.new(current_user, project_owner) }
describe '#execute' do
- context 'current user does not have access to projects' do
- it 'returns public and internal events' do
- records = finder.execute
-
- expect(records).to include(public_event, internal_event)
- expect(records).not_to include(private_event)
+ context 'when profile is public' do
+ it 'returns all the events' do
+ expect(finder.execute).to include(private_event, internal_event, public_event)
end
end
- context 'when current user has access to the projects' do
- before do
- private_project.add_developer(current_user)
- internal_project.add_developer(current_user)
- public_project.add_developer(current_user)
- end
-
- context 'when profile is public' do
- it 'returns all the events' do
- expect(finder.execute).to include(private_event, internal_event, public_event)
- end
- end
-
- context 'when profile is private' do
- it 'returns no event' do
- allow(Ability).to receive(:allowed?).and_call_original
- allow(Ability).to receive(:allowed?).with(current_user, :read_user_profile, project_owner).and_return(false)
- expect(finder.execute).to be_empty
- end
- end
+ context 'when profile is private' do
+ it 'returns no event' do
+ allow(Ability).to receive(:allowed?).and_call_original
+ allow(Ability).to receive(:allowed?).with(current_user, :read_user_profile, project_owner).and_return(false)
- it 'does not include the events if the user cannot read cross project' do
- expect(Ability).to receive(:allowed?).and_call_original
- expect(Ability).to receive(:allowed?).with(current_user, :read_cross_project) { false }
expect(finder.execute).to be_empty
end
end
- context 'when current user is anonymous' do
- let(:current_user) { nil }
-
- it 'returns public events only' do
- expect(finder.execute).to eq([public_event])
- end
+ it 'does not include the events if the user cannot read cross project' do
+ expect(Ability).to receive(:allowed?).and_call_original
+ expect(Ability).to receive(:allowed?).with(current_user, :read_cross_project) { false }
+ expect(finder.execute).to be_empty
end
end
end
diff --git a/spec/fixtures/api/schemas/job/job.json b/spec/fixtures/api/schemas/job/job.json
index c793d93c0f6..f5d58b21e3d 100644
--- a/spec/fixtures/api/schemas/job/job.json
+++ b/spec/fixtures/api/schemas/job/job.json
@@ -25,7 +25,7 @@
"playable": { "type": "boolean" },
"created_at": { "type": "string" },
"updated_at": { "type": "string" },
- "status": { "$ref": "../ci_detailed_status.json" }
+ "status": { "$ref": "../status/ci_detailed_status.json" }
},
"additionalProperties": true
}
diff --git a/spec/fixtures/api/schemas/job/job_details.json b/spec/fixtures/api/schemas/job/job_details.json
index b8c099250be..b82f7413b50 100644
--- a/spec/fixtures/api/schemas/job/job_details.json
+++ b/spec/fixtures/api/schemas/job/job_details.json
@@ -1,8 +1,11 @@
{
- "allOf": [{ "$ref": "job.json" }],
+ "allOf": [
+ { "$ref": "job.json" }
+ ],
"description": "An extension of job.json with more detailed information",
"properties": {
"artifact": { "$ref": "artifact.json" },
- "terminal_path": { "type": "string" }
+ "terminal_path": { "type": "string" },
+ "trigger": { "$ref": "trigger.json" }
}
}
diff --git a/spec/fixtures/api/schemas/job/trigger.json b/spec/fixtures/api/schemas/job/trigger.json
new file mode 100644
index 00000000000..1c7e9cc7693
--- /dev/null
+++ b/spec/fixtures/api/schemas/job/trigger.json
@@ -0,0 +1,28 @@
+{
+ "type": "object",
+ "required": [
+ "short_token",
+ "variables"
+ ],
+ "properties": {
+ "short_token": { "type": "string" },
+ "variables": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": [
+ "key",
+ "value",
+ "public"
+ ],
+ "properties": {
+ "key": { "type": "string" },
+ "value": { "type": "string" },
+ "public": { "type": "boolean" }
+ },
+ "additionalProperties": false
+ }
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/pipeline_stage.json b/spec/fixtures/api/schemas/pipeline_stage.json
index eb2667295f0..f72988a3d3d 100644
--- a/spec/fixtures/api/schemas/pipeline_stage.json
+++ b/spec/fixtures/api/schemas/pipeline_stage.json
@@ -16,7 +16,7 @@
"items": { "$ref": "job/job.json" },
"optional": true
},
- "status": { "$ref": "ci_detailed_status.json" },
+ "status": { "$ref": "status/ci_detailed_status.json" },
"path": { "type": "string" },
"dropdown_path": { "type": "string" }
},
diff --git a/spec/fixtures/api/schemas/status/action.json b/spec/fixtures/api/schemas/status/action.json
new file mode 100644
index 00000000000..99a576e6c5b
--- /dev/null
+++ b/spec/fixtures/api/schemas/status/action.json
@@ -0,0 +1,22 @@
+{
+ "type": "object",
+ "required": [
+ "icon",
+ "title",
+ "path",
+ "method"
+ ],
+ "properties": {
+ "icon": {
+ "type": "string",
+ "enum": [
+ "retry",
+ "play",
+ "cancel"
+ ]
+ },
+ "title": { "type": "string" },
+ "path": { "type": "string" },
+ "method": { "$ref": "../http_method.json" }
+ }
+}
diff --git a/spec/fixtures/api/schemas/ci_detailed_status.json b/spec/fixtures/api/schemas/status/ci_detailed_status.json
index d74248eabef..8d0f1e4a6af 100644
--- a/spec/fixtures/api/schemas/ci_detailed_status.json
+++ b/spec/fixtures/api/schemas/status/ci_detailed_status.json
@@ -1,6 +1,6 @@
{
"type": "object",
- "required" : [
+ "required": [
"icon",
"text",
"label",
@@ -19,28 +19,8 @@
"has_details": { "type": "boolean" },
"details_path": { "type": "string" },
"favicon": { "type": "string" },
- "action": {
- "type": "object",
- "required": [
- "icon",
- "title",
- "path",
- "method"
- ],
- "properties": {
- "icon": {
- "type": "string",
- "enum": [
- "retry",
- "play",
- "cancel"
- ]
- },
- "title": { "type": "string" },
- "path": { "type": "string" },
- "method": { "$ref": "http_method.json" }
- }
- }
+ "illustration": { "$ref": "illustration.json" },
+ "action": { "$ref": "action.json" }
},
"additionalProperties": false
}
diff --git a/spec/fixtures/api/schemas/status/illustration.json b/spec/fixtures/api/schemas/status/illustration.json
new file mode 100644
index 00000000000..9a085f5f1ee
--- /dev/null
+++ b/spec/fixtures/api/schemas/status/illustration.json
@@ -0,0 +1,19 @@
+{
+ "oneOf": [
+ { "type": "null" },
+ {
+ "type": "object",
+ "required": [
+ "image",
+ "size",
+ "title"
+ ],
+ "properties": {
+ "image": { "type": "string" },
+ "size": { "type": "string" },
+ "title": { "type": "string" },
+ "content": { "type": "string" }
+ }
+ }
+ ]
+}
diff --git a/spec/fixtures/gitlab/ci/external_files/.gitlab-ci-template-1.yml b/spec/fixtures/gitlab/ci/external_files/.gitlab-ci-template-1.yml
new file mode 100644
index 00000000000..0bab94a7c2e
--- /dev/null
+++ b/spec/fixtures/gitlab/ci/external_files/.gitlab-ci-template-1.yml
@@ -0,0 +1,10 @@
+before_script:
+ - apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs
+ - ruby -v
+ - which ruby
+ - gem install bundler --no-ri --no-rdoc
+ - bundle install --jobs $(nproc) "${FLAGS[@]}"
+
+rspec:
+ script:
+ - bundle exec rspec
diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb
index f76ed4bfda4..4af98bc3678 100644
--- a/spec/helpers/issuables_helper_spec.rb
+++ b/spec/helpers/issuables_helper_spec.rb
@@ -184,7 +184,7 @@ describe IssuablesHelper do
issuableRef: "##{issue.iid}",
markdownPreviewPath: "/#{@project.full_path}/preview_markdown",
markdownDocsPath: '/help/user/markdown',
- markdownVersion: 11,
+ markdownVersion: CacheMarkdownField::CACHE_COMMONMARK_VERSION,
issuableTemplates: [],
projectPath: @project.path,
projectNamespace: @project.namespace.path,
diff --git a/spec/javascripts/boards/mock_data.js b/spec/javascripts/boards/mock_data.js
index 81f1a97112f..f380ef450db 100644
--- a/spec/javascripts/boards/mock_data.js
+++ b/spec/javascripts/boards/mock_data.js
@@ -27,7 +27,7 @@ export const listObjDuplicate = {
export const BoardsMockData = {
GET: {
- '/test/-/boards/1/lists/300/issues?id=300&page=1&=': {
+ '/test/-/boards/1/lists/300/issues?id=300&page=1': {
issues: [
{
title: 'Testing',
diff --git a/spec/javascripts/boards/utils/query_data_spec.js b/spec/javascripts/boards/utils/query_data_spec.js
deleted file mode 100644
index 922215ffc1d..00000000000
--- a/spec/javascripts/boards/utils/query_data_spec.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import queryData from '~/boards/utils/query_data';
-
-describe('queryData', () => {
- it('parses path for label with trailing +', () => {
- expect(
- queryData('label_name[]=label%2B', {}),
- ).toEqual({
- label_name: ['label+'],
- });
- });
-
- it('parses path for milestone with trailing +', () => {
- expect(
- queryData('milestone_title=A%2B', {}),
- ).toEqual({
- milestone_title: 'A+',
- });
- });
-
- it('parses path for search terms with spaces', () => {
- expect(
- queryData('search=two+words', {}),
- ).toEqual({
- search: 'two words',
- });
- });
-});
diff --git a/spec/javascripts/diffs/components/diff_file_spec.js b/spec/javascripts/diffs/components/diff_file_spec.js
index 44a38f7ca82..845fef23db6 100644
--- a/spec/javascripts/diffs/components/diff_file_spec.js
+++ b/spec/javascripts/diffs/components/diff_file_spec.js
@@ -51,7 +51,9 @@ describe('DiffFile', () => {
});
it('should have collapsed text and link', done => {
- vm.file.collapsed = true;
+ vm.file.renderIt = true;
+ vm.file.collapsed = false;
+ vm.file.highlightedDiffLines = null;
vm.$nextTick(() => {
expect(vm.$el.innerText).toContain('This diff is collapsed');
diff --git a/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js b/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js
index a1a37b342b7..663c0680845 100644
--- a/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js
+++ b/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js
@@ -6,61 +6,61 @@ import discussionsMockData from '../mock_data/diff_discussions';
import diffFileMockData from '../mock_data/diff_file';
describe('DiffLineGutterContent', () => {
- const getDiscussionsMockData = () => [Object.assign({}, discussionsMockData)];
const getDiffFileMock = () => Object.assign({}, diffFileMockData);
const createComponent = (options = {}) => {
const cmp = Vue.extend(DiffLineGutterContent);
const props = Object.assign({}, options);
+ props.line = {
+ lineCode: 'LC_42',
+ type: 'new',
+ oldLine: null,
+ newLine: 1,
+ discussions: [],
+ text: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n',
+ richText: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n',
+ metaData: null,
+ };
props.fileHash = getDiffFileMock().fileHash;
props.contextLinesPath = '/context/lines/path';
return createComponentWithStore(cmp, store, props).$mount();
};
- const setDiscussions = component => {
- component.$store.dispatch('setInitialNotes', getDiscussionsMockData());
- };
-
- const resetDiscussions = component => {
- component.$store.dispatch('setInitialNotes', []);
- };
describe('computed', () => {
describe('lineHref', () => {
it('should prepend # to lineCode', () => {
const lineCode = 'LC_42';
- const component = createComponent({ lineCode });
+ const component = createComponent();
expect(component.lineHref).toEqual(`#${lineCode}`);
});
it('should return # if there is no lineCode', () => {
- const component = createComponent({ lineCode: null });
+ const component = createComponent();
+ component.line.lineCode = '';
expect(component.lineHref).toEqual('#');
});
});
describe('discussions, hasDiscussions, shouldShowAvatarsOnGutter', () => {
it('should return empty array when there is no discussion', () => {
- const component = createComponent({ lineCode: 'LC_42' });
- expect(component.discussions).toEqual([]);
+ const component = createComponent();
expect(component.hasDiscussions).toEqual(false);
expect(component.shouldShowAvatarsOnGutter).toEqual(false);
});
it('should return discussions for the given lineCode', () => {
- const { lineCode } = getDiffFileMock().highlightedDiffLines[1];
- const component = createComponent({
- lineCode,
+ const cmp = Vue.extend(DiffLineGutterContent);
+ const props = {
+ line: getDiffFileMock().highlightedDiffLines[1],
+ fileHash: getDiffFileMock().fileHash,
showCommentButton: true,
- discussions: getDiscussionsMockData(),
- });
+ contextLinesPath: '/context/lines/path',
+ };
+ props.line.discussions = [Object.assign({}, discussionsMockData)];
+ const component = createComponentWithStore(cmp, store, props).$mount();
- setDiscussions(component);
-
- expect(component.discussions).toEqual(getDiscussionsMockData());
expect(component.hasDiscussions).toEqual(true);
expect(component.shouldShowAvatarsOnGutter).toEqual(true);
-
- resetDiscussions(component);
});
});
});
@@ -104,9 +104,7 @@ describe('DiffLineGutterContent', () => {
lineCode: getDiffFileMock().highlightedDiffLines[1].lineCode,
});
- setDiscussions(component);
expect(component.$el.querySelector('.diff-comment-avatar-holders')).toBeDefined();
- resetDiscussions(component);
});
});
});
diff --git a/spec/javascripts/diffs/components/parallel_diff_view_spec.js b/spec/javascripts/diffs/components/parallel_diff_view_spec.js
index 165e4b69b6c..091e01868d3 100644
--- a/spec/javascripts/diffs/components/parallel_diff_view_spec.js
+++ b/spec/javascripts/diffs/components/parallel_diff_view_spec.js
@@ -18,11 +18,11 @@ describe('ParallelDiffView', () => {
}).$mount();
});
- describe('computed', () => {
- describe('parallelDiffLines', () => {
+ describe('assigned', () => {
+ describe('diffLines', () => {
it('should normalize lines for empty cells', () => {
- expect(component.parallelDiffLines[0].left.type).toEqual(constants.EMPTY_CELL_TYPE);
- expect(component.parallelDiffLines[1].left.type).toEqual(constants.EMPTY_CELL_TYPE);
+ expect(component.diffLines[0].left.type).toEqual(constants.EMPTY_CELL_TYPE);
+ expect(component.diffLines[1].left.type).toEqual(constants.EMPTY_CELL_TYPE);
});
});
});
diff --git a/spec/javascripts/diffs/mock_data/diff_discussions.js b/spec/javascripts/diffs/mock_data/diff_discussions.js
index 41d0dfd8939..b29a22da7c2 100644
--- a/spec/javascripts/diffs/mock_data/diff_discussions.js
+++ b/spec/javascripts/diffs/mock_data/diff_discussions.js
@@ -16,7 +16,7 @@ export default {
expanded: true,
notes: [
{
- id: 1749,
+ id: '1749',
type: 'DiffNote',
attachment: null,
author: {
@@ -68,7 +68,7 @@ export default {
'/gitlab-org/gitlab-test/issues/new?discussion_to_resolve=6b232e05bea388c6b043ccc243ba505faac04ea8&merge_request_to_resolve_discussions_of=20',
},
{
- id: 1753,
+ id: '1753',
type: 'DiffNote',
attachment: null,
author: {
@@ -120,7 +120,7 @@ export default {
'/gitlab-org/gitlab-test/issues/new?discussion_to_resolve=6b232e05bea388c6b043ccc243ba505faac04ea8&merge_request_to_resolve_discussions_of=20',
},
{
- id: 1754,
+ id: '1754',
type: 'DiffNote',
attachment: null,
author: {
@@ -162,7 +162,7 @@ export default {
'/gitlab-org/gitlab-test/issues/new?discussion_to_resolve=6b232e05bea388c6b043ccc243ba505faac04ea8&merge_request_to_resolve_discussions_of=20',
},
{
- id: 1755,
+ id: '1755',
type: 'DiffNote',
attachment: null,
author: {
@@ -204,7 +204,7 @@ export default {
'/gitlab-org/gitlab-test/issues/new?discussion_to_resolve=6b232e05bea388c6b043ccc243ba505faac04ea8&merge_request_to_resolve_discussions_of=20',
},
{
- id: 1756,
+ id: '1756',
type: 'DiffNote',
attachment: null,
author: {
diff --git a/spec/javascripts/diffs/mock_data/diff_file.js b/spec/javascripts/diffs/mock_data/diff_file.js
index cce36ecc91f..372b8f066cf 100644
--- a/spec/javascripts/diffs/mock_data/diff_file.js
+++ b/spec/javascripts/diffs/mock_data/diff_file.js
@@ -49,6 +49,7 @@ export default {
type: 'new',
oldLine: null,
newLine: 1,
+ discussions: [],
text: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n',
richText: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n',
metaData: null,
@@ -58,6 +59,7 @@ export default {
type: 'new',
oldLine: null,
newLine: 2,
+ discussions: [],
text: '+<span id="LC2" class="line" lang="plaintext"></span>\n',
richText: '+<span id="LC2" class="line" lang="plaintext"></span>\n',
metaData: null,
@@ -67,6 +69,7 @@ export default {
type: null,
oldLine: 1,
newLine: 3,
+ discussions: [],
text: ' <span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n',
richText: ' <span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n',
metaData: null,
@@ -76,6 +79,7 @@ export default {
type: null,
oldLine: 2,
newLine: 4,
+ discussions: [],
text: ' <span id="LC4" class="line" lang="plaintext"></span>\n',
richText: ' <span id="LC4" class="line" lang="plaintext"></span>\n',
metaData: null,
@@ -85,6 +89,7 @@ export default {
type: null,
oldLine: 3,
newLine: 5,
+ discussions: [],
text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n',
richText: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n',
metaData: null,
@@ -94,6 +99,7 @@ export default {
type: 'match',
oldLine: null,
newLine: null,
+ discussions: [],
text: '',
richText: '',
metaData: {
@@ -112,6 +118,7 @@ export default {
type: 'new',
oldLine: null,
newLine: 1,
+ discussions: [],
text: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n',
richText: '<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n',
metaData: null,
@@ -126,6 +133,7 @@ export default {
type: 'new',
oldLine: null,
newLine: 2,
+ discussions: [],
text: '+<span id="LC2" class="line" lang="plaintext"></span>\n',
richText: '<span id="LC2" class="line" lang="plaintext"></span>\n',
metaData: null,
@@ -137,6 +145,7 @@ export default {
type: null,
oldLine: 1,
newLine: 3,
+ discussions: [],
text: ' <span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n',
richText: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n',
metaData: null,
@@ -146,6 +155,7 @@ export default {
type: null,
oldLine: 1,
newLine: 3,
+ discussions: [],
text: ' <span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n',
richText: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n',
metaData: null,
@@ -157,6 +167,7 @@ export default {
type: null,
oldLine: 2,
newLine: 4,
+ discussions: [],
text: ' <span id="LC4" class="line" lang="plaintext"></span>\n',
richText: '<span id="LC4" class="line" lang="plaintext"></span>\n',
metaData: null,
@@ -166,6 +177,7 @@ export default {
type: null,
oldLine: 2,
newLine: 4,
+ discussions: [],
text: ' <span id="LC4" class="line" lang="plaintext"></span>\n',
richText: '<span id="LC4" class="line" lang="plaintext"></span>\n',
metaData: null,
@@ -177,6 +189,7 @@ export default {
type: null,
oldLine: 3,
newLine: 5,
+ discussions: [],
text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n',
richText: '<span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n',
metaData: null,
@@ -186,6 +199,7 @@ export default {
type: null,
oldLine: 3,
newLine: 5,
+ discussions: [],
text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n',
richText: '<span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n',
metaData: null,
@@ -197,6 +211,7 @@ export default {
type: 'match',
oldLine: null,
newLine: null,
+ discussions: [],
text: '',
richText: '',
metaData: {
@@ -209,6 +224,7 @@ export default {
type: 'match',
oldLine: null,
newLine: null,
+ discussions: [],
text: '',
richText: '',
metaData: {
diff --git a/spec/javascripts/diffs/store/actions_spec.js b/spec/javascripts/diffs/store/actions_spec.js
index c1560dac1a0..cfb8f862598 100644
--- a/spec/javascripts/diffs/store/actions_spec.js
+++ b/spec/javascripts/diffs/store/actions_spec.js
@@ -7,10 +7,30 @@ import {
} from '~/diffs/constants';
import * as actions from '~/diffs/store/actions';
import * as types from '~/diffs/store/mutation_types';
+import { reduceDiscussionsToLineCodes } from '~/notes/stores/utils';
import axios from '~/lib/utils/axios_utils';
import testAction from '../../helpers/vuex_action_helper';
describe('DiffsStoreActions', () => {
+ const originalMethods = {
+ requestAnimationFrame: global.requestAnimationFrame,
+ requestIdleCallback: global.requestIdleCallback,
+ };
+
+ beforeEach(() => {
+ ['requestAnimationFrame', 'requestIdleCallback'].forEach(method => {
+ global[method] = cb => {
+ cb();
+ };
+ });
+ });
+
+ afterEach(() => {
+ ['requestAnimationFrame', 'requestIdleCallback'].forEach(method => {
+ global[method] = originalMethods[method];
+ });
+ });
+
describe('setBaseConfig', () => {
it('should set given endpoint and project path', done => {
const endpoint = '/diffs/set/endpoint';
@@ -53,6 +73,198 @@ describe('DiffsStoreActions', () => {
});
});
+ describe('assignDiscussionsToDiff', () => {
+ it('should merge discussions into diffs', done => {
+ const state = {
+ diffFiles: [
+ {
+ fileHash: 'ABC',
+ parallelDiffLines: [
+ {
+ left: {
+ lineCode: 'ABC_1_1',
+ discussions: [],
+ },
+ right: {
+ lineCode: 'ABC_1_1',
+ discussions: [],
+ },
+ },
+ ],
+ highlightedDiffLines: [
+ {
+ lineCode: 'ABC_1_1',
+ discussions: [],
+ oldLine: 5,
+ newLine: null,
+ },
+ ],
+ diffRefs: {
+ baseSha: 'abc',
+ headSha: 'def',
+ startSha: 'ghi',
+ },
+ newPath: 'file1',
+ oldPath: 'file2',
+ },
+ ],
+ };
+
+ const diffPosition = {
+ baseSha: 'abc',
+ headSha: 'def',
+ startSha: 'ghi',
+ newLine: null,
+ newPath: 'file1',
+ oldLine: 5,
+ oldPath: 'file2',
+ };
+
+ const singleDiscussion = {
+ line_code: 'ABC_1_1',
+ diff_discussion: {},
+ diff_file: {
+ file_hash: 'ABC',
+ },
+ fileHash: 'ABC',
+ resolvable: true,
+ position: {
+ formatter: diffPosition,
+ },
+ original_position: {
+ formatter: diffPosition,
+ },
+ };
+
+ const discussions = reduceDiscussionsToLineCodes([singleDiscussion]);
+
+ testAction(
+ actions.assignDiscussionsToDiff,
+ discussions,
+ state,
+ [
+ {
+ type: types.SET_LINE_DISCUSSIONS_FOR_FILE,
+ payload: {
+ fileHash: 'ABC',
+ discussions: [singleDiscussion],
+ diffPositionByLineCode: {
+ ABC_1_1: {
+ baseSha: 'abc',
+ headSha: 'def',
+ startSha: 'ghi',
+ newLine: null,
+ newPath: 'file1',
+ oldLine: 5,
+ oldPath: 'file2',
+ },
+ },
+ },
+ },
+ ],
+ [],
+ () => {
+ done();
+ },
+ );
+ });
+ });
+
+ describe('removeDiscussionsFromDiff', () => {
+ it('should remove discussions from diffs', done => {
+ const state = {
+ diffFiles: [
+ {
+ fileHash: 'ABC',
+ parallelDiffLines: [
+ {
+ left: {
+ lineCode: 'ABC_1_1',
+ discussions: [
+ {
+ id: 1,
+ },
+ ],
+ },
+ right: {
+ lineCode: 'ABC_1_1',
+ discussions: [],
+ },
+ },
+ ],
+ highlightedDiffLines: [
+ {
+ lineCode: 'ABC_1_1',
+ discussions: [],
+ },
+ ],
+ },
+ ],
+ };
+ const singleDiscussion = {
+ fileHash: 'ABC',
+ line_code: 'ABC_1_1',
+ };
+
+ testAction(
+ actions.removeDiscussionsFromDiff,
+ singleDiscussion,
+ state,
+ [
+ {
+ type: types.REMOVE_LINE_DISCUSSIONS_FOR_FILE,
+ payload: {
+ fileHash: 'ABC',
+ lineCode: 'ABC_1_1',
+ },
+ },
+ ],
+ [],
+ () => {
+ done();
+ },
+ );
+ });
+ });
+
+ describe('startRenderDiffsQueue', () => {
+ it('should set all files to RENDER_FILE', done => {
+ const state = {
+ diffFiles: [
+ {
+ id: 1,
+ renderIt: false,
+ collapsed: false,
+ },
+ {
+ id: 2,
+ renderIt: false,
+ collapsed: false,
+ },
+ ],
+ };
+
+ const pseudoCommit = (commitType, file) => {
+ expect(commitType).toBe(types.RENDER_FILE);
+ Object.assign(file, {
+ renderIt: true,
+ });
+ };
+
+ actions
+ .startRenderDiffsQueue({ state, commit: pseudoCommit })
+ .then(() => {
+ expect(state.diffFiles[0].renderIt).toBeTruthy();
+ expect(state.diffFiles[1].renderIt).toBeTruthy();
+
+ done();
+ })
+ .catch(() => {
+ done.fail();
+ });
+ });
+ });
+
describe('setInlineDiffViewType', () => {
it('should set diff view type to inline and also set the cookie properly', done => {
testAction(
@@ -204,7 +416,11 @@ describe('DiffsStoreActions', () => {
actions.toggleFileDiscussions({ getters, dispatch });
- expect(dispatch).toHaveBeenCalledWith('collapseDiscussion', { discussionId: 1 }, { root: true });
+ expect(dispatch).toHaveBeenCalledWith(
+ 'collapseDiscussion',
+ { discussionId: 1 },
+ { root: true },
+ );
});
it('should dispatch expandDiscussion when all discussions are collapsed', () => {
@@ -218,7 +434,11 @@ describe('DiffsStoreActions', () => {
actions.toggleFileDiscussions({ getters, dispatch });
- expect(dispatch).toHaveBeenCalledWith('expandDiscussion', { discussionId: 1 }, { root: true });
+ expect(dispatch).toHaveBeenCalledWith(
+ 'expandDiscussion',
+ { discussionId: 1 },
+ { root: true },
+ );
});
it('should dispatch expandDiscussion when some discussions are collapsed and others are expanded for the collapsed discussion', () => {
@@ -232,7 +452,11 @@ describe('DiffsStoreActions', () => {
actions.toggleFileDiscussions({ getters, dispatch });
- expect(dispatch).toHaveBeenCalledWith('expandDiscussion', { discussionId: 1 }, { root: true });
+ expect(dispatch).toHaveBeenCalledWith(
+ 'expandDiscussion',
+ { discussionId: 1 },
+ { root: true },
+ );
});
});
});
diff --git a/spec/javascripts/diffs/store/getters_spec.js b/spec/javascripts/diffs/store/getters_spec.js
index a59b26b2634..4747e437c4e 100644
--- a/spec/javascripts/diffs/store/getters_spec.js
+++ b/spec/javascripts/diffs/store/getters_spec.js
@@ -184,101 +184,73 @@ describe('Diffs Module Getters', () => {
});
});
- describe('singleDiscussionByLineCode', () => {
- it('returns found discussion per line Code', () => {
- const discussionsMock = {};
- discussionsMock.ABC = discussionMock;
-
- expect(
- getters.singleDiscussionByLineCode(localState, {}, null, {
- discussionsByLineCode: () => discussionsMock,
- })('DEF'),
- ).toEqual([]);
- });
-
- it('returns empty array when no discussions match', () => {
- expect(
- getters.singleDiscussionByLineCode(localState, {}, null, {
- discussionsByLineCode: () => {},
- })('DEF'),
- ).toEqual([]);
- });
- });
-
describe('shouldRenderParallelCommentRow', () => {
let line;
beforeEach(() => {
line = {};
+ discussionMock.expanded = true;
+
line.left = {
lineCode: 'ABC',
+ discussions: [discussionMock],
};
line.right = {
lineCode: 'DEF',
+ discussions: [discussionMock1],
};
});
it('returns true when discussion is expanded', () => {
- discussionMock.expanded = true;
-
- expect(
- getters.shouldRenderParallelCommentRow(localState, {
- singleDiscussionByLineCode: () => [discussionMock],
- })(line),
- ).toEqual(true);
+ expect(getters.shouldRenderParallelCommentRow(localState)(line)).toEqual(true);
});
it('returns false when no discussion was found', () => {
+ line.left.discussions = [];
+ line.right.discussions = [];
+
localState.diffLineCommentForms.ABC = false;
localState.diffLineCommentForms.DEF = false;
- expect(
- getters.shouldRenderParallelCommentRow(localState, {
- singleDiscussionByLineCode: () => [],
- })(line),
- ).toEqual(false);
+ expect(getters.shouldRenderParallelCommentRow(localState)(line)).toEqual(false);
});
it('returns true when discussionForm was found', () => {
localState.diffLineCommentForms.ABC = {};
- expect(
- getters.shouldRenderParallelCommentRow(localState, {
- singleDiscussionByLineCode: () => [discussionMock],
- })(line),
- ).toEqual(true);
+ expect(getters.shouldRenderParallelCommentRow(localState)(line)).toEqual(true);
});
});
describe('shouldRenderInlineCommentRow', () => {
+ let line;
+
+ beforeEach(() => {
+ discussionMock.expanded = true;
+
+ line = {
+ lineCode: 'ABC',
+ discussions: [discussionMock],
+ };
+ });
+
it('returns true when diffLineCommentForms has form', () => {
localState.diffLineCommentForms.ABC = {};
- expect(
- getters.shouldRenderInlineCommentRow(localState)({
- lineCode: 'ABC',
- }),
- ).toEqual(true);
+ expect(getters.shouldRenderInlineCommentRow(localState)(line)).toEqual(true);
});
it('returns false when no line discussions were found', () => {
- expect(
- getters.shouldRenderInlineCommentRow(localState, {
- singleDiscussionByLineCode: () => [],
- })('DEF'),
- ).toEqual(false);
+ line.discussions = [];
+ expect(getters.shouldRenderInlineCommentRow(localState)(line)).toEqual(false);
});
it('returns true if all found discussions are expanded', () => {
discussionMock.expanded = true;
- expect(
- getters.shouldRenderInlineCommentRow(localState, {
- singleDiscussionByLineCode: () => [discussionMock],
- })('ABC'),
- ).toEqual(true);
+ expect(getters.shouldRenderInlineCommentRow(localState)(line)).toEqual(true);
});
});
diff --git a/spec/javascripts/diffs/store/mutations_spec.js b/spec/javascripts/diffs/store/mutations_spec.js
index 8f89984c6e5..7eeca6712cc 100644
--- a/spec/javascripts/diffs/store/mutations_spec.js
+++ b/spec/javascripts/diffs/store/mutations_spec.js
@@ -138,10 +138,9 @@ describe('DiffsStoreMutations', () => {
const fileHash = 123;
const state = { diffFiles: [{}, { fileHash, existingField: 0 }] };
- const file = { fileHash };
const data = { diff_files: [{ file_hash: fileHash, extra_field: 1, existingField: 1 }] };
- mutations[types.ADD_COLLAPSED_DIFFS](state, { file, data });
+ mutations[types.ADD_COLLAPSED_DIFFS](state, { file: state.diffFiles[1], data });
expect(spy).toHaveBeenCalledWith(data, { deep: true });
expect(state.diffFiles[1].fileHash).toEqual(fileHash);
@@ -149,4 +148,141 @@ describe('DiffsStoreMutations', () => {
expect(state.diffFiles[1].extraField).toEqual(1);
});
});
+
+ describe('SET_LINE_DISCUSSIONS_FOR_FILE', () => {
+ it('should add discussions to the given line', () => {
+ const diffPosition = {
+ baseSha: 'ed13df29948c41ba367caa757ab3ec4892509910',
+ headSha: 'b921914f9a834ac47e6fd9420f78db0f83559130',
+ newLine: null,
+ newPath: '500-lines-4.txt',
+ oldLine: 5,
+ oldPath: '500-lines-4.txt',
+ startSha: 'ed13df29948c41ba367caa757ab3ec4892509910',
+ };
+
+ const state = {
+ diffFiles: [
+ {
+ fileHash: 'ABC',
+ parallelDiffLines: [
+ {
+ left: {
+ lineCode: 'ABC_1',
+ discussions: [],
+ },
+ right: {
+ lineCode: 'ABC_1',
+ discussions: [],
+ },
+ },
+ ],
+ highlightedDiffLines: [
+ {
+ lineCode: 'ABC_1',
+ discussions: [],
+ },
+ ],
+ },
+ ],
+ };
+ const discussions = [
+ {
+ id: 1,
+ line_code: 'ABC_1',
+ diff_discussion: true,
+ resolvable: true,
+ original_position: {
+ formatter: diffPosition,
+ },
+ position: {
+ formatter: diffPosition,
+ },
+ },
+ {
+ id: 2,
+ line_code: 'ABC_1',
+ diff_discussion: true,
+ resolvable: true,
+ original_position: {
+ formatter: diffPosition,
+ },
+ position: {
+ formatter: diffPosition,
+ },
+ },
+ ];
+
+ const diffPositionByLineCode = {
+ ABC_1: diffPosition,
+ };
+
+ mutations[types.SET_LINE_DISCUSSIONS_FOR_FILE](state, {
+ fileHash: 'ABC',
+ discussions,
+ diffPositionByLineCode,
+ });
+
+ expect(state.diffFiles[0].parallelDiffLines[0].left.discussions.length).toEqual(2);
+ expect(state.diffFiles[0].parallelDiffLines[0].left.discussions[1].id).toEqual(2);
+
+ expect(state.diffFiles[0].highlightedDiffLines[0].discussions.length).toEqual(2);
+ expect(state.diffFiles[0].highlightedDiffLines[0].discussions[1].id).toEqual(2);
+ });
+ });
+
+ describe('REMOVE_LINE_DISCUSSIONS', () => {
+ it('should remove the existing discussions on the given line', () => {
+ const state = {
+ diffFiles: [
+ {
+ fileHash: 'ABC',
+ parallelDiffLines: [
+ {
+ left: {
+ lineCode: 'ABC_1',
+ discussions: [
+ {
+ id: 1,
+ line_code: 'ABC_1',
+ },
+ {
+ id: 2,
+ line_code: 'ABC_1',
+ },
+ ],
+ },
+ right: {
+ lineCode: 'ABC_1',
+ discussions: [],
+ },
+ },
+ ],
+ highlightedDiffLines: [
+ {
+ lineCode: 'ABC_1',
+ discussions: [
+ {
+ id: 1,
+ line_code: 'ABC_1',
+ },
+ {
+ id: 2,
+ line_code: 'ABC_1',
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ };
+
+ mutations[types.REMOVE_LINE_DISCUSSIONS_FOR_FILE](state, {
+ fileHash: 'ABC',
+ lineCode: 'ABC_1',
+ });
+ expect(state.diffFiles[0].parallelDiffLines[0].left.discussions.length).toEqual(0);
+ expect(state.diffFiles[0].highlightedDiffLines[0].discussions.length).toEqual(0);
+ });
+ });
});
diff --git a/spec/javascripts/diffs/store/utils_spec.js b/spec/javascripts/diffs/store/utils_spec.js
index 32136d9ebff..4b5cf450c68 100644
--- a/spec/javascripts/diffs/store/utils_spec.js
+++ b/spec/javascripts/diffs/store/utils_spec.js
@@ -179,32 +179,126 @@ describe('DiffsStoreUtils', () => {
describe('trimFirstCharOfLineContent', () => {
it('trims the line when it starts with a space', () => {
- expect(utils.trimFirstCharOfLineContent({ richText: ' diff' })).toEqual({ richText: 'diff' });
+ expect(utils.trimFirstCharOfLineContent({ richText: ' diff' })).toEqual({
+ discussions: [],
+ richText: 'diff',
+ });
});
it('trims the line when it starts with a +', () => {
- expect(utils.trimFirstCharOfLineContent({ richText: '+diff' })).toEqual({ richText: 'diff' });
+ expect(utils.trimFirstCharOfLineContent({ richText: '+diff' })).toEqual({
+ discussions: [],
+ richText: 'diff',
+ });
});
it('trims the line when it starts with a -', () => {
- expect(utils.trimFirstCharOfLineContent({ richText: '-diff' })).toEqual({ richText: 'diff' });
+ expect(utils.trimFirstCharOfLineContent({ richText: '-diff' })).toEqual({
+ discussions: [],
+ richText: 'diff',
+ });
});
it('does not trims the line when it starts with a letter', () => {
- expect(utils.trimFirstCharOfLineContent({ richText: 'diff' })).toEqual({ richText: 'diff' });
+ expect(utils.trimFirstCharOfLineContent({ richText: 'diff' })).toEqual({
+ discussions: [],
+ richText: 'diff',
+ });
});
it('does not modify the provided object', () => {
const lineObj = {
+ discussions: [],
richText: ' diff',
};
utils.trimFirstCharOfLineContent(lineObj);
- expect(lineObj).toEqual({ richText: ' diff' });
+ expect(lineObj).toEqual({ discussions: [], richText: ' diff' });
});
it('handles a undefined or null parameter', () => {
- expect(utils.trimFirstCharOfLineContent()).toEqual({});
+ expect(utils.trimFirstCharOfLineContent()).toEqual({ discussions: [] });
+ });
+ });
+
+ describe('prepareDiffData', () => {
+ it('sets the renderIt and collapsed attribute on files', () => {
+ const preparedDiff = { diffFiles: [getDiffFileMock()] };
+ utils.prepareDiffData(preparedDiff);
+
+ const firstParallelDiffLine = preparedDiff.diffFiles[0].parallelDiffLines[2];
+ expect(firstParallelDiffLine.left.discussions.length).toBe(0);
+ expect(firstParallelDiffLine.left).not.toHaveAttr('text');
+ expect(firstParallelDiffLine.right.discussions.length).toBe(0);
+ expect(firstParallelDiffLine.right).not.toHaveAttr('text');
+ const firstParallelChar = firstParallelDiffLine.right.richText.charAt(0);
+ expect(firstParallelChar).not.toBe(' ');
+ expect(firstParallelChar).not.toBe('+');
+ expect(firstParallelChar).not.toBe('-');
+
+ const checkLine = preparedDiff.diffFiles[0].highlightedDiffLines[0];
+ expect(checkLine.discussions.length).toBe(0);
+ expect(checkLine).not.toHaveAttr('text');
+ const firstChar = checkLine.richText.charAt(0);
+ expect(firstChar).not.toBe(' ');
+ expect(firstChar).not.toBe('+');
+ expect(firstChar).not.toBe('-');
+
+ expect(preparedDiff.diffFiles[0].renderIt).toBeTruthy();
+ expect(preparedDiff.diffFiles[0].collapsed).toBeFalsy();
+ });
+ });
+
+ describe('isDiscussionApplicableToLine', () => {
+ const diffPosition = {
+ baseSha: 'ed13df29948c41ba367caa757ab3ec4892509910',
+ headSha: 'b921914f9a834ac47e6fd9420f78db0f83559130',
+ newLine: null,
+ newPath: '500-lines-4.txt',
+ oldLine: 5,
+ oldPath: '500-lines-4.txt',
+ startSha: 'ed13df29948c41ba367caa757ab3ec4892509910',
+ };
+
+ const wrongDiffPosition = {
+ baseSha: 'wrong',
+ headSha: 'wrong',
+ newLine: null,
+ newPath: '500-lines-4.txt',
+ oldLine: 5,
+ oldPath: '500-lines-4.txt',
+ startSha: 'wrong',
+ };
+
+ const discussions = {
+ upToDateDiscussion1: {
+ original_position: {
+ formatter: diffPosition,
+ },
+ position: {
+ formatter: wrongDiffPosition,
+ },
+ },
+ outDatedDiscussion1: {
+ original_position: {
+ formatter: wrongDiffPosition,
+ },
+ position: {
+ formatter: wrongDiffPosition,
+ },
+ },
+ };
+
+ it('returns true when the discussion is up to date', () => {
+ expect(
+ utils.isDiscussionApplicableToLine(discussions.upToDateDiscussion1, diffPosition),
+ ).toBe(true);
+ });
+
+ it('returns false when the discussion is not up to date', () => {
+ expect(
+ utils.isDiscussionApplicableToLine(discussions.outDatedDiscussion1, diffPosition),
+ ).toBe(false);
});
});
});
diff --git a/spec/javascripts/dropzone_input_spec.js b/spec/javascripts/dropzone_input_spec.js
new file mode 100644
index 00000000000..0c6b1a8946d
--- /dev/null
+++ b/spec/javascripts/dropzone_input_spec.js
@@ -0,0 +1,68 @@
+import $ from 'jquery';
+import dropzoneInput from '~/dropzone_input';
+import { TEST_HOST } from 'spec/test_constants';
+
+const TEST_FILE = {
+ upload: {},
+};
+const TEST_UPLOAD_PATH = `${TEST_HOST}/upload/file`;
+const TEST_ERROR_MESSAGE = 'A big error occurred!';
+const TEMPLATE = (
+`<form class="gfm-form" data-uploads-path="${TEST_UPLOAD_PATH}">
+ <textarea class="js-gfm-input"></textarea>
+ <div class="uploading-error-message"></div>
+</form>`
+);
+
+describe('dropzone_input', () => {
+ let form;
+ let dropzone;
+ let xhr;
+ let oldXMLHttpRequest;
+
+ beforeEach(() => {
+ form = $(TEMPLATE);
+
+ dropzone = dropzoneInput(form);
+
+ xhr = jasmine.createSpyObj(Object.keys(XMLHttpRequest.prototype));
+ oldXMLHttpRequest = window.XMLHttpRequest;
+ window.XMLHttpRequest = () => xhr;
+ });
+
+ afterEach(() => {
+ window.XMLHttpRequest = oldXMLHttpRequest;
+ });
+
+ it('shows error message, when AJAX fails with json', () => {
+ xhr = {
+ ...xhr,
+ statusCode: 400,
+ readyState: 4,
+ responseText: JSON.stringify({ message: TEST_ERROR_MESSAGE }),
+ getResponseHeader: () => 'application/json',
+ };
+
+ dropzone.processFile(TEST_FILE);
+
+ xhr.onload();
+
+ expect(form.find('.uploading-error-message').text()).toEqual(TEST_ERROR_MESSAGE);
+ });
+
+ it('shows error message, when AJAX fails with text', () => {
+ xhr = {
+ ...xhr,
+ statusCode: 400,
+ readyState: 4,
+ responseText: TEST_ERROR_MESSAGE,
+ getResponseHeader: () => 'text/plain',
+ };
+
+ dropzone.processFile(TEST_FILE);
+
+ xhr.onload();
+
+ expect(form.find('.uploading-error-message').text()).toEqual(TEST_ERROR_MESSAGE);
+ });
+});
diff --git a/spec/javascripts/gfm_auto_complete_spec.js b/spec/javascripts/gfm_auto_complete_spec.js
index 1cb20a1e7ff..4f9cacf2724 100644
--- a/spec/javascripts/gfm_auto_complete_spec.js
+++ b/spec/javascripts/gfm_auto_complete_spec.js
@@ -146,7 +146,7 @@ describe('GfmAutoComplete', function () {
shouldNotBeFollowedBy.forEach((followedSymbol) => {
const seq = atSign + followedSymbol;
- it(`should not match "${seq}"`, () => {
+ it(`should not match ${JSON.stringify(seq)}`, () => {
expect(defaultMatcher(atwhoInstance, atSign, seq)).toBe(null);
});
});
diff --git a/spec/javascripts/groups/components/app_spec.js b/spec/javascripts/groups/components/app_spec.js
index 03d4b472b87..89c07d1f06d 100644
--- a/spec/javascripts/groups/components/app_spec.js
+++ b/spec/javascripts/groups/components/app_spec.js
@@ -9,9 +9,14 @@ import GroupsStore from '~/groups/store/groups_store';
import GroupsService from '~/groups/service/groups_service';
import {
- mockEndpoint, mockGroups, mockSearchedGroups,
- mockRawPageInfo, mockParentGroupItem, mockRawChildren,
- mockChildren, mockPageInfo,
+ mockEndpoint,
+ mockGroups,
+ mockSearchedGroups,
+ mockRawPageInfo,
+ mockParentGroupItem,
+ mockRawChildren,
+ mockChildren,
+ mockPageInfo,
} from '../mock_data';
const createComponent = (hideProjects = false) => {
@@ -19,6 +24,8 @@ const createComponent = (hideProjects = false) => {
const store = new GroupsStore(false);
const service = new GroupsService(mockEndpoint);
+ store.state.pageInfo = mockPageInfo;
+
return new Component({
propsData: {
store,
@@ -28,22 +35,23 @@ const createComponent = (hideProjects = false) => {
});
};
-const returnServicePromise = (data, failed) => new Promise((resolve, reject) => {
- if (failed) {
- reject(data);
- } else {
- resolve({
- json() {
- return data;
- },
- });
- }
-});
+const returnServicePromise = (data, failed) =>
+ new Promise((resolve, reject) => {
+ if (failed) {
+ reject(data);
+ } else {
+ resolve({
+ json() {
+ return data;
+ },
+ });
+ }
+ });
describe('AppComponent', () => {
let vm;
- beforeEach((done) => {
+ beforeEach(done => {
Vue.component('group-folder', groupFolderComponent);
Vue.component('group-item', groupItemComponent);
@@ -94,7 +102,7 @@ describe('AppComponent', () => {
});
describe('fetchGroups', () => {
- it('should call `getGroups` with all the params provided', (done) => {
+ it('should call `getGroups` with all the params provided', done => {
spyOn(vm.service, 'getGroups').and.returnValue(returnServicePromise(mockGroups));
vm.fetchGroups({
@@ -110,8 +118,10 @@ describe('AppComponent', () => {
}, 0);
});
- it('should set headers to store for building pagination info when called with `updatePagination`', (done) => {
- spyOn(vm.service, 'getGroups').and.returnValue(returnServicePromise({ headers: mockRawPageInfo }));
+ it('should set headers to store for building pagination info when called with `updatePagination`', done => {
+ spyOn(vm.service, 'getGroups').and.returnValue(
+ returnServicePromise({ headers: mockRawPageInfo }),
+ );
spyOn(vm, 'updatePagination');
vm.fetchGroups({ updatePagination: true });
@@ -122,7 +132,7 @@ describe('AppComponent', () => {
}, 0);
});
- it('should show flash error when request fails', (done) => {
+ it('should show flash error when request fails', done => {
spyOn(vm.service, 'getGroups').and.returnValue(returnServicePromise(null, true));
spyOn($, 'scrollTo');
spyOn(window, 'Flash');
@@ -138,7 +148,7 @@ describe('AppComponent', () => {
});
describe('fetchAllGroups', () => {
- it('should fetch default set of groups', (done) => {
+ it('should fetch default set of groups', done => {
spyOn(vm, 'fetchGroups').and.returnValue(returnServicePromise(mockGroups));
spyOn(vm, 'updatePagination').and.callThrough();
spyOn(vm, 'updateGroups').and.callThrough();
@@ -153,7 +163,7 @@ describe('AppComponent', () => {
}, 0);
});
- it('should fetch matching set of groups when app is loaded with search query', (done) => {
+ it('should fetch matching set of groups when app is loaded with search query', done => {
spyOn(vm, 'fetchGroups').and.returnValue(returnServicePromise(mockSearchedGroups));
spyOn(vm, 'updateGroups').and.callThrough();
@@ -173,7 +183,7 @@ describe('AppComponent', () => {
});
describe('fetchPage', () => {
- it('should fetch groups for provided page details and update window state', (done) => {
+ it('should fetch groups for provided page details and update window state', done => {
spyOn(vm, 'fetchGroups').and.returnValue(returnServicePromise(mockGroups));
spyOn(vm, 'updateGroups').and.callThrough();
const mergeUrlParams = spyOnDependency(appComponent, 'mergeUrlParams').and.callThrough();
@@ -193,9 +203,13 @@ describe('AppComponent', () => {
expect(vm.isLoading).toBe(false);
expect($.scrollTo).toHaveBeenCalledWith(0);
expect(mergeUrlParams).toHaveBeenCalledWith({ page: 2 }, jasmine.any(String));
- expect(window.history.replaceState).toHaveBeenCalledWith({
- page: jasmine.any(String),
- }, jasmine.any(String), jasmine.any(String));
+ expect(window.history.replaceState).toHaveBeenCalledWith(
+ {
+ page: jasmine.any(String),
+ },
+ jasmine.any(String),
+ jasmine.any(String),
+ );
expect(vm.updateGroups).toHaveBeenCalled();
done();
}, 0);
@@ -211,7 +225,7 @@ describe('AppComponent', () => {
groupItem.isChildrenLoading = false;
});
- it('should fetch children of given group and expand it if group is collapsed and children are not loaded', (done) => {
+ it('should fetch children of given group and expand it if group is collapsed and children are not loaded', done => {
spyOn(vm, 'fetchGroups').and.returnValue(returnServicePromise(mockRawChildren));
spyOn(vm.store, 'setGroupChildren');
@@ -244,7 +258,7 @@ describe('AppComponent', () => {
expect(groupItem.isOpen).toBe(false);
});
- it('should set `isChildrenLoading` back to `false` if load request fails', (done) => {
+ it('should set `isChildrenLoading` back to `false` if load request fails', done => {
spyOn(vm, 'fetchGroups').and.returnValue(returnServicePromise({}, true));
vm.toggleChildren(groupItem);
@@ -272,7 +286,9 @@ describe('AppComponent', () => {
expect(vm.groupLeaveConfirmationMessage).toBe('');
vm.showLeaveGroupModal(group, mockParentGroupItem);
expect(vm.showModal).toBe(true);
- expect(vm.groupLeaveConfirmationMessage).toBe(`Are you sure you want to leave the "${group.fullName}" group?`);
+ expect(vm.groupLeaveConfirmationMessage).toBe(
+ `Are you sure you want to leave the "${group.fullName}" group?`,
+ );
});
});
@@ -299,7 +315,7 @@ describe('AppComponent', () => {
vm.targetParentGroup = groupItem;
});
- it('hides modal confirmation leave group and remove group item from tree', (done) => {
+ it('hides modal confirmation leave group and remove group item from tree', done => {
const notice = `You left the "${childGroupItem.fullName}" group.`;
spyOn(vm.service, 'leaveGroup').and.returnValue(returnServicePromise({ notice }));
spyOn(vm.store, 'removeGroup').and.callThrough();
@@ -318,9 +334,11 @@ describe('AppComponent', () => {
}, 0);
});
- it('should show error flash message if request failed to leave group', (done) => {
+ it('should show error flash message if request failed to leave group', done => {
const message = 'An error occurred. Please try again.';
- spyOn(vm.service, 'leaveGroup').and.returnValue(returnServicePromise({ status: 500 }, true));
+ spyOn(vm.service, 'leaveGroup').and.returnValue(
+ returnServicePromise({ status: 500 }, true),
+ );
spyOn(vm.store, 'removeGroup').and.callThrough();
spyOn(window, 'Flash');
@@ -335,9 +353,11 @@ describe('AppComponent', () => {
}, 0);
});
- it('should show appropriate error flash message if request forbids to leave group', (done) => {
+ it('should show appropriate error flash message if request forbids to leave group', done => {
const message = 'Failed to leave the group. Please make sure you are not the only owner.';
- spyOn(vm.service, 'leaveGroup').and.returnValue(returnServicePromise({ status: 403 }, true));
+ spyOn(vm.service, 'leaveGroup').and.returnValue(
+ returnServicePromise({ status: 403 }, true),
+ );
spyOn(vm.store, 'removeGroup').and.callThrough();
spyOn(window, 'Flash');
@@ -388,7 +408,7 @@ describe('AppComponent', () => {
});
describe('created', () => {
- it('should bind event listeners on eventHub', (done) => {
+ it('should bind event listeners on eventHub', done => {
spyOn(eventHub, '$on');
const newVm = createComponent();
@@ -405,21 +425,21 @@ describe('AppComponent', () => {
});
});
- it('should initialize `searchEmptyMessage` prop with correct string when `hideProjects` is `false`', (done) => {
+ it('should initialize `searchEmptyMessage` prop with correct string when `hideProjects` is `false`', done => {
const newVm = createComponent();
newVm.$mount();
Vue.nextTick(() => {
- expect(newVm.searchEmptyMessage).toBe('Sorry, no groups or projects matched your search');
+ expect(newVm.searchEmptyMessage).toBe('No groups or projects matched your search');
newVm.$destroy();
done();
});
});
- it('should initialize `searchEmptyMessage` prop with correct string when `hideProjects` is `true`', (done) => {
+ it('should initialize `searchEmptyMessage` prop with correct string when `hideProjects` is `true`', done => {
const newVm = createComponent(true);
newVm.$mount();
Vue.nextTick(() => {
- expect(newVm.searchEmptyMessage).toBe('Sorry, no groups matched your search');
+ expect(newVm.searchEmptyMessage).toBe('No groups matched your search');
newVm.$destroy();
done();
});
@@ -427,7 +447,7 @@ describe('AppComponent', () => {
});
describe('beforeDestroy', () => {
- it('should unbind event listeners on eventHub', (done) => {
+ it('should unbind event listeners on eventHub', done => {
spyOn(eventHub, '$off');
const newVm = createComponent();
@@ -454,7 +474,7 @@ describe('AppComponent', () => {
vm.$destroy();
});
- it('should render loading icon', (done) => {
+ it('should render loading icon', done => {
vm.isLoading = true;
Vue.nextTick(() => {
expect(vm.$el.querySelector('.loading-animation')).toBeDefined();
@@ -463,17 +483,16 @@ describe('AppComponent', () => {
});
});
- it('should render groups tree', (done) => {
+ it('should render groups tree', done => {
vm.store.state.groups = [mockParentGroupItem];
vm.isLoading = false;
- vm.store.state.pageInfo = mockPageInfo;
Vue.nextTick(() => {
expect(vm.$el.querySelector('.groups-list-tree-container')).toBeDefined();
done();
});
});
- it('renders modal confirmation dialog', (done) => {
+ it('renders modal confirmation dialog', done => {
vm.groupLeaveConfirmationMessage = 'Are you sure you want to leave the "foo" group?';
vm.showModal = true;
Vue.nextTick(() => {
diff --git a/spec/javascripts/helpers/vue_resource_helper.js b/spec/javascripts/helpers/vue_resource_helper.js
index 70b7ec4e574..0d1bf5e2e80 100644
--- a/spec/javascripts/helpers/vue_resource_helper.js
+++ b/spec/javascripts/helpers/vue_resource_helper.js
@@ -5,6 +5,7 @@ export const headersInterceptor = (request, next) => {
response.headers.forEach((value, key) => {
headers[key] = value;
});
+ // eslint-disable-next-line no-param-reassign
response.headers = headers;
});
};
diff --git a/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js b/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js
index 41d8bfff7e7..b45ae5bbb0f 100644
--- a/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js
+++ b/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js
@@ -30,7 +30,7 @@ describe('Multi-file editor commit sidebar list item', () => {
});
it('renders file path', () => {
- expect(vm.$el.querySelector('.multi-file-commit-list-path').textContent.trim()).toBe(f.path);
+ expect(vm.$el.querySelector('.multi-file-commit-list-path').textContent).toContain(f.path);
});
it('renders actionn button', () => {
diff --git a/spec/javascripts/ide/components/commit_sidebar/stage_button_spec.js b/spec/javascripts/ide/components/commit_sidebar/stage_button_spec.js
index a5b906da8a1..e09ccbe2a63 100644
--- a/spec/javascripts/ide/components/commit_sidebar/stage_button_spec.js
+++ b/spec/javascripts/ide/components/commit_sidebar/stage_button_spec.js
@@ -29,7 +29,7 @@ describe('IDE stage file button', () => {
});
it('renders button to discard & stage', () => {
- expect(vm.$el.querySelectorAll('.btn').length).toBe(2);
+ expect(vm.$el.querySelectorAll('.btn-blank').length).toBe(2);
});
it('calls store with stage button', () => {
@@ -39,7 +39,7 @@ describe('IDE stage file button', () => {
});
it('calls store with discard button', () => {
- vm.$el.querySelector('.dropdown-menu button').click();
+ vm.$el.querySelector('.btn-danger').click();
expect(vm.discardFileChanges).toHaveBeenCalledWith(f.path);
});
diff --git a/spec/javascripts/ide/components/file_templates/bar_spec.js b/spec/javascripts/ide/components/file_templates/bar_spec.js
new file mode 100644
index 00000000000..a688f7f69a6
--- /dev/null
+++ b/spec/javascripts/ide/components/file_templates/bar_spec.js
@@ -0,0 +1,117 @@
+import Vue from 'vue';
+import { createStore } from '~/ide/stores';
+import Bar from '~/ide/components/file_templates/bar.vue';
+import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { resetStore, file } from '../../helpers';
+
+describe('IDE file templates bar component', () => {
+ let Component;
+ let vm;
+
+ beforeAll(() => {
+ Component = Vue.extend(Bar);
+ });
+
+ beforeEach(() => {
+ const store = createStore();
+
+ store.state.openFiles.push({
+ ...file('file'),
+ opened: true,
+ active: true,
+ });
+
+ vm = mountComponentWithStore(Component, { store });
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ resetStore(vm.$store);
+ });
+
+ describe('template type dropdown', () => {
+ it('renders dropdown component', () => {
+ expect(vm.$el.querySelector('.dropdown').textContent).toContain('Choose a type');
+ });
+
+ it('calls setSelectedTemplateType when clicking item', () => {
+ spyOn(vm, 'setSelectedTemplateType').and.stub();
+
+ vm.$el.querySelector('.dropdown-content button').click();
+
+ expect(vm.setSelectedTemplateType).toHaveBeenCalledWith({
+ name: '.gitlab-ci.yml',
+ key: 'gitlab_ci_ymls',
+ });
+ });
+ });
+
+ describe('template dropdown', () => {
+ beforeEach(done => {
+ vm.$store.state.fileTemplates.templates = [
+ {
+ name: 'test',
+ },
+ ];
+ vm.$store.state.fileTemplates.selectedTemplateType = {
+ name: '.gitlab-ci.yml',
+ key: 'gitlab_ci_ymls',
+ };
+
+ vm.$nextTick(done);
+ });
+
+ it('renders dropdown component', () => {
+ expect(vm.$el.querySelectorAll('.dropdown')[1].textContent).toContain('Choose a template');
+ });
+
+ it('calls fetchTemplate on click', () => {
+ spyOn(vm, 'fetchTemplate').and.stub();
+
+ vm.$el
+ .querySelectorAll('.dropdown-content')[1]
+ .querySelector('button')
+ .click();
+
+ expect(vm.fetchTemplate).toHaveBeenCalledWith({
+ name: 'test',
+ });
+ });
+ });
+
+ it('shows undo button if updateSuccess is true', done => {
+ vm.$store.state.fileTemplates.updateSuccess = true;
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.btn-default').style.display).not.toBe('none');
+
+ done();
+ });
+ });
+
+ it('calls undoFileTemplate when clicking undo button', () => {
+ spyOn(vm, 'undoFileTemplate').and.stub();
+
+ vm.$el.querySelector('.btn-default').click();
+
+ expect(vm.undoFileTemplate).toHaveBeenCalled();
+ });
+
+ it('calls setSelectedTemplateType if activeFile name matches a template', done => {
+ const fileName = '.gitlab-ci.yml';
+
+ spyOn(vm, 'setSelectedTemplateType');
+ vm.$store.state.openFiles[0].name = fileName;
+
+ vm.setInitialType();
+
+ vm.$nextTick(() => {
+ expect(vm.setSelectedTemplateType).toHaveBeenCalledWith({
+ name: fileName,
+ key: 'gitlab_ci_ymls',
+ });
+
+ done();
+ });
+ });
+});
diff --git a/spec/javascripts/ide/components/file_templates/dropdown_spec.js b/spec/javascripts/ide/components/file_templates/dropdown_spec.js
new file mode 100644
index 00000000000..898796f4fa0
--- /dev/null
+++ b/spec/javascripts/ide/components/file_templates/dropdown_spec.js
@@ -0,0 +1,201 @@
+import $ from 'jquery';
+import Vue from 'vue';
+import { createStore } from '~/ide/stores';
+import Dropdown from '~/ide/components/file_templates/dropdown.vue';
+import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { resetStore } from '../../helpers';
+
+describe('IDE file templates dropdown component', () => {
+ let Component;
+ let vm;
+
+ beforeAll(() => {
+ Component = Vue.extend(Dropdown);
+ });
+
+ beforeEach(() => {
+ const store = createStore();
+
+ vm = createComponentWithStore(Component, store, {
+ label: 'Test',
+ }).$mount();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ resetStore(vm.$store);
+ });
+
+ describe('async', () => {
+ beforeEach(() => {
+ vm.isAsyncData = true;
+ });
+
+ it('calls async store method on Bootstrap dropdown event', () => {
+ spyOn(vm, 'fetchTemplateTypes').and.stub();
+
+ $(vm.$el).trigger('show.bs.dropdown');
+
+ expect(vm.fetchTemplateTypes).toHaveBeenCalled();
+ });
+
+ it('renders templates when async', done => {
+ vm.$store.state.fileTemplates.templates = [
+ {
+ name: 'test',
+ },
+ ];
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.dropdown-content').textContent).toContain('test');
+
+ done();
+ });
+ });
+
+ it('renders loading icon when isLoading is true', done => {
+ vm.$store.state.fileTemplates.isLoading = true;
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.loading-container')).not.toBe(null);
+
+ done();
+ });
+ });
+
+ it('searches template data', () => {
+ vm.$store.state.fileTemplates.templates = [
+ {
+ name: 'test',
+ },
+ ];
+ vm.searchable = true;
+ vm.search = 'hello';
+
+ expect(vm.outputData).toEqual([]);
+ });
+
+ it('does not filter data is searchable is false', () => {
+ vm.$store.state.fileTemplates.templates = [
+ {
+ name: 'test',
+ },
+ ];
+ vm.search = 'hello';
+
+ expect(vm.outputData).toEqual([
+ {
+ name: 'test',
+ },
+ ]);
+ });
+
+ it('calls clickItem on click', done => {
+ spyOn(vm, 'clickItem').and.stub();
+
+ vm.$store.state.fileTemplates.templates = [
+ {
+ name: 'test',
+ },
+ ];
+
+ vm.$nextTick(() => {
+ vm.$el.querySelector('.dropdown-content button').click();
+
+ expect(vm.clickItem).toHaveBeenCalledWith({
+ name: 'test',
+ });
+
+ done();
+ });
+ });
+
+ it('renders input when searchable is true', done => {
+ vm.searchable = true;
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.dropdown-input')).not.toBe(null);
+
+ done();
+ });
+ });
+
+ it('does not render input when searchable is true & showLoading is true', done => {
+ vm.searchable = true;
+ vm.$store.state.fileTemplates.isLoading = true;
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.dropdown-input')).toBe(null);
+
+ done();
+ });
+ });
+ });
+
+ describe('sync', () => {
+ beforeEach(done => {
+ vm.data = [
+ {
+ name: 'test sync',
+ },
+ ];
+
+ vm.$nextTick(done);
+ });
+
+ it('renders props data', () => {
+ expect(vm.$el.querySelector('.dropdown-content').textContent).toContain('test sync');
+ });
+
+ it('renders input when searchable is true', done => {
+ vm.searchable = true;
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.dropdown-input')).not.toBe(null);
+
+ done();
+ });
+ });
+
+ it('calls clickItem on click', done => {
+ spyOn(vm, 'clickItem').and.stub();
+
+ vm.$nextTick(() => {
+ vm.$el.querySelector('.dropdown-content button').click();
+
+ expect(vm.clickItem).toHaveBeenCalledWith({
+ name: 'test sync',
+ });
+
+ done();
+ });
+ });
+
+ it('searches template data', () => {
+ vm.searchable = true;
+ vm.search = 'hello';
+
+ expect(vm.outputData).toEqual([]);
+ });
+
+ it('does not filter data is searchable is false', () => {
+ vm.search = 'hello';
+
+ expect(vm.outputData).toEqual([
+ {
+ name: 'test sync',
+ },
+ ]);
+ });
+
+ it('renders dropdown title', done => {
+ vm.title = 'Test title';
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.dropdown-title').textContent).toContain('Test title');
+
+ done();
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/ide/components/repo_commit_section_spec.js b/spec/javascripts/ide/components/repo_commit_section_spec.js
index 30cd92b2ca4..d09ccd7ac34 100644
--- a/spec/javascripts/ide/components/repo_commit_section_spec.js
+++ b/spec/javascripts/ide/components/repo_commit_section_spec.js
@@ -111,7 +111,7 @@ describe('RepoCommitSection', () => {
.then(vm.$nextTick)
.then(() => {
expect(vm.$el.querySelector('.ide-commit-list-container').textContent).toContain(
- 'No changes',
+ 'There are no unstaged changes',
);
})
.then(done)
@@ -133,7 +133,7 @@ describe('RepoCommitSection', () => {
});
it('discards a single file', done => {
- vm.$el.querySelector('.multi-file-discard-btn .dropdown-menu button').click();
+ vm.$el.querySelector('.multi-file-commit-list li:first-child .js-modal-primary-action').click();
Vue.nextTick(() => {
expect(vm.$el.querySelector('.ide-commit-list-container').textContent).not.toContain('file1');
diff --git a/spec/javascripts/ide/helpers.js b/spec/javascripts/ide/helpers.js
index c11c482fef8..3ce9c9fcda1 100644
--- a/spec/javascripts/ide/helpers.js
+++ b/spec/javascripts/ide/helpers.js
@@ -5,6 +5,7 @@ import commitState from '~/ide/stores/modules/commit/state';
import mergeRequestsState from '~/ide/stores/modules/merge_requests/state';
import pipelinesState from '~/ide/stores/modules/pipelines/state';
import branchesState from '~/ide/stores/modules/branches/state';
+import fileTemplatesState from '~/ide/stores/modules/file_templates/state';
export const resetStore = store => {
const newState = {
@@ -13,6 +14,7 @@ export const resetStore = store => {
mergeRequests: mergeRequestsState(),
pipelines: pipelinesState(),
branches: branchesState(),
+ fileTemplates: fileTemplatesState(),
};
store.replaceState(newState);
};
diff --git a/spec/javascripts/ide/stores/actions/file_spec.js b/spec/javascripts/ide/stores/actions/file_spec.js
index bca2033ff97..1ca811e996b 100644
--- a/spec/javascripts/ide/stores/actions/file_spec.js
+++ b/spec/javascripts/ide/stores/actions/file_spec.js
@@ -692,21 +692,6 @@ describe('IDE store file actions', () => {
.then(done)
.catch(done.fail);
});
-
- it('calls scrollToTab', done => {
- const scrollToTabSpy = jasmine.createSpy('scrollToTab');
- const oldScrollToTab = store._actions.scrollToTab; // eslint-disable-line
- store._actions.scrollToTab = [scrollToTabSpy]; // eslint-disable-line
-
- store
- .dispatch('openPendingTab', { file: f, keyPrefix: 'pending' })
- .then(() => {
- expect(scrollToTabSpy).toHaveBeenCalled();
- store._actions.scrollToTab = oldScrollToTab; // eslint-disable-line
- })
- .then(done)
- .catch(done.fail);
- });
});
describe('removePendingTab', () => {
diff --git a/spec/javascripts/ide/stores/actions_spec.js b/spec/javascripts/ide/stores/actions_spec.js
index d84f1717a61..c9a1158a14e 100644
--- a/spec/javascripts/ide/stores/actions_spec.js
+++ b/spec/javascripts/ide/stores/actions_spec.js
@@ -305,7 +305,11 @@ describe('Multi-file store actions', () => {
describe('stageAllChanges', () => {
it('adds all files from changedFiles to stagedFiles', done => {
- store.state.changedFiles.push(file(), file('new'));
+ const openFile = { ...file(), path: 'test' };
+
+ store.state.openFiles.push(openFile);
+ store.state.stagedFiles.push(openFile);
+ store.state.changedFiles.push(openFile, file('new'));
testAction(
stageAllChanges,
@@ -316,7 +320,12 @@ describe('Multi-file store actions', () => {
{ type: types.STAGE_CHANGE, payload: store.state.changedFiles[0].path },
{ type: types.STAGE_CHANGE, payload: store.state.changedFiles[1].path },
],
- [],
+ [
+ {
+ type: 'openPendingTab',
+ payload: { file: openFile, keyPrefix: 'staged' },
+ },
+ ],
done,
);
});
@@ -324,7 +333,11 @@ describe('Multi-file store actions', () => {
describe('unstageAllChanges', () => {
it('removes all files from stagedFiles after unstaging', done => {
- store.state.stagedFiles.push(file(), file('new'));
+ const openFile = { ...file(), path: 'test' };
+
+ store.state.openFiles.push(openFile);
+ store.state.changedFiles.push(openFile);
+ store.state.stagedFiles.push(openFile, file('new'));
testAction(
unstageAllChanges,
@@ -334,7 +347,12 @@ describe('Multi-file store actions', () => {
{ type: types.UNSTAGE_CHANGE, payload: store.state.stagedFiles[0].path },
{ type: types.UNSTAGE_CHANGE, payload: store.state.stagedFiles[1].path },
],
- [],
+ [
+ {
+ type: 'openPendingTab',
+ payload: { file: openFile, keyPrefix: 'unstaged' },
+ },
+ ],
done,
);
});
diff --git a/spec/javascripts/ide/stores/modules/file_templates/actions_spec.js b/spec/javascripts/ide/stores/modules/file_templates/actions_spec.js
index f831a9f0a5d..c29dd9f0d06 100644
--- a/spec/javascripts/ide/stores/modules/file_templates/actions_spec.js
+++ b/spec/javascripts/ide/stores/modules/file_templates/actions_spec.js
@@ -148,14 +148,66 @@ describe('IDE file templates actions', () => {
});
describe('setSelectedTemplateType', () => {
- it('commits SET_SELECTED_TEMPLATE_TYPE', done => {
- testAction(
- actions.setSelectedTemplateType,
- 'test',
- state,
- [{ type: types.SET_SELECTED_TEMPLATE_TYPE, payload: 'test' }],
- [],
- done,
+ it('commits SET_SELECTED_TEMPLATE_TYPE', () => {
+ const commit = jasmine.createSpy('commit');
+ const options = {
+ commit,
+ dispatch() {},
+ rootGetters: {
+ activeFile: {
+ name: 'test',
+ prevPath: '',
+ },
+ },
+ };
+
+ actions.setSelectedTemplateType(options, { name: 'test' });
+
+ expect(commit).toHaveBeenCalledWith(types.SET_SELECTED_TEMPLATE_TYPE, { name: 'test' });
+ });
+
+ it('dispatches discardFileChanges if prevPath matches templates name', () => {
+ const dispatch = jasmine.createSpy('dispatch');
+ const options = {
+ commit() {},
+ dispatch,
+ rootGetters: {
+ activeFile: {
+ name: 'test',
+ path: 'test',
+ prevPath: 'test',
+ },
+ },
+ };
+
+ actions.setSelectedTemplateType(options, { name: 'test' });
+
+ expect(dispatch).toHaveBeenCalledWith('discardFileChanges', 'test', { root: true });
+ });
+
+ it('dispatches renameEntry if file name doesnt match', () => {
+ const dispatch = jasmine.createSpy('dispatch');
+ const options = {
+ commit() {},
+ dispatch,
+ rootGetters: {
+ activeFile: {
+ name: 'oldtest',
+ path: 'oldtest',
+ prevPath: '',
+ },
+ },
+ };
+
+ actions.setSelectedTemplateType(options, { name: 'test' });
+
+ expect(dispatch).toHaveBeenCalledWith(
+ 'renameEntry',
+ {
+ path: 'oldtest',
+ name: 'test',
+ },
+ { root: true },
);
});
});
@@ -332,5 +384,20 @@ describe('IDE file templates actions', () => {
expect(commit).toHaveBeenCalledWith('SET_UPDATE_SUCCESS', false);
});
+
+ it('dispatches discardFileChanges if file has prevPath', () => {
+ const dispatch = jasmine.createSpy('dispatch');
+ const rootGetters = {
+ activeFile: { path: 'test', prevPath: 'newtest', raw: 'raw content' },
+ };
+
+ actions.undoFileTemplate({ dispatch, commit() {}, rootGetters });
+
+ expect(dispatch.calls.mostRecent().args).toEqual([
+ 'discardFileChanges',
+ 'test',
+ { root: true },
+ ]);
+ });
});
});
diff --git a/spec/javascripts/ide/stores/modules/file_templates/getters_spec.js b/spec/javascripts/ide/stores/modules/file_templates/getters_spec.js
index e337c3f331b..17cb457881f 100644
--- a/spec/javascripts/ide/stores/modules/file_templates/getters_spec.js
+++ b/spec/javascripts/ide/stores/modules/file_templates/getters_spec.js
@@ -1,3 +1,5 @@
+import createState from '~/ide/stores/state';
+import { activityBarViews } from '~/ide/constants';
import * as getters from '~/ide/stores/modules/file_templates/getters';
describe('IDE file templates getters', () => {
@@ -8,22 +10,49 @@ describe('IDE file templates getters', () => {
});
describe('showFileTemplatesBar', () => {
- it('finds template type by name', () => {
+ let rootState;
+
+ beforeEach(() => {
+ rootState = createState();
+ });
+
+ it('returns true if template is found and currentActivityView is edit', () => {
+ rootState.currentActivityView = activityBarViews.edit;
+
+ expect(
+ getters.showFileTemplatesBar(
+ null,
+ {
+ templateTypes: getters.templateTypes(),
+ },
+ rootState,
+ )('LICENSE'),
+ ).toBe(true);
+ });
+
+ it('returns false if template is found and currentActivityView is not edit', () => {
+ rootState.currentActivityView = activityBarViews.commit;
+
expect(
- getters.showFileTemplatesBar(null, {
- templateTypes: getters.templateTypes(),
- })('LICENSE'),
- ).toEqual({
- name: 'LICENSE',
- key: 'licenses',
- });
+ getters.showFileTemplatesBar(
+ null,
+ {
+ templateTypes: getters.templateTypes(),
+ },
+ rootState,
+ )('LICENSE'),
+ ).toBe(false);
});
it('returns undefined if not found', () => {
expect(
- getters.showFileTemplatesBar(null, {
- templateTypes: getters.templateTypes(),
- })('test'),
+ getters.showFileTemplatesBar(
+ null,
+ {
+ templateTypes: getters.templateTypes(),
+ },
+ rootState,
+ )('test'),
).toBe(undefined);
});
});
diff --git a/spec/javascripts/ide/stores/mutations_spec.js b/spec/javascripts/ide/stores/mutations_spec.js
index 6ce76aaa03b..41dd3d3c67f 100644
--- a/spec/javascripts/ide/stores/mutations_spec.js
+++ b/spec/javascripts/ide/stores/mutations_spec.js
@@ -339,5 +339,13 @@ describe('Multi-file store mutations', () => {
expect(localState.entries.parentPath.tree.length).toBe(1);
});
+
+ it('adds to openFiles if previously opened', () => {
+ localState.entries.oldPath.opened = true;
+
+ mutations.RENAME_ENTRY(localState, { path: 'oldPath', name: 'newPath' });
+
+ expect(localState.openFiles).toEqual([localState.entries.newPath]);
+ });
});
});
diff --git a/spec/javascripts/lazy_loader_spec.js b/spec/javascripts/lazy_loader_spec.js
index 1d81e4e2d1a..c177d79b9e0 100644
--- a/spec/javascripts/lazy_loader_spec.js
+++ b/spec/javascripts/lazy_loader_spec.js
@@ -2,10 +2,10 @@ import LazyLoader from '~/lazy_loader';
let lazyLoader = null;
-describe('LazyLoader', function () {
+describe('LazyLoader', function() {
preloadFixtures('issues/issue_with_comment.html.raw');
- beforeEach(function () {
+ beforeEach(function() {
loadFixtures('issues/issue_with_comment.html.raw');
lazyLoader = new LazyLoader({
observerNode: 'body',
@@ -13,8 +13,8 @@ describe('LazyLoader', function () {
// Doing everything that happens normally in onload
lazyLoader.loadCheck();
});
- describe('behavior', function () {
- it('should copy value from data-src to src for img 1', function (done) {
+ describe('behavior', function() {
+ it('should copy value from data-src to src for img 1', function(done) {
const img = document.querySelectorAll('img[data-src]')[0];
const originalDataSrc = img.getAttribute('data-src');
img.scrollIntoView();
@@ -26,7 +26,7 @@ describe('LazyLoader', function () {
}, 100);
});
- it('should lazy load dynamically added data-src images', function (done) {
+ it('should lazy load dynamically added data-src images', function(done) {
const newImg = document.createElement('img');
const testPath = '/img/testimg.png';
newImg.className = 'lazy';
@@ -41,7 +41,7 @@ describe('LazyLoader', function () {
}, 100);
});
- it('should not alter normal images', function (done) {
+ it('should not alter normal images', function(done) {
const newImg = document.createElement('img');
const testPath = '/img/testimg.png';
newImg.setAttribute('src', testPath);
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js
index babad296f09..28151c7e658 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js
+++ b/spec/javascripts/lib/utils/common_utils_spec.js
@@ -29,24 +29,46 @@ describe('common_utils', () => {
});
});
- describe('getUrlParamsArray', () => {
- it('should return params array', () => {
- expect(commonUtils.getUrlParamsArray() instanceof Array).toBe(true);
+ describe('urlParamsToArray', () => {
+ it('returns empty array for empty querystring', () => {
+ expect(commonUtils.urlParamsToArray('')).toEqual([]);
+ });
+
+ it('should decode params', () => {
+ expect(
+ commonUtils.urlParamsToArray('?label_name%5B%5D=test')[0],
+ ).toBe('label_name[]=test');
});
it('should remove the question mark from the search params', () => {
- const paramsArray = commonUtils.getUrlParamsArray();
+ const paramsArray = commonUtils.urlParamsToArray('?test=thing');
expect(paramsArray[0][0] !== '?').toBe(true);
});
+ });
- it('should decode params', () => {
- window.history.pushState('', '', '?label_name%5B%5D=test');
+ describe('urlParamsToObject', () => {
+ it('parses path for label with trailing +', () => {
+ expect(
+ commonUtils.urlParamsToObject('label_name[]=label%2B', {}),
+ ).toEqual({
+ label_name: ['label+'],
+ });
+ });
+ it('parses path for milestone with trailing +', () => {
expect(
- commonUtils.getUrlParamsArray()[0],
- ).toBe('label_name[]=test');
+ commonUtils.urlParamsToObject('milestone_title=A%2B', {}),
+ ).toEqual({
+ milestone_title: 'A+',
+ });
+ });
- window.history.pushState('', '', '?');
+ it('parses path for search terms with spaces', () => {
+ expect(
+ commonUtils.urlParamsToObject('search=two+words', {}),
+ ).toEqual({
+ search: 'two words',
+ });
});
});
diff --git a/spec/javascripts/lib/utils/mock_data.js b/spec/javascripts/lib/utils/mock_data.js
index fd0d62b751f..93d0d6259b9 100644
--- a/spec/javascripts/lib/utils/mock_data.js
+++ b/spec/javascripts/lib/utils/mock_data.js
@@ -2,4 +2,4 @@ export const faviconDataUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACA
export const overlayDataUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAA85JREFUWAntVllIVGEUPv/9b46O41KplYN7PeRkti8TjQlhCUGh3MmeQugpIsGKAi2soIcIooiohxYKK2daqDAlIpIiWwxtQaJcaHE0d5tMrbn37z9XRqfR0TvVW56Hudf//uec72zfEWBCJjIwkYGJDPzvGSD/KgExN3Oi2Q+2DJgSDYQEMwItVGH1iZGmJw/Si1y+/PwVAMYYib22MYc/8hVQFgKDEfYoId0KYzagAQebsos/ewMZoeB9wdffcTYpQSaCTWHKoqSQaDk7zkIt0+aCUR8BelEHrf3dUNv9AcqbnsHtT5UKB/hTASh0SLYjnjb/CIDRJi0XiFAaJOpCD8zLpdb4NB66b1OfelthX815dtdRRfiti2aAXLvVLiMQ6olGyztGDkSo4JGGXk8/QFdGpYzpHG2GBQTDhtgVhPEaVbbVpvI6GJz22rv4TcAfrYI1x7Rj5MWWAppomKFVVb2302SFzUkZHAbkG+0b1+Gh77yNYjrmqnWTrLBLRxdvBWv8qlFujH/kYjJYyvLkj71t78zAUvzMAMnHhpN4zf9UREJhd8omyssxu1IgazQDwDnHUcNuH6vhPIE1fmuBzHt74Hn7W89jWGtcAjoaIDOFrdcMYJBkgOCoaRF0Lj0oglddDbCj6tRvKjphEpgjkzEQs2YAKsNxMzjn3nKurhzK+Ly7xe28ua8TwgMMcHJZnvvT0BPtEEKM4tDJ+C8GvIIk4ylINIXVZ0EUKJxYuh3mhCeokbudl6TtVc88dfBdLwbyaWB6zQCYQJpBYSrDGQxBQ/ZWRM2B+VNmQnVnHWx7elyNuL2/R336co7KyJR8CL9oLgEuFlREevWUkEl6uGwpVEG4FBm0OEf9N10NMgPlvWYAuNVwsWDKvcUNYsHUWTCZ13ysyFEXe6TO6aC8CUr9IiK+A05TQrc8yjwmxARHeeMAPlfQJw+AQRwu0YhL/GDXi9NwufG+S8dYkuYMqIb4SsWthotlNMOUCOM6r+G9cqXxPmd1dqrBav/o1zJy2l5/NUjJA/VORwYuFnOUaTQcPs9wMqwV++Xv8oADxKAcZ8nLPr8AoGW+xR6HSqYk3GodAz2QNj0V+Gr26dT9ASNH5239Pf0gktVNWZca8ZvfAFBprWS6hSu1pqt++Y0PD+WIwDAhIWQGtzvSHDbcodfFUFB9hg1Gjs5LXqIdFL+acFBl+FddqYwdxsWC3I70OvgfUaA65zhq2O2c8VxYcyIGFTVlXegYtvCXANCQZJMobjVcLMjtSK/IcEgyOOe8Ve5w7ryKDefp2P3+C/5ohv8HZmVLAAAAAElFTkSuQmCC';
-export const faviconWithOverlayDataUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAGt0lEQVRYR8WWf3DT5R3H35/vN0lDiCztSuiPAEnTFhSOUamIzFGokwMH55g0E845yjbP6+4qIoiHY6JjnHLeRI6h9jgQpQcD3ImH2u00eHBwjGthKLI1TSm26S8oKYVS0vT7/X52z7ckTUhqC/yx55/kks/zeb+ez6/nIWYm/B8XJQB4SGq6lL+CJA47vvRtvWs2D0nNl/Kf0qBZxx6u23arv0QAAIHivK8BynB4ffa7BgDQXJx/ngGnw+uThgTwP5ZnMocoJAxVxZDVZ+0L5n5WF75TkMafjLdJxpSg2E+gqW1X7zk3rbpaifhLiEBgTv4mEFbpBoTyu01DoDj/dQAv9rvjtdnp/k3Yx9rgAMV5QYCsAAwAgg6vL/1OTy/2BYrzzwLIBWACuNHhrXPG+otGoKaw0JA58kqGJtOFfgPS8yWT4sz88nzj7UIIfz+wd0mRdEZPLMnp2V/8R0+JrhLbBYFHJvwWzBUxYgqYNzhG+zfEhm24MIE5ectBtP0W+y0Or29FcoDifHFSRxwAcMrh9c0YrmisXaA4r0V0U8xvopgDDq9PpCQ+Ag0/zbEbNUNbMiG9fTwkDTsKHpJa2t1Zmiw1AqLg+tMZ+R6WVVtnZ2qP6Ib+FIjh05G3lsDrB4xjUIbRDeM+WZLJYZ4B1rKMzKPm/fdyzs9qg6WT225IMnPcuYjxbPZhn57qaA00zc4/QYT7b1b/wAZmDYSLjsN1WcmiM+6jXz7JTCs1aNPASBjrtrCGOXVBLK9ph72772bc0REZcsQlkEVoOxblhaFBH0Bxi6GBWFNC8gpV0XqYSe/hI85R9o1zxr/QaZbdbmuW9oRzljRrzBRkW9JhMaTgYugKzl35DlXNJ/Fp43FImoZnz7T0ln7bLihM0g85N627vkWPgLrbvYyCvAP1+rRIWETA5QsyQlcJYOCbMRasWpALtljwSsFyeJxFYsoNWqdN1y/ildM78Y/WGjxx8TL+ol3oluy8VupKe7cfoNLdCJkdqEUPOmBJ5ksJoae91mBps5lQ6pkIm20MPiz6A3KsmcNukDe/3Ye3zh3A77Q2XqcGjslLz88i/nB8pkpSoL8nAFSTBpUN4qSxS5KB5jOGUOniCebmzFQcevSN2xKP+Fp7ajt21f8TOxU/5i45JZFS6XwcTB9HxZgUnGTRNgk31x5jet+aGU7jWw+UweOcPeyTxxoqrGL25+UwdjehSvnmOVIqcz4C8y8GAABcQwjnYI5NheikhQWT+EZmDh2ev/l7cz4U2cGmYyg78TYqVH87Kbtd1wFY4hsVQAt14zu2RiDaTUZMf/BHWD35STx37wDv94k1dLeh7MRmvDZ1GR5Inxg17dX6MPnjZfh5X6tGSqXrV2B8ACIx98UNGOlV4CxCuA6zqIeq9FQ8c68bhx7ZiIK06CQdVF+Il3y1Hq03gnDfk4Uj8zbH2T51dCPOtlW39Q+iPTl2VSMfwKPiKw8aTuhgpl1Zdqxzj8PphRWwm21xZjv9VcgYkYb52dP132PFbSYr/la0DpNtrrg9a2oqsKfB2zlwG+4nSe1z7QDjaQBi2Eh6J4QRwimYt43LwOsuB2oX7YLVMCLqTAya3xx/EwZJxtYHy3WhyMkHExebXz3zAbbXfdo7AFBRaMAz1Ypa6XoaoPejKRGteZm6D3SlWVdOcOHo/Lfj2u9aXw+WHNmA00G/DiFEO0Jd+meyk0fIf/+vLfik6Xhj4qN0v7i5HCY1bBQPk+ij9GSzNbzYNdH03kMrscARfzvHQgiBocSFTVHVCrW+u+WrpK9iCIgS1rRK93oG/1GkRJVIup8KMNs1Sw/1rUtALD36ZzRca8XeJDmPtRc18vDn5SCJViYHENY3IZTK3JkE7RAYtpdkp3bAaJeOzN+CsSMTX+wqa7ih9sbVSLI2WV3znihAJYXZPThA7M6KQoM2MniyhUxTioxTpKLMadjx8Jqh5k3S//8d9GOh92XWmP/aXLKvfHgA0ZTklL0jj9m6UR6L5+9bjFWTPLcFIWbCY1+8pHb0drWybJ4aWLQrODyAWJndzoyylNyGg0hL+bV7Ll4rKIWB5CFBxMlLj21SL4W6QjDQjwOL9n4tNt0+AADPfo+UqgXPHJLSJrkso7F6ylLMy56OFMmYACIKblvtQext8Iqp0swyLYiI3zEAbs6Ml3cXv/p3Y+ryq5KcnSKb1Jmj75P7X0Rm/UV0tvO86r/WIhORwszvkmHEehH2WMo7ikDUQUWhoaIG+NNc96Os8eMEmklE2Qy2ANTO0OrA+CwFOFBfsq8pWZ7+B25aDBxvPp+QAAAAAElFTkSuQmCC';
+export const faviconWithOverlayDataUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAGtElEQVRYR8WXf3CT9R3H35/nSdIQIktrCf0RStI0FYRjVBAccxTq5MDBKUoz4ZyjbPO87q4yBsPDMdExTjlvIsdQexyI0oMBeuKhdjsNHhwcMgpjIlublLIm/UlJKZSSJs/z/e6+T5v0CQ22wB/7/pPck8/383l9fj6fEOec8H88NAjAS1LwknsFSVLU8WXd1rtm85LUeKnwGQKzjj3s33azvsEAAEIlnn8ByHL4/Pa7BgAQLCm8QOBOh88vDQkQeMxjMkcQEYKqYsyJWWPhgs/80TsFafzROJtkNIXFfYI0pfXqPeennjqlxPUNikBoTuEmEF+lCRBV3G0aQiWFrwH8d30AWJubGdiEfZzdGqDEEwbICnADQGGHry7zTr0X94IlnnMACggwAWh0+PxOvb5EBGqmTTNkj7ySxWS62C+g5Usm1Zn95YXG24UQ+r5n75Li6Ux4LBkyc7/4t5YSLSr6Lgg9UvBLcKocMEYKON/gGB3YoA/bcGFCczzLQdieLE9bHL66FakBSjzCU0cSAHDa4at7aLhG9XLBEk8zAVnxZxyIEhBy+PwFgwAafpxvNzK5NZUhrX28JA07Cl6SmtvcOUwm4ZAouHj7ad+jMrN1dqb3iG7oS4EYPh2etQS+XiesC8TQ3ZD3yZJsHuUPgbMcI+ej5v3ncv5PasNlk1p7JJnzJL+I0/O5h+u0VCdqIDi78AQRHuirft3hYJzQPvawPydVdPI+/OnTnNNKBjYVXHRa8rFFGeb4w1he0wZ7d/84IXTEhxzxUsgitB2LPFGwvgGUfLSeZUpEXqEqrIdz0nr4iHOUfeOccb/tNMtutzWHPeWcJc0aMxm5lkxYDGloj1zB+Sv/RXXTSXzaeBwSY3j+bHNv2bdtMYCbpHtRkNFd36xFQN3tXkZhvgP1fdPi5kMEXL4oIXKVAA58M8aCVQs84BYLXi5aDq+zGJTqYr+i4PV2vHxmJ/7WUoOn2i/jz6yhW7JjrdSV8U4fQFV+I2Q4UIsedMCSSlcsgp72WtnSajOhzDsBNtsYfFD8e+Rbs4fdIG98uw9vnj+AX7FWvk4NHZOXXphF/INx2SpJIU2L8L4GDAoMwlP9kWSg6awcKVs83tyUnY5Dj75+W8bjutae3o5d9X/HTiWAuUtOS6RUOR8Hp48TxjgU/AMSeKJ1Ej/tMWXG1sxwGt98sBxe5+xhe64XVLiK2Z9XwNgdRLXyzQsC4ENwelIHAFxDBOdh1qdCdNLCoon8RnY+HZ6/+TtzPhTZweAxlJ94C5VqoI2U3a7rACzJjQqgBd24CGscos1kxPQZ38fqSU/jhQkDvN9lrKG7FeUnNuPVKcvwYOb4hGgvi2HSx8vwRKyJkVLl+hk43gdBAcfADBD1cA4RXIdZ1EN1Zjqem+DGoUc2oigjMUlvaV8YL/1qPVpuhOG+JwdH5m1Okn3m6Eacaz3V2jeI9uTbVYY6AKOSKw8MX0MBg2lXjh3r3Hk4s7ASdrMtSWxnoBpZIzIwP3e69lxv3Gay4q/F6zDJ5kq6s6amEnsafJ0Db8P9JKkx1w5wPJuY36IToojgNMzb8rLwmsuB2kW7YDWMSCgTg+YXx9+AQZKxdUaFZiju+a2Mi8uvnH0f2/2f9g4AVE4z4LlTilrlehag9xIpEam4jO4DXfdaV97nwtH5byW137VYD5Yc2YAz4YAGIYx2RLq0z1Sex8l//fUWfBI83jh4Kd1PEuAwqVGjWEwSS+nJJmt0sWu86d0frMQCR/LbWQ8hDAxlXMgUV69Q67ubv0q5FUNAlHKmVLnXE/gfREpUiaQHqAizXbO0UN98BMTSo39Cw7UW7E2Rc728qJGHP68ASbQyNYCQTkAUzCSwQ+CwvSjnsQPGLOnI/C0YO3Lwxq5yhhtqb1KNpGqT1TXvigJU0jh33xpAf7NymoGNDJ9sJtPkYuNkqTh7KnY8vGaoeZPy93+GA1joe4kzzv/SVLqvYngA/dFgVfnlb8tjtm6Ux+I39y/Gqone24IQM+GxL15UO3q7WrhsnhJatCs8PAC9md3OrPK0goaDyEj7uXsuXi0qg4HkIUGE52XHNqmXIl0RGOiHoUV7xb+v5K14SC39At79Ximdhc8ekjImuiyjsXryUszLnY40yThIhSi4bbUHsbfBJ6ZKE5dpQdz4HQOgf2a8tLvklY+M6cuvSnJummxSZ46+X+7biMzaRnSu84IauNYsE5HCOX+HDCPWi7DrKW8/BTcVZ2UN8Me57kc5448TaCYR5XJwC0BtHMwPjs/SgAP1pfuCqSL8Pxhr/wunLWAOAAAAAElFTkSuQmCC';
diff --git a/spec/javascripts/lib/utils/text_utility_spec.js b/spec/javascripts/lib/utils/text_utility_spec.js
index d60485b1308..ac3270baef5 100644
--- a/spec/javascripts/lib/utils/text_utility_spec.js
+++ b/spec/javascripts/lib/utils/text_utility_spec.js
@@ -63,6 +63,12 @@ describe('text_utility', () => {
});
});
+ describe('slugifyWithHyphens', () => {
+ it('should replaces whitespaces with hyphens and convert to lower case', () => {
+ expect(textUtils.slugifyWithHyphens('My Input String')).toEqual('my-input-string');
+ });
+ });
+
describe('stripHtml', () => {
it('replaces html tag with the default replacement', () => {
expect(textUtils.stripHtml('This is a text with <p>html</p>.')).toEqual(
diff --git a/spec/javascripts/monitoring/graph/flag_spec.js b/spec/javascripts/monitoring/graph/flag_spec.js
index 19278312b6d..a837b71db0b 100644
--- a/spec/javascripts/monitoring/graph/flag_spec.js
+++ b/spec/javascripts/monitoring/graph/flag_spec.js
@@ -35,7 +35,7 @@ const defaultValuesComponent = {
unitOfDisplay: 'ms',
currentDataIndex: 0,
legendTitle: 'Average',
- currentCoordinates: [],
+ currentCoordinates: {},
};
const deploymentFlagData = {
diff --git a/spec/javascripts/monitoring/graph_spec.js b/spec/javascripts/monitoring/graph_spec.js
index a46a387a534..990619b4109 100644
--- a/spec/javascripts/monitoring/graph_spec.js
+++ b/spec/javascripts/monitoring/graph_spec.js
@@ -113,6 +113,9 @@ describe('Graph', () => {
projectPath,
});
+ // simulate moving mouse over data series
+ component.seriesUnderMouse = component.timeSeries;
+
component.positionFlag();
expect(component.currentData).toBe(component.timeSeries[0].values[10]);
});
diff --git a/spec/javascripts/notes/components/note_actions_spec.js b/spec/javascripts/notes/components/note_actions_spec.js
index 52cc42cb53d..d7298cb3483 100644
--- a/spec/javascripts/notes/components/note_actions_spec.js
+++ b/spec/javascripts/notes/components/note_actions_spec.js
@@ -28,7 +28,7 @@ describe('issue_note_actions component', () => {
canEdit: true,
canAwardEmoji: true,
canReportAsAbuse: true,
- noteId: 539,
+ noteId: '539',
noteUrl: 'https://localhost:3000/group/project/merge_requests/1#note_1',
reportAbusePath:
'/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F7%23note_539&user_id=26',
@@ -59,6 +59,20 @@ describe('issue_note_actions component', () => {
expect(vm.$el.querySelector(`a[href="${props.reportAbusePath}"]`)).toBeDefined();
});
+ it('should be possible to copy link to a note', () => {
+ expect(vm.$el.querySelector('.js-btn-copy-note-link')).not.toBeNull();
+ });
+
+ it('should not show copy link action when `noteUrl` prop is empty', done => {
+ vm.noteUrl = '';
+ Vue.nextTick()
+ .then(() => {
+ expect(vm.$el.querySelector('.js-btn-copy-note-link')).toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
it('should be possible to delete comment', () => {
expect(vm.$el.querySelector('.js-note-delete')).toBeDefined();
});
@@ -77,7 +91,7 @@ describe('issue_note_actions component', () => {
canEdit: false,
canAwardEmoji: false,
canReportAsAbuse: false,
- noteId: 539,
+ noteId: '539',
noteUrl: 'https://localhost:3000/group/project/merge_requests/1#note_1',
reportAbusePath:
'/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F7%23note_539&user_id=26',
diff --git a/spec/javascripts/notes/components/note_awards_list_spec.js b/spec/javascripts/notes/components/note_awards_list_spec.js
index 9d98ba219da..6a6a810acff 100644
--- a/spec/javascripts/notes/components/note_awards_list_spec.js
+++ b/spec/javascripts/notes/components/note_awards_list_spec.js
@@ -30,7 +30,7 @@ describe('note_awards_list component', () => {
propsData: {
awards: awardsMock,
noteAuthorId: 2,
- noteId: 545,
+ noteId: '545',
canAwardEmoji: true,
toggleAwardPath: '/gitlab-org/gitlab-ce/notes/545/toggle_award_emoji',
},
@@ -70,7 +70,7 @@ describe('note_awards_list component', () => {
propsData: {
awards: awardsMock,
noteAuthorId: 2,
- noteId: 545,
+ noteId: '545',
canAwardEmoji: false,
toggleAwardPath: '/gitlab-org/gitlab-ce/notes/545/toggle_award_emoji',
},
diff --git a/spec/javascripts/notes/components/note_form_spec.js b/spec/javascripts/notes/components/note_form_spec.js
index 95d400ab3df..147ffcf1b81 100644
--- a/spec/javascripts/notes/components/note_form_spec.js
+++ b/spec/javascripts/notes/components/note_form_spec.js
@@ -19,7 +19,7 @@ describe('issue_note_form component', () => {
props = {
isEditing: false,
noteBody: 'Magni suscipit eius consectetur enim et ex et commodi.',
- noteId: 545,
+ noteId: '545',
};
vm = new Component({
@@ -32,6 +32,22 @@ describe('issue_note_form component', () => {
vm.$destroy();
});
+ describe('noteHash', () => {
+ it('returns note hash string based on `noteId`', () => {
+ expect(vm.noteHash).toBe(`#note_${props.noteId}`);
+ });
+
+ it('return note hash as `#` when `noteId` is empty', done => {
+ vm.noteId = '';
+ Vue.nextTick()
+ .then(() => {
+ expect(vm.noteHash).toBe('#');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
describe('conflicts editing', () => {
it('should show conflict message if note changes outside the component', done => {
vm.isEditing = true;
diff --git a/spec/javascripts/notes/components/note_header_spec.js b/spec/javascripts/notes/components/note_header_spec.js
index a3c6bf78988..379780f43a0 100644
--- a/spec/javascripts/notes/components/note_header_spec.js
+++ b/spec/javascripts/notes/components/note_header_spec.js
@@ -33,7 +33,7 @@ describe('note_header component', () => {
},
createdAt: '2017-08-02T10:51:58.559Z',
includeToggle: false,
- noteId: 1394,
+ noteId: '1394',
expanded: true,
},
}).$mount();
@@ -47,6 +47,16 @@ describe('note_header component', () => {
it('should render timestamp link', () => {
expect(vm.$el.querySelector('a[href="#note_1394"]')).toBeDefined();
});
+
+ it('should not render user information when prop `author` is empty object', done => {
+ vm.author = {};
+ Vue.nextTick()
+ .then(() => {
+ expect(vm.$el.querySelector('.note-header-author-name')).toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
});
describe('discussion', () => {
@@ -66,7 +76,7 @@ describe('note_header component', () => {
},
createdAt: '2017-08-02T10:51:58.559Z',
includeToggle: true,
- noteId: 1395,
+ noteId: '1395',
expanded: true,
},
}).$mount();
diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js
index 0423fcb6ec4..1f030e5af28 100644
--- a/spec/javascripts/notes/mock_data.js
+++ b/spec/javascripts/notes/mock_data.js
@@ -66,7 +66,7 @@ export const individualNote = {
individual_note: true,
notes: [
{
- id: 1390,
+ id: '1390',
attachment: {
url: null,
filename: null,
@@ -111,7 +111,7 @@ export const individualNote = {
};
export const note = {
- id: 546,
+ id: '546',
attachment: {
url: null,
filename: null,
@@ -174,7 +174,7 @@ export const discussionMock = {
expanded: true,
notes: [
{
- id: 1395,
+ id: '1395',
attachment: {
url: null,
filename: null,
@@ -211,7 +211,7 @@ export const discussionMock = {
path: '/gitlab-org/gitlab-ce/notes/1395',
},
{
- id: 1396,
+ id: '1396',
attachment: {
url: null,
filename: null,
@@ -257,7 +257,7 @@ export const discussionMock = {
path: '/gitlab-org/gitlab-ce/notes/1396',
},
{
- id: 1437,
+ id: '1437',
attachment: {
url: null,
filename: null,
@@ -308,7 +308,7 @@ export const discussionMock = {
};
export const loggedOutnoteableData = {
- id: 98,
+ id: '98',
iid: 26,
author_id: 1,
description: '',
@@ -358,7 +358,7 @@ export const collapseNotesMock = [
individual_note: true,
notes: [
{
- id: 1390,
+ id: '1390',
attachment: null,
author: {
id: 1,
@@ -393,7 +393,7 @@ export const collapseNotesMock = [
individual_note: true,
notes: [
{
- id: 1391,
+ id: '1391',
attachment: null,
author: {
id: 1,
@@ -433,7 +433,7 @@ export const INDIVIDUAL_NOTE_RESPONSE_MAP = {
expanded: true,
notes: [
{
- id: 1390,
+ id: '1390',
attachment: {
url: null,
filename: null,
@@ -495,7 +495,7 @@ export const INDIVIDUAL_NOTE_RESPONSE_MAP = {
expanded: true,
notes: [
{
- id: 1391,
+ id: '1391',
attachment: {
url: null,
filename: null,
@@ -544,7 +544,7 @@ export const INDIVIDUAL_NOTE_RESPONSE_MAP = {
'/gitlab-org/gitlab-ce/notes/1471': {
commands_changes: null,
valid: true,
- id: 1471,
+ id: '1471',
attachment: null,
author: {
id: 1,
@@ -600,7 +600,7 @@ export const DISCUSSION_NOTE_RESPONSE_MAP = {
expanded: true,
notes: [
{
- id: 1471,
+ id: '1471',
attachment: {
url: null,
filename: null,
@@ -671,7 +671,7 @@ export const notesWithDescriptionChanges = [
expanded: true,
notes: [
{
- id: 901,
+ id: '901',
type: null,
attachment: null,
author: {
@@ -718,7 +718,7 @@ export const notesWithDescriptionChanges = [
expanded: true,
notes: [
{
- id: 902,
+ id: '902',
type: null,
attachment: null,
author: {
@@ -765,7 +765,7 @@ export const notesWithDescriptionChanges = [
expanded: true,
notes: [
{
- id: 903,
+ id: '903',
type: null,
attachment: null,
author: {
@@ -809,7 +809,7 @@ export const notesWithDescriptionChanges = [
expanded: true,
notes: [
{
- id: 904,
+ id: '904',
type: null,
attachment: null,
author: {
@@ -854,7 +854,7 @@ export const notesWithDescriptionChanges = [
expanded: true,
notes: [
{
- id: 905,
+ id: '905',
type: null,
attachment: null,
author: {
@@ -898,7 +898,7 @@ export const notesWithDescriptionChanges = [
expanded: true,
notes: [
{
- id: 906,
+ id: '906',
type: null,
attachment: null,
author: {
@@ -945,7 +945,7 @@ export const collapsedSystemNotes = [
expanded: true,
notes: [
{
- id: 901,
+ id: '901',
type: null,
attachment: null,
author: {
@@ -992,7 +992,7 @@ export const collapsedSystemNotes = [
expanded: true,
notes: [
{
- id: 902,
+ id: '902',
type: null,
attachment: null,
author: {
@@ -1039,7 +1039,7 @@ export const collapsedSystemNotes = [
expanded: true,
notes: [
{
- id: 904,
+ id: '904',
type: null,
attachment: null,
author: {
@@ -1084,7 +1084,7 @@ export const collapsedSystemNotes = [
expanded: true,
notes: [
{
- id: 905,
+ id: '905',
type: null,
attachment: null,
author: {
@@ -1129,7 +1129,7 @@ export const collapsedSystemNotes = [
expanded: true,
notes: [
{
- id: 906,
+ id: '906',
type: null,
attachment: null,
author: {
diff --git a/spec/javascripts/projects/project_new_spec.js b/spec/javascripts/projects/project_new_spec.js
index 84515d2bf97..dace834a3c8 100644
--- a/spec/javascripts/projects/project_new_spec.js
+++ b/spec/javascripts/projects/project_new_spec.js
@@ -4,12 +4,14 @@ import projectNew from '~/projects/project_new';
describe('New Project', () => {
let $projectImportUrl;
let $projectPath;
+ let $projectName;
beforeEach(() => {
setFixtures(`
<div class='toggle-import-form'>
<div class='import-url-data'>
<input id="project_import_url" />
+ <input id="project_name" />
<input id="project_path" />
</div>
</div>
@@ -17,6 +19,7 @@ describe('New Project', () => {
$projectImportUrl = $('#project_import_url');
$projectPath = $('#project_path');
+ $projectName = $('#project_name');
});
describe('deriveProjectPathFromUrl', () => {
@@ -129,4 +132,31 @@ describe('New Project', () => {
});
});
});
+
+ describe('deriveSlugFromProjectName', () => {
+ beforeEach(() => {
+ projectNew.bindEvents();
+ $projectName.val('').keyup();
+ });
+
+ it('converts project name to lower case and dash-limited slug', () => {
+ const dummyProjectName = 'My Awesome Project';
+
+ $projectName.val(dummyProjectName);
+
+ projectNew.onProjectNameChange($projectName, $projectPath);
+
+ expect($projectPath.val()).toEqual('my-awesome-project');
+ });
+
+ it('does not add additional dashes in the slug if the project name already contains dashes', () => {
+ const dummyProjectName = 'My-Dash-Delimited Awesome Project';
+
+ $projectName.val(dummyProjectName);
+
+ projectNew.onProjectNameChange($projectName, $projectPath);
+
+ expect($projectPath.val()).toEqual('my-dash-delimited-awesome-project');
+ });
+ });
});
diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js
index 4452c470b82..b89d10cb993 100644
--- a/spec/javascripts/test_bundle.js
+++ b/spec/javascripts/test_bundle.js
@@ -1,6 +1,7 @@
/* eslint-disable
jasmine/no-global-setup, jasmine/no-unsafe-spy, no-underscore-dangle, no-console
*/
+/* global __karma__ */
import $ from 'jquery';
import 'vendor/jasmine-jquery';
@@ -41,8 +42,8 @@ jasmine.getJSONFixtures().fixturesPath = FIXTURES_PATH;
beforeAll(() => {
jasmine.addMatchers(
jasmineDiff(jasmine, {
- colors: true,
- inline: true,
+ colors: !__karma__.config.isCi,
+ inline: !__karma__.config.isCi,
}),
);
jasmine.addMatchers(customMatchers);
diff --git a/spec/javascripts/vue_shared/components/file_icon_spec.js b/spec/javascripts/vue_shared/components/file_icon_spec.js
index 1c666fc6c55..f2a09d08829 100644
--- a/spec/javascripts/vue_shared/components/file_icon_spec.js
+++ b/spec/javascripts/vue_shared/components/file_icon_spec.js
@@ -62,9 +62,11 @@ describe('File Icon component', () => {
loading: true,
});
- expect(
- vm.$el.querySelector('i').getAttribute('class'),
- ).toEqual('fa fa-spin fa-spinner fa-1x');
+ const { classList } = vm.$el.querySelector('i');
+ expect(classList.contains('fa')).toEqual(true);
+ expect(classList.contains('fa-spin')).toEqual(true);
+ expect(classList.contains('fa-spinner')).toEqual(true);
+ expect(classList.contains('fa-1x')).toEqual(true);
});
it('should add a special class and a size class', () => {
diff --git a/spec/javascripts/vue_shared/components/loading_icon_spec.js b/spec/javascripts/vue_shared/components/loading_icon_spec.js
deleted file mode 100644
index 5cd3466f501..00000000000
--- a/spec/javascripts/vue_shared/components/loading_icon_spec.js
+++ /dev/null
@@ -1,54 +0,0 @@
-import Vue from 'vue';
-import loadingIcon from '~/vue_shared/components/loading_icon.vue';
-
-describe('Loading Icon Component', () => {
- let LoadingIconComponent;
-
- beforeEach(() => {
- LoadingIconComponent = Vue.extend(loadingIcon);
- });
-
- it('should render a spinner font awesome icon', () => {
- const component = new LoadingIconComponent().$mount();
-
- expect(
- component.$el.querySelector('i').getAttribute('class'),
- ).toEqual('fa fa-spin fa-spinner fa-1x');
-
- expect(component.$el.tagName).toEqual('DIV');
- expect(component.$el.classList).toContain('text-center');
- expect(component.$el.classList).toContain('loading-container');
- });
-
- it('should render accessibility attributes', () => {
- const component = new LoadingIconComponent().$mount();
-
- const icon = component.$el.querySelector('i');
- expect(icon.getAttribute('aria-hidden')).toEqual('true');
- expect(icon.getAttribute('aria-label')).toEqual('Loading');
- });
-
- it('should render the provided label', () => {
- const component = new LoadingIconComponent({
- propsData: {
- label: 'This is a loading icon',
- },
- }).$mount();
-
- expect(
- component.$el.querySelector('i').getAttribute('aria-label'),
- ).toEqual('This is a loading icon');
- });
-
- it('should render the provided size', () => {
- const component = new LoadingIconComponent({
- propsData: {
- size: '2',
- },
- }).$mount();
-
- expect(
- component.$el.querySelector('i').classList.contains('fa-2x'),
- ).toEqual(true);
- });
-});
diff --git a/spec/javascripts/vue_shared/components/notes/system_note_spec.js b/spec/javascripts/vue_shared/components/notes/system_note_spec.js
index 2a6015fe35f..adcb1c858aa 100644
--- a/spec/javascripts/vue_shared/components/notes/system_note_spec.js
+++ b/spec/javascripts/vue_shared/components/notes/system_note_spec.js
@@ -9,7 +9,7 @@ describe('system note component', () => {
beforeEach(() => {
props = {
note: {
- id: 1424,
+ id: '1424',
author: {
id: 1,
name: 'Root',
diff --git a/spec/javascripts/vue_shared/components/pagination_links_spec.js b/spec/javascripts/vue_shared/components/pagination_links_spec.js
new file mode 100644
index 00000000000..c9d183872b4
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/pagination_links_spec.js
@@ -0,0 +1,72 @@
+import Vue from 'vue';
+import PaginationLinks from '~/vue_shared/components/pagination_links.vue';
+import { s__ } from '~/locale';
+import mountComponent from '../../helpers/vue_mount_component_helper';
+
+describe('Pagination links component', () => {
+ const paginationLinksComponent = Vue.extend(PaginationLinks);
+ const change = page => page;
+ const pageInfo = {
+ page: 3,
+ perPage: 5,
+ total: 30,
+ };
+ const translations = {
+ firstText: s__('Pagination|« First'),
+ prevText: s__('Pagination|Prev'),
+ nextText: s__('Pagination|Next'),
+ lastText: s__('Pagination|Last »'),
+ };
+
+ let paginationLinks;
+ let glPagination;
+ let destinationComponent;
+
+ beforeEach(() => {
+ paginationLinks = mountComponent(
+ paginationLinksComponent,
+ {
+ change,
+ pageInfo,
+ },
+ );
+ [glPagination] = paginationLinks.$children;
+ [destinationComponent] = glPagination.$children;
+ });
+
+ afterEach(() => {
+ paginationLinks.$destroy();
+ });
+
+ it('should provide translated text to GitLab UI pagination', () => {
+ Object.entries(translations).forEach(entry =>
+ expect(
+ destinationComponent[entry[0]],
+ ).toBe(entry[1]),
+ );
+ });
+
+ it('should pass change to GitLab UI pagination', () => {
+ expect(
+ Object.is(glPagination.change, change),
+ ).toBe(true);
+ });
+
+ it('should pass page from pageInfo to GitLab UI pagination', () => {
+ expect(
+ destinationComponent.value,
+ ).toBe(pageInfo.page);
+ });
+
+ it('should pass per page from pageInfo to GitLab UI pagination', () => {
+ expect(
+ destinationComponent.perPage,
+ ).toBe(pageInfo.perPage);
+ });
+
+ it('should pass total items from pageInfo to GitLab UI pagination', () => {
+ expect(
+ destinationComponent.totalRows,
+ ).toBe(pageInfo.total);
+ });
+});
diff --git a/spec/lib/api/helpers/pagination_spec.rb b/spec/lib/api/helpers/pagination_spec.rb
index c73c6023b60..0a7682d906b 100644
--- a/spec/lib/api/helpers/pagination_spec.rb
+++ b/spec/lib/api/helpers/pagination_spec.rb
@@ -189,9 +189,9 @@ describe API::Helpers::Pagination do
it 'it returns the right link to the next page' do
allow(subject).to receive(:params)
.and_return({ pagination: 'keyset', ks_prev_id: projects[3].id, ks_prev_name: projects[3].name, per_page: 2 })
+
expect_header('X-Per-Page', '2')
expect_header('X-Next-Page', "#{value}?ks_prev_id=#{projects[6].id}&ks_prev_name=#{projects[6].name}&pagination=keyset&per_page=2")
-
expect_header('Link', anything) do |_key, val|
expect(val).to include('rel="next"')
end
diff --git a/spec/lib/banzai/filter/markdown_filter_spec.rb b/spec/lib/banzai/filter/markdown_filter_spec.rb
index a515d07b072..cf49249756a 100644
--- a/spec/lib/banzai/filter/markdown_filter_spec.rb
+++ b/spec/lib/banzai/filter/markdown_filter_spec.rb
@@ -40,6 +40,12 @@ describe Banzai::Filter::MarkdownFilter do
expect(result).to start_with("<pre><code>")
end
+
+ it 'works with utf8 chars in language' do
+ result = filter("```æ—¥\nsome code\n```")
+
+ expect(result).to start_with("<pre><code lang=\"æ—¥\">")
+ end
end
context 'using Redcarpet' do
@@ -60,4 +66,21 @@ describe Banzai::Filter::MarkdownFilter do
end
end
end
+
+ describe 'footnotes in tables' do
+ it 'processes footnotes in table cells' do
+ text = <<-MD.strip_heredoc
+ | Column1 |
+ | --------- |
+ | foot [^1] |
+
+ [^1]: a footnote
+ MD
+
+ result = filter(text)
+
+ expect(result).to include('<td>foot <sup')
+ expect(result).to include('<section class="footnotes">')
+ end
+ end
end
diff --git a/spec/lib/banzai/filter/spaced_link_filter_spec.rb b/spec/lib/banzai/filter/spaced_link_filter_spec.rb
index 4463c011522..1ad7f3ff567 100644
--- a/spec/lib/banzai/filter/spaced_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/spaced_link_filter_spec.rb
@@ -3,49 +3,73 @@ require 'spec_helper'
describe Banzai::Filter::SpacedLinkFilter do
include FilterSpecHelper
- let(:link) { '[example](page slug)' }
+ let(:link) { '[example](page slug)' }
+ let(:image) { '![example](img test.jpg)' }
- it 'converts slug with spaces to a link' do
- doc = filter("See #{link}")
+ context 'when a link is detected' do
+ it 'converts slug with spaces to a link' do
+ doc = filter("See #{link}")
- expect(doc.at_css('a').text).to eq 'example'
- expect(doc.at_css('a')['href']).to eq 'page%20slug'
- expect(doc.at_css('p')).to eq nil
- end
+ expect(doc.at_css('a').text).to eq 'example'
+ expect(doc.at_css('a')['href']).to eq 'page%20slug'
+ expect(doc.at_css('a')['title']).to be_nil
+ expect(doc.at_css('p')).to be_nil
+ end
- it 'converts slug with spaces and a title to a link' do
- link = '[example](page slug "title")'
- doc = filter("See #{link}")
+ it 'converts slug with spaces and a title to a link' do
+ link = '[example](page slug "title")'
+ doc = filter("See #{link}")
- expect(doc.at_css('a').text).to eq 'example'
- expect(doc.at_css('a')['href']).to eq 'page%20slug'
- expect(doc.at_css('a')['title']).to eq 'title'
- expect(doc.at_css('p')).to eq nil
- end
+ expect(doc.at_css('a').text).to eq 'example'
+ expect(doc.at_css('a')['href']).to eq 'page%20slug'
+ expect(doc.at_css('a')['title']).to eq 'title'
+ expect(doc.at_css('p')).to be_nil
+ end
- it 'does nothing when markdown_engine is redcarpet' do
- exp = act = link
- expect(filter(act, markdown_engine: :redcarpet).to_html).to eq exp
- end
+ it 'does nothing when markdown_engine is redcarpet' do
+ exp = act = link
+ expect(filter(act, markdown_engine: :redcarpet).to_html).to eq exp
+ end
+
+ it 'does nothing with empty text' do
+ link = '[](page slug)'
+ doc = filter("See #{link}")
+
+ expect(doc.at_css('a')).to be_nil
+ end
- it 'does nothing with empty text' do
- link = '[](page slug)'
- doc = filter("See #{link}")
+ it 'does nothing with an empty slug' do
+ link = '[example]()'
+ doc = filter("See #{link}")
- expect(doc.at_css('a')).to eq nil
+ expect(doc.at_css('a')).to be_nil
+ end
end
- it 'does nothing with an empty slug' do
- link = '[example]()'
- doc = filter("See #{link}")
+ context 'when an image is detected' do
+ it 'converts slug with spaces to an iamge' do
+ doc = filter("See #{image}")
+
+ expect(doc.at_css('img')['src']).to eq 'img%20test.jpg'
+ expect(doc.at_css('img')['alt']).to eq 'example'
+ expect(doc.at_css('p')).to be_nil
+ end
+
+ it 'converts slug with spaces and a title to an image' do
+ image = '![example](img test.jpg "title")'
+ doc = filter("See #{image}")
- expect(doc.at_css('a')).to eq nil
+ expect(doc.at_css('img')['src']).to eq 'img%20test.jpg'
+ expect(doc.at_css('img')['alt']).to eq 'example'
+ expect(doc.at_css('img')['title']).to eq 'title'
+ expect(doc.at_css('p')).to be_nil
+ end
end
it 'converts multiple URLs' do
link1 = '[first](slug one)'
link2 = '[second](http://example.com/slug two)'
- doc = filter("See #{link1} and #{link2}")
+ doc = filter("See #{link1} and #{image} and #{link2}")
found_links = doc.css('a')
@@ -54,6 +78,12 @@ describe Banzai::Filter::SpacedLinkFilter do
expect(found_links[0]['href']).to eq 'slug%20one'
expect(found_links[1].text).to eq 'second'
expect(found_links[1]['href']).to eq 'http://example.com/slug%20two'
+
+ found_images = doc.css('img')
+
+ expect(found_images.size).to eq(1)
+ expect(found_images[0]['src']).to eq 'img%20test.jpg'
+ expect(found_images[0]['alt']).to eq 'example'
end
described_class::IGNORE_PARENTS.each do |elem|
diff --git a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
index 75413596431..df24cef0b8b 100644
--- a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
@@ -87,4 +87,22 @@ describe Banzai::Pipeline::GfmPipeline do
end
end
end
+
+ describe 'markdown link or image urls having spaces' do
+ let(:project) { create(:project, :public) }
+
+ it 'rewrites links with spaces in url' do
+ markdown = "[Link to Page](page slug)"
+ output = described_class.to_html(markdown, project: project)
+
+ expect(output).to include("href=\"page%20slug\"")
+ end
+
+ it 'rewrites images with spaces in url' do
+ markdown = "![My Image](test image.png)"
+ output = described_class.to_html(markdown, project: project)
+
+ expect(output).to include("src=\"test%20image.png\"")
+ end
+ end
end
diff --git a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
index 52b8c9be647..64ca3ec345d 100644
--- a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
@@ -178,4 +178,25 @@ describe Banzai::Pipeline::WikiPipeline do
end
end
end
+
+ describe 'videos' do
+ let(:namespace) { create(:namespace, name: "wiki_link_ns") }
+ let(:project) { create(:project, :public, name: "wiki_link_project", namespace: namespace) }
+ let(:project_wiki) { ProjectWiki.new(project, double(:user)) }
+ let(:page) { build(:wiki_page, wiki: project_wiki, page: OpenStruct.new(url_path: 'nested/twice/start-page')) }
+
+ it 'generates video html structure' do
+ markdown = "![video_file](video_file_name.mp4)"
+ output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug)
+
+ expect(output).to include('<video src="/wiki_link_ns/wiki_link_project/wikis/nested/twice/video_file_name.mp4"')
+ end
+
+ it 'rewrites and replaces video links names with white spaces to %20' do
+ markdown = "![video file](video file name.mp4)"
+ output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug)
+
+ expect(output).to include('<video src="/wiki_link_ns/wiki_link_project/wikis/nested/twice/video%20file%20name.mp4"')
+ end
+ end
end
diff --git a/spec/lib/forever_spec.rb b/spec/lib/forever_spec.rb
index cf40c467c72..494c0561975 100644
--- a/spec/lib/forever_spec.rb
+++ b/spec/lib/forever_spec.rb
@@ -7,6 +7,7 @@ describe Forever do
context 'when using PostgreSQL' do
it 'should return Postgresql future date' do
allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
+
expect(subject).to eq(described_class::POSTGRESQL_DATE)
end
end
@@ -14,6 +15,7 @@ describe Forever do
context 'when using MySQL' do
it 'should return MySQL future date' do
allow(Gitlab::Database).to receive(:postgresql?).and_return(false)
+
expect(subject).to eq(described_class::MYSQL_DATE)
end
end
diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb
index 5a78ce783dd..b43aca8a354 100644
--- a/spec/lib/gitlab/ci/config_spec.rb
+++ b/spec/lib/gitlab/ci/config_spec.rb
@@ -124,4 +124,237 @@ describe Gitlab::Ci::Config do
end
end
end
+
+ context "when using 'include' directive" do
+ let(:project) { create(:project, :repository) }
+ let(:remote_location) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
+ let(:local_location) { 'spec/fixtures/gitlab/ci/external_files/.gitlab-ci-template-1.yml' }
+
+ let(:remote_file_content) do
+ <<~HEREDOC
+ variables:
+ AUTO_DEVOPS_DOMAIN: domain.example.com
+ POSTGRES_USER: user
+ POSTGRES_PASSWORD: testing-password
+ POSTGRES_ENABLED: "true"
+ POSTGRES_DB: $CI_ENVIRONMENT_SLUG
+ HEREDOC
+ end
+
+ let(:local_file_content) do
+ File.read(Rails.root.join(local_location))
+ end
+
+ let(:gitlab_ci_yml) do
+ <<~HEREDOC
+ include:
+ - #{local_location}
+ - #{remote_location}
+
+ image: ruby:2.2
+ HEREDOC
+ end
+
+ let(:config) do
+ described_class.new(gitlab_ci_yml, project: project, sha: '12345')
+ end
+
+ before do
+ WebMock.stub_request(:get, remote_location)
+ .to_return(body: remote_file_content)
+
+ allow(project.repository)
+ .to receive(:blob_data_at).and_return(local_file_content)
+ end
+
+ context "when gitlab_ci_yml has valid 'include' defined" do
+ it 'should return a composed hash' do
+ before_script_values = [
+ "apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs", "ruby -v",
+ "which ruby",
+ "gem install bundler --no-ri --no-rdoc",
+ "bundle install --jobs $(nproc) \"${FLAGS[@]}\""
+ ]
+ variables = {
+ AUTO_DEVOPS_DOMAIN: "domain.example.com",
+ POSTGRES_USER: "user",
+ POSTGRES_PASSWORD: "testing-password",
+ POSTGRES_ENABLED: "true",
+ POSTGRES_DB: "$CI_ENVIRONMENT_SLUG"
+ }
+ composed_hash = {
+ before_script: before_script_values,
+ image: "ruby:2.2",
+ rspec: { script: ["bundle exec rspec"] },
+ variables: variables
+ }
+
+ expect(config.to_hash).to eq(composed_hash)
+ end
+ end
+
+ context "when gitlab_ci.yml has invalid 'include' defined" do
+ let(:gitlab_ci_yml) do
+ <<~HEREDOC
+ include: invalid
+ HEREDOC
+ end
+
+ it 'raises error YamlProcessor validationError' do
+ expect { config }.to raise_error(
+ ::Gitlab::Ci::YamlProcessor::ValidationError,
+ "Local file 'invalid' is not valid."
+ )
+ end
+ end
+
+ describe 'external file version' do
+ context 'when external local file SHA is defined' do
+ it 'is using a defined value' do
+ expect(project.repository).to receive(:blob_data_at)
+ .with('eeff1122', local_location)
+
+ described_class.new(gitlab_ci_yml, project: project, sha: 'eeff1122')
+ end
+ end
+
+ context 'when external local file SHA is not defined' do
+ it 'is using latest SHA on the default branch' do
+ expect(project.repository).to receive(:root_ref_sha)
+
+ described_class.new(gitlab_ci_yml, project: project)
+ end
+ end
+ end
+
+ context "when both external files and gitlab_ci.yml defined the same key" do
+ let(:gitlab_ci_yml) do
+ <<~HEREDOC
+ include:
+ - #{remote_location}
+
+ image: ruby:2.2
+ HEREDOC
+ end
+
+ let(:remote_file_content) do
+ <<~HEREDOC
+ image: php:5-fpm-alpine
+ HEREDOC
+ end
+
+ it 'should take precedence' do
+ expect(config.to_hash).to eq({ image: 'ruby:2.2' })
+ end
+ end
+
+ context "when both external files and gitlab_ci.yml define a dictionary of distinct variables" do
+ let(:remote_file_content) do
+ <<~HEREDOC
+ variables:
+ A: 'alpha'
+ B: 'beta'
+ HEREDOC
+ end
+
+ let(:gitlab_ci_yml) do
+ <<~HEREDOC
+ include:
+ - #{remote_location}
+
+ variables:
+ C: 'gamma'
+ D: 'delta'
+ HEREDOC
+ end
+
+ it 'should merge the variables dictionaries' do
+ expect(config.to_hash).to eq({ variables: { A: 'alpha', B: 'beta', C: 'gamma', D: 'delta' } })
+ end
+ end
+
+ context "when both external files and gitlab_ci.yml define a dictionary of overlapping variables" do
+ let(:remote_file_content) do
+ <<~HEREDOC
+ variables:
+ A: 'alpha'
+ B: 'beta'
+ C: 'omnicron'
+ HEREDOC
+ end
+
+ let(:gitlab_ci_yml) do
+ <<~HEREDOC
+ include:
+ - #{remote_location}
+
+ variables:
+ C: 'gamma'
+ D: 'delta'
+ HEREDOC
+ end
+
+ it 'later declarations should take precedence' do
+ expect(config.to_hash).to eq({ variables: { A: 'alpha', B: 'beta', C: 'gamma', D: 'delta' } })
+ end
+ end
+
+ context 'when both external files and gitlab_ci.yml define a job' do
+ let(:remote_file_content) do
+ <<~HEREDOC
+ job1:
+ script:
+ - echo 'hello from remote file'
+ HEREDOC
+ end
+
+ let(:gitlab_ci_yml) do
+ <<~HEREDOC
+ include:
+ - #{remote_location}
+
+ job1:
+ variables:
+ VARIABLE_DEFINED_IN_MAIN_FILE: 'some value'
+ HEREDOC
+ end
+
+ it 'merges the jobs' do
+ expect(config.to_hash).to eq({
+ job1: {
+ script: ["echo 'hello from remote file'"],
+ variables: {
+ VARIABLE_DEFINED_IN_MAIN_FILE: 'some value'
+ }
+ }
+ })
+ end
+
+ context 'when the script key is in both' do
+ let(:gitlab_ci_yml) do
+ <<~HEREDOC
+ include:
+ - #{remote_location}
+
+ job1:
+ script:
+ - echo 'hello from main file'
+ variables:
+ VARIABLE_DEFINED_IN_MAIN_FILE: 'some value'
+ HEREDOC
+ end
+
+ it 'uses the script from the gitlab_ci.yml' do
+ expect(config.to_hash).to eq({
+ job1: {
+ script: ["echo 'hello from main file'"],
+ variables: {
+ VARIABLE_DEFINED_IN_MAIN_FILE: 'some value'
+ }
+ }
+ })
+ end
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/external/file/local_spec.rb b/spec/lib/gitlab/ci/external/file/local_spec.rb
new file mode 100644
index 00000000000..3f32d81a827
--- /dev/null
+++ b/spec/lib/gitlab/ci/external/file/local_spec.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::External::File::Local do
+ let(:project) { create(:project, :repository) }
+ let(:local_file) { described_class.new(location, { project: project, sha: '12345' }) }
+
+ describe '#valid?' do
+ context 'when is a valid local path' do
+ let(:location) { '/vendor/gitlab-ci-yml/existent-file.yml' }
+
+ before do
+ allow_any_instance_of(described_class).to receive(:fetch_local_content).and_return("image: 'ruby2:2'")
+ end
+
+ it 'should return true' do
+ expect(local_file.valid?).to be_truthy
+ end
+ end
+
+ context 'when is not a valid local path' do
+ let(:location) { '/vendor/gitlab-ci-yml/non-existent-file.yml' }
+
+ it 'should return false' do
+ expect(local_file.valid?).to be_falsy
+ end
+ end
+
+ context 'when is not a yaml file' do
+ let(:location) { '/config/application.rb' }
+
+ it 'should return false' do
+ expect(local_file.valid?).to be_falsy
+ end
+ end
+ end
+
+ describe '#content' do
+ context 'with a a valid file' do
+ let(:local_file_content) do
+ <<~HEREDOC
+ before_script:
+ - apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs
+ - ruby -v
+ - which ruby
+ - gem install bundler --no-ri --no-rdoc
+ - bundle install --jobs $(nproc) "${FLAGS[@]}"
+ HEREDOC
+ end
+ let(:location) { '/vendor/gitlab-ci-yml/existent-file.yml' }
+
+ before do
+ allow_any_instance_of(described_class).to receive(:fetch_local_content).and_return(local_file_content)
+ end
+
+ it 'should return the content of the file' do
+ expect(local_file.content).to eq(local_file_content)
+ end
+ end
+
+ context 'with an invalid file' do
+ let(:location) { '/vendor/gitlab-ci-yml/non-existent-file.yml' }
+
+ it 'should be nil' do
+ expect(local_file.content).to be_nil
+ end
+ end
+ end
+
+ describe '#error_message' do
+ let(:location) { '/vendor/gitlab-ci-yml/non-existent-file.yml' }
+
+ it 'should return an error message' do
+ expect(local_file.error_message).to eq("Local file '#{location}' is not valid.")
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/external/file/remote_spec.rb b/spec/lib/gitlab/ci/external/file/remote_spec.rb
new file mode 100644
index 00000000000..b1819c8960b
--- /dev/null
+++ b/spec/lib/gitlab/ci/external/file/remote_spec.rb
@@ -0,0 +1,114 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::External::File::Remote do
+ let(:remote_file) { described_class.new(location) }
+ let(:location) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
+ let(:remote_file_content) do
+ <<~HEREDOC
+ before_script:
+ - apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs
+ - ruby -v
+ - which ruby
+ - gem install bundler --no-ri --no-rdoc
+ - bundle install --jobs $(nproc) "${FLAGS[@]}"
+ HEREDOC
+ end
+
+ describe "#valid?" do
+ context 'when is a valid remote url' do
+ before do
+ WebMock.stub_request(:get, location).to_return(body: remote_file_content)
+ end
+
+ it 'should return true' do
+ expect(remote_file.valid?).to be_truthy
+ end
+ end
+
+ context 'with an irregular url' do
+ let(:location) { 'not-valid://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
+
+ it 'should return false' do
+ expect(remote_file.valid?).to be_falsy
+ end
+ end
+
+ context 'with a timeout' do
+ before do
+ allow(Gitlab::HTTP).to receive(:get).and_raise(Timeout::Error)
+ end
+
+ it 'should be falsy' do
+ expect(remote_file.valid?).to be_falsy
+ end
+ end
+
+ context 'when is not a yaml file' do
+ let(:location) { 'https://asdasdasdaj48ggerexample.com' }
+
+ it 'should be falsy' do
+ expect(remote_file.valid?).to be_falsy
+ end
+ end
+
+ context 'with an internal url' do
+ let(:location) { 'http://localhost:8080' }
+
+ it 'should be falsy' do
+ expect(remote_file.valid?).to be_falsy
+ end
+ end
+ end
+
+ describe "#content" do
+ context 'with a valid remote file' do
+ before do
+ WebMock.stub_request(:get, location).to_return(body: remote_file_content)
+ end
+
+ it 'should return the content of the file' do
+ expect(remote_file.content).to eql(remote_file_content)
+ end
+ end
+
+ context 'with a timeout' do
+ before do
+ allow(Gitlab::HTTP).to receive(:get).and_raise(Timeout::Error)
+ end
+
+ it 'should be falsy' do
+ expect(remote_file.content).to be_falsy
+ end
+ end
+
+ context 'with an invalid remote url' do
+ let(:location) { 'https://asdasdasdaj48ggerexample.com' }
+
+ before do
+ WebMock.stub_request(:get, location).to_raise(SocketError.new('Some HTTP error'))
+ end
+
+ it 'should be nil' do
+ expect(remote_file.content).to be_nil
+ end
+ end
+
+ context 'with an internal url' do
+ let(:location) { 'http://localhost:8080' }
+
+ it 'should be nil' do
+ expect(remote_file.content).to be_nil
+ end
+ end
+ end
+
+ describe "#error_message" do
+ let(:location) { 'not-valid://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
+
+ it 'should return an error message' do
+ expect(remote_file.error_message).to eq("Remote file '#{location}' is not valid.")
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/external/mapper_spec.rb b/spec/lib/gitlab/ci/external/mapper_spec.rb
new file mode 100644
index 00000000000..6270d27a36d
--- /dev/null
+++ b/spec/lib/gitlab/ci/external/mapper_spec.rb
@@ -0,0 +1,96 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::External::Mapper do
+ let(:project) { create(:project, :repository) }
+ let(:file_content) do
+ <<~HEREDOC
+ image: 'ruby:2.2'
+ HEREDOC
+ end
+
+ describe '#process' do
+ subject { described_class.new(values, project, '123456').process }
+
+ context "when 'include' keyword is defined as string" do
+ context 'when the string is a local file' do
+ let(:values) do
+ {
+ include: '/vendor/gitlab-ci-yml/non-existent-file.yml',
+ image: 'ruby:2.2'
+ }
+ end
+
+ it 'returns an array' do
+ expect(subject).to be_an(Array)
+ end
+
+ it 'returns File instances' do
+ expect(subject.first).to be_an_instance_of(Gitlab::Ci::External::File::Local)
+ end
+ end
+
+ context 'when the string is a remote file' do
+ let(:remote_url) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
+ let(:values) do
+ {
+ include: remote_url,
+ image: 'ruby:2.2'
+ }
+ end
+
+ before do
+ WebMock.stub_request(:get, remote_url).to_return(body: file_content)
+ end
+
+ it 'returns an array' do
+ expect(subject).to be_an(Array)
+ end
+
+ it 'returns File instances' do
+ expect(subject.first).to be_an_instance_of(Gitlab::Ci::External::File::Remote)
+ end
+ end
+ end
+
+ context "when 'include' is defined as an array" do
+ let(:remote_url) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
+ let(:values) do
+ {
+ include:
+ [
+ remote_url,
+ '/vendor/gitlab-ci-yml/template.yml'
+ ],
+ image: 'ruby:2.2'
+ }
+ end
+
+ before do
+ WebMock.stub_request(:get, remote_url).to_return(body: file_content)
+ end
+
+ it 'returns an array' do
+ expect(subject).to be_an(Array)
+ end
+
+ it 'returns Files instances' do
+ expect(subject).to all(respond_to(:valid?))
+ expect(subject).to all(respond_to(:content))
+ end
+ end
+
+ context "when 'include' is not defined" do
+ let(:values) do
+ {
+ image: 'ruby:2.2'
+ }
+ end
+
+ it 'returns an empty array' do
+ expect(subject).to be_empty
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/external/processor_spec.rb b/spec/lib/gitlab/ci/external/processor_spec.rb
new file mode 100644
index 00000000000..688c2b3c8aa
--- /dev/null
+++ b/spec/lib/gitlab/ci/external/processor_spec.rb
@@ -0,0 +1,182 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::External::Processor do
+ let(:project) { create(:project, :repository) }
+ let(:processor) { described_class.new(values, project, '12345') }
+
+ describe "#perform" do
+ context 'when no external files defined' do
+ let(:values) { { image: 'ruby:2.2' } }
+
+ it 'should return the same values' do
+ expect(processor.perform).to eq(values)
+ end
+ end
+
+ context 'when an invalid local file is defined' do
+ let(:values) { { include: '/vendor/gitlab-ci-yml/non-existent-file.yml', image: 'ruby:2.2' } }
+
+ it 'should raise an error' do
+ expect { processor.perform }.to raise_error(
+ described_class::FileError,
+ "Local file '/vendor/gitlab-ci-yml/non-existent-file.yml' is not valid."
+ )
+ end
+ end
+
+ context 'when an invalid remote file is defined' do
+ let(:remote_file) { 'http://doesntexist.com/.gitlab-ci-1.yml' }
+ let(:values) { { include: remote_file, image: 'ruby:2.2' } }
+
+ before do
+ WebMock.stub_request(:get, remote_file).to_raise(SocketError.new('Some HTTP error'))
+ end
+
+ it 'should raise an error' do
+ expect { processor.perform }.to raise_error(
+ described_class::FileError,
+ "Remote file '#{remote_file}' is not valid."
+ )
+ end
+ end
+
+ context 'with a valid remote external file is defined' do
+ let(:remote_file) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
+ let(:values) { { include: remote_file, image: 'ruby:2.2' } }
+ let(:external_file_content) do
+ <<-HEREDOC
+ before_script:
+ - apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs
+ - ruby -v
+ - which ruby
+ - gem install bundler --no-ri --no-rdoc
+ - bundle install --jobs $(nproc) "${FLAGS[@]}"
+
+ rspec:
+ script:
+ - bundle exec rspec
+
+ rubocop:
+ script:
+ - bundle exec rubocop
+ HEREDOC
+ end
+
+ before do
+ WebMock.stub_request(:get, remote_file).to_return(body: external_file_content)
+ end
+
+ it 'should append the file to the values' do
+ output = processor.perform
+ expect(output.keys).to match_array([:image, :before_script, :rspec, :rubocop])
+ end
+
+ it "should remove the 'include' keyword" do
+ expect(processor.perform[:include]).to be_nil
+ end
+ end
+
+ context 'with a valid local external file is defined' do
+ let(:values) { { include: '/vendor/gitlab-ci-yml/template.yml', image: 'ruby:2.2' } }
+ let(:local_file_content) do
+ <<-HEREDOC
+ before_script:
+ - apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs
+ - ruby -v
+ - which ruby
+ - gem install bundler --no-ri --no-rdoc
+ - bundle install --jobs $(nproc) "${FLAGS[@]}"
+ HEREDOC
+ end
+
+ before do
+ allow_any_instance_of(Gitlab::Ci::External::File::Local).to receive(:fetch_local_content).and_return(local_file_content)
+ end
+
+ it 'should append the file to the values' do
+ output = processor.perform
+ expect(output.keys).to match_array([:image, :before_script])
+ end
+
+ it "should remove the 'include' keyword" do
+ expect(processor.perform[:include]).to be_nil
+ end
+ end
+
+ context 'with multiple external files are defined' do
+ let(:remote_file) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
+ let(:external_files) do
+ [
+ '/spec/fixtures/gitlab/ci/external_files/.gitlab-ci-template-1.yml',
+ remote_file
+ ]
+ end
+ let(:values) do
+ {
+ include: external_files,
+ image: 'ruby:2.2'
+ }
+ end
+
+ let(:remote_file_content) do
+ <<-HEREDOC
+ stages:
+ - build
+ - review
+ - cleanup
+ HEREDOC
+ end
+
+ before do
+ local_file_content = File.read(Rails.root.join('spec/fixtures/gitlab/ci/external_files/.gitlab-ci-template-1.yml'))
+ allow_any_instance_of(Gitlab::Ci::External::File::Local).to receive(:fetch_local_content).and_return(local_file_content)
+ WebMock.stub_request(:get, remote_file).to_return(body: remote_file_content)
+ end
+
+ it 'should append the files to the values' do
+ expect(processor.perform.keys).to match_array([:image, :stages, :before_script, :rspec])
+ end
+
+ it "should remove the 'include' keyword" do
+ expect(processor.perform[:include]).to be_nil
+ end
+ end
+
+ context 'when external files are defined but not valid' do
+ let(:values) { { include: '/vendor/gitlab-ci-yml/template.yml', image: 'ruby:2.2' } }
+
+ let(:local_file_content) { 'invalid content file ////' }
+
+ before do
+ allow_any_instance_of(Gitlab::Ci::External::File::Local).to receive(:fetch_local_content).and_return(local_file_content)
+ end
+
+ it 'should raise an error' do
+ expect { processor.perform }.to raise_error(Gitlab::Ci::Config::Loader::FormatError)
+ end
+ end
+
+ context "when both external files and values defined the same key" do
+ let(:remote_file) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
+ let(:values) do
+ {
+ include: remote_file,
+ image: 'ruby:2.2'
+ }
+ end
+
+ let(:remote_file_content) do
+ <<~HEREDOC
+ image: php:5-fpm-alpine
+ HEREDOC
+ end
+
+ it 'should take precedence' do
+ WebMock.stub_request(:get, remote_file).to_return(body: remote_file_content)
+ expect(processor.perform[:image]).to eq('ruby:2.2')
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/cleanup/project_uploads_spec.rb b/spec/lib/gitlab/cleanup/project_uploads_spec.rb
index 11e605eece6..bf130b8fabd 100644
--- a/spec/lib/gitlab/cleanup/project_uploads_spec.rb
+++ b/spec/lib/gitlab/cleanup/project_uploads_spec.rb
@@ -132,7 +132,6 @@ describe Gitlab::Cleanup::ProjectUploads do
let!(:path) { File.join(FileUploader.root, orphaned.model.full_path, orphaned.path) }
before do
- stub_feature_flags(import_export_object_storage: true)
stub_uploads_object_storage(FileUploader)
FileUtils.mkdir_p(File.dirname(path))
@@ -156,7 +155,6 @@ describe Gitlab::Cleanup::ProjectUploads do
let!(:new_path) { File.join(FileUploader.root, '-', 'project-lost-found', 'wrong', orphaned.path) }
before do
- stub_feature_flags(import_export_object_storage: true)
stub_uploads_object_storage(FileUploader)
FileUtils.mkdir_p(File.dirname(path))
diff --git a/spec/lib/gitlab/contributions_calendar_spec.rb b/spec/lib/gitlab/contributions_calendar_spec.rb
index 2c63f3b0455..6d29044ffd5 100644
--- a/spec/lib/gitlab/contributions_calendar_spec.rb
+++ b/spec/lib/gitlab/contributions_calendar_spec.rb
@@ -62,13 +62,16 @@ describe Gitlab::ContributionsCalendar do
expect(calendar.activity_dates).to eq(last_week => 2, today => 1)
end
- it "only shows private events to authorized users" do
- create_event(private_project, today)
- create_event(feature_project, today)
+ context "when the user has opted-in for private contributions" do
+ it "shows private and public events to all users" do
+ user.update_column(:include_private_contributions, true)
+ create_event(private_project, today)
+ create_event(public_project, today)
- expect(calendar.activity_dates[today]).to eq(0)
- expect(calendar(user).activity_dates[today]).to eq(0)
- expect(calendar(contributor).activity_dates[today]).to eq(2)
+ expect(calendar.activity_dates[today]).to eq(1)
+ expect(calendar(user).activity_dates[today]).to eq(1)
+ expect(calendar(contributor).activity_dates[today]).to eq(2)
+ end
end
it "counts the diff notes on merge request" do
@@ -128,7 +131,7 @@ describe Gitlab::ContributionsCalendar do
e3 = create_event(feature_project, today)
create_event(public_project, last_week)
- expect(calendar.events_by_date(today)).to contain_exactly(e1)
+ expect(calendar.events_by_date(today)).to contain_exactly(e1, e3)
expect(calendar(contributor).events_by_date(today)).to contain_exactly(e1, e2, e3)
end
diff --git a/spec/lib/gitlab/diff/highlight_cache_spec.rb b/spec/lib/gitlab/diff/highlight_cache_spec.rb
new file mode 100644
index 00000000000..bfcfed4231f
--- /dev/null
+++ b/spec/lib/gitlab/diff/highlight_cache_spec.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Diff::HighlightCache do
+ let(:merge_request) { create(:merge_request_with_diffs) }
+
+ subject(:cache) { described_class.new(merge_request.diffs, backend: backend) }
+
+ describe '#decorate' do
+ let(:backend) { double('backend').as_null_object }
+
+ # Manually creates a Diff::File object to avoid triggering the cache on
+ # the FileCollection::MergeRequestDiff
+ let(:diff_file) do
+ diffs = merge_request.diffs
+ raw_diff = diffs.diffable.raw_diffs(diffs.diff_options.merge(paths: ['CHANGELOG'])).first
+ Gitlab::Diff::File.new(raw_diff,
+ repository: diffs.project.repository,
+ diff_refs: diffs.diff_refs,
+ fallback_diff_refs: diffs.fallback_diff_refs)
+ end
+
+ it 'does not calculate highlighting when reading from cache' do
+ cache.write_if_empty
+ cache.decorate(diff_file)
+
+ expect_any_instance_of(Gitlab::Diff::Highlight).not_to receive(:highlight)
+
+ diff_file.highlighted_diff_lines
+ end
+
+ it 'assigns highlighted diff lines to the DiffFile' do
+ cache.write_if_empty
+ cache.decorate(diff_file)
+
+ expect(diff_file.highlighted_diff_lines.size).to be > 5
+ end
+
+ it 'submits a single reading from the cache' do
+ cache.decorate(diff_file)
+ cache.decorate(diff_file)
+
+ expect(backend).to have_received(:read).with(cache.key).once
+ end
+ end
+
+ describe '#write_if_empty' do
+ let(:backend) { double('backend', read: {}).as_null_object }
+
+ it 'submits a single writing to the cache' do
+ cache.write_if_empty
+ cache.write_if_empty
+
+ expect(backend).to have_received(:write).with(cache.key,
+ hash_including('CHANGELOG-false-false-false'),
+ expires_in: 1.week).once
+ end
+ end
+
+ describe '#clear' do
+ let(:backend) { double('backend').as_null_object }
+
+ it 'clears cache' do
+ cache.clear
+
+ expect(backend).to have_received(:delete).with(cache.key)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/git/committer_with_hooks_spec.rb b/spec/lib/gitlab/git/committer_with_hooks_spec.rb
deleted file mode 100644
index c7626058acd..00000000000
--- a/spec/lib/gitlab/git/committer_with_hooks_spec.rb
+++ /dev/null
@@ -1,156 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Git::CommitterWithHooks, :seed_helper do
- # TODO https://gitlab.com/gitlab-org/gitaly/issues/1234
- skip 'needs to be moved to gitaly-ruby test suite' do
- shared_examples 'calling wiki hooks' do
- let(:project) { create(:project) }
- let(:user) { project.owner }
- let(:project_wiki) { ProjectWiki.new(project, user) }
- let(:wiki) { project_wiki.wiki }
- let(:options) do
- {
- id: user.id,
- username: user.username,
- name: user.name,
- email: user.email,
- message: 'commit message'
- }
- end
-
- subject { described_class.new(wiki, options) }
-
- before do
- project_wiki.create_page('home', 'test content')
- end
-
- shared_examples 'failing pre-receive hook' do
- before do
- expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('pre-receive').and_return([false, ''])
- expect_any_instance_of(Gitlab::Git::HooksService).not_to receive(:run_hook).with('update')
- expect_any_instance_of(Gitlab::Git::HooksService).not_to receive(:run_hook).with('post-receive')
- end
-
- it 'raises exception' do
- expect { subject.commit }.to raise_error(Gitlab::Git::Wiki::OperationError)
- end
-
- it 'does not create a new commit inside the repository' do
- current_rev = find_current_rev
-
- expect { subject.commit }.to raise_error(Gitlab::Git::Wiki::OperationError)
-
- expect(current_rev).to eq find_current_rev
- end
- end
-
- shared_examples 'failing update hook' do
- before do
- expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('pre-receive').and_return([true, ''])
- expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('update').and_return([false, ''])
- expect_any_instance_of(Gitlab::Git::HooksService).not_to receive(:run_hook).with('post-receive')
- end
-
- it 'raises exception' do
- expect { subject.commit }.to raise_error(Gitlab::Git::Wiki::OperationError)
- end
-
- it 'does not create a new commit inside the repository' do
- current_rev = find_current_rev
-
- expect { subject.commit }.to raise_error(Gitlab::Git::Wiki::OperationError)
-
- expect(current_rev).to eq find_current_rev
- end
- end
-
- shared_examples 'failing post-receive hook' do
- before do
- expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('pre-receive').and_return([true, ''])
- expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('update').and_return([true, ''])
- expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('post-receive').and_return([false, ''])
- end
-
- it 'does not raise exception' do
- expect { subject.commit }.not_to raise_error
- end
-
- it 'creates the commit' do
- current_rev = find_current_rev
-
- subject.commit
-
- expect(current_rev).not_to eq find_current_rev
- end
- end
-
- shared_examples 'when hooks call succceeds' do
- let(:hook) { double(:hook) }
-
- it 'calls the three hooks' do
- expect(Gitlab::Git::Hook).to receive(:new).exactly(3).times.and_return(hook)
- expect(hook).to receive(:trigger).exactly(3).times.and_return([true, nil])
-
- subject.commit
- end
-
- it 'creates the commit' do
- current_rev = find_current_rev
-
- subject.commit
-
- expect(current_rev).not_to eq find_current_rev
- end
- end
-
- context 'when creating a page' do
- before do
- project_wiki.create_page('index', 'test content')
- end
-
- it_behaves_like 'failing pre-receive hook'
- it_behaves_like 'failing update hook'
- it_behaves_like 'failing post-receive hook'
- it_behaves_like 'when hooks call succceeds'
- end
-
- context 'when updating a page' do
- before do
- project_wiki.update_page(find_page('home'), content: 'some other content', format: :markdown)
- end
-
- it_behaves_like 'failing pre-receive hook'
- it_behaves_like 'failing update hook'
- it_behaves_like 'failing post-receive hook'
- it_behaves_like 'when hooks call succceeds'
- end
-
- context 'when deleting a page' do
- before do
- project_wiki.delete_page(find_page('home'))
- end
-
- it_behaves_like 'failing pre-receive hook'
- it_behaves_like 'failing update hook'
- it_behaves_like 'failing post-receive hook'
- it_behaves_like 'when hooks call succceeds'
- end
-
- def find_current_rev
- wiki.gollum_wiki.repo.commits.first&.sha
- end
-
- def find_page(name)
- wiki.page(title: name)
- end
- end
-
- context 'when Gitaly is enabled' do
- it_behaves_like 'calling wiki hooks'
- end
-
- context 'when Gitaly is disabled', :disable_gitaly do
- it_behaves_like 'calling wiki hooks'
- end
- end
-end
diff --git a/spec/lib/gitlab/git/diff_spec.rb b/spec/lib/gitlab/git/diff_spec.rb
index 87d9fcee39e..27d803e0117 100644
--- a/spec/lib/gitlab/git/diff_spec.rb
+++ b/spec/lib/gitlab/git/diff_spec.rb
@@ -2,12 +2,24 @@ require "spec_helper"
describe Gitlab::Git::Diff, :seed_helper do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') }
+ let(:gitaly_diff) do
+ Gitlab::GitalyClient::Diff.new(
+ from_path: '.gitmodules',
+ to_path: '.gitmodules',
+ old_mode: 0100644,
+ new_mode: 0100644,
+ from_id: '0792c58905eff3432b721f8c4a64363d8e28d9ae',
+ to_id: 'efd587ccb47caf5f31fc954edb21f0a713d9ecc3',
+ overflow_marker: false,
+ collapsed: false,
+ too_large: false,
+ patch: "@@ -4,3 +4,6 @@\n [submodule \"gitlab-shell\"]\n \tpath = gitlab-shell\n \turl = https://github.com/gitlabhq/gitlab-shell.git\n+[submodule \"gitlab-grack\"]\n+\tpath = gitlab-grack\n+\turl = https://gitlab.com/gitlab-org/gitlab-grack.git\n"
+ )
+ end
before do
@raw_diff_hash = {
diff: <<EOT.gsub(/^ {8}/, "").sub(/\n$/, ""),
- --- a/.gitmodules
- +++ b/.gitmodules
@@ -4,3 +4,6 @@
[submodule "gitlab-shell"]
\tpath = gitlab-shell
@@ -26,12 +38,6 @@ EOT
deleted_file: false,
too_large: false
}
-
- # TODO use a Gitaly diff object instead
- @rugged_diff = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- repository.rugged.diff("5937ac0a7beb003549fc5fd26fc247adbce4a52e^", "5937ac0a7beb003549fc5fd26fc247adbce4a52e", paths:
- [".gitmodules"]).patches.first
- end
end
describe '.new' do
@@ -60,7 +66,7 @@ EOT
context 'using a Rugged::Patch' do
context 'with a small diff' do
- let(:diff) { described_class.new(@rugged_diff) }
+ let(:diff) { described_class.new(gitaly_diff) }
it 'initializes the diff' do
expect(diff.to_hash).to eq(@raw_diff_hash)
@@ -73,10 +79,8 @@ EOT
context 'using a diff that is too large' do
it 'prunes the diff' do
- expect_any_instance_of(String).to receive(:bytesize)
- .and_return(1024 * 1024 * 1024)
-
- diff = described_class.new(@rugged_diff)
+ gitaly_diff.too_large = true
+ diff = described_class.new(gitaly_diff)
expect(diff.diff).to be_empty
expect(diff).to be_too_large
@@ -84,33 +88,15 @@ EOT
end
context 'using a collapsable diff that is too large' do
- before do
- # The patch total size is 200, with lines between 21 and 54.
- # This is a quick-and-dirty way to test this. Ideally, a new patch is
- # added to the test repo with a size that falls between the real limits.
- stub_const("#{described_class}::SIZE_LIMIT", 150)
- stub_const("#{described_class}::COLLAPSE_LIMIT", 100)
- end
-
it 'prunes the diff as a large diff instead of as a collapsed diff' do
- diff = described_class.new(@rugged_diff, expanded: false)
+ gitaly_diff.too_large = true
+ diff = described_class.new(gitaly_diff, expanded: false)
expect(diff.diff).to be_empty
expect(diff).to be_too_large
expect(diff).not_to be_collapsed
end
end
-
- context 'using a large binary diff' do
- it 'does not prune the diff' do
- expect_any_instance_of(Rugged::Diff::Delta).to receive(:binary?)
- .and_return(true)
-
- diff = described_class.new(@rugged_diff)
-
- expect(diff.diff).not_to be_empty
- end
- end
end
context 'using a GitalyClient::Diff' do
@@ -259,31 +245,37 @@ EOT
end
it 'leave non-binary diffs as-is' do
- diff = described_class.new(@rugged_diff)
+ diff = described_class.new(gitaly_diff)
expect(diff.json_safe_diff).to eq(diff.diff)
end
end
describe '#submodule?' do
- before do
- # TODO use a Gitaly diff object instead
- rugged_commit = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- repository.rugged.rev_parse('5937ac0a7beb003549fc5fd26fc247adbce4a52e')
- end
-
- @diffs = rugged_commit.parents[0].diff(rugged_commit).patches
+ let(:gitaly_submodule_diff) do
+ Gitlab::GitalyClient::Diff.new(
+ from_path: 'gitlab-grack',
+ to_path: 'gitlab-grack',
+ old_mode: 0,
+ new_mode: 57344,
+ from_id: '0000000000000000000000000000000000000000',
+ to_id: '645f6c4c82fd3f5e06f67134450a570b795e55a6',
+ overflow_marker: false,
+ collapsed: false,
+ too_large: false,
+ patch: "@@ -0,0 +1 @@\n+Subproject commit 645f6c4c82fd3f5e06f67134450a570b795e55a6\n"
+ )
end
- it { expect(described_class.new(@diffs[0]).submodule?).to eq(false) }
- it { expect(described_class.new(@diffs[1]).submodule?).to eq(true) }
+ it { expect(described_class.new(gitaly_diff).submodule?).to eq(false) }
+ it { expect(described_class.new(gitaly_submodule_diff).submodule?).to eq(true) }
end
describe '#line_count' do
it 'returns the correct number of lines' do
- diff = described_class.new(@rugged_diff)
+ diff = described_class.new(gitaly_diff)
- expect(diff.line_count).to eq(9)
+ expect(diff.line_count).to eq(7)
end
end
diff --git a/spec/lib/gitlab/git/gitlab_projects_spec.rb b/spec/lib/gitlab/git/gitlab_projects_spec.rb
deleted file mode 100644
index f5d8503c30c..00000000000
--- a/spec/lib/gitlab/git/gitlab_projects_spec.rb
+++ /dev/null
@@ -1,321 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Git::GitlabProjects do
- after do
- TestEnv.clean_test_path
- end
-
- around do |example|
- # TODO move this spec to gitaly-ruby. GitlabProjects is not used in gitlab-ce
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- example.run
- end
- end
-
- let(:project) { create(:project, :repository) }
-
- if $VERBOSE
- let(:logger) { Logger.new(STDOUT) }
- else
- let(:logger) { double('logger').as_null_object }
- end
-
- let(:tmp_repos_path) { TestEnv.repos_path }
- let(:repo_name) { project.disk_path + '.git' }
- let(:tmp_repo_path) { File.join(tmp_repos_path, repo_name) }
- let(:gl_projects) { build_gitlab_projects(TestEnv::REPOS_STORAGE, repo_name) }
-
- describe '#initialize' do
- it { expect(gl_projects.shard_path).to eq(tmp_repos_path) }
- it { expect(gl_projects.repository_relative_path).to eq(repo_name) }
- it { expect(gl_projects.repository_absolute_path).to eq(File.join(tmp_repos_path, repo_name)) }
- it { expect(gl_projects.logger).to eq(logger) }
- end
-
- describe '#push_branches' do
- let(:remote_name) { 'remote-name' }
- let(:branch_name) { 'master' }
- let(:cmd) { %W(#{Gitlab.config.git.bin_path} push -- #{remote_name} #{branch_name}) }
- let(:force) { false }
-
- subject { gl_projects.push_branches(remote_name, 600, force, [branch_name]) }
-
- it 'executes the command' do
- stub_spawn(cmd, 600, tmp_repo_path, success: true)
-
- is_expected.to be_truthy
- end
-
- it 'fails' do
- stub_spawn(cmd, 600, tmp_repo_path, success: false)
-
- is_expected.to be_falsy
- end
-
- context 'with --force' do
- let(:cmd) { %W(#{Gitlab.config.git.bin_path} push --force -- #{remote_name} #{branch_name}) }
- let(:force) { true }
-
- it 'executes the command' do
- stub_spawn(cmd, 600, tmp_repo_path, success: true)
-
- is_expected.to be_truthy
- end
- end
- end
-
- describe '#fetch_remote' do
- let(:remote_name) { 'remote-name' }
- let(:branch_name) { 'master' }
- let(:force) { false }
- let(:prune) { true }
- let(:tags) { true }
- let(:args) { { force: force, tags: tags, prune: prune }.merge(extra_args) }
- let(:extra_args) { {} }
- let(:cmd) { %W(#{Gitlab.config.git.bin_path} fetch #{remote_name} --quiet --prune --tags) }
-
- subject { gl_projects.fetch_remote(remote_name, 600, args) }
-
- def stub_tempfile(name, filename, opts = {})
- chmod = opts.delete(:chmod)
- file = StringIO.new
-
- allow(file).to receive(:close!)
- allow(file).to receive(:path).and_return(name)
-
- expect(Tempfile).to receive(:new).with(filename).and_return(file)
- expect(file).to receive(:chmod).with(chmod) if chmod
-
- file
- end
-
- context 'with default args' do
- it 'executes the command' do
- stub_spawn(cmd, 600, tmp_repo_path, {}, success: true)
-
- is_expected.to be_truthy
- end
-
- it 'fails' do
- stub_spawn(cmd, 600, tmp_repo_path, {}, success: false)
-
- is_expected.to be_falsy
- end
- end
-
- context 'with --force' do
- let(:force) { true }
- let(:cmd) { %W(#{Gitlab.config.git.bin_path} fetch #{remote_name} --quiet --prune --force --tags) }
-
- it 'executes the command with forced option' do
- stub_spawn(cmd, 600, tmp_repo_path, {}, success: true)
-
- is_expected.to be_truthy
- end
- end
-
- context 'with --no-tags' do
- let(:tags) { false }
- let(:cmd) { %W(#{Gitlab.config.git.bin_path} fetch #{remote_name} --quiet --prune --no-tags) }
-
- it 'executes the command' do
- stub_spawn(cmd, 600, tmp_repo_path, {}, success: true)
-
- is_expected.to be_truthy
- end
- end
-
- context 'with no prune' do
- let(:prune) { false }
- let(:cmd) { %W(#{Gitlab.config.git.bin_path} fetch #{remote_name} --quiet --tags) }
-
- it 'executes the command' do
- stub_spawn(cmd, 600, tmp_repo_path, {}, success: true)
-
- is_expected.to be_truthy
- end
- end
-
- describe 'with an SSH key' do
- let(:extra_args) { { ssh_key: 'SSH KEY' } }
-
- it 'sets GIT_SSH to a custom script' do
- script = stub_tempfile('scriptFile', 'gitlab-shell-ssh-wrapper', chmod: 0o755)
- key = stub_tempfile('/tmp files/keyFile', 'gitlab-shell-key-file', chmod: 0o400)
-
- stub_spawn(cmd, 600, tmp_repo_path, { 'GIT_SSH' => 'scriptFile' }, success: true)
-
- is_expected.to be_truthy
-
- expect(script.string).to eq("#!/bin/sh\nexec ssh '-oIdentityFile=\"/tmp files/keyFile\"' '-oIdentitiesOnly=\"yes\"' \"$@\"")
- expect(key.string).to eq('SSH KEY')
- end
- end
-
- describe 'with known_hosts data' do
- let(:extra_args) { { known_hosts: 'KNOWN HOSTS' } }
-
- it 'sets GIT_SSH to a custom script' do
- script = stub_tempfile('scriptFile', 'gitlab-shell-ssh-wrapper', chmod: 0o755)
- key = stub_tempfile('/tmp files/knownHosts', 'gitlab-shell-known-hosts', chmod: 0o400)
-
- stub_spawn(cmd, 600, tmp_repo_path, { 'GIT_SSH' => 'scriptFile' }, success: true)
-
- is_expected.to be_truthy
-
- expect(script.string).to eq("#!/bin/sh\nexec ssh '-oStrictHostKeyChecking=\"yes\"' '-oUserKnownHostsFile=\"/tmp files/knownHosts\"' \"$@\"")
- expect(key.string).to eq('KNOWN HOSTS')
- end
- end
- end
-
- describe '#import_project' do
- let(:project) { create(:project) }
- let(:import_url) { TestEnv.factory_repo_path_bare }
- let(:cmd) { %W(#{Gitlab.config.git.bin_path} clone --bare -- #{import_url} #{tmp_repo_path}) }
- let(:timeout) { 600 }
-
- subject { gl_projects.import_project(import_url, timeout) }
-
- shared_examples 'importing repository' do
- context 'success import' do
- it 'imports a repo' do
- expect(File.exist?(File.join(tmp_repo_path, 'HEAD'))).to be_falsy
-
- is_expected.to be_truthy
-
- expect(File.exist?(File.join(tmp_repo_path, 'HEAD'))).to be_truthy
- end
- end
-
- context 'already exists' do
- it "doesn't import" do
- FileUtils.mkdir_p(tmp_repo_path)
-
- is_expected.to be_falsy
- end
- end
- end
-
- describe 'logging' do
- it 'imports a repo' do
- message = "Importing project from <#{import_url}> to <#{tmp_repo_path}>."
- expect(logger).to receive(:info).with(message)
-
- subject
- end
- end
-
- context 'timeout' do
- it 'does not import a repo' do
- stub_spawn_timeout(cmd, timeout, nil)
-
- message = "Importing project from <#{import_url}> to <#{tmp_repo_path}> failed."
- expect(logger).to receive(:error).with(message)
-
- is_expected.to be_falsy
-
- expect(gl_projects.output).to eq("Timed out\n")
- expect(File.exist?(File.join(tmp_repo_path, 'HEAD'))).to be_falsy
- end
- end
-
- it_behaves_like 'importing repository'
- end
-
- describe '#fork_repository' do
- let(:dest_repos) { TestEnv::REPOS_STORAGE }
- let(:dest_repos_path) { tmp_repos_path }
- let(:dest_repo_name) { File.join('@hashed', 'aa', 'bb', 'xyz.git') }
- let(:dest_repo) { File.join(dest_repos_path, dest_repo_name) }
-
- subject { gl_projects.fork_repository(dest_repos, dest_repo_name) }
-
- before do
- FileUtils.mkdir_p(dest_repos_path)
- end
-
- after do
- FileUtils.rm_rf(dest_repos_path)
- end
-
- shared_examples 'forking a repository' do
- it 'forks the repository' do
- is_expected.to be_truthy
-
- expect(File.exist?(dest_repo)).to be_truthy
- expect(File.exist?(File.join(dest_repo, 'hooks', 'pre-receive'))).to be_truthy
- expect(File.exist?(File.join(dest_repo, 'hooks', 'post-receive'))).to be_truthy
- end
-
- it 'does not fork if a project of the same name already exists' do
- # create a fake project at the intended destination
- FileUtils.mkdir_p(dest_repo)
-
- is_expected.to be_falsy
- end
- end
-
- it_behaves_like 'forking a repository'
-
- # We seem to be stuck to having only one working Gitaly storage in tests, changing
- # that is not very straight-forward so I'm leaving this test here for now till
- # https://gitlab.com/gitlab-org/gitlab-ce/issues/41393 is fixed.
- context 'different storages' do
- let(:dest_repos) { 'alternative' }
- let(:dest_repos_path) { File.join(File.dirname(tmp_repos_path), dest_repos) }
-
- before do
- stub_storage_settings(dest_repos => { 'path' => dest_repos_path })
- end
-
- it 'forks the repo' do
- is_expected.to be_truthy
-
- expect(File.exist?(dest_repo)).to be_truthy
- expect(File.exist?(File.join(dest_repo, 'hooks', 'pre-receive'))).to be_truthy
- expect(File.exist?(File.join(dest_repo, 'hooks', 'post-receive'))).to be_truthy
- end
- end
-
- describe 'log messages' do
- describe 'successful fork' do
- it do
- message = "Forking repository from <#{tmp_repo_path}> to <#{dest_repo}>."
- expect(logger).to receive(:info).with(message)
-
- subject
- end
- end
-
- describe 'failed fork due existing destination' do
- it do
- FileUtils.mkdir_p(dest_repo)
- message = "fork-repository failed: destination repository <#{dest_repo}> already exists."
- expect(logger).to receive(:error).with(message)
-
- subject
- end
- end
- end
- end
-
- def build_gitlab_projects(*args)
- described_class.new(
- *args,
- global_hooks_path: Gitlab.config.gitlab_shell.hooks_path,
- logger: logger
- )
- end
-
- def stub_spawn(*args, success: true)
- exitstatus = success ? 0 : nil
- expect(gl_projects).to receive(:popen_with_timeout).with(*args)
- .and_return(["output", exitstatus])
- end
-
- def stub_spawn_timeout(*args)
- expect(gl_projects).to receive(:popen_with_timeout).with(*args)
- .and_raise(Timeout::Error)
- end
-end
diff --git a/spec/lib/gitlab/git/hook_spec.rb b/spec/lib/gitlab/git/hook_spec.rb
deleted file mode 100644
index a45c8510b15..00000000000
--- a/spec/lib/gitlab/git/hook_spec.rb
+++ /dev/null
@@ -1,111 +0,0 @@
-require 'spec_helper'
-require 'fileutils'
-
-describe Gitlab::Git::Hook do
- before do
- # We need this because in the spec/spec_helper.rb we define it like this:
- # allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, nil])
- allow_any_instance_of(described_class).to receive(:trigger).and_call_original
- end
-
- around do |example|
- # TODO move hook tests to gitaly-ruby. Hook will disappear from gitlab-ce
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- example.run
- end
- end
-
- describe "#trigger" do
- set(:project) { create(:project, :repository) }
- let(:repository) { project.repository.raw_repository }
- let(:repo_path) { repository.path }
- let(:hooks_dir) { File.join(repo_path, 'hooks') }
- let(:user) { create(:user) }
- let(:gl_id) { Gitlab::GlId.gl_id(user) }
- let(:gl_username) { user.username }
-
- def create_hook(name)
- FileUtils.mkdir_p(hooks_dir)
- hook_path = File.join(hooks_dir, name)
- File.open(hook_path, 'w', 0755) do |f|
- f.write(<<~HOOK)
- #!/bin/sh
- exit 0
- HOOK
- end
- end
-
- def create_failing_hook(name)
- FileUtils.mkdir_p(hooks_dir)
- hook_path = File.join(hooks_dir, name)
- File.open(hook_path, 'w', 0755) do |f|
- f.write(<<~HOOK)
- #!/bin/sh
- echo 'regular message from the hook'
- echo 'error message from the hook' 1>&2
- echo 'error message from the hook line 2' 1>&2
- exit 1
- HOOK
- end
- end
-
- ['pre-receive', 'post-receive', 'update'].each do |hook_name|
- context "when triggering a #{hook_name} hook" do
- context "when the hook is successful" do
- let(:hook_path) { File.join(hooks_dir, hook_name) }
- let(:gl_repository) { Gitlab::GlRepository.gl_repository(project, false) }
- let(:env) do
- {
- 'GL_ID' => gl_id,
- 'GL_USERNAME' => gl_username,
- 'PWD' => repo_path,
- 'GL_PROTOCOL' => 'web',
- 'GL_REPOSITORY' => gl_repository
- }
- end
-
- it "returns success with no errors" do
- create_hook(hook_name)
- hook = described_class.new(hook_name, repository)
- blank = Gitlab::Git::BLANK_SHA
- ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch'
-
- if hook_name != 'update'
- expect(Open3).to receive(:popen3)
- .with(env, hook_path, chdir: repo_path).and_call_original
- end
-
- status, errors = hook.trigger(gl_id, gl_username, blank, blank, ref)
- expect(status).to be true
- expect(errors).to be_blank
- end
- end
-
- context "when the hook is unsuccessful" do
- it "returns failure with errors" do
- create_failing_hook(hook_name)
- hook = described_class.new(hook_name, repository)
- blank = Gitlab::Git::BLANK_SHA
- ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch'
-
- status, errors = hook.trigger(gl_id, gl_username, blank, blank, ref)
- expect(status).to be false
- expect(errors).to eq("error message from the hook\nerror message from the hook line 2\n")
- end
- end
- end
- end
-
- context "when the hook doesn't exist" do
- it "returns success with no errors" do
- hook = described_class.new('unknown_hook', repository)
- blank = Gitlab::Git::BLANK_SHA
- ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch'
-
- status, errors = hook.trigger(gl_id, gl_username, blank, blank, ref)
- expect(status).to be true
- expect(errors).to be_nil
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/git/hooks_service_spec.rb b/spec/lib/gitlab/git/hooks_service_spec.rb
deleted file mode 100644
index 55ffced36ac..00000000000
--- a/spec/lib/gitlab/git/hooks_service_spec.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Git::HooksService, :seed_helper do
- let(:gl_id) { 'user-456' }
- let(:gl_username) { 'janedoe' }
- let(:user) { Gitlab::Git::User.new(gl_username, 'Jane Doe', 'janedoe@example.com', gl_id) }
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, 'project-123') }
- let(:service) { described_class.new }
- let(:blankrev) { Gitlab::Git::BLANK_SHA }
- let(:oldrev) { SeedRepo::Commit::PARENT_ID }
- let(:newrev) { SeedRepo::Commit::ID }
- let(:ref) { 'refs/heads/feature' }
-
- describe '#execute' do
- context 'when receive hooks were successful' do
- let(:hook) { double(:hook) }
-
- it 'calls all three hooks' do
- expect(Gitlab::Git::Hook).to receive(:new).exactly(3).times.and_return(hook)
- expect(hook).to receive(:trigger).with(gl_id, gl_username, blankrev, newrev, ref)
- .exactly(3).times.and_return([true, nil])
-
- service.execute(user, repository, blankrev, newrev, ref) { }
- end
- end
-
- context 'when pre-receive hook failed' do
- it 'does not call post-receive hook' do
- expect(service).to receive(:run_hook).with('pre-receive').and_return([false, 'hello world'])
- expect(service).not_to receive(:run_hook).with('post-receive')
-
- expect do
- service.execute(user, repository, blankrev, newrev, ref)
- end.to raise_error(Gitlab::Git::PreReceiveError, 'hello world')
- end
- end
-
- context 'when update hook failed' do
- it 'does not call post-receive hook' do
- expect(service).to receive(:run_hook).with('pre-receive').and_return([true, nil])
- expect(service).to receive(:run_hook).with('update').and_return([false, 'hello world'])
- expect(service).not_to receive(:run_hook).with('post-receive')
-
- expect do
- service.execute(user, repository, blankrev, newrev, ref)
- end.to raise_error(Gitlab::Git::PreReceiveError, 'hello world')
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/git/index_spec.rb b/spec/lib/gitlab/git/index_spec.rb
deleted file mode 100644
index c4edd6961e1..00000000000
--- a/spec/lib/gitlab/git/index_spec.rb
+++ /dev/null
@@ -1,239 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Git::Index, :seed_helper do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') }
- let(:index) { described_class.new(repository) }
-
- before do
- index.read_tree(lookup('master').tree)
- end
-
- around do |example|
- # TODO move these specs to gitaly-ruby. The Index class will disappear from gitlab-ce
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- example.run
- end
- end
-
- describe '#create' do
- let(:options) do
- {
- content: 'Lorem ipsum...',
- file_path: 'documents/story.txt'
- }
- end
-
- context 'when no file at that path exists' do
- it 'creates the file in the index' do
- index.create(options)
-
- entry = index.get(options[:file_path])
-
- expect(entry).not_to be_nil
- expect(lookup(entry[:oid]).content).to eq(options[:content])
- end
- end
-
- context 'when a file at that path exists' do
- before do
- options[:file_path] = 'files/executables/ls'
- end
-
- it 'raises an error' do
- expect { index.create(options) }.to raise_error('A file with this name already exists')
- end
- end
-
- context 'when content is in base64' do
- before do
- options[:content] = Base64.encode64(options[:content])
- options[:encoding] = 'base64'
- end
-
- it 'decodes base64' do
- index.create(options)
-
- entry = index.get(options[:file_path])
- expect(lookup(entry[:oid]).content).to eq(Base64.decode64(options[:content]))
- end
- end
-
- context 'when content contains CRLF' do
- before do
- repository.autocrlf = :input
- options[:content] = "Hello,\r\nWorld"
- end
-
- it 'converts to LF' do
- index.create(options)
-
- entry = index.get(options[:file_path])
- expect(lookup(entry[:oid]).content).to eq("Hello,\nWorld")
- end
- end
- end
-
- describe '#create_dir' do
- let(:options) do
- {
- file_path: 'newdir'
- }
- end
-
- context 'when no file or dir at that path exists' do
- it 'creates the dir in the index' do
- index.create_dir(options)
-
- entry = index.get(options[:file_path] + '/.gitkeep')
-
- expect(entry).not_to be_nil
- end
- end
-
- context 'when a file at that path exists' do
- before do
- options[:file_path] = 'files/executables/ls'
- end
-
- it 'raises an error' do
- expect { index.create_dir(options) }.to raise_error('A file with this name already exists')
- end
- end
-
- context 'when a directory at that path exists' do
- before do
- options[:file_path] = 'files/executables'
- end
-
- it 'raises an error' do
- expect { index.create_dir(options) }.to raise_error('A directory with this name already exists')
- end
- end
- end
-
- describe '#update' do
- let(:options) do
- {
- content: 'Lorem ipsum...',
- file_path: 'README.md'
- }
- end
-
- context 'when no file at that path exists' do
- before do
- options[:file_path] = 'documents/story.txt'
- end
-
- it 'raises an error' do
- expect { index.update(options) }.to raise_error("A file with this name doesn't exist")
- end
- end
-
- context 'when a file at that path exists' do
- it 'updates the file in the index' do
- index.update(options)
-
- entry = index.get(options[:file_path])
-
- expect(lookup(entry[:oid]).content).to eq(options[:content])
- end
-
- it 'preserves file mode' do
- options[:file_path] = 'files/executables/ls'
-
- index.update(options)
-
- entry = index.get(options[:file_path])
-
- expect(entry[:mode]).to eq(0100755)
- end
- end
- end
-
- describe '#move' do
- let(:options) do
- {
- content: 'Lorem ipsum...',
- previous_path: 'README.md',
- file_path: 'NEWREADME.md'
- }
- end
-
- context 'when no file at that path exists' do
- it 'raises an error' do
- options[:previous_path] = 'documents/story.txt'
-
- expect { index.move(options) }.to raise_error("A file with this name doesn't exist")
- end
- end
-
- context 'when a file at the new path already exists' do
- it 'raises an error' do
- options[:file_path] = 'CHANGELOG'
-
- expect { index.move(options) }.to raise_error("A file with this name already exists")
- end
- end
-
- context 'when a file at that path exists' do
- it 'removes the old file in the index' do
- index.move(options)
-
- entry = index.get(options[:previous_path])
-
- expect(entry).to be_nil
- end
-
- it 'creates the new file in the index' do
- index.move(options)
-
- entry = index.get(options[:file_path])
-
- expect(entry).not_to be_nil
- expect(lookup(entry[:oid]).content).to eq(options[:content])
- end
-
- it 'preserves file mode' do
- options[:previous_path] = 'files/executables/ls'
-
- index.move(options)
-
- entry = index.get(options[:file_path])
-
- expect(entry[:mode]).to eq(0100755)
- end
- end
- end
-
- describe '#delete' do
- let(:options) do
- {
- file_path: 'README.md'
- }
- end
-
- context 'when no file at that path exists' do
- before do
- options[:file_path] = 'documents/story.txt'
- end
-
- it 'raises an error' do
- expect { index.delete(options) }.to raise_error("A file with this name doesn't exist")
- end
- end
-
- context 'when a file at that path exists' do
- it 'removes the file in the index' do
- index.delete(options)
-
- entry = index.get(options[:file_path])
-
- expect(entry).to be_nil
- end
- end
- end
-
- def lookup(revision)
- repository.rugged.rev_parse(revision)
- end
-end
diff --git a/spec/lib/gitlab/git/popen_spec.rb b/spec/lib/gitlab/git/popen_spec.rb
deleted file mode 100644
index 074e66d2a5d..00000000000
--- a/spec/lib/gitlab/git/popen_spec.rb
+++ /dev/null
@@ -1,179 +0,0 @@
-require 'spec_helper'
-
-describe 'Gitlab::Git::Popen' do
- let(:path) { Rails.root.join('tmp').to_s }
- let(:test_string) { 'The quick brown fox jumped over the lazy dog' }
- # The pipe buffer is typically 64K. This string is about 440K.
- let(:spew_command) { ['bash', '-c', "for i in {1..10000}; do echo '#{test_string}' 1>&2; done"] }
-
- let(:klass) do
- Class.new(Object) do
- include Gitlab::Git::Popen
- end
- end
-
- context 'popen' do
- context 'zero status' do
- let(:result) { klass.new.popen(%w(ls), path) }
- let(:output) { result.first }
- let(:status) { result.last }
-
- it { expect(status).to be_zero }
- it { expect(output).to include('tests') }
- end
-
- context 'non-zero status' do
- let(:result) { klass.new.popen(%w(cat NOTHING), path) }
- let(:output) { result.first }
- let(:status) { result.last }
-
- it { expect(status).to eq(1) }
- it { expect(output).to include('No such file or directory') }
- end
-
- context 'unsafe string command' do
- it 'raises an error when it gets called with a string argument' do
- expect { klass.new.popen('ls', path) }.to raise_error(RuntimeError)
- end
- end
-
- context 'with custom options' do
- let(:vars) { { 'foobar' => 123, 'PWD' => path } }
- let(:options) { { chdir: path } }
-
- it 'calls popen3 with the provided environment variables' do
- expect(Open3).to receive(:popen3).with(vars, 'ls', options)
-
- klass.new.popen(%w(ls), path, { 'foobar' => 123 })
- end
- end
-
- context 'use stdin' do
- let(:result) { klass.new.popen(%w[cat], path) { |stdin| stdin.write 'hello' } }
- let(:output) { result.first }
- let(:status) { result.last }
-
- it { expect(status).to be_zero }
- it { expect(output).to eq('hello') }
- end
-
- context 'with lazy block' do
- it 'yields a lazy io' do
- expect_lazy_io = lambda do |io|
- expect(io).to be_a Enumerator::Lazy
- expect(io.inspect).to include('#<IO:fd')
- end
-
- klass.new.popen(%w[ls], path, lazy_block: expect_lazy_io)
- end
-
- it "doesn't wait for process exit" do
- Timeout.timeout(2) do
- klass.new.popen(%w[yes], path, lazy_block: ->(io) {})
- end
- end
- end
-
- context 'with a process that writes a lot of data to stderr' do
- it 'returns zero' do
- output, status = klass.new.popen(spew_command, path)
-
- expect(output).to include(test_string)
- expect(status).to eq(0)
- end
- end
- end
-
- context 'popen_with_timeout' do
- let(:timeout) { 1.second }
-
- context 'no timeout' do
- context 'zero status' do
- let(:result) { klass.new.popen_with_timeout(%w(ls), timeout, path) }
- let(:output) { result.first }
- let(:status) { result.last }
-
- it { expect(status).to be_zero }
- it { expect(output).to include('tests') }
- end
-
- context 'multi-line string' do
- let(:test_string) { "this is 1 line\n2nd line\n3rd line\n" }
- let(:result) { klass.new.popen_with_timeout(['echo', test_string], timeout, path) }
- let(:output) { result.first }
- let(:status) { result.last }
-
- it { expect(status).to be_zero }
- # echo adds its own line
- it { expect(output).to eq(test_string + "\n") }
- end
-
- context 'non-zero status' do
- let(:result) { klass.new.popen_with_timeout(%w(cat NOTHING), timeout, path) }
- let(:output) { result.first }
- let(:status) { result.last }
-
- it { expect(status).to eq(1) }
- it { expect(output).to include('No such file or directory') }
- end
-
- context 'unsafe string command' do
- it 'raises an error when it gets called with a string argument' do
- expect { klass.new.popen_with_timeout('ls', timeout, path) }.to raise_error(RuntimeError)
- end
- end
- end
-
- context 'timeout' do
- context 'timeout' do
- it "raises a Timeout::Error" do
- expect { klass.new.popen_with_timeout(%w(sleep 1000), timeout, path) }.to raise_error(Timeout::Error)
- end
-
- it "handles processes that do not shutdown correctly" do
- expect { klass.new.popen_with_timeout(['bash', '-c', "trap -- '' SIGTERM; sleep 1000"], timeout, path) }.to raise_error(Timeout::Error)
- end
-
- it 'handles process that writes a lot of data to stderr' do
- output, status = klass.new.popen_with_timeout(spew_command, timeout, path)
-
- expect(output).to include(test_string)
- expect(status).to eq(0)
- end
- end
-
- context 'timeout period' do
- let(:time_taken) do
- begin
- start = Time.now
- klass.new.popen_with_timeout(%w(sleep 1000), timeout, path)
- rescue
- Time.now - start
- end
- end
-
- it { expect(time_taken).to be >= timeout }
- end
-
- context 'clean up' do
- let(:instance) { klass.new }
-
- it 'kills the child process' do
- expect(instance).to receive(:kill_process_group_for_pid).and_wrap_original do |m, *args|
- # is the PID, and it should not be running at this point
- m.call(*args)
-
- pid = args.first
- begin
- Process.getpgid(pid)
- raise "The child process should have been killed"
- rescue Errno::ESRCH
- end
- end
-
- expect { instance.popen_with_timeout(['bash', '-c', "trap -- '' SIGTERM; sleep 1000"], timeout, path) }.to raise_error(Timeout::Error)
- end
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 17348b01006..dfe36d7d459 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -322,21 +322,12 @@ describe Gitlab::Git::Repository, :seed_helper do
end
describe '#commit_count' do
- shared_examples 'simple commit counting' do
- it { expect(repository.commit_count("master")).to eq(25) }
- it { expect(repository.commit_count("feature")).to eq(9) }
- it { expect(repository.commit_count("does-not-exist")).to eq(0) }
- end
-
- context 'when Gitaly commit_count feature is enabled' do
- it_behaves_like 'simple commit counting'
- it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::CommitService, :commit_count do
- subject { repository.commit_count('master') }
- end
- end
+ it { expect(repository.commit_count("master")).to eq(25) }
+ it { expect(repository.commit_count("feature")).to eq(9) }
+ it { expect(repository.commit_count("does-not-exist")).to eq(0) }
- context 'when Gitaly commit_count feature is disabled', :skip_gitaly_mock do
- it_behaves_like 'simple commit counting'
+ it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::CommitService, :commit_count do
+ subject { repository.commit_count('master') }
end
end
@@ -378,118 +369,88 @@ describe Gitlab::Git::Repository, :seed_helper do
end
describe "#delete_branch" do
- shared_examples "deleting a branch" do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') }
-
- after do
- ensure_seeds
- end
+ let(:repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') }
- it "removes the branch from the repo" do
- branch_name = "to-be-deleted-soon"
+ after do
+ ensure_seeds
+ end
- repository.create_branch(branch_name)
- expect(repository_rugged.branches[branch_name]).not_to be_nil
+ it "removes the branch from the repo" do
+ branch_name = "to-be-deleted-soon"
- repository.delete_branch(branch_name)
- expect(repository_rugged.branches[branch_name]).to be_nil
- end
+ repository.create_branch(branch_name)
+ expect(repository_rugged.branches[branch_name]).not_to be_nil
- context "when branch does not exist" do
- it "raises a DeleteBranchError exception" do
- expect { repository.delete_branch("this-branch-does-not-exist") }.to raise_error(Gitlab::Git::Repository::DeleteBranchError)
- end
- end
+ repository.delete_branch(branch_name)
+ expect(repository_rugged.branches[branch_name]).to be_nil
end
- context "when Gitaly delete_branch is enabled" do
- it_behaves_like "deleting a branch"
- end
-
- context "when Gitaly delete_branch is disabled", :skip_gitaly_mock do
- it_behaves_like "deleting a branch"
+ context "when branch does not exist" do
+ it "raises a DeleteBranchError exception" do
+ expect { repository.delete_branch("this-branch-does-not-exist") }.to raise_error(Gitlab::Git::Repository::DeleteBranchError)
+ end
end
end
describe "#create_branch" do
- shared_examples 'creating a branch' do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') }
-
- after do
- ensure_seeds
- end
-
- it "should create a new branch" do
- expect(repository.create_branch('new_branch', 'master')).not_to be_nil
- end
+ let(:repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') }
- it "should create a new branch with the right name" do
- expect(repository.create_branch('another_branch', 'master').name).to eq('another_branch')
- end
+ after do
+ ensure_seeds
+ end
- it "should fail if we create an existing branch" do
- repository.create_branch('duplicated_branch', 'master')
- expect {repository.create_branch('duplicated_branch', 'master')}.to raise_error("Branch duplicated_branch already exists")
- end
+ it "should create a new branch" do
+ expect(repository.create_branch('new_branch', 'master')).not_to be_nil
+ end
- it "should fail if we create a branch from a non existing ref" do
- expect {repository.create_branch('branch_based_in_wrong_ref', 'master_2_the_revenge')}.to raise_error("Invalid reference master_2_the_revenge")
- end
+ it "should create a new branch with the right name" do
+ expect(repository.create_branch('another_branch', 'master').name).to eq('another_branch')
end
- context 'when Gitaly create_branch feature is enabled' do
- it_behaves_like 'creating a branch'
+ it "should fail if we create an existing branch" do
+ repository.create_branch('duplicated_branch', 'master')
+ expect {repository.create_branch('duplicated_branch', 'master')}.to raise_error("Branch duplicated_branch already exists")
end
- context 'when Gitaly create_branch feature is disabled', :skip_gitaly_mock do
- it_behaves_like 'creating a branch'
+ it "should fail if we create a branch from a non existing ref" do
+ expect {repository.create_branch('branch_based_in_wrong_ref', 'master_2_the_revenge')}.to raise_error("Invalid reference master_2_the_revenge")
end
end
describe '#delete_refs' do
- shared_examples 'deleting refs' do
- let(:repo) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') }
-
- def repo_rugged
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- repo.rugged
- end
- end
+ let(:repo) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') }
- after do
- ensure_seeds
+ def repo_rugged
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ repo.rugged
end
+ end
- it 'deletes the ref' do
- repo.delete_refs('refs/heads/feature')
+ after do
+ ensure_seeds
+ end
- expect(repo_rugged.references['refs/heads/feature']).to be_nil
- end
+ it 'deletes the ref' do
+ repo.delete_refs('refs/heads/feature')
- it 'deletes all refs' do
- refs = %w[refs/heads/wip refs/tags/v1.1.0]
- repo.delete_refs(*refs)
+ expect(repo_rugged.references['refs/heads/feature']).to be_nil
+ end
- refs.each do |ref|
- expect(repo_rugged.references[ref]).to be_nil
- end
- end
+ it 'deletes all refs' do
+ refs = %w[refs/heads/wip refs/tags/v1.1.0]
+ repo.delete_refs(*refs)
- it 'does not fail when deleting an empty list of refs' do
- expect { repo.delete_refs(*[]) }.not_to raise_error
- end
-
- it 'raises an error if it failed' do
- expect { repo.delete_refs('refs\heads\fix') }.to raise_error(Gitlab::Git::Repository::GitError)
+ refs.each do |ref|
+ expect(repo_rugged.references[ref]).to be_nil
end
end
- context 'when Gitaly delete_refs feature is enabled' do
- it_behaves_like 'deleting refs'
+ it 'does not fail when deleting an empty list of refs' do
+ expect { repo.delete_refs(*[]) }.not_to raise_error
end
- context 'when Gitaly delete_refs feature is disabled', :disable_gitaly do
- it_behaves_like 'deleting refs'
+ it 'raises an error if it failed' do
+ expect { repo.delete_refs('refs\heads\fix') }.to raise_error(Gitlab::Git::Repository::GitError)
end
end
@@ -542,44 +503,65 @@ describe Gitlab::Git::Repository, :seed_helper do
Gitlab::Shell.new.remove_repository('default', 'my_project')
end
- shared_examples 'repository mirror fetching' do
- it 'fetches a repository as a mirror remote' do
+ it 'fetches a repository as a mirror remote' do
+ subject
+
+ expect(refs(new_repository_path)).to eq(refs(repository_path))
+ end
+
+ context 'with keep-around refs' do
+ let(:sha) { SeedRepo::Commit::ID }
+ let(:keep_around_ref) { "refs/keep-around/#{sha}" }
+ let(:tmp_ref) { "refs/tmp/#{SecureRandom.hex}" }
+
+ before do
+ repository_rugged.references.create(keep_around_ref, sha, force: true)
+ repository_rugged.references.create(tmp_ref, sha, force: true)
+ end
+
+ it 'includes the temporary and keep-around refs' do
subject
- expect(refs(new_repository_path)).to eq(refs(repository_path))
+ expect(refs(new_repository_path)).to include(keep_around_ref)
+ expect(refs(new_repository_path)).to include(tmp_ref)
end
+ end
- context 'with keep-around refs' do
- let(:sha) { SeedRepo::Commit::ID }
- let(:keep_around_ref) { "refs/keep-around/#{sha}" }
- let(:tmp_ref) { "refs/tmp/#{SecureRandom.hex}" }
+ def new_repository_path
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ new_repository.path
+ end
+ end
+ end
- before do
- repository_rugged.references.create(keep_around_ref, sha, force: true)
- repository_rugged.references.create(tmp_ref, sha, force: true)
- end
+ describe '#find_remote_root_ref' do
+ it 'gets the remote root ref from GitalyClient' do
+ expect_any_instance_of(Gitlab::GitalyClient::RemoteService)
+ .to receive(:find_remote_root_ref).and_call_original
- it 'includes the temporary and keep-around refs' do
- subject
+ expect(repository.find_remote_root_ref('origin')).to eq 'master'
+ end
- expect(refs(new_repository_path)).to include(keep_around_ref)
- expect(refs(new_repository_path)).to include(tmp_ref)
- end
- end
+ it 'returns UTF-8' do
+ expect(repository.find_remote_root_ref('origin')).to be_utf8
end
- context 'with gitaly enabled' do
- it_behaves_like 'repository mirror fetching'
+ it 'returns nil when remote name is nil' do
+ expect_any_instance_of(Gitlab::GitalyClient::RemoteService)
+ .not_to receive(:find_remote_root_ref)
+
+ expect(repository.find_remote_root_ref(nil)).to be_nil
end
- context 'with gitaly enabled', :skip_gitaly_mock do
- it_behaves_like 'repository mirror fetching'
+ it 'returns nil when remote name is empty' do
+ expect_any_instance_of(Gitlab::GitalyClient::RemoteService)
+ .not_to receive(:find_remote_root_ref)
+
+ expect(repository.find_remote_root_ref('')).to be_nil
end
- def new_repository_path
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- new_repository.path
- end
+ it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RemoteService, :find_remote_root_ref do
+ subject { repository.find_remote_root_ref('origin') }
end
end
@@ -887,25 +869,15 @@ describe Gitlab::Git::Repository, :seed_helper do
end
describe '#merge_base' do
- shared_examples '#merge_base' do
- where(:from, :to, :result) do
- '570e7b2abdd848b95f2f578043fc23bd6f6fd24d' | '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | '570e7b2abdd848b95f2f578043fc23bd6f6fd24d'
- '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | '570e7b2abdd848b95f2f578043fc23bd6f6fd24d' | '570e7b2abdd848b95f2f578043fc23bd6f6fd24d'
- '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | 'foobar' | nil
- 'foobar' | '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | nil
- end
-
- with_them do
- it { expect(repository.merge_base(from, to)).to eq(result) }
- end
- end
-
- context 'with gitaly' do
- it_behaves_like '#merge_base'
+ where(:from, :to, :result) do
+ '570e7b2abdd848b95f2f578043fc23bd6f6fd24d' | '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | '570e7b2abdd848b95f2f578043fc23bd6f6fd24d'
+ '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | '570e7b2abdd848b95f2f578043fc23bd6f6fd24d' | '570e7b2abdd848b95f2f578043fc23bd6f6fd24d'
+ '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | 'foobar' | nil
+ 'foobar' | '40f4a7a617393735a95a0bb67b08385bc1e7c66d' | nil
end
- context 'without gitaly', :skip_gitaly_mock do
- it_behaves_like '#merge_base'
+ with_them do
+ it { expect(repository.merge_base(from, to)).to eq(result) }
end
end
@@ -997,54 +969,6 @@ describe Gitlab::Git::Repository, :seed_helper do
end
end
- describe '#autocrlf' do
- before(:all) do
- @repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
- @repo.rugged.config['core.autocrlf'] = true
- end
-
- around do |example|
- # OK because autocrlf is only used in gitaly-ruby
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- example.run
- end
- end
-
- it 'return the value of the autocrlf option' do
- expect(@repo.autocrlf).to be(true)
- end
-
- after(:all) do
- @repo.rugged.config.delete('core.autocrlf')
- end
- end
-
- describe '#autocrlf=' do
- before(:all) do
- @repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
- @repo.rugged.config['core.autocrlf'] = false
- end
-
- around do |example|
- # OK because autocrlf= is only used in gitaly-ruby
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- example.run
- end
- end
-
- it 'should set the autocrlf option to the provided option' do
- @repo.autocrlf = :input
-
- File.open(File.join(SEED_STORAGE_PATH, TEST_MUTABLE_REPO_PATH, 'config')) do |config_file|
- expect(config_file.read).to match('autocrlf = input')
- end
- end
-
- after(:all) do
- @repo.rugged.config.delete('core.autocrlf')
- end
- end
-
describe '#find_branch' do
it 'should return a Branch for master' do
branch = repository.find_branch('master')
@@ -1144,57 +1068,47 @@ describe Gitlab::Git::Repository, :seed_helper do
end
describe '#merged_branch_names' do
- shared_examples 'finding merged branch names' do
- context 'when branch names are passed' do
- it 'only returns the names we are asking' do
- names = repository.merged_branch_names(%w[merge-test])
+ context 'when branch names are passed' do
+ it 'only returns the names we are asking' do
+ names = repository.merged_branch_names(%w[merge-test])
- expect(names).to contain_exactly('merge-test')
- end
+ expect(names).to contain_exactly('merge-test')
+ end
- it 'does not return unmerged branch names' do
- names = repository.merged_branch_names(%w[feature])
+ it 'does not return unmerged branch names' do
+ names = repository.merged_branch_names(%w[feature])
- expect(names).to be_empty
- end
+ expect(names).to be_empty
end
+ end
- context 'when no root ref is available' do
- it 'returns empty list' do
- project = create(:project, :empty_repo)
+ context 'when no root ref is available' do
+ it 'returns empty list' do
+ project = create(:project, :empty_repo)
- names = project.repository.merged_branch_names(%w[feature])
+ names = project.repository.merged_branch_names(%w[feature])
- expect(names).to be_empty
- end
+ expect(names).to be_empty
end
+ end
- context 'when no branch names are specified' do
- before do
- repository.create_branch('identical', 'master')
- end
-
- after do
- ensure_seeds
- end
-
- it 'returns all merged branch names except for identical one' do
- names = repository.merged_branch_names
+ context 'when no branch names are specified' do
+ before do
+ repository.create_branch('identical', 'master')
+ end
- expect(names).to include('merge-test')
- expect(names).to include('fix-mode')
- expect(names).not_to include('feature')
- expect(names).not_to include('identical')
- end
+ after do
+ ensure_seeds
end
- end
- context 'when Gitaly merged_branch_names feature is enabled' do
- it_behaves_like 'finding merged branch names'
- end
+ it 'returns all merged branch names except for identical one' do
+ names = repository.merged_branch_names
- context 'when Gitaly merged_branch_names feature is disabled', :disable_gitaly do
- it_behaves_like 'finding merged branch names'
+ expect(names).to include('merge-test')
+ expect(names).to include('fix-mode')
+ expect(names).not_to include('feature')
+ expect(names).not_to include('identical')
+ end
end
end
@@ -1311,76 +1225,46 @@ describe Gitlab::Git::Repository, :seed_helper do
end
describe '#ref_exists?' do
- shared_examples 'checks the existence of refs' do
- it 'returns true for an existing tag' do
- expect(repository.ref_exists?('refs/heads/master')).to eq(true)
- end
-
- it 'returns false for a non-existing tag' do
- expect(repository.ref_exists?('refs/tags/THIS_TAG_DOES_NOT_EXIST')).to eq(false)
- end
-
- it 'raises an ArgumentError for an empty string' do
- expect { repository.ref_exists?('') }.to raise_error(ArgumentError)
- end
+ it 'returns true for an existing tag' do
+ expect(repository.ref_exists?('refs/heads/master')).to eq(true)
+ end
- it 'raises an ArgumentError for an invalid ref' do
- expect { repository.ref_exists?('INVALID') }.to raise_error(ArgumentError)
- end
+ it 'returns false for a non-existing tag' do
+ expect(repository.ref_exists?('refs/tags/THIS_TAG_DOES_NOT_EXIST')).to eq(false)
end
- context 'when Gitaly ref_exists feature is enabled' do
- it_behaves_like 'checks the existence of refs'
+ it 'raises an ArgumentError for an empty string' do
+ expect { repository.ref_exists?('') }.to raise_error(ArgumentError)
end
- context 'when Gitaly ref_exists feature is disabled', :skip_gitaly_mock do
- it_behaves_like 'checks the existence of refs'
+ it 'raises an ArgumentError for an invalid ref' do
+ expect { repository.ref_exists?('INVALID') }.to raise_error(ArgumentError)
end
end
describe '#tag_exists?' do
- shared_examples 'checks the existence of tags' do
- it 'returns true for an existing tag' do
- tag = repository.tag_names.first
+ it 'returns true for an existing tag' do
+ tag = repository.tag_names.first
- expect(repository.tag_exists?(tag)).to eq(true)
- end
-
- it 'returns false for a non-existing tag' do
- expect(repository.tag_exists?('v9000')).to eq(false)
- end
- end
-
- context 'when Gitaly ref_exists_tags feature is enabled' do
- it_behaves_like 'checks the existence of tags'
+ expect(repository.tag_exists?(tag)).to eq(true)
end
- context 'when Gitaly ref_exists_tags feature is disabled', :skip_gitaly_mock do
- it_behaves_like 'checks the existence of tags'
+ it 'returns false for a non-existing tag' do
+ expect(repository.tag_exists?('v9000')).to eq(false)
end
end
describe '#branch_exists?' do
- shared_examples 'checks the existence of branches' do
- it 'returns true for an existing branch' do
- expect(repository.branch_exists?('master')).to eq(true)
- end
-
- it 'returns false for a non-existing branch' do
- expect(repository.branch_exists?('kittens')).to eq(false)
- end
-
- it 'returns false when using an invalid branch name' do
- expect(repository.branch_exists?('.bla')).to eq(false)
- end
+ it 'returns true for an existing branch' do
+ expect(repository.branch_exists?('master')).to eq(true)
end
- context 'when Gitaly ref_exists_branches feature is enabled' do
- it_behaves_like 'checks the existence of branches'
+ it 'returns false for a non-existing branch' do
+ expect(repository.branch_exists?('kittens')).to eq(false)
end
- context 'when Gitaly ref_exists_branches feature is disabled', :skip_gitaly_mock do
- it_behaves_like 'checks the existence of branches'
+ it 'returns false when using an invalid branch name' do
+ expect(repository.branch_exists?('.bla')).to eq(false)
end
end
@@ -1471,52 +1355,6 @@ describe Gitlab::Git::Repository, :seed_helper do
end
end
- describe '#with_repo_branch_commit' do
- context 'when comparing with the same repository' do
- let(:start_repository) { repository }
-
- context 'when the branch exists' do
- let(:start_branch_name) { 'master' }
-
- it 'yields the commit' do
- expect { |b| repository.with_repo_branch_commit(start_repository, start_branch_name, &b) }
- .to yield_with_args(an_instance_of(Gitlab::Git::Commit))
- end
- end
-
- context 'when the branch does not exist' do
- let(:start_branch_name) { 'definitely-not-master' }
-
- it 'yields nil' do
- expect { |b| repository.with_repo_branch_commit(start_repository, start_branch_name, &b) }
- .to yield_with_args(nil)
- end
- end
- end
-
- context 'when comparing with another repository' do
- let(:start_repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') }
-
- context 'when the branch exists' do
- let(:start_branch_name) { 'master' }
-
- it 'yields the commit' do
- expect { |b| repository.with_repo_branch_commit(start_repository, start_branch_name, &b) }
- .to yield_with_args(an_instance_of(Gitlab::Git::Commit))
- end
- end
-
- context 'when the branch does not exist' do
- let(:start_branch_name) { 'definitely-not-master' }
-
- it 'yields nil' do
- expect { |b| repository.with_repo_branch_commit(start_repository, start_branch_name, &b) }
- .to yield_with_args(nil)
- end
- end
- end
- end
-
describe '#fetch_source_branch!' do
let(:local_ref) { 'refs/merge-requests/1/head' }
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') }
@@ -1567,29 +1405,19 @@ describe Gitlab::Git::Repository, :seed_helper do
end
describe '#rm_branch' do
- shared_examples "user deleting a branch" do
- let(:project) { create(:project, :repository) }
- let(:repository) { project.repository.raw }
- let(:branch_name) { "to-be-deleted-soon" }
-
- before do
- project.add_developer(user)
- repository.create_branch(branch_name)
- end
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository.raw }
+ let(:branch_name) { "to-be-deleted-soon" }
- it "removes the branch from the repo" do
- repository.rm_branch(branch_name, user: user)
-
- expect(repository_rugged.branches[branch_name]).to be_nil
- end
+ before do
+ project.add_developer(user)
+ repository.create_branch(branch_name)
end
- context "when Gitaly user_delete_branch is enabled" do
- it_behaves_like "user deleting a branch"
- end
+ it "removes the branch from the repo" do
+ repository.rm_branch(branch_name, user: user)
- context "when Gitaly user_delete_branch is disabled", :skip_gitaly_mock do
- it_behaves_like "user deleting a branch"
+ expect(repository_rugged.branches[branch_name]).to be_nil
end
end
@@ -1713,39 +1541,29 @@ describe Gitlab::Git::Repository, :seed_helper do
ensure_seeds
end
- shared_examples '#merge' do
- it 'can perform a merge' do
- merge_commit_id = nil
- result = repository.merge(user, source_sha, target_branch, 'Test merge') do |commit_id|
- merge_commit_id = commit_id
- end
-
- expect(result.newrev).to eq(merge_commit_id)
- expect(result.repo_created).to eq(false)
- expect(result.branch_created).to eq(false)
+ it 'can perform a merge' do
+ merge_commit_id = nil
+ result = repository.merge(user, source_sha, target_branch, 'Test merge') do |commit_id|
+ merge_commit_id = commit_id
end
- it 'returns nil if there was a concurrent branch update' do
- concurrent_update_id = '33f3729a45c02fc67d00adb1b8bca394b0e761d9'
- result = repository.merge(user, source_sha, target_branch, 'Test merge') do
- # This ref update should make the merge fail
- repository.write_ref(Gitlab::Git::BRANCH_REF_PREFIX + target_branch, concurrent_update_id)
- end
-
- # This 'nil' signals that the merge was not applied
- expect(result).to be_nil
+ expect(result.newrev).to eq(merge_commit_id)
+ expect(result.repo_created).to eq(false)
+ expect(result.branch_created).to eq(false)
+ end
- # Our concurrent ref update should not have been undone
- expect(repository.find_branch(target_branch).target).to eq(concurrent_update_id)
+ it 'returns nil if there was a concurrent branch update' do
+ concurrent_update_id = '33f3729a45c02fc67d00adb1b8bca394b0e761d9'
+ result = repository.merge(user, source_sha, target_branch, 'Test merge') do
+ # This ref update should make the merge fail
+ repository.write_ref(Gitlab::Git::BRANCH_REF_PREFIX + target_branch, concurrent_update_id)
end
- end
- context 'with gitaly' do
- it_behaves_like '#merge'
- end
+ # This 'nil' signals that the merge was not applied
+ expect(result).to be_nil
- context 'without gitaly', :skip_gitaly_mock do
- it_behaves_like '#merge'
+ # Our concurrent ref update should not have been undone
+ expect(repository.find_branch(target_branch).target).to eq(concurrent_update_id)
end
end
@@ -1857,88 +1675,47 @@ describe Gitlab::Git::Repository, :seed_helper do
describe '#add_remote' do
let(:mirror_refmap) { '+refs/*:refs/*' }
- shared_examples 'add_remote' do
- it 'added the remote' do
- begin
- rugged.remotes.delete(remote_name)
- rescue Rugged::ConfigError
- end
-
- repository.add_remote(remote_name, url, mirror_refmap: mirror_refmap)
-
- expect(rugged.remotes[remote_name]).not_to be_nil
- expect(rugged.config["remote.#{remote_name}.mirror"]).to eq('true')
- expect(rugged.config["remote.#{remote_name}.prune"]).to eq('true')
- expect(rugged.config["remote.#{remote_name}.fetch"]).to eq(mirror_refmap)
+ it 'added the remote' do
+ begin
+ rugged.remotes.delete(remote_name)
+ rescue Rugged::ConfigError
end
- end
- context 'using Gitaly' do
- it_behaves_like 'add_remote'
- end
+ repository.add_remote(remote_name, url, mirror_refmap: mirror_refmap)
- context 'with Gitaly disabled', :disable_gitaly do
- it_behaves_like 'add_remote'
+ expect(rugged.remotes[remote_name]).not_to be_nil
+ expect(rugged.config["remote.#{remote_name}.mirror"]).to eq('true')
+ expect(rugged.config["remote.#{remote_name}.prune"]).to eq('true')
+ expect(rugged.config["remote.#{remote_name}.fetch"]).to eq(mirror_refmap)
end
end
describe '#remove_remote' do
- shared_examples 'remove_remote' do
- it 'removes the remote' do
- rugged.remotes.create(remote_name, url)
-
- repository.remove_remote(remote_name)
-
- expect(rugged.remotes[remote_name]).to be_nil
- end
- end
-
- context 'using Gitaly' do
- it_behaves_like 'remove_remote'
- end
-
- context 'with Gitaly disabled', :disable_gitaly do
- it_behaves_like 'remove_remote'
- end
- end
- end
+ it 'removes the remote' do
+ rugged.remotes.create(remote_name, url)
- describe '#gitlab_projects' do
- subject { repository.gitlab_projects }
+ repository.remove_remote(remote_name)
- it do
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- expect(subject.shard_path).to eq(storage_path)
+ expect(rugged.remotes[remote_name]).to be_nil
end
end
- it { expect(subject.repository_relative_path).to eq(repository.relative_path) }
end
describe '#bundle_to_disk' do
- shared_examples 'bundling to disk' do
- let(:save_path) { File.join(Dir.tmpdir, "repo-#{SecureRandom.hex}.bundle") }
+ let(:save_path) { File.join(Dir.tmpdir, "repo-#{SecureRandom.hex}.bundle") }
- after do
- FileUtils.rm_rf(save_path)
- end
-
- it 'saves a bundle to disk' do
- repository.bundle_to_disk(save_path)
-
- success = system(
- *%W(#{Gitlab.config.git.bin_path} -C #{repository_path} bundle verify #{save_path}),
- [:out, :err] => '/dev/null'
- )
- expect(success).to be true
- end
+ after do
+ FileUtils.rm_rf(save_path)
end
- context 'when Gitaly bundle_to_disk feature is enabled' do
- it_behaves_like 'bundling to disk'
- end
+ it 'saves a bundle to disk' do
+ repository.bundle_to_disk(save_path)
- context 'when Gitaly bundle_to_disk feature is disabled', :disable_gitaly do
- it_behaves_like 'bundling to disk'
+ success = system(
+ *%W(#{Gitlab.config.git.bin_path} -C #{repository_path} bundle verify #{save_path}),
+ [:out, :err] => '/dev/null'
+ )
+ expect(success).to be true
end
end
@@ -2013,138 +1790,41 @@ describe Gitlab::Git::Repository, :seed_helper do
end
end
- context 'gitlab_projects commands' do
- let(:gitlab_projects) { repository.gitlab_projects }
- let(:timeout) { Gitlab.config.gitlab_shell.git_timeout }
+ describe '#clean_stale_repository_files' do
+ let(:worktree_path) { File.join(repository_path, 'worktrees', 'delete-me') }
- describe '#push_remote_branches' do
- subject do
- repository.push_remote_branches('downstream-remote', ['master'])
- end
+ it 'cleans up the files' do
+ create_worktree = %W[git -C #{repository_path} worktree add --detach #{worktree_path} master]
+ raise 'preparation failed' unless system(*create_worktree, err: '/dev/null')
- it 'executes the command' do
- expect(gitlab_projects).to receive(:push_branches)
- .with('downstream-remote', timeout, true, ['master'])
- .and_return(true)
+ FileUtils.touch(worktree_path, mtime: Time.now - 8.hours)
+ # git rev-list --all will fail in git 2.16 if HEAD is pointing to a non-existent object,
+ # but the HEAD must be 40 characters long or git will ignore it.
+ File.write(File.join(worktree_path, 'HEAD'), Gitlab::Git::BLANK_SHA)
- is_expected.to be_truthy
- end
+ # git 2.16 fails with "fatal: bad object HEAD"
+ expect(rev_list_all).to be false
- it 'raises an error if the command fails' do
- allow(gitlab_projects).to receive(:output) { 'error' }
- expect(gitlab_projects).to receive(:push_branches)
- .with('downstream-remote', timeout, true, ['master'])
- .and_return(false)
+ repository.clean_stale_repository_files
- expect { subject }.to raise_error(Gitlab::Git::CommandError, 'error')
- end
+ expect(rev_list_all).to be true
+ expect(File.exist?(worktree_path)).to be_falsey
end
- describe '#delete_remote_branches' do
- subject do
- repository.delete_remote_branches('downstream-remote', ['master'])
- end
-
- it 'executes the command' do
- expect(gitlab_projects).to receive(:delete_remote_branches)
- .with('downstream-remote', ['master'])
- .and_return(true)
-
- is_expected.to be_truthy
- end
-
- it 'raises an error if the command fails' do
- allow(gitlab_projects).to receive(:output) { 'error' }
- expect(gitlab_projects).to receive(:delete_remote_branches)
- .with('downstream-remote', ['master'])
- .and_return(false)
-
- expect { subject }.to raise_error(Gitlab::Git::CommandError, 'error')
- end
- end
-
- describe '#delete_remote_branches' do
- subject do
- repository.delete_remote_branches('downstream-remote', ['master'])
- end
-
- it 'executes the command' do
- expect(gitlab_projects).to receive(:delete_remote_branches)
- .with('downstream-remote', ['master'])
- .and_return(true)
-
- is_expected.to be_truthy
- end
-
- it 'raises an error if the command fails' do
- allow(gitlab_projects).to receive(:output) { 'error' }
- expect(gitlab_projects).to receive(:delete_remote_branches)
- .with('downstream-remote', ['master'])
- .and_return(false)
-
- expect { subject }.to raise_error(Gitlab::Git::CommandError, 'error')
- end
- end
-
- describe '#clean_stale_repository_files' do
- let(:worktree_path) { File.join(repository_path, 'worktrees', 'delete-me') }
-
- it 'cleans up the files' do
- create_worktree = %W[git -C #{repository_path} worktree add --detach #{worktree_path} master]
- raise 'preparation failed' unless system(*create_worktree, err: '/dev/null')
-
- FileUtils.touch(worktree_path, mtime: Time.now - 8.hours)
- # git rev-list --all will fail in git 2.16 if HEAD is pointing to a non-existent object,
- # but the HEAD must be 40 characters long or git will ignore it.
- File.write(File.join(worktree_path, 'HEAD'), Gitlab::Git::BLANK_SHA)
-
- # git 2.16 fails with "fatal: bad object HEAD"
- expect(rev_list_all).to be false
-
- repository.clean_stale_repository_files
-
- expect(rev_list_all).to be true
- expect(File.exist?(worktree_path)).to be_falsey
- end
-
- def rev_list_all
- system(*%W[git -C #{repository_path} rev-list --all], out: '/dev/null', err: '/dev/null')
- end
-
- it 'increments a counter upon an error' do
- expect(repository.gitaly_repository_client).to receive(:cleanup).and_raise(Gitlab::Git::CommandError)
-
- counter = double(:counter)
-
- expect(counter).to receive(:increment)
- expect(Gitlab::Metrics).to receive(:counter).with(:failed_repository_cleanup_total,
- 'Number of failed repository cleanup events').and_return(counter)
-
- repository.clean_stale_repository_files
- end
+ def rev_list_all
+ system(*%W[git -C #{repository_path} rev-list --all], out: '/dev/null', err: '/dev/null')
end
- describe '#delete_remote_branches' do
- subject do
- repository.delete_remote_branches('downstream-remote', ['master'])
- end
+ it 'increments a counter upon an error' do
+ expect(repository.gitaly_repository_client).to receive(:cleanup).and_raise(Gitlab::Git::CommandError)
- it 'executes the command' do
- expect(gitlab_projects).to receive(:delete_remote_branches)
- .with('downstream-remote', ['master'])
- .and_return(true)
+ counter = double(:counter)
- is_expected.to be_truthy
- end
-
- it 'raises an error if the command fails' do
- allow(gitlab_projects).to receive(:output) { 'error' }
- expect(gitlab_projects).to receive(:delete_remote_branches)
- .with('downstream-remote', ['master'])
- .and_return(false)
+ expect(counter).to receive(:increment)
+ expect(Gitlab::Metrics).to receive(:counter).with(:failed_repository_cleanup_total,
+ 'Number of failed repository cleanup events').and_return(counter)
- expect { subject }.to raise_error(Gitlab::Git::CommandError, 'error')
- end
+ repository.clean_stale_repository_files
end
end
diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
index 54f2ea33f90..bcdf12a00a0 100644
--- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
@@ -19,7 +19,14 @@ describe Gitlab::GitalyClient::CommitService do
right_commit_id: commit.id,
collapse_diffs: false,
enforce_limits: true,
- **Gitlab::Git::DiffCollection.collection_limits.to_h
+ # Tests limitation parameters explicitly
+ max_files: 100,
+ max_lines: 5000,
+ max_bytes: 512000,
+ safe_max_files: 100,
+ safe_max_lines: 5000,
+ safe_max_bytes: 512000,
+ max_patch_bytes: 102400
)
expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_diff).with(request, kind_of(Hash))
@@ -37,7 +44,14 @@ describe Gitlab::GitalyClient::CommitService do
right_commit_id: initial_commit.id,
collapse_diffs: false,
enforce_limits: true,
- **Gitlab::Git::DiffCollection.collection_limits.to_h
+ # Tests limitation parameters explicitly
+ max_files: 100,
+ max_lines: 5000,
+ max_bytes: 512000,
+ safe_max_files: 100,
+ safe_max_lines: 5000,
+ safe_max_bytes: 512000,
+ max_patch_bytes: 102400
)
expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_diff).with(request, kind_of(Hash))
diff --git a/spec/lib/gitlab/gitaly_client/remote_service_spec.rb b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb
index f03c7e3f04b..9030a49983d 100644
--- a/spec/lib/gitlab/gitaly_client/remote_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb
@@ -45,6 +45,26 @@ describe Gitlab::GitalyClient::RemoteService do
end
end
+ describe '#find_remote_root_ref' do
+ it 'sends an find_remote_root_ref message and returns the root ref' do
+ expect_any_instance_of(Gitaly::RemoteService::Stub)
+ .to receive(:find_remote_root_ref)
+ .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
+ .and_return(double(ref: 'master'))
+
+ expect(client.find_remote_root_ref('origin')).to eq 'master'
+ end
+
+ it 'ensure ref is a valid UTF-8 string' do
+ expect_any_instance_of(Gitaly::RemoteService::Stub)
+ .to receive(:find_remote_root_ref)
+ .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
+ .and_return(double(ref: "an_invalid_ref_\xE5"))
+
+ expect(client.find_remote_root_ref('origin')).to eq "an_invalid_ref_Ã¥"
+ end
+ end
+
describe '#update_remote_mirror' do
let(:ref_name) { 'remote_mirror_1' }
let(:only_branches_matching) { ['my-branch', 'master'] }
diff --git a/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_object_storage_spec.rb b/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_object_storage_spec.rb
deleted file mode 100644
index 5059d68e54b..00000000000
--- a/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_object_storage_spec.rb
+++ /dev/null
@@ -1,105 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::ImportExport::AfterExportStrategies::BaseAfterExportStrategy do
- let!(:service) { described_class.new }
- let!(:project) { create(:project, :with_object_export) }
- let(:shared) { project.import_export_shared }
- let!(:user) { create(:user) }
-
- describe '#execute' do
- before do
- allow(service).to receive(:strategy_execute)
- stub_feature_flags(import_export_object_storage: true)
- end
-
- it 'returns if project exported file is not found' do
- allow(project).to receive(:export_project_object_exists?).and_return(false)
-
- expect(service).not_to receive(:strategy_execute)
-
- service.execute(user, project)
- end
-
- it 'creates a lock file in the export dir' do
- allow(service).to receive(:delete_after_export_lock)
-
- service.execute(user, project)
-
- expect(lock_path_exist?).to be_truthy
- end
-
- context 'when the method succeeds' do
- it 'removes the lock file' do
- service.execute(user, project)
-
- expect(lock_path_exist?).to be_falsey
- end
- end
-
- context 'when the method fails' do
- before do
- allow(service).to receive(:strategy_execute).and_call_original
- end
-
- context 'when validation fails' do
- before do
- allow(service).to receive(:invalid?).and_return(true)
- end
-
- it 'does not create the lock file' do
- expect(service).not_to receive(:create_or_update_after_export_lock)
-
- service.execute(user, project)
- end
-
- it 'does not execute main logic' do
- expect(service).not_to receive(:strategy_execute)
-
- service.execute(user, project)
- end
-
- it 'logs validation errors in shared context' do
- expect(service).to receive(:log_validation_errors)
-
- service.execute(user, project)
- end
- end
-
- context 'when an exception is raised' do
- it 'removes the lock' do
- expect { service.execute(user, project) }.to raise_error(NotImplementedError)
-
- expect(lock_path_exist?).to be_falsey
- end
- end
- end
- end
-
- describe '#log_validation_errors' do
- it 'add the message to the shared context' do
- errors = %w(test_message test_message2)
-
- allow(service).to receive(:invalid?).and_return(true)
- allow(service.errors).to receive(:full_messages).and_return(errors)
-
- expect(shared).to receive(:add_error_message).twice.and_call_original
-
- service.execute(user, project)
-
- expect(shared.errors).to eq errors
- end
- end
-
- describe '#to_json' do
- it 'adds the current strategy class to the serialized attributes' do
- params = { param1: 1 }
- result = params.merge(klass: described_class.to_s).to_json
-
- expect(described_class.new(params).to_json).to eq result
- end
- end
-
- def lock_path_exist?
- File.exist?(described_class.lock_file_path(project))
- end
-end
diff --git a/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb b/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb
index 566b7f46c87..9a442de2900 100644
--- a/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb
+++ b/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb
@@ -9,11 +9,10 @@ describe Gitlab::ImportExport::AfterExportStrategies::BaseAfterExportStrategy do
describe '#execute' do
before do
allow(service).to receive(:strategy_execute)
- stub_feature_flags(import_export_object_storage: false)
end
it 'returns if project exported file is not found' do
- allow(project).to receive(:export_project_path).and_return(nil)
+ allow(project).to receive(:export_file_exists?).and_return(false)
expect(service).not_to receive(:strategy_execute)
diff --git a/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb b/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb
index 7f2e0a4ee2c..ec17ad8541f 100644
--- a/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb
+++ b/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb
@@ -24,34 +24,13 @@ describe Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy do
end
describe '#execute' do
- context 'without object storage' do
- before do
- stub_feature_flags(import_export_object_storage: false)
- end
-
- it 'removes the exported project file after the upload' do
- allow(strategy).to receive(:send_file)
- allow(strategy).to receive(:handle_response_error)
-
- expect(project).to receive(:remove_exported_project_file)
-
- strategy.execute(user, project)
- end
- end
-
- context 'with object storage' do
- before do
- stub_feature_flags(import_export_object_storage: true)
- end
+ it 'removes the exported project file after the upload' do
+ allow(strategy).to receive(:send_file)
+ allow(strategy).to receive(:handle_response_error)
- it 'removes the exported project file after the upload' do
- allow(strategy).to receive(:send_file)
- allow(strategy).to receive(:handle_response_error)
+ expect(project).to receive(:remove_exports)
- expect(project).to receive(:remove_exported_project_file)
-
- strategy.execute(user, project)
- end
+ strategy.execute(user, project)
end
end
end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index b4269bd5786..ec2bdbe22e1 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -288,6 +288,7 @@ project:
- fork_network_member
- fork_network
- custom_attributes
+- prometheus_metrics
- lfs_file_locks
- project_badges
- source_of_merge_requests
@@ -303,6 +304,8 @@ award_emoji:
- user
priorities:
- label
+prometheus_metrics:
+- project
timelogs:
- issue
- merge_request
@@ -321,3 +324,9 @@ metrics:
- latest_closed_by
- merged_by
- pipeline
+resource_label_events:
+- user
+- issue
+- merge_request
+- epic
+- label
diff --git a/spec/lib/gitlab/import_export/avatar_restorer_spec.rb b/spec/lib/gitlab/import_export/avatar_restorer_spec.rb
index 4897d604bc1..e44ff6bbcbd 100644
--- a/spec/lib/gitlab/import_export/avatar_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/avatar_restorer_spec.rb
@@ -6,22 +6,35 @@ describe Gitlab::ImportExport::AvatarRestorer do
let(:shared) { project.import_export_shared }
let(:project) { create(:project) }
- before do
- allow_any_instance_of(described_class).to receive(:avatar_export_file)
- .and_return(uploaded_image_temp_path)
- end
-
after do
project.remove_avatar!
end
- it 'restores a project avatar' do
- expect(described_class.new(project: project, shared: shared).restore).to be true
+ context 'with avatar' do
+ before do
+ allow_any_instance_of(described_class).to receive(:avatar_export_file)
+ .and_return(uploaded_image_temp_path)
+ end
+
+ it 'restores a project avatar' do
+ expect(described_class.new(project: project, shared: shared).restore).to be true
+ end
+
+ it 'saves the avatar into the project' do
+ described_class.new(project: project, shared: shared).restore
+
+ expect(project.reload.avatar.file.exists?).to be true
+ end
end
- it 'saves the avatar into the project' do
- described_class.new(project: project, shared: shared).restore
+ it 'does not break if there is just a directory' do
+ Dir.mktmpdir do |tmpdir|
+ FileUtils.mkdir_p("#{tmpdir}/a/b")
+
+ allow_any_instance_of(described_class).to receive(:avatar_export_path)
+ .and_return("#{tmpdir}/a")
- expect(project.reload.avatar.file.exists?).to be true
+ expect(described_class.new(project: project, shared: shared).restore).to be true
+ end
end
end
diff --git a/spec/lib/gitlab/import_export/avatar_saver_spec.rb b/spec/lib/gitlab/import_export/avatar_saver_spec.rb
index 90e6d653d34..2bd1b9924c6 100644
--- a/spec/lib/gitlab/import_export/avatar_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/avatar_saver_spec.rb
@@ -8,8 +8,7 @@ describe Gitlab::ImportExport::AvatarSaver do
before do
FileUtils.mkdir_p("#{shared.export_path}/avatar/")
- allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
- stub_feature_flags(import_export_object_storage: false)
+ allow_any_instance_of(Gitlab::ImportExport::Shared).to receive(:export_path).and_return(export_path)
end
after do
@@ -19,7 +18,7 @@ describe Gitlab::ImportExport::AvatarSaver do
it 'saves a project avatar' do
described_class.new(project: project_with_avatar, shared: shared).save
- expect(File).to exist("#{shared.export_path}/avatar/dk.png")
+ expect(File).to exist(Dir["#{shared.export_path}/avatar/**/dk.png"].first)
end
it 'is fine not to have an avatar' do
diff --git a/spec/lib/gitlab/import_export/file_importer_object_storage_spec.rb b/spec/lib/gitlab/import_export/file_importer_object_storage_spec.rb
deleted file mode 100644
index 287745eb40e..00000000000
--- a/spec/lib/gitlab/import_export/file_importer_object_storage_spec.rb
+++ /dev/null
@@ -1,89 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::ImportExport::FileImporter do
- let(:shared) { Gitlab::ImportExport::Shared.new(nil) }
- let(:storage_path) { "#{Dir.tmpdir}/file_importer_spec" }
- let(:valid_file) { "#{shared.export_path}/valid.json" }
- let(:symlink_file) { "#{shared.export_path}/invalid.json" }
- let(:hidden_symlink_file) { "#{shared.export_path}/.hidden" }
- let(:subfolder_symlink_file) { "#{shared.export_path}/subfolder/invalid.json" }
- let(:evil_symlink_file) { "#{shared.export_path}/.\nevil" }
-
- before do
- stub_const('Gitlab::ImportExport::FileImporter::MAX_RETRIES', 0)
- stub_feature_flags(import_export_object_storage: true)
- stub_uploads_object_storage(FileUploader)
-
- allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(storage_path)
- allow_any_instance_of(Gitlab::ImportExport::CommandLineUtil).to receive(:untar_zxf).and_return(true)
- allow_any_instance_of(Gitlab::ImportExport::Shared).to receive(:relative_archive_path).and_return('test')
- allow(SecureRandom).to receive(:hex).and_return('abcd')
- setup_files
- end
-
- after do
- FileUtils.rm_rf(storage_path)
- end
-
- context 'normal run' do
- before do
- described_class.import(project: build(:project), archive_file: '', shared: shared)
- end
-
- it 'removes symlinks in root folder' do
- expect(File.exist?(symlink_file)).to be false
- end
-
- it 'removes hidden symlinks in root folder' do
- expect(File.exist?(hidden_symlink_file)).to be false
- end
-
- it 'removes evil symlinks in root folder' do
- expect(File.exist?(evil_symlink_file)).to be false
- end
-
- it 'removes symlinks in subfolders' do
- expect(File.exist?(subfolder_symlink_file)).to be false
- end
-
- it 'does not remove a valid file' do
- expect(File.exist?(valid_file)).to be true
- end
-
- it 'creates the file in the right subfolder' do
- expect(shared.export_path).to include('test/abcd')
- end
- end
-
- context 'error' do
- before do
- allow_any_instance_of(described_class).to receive(:wait_for_archived_file).and_raise(StandardError)
- described_class.import(project: build(:project), archive_file: '', shared: shared)
- end
-
- it 'removes symlinks in root folder' do
- expect(File.exist?(symlink_file)).to be false
- end
-
- it 'removes hidden symlinks in root folder' do
- expect(File.exist?(hidden_symlink_file)).to be false
- end
-
- it 'removes symlinks in subfolders' do
- expect(File.exist?(subfolder_symlink_file)).to be false
- end
-
- it 'does not remove a valid file' do
- expect(File.exist?(valid_file)).to be true
- end
- end
-
- def setup_files
- FileUtils.mkdir_p("#{shared.export_path}/subfolder/")
- FileUtils.touch(valid_file)
- FileUtils.ln_s(valid_file, symlink_file)
- FileUtils.ln_s(valid_file, subfolder_symlink_file)
- FileUtils.ln_s(valid_file, hidden_symlink_file)
- FileUtils.ln_s(valid_file, evil_symlink_file)
- end
-end
diff --git a/spec/lib/gitlab/import_export/file_importer_spec.rb b/spec/lib/gitlab/import_export/file_importer_spec.rb
index 78fccdf1dfc..bf34cefe18f 100644
--- a/spec/lib/gitlab/import_export/file_importer_spec.rb
+++ b/spec/lib/gitlab/import_export/file_importer_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe Gitlab::ImportExport::FileImporter do
let(:shared) { Gitlab::ImportExport::Shared.new(nil) }
- let(:export_path) { "#{Dir.tmpdir}/file_importer_spec" }
+ let(:storage_path) { "#{Dir.tmpdir}/file_importer_spec" }
let(:valid_file) { "#{shared.export_path}/valid.json" }
let(:symlink_file) { "#{shared.export_path}/invalid.json" }
let(:hidden_symlink_file) { "#{shared.export_path}/.hidden" }
@@ -11,7 +11,9 @@ describe Gitlab::ImportExport::FileImporter do
before do
stub_const('Gitlab::ImportExport::FileImporter::MAX_RETRIES', 0)
- allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
+ stub_uploads_object_storage(FileUploader)
+
+ allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(storage_path)
allow_any_instance_of(Gitlab::ImportExport::CommandLineUtil).to receive(:untar_zxf).and_return(true)
allow_any_instance_of(Gitlab::ImportExport::Shared).to receive(:relative_archive_path).and_return('test')
allow(SecureRandom).to receive(:hex).and_return('abcd')
@@ -19,12 +21,12 @@ describe Gitlab::ImportExport::FileImporter do
end
after do
- FileUtils.rm_rf(export_path)
+ FileUtils.rm_rf(storage_path)
end
context 'normal run' do
before do
- described_class.import(project: nil, archive_file: '', shared: shared)
+ described_class.import(project: build(:project), archive_file: '', shared: shared)
end
it 'removes symlinks in root folder' do
@@ -55,7 +57,7 @@ describe Gitlab::ImportExport::FileImporter do
context 'error' do
before do
allow_any_instance_of(described_class).to receive(:wait_for_archived_file).and_raise(StandardError)
- described_class.import(project: nil, archive_file: '', shared: shared)
+ described_class.import(project: build(:project), archive_file: '', shared: shared)
end
it 'removes symlinks in root folder' do
diff --git a/spec/lib/gitlab/import_export/importer_object_storage_spec.rb b/spec/lib/gitlab/import_export/importer_object_storage_spec.rb
deleted file mode 100644
index 24a994b3611..00000000000
--- a/spec/lib/gitlab/import_export/importer_object_storage_spec.rb
+++ /dev/null
@@ -1,115 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::ImportExport::Importer do
- let(:user) { create(:user) }
- let(:test_path) { "#{Dir.tmpdir}/importer_spec" }
- let(:shared) { project.import_export_shared }
- let(:project) { create(:project) }
- let(:import_file) { fixture_file_upload('spec/features/projects/import_export/test_project_export.tar.gz') }
-
- subject(:importer) { described_class.new(project) }
-
- before do
- allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(test_path)
- allow_any_instance_of(Gitlab::ImportExport::FileImporter).to receive(:remove_import_file)
- stub_feature_flags(import_export_object_storage: true)
- stub_uploads_object_storage(FileUploader)
-
- FileUtils.mkdir_p(shared.export_path)
- ImportExportUpload.create(project: project, import_file: import_file)
- end
-
- after do
- FileUtils.rm_rf(test_path)
- end
-
- describe '#execute' do
- it 'succeeds' do
- importer.execute
-
- expect(shared.errors).to be_empty
- end
-
- it 'extracts the archive' do
- expect(Gitlab::ImportExport::FileImporter).to receive(:import).and_call_original
-
- importer.execute
- end
-
- it 'checks the version' do
- expect(Gitlab::ImportExport::VersionChecker).to receive(:check!).and_call_original
-
- importer.execute
- end
-
- context 'all restores are executed' do
- [
- Gitlab::ImportExport::AvatarRestorer,
- Gitlab::ImportExport::RepoRestorer,
- Gitlab::ImportExport::WikiRestorer,
- Gitlab::ImportExport::UploadsRestorer,
- Gitlab::ImportExport::LfsRestorer,
- Gitlab::ImportExport::StatisticsRestorer
- ].each do |restorer|
- it "calls the #{restorer}" do
- fake_restorer = double(restorer.to_s)
-
- expect(fake_restorer).to receive(:restore).and_return(true).at_least(1)
- expect(restorer).to receive(:new).and_return(fake_restorer).at_least(1)
-
- importer.execute
- end
- end
-
- it 'restores the ProjectTree' do
- expect(Gitlab::ImportExport::ProjectTreeRestorer).to receive(:new).and_call_original
-
- importer.execute
- end
-
- it 'removes the import file' do
- expect(importer).to receive(:remove_import_file).and_call_original
-
- importer.execute
-
- expect(project.import_export_upload.import_file&.file).to be_nil
- end
- end
-
- context 'when project successfully restored' do
- let!(:existing_project) { create(:project, namespace: user.namespace) }
- let(:project) { create(:project, namespace: user.namespace, name: 'whatever', path: 'whatever') }
-
- before do
- restorers = double(:restorers, all?: true)
-
- allow(subject).to receive(:import_file).and_return(true)
- allow(subject).to receive(:check_version!).and_return(true)
- allow(subject).to receive(:restorers).and_return(restorers)
- allow(project).to receive(:import_data).and_return(double(data: { 'original_path' => existing_project.path }))
- end
-
- context 'when import_data' do
- context 'has original_path' do
- it 'overwrites existing project' do
- expect_any_instance_of(::Projects::OverwriteProjectService).to receive(:execute).with(existing_project)
-
- subject.execute
- end
- end
-
- context 'has not original_path' do
- before do
- allow(project).to receive(:import_data).and_return(double(data: {}))
- end
-
- it 'does not call the overwrite service' do
- expect_any_instance_of(::Projects::OverwriteProjectService).not_to receive(:execute).with(existing_project)
-
- subject.execute
- end
- end
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/import_export/importer_spec.rb b/spec/lib/gitlab/import_export/importer_spec.rb
index 8053c48ad6c..11f98d782b1 100644
--- a/spec/lib/gitlab/import_export/importer_spec.rb
+++ b/spec/lib/gitlab/import_export/importer_spec.rb
@@ -4,16 +4,18 @@ describe Gitlab::ImportExport::Importer do
let(:user) { create(:user) }
let(:test_path) { "#{Dir.tmpdir}/importer_spec" }
let(:shared) { project.import_export_shared }
- let(:project) { create(:project, import_source: File.join(test_path, 'test_project_export.tar.gz')) }
+ let(:project) { create(:project) }
+ let(:import_file) { fixture_file_upload('spec/features/projects/import_export/test_project_export.tar.gz') }
subject(:importer) { described_class.new(project) }
before do
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(test_path)
allow_any_instance_of(Gitlab::ImportExport::FileImporter).to receive(:remove_import_file)
+ stub_uploads_object_storage(FileUploader)
FileUtils.mkdir_p(shared.export_path)
- FileUtils.cp(Rails.root.join('spec/features/projects/import_export/test_project_export.tar.gz'), test_path)
+ ImportExportUpload.create(project: project, import_file: import_file)
end
after do
@@ -64,6 +66,14 @@ describe Gitlab::ImportExport::Importer do
importer.execute
end
+ it 'removes the import file' do
+ expect(importer).to receive(:remove_import_file).and_call_original
+
+ importer.execute
+
+ expect(project.import_export_upload.import_file&.file).to be_nil
+ end
+
it 'sets the correct visibility_level when visibility level is a string' do
project.create_or_update_import_data(
data: { override_params: { visibility_level: Gitlab::VisibilityLevel::PRIVATE.to_s } }
@@ -85,7 +95,6 @@ describe Gitlab::ImportExport::Importer do
allow(subject).to receive(:import_file).and_return(true)
allow(subject).to receive(:check_version!).and_return(true)
allow(subject).to receive(:restorers).and_return(restorers)
- allow(restorers).to receive(:all?).and_return(true)
allow(project).to receive(:import_data).and_return(double(data: { 'original_path' => existing_project.path }))
end
diff --git a/spec/lib/gitlab/import_export/model_configuration_spec.rb b/spec/lib/gitlab/import_export/model_configuration_spec.rb
index 5cb8f2589c8..2e28f978c3a 100644
--- a/spec/lib/gitlab/import_export/model_configuration_spec.rb
+++ b/spec/lib/gitlab/import_export/model_configuration_spec.rb
@@ -16,14 +16,30 @@ describe 'Import/Export model configuration' do
# - User, Author... Models we do not care about for checking models
names.flatten.uniq - %w(milestones labels user author) + ['project']
end
+ let(:ce_models_yml) { 'spec/lib/gitlab/import_export/all_models.yml' }
+ let(:ce_models_hash) { YAML.load_file(ce_models_yml) }
+
+ let(:ee_models_yml) { 'ee/spec/lib/gitlab/import_export/all_models.yml' }
+ let(:ee_models_hash) { File.exist?(ee_models_yml) ? YAML.load_file(ee_models_yml) : {} }
- let(:all_models_yml) { 'spec/lib/gitlab/import_export/all_models.yml' }
- let(:all_models) { YAML.load_file(all_models_yml) }
let(:current_models) { setup_models }
+ let(:all_models_hash) do
+ all_models_hash = ce_models_hash.dup
+
+ all_models_hash.each do |model, associations|
+ associations.concat(ee_models_hash[model] || [])
+ end
+
+ ee_models_hash.each do |model, associations|
+ all_models_hash[model] ||= associations
+ end
+
+ all_models_hash
+ end
it 'has no new models' do
model_names.each do |model_name|
- new_models = Array(current_models[model_name]) - Array(all_models[model_name])
+ new_models = Array(current_models[model_name]) - Array(all_models_hash[model_name])
expect(new_models).to be_empty, failure_message(model_name.classify, new_models)
end
end
@@ -31,27 +47,21 @@ describe 'Import/Export model configuration' do
# List of current models between models, in the format of
# {model: [model_2, model3], ...}
def setup_models
- all_models_hash = {}
-
- model_names.each do |model_name|
- model_class = relation_class_for_name(model_name)
-
- all_models_hash[model_name] = associations_for(model_class) - ['project']
+ model_names.each_with_object({}) do |model_name, hash|
+ hash[model_name] = associations_for(relation_class_for_name(model_name)) - ['project']
end
-
- all_models_hash
end
def failure_message(parent_model_name, new_models)
- <<-MSG
+ <<~MSG
New model(s) <#{new_models.join(',')}> have been added, related to #{parent_model_name}, which is exported by
the Import/Export feature.
- If you think this model should be included in the export, please add it to IMPORT_EXPORT_CONFIG.
- Definitely add it to MODELS_JSON to signal that you've handled this error and to prevent it from showing up in the future.
+ If you think this model should be included in the export, please add it to `#{Gitlab::ImportExport.config_file}`.
- MODELS_JSON: #{File.expand_path(all_models_yml)}
- IMPORT_EXPORT_CONFIG: #{Gitlab::ImportExport.config_file}
+ Definitely add it to `#{File.expand_path(ce_models_yml)}`
+ #{"or `#{File.expand_path(ee_models_yml)}` if the model/associations are EE-specific\n" if ee_models_hash.any?}
+ to signal that you've handled this error and to prevent it from showing up in the future.
MSG
end
end
diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json
index 1b7fa11cb3c..eefd00e7383 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -331,6 +331,28 @@
},
"events": []
}
+ ],
+ "resource_label_events": [
+ {
+ "id":244,
+ "action":"remove",
+ "issue_id":40,
+ "merge_request_id":null,
+ "label_id":2,
+ "user_id":1,
+ "created_at":"2018-08-28T08:24:00.494Z",
+ "label": {
+ "id": 2,
+ "title": "test2",
+ "color": "#428bca",
+ "project_id": 8,
+ "created_at": "2016-07-22T08:55:44.161Z",
+ "updated_at": "2016-07-22T08:55:44.161Z",
+ "template": false,
+ "description": "",
+ "type": "ProjectLabel"
+ }
+ }
]
},
{
@@ -2515,6 +2537,17 @@
"events": []
}
],
+ "resource_label_events": [
+ {
+ "id":243,
+ "action":"add",
+ "issue_id":null,
+ "merge_request_id":27,
+ "label_id":null,
+ "user_id":1,
+ "created_at":"2018-08-28T08:24:00.494Z"
+ }
+ ],
"merge_request_diff": {
"id": 27,
"state": "collected",
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index a88ac0a091e..3ff6be595a8 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -89,6 +89,14 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
expect(ProtectedTag.first.create_access_levels).not_to be_empty
end
+ it 'restores issue resource label events' do
+ expect(Issue.find_by(title: 'Voluptatem').resource_label_events).not_to be_empty
+ end
+
+ it 'restores merge requests resource label events' do
+ expect(MergeRequest.find_by(title: 'MR1').resource_label_events).not_to be_empty
+ end
+
context 'event at forth level of the tree' do
let(:event) { Event.where(action: 6).first }
diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
index fec8a2af9ab..5dc372263ad 100644
--- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
@@ -169,6 +169,14 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
expect(priorities.flatten).not_to be_empty
end
+ it 'has issue resource label events' do
+ expect(saved_project_json['issues'].first['resource_label_events']).not_to be_empty
+ end
+
+ it 'has merge request resource label events' do
+ expect(saved_project_json['merge_requests'].first['resource_label_events']).not_to be_empty
+ end
+
it 'saves the correct service type' do
expect(saved_project_json['services'].first['type']).to eq('CustomIssueTrackerService')
end
@@ -291,6 +299,9 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
project: project,
commit_id: ci_build.pipeline.sha)
+ create(:resource_label_event, label: project_label, issue: issue)
+ create(:resource_label_event, label: group_label, merge_request: merge_request)
+
create(:event, :created, target: milestone, project: project, author: user)
create(:service, project: project, type: 'CustomIssueTrackerService', category: 'issue_tracker', properties: { one: 'value' })
diff --git a/spec/lib/gitlab/import_export/repo_restorer_spec.rb b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
index 7ffa84f906d..8a699eb1461 100644
--- a/spec/lib/gitlab/import_export/repo_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe Gitlab::ImportExport::RepoRestorer do
+ include GitHelpers
+
describe 'bundle a project Git repo' do
let(:user) { create(:user) }
let!(:project_with_repo) { create(:project, :repository, name: 'test-repo-restorer', path: 'test-repo-restorer') }
@@ -36,9 +38,7 @@ describe Gitlab::ImportExport::RepoRestorer do
it 'has the webhooks' do
restorer.restore
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- expect(Gitlab::Git::Hook.new('post-receive', project.repository.raw_repository)).to exist
- end
+ expect(project_hook_exists?(project)).to be true
end
end
end
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 579f175c4a8..e9f1be172b0 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -555,6 +555,19 @@ ProjectCustomAttribute:
- project_id
- key
- value
+PrometheusMetric:
+- id
+- created_at
+- updated_at
+- project_id
+- y_label
+- unit
+- legend
+- title
+- query
+- group
+- common
+- identifier
Badge:
- id
- link_url
@@ -566,3 +579,11 @@ Badge:
- type
ProjectCiCdSetting:
- group_runners_enabled
+ResourceLabelEvent:
+- id
+- action
+- issue_id
+- merge_request_id
+- label_id
+- user_id
+- created_at
diff --git a/spec/lib/gitlab/import_export/saver_spec.rb b/spec/lib/gitlab/import_export/saver_spec.rb
index 02f1a4b81aa..d185ff2dfcc 100644
--- a/spec/lib/gitlab/import_export/saver_spec.rb
+++ b/spec/lib/gitlab/import_export/saver_spec.rb
@@ -18,26 +18,12 @@ describe Gitlab::ImportExport::Saver do
FileUtils.rm_rf(export_path)
end
- context 'local archive' do
- it 'saves the repo to disk' do
- stub_feature_flags(import_export_object_storage: false)
+ it 'saves the repo using object storage' do
+ stub_uploads_object_storage(ImportExportUploader)
- subject.save
+ subject.save
- expect(shared.errors).to be_empty
- expect(Dir.empty?(shared.archive_path)).to be false
- end
- end
-
- context 'object storage' do
- it 'saves the repo using object storage' do
- stub_feature_flags(import_export_object_storage: true)
- stub_uploads_object_storage(ImportExportUploader)
-
- subject.save
-
- expect(ImportExportUpload.find_by(project: project).export_file.url)
- .to match(%r[\/uploads\/-\/system\/import_export_upload\/export_file.*])
- end
+ expect(ImportExportUpload.find_by(project: project).export_file.url)
+ .to match(%r[\/uploads\/-\/system\/import_export_upload\/export_file.*])
end
end
diff --git a/spec/lib/gitlab/import_export/uploads_manager_spec.rb b/spec/lib/gitlab/import_export/uploads_manager_spec.rb
index f799de18cd0..792117e1df1 100644
--- a/spec/lib/gitlab/import_export/uploads_manager_spec.rb
+++ b/spec/lib/gitlab/import_export/uploads_manager_spec.rb
@@ -4,6 +4,7 @@ describe Gitlab::ImportExport::UploadsManager do
let(:shared) { project.import_export_shared }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:project) { create(:project) }
+ let(:upload) { create(:upload, :issuable_upload, :object_storage, model: project) }
let(:exported_file_path) { "#{shared.export_path}/uploads/#{upload.secret}/#{File.basename(upload.path)}" }
subject(:manager) { described_class.new(project: project, shared: shared) }
@@ -69,44 +70,20 @@ describe Gitlab::ImportExport::UploadsManager do
end
end
end
+ end
- context 'using object storage' do
- let!(:upload) { create(:upload, :issuable_upload, :object_storage, model: project) }
-
- before do
- stub_feature_flags(import_export_object_storage: true)
- stub_uploads_object_storage(FileUploader)
- end
-
- it 'saves the file' do
- fake_uri = double
-
- expect(fake_uri).to receive(:open).and_return(StringIO.new('File content'))
- expect(URI).to receive(:parse).and_return(fake_uri)
-
- manager.save
+ describe '#restore' do
+ before do
+ stub_uploads_object_storage(FileUploader)
- expect(File.read(exported_file_path)).to eq('File content')
- end
+ FileUtils.mkdir_p(File.join(shared.export_path, 'uploads/72a497a02fe3ee09edae2ed06d390038'))
+ FileUtils.touch(File.join(shared.export_path, 'uploads/72a497a02fe3ee09edae2ed06d390038', "dummy.txt"))
end
- describe '#restore' do
- context 'using object storage' do
- before do
- stub_feature_flags(import_export_object_storage: true)
- stub_uploads_object_storage(FileUploader)
-
- FileUtils.mkdir_p(File.join(shared.export_path, 'uploads/72a497a02fe3ee09edae2ed06d390038'))
- FileUtils.touch(File.join(shared.export_path, 'uploads/72a497a02fe3ee09edae2ed06d390038', "dummy.txt"))
- end
+ it 'restores the file' do
+ manager.restore
- it 'restores the file' do
- manager.restore
-
- expect(project.uploads.size).to eq(1)
- expect(project.uploads.first.build_uploader.filename).to eq('dummy.txt')
- end
- end
+ expect(project.uploads.map { |u| u.build_uploader.filename }).to include('dummy.txt')
end
end
end
diff --git a/spec/lib/gitlab/import_export/uploads_restorer_spec.rb b/spec/lib/gitlab/import_export/uploads_restorer_spec.rb
index acef97459b8..6072f18b8c7 100644
--- a/spec/lib/gitlab/import_export/uploads_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/uploads_restorer_spec.rb
@@ -8,7 +8,7 @@ describe Gitlab::ImportExport::UploadsRestorer do
before do
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
FileUtils.mkdir_p(File.join(shared.export_path, 'uploads/random'))
- FileUtils.touch(File.join(shared.export_path, 'uploads/random', "dummy.txt"))
+ FileUtils.touch(File.join(shared.export_path, 'uploads/random', 'dummy.txt'))
end
after do
@@ -27,9 +27,7 @@ describe Gitlab::ImportExport::UploadsRestorer do
it 'copies the uploads to the project path' do
subject.restore
- uploads = Dir.glob(File.join(subject.uploads_path, '**/*')).map { |file| File.basename(file) }
-
- expect(uploads).to include('dummy.txt')
+ expect(project.uploads.map { |u| u.build_uploader.filename }).to include('dummy.txt')
end
end
@@ -45,9 +43,7 @@ describe Gitlab::ImportExport::UploadsRestorer do
it 'copies the uploads to the project path' do
subject.restore
- uploads = Dir.glob(File.join(subject.uploads_path, '**/*')).map { |file| File.basename(file) }
-
- expect(uploads).to include('dummy.txt')
+ expect(project.uploads.map { |u| u.build_uploader.filename }).to include('dummy.txt')
end
end
end
diff --git a/spec/lib/gitlab/import_export/uploads_saver_spec.rb b/spec/lib/gitlab/import_export/uploads_saver_spec.rb
index c716edd9397..24993460e51 100644
--- a/spec/lib/gitlab/import_export/uploads_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/uploads_saver_spec.rb
@@ -7,7 +7,6 @@ describe Gitlab::ImportExport::UploadsSaver do
let(:shared) { project.import_export_shared }
before do
- stub_feature_flags(import_export_object_storage: false)
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
end
diff --git a/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb b/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb
index 5589db92b1d..1a108003bc2 100644
--- a/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb
+++ b/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb
@@ -6,7 +6,7 @@ describe Gitlab::Prometheus::AdditionalMetricsParser do
let(:parser_error_class) { Gitlab::Prometheus::ParsingError }
describe '#load_groups_from_yaml' do
- subject { described_class.load_groups_from_yaml }
+ subject { described_class.load_groups_from_yaml('dummy.yaml') }
describe 'parsing sample yaml' do
let(:sample_yaml) do
diff --git a/spec/lib/gitlab/prometheus/metric_group_spec.rb b/spec/lib/gitlab/prometheus/metric_group_spec.rb
new file mode 100644
index 00000000000..e7d16e73663
--- /dev/null
+++ b/spec/lib/gitlab/prometheus/metric_group_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Gitlab::Prometheus::MetricGroup do
+ describe '.common_metrics' do
+ let!(:project_metric) { create(:prometheus_metric) }
+ let!(:common_metric_group_a) { create(:prometheus_metric, :common, group: :aws_elb) }
+ let!(:common_metric_group_b_q1) { create(:prometheus_metric, :common, group: :kubernetes) }
+ let!(:common_metric_group_b_q2) { create(:prometheus_metric, :common, group: :kubernetes) }
+
+ subject { described_class.common_metrics }
+
+ it 'returns exactly two groups' do
+ expect(subject.map(&:name)).to contain_exactly(
+ 'Response metrics (AWS ELB)', 'System metrics (Kubernetes)')
+ end
+
+ it 'returns exactly three metric queries' do
+ expect(subject.map(&:metrics).flatten.map(&:id)).to contain_exactly(
+ common_metric_group_a.id, common_metric_group_b_q1.id,
+ common_metric_group_b_q2.id)
+ end
+ end
+
+ describe '.for_project' do
+ let!(:other_project) { create(:project) }
+ let!(:project_metric) { create(:prometheus_metric) }
+ let!(:common_metric) { create(:prometheus_metric, :common, group: :aws_elb) }
+
+ subject do
+ described_class.for_project(other_project)
+ .map(&:metrics).flatten
+ .map(&:id)
+ end
+
+ it 'returns exactly one common metric' do
+ is_expected.to contain_exactly(common_metric.id)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb
index f8bf896950e..b1b7c427313 100644
--- a/spec/lib/gitlab/shell_spec.rb
+++ b/spec/lib/gitlab/shell_spec.rb
@@ -7,15 +7,10 @@ describe Gitlab::Shell do
let(:repository) { project.repository }
let(:gitlab_shell) { described_class.new }
let(:popen_vars) { { 'GIT_TERMINAL_PROMPT' => ENV['GIT_TERMINAL_PROMPT'] } }
- let(:gitlab_projects) { double('gitlab_projects') }
let(:timeout) { Gitlab.config.gitlab_shell.git_timeout }
before do
allow(Project).to receive(:find).and_return(project)
-
- allow(gitlab_shell).to receive(:gitlab_projects)
- .with(project.repository_storage, project.disk_path + '.git')
- .and_return(gitlab_projects)
end
it { is_expected.to respond_to :add_key }
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index 23869f3d2da..b3f55a2e1bd 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -336,6 +336,22 @@ describe Gitlab::Workhorse do
it { expect { subject }.to raise_exception('Unsupported action: download') }
end
end
+
+ context 'when receive_max_input_size has been updated' do
+ it 'returns custom git config' do
+ allow(Gitlab::CurrentSettings).to receive(:receive_max_input_size) { 1 }
+
+ expect(subject[:GitConfigOptions]).to be_present
+ end
+ end
+
+ context 'when receive_max_input_size is empty' do
+ it 'returns an empty git config' do
+ allow(Gitlab::CurrentSettings).to receive(:receive_max_input_size) { nil }
+
+ expect(subject[:GitConfigOptions]).to be_empty
+ end
+ end
end
describe '.set_key_and_notify' do
diff --git a/spec/mailers/emails/auto_devops_spec.rb b/spec/mailers/emails/auto_devops_spec.rb
new file mode 100644
index 00000000000..839caf3f50e
--- /dev/null
+++ b/spec/mailers/emails/auto_devops_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Emails::AutoDevops do
+ include EmailSpec::Matchers
+
+ describe '#auto_devops_disabled_email' do
+ let(:owner) { create(:user) }
+ let(:namespace) { create(:namespace, owner: owner) }
+ let(:project) { create(:project, :repository, :auto_devops) }
+ let(:pipeline) { create(:ci_pipeline, :failed, project: project) }
+
+ subject { Notify.autodevops_disabled_email(pipeline, owner.email) }
+
+ it 'sents email with correct subject' do
+ is_expected.to have_subject("#{project.name} | Auto DevOps pipeline was disabled for #{project.name}")
+ end
+
+ it 'sents an email to the user' do
+ recipient = subject.header[:to].addrs.map(&:address).first
+
+ expect(recipient).to eq(owner.email)
+ end
+
+ it 'is sent as GitLab email' do
+ sender = subject.header[:from].addrs[0].address
+
+ expect(sender).to match(/gitlab/)
+ end
+ end
+end
diff --git a/spec/migrations/import_common_metrics_spec.rb b/spec/migrations/import_common_metrics_spec.rb
new file mode 100644
index 00000000000..1001629007c
--- /dev/null
+++ b/spec/migrations/import_common_metrics_spec.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20180831164910_import_common_metrics.rb')
+
+describe ImportCommonMetrics, :migration do
+ describe '#up' do
+ it "imports all prometheus metrics" do
+ expect(PrometheusMetric.common).to be_empty
+
+ migrate!
+
+ expect(PrometheusMetric.common).not_to be_empty
+ end
+ end
+end
diff --git a/spec/migrations/remove_orphaned_label_links_spec.rb b/spec/migrations/remove_orphaned_label_links_spec.rb
new file mode 100644
index 00000000000..13b8919343e
--- /dev/null
+++ b/spec/migrations/remove_orphaned_label_links_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20180906051323_remove_orphaned_label_links.rb')
+
+describe RemoveOrphanedLabelLinks, :migration do
+ let(:label_links) { table(:label_links) }
+ let(:labels) { table(:labels) }
+
+ let(:project) { create(:project) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let(:label) { create_label }
+
+ context 'add foreign key on label_id' do
+ let!(:label_link_with_label) { create_label_link(label_id: label.id) }
+ let!(:label_link_without_label) { create_label_link(label_id: nil) }
+
+ it 'removes orphaned labels without corresponding label' do
+ expect { migrate! }.to change { LabelLink.count }.from(2).to(1)
+ end
+
+ it 'does not remove entries with valid label_id' do
+ expect { migrate! }.not_to change { label_link_with_label.reload }
+ end
+ end
+
+ def create_label(**opts)
+ labels.create!(
+ project_id: project.id,
+ **opts
+ )
+ end
+
+ def create_label_link(**opts)
+ label_links.create!(
+ target_id: 1,
+ target_type: 'Issue',
+ **opts
+ )
+ end
+end
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 483cc546423..9647c1b9f63 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -305,6 +305,36 @@ describe ApplicationSetting do
end
end
+ describe 'setting Sentry DSNs' do
+ context 'server DSN' do
+ it 'strips leading and trailing whitespace' do
+ subject.update(sentry_dsn: ' http://test ')
+
+ expect(subject.sentry_dsn).to eq('http://test')
+ end
+
+ it 'handles nil values' do
+ subject.update(sentry_dsn: nil)
+
+ expect(subject.sentry_dsn).to be_nil
+ end
+ end
+
+ context 'client-side DSN' do
+ it 'strips leading and trailing whitespace' do
+ subject.update(clientside_sentry_dsn: ' http://test ')
+
+ expect(subject.clientside_sentry_dsn).to eq('http://test')
+ end
+
+ it 'handles nil values' do
+ subject.update(clientside_sentry_dsn: nil)
+
+ expect(subject.clientside_sentry_dsn).to be_nil
+ end
+ end
+ end
+
describe '#disabled_oauth_sign_in_sources=' do
before do
allow(Devise).to receive(:omniauth_providers).and_return([:github])
diff --git a/spec/models/blob_viewer/gitlab_ci_yml_spec.rb b/spec/models/blob_viewer/gitlab_ci_yml_spec.rb
index bed364a8c14..01c555a7a90 100644
--- a/spec/models/blob_viewer/gitlab_ci_yml_spec.rb
+++ b/spec/models/blob_viewer/gitlab_ci_yml_spec.rb
@@ -2,22 +2,24 @@ require 'spec_helper'
describe BlobViewer::GitlabCiYml do
include FakeBlobHelpers
+ include RepoHelpers
- let(:project) { build_stubbed(:project) }
+ let(:project) { create(:project, :repository) }
let(:data) { File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) }
let(:blob) { fake_blob(path: '.gitlab-ci.yml', data: data) }
+ let(:sha) { sample_commit.id }
subject { described_class.new(blob) }
describe '#validation_message' do
it 'calls prepare! on the viewer' do
expect(subject).to receive(:prepare!)
- subject.validation_message
+ subject.validation_message(project, sha)
end
context 'when the configuration is valid' do
it 'returns nil' do
- expect(subject.validation_message).to be_nil
+ expect(subject.validation_message(project, sha)).to be_nil
end
end
@@ -25,7 +27,7 @@ describe BlobViewer::GitlabCiYml do
let(:data) { 'oof' }
it 'returns the error message' do
- expect(subject.validation_message).to eq('Invalid configuration format')
+ expect(subject.validation_message(project, sha)).to eq('Invalid configuration format')
end
end
end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 77b7332a761..2216705c032 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -1743,7 +1743,7 @@ describe Ci::Pipeline, :mailer do
create(:ci_pipeline, config: { rspec: { script: 'rake test' } })
end
- it 'does not containyaml errors' do
+ it 'does not contain yaml errors' do
expect(pipeline).not_to have_yaml_errors
end
end
@@ -1941,4 +1941,28 @@ describe Ci::Pipeline, :mailer do
expect(pipeline.total_size).to eq(5)
end
end
+
+ describe '#status' do
+ context 'when transitioning to failed' do
+ context 'when pipeline has autodevops as source' do
+ let(:pipeline) { create(:ci_pipeline, :running, :auto_devops_source) }
+
+ it 'calls autodevops disable service' do
+ expect(AutoDevops::DisableWorker).to receive(:perform_async).with(pipeline.id)
+
+ pipeline.drop
+ end
+ end
+
+ context 'when pipeline has other source' do
+ let(:pipeline) { create(:ci_pipeline, :running, :repository_source) }
+
+ it 'does not call auto devops disable service' do
+ expect(AutoDevops::DisableWorker).not_to receive(:perform_async)
+
+ pipeline.drop
+ end
+ end
+ end
+ end
end
diff --git a/spec/models/clusters/applications/jupyter_spec.rb b/spec/models/clusters/applications/jupyter_spec.rb
index 591a01d78a9..44a64928e94 100644
--- a/spec/models/clusters/applications/jupyter_spec.rb
+++ b/spec/models/clusters/applications/jupyter_spec.rb
@@ -108,8 +108,21 @@ describe Clusters::Applications::Jupyter do
expect(values).to include('rbac')
expect(values).to include('proxy')
expect(values).to include('auth')
+ expect(values).to include('singleuser')
expect(values).to match(/clientId: '?#{application.oauth_application.uid}/)
expect(values).to match(/callbackUrl: '?#{application.callback_url}/)
end
+
+ context 'when cluster belongs to a project' do
+ let(:project) { create(:project) }
+
+ before do
+ application.cluster.projects << project
+ end
+
+ it 'sets GitLab project id' do
+ expect(values).to match(/GITLAB_PROJECT_ID: '?#{project.id}/)
+ end
+ end
end
end
diff --git a/spec/models/label_note_spec.rb b/spec/models/label_note_spec.rb
new file mode 100644
index 00000000000..f69874d94aa
--- /dev/null
+++ b/spec/models/label_note_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe LabelNote do
+ set(:project) { create(:project, :repository) }
+ set(:user) { create(:user) }
+ set(:label) { create(:label, project: project) }
+ set(:label2) { create(:label, project: project) }
+ let(:resource_parent) { project }
+
+ context 'when resource is issue' do
+ set(:resource) { create(:issue, project: project) }
+
+ it_behaves_like 'label note created from events'
+ end
+
+ context 'when resource is merge request' do
+ set(:resource) { create(:merge_request, source_project: project, target_project: project) }
+
+ it_behaves_like 'label note created from events'
+ end
+end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 9b7f932ec3a..3649990670b 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -394,12 +394,6 @@ describe Namespace do
child.destroy
end
end
-
- it 'removes the exports folder' do
- expect(namespace).to receive(:remove_exports!)
-
- namespace.destroy
- end
end
context 'hashed storage' do
@@ -414,12 +408,6 @@ describe Namespace do
expect(File.exist?(deleted_path_in_dir)).to be(false)
end
-
- it 'removes the exports folder' do
- expect(namespace).to receive(:remove_exports!)
-
- namespace.destroy
- end
end
end
@@ -706,26 +694,6 @@ describe Namespace do
end
end
- describe '#remove_exports' do
- let(:legacy_project) { create(:project, :with_export, :legacy_storage, namespace: namespace) }
- let(:hashed_project) { create(:project, :with_export, namespace: namespace) }
- let(:export_path) { Dir.mktmpdir('namespace_remove_exports_spec') }
- let(:legacy_export) { legacy_project.export_project_path }
- let(:hashed_export) { hashed_project.export_project_path }
-
- it 'removes exports for legacy and hashed projects' do
- allow(Gitlab::ImportExport).to receive(:storage_path) { export_path }
-
- expect(File.exist?(legacy_export)).to be_truthy
- expect(File.exist?(hashed_export)).to be_truthy
-
- namespace.remove_exports!
-
- expect(File.exist?(legacy_export)).to be_falsy
- expect(File.exist?(hashed_export)).to be_falsy
- end
- end
-
describe '#full_path_was' do
context 'when the group has no parent' do
it 'should return the path was' do
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index 54f1a0e38a5..788b3179b01 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -231,12 +231,12 @@ describe JiraService do
end
it 'logs exception when transition id is not valid' do
- allow(Rails.logger).to receive(:info)
- WebMock.stub_request(:post, @transitions_url).with(basic_auth: %w(gitlab_jira_username gitlab_jira_password)).and_raise('Bad Request')
+ allow(@jira_service).to receive(:log_error)
+ WebMock.stub_request(:post, @transitions_url).with(basic_auth: %w(gitlab_jira_username gitlab_jira_password)).and_raise("Bad Request")
@jira_service.close_issue(resource, ExternalIssue.new('JIRA-123', project))
- expect(Rails.logger).to have_received(:info).with('JiraService Issue Transition failed message ERROR: http://jira.example.com - Bad Request')
+ expect(@jira_service).to have_received(:log_error).with("Issue transition failed", error: "Bad Request", client_url: "http://jira.example.com")
end
it 'calls the api with jira_issue_transition_id' do
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 264632dba4b..dfe2de71a76 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -2854,73 +2854,12 @@ describe Project do
end
describe '#remove_export' do
- let(:legacy_project) { create(:project, :legacy_storage, :with_export) }
let(:project) { create(:project, :with_export) }
- before do
- stub_feature_flags(import_export_object_storage: false)
- end
-
- it 'removes the exports directory for the project' do
- expect(File.exist?(project.export_path)).to be_truthy
-
- allow(FileUtils).to receive(:rm_rf).and_call_original
- expect(FileUtils).to receive(:rm_rf).with(project.export_path).and_call_original
- project.remove_exports
-
- expect(File.exist?(project.export_path)).to be_falsy
- end
-
- it 'is a no-op on legacy projects when there is no namespace' do
- export_path = legacy_project.export_path
-
- legacy_project.namespace.delete
- legacy_project.reload
-
- expect(FileUtils).not_to receive(:rm_rf).with(export_path)
-
- legacy_project.remove_exports
-
- expect(File.exist?(export_path)).to be_truthy
- end
-
- it 'runs on hashed storage projects when there is no namespace' do
- export_path = project.export_path
-
- project.namespace.delete
- legacy_project.reload
-
- allow(FileUtils).to receive(:rm_rf).and_call_original
- expect(FileUtils).to receive(:rm_rf).with(export_path).and_call_original
-
+ it 'removes the export' do
project.remove_exports
- expect(File.exist?(export_path)).to be_falsy
- end
-
- it 'is run when the project is destroyed' do
- expect(project).to receive(:remove_exports).and_call_original
-
- project.destroy
- end
- end
-
- describe '#remove_exported_project_file' do
- let(:project) { create(:project, :with_export) }
-
- it 'removes the exported project file' do
- stub_feature_flags(import_export_object_storage: false)
-
- exported_file = project.export_project_path
-
- expect(File.exist?(exported_file)).to be_truthy
-
- allow(FileUtils).to receive(:rm_rf).and_call_original
- expect(FileUtils).to receive(:rm_rf).with(exported_file).and_call_original
-
- project.remove_exported_project_file
-
- expect(File.exist?(exported_file)).to be_falsy
+ expect(project.export_file_exists?).to be_falsey
end
end
@@ -4044,6 +3983,40 @@ describe Project do
end
end
+ describe '#update_root_ref' do
+ let(:project) { create(:project, :repository) }
+
+ it 'updates the default branch when HEAD has changed' do
+ stub_find_remote_root_ref(project, ref: 'feature')
+
+ expect { project.update_root_ref('origin') }
+ .to change { project.default_branch }
+ .from('master')
+ .to('feature')
+ end
+
+ it 'does not update the default branch when HEAD does not change' do
+ stub_find_remote_root_ref(project, ref: 'master')
+
+ expect { project.update_root_ref('origin') }
+ .not_to change { project.default_branch }
+ end
+
+ it 'does not update the default branch when HEAD does not exist' do
+ stub_find_remote_root_ref(project, ref: 'foo')
+
+ expect { project.update_root_ref('origin') }
+ .not_to change { project.default_branch }
+ end
+
+ def stub_find_remote_root_ref(project, ref:)
+ allow(project.repository)
+ .to receive(:find_remote_root_ref)
+ .with('origin')
+ .and_return(ref)
+ end
+ end
+
def rugged_config
Gitlab::GitalyClient::StorageSettings.allow_disk_access do
project.repository.rugged.config
diff --git a/spec/models/prometheus_metric_spec.rb b/spec/models/prometheus_metric_spec.rb
new file mode 100644
index 00000000000..a83a31ae88c
--- /dev/null
+++ b/spec/models/prometheus_metric_spec.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe PrometheusMetric do
+ subject { build(:prometheus_metric) }
+ let(:other_project) { build(:project) }
+
+ it { is_expected.to belong_to(:project) }
+ it { is_expected.to validate_presence_of(:title) }
+ it { is_expected.to validate_presence_of(:query) }
+ it { is_expected.to validate_presence_of(:group) }
+
+ describe 'common metrics' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:common, :project, :result) do
+ false | other_project | true
+ false | nil | false
+ true | other_project | false
+ true | nil | true
+ end
+
+ with_them do
+ before do
+ subject.common = common
+ subject.project = project
+ end
+
+ it { expect(subject.valid?).to eq(result) }
+ end
+ end
+
+ describe '#query_series' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:legend, :type) do
+ 'Some other legend' | NilClass
+ 'Status Code' | Array
+ end
+
+ with_them do
+ before do
+ subject.legend = legend
+ end
+
+ it { expect(subject.query_series).to be_a(type) }
+ end
+ end
+
+ describe '#group_title' do
+ shared_examples 'group_title' do |group, title|
+ subject { build(:prometheus_metric, group: group).group_title }
+
+ it "returns text #{title} for group #{group}" do
+ expect(subject).to eq(title)
+ end
+ end
+
+ it_behaves_like 'group_title', :business, 'Business metrics (Custom)'
+ it_behaves_like 'group_title', :response, 'Response metrics (Custom)'
+ it_behaves_like 'group_title', :system, 'System metrics (Custom)'
+ end
+
+ describe '#to_query_metric' do
+ it 'converts to queryable metric object' do
+ expect(subject.to_query_metric).to be_instance_of(Gitlab::Prometheus::Metric)
+ end
+
+ it 'queryable metric object has title' do
+ expect(subject.to_query_metric.title).to eq(subject.title)
+ end
+
+ it 'queryable metric object has y_label' do
+ expect(subject.to_query_metric.y_label).to eq(subject.y_label)
+ end
+
+ it 'queryable metric has no required_metric' do
+ expect(subject.to_query_metric.required_metrics).to eq([])
+ end
+
+ it 'queryable metric has weight 0' do
+ expect(subject.to_query_metric.weight).to eq(0)
+ end
+
+ it 'queryable metrics has query description' do
+ queries = [
+ {
+ query_range: subject.query,
+ unit: subject.unit,
+ label: subject.legend
+ }
+ ]
+
+ expect(subject.to_query_metric.queries).to eq(queries)
+ end
+ end
+end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 93898012d34..dffac05152b 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -1028,194 +1028,6 @@ describe Repository do
end
end
- describe '#update_branch_with_hooks' do
- let(:old_rev) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } # git rev-parse feature
- let(:new_rev) { 'a74ae73c1ccde9b974a70e82b901588071dc142a' } # commit whose parent is old_rev
- let(:updating_ref) { 'refs/heads/feature' }
- let(:target_project) { project }
- let(:target_repository) { target_project.repository }
-
- around do |example|
- # TODO Gitlab::Git::OperationService will be moved to gitaly-ruby and disappear from this repo
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- example.run
- end
- end
-
- context 'when pre hooks were successful' do
- before do
- service = Gitlab::Git::HooksService.new
- expect(Gitlab::Git::HooksService).to receive(:new).and_return(service)
- expect(service).to receive(:execute)
- .with(git_user, target_repository.raw_repository, old_rev, new_rev, updating_ref)
- .and_yield(service).and_return(true)
- end
-
- it 'runs without errors' do
- expect do
- Gitlab::Git::OperationService.new(git_user, repository.raw_repository).with_branch('feature') do
- new_rev
- end
- end.not_to raise_error
- end
-
- it 'ensures the autocrlf Git option is set to :input' do
- service = Gitlab::Git::OperationService.new(git_user, repository.raw_repository)
-
- expect(service).to receive(:update_autocrlf_option)
-
- service.with_branch('feature') { new_rev }
- end
-
- context "when the branch wasn't empty" do
- it 'updates the head' do
- expect(repository.find_branch('feature').dereferenced_target.id).to eq(old_rev)
-
- Gitlab::Git::OperationService.new(git_user, repository.raw_repository).with_branch('feature') do
- new_rev
- end
-
- expect(repository.find_branch('feature').dereferenced_target.id).to eq(new_rev)
- end
- end
-
- context 'when target project does not have the commit' do
- let(:target_project) { create(:project, :empty_repo) }
- let(:old_rev) { Gitlab::Git::BLANK_SHA }
- let(:new_rev) { project.commit('feature').sha }
- let(:updating_ref) { 'refs/heads/master' }
-
- it 'fetch_ref and create the branch' do
- expect(target_project.repository.raw_repository).to receive(:fetch_ref)
- .and_call_original
-
- Gitlab::Git::OperationService.new(git_user, target_repository.raw_repository)
- .with_branch(
- 'master',
- start_repository: project.repository.raw_repository,
- start_branch_name: 'feature') { new_rev }
-
- expect(target_repository.branch_names).to contain_exactly('master')
- end
- end
-
- context 'when target project already has the commit' do
- let(:target_project) { create(:project, :repository) }
-
- it 'does not fetch_ref and just pass the commit' do
- expect(target_repository).not_to receive(:fetch_ref)
-
- Gitlab::Git::OperationService.new(git_user, target_repository.raw_repository)
- .with_branch('feature', start_repository: project.repository.raw_repository) { new_rev }
- end
- end
- end
-
- context 'when temporary ref failed to be created from other project' do
- let(:target_project) { create(:project, :empty_repo) }
-
- before do
- expect(target_project.repository.raw_repository).to receive(:run_git)
- end
-
- it 'raises Rugged::ReferenceError' do
- expect do
- Gitlab::Git::OperationService.new(git_user, target_project.repository.raw_repository)
- .with_branch('feature',
- start_repository: project.repository.raw_repository,
- &:itself)
- end.to raise_error(Gitlab::Git::CommandError)
- end
- end
-
- context 'when the update adds more than one commit' do
- let(:old_rev) { '33f3729a45c02fc67d00adb1b8bca394b0e761d9' }
-
- it 'runs without errors' do
- # old_rev is an ancestor of new_rev
- expect(repository.merge_base(old_rev, new_rev)).to eq(old_rev)
-
- # old_rev is not a direct ancestor (parent) of new_rev
- expect(repository.rugged.lookup(new_rev).parent_ids).not_to include(old_rev)
-
- branch = 'feature-ff-target'
- repository.add_branch(user, branch, old_rev)
-
- expect do
- Gitlab::Git::OperationService.new(git_user, repository.raw_repository).with_branch(branch) do
- new_rev
- end
- end.not_to raise_error
- end
- end
-
- context 'when the update would remove commits from the target branch' do
- let(:branch) { 'master' }
- let(:old_rev) { repository.find_branch(branch).dereferenced_target.sha }
-
- it 'raises an exception' do
- # The 'master' branch is NOT an ancestor of new_rev.
- expect(repository.merge_base(old_rev, new_rev)).not_to eq(old_rev)
-
- # Updating 'master' to new_rev would lose the commits on 'master' that
- # are not contained in new_rev. This should not be allowed.
- expect do
- Gitlab::Git::OperationService.new(git_user, repository.raw_repository).with_branch(branch) do
- new_rev
- end
- end.to raise_error(Gitlab::Git::CommitError)
- end
- end
-
- context 'when pre hooks failed' do
- it 'gets an error' do
- allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, ''])
-
- expect do
- Gitlab::Git::OperationService.new(git_user, repository.raw_repository).with_branch('feature') do
- new_rev
- end
- end.to raise_error(Gitlab::Git::PreReceiveError)
- end
- end
-
- context 'when target branch is different from source branch' do
- before do
- allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, ''])
- end
-
- subject do
- Gitlab::Git::OperationService.new(git_user, repository.raw_repository).with_branch('new-feature') do
- new_rev
- end
- end
-
- it 'returns branch_created as true' do
- expect(subject).not_to be_repo_created
- expect(subject).to be_branch_created
- end
- end
-
- context 'when repository is empty' do
- before do
- allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, ''])
- end
-
- it 'expires creation and branch cache' do
- empty_repository = create(:project, :empty_repo).repository
-
- expect(empty_repository).to receive(:expire_exists_cache)
- expect(empty_repository).to receive(:expire_root_ref_cache)
- expect(empty_repository).to receive(:expire_emptiness_caches)
- expect(empty_repository).to receive(:expire_branches_cache)
-
- empty_repository.create_file(user, 'CHANGELOG', 'Changelog!',
- message: 'Updates file content',
- branch_name: 'master')
- end
- end
- end
-
describe '#exists?' do
it 'returns true when a repository exists' do
expect(repository.exists?).to be(true)
@@ -1298,40 +1110,6 @@ describe Repository do
end
end
- describe '#update_autocrlf_option' do
- around do |example|
- # TODO Gitlab::Git::OperationService will be moved to gitaly-ruby and disappear from this repo
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- example.run
- end
- end
-
- describe 'when autocrlf is not already set to :input' do
- before do
- repository.raw_repository.autocrlf = true
- end
-
- it 'sets autocrlf to :input' do
- Gitlab::Git::OperationService.new(nil, repository.raw_repository).send(:update_autocrlf_option)
-
- expect(repository.raw_repository.autocrlf).to eq(:input)
- end
- end
-
- describe 'when autocrlf is already set to :input' do
- before do
- repository.raw_repository.autocrlf = :input
- end
-
- it 'does nothing' do
- expect(repository.raw_repository).not_to receive(:autocrlf=)
- .with(:input)
-
- Gitlab::Git::OperationService.new(nil, repository.raw_repository).send(:update_autocrlf_option)
- end
- end
- end
-
describe '#empty?' do
let(:empty_repository) { create(:project_empty_repo).repository }
@@ -2025,27 +1803,6 @@ describe Repository do
end
end
- describe '#update_ref' do
- around do |example|
- # TODO Gitlab::Git::OperationService will be moved to gitaly-ruby and disappear from this repo
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- example.run
- end
- end
-
- it 'can create a ref' do
- Gitlab::Git::OperationService.new(nil, repository.raw_repository).send(:update_ref, 'refs/heads/foobar', 'refs/heads/master', Gitlab::Git::BLANK_SHA)
-
- expect(repository.find_branch('foobar')).not_to be_nil
- end
-
- it 'raises CommitError when the ref update fails' do
- expect do
- Gitlab::Git::OperationService.new(nil, repository.raw_repository).send(:update_ref, 'refs/heads/master', 'refs/heads/master', Gitlab::Git::BLANK_SHA)
- end.to raise_error(Gitlab::Git::CommitError)
- end
- end
-
describe '#contribution_guide', :use_clean_rails_memory_store_caching do
it 'returns and caches the output' do
expect(repository).to receive(:file_on_head)
diff --git a/spec/models/resource_label_event_spec.rb b/spec/models/resource_label_event_spec.rb
index 4756caa1b97..da6e1b5610d 100644
--- a/spec/models/resource_label_event_spec.rb
+++ b/spec/models/resource_label_event_spec.rb
@@ -3,7 +3,7 @@
require 'rails_helper'
RSpec.describe ResourceLabelEvent, type: :model do
- subject { build(:resource_label_event) }
+ subject { build(:resource_label_event, issue: issue) }
let(:issue) { create(:issue) }
let(:merge_request) { create(:merge_request) }
@@ -16,8 +16,6 @@ RSpec.describe ResourceLabelEvent, type: :model do
describe 'validations' do
it { is_expected.to be_valid }
- it { is_expected.to validate_presence_of(:label) }
- it { is_expected.to validate_presence_of(:user) }
describe 'Issuable validation' do
it 'is invalid if issue_id and merge_request_id are missing' do
@@ -45,4 +43,52 @@ RSpec.describe ResourceLabelEvent, type: :model do
end
end
end
+
+ describe '#expire_etag_cache' do
+ def expect_expiration(issue)
+ expect_any_instance_of(Gitlab::EtagCaching::Store)
+ .to receive(:touch)
+ .with("/#{issue.project.namespace.to_param}/#{issue.project.to_param}/noteable/issue/#{issue.id}/notes")
+ end
+
+ it 'expires resource note etag cache on event save' do
+ expect_expiration(subject.issuable)
+
+ subject.save!
+ end
+
+ it 'expires resource note etag cache on event destroy' do
+ subject.save!
+
+ expect_expiration(subject.issuable)
+
+ subject.destroy!
+ end
+ end
+
+ describe '#outdated_markdown?' do
+ it 'returns true if label is missing and reference is not empty' do
+ subject.attributes = { reference: 'ref', label_id: nil }
+
+ expect(subject.outdated_markdown?).to be true
+ end
+
+ it 'returns true if reference is not set yet' do
+ subject.attributes = { reference: nil }
+
+ expect(subject.outdated_markdown?).to be true
+ end
+
+ it 'returns true markdown is outdated' do
+ subject.attributes = { cached_markdown_version: 0 }
+
+ expect(subject.outdated_markdown?).to be true
+ end
+
+ it 'returns false if label and reference are set' do
+ subject.attributes = { reference: 'whatever', cached_markdown_version: CacheMarkdownField::CACHE_COMMONMARK_VERSION }
+
+ expect(subject.outdated_markdown?).to be false
+ end
+ end
end
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index 029ad7f3e9f..25eecb3f909 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -345,4 +345,31 @@ describe Service do
expect(service.api_field_names).to eq(['safe_field'])
end
end
+
+ context 'logging' do
+ let(:project) { create(:project) }
+ let(:service) { create(:service, project: project) }
+ let(:test_message) { "test message" }
+ let(:arguments) do
+ {
+ service_class: service.class.name,
+ project_path: project.full_path,
+ project_id: project.id,
+ message: test_message,
+ additional_argument: 'some argument'
+ }
+ end
+
+ it 'logs info messages using json logger' do
+ expect(Gitlab::JsonLogger).to receive(:info).with(arguments)
+
+ service.log_info(test_message, additional_argument: 'some argument')
+ end
+
+ it 'logs error messages using json logger' do
+ expect(Gitlab::JsonLogger).to receive(:error).with(arguments)
+
+ service.log_error(test_message, additional_argument: 'some argument')
+ end
+ end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 2a7aff39240..bee4a3d24a7 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -2957,6 +2957,48 @@ describe User do
end
end
+ describe '#requires_usage_stats_consent?' do
+ let(:user) { create(:user, created_at: 8.days.ago) }
+
+ before do
+ allow(user).to receive(:has_current_license?).and_return false
+ end
+
+ context 'in single-user environment' do
+ it 'requires user consent after one week' do
+ create(:user, ghost: true)
+
+ expect(user.requires_usage_stats_consent?).to be true
+ end
+
+ it 'requires user consent after one week if there is another ghost user' do
+ expect(user.requires_usage_stats_consent?).to be true
+ end
+
+ it 'does not require consent in the first week' do
+ user.created_at = 6.days.ago
+
+ expect(user.requires_usage_stats_consent?).to be false
+ end
+
+ it 'does not require consent if usage stats were set by this user' do
+ allow(Gitlab::CurrentSettings).to receive(:usage_stats_set_by_user_id).and_return(user.id)
+
+ expect(user.requires_usage_stats_consent?).to be false
+ end
+ end
+
+ context 'in multi-user environment' do
+ before do
+ create(:user)
+ end
+
+ it 'does not require consent' do
+ expect(user.requires_usage_stats_consent?).to be false
+ end
+ end
+ end
+
context 'with uploads' do
it_behaves_like 'model with mounted uploader', false do
let(:model_object) { create(:user, :with_avatar) }
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 246947e58a8..d5b31610dad 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -1040,6 +1040,14 @@ describe API::Commits do
end
end
+ context 'when branch is empty' do
+ ['', ' '].each do |branch|
+ it_behaves_like '400 response' do
+ let(:request) { post api(route, current_user), branch: branch }
+ end
+ end
+ end
+
context 'when branch does not exist' do
it_behaves_like '404 response' do
let(:request) { post api(route, current_user), branch: 'foo' }
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index 6890f46c724..e0b5b34f9c4 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -369,6 +369,26 @@ describe API::Internal do
expect(user.reload.last_activity_on).to be_nil
end
end
+
+ context 'when receive_max_input_size has been updated' do
+ it 'returns custom git config' do
+ allow(Gitlab::CurrentSettings).to receive(:receive_max_input_size) { 1 }
+
+ push(key, project)
+
+ expect(json_response["git_config_options"]).to be_present
+ end
+ end
+
+ context 'when receive_max_input_size is empty' do
+ it 'returns an empty git config' do
+ allow(Gitlab::CurrentSettings).to receive(:receive_max_input_size) { nil }
+
+ push(key, project)
+
+ expect(json_response["git_config_options"]).to be_empty
+ end
+ end
end
end
diff --git a/spec/requests/api/project_export_spec.rb b/spec/requests/api/project_export_spec.rb
index 45e4e35d773..0586025956f 100644
--- a/spec/requests/api/project_export_spec.rb
+++ b/spec/requests/api/project_export_spec.rb
@@ -4,8 +4,8 @@ describe API::ProjectExport do
set(:project) { create(:project) }
set(:project_none) { create(:project) }
set(:project_started) { create(:project) }
- set(:project_finished) { create(:project) }
- set(:project_after_export) { create(:project) }
+ let(:project_finished) { create(:project, :with_export) }
+ let(:project_after_export) { create(:project, :with_export) }
set(:user) { create(:user) }
set(:admin) { create(:admin) }
@@ -29,13 +29,7 @@ describe API::ProjectExport do
# simulate exporting work directory
FileUtils.mkdir_p File.join(project_started.export_path, 'securerandom-hex')
- # simulate exported
- FileUtils.mkdir_p project_finished.export_path
- FileUtils.touch File.join(project_finished.export_path, '_export.tar.gz')
-
# simulate in after export action
- FileUtils.mkdir_p project_after_export.export_path
- FileUtils.touch File.join(project_after_export.export_path, '_export.tar.gz')
FileUtils.touch Gitlab::ImportExport::AfterExportStrategies::BaseAfterExportStrategy.lock_file_path(project_after_export)
end
@@ -191,14 +185,11 @@ describe API::ProjectExport do
context 'when upload complete' do
before do
- FileUtils.rm_rf(project_after_export.export_path)
-
- if project_after_export.export_project_object_exists?
- upload = project_after_export.import_export_upload
+ project_after_export.remove_exports
+ end
- upload.remove_export_file!
- upload.save
- end
+ it 'has removed the export' do
+ expect(project_after_export.export_file_exists?).to be_falsey
end
it_behaves_like '404 response' do
@@ -273,13 +264,13 @@ describe API::ProjectExport do
before do
stub_uploads_object_storage(ImportExportUploader)
- [project, project_finished, project_after_export].each do |p|
- p.add_maintainer(user)
+ project.add_maintainer(user)
+ project_finished.add_maintainer(user)
+ project_after_export.add_maintainer(user)
- upload = ImportExportUpload.new(project: p)
- upload.export_file = fixture_file_upload('spec/fixtures/project_export.tar.gz', "`/tar.gz")
- upload.save!
- end
+ upload = ImportExportUpload.new(project: project)
+ upload.export_file = fixture_file_upload('spec/fixtures/project_export.tar.gz', "`/tar.gz")
+ upload.save!
end
it_behaves_like 'get project download by strategy'
diff --git a/spec/requests/api/project_import_spec.rb b/spec/requests/api/project_import_spec.rb
index bc06f3c3732..c8fa4754810 100644
--- a/spec/requests/api/project_import_spec.rb
+++ b/spec/requests/api/project_import_spec.rb
@@ -7,7 +7,6 @@ describe API::ProjectImport do
let(:namespace) { create(:group) }
before do
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
- stub_feature_flags(import_export_object_storage: true)
stub_uploads_object_storage(FileUploader)
namespace.add_owner(user)
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index b6f92042ecc..c8e98e6024c 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -148,6 +148,16 @@ describe API::Projects do
expect(json_response.first.keys).to include('open_issues_count')
end
+ it 'does not include projects marked for deletion' do
+ project.update(pending_delete: true)
+
+ get api('/projects', user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.map { |p| p['id'] }).not_to include(project.id)
+ end
+
it 'does not include open_issues_count if issues are disabled' do
project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
@@ -557,6 +567,14 @@ describe API::Projects do
expect(json_response['visibility']).to eq('private')
end
+ it 'creates a new project initialized with a README.md' do
+ project = attributes_for(:project, initialize_with_readme: 1, name: 'somewhere')
+
+ post api('/projects', user), project
+
+ expect(json_response['readme_url']).to eql("#{Gitlab.config.gitlab.url}/#{json_response['namespace']['full_path']}/somewhere/blob/master/README.md")
+ end
+
it 'sets tag list to a project' do
project = attributes_for(:project, tag_list: %w[tagFirst tagSecond])
@@ -1004,6 +1022,15 @@ describe API::Projects do
expect(json_response).not_to include("import_error")
end
+ it 'returns 404 when project is marked for deletion' do
+ project.update(pending_delete: true)
+
+ get api("/projects/#{project.id}", user)
+
+ expect(response).to have_gitlab_http_status(404)
+ expect(json_response['message']).to eq('404 Project Not Found')
+ end
+
context 'links exposure' do
it 'exposes related resources full URIs' do
get api("/projects/#{project.id}", user)
diff --git a/spec/requests/api/resource_label_events_spec.rb b/spec/requests/api/resource_label_events_spec.rb
new file mode 100644
index 00000000000..b7d4a5152cc
--- /dev/null
+++ b/spec/requests/api/resource_label_events_spec.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe API::ResourceLabelEvents do
+ set(:user) { create(:user) }
+ set(:project) { create(:project, :public, :repository, namespace: user.namespace) }
+ set(:private_user) { create(:user) }
+
+ before do
+ project.add_developer(user)
+ end
+
+ shared_examples 'resource_label_events API' do |parent_type, eventable_type, id_name|
+ describe "GET /#{parent_type}/:id/#{eventable_type}/:noteable_id/resource_label_events" do
+ it "returns an array of resource label events" do
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_label_events", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.first['id']).to eq(event.id)
+ end
+
+ it "returns a 404 error when eventable id not found" do
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/12345/resource_label_events", user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ it "returns 404 when not authorized" do
+ parent.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_label_events", private_user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ describe "GET /#{parent_type}/:id/#{eventable_type}/:noteable_id/resource_label_events/:event_id" do
+ it "returns a resource label event by id" do
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_label_events/#{event.id}", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['id']).to eq(event.id)
+ end
+
+ it "returns a 404 error if resource label event not found" do
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_label_events/12345", user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+
+ context 'when eventable is an Issue' do
+ let(:issue) { create(:issue, project: project, author: user) }
+
+ it_behaves_like 'resource_label_events API', 'projects', 'issues', 'iid' do
+ let(:parent) { project }
+ let(:eventable) { issue }
+ let!(:event) { create(:resource_label_event, issue: issue) }
+ end
+ end
+
+ context 'when eventable is a Merge Request' do
+ let(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: user) }
+
+ it_behaves_like 'resource_label_events API', 'projects', 'merge_requests', 'iid' do
+ let(:parent) { project }
+ let(:eventable) { merge_request }
+ let!(:event) { create(:resource_label_event, merge_request: merge_request) }
+ end
+ end
+end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index d48d577afa1..b7d62df0663 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -1031,11 +1031,14 @@ describe API::Users do
expect(json_response['error']).to eq('email is missing')
end
- it "creates email" do
+ it "creates unverified email" do
email_attrs = attributes_for :email
expect do
post api("/users/#{user.id}/emails", admin), email_attrs
end.to change { user.emails.count }.by(1)
+
+ email = Email.find_by(user_id: user.id, email: email_attrs[:email])
+ expect(email).not_to be_confirmed
end
it "returns a 400 for invalid ID" do
@@ -1043,6 +1046,18 @@ describe API::Users do
expect(response).to have_gitlab_http_status(400)
end
+
+ it "creates verified email" do
+ email_attrs = attributes_for :email
+ email_attrs[:skip_confirmation] = true
+
+ post api("/users/#{user.id}/emails", admin), email_attrs
+
+ expect(response).to have_gitlab_http_status(201)
+
+ email = Email.find_by(user_id: user.id, email: email_attrs[:email])
+ expect(email).to be_confirmed
+ end
end
describe 'GET /user/:id/emails' do
diff --git a/spec/serializers/status_entity_spec.rb b/spec/serializers/detailed_status_entity_spec.rb
index 0b010ebd507..62f57ca8689 100644
--- a/spec/serializers/status_entity_spec.rb
+++ b/spec/serializers/detailed_status_entity_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe StatusEntity do
+describe DetailedStatusEntity do
let(:entity) { described_class.new(status) }
let(:status) do
diff --git a/spec/services/issuable/common_system_notes_service_spec.rb b/spec/services/issuable/common_system_notes_service_spec.rb
index dcf4503ef9c..fa1a421d528 100644
--- a/spec/services/issuable/common_system_notes_service_spec.rb
+++ b/spec/services/issuable/common_system_notes_service_spec.rb
@@ -12,12 +12,21 @@ describe Issuable::CommonSystemNotesService do
it_behaves_like 'system note creation', { time_estimate: 5 }, 'changed time estimate'
context 'when new label is added' do
+ let(:label) { create(:label, project: project) }
+
before do
- label = create(:label, project: project)
issuable.labels << label
+ issuable.save
end
- it_behaves_like 'system note creation', {}, /added ~\w+ label/
+ it 'creates a resource label event' do
+ described_class.new(project, user).execute(issuable, [])
+ event = issuable.reload.resource_label_events.last
+
+ expect(event).not_to be_nil
+ expect(event.label_id).to eq label.id
+ expect(event.user_id).to eq user.id
+ end
end
context 'when new milestone is assigned' do
diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb
index 609eef76d2c..b5767583952 100644
--- a/spec/services/issues/move_service_spec.rb
+++ b/spec/services/issues/move_service_spec.rb
@@ -122,6 +122,17 @@ describe Issues::MoveService do
end
end
+ context 'issue with resource label events' do
+ it 'assigns resource label events to new issue' do
+ old_issue.resource_label_events = create_list(:resource_label_event, 2, issue: old_issue)
+
+ new_issue = move_service.execute(old_issue, new_project)
+
+ expected = old_issue.resource_label_events.map(&:label_id)
+ expect(new_issue.resource_label_events.map(&:label_id)).to match_array(expected)
+ end
+ end
+
context 'generic issue' do
include_context 'issue move executed'
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index 5bcfef46b75..07aa8449a66 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -189,11 +189,12 @@ describe Issues::UpdateService, :mailer do
expect(note.note).to include "assigned to #{user2.to_reference}"
end
- it 'creates system note about issue label edit' do
- note = find_note('added ~')
+ it 'creates a resource label event' do
+ event = issue.resource_label_events.last
- expect(note).not_to be_nil
- expect(note.note).to include "added #{label.to_reference} label"
+ expect(event).not_to be_nil
+ expect(event.label_id).to eq label.id
+ expect(event.user_id).to eq user.id
end
it 'creates system note about title change' do
diff --git a/spec/services/merge_requests/reload_diffs_service_spec.rb b/spec/services/merge_requests/reload_diffs_service_spec.rb
index a0a27d247fc..21f369a3818 100644
--- a/spec/services/merge_requests/reload_diffs_service_spec.rb
+++ b/spec/services/merge_requests/reload_diffs_service_spec.rb
@@ -57,6 +57,7 @@ describe MergeRequests::ReloadDiffsService, :use_clean_rails_memory_store_cachin
expect(Rails.cache).to receive(:delete).with(old_cache_key).and_call_original
expect(Rails.cache).to receive(:read).with(new_cache_key).and_call_original
expect(Rails.cache).to receive(:write).with(new_cache_key, anything, anything).and_call_original
+
subject.execute
end
end
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index f0029af83cc..55dfab81c26 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -109,11 +109,12 @@ describe MergeRequests::UpdateService, :mailer do
expect(note.note).to include "assigned to #{user2.to_reference}"
end
- it 'creates system note about merge_request label edit' do
- note = find_note('added ~')
+ it 'creates a resource label event' do
+ event = merge_request.resource_label_events.last
- expect(note).not_to be_nil
- expect(note.note).to include "added #{label.to_reference} label"
+ expect(event).not_to be_nil
+ expect(event.label_id).to eq label.id
+ expect(event.user_id).to eq user.id
end
it 'creates system note about title change' do
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index c442f6fe32f..68a361fa882 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -1969,6 +1969,23 @@ describe NotificationService, :mailer do
end
end
+ context 'Auto DevOps notifications' do
+ describe '#autodevops_disabled' do
+ let(:owner) { create(:user) }
+ let(:namespace) { create(:namespace, owner: owner) }
+ let(:project) { create(:project, :repository, :auto_devops, namespace: namespace) }
+ let(:pipeline_user) { create(:user) }
+ let(:pipeline) { create(:ci_pipeline, :failed, project: project, user: pipeline_user) }
+
+ it 'emails project owner and user that triggered the pipeline' do
+ notification.autodevops_disabled(pipeline, [owner.email, pipeline_user.email])
+
+ should_email(owner)
+ should_email(pipeline_user)
+ end
+ end
+ end
+
def build_team(project)
@u_watcher = create_global_setting_for(create(:user), :watch)
@u_participating = create_global_setting_for(create(:user), :participating)
diff --git a/spec/services/projects/auto_devops/disable_service_spec.rb b/spec/services/projects/auto_devops/disable_service_spec.rb
new file mode 100644
index 00000000000..76977d7a1a7
--- /dev/null
+++ b/spec/services/projects/auto_devops/disable_service_spec.rb
@@ -0,0 +1,100 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Projects::AutoDevops::DisableService, '#execute' do
+ let(:project) { create(:project, :repository, :auto_devops) }
+ let(:auto_devops) { project.auto_devops }
+
+ subject { described_class.new(project).execute }
+
+ context 'when Auto DevOps disabled at instance level' do
+ before do
+ stub_application_setting(auto_devops_enabled: false)
+ end
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'when Auto DevOps enabled at instance level' do
+ before do
+ stub_application_setting(auto_devops_enabled: true)
+ end
+
+ context 'when Auto DevOps explicitly enabled on project' do
+ before do
+ auto_devops.update!(enabled: true)
+ end
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'when Auto DevOps explicitly disabled on project' do
+ before do
+ auto_devops.update!(enabled: false)
+ end
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'when Auto DevOps is implicitly enabled' do
+ before do
+ auto_devops.update!(enabled: nil)
+ end
+
+ context 'when is the first pipeline failure' do
+ before do
+ create(:ci_pipeline, :failed, :auto_devops_source, project: project)
+ end
+
+ it 'should disable Auto DevOps for project' do
+ subject
+
+ expect(auto_devops.enabled).to eq(false)
+ end
+ end
+
+ context 'when it is not the first pipeline failure' do
+ before do
+ create_list(:ci_pipeline, 2, :failed, :auto_devops_source, project: project)
+ end
+
+ it 'should explicitly disable Auto DevOps for project' do
+ subject
+
+ expect(auto_devops.reload.enabled).to eq(false)
+ end
+ end
+
+ context 'when an Auto DevOps pipeline has succeeded before' do
+ before do
+ create(:ci_pipeline, :success, :auto_devops_source, project: project)
+ end
+
+ it 'should not disable Auto DevOps for project' do
+ subject
+
+ expect(auto_devops.reload.enabled).to be_nil
+ end
+ end
+ end
+
+ context 'when project does not have an Auto DevOps record related' do
+ let(:project) { create(:project, :repository) }
+
+ before do
+ create(:ci_pipeline, :failed, :auto_devops_source, project: project)
+ end
+
+ it 'should disable Auto DevOps for project' do
+ subject
+ auto_devops = project.reload.auto_devops
+
+ expect(auto_devops.enabled).to eq(false)
+ end
+
+ it 'should create a ProjectAutoDevops record' do
+ expect { subject }.to change { ProjectAutoDevops.count }.from(0).to(1)
+ end
+ end
+ end
+end
diff --git a/spec/services/projects/container_repository/destroy_service_spec.rb b/spec/services/projects/container_repository/destroy_service_spec.rb
new file mode 100644
index 00000000000..307ccc88865
--- /dev/null
+++ b/spec/services/projects/container_repository/destroy_service_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::ContainerRepository::DestroyService do
+ set(:user) { create(:user) }
+ set(:project) { create(:project, :private) }
+
+ subject { described_class.new(project, user) }
+
+ before do
+ stub_container_registry_config(enabled: true)
+ end
+
+ context 'when user does not have access to registry' do
+ let!(:repository) { create(:container_repository, :root, project: project) }
+
+ it 'does not delete a repository' do
+ expect { subject.execute(repository) }.not_to change { ContainerRepository.all.count }
+ end
+ end
+
+ context 'when user has access to registry' do
+ before do
+ project.add_developer(user)
+ end
+
+ context 'when root container repository exists' do
+ let!(:repository) { create(:container_repository, :root, project: project) }
+
+ before do
+ stub_container_registry_tags(repository: :any, tags: [])
+ end
+
+ it 'deletes the repository' do
+ expect { described_class.new(project, user).execute(repository) }.to change { ContainerRepository.all.count }.by(-1)
+ end
+ end
+ end
+end
diff --git a/spec/services/projects/update_remote_mirror_service_spec.rb b/spec/services/projects/update_remote_mirror_service_spec.rb
index 96e8a80b334..56a36432462 100644
--- a/spec/services/projects/update_remote_mirror_service_spec.rb
+++ b/spec/services/projects/update_remote_mirror_service_spec.rb
@@ -1,107 +1,118 @@
require 'spec_helper'
describe Projects::UpdateRemoteMirrorService do
- set(:project) { create(:project, :repository) }
- let(:owner) { project.owner }
+ let(:project) { create(:project, :repository) }
let(:remote_project) { create(:forked_project_with_submodules) }
- let(:repository) { project.repository }
- let(:raw_repository) { repository.raw }
let(:remote_mirror) { project.remote_mirrors.create!(url: remote_project.http_url_to_repo, enabled: true, only_protected_branches: false) }
+ let(:remote_name) { remote_mirror.remote_name }
- subject { described_class.new(project, project.creator) }
+ subject(:service) { described_class.new(project, project.creator) }
describe "#execute" do
before do
- repository.add_branch(owner, 'existing-branch', 'master')
+ project.repository.add_branch(project.owner, 'existing-branch', 'master')
allow(remote_mirror).to receive(:update_repository).and_return(true)
end
+ it "ensures the remote exists" do
+ stub_fetch_remote(project, remote_name: remote_name)
+ stub_find_remote_root_ref(project, remote_name: remote_name)
+
+ expect(remote_mirror).to receive(:ensure_remote!)
+
+ service.execute(remote_mirror)
+ end
+
it "fetches the remote repository" do
- expect(remote_mirror).to receive(:ensure_remote!).and_call_original
- expect(repository).to receive(:fetch_remote).with(remote_mirror.remote_name, no_tags: true) do
- sync_remote(repository, remote_mirror.remote_name, local_branch_names)
- end
+ stub_find_remote_root_ref(project, remote_name: remote_name)
+
+ expect(project.repository)
+ .to receive(:fetch_remote)
+ .with(remote_mirror.remote_name, no_tags: true)
+
+ service.execute(remote_mirror)
+ end
+
+ it "updates the default branch when HEAD has changed" do
+ stub_fetch_remote(project, remote_name: remote_name)
+ stub_find_remote_root_ref(project, remote_name: remote_name, ref: "existing-branch")
- subject.execute(remote_mirror)
+ expect { service.execute(remote_mirror) }
+ .to change { project.default_branch }
+ .from("master")
+ .to("existing-branch")
end
- it "succeeds" do
- allow(repository).to receive(:fetch_remote) { sync_remote(repository, remote_mirror.remote_name, local_branch_names) }
+ it "does not update the default branch when HEAD does not change" do
+ stub_fetch_remote(project, remote_name: remote_name)
+ stub_find_remote_root_ref(project, remote_name: remote_name, ref: "master")
+
+ expect { service.execute(remote_mirror) }.not_to change { project.default_branch }
+ end
- result = subject.execute(remote_mirror)
+ it "returns success when updated succeeds" do
+ stub_fetch_remote(project, remote_name: remote_name)
+ stub_find_remote_root_ref(project, remote_name: remote_name)
+
+ result = service.execute(remote_mirror)
expect(result[:status]).to eq(:success)
end
context 'when syncing all branches' do
it "push all the branches the first time" do
- allow(repository).to receive(:fetch_remote)
+ stub_fetch_remote(project, remote_name: remote_name)
+ stub_find_remote_root_ref(project, remote_name: remote_name)
expect(remote_mirror).to receive(:update_repository).with({})
- subject.execute(remote_mirror)
+ service.execute(remote_mirror)
end
end
context 'when only syncing protected branches' do
- let(:unprotected_branch_name) { 'existing-branch' }
- let(:protected_branch_name) do
- project.repository.branch_names.find { |n| n != unprotected_branch_name }
- end
- let!(:protected_branch) do
- create(:protected_branch, project: project, name: protected_branch_name)
- end
-
- before do
- project.reload
+ it "sync updated protected branches" do
+ stub_fetch_remote(project, remote_name: remote_name)
+ stub_find_remote_root_ref(project, remote_name: remote_name)
+ protected_branch = create_protected_branch(project)
remote_mirror.only_protected_branches = true
- end
- it "sync updated protected branches" do
- allow(repository).to receive(:fetch_remote)
- expect(remote_mirror).to receive(:update_repository).with(only_branches_matching: [protected_branch_name])
+ expect(remote_mirror)
+ .to receive(:update_repository)
+ .with(only_branches_matching: [protected_branch.name])
- subject.execute(remote_mirror)
+ service.execute(remote_mirror)
end
- end
- end
- def sync_remote(repository, remote_name, local_branch_names)
- local_branch_names.each do |branch|
- commit = repository.commit(branch)
- repository.write_ref("refs/remotes/#{remote_name}/#{branch}", commit.id) if commit
+ def create_protected_branch(project)
+ branch_name = project.repository.branch_names.find { |n| n != 'existing-branch' }
+ create(:protected_branch, project: project, name: branch_name)
+ end
end
end
- def update_remote_branch(repository, remote_name, branch)
- masterrev = repository.commit('master').id
-
- repository.write_ref("refs/remotes/#{remote_name}/#{branch}", masterrev, force: true)
- repository.expire_branches_cache
+ def stub_find_remote_root_ref(project, ref: 'master', remote_name:)
+ allow(project.repository)
+ .to receive(:find_remote_root_ref)
+ .with(remote_name)
+ .and_return(ref)
end
- def update_branch(repository, branch)
- masterrev = repository.commit('master').id
-
- repository.write_ref("refs/heads/#{branch}", masterrev, force: true)
- repository.expire_branches_cache
+ def stub_fetch_remote(project, remote_name:)
+ allow(project.repository)
+ .to receive(:fetch_remote)
+ .with(remote_name, no_tags: true) { fetch_remote(project.repository, remote_name) }
end
- def generate_tags(repository, *tag_names)
- tag_names.each_with_object([]) do |name, tags|
- tag = repository.find_tag(name)
- target = tag.try(:target)
- target_commit = tag.try(:dereferenced_target)
- tags << Gitlab::Git::Tag.new(repository.raw_repository, {
- name: name,
- target: target,
- target_commit: target_commit
- })
+ def fetch_remote(repository, remote_name)
+ local_branch_names(repository).each do |branch|
+ commit = repository.commit(branch)
+ repository.write_ref("refs/remotes/#{remote_name}/#{branch}", commit.id) if commit
end
end
- def local_branch_names
+ def local_branch_names(repository)
branch_names = repository.branches.map(&:name)
# we want the protected branch to be pushed first
branch_names.unshift(branch_names.delete('master'))
diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb
index bf1c157c4a2..06cad9c00d2 100644
--- a/spec/services/quick_actions/interpret_service_spec.rb
+++ b/spec/services/quick_actions/interpret_service_spec.rb
@@ -272,6 +272,28 @@ describe QuickActions::InterpretService do
end
end
+ shared_examples 'lock command' do
+ let(:issue) { create(:issue, project: project, discussion_locked: false) }
+ let(:merge_request) { create(:merge_request, source_project: project, discussion_locked: false) }
+
+ it 'returns discussion_locked: true if content contains /lock' do
+ _, updates = service.execute(content, issuable)
+
+ expect(updates).to eq(discussion_locked: true)
+ end
+ end
+
+ shared_examples 'unlock command' do
+ let(:issue) { create(:issue, project: project, discussion_locked: true) }
+ let(:merge_request) { create(:merge_request, source_project: project, discussion_locked: true) }
+
+ it 'returns discussion_locked: true if content contains /unlock' do
+ _, updates = service.execute(content, issuable)
+
+ expect(updates).to eq(discussion_locked: false)
+ end
+ end
+
shared_examples 'empty command' do
it 'populates {} if content contains an unsupported command' do
_, updates = service.execute(content, issuable)
@@ -786,6 +808,26 @@ describe QuickActions::InterpretService do
let(:issuable) { issue }
end
+ it_behaves_like 'lock command' do
+ let(:content) { '/lock' }
+ let(:issuable) { issue }
+ end
+
+ it_behaves_like 'lock command' do
+ let(:content) { '/lock' }
+ let(:issuable) { merge_request }
+ end
+
+ it_behaves_like 'unlock command' do
+ let(:content) { '/unlock' }
+ let(:issuable) { issue }
+ end
+
+ it_behaves_like 'unlock command' do
+ let(:content) { '/unlock' }
+ let(:issuable) { merge_request }
+ end
+
context '/todo' do
let(:content) { '/todo' }
@@ -961,6 +1003,16 @@ describe QuickActions::InterpretService do
let(:content) { '/duplicate #{issue.to_reference}' }
let(:issuable) { issue }
end
+
+ it_behaves_like 'empty command' do
+ let(:content) { '/lock' }
+ let(:issuable) { issue }
+ end
+
+ it_behaves_like 'empty command' do
+ let(:content) { '/unlock' }
+ let(:issuable) { issue }
+ end
end
context '/award command' do
diff --git a/spec/services/resource_events/change_labels_service_spec.rb b/spec/services/resource_events/change_labels_service_spec.rb
index 41b0fb3eea3..4c9138fb1ef 100644
--- a/spec/services/resource_events/change_labels_service_spec.rb
+++ b/spec/services/resource_events/change_labels_service_spec.rb
@@ -18,6 +18,14 @@ describe ResourceEvents::ChangeLabelsService do
expect(event.action).to eq(action)
end
+ it 'expires resource note etag cache' do
+ expect_any_instance_of(Gitlab::EtagCaching::Store)
+ .to receive(:touch)
+ .with("/#{resource.project.namespace.to_param}/#{resource.project.to_param}/noteable/issue/#{resource.id}/notes")
+
+ described_class.new(resource, author).execute(added_labels: [labels[0]])
+ end
+
context 'when adding a label' do
let(:added) { [labels[0]] }
let(:removed) { [] }
diff --git a/spec/services/resource_events/merge_into_notes_service_spec.rb b/spec/services/resource_events/merge_into_notes_service_spec.rb
new file mode 100644
index 00000000000..0d333d541c9
--- /dev/null
+++ b/spec/services/resource_events/merge_into_notes_service_spec.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ResourceEvents::MergeIntoNotesService do
+ def create_event(params)
+ event_params = { action: :add, label: label, issue: resource,
+ user: user }
+
+ create(:resource_label_event, event_params.merge(params))
+ end
+
+ def create_note(params)
+ opts = { noteable: resource, project: project }
+
+ create(:note_on_issue, opts.merge(params))
+ end
+
+ set(:project) { create(:project) }
+ set(:user) { create(:user) }
+ set(:resource) { create(:issue, project: project) }
+ set(:label) { create(:label, project: project) }
+ set(:label2) { create(:label, project: project) }
+ let(:time) { Time.now }
+
+ describe '#execute' do
+ it 'merges label events into notes in order of created_at' do
+ note1 = create_note(created_at: 4.days.ago)
+ note2 = create_note(created_at: 2.days.ago)
+ event1 = create_event(created_at: 3.days.ago)
+ event2 = create_event(created_at: 1.day.ago)
+
+ notes = described_class.new(resource, user).execute([note1, note2])
+
+ expected = [note1, event1, note2, event2].map(&:discussion_id)
+ expect(notes.map(&:discussion_id)).to eq expected
+ end
+
+ it 'squashes events with same time and author into single note' do
+ user2 = create(:user)
+
+ create_event(created_at: time)
+ create_event(created_at: time, label: label2, action: :remove)
+ create_event(created_at: time, user: user2)
+ create_event(created_at: 1.day.ago, label: label2)
+
+ notes = described_class.new(resource, user).execute()
+
+ expected = [
+ "added #{label.to_reference} label and removed #{label2.to_reference} label",
+ "added #{label.to_reference} label",
+ "added #{label2.to_reference} label"
+ ]
+
+ expect(notes.count).to eq 3
+ expect(notes.map(&:note)).to match_array expected
+ end
+
+ it 'fetches only notes created after last_fetched_at' do
+ create_event(created_at: 4.days.ago)
+ event = create_event(created_at: 1.day.ago)
+
+ notes = described_class.new(resource, user,
+ last_fetched_at: 2.days.ago.to_i).execute()
+
+ expect(notes.count).to eq 1
+ expect(notes.first.discussion_id).to eq event.discussion_id
+ end
+ end
+end
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 442de61f69b..f4b7cb8c90a 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -197,45 +197,6 @@ describe SystemNoteService do
end
end
- describe '.change_label' do
- subject { described_class.change_label(noteable, project, author, added, removed) }
-
- let(:labels) { create_list(:label, 2, project: project) }
- let(:added) { [] }
- let(:removed) { [] }
-
- it_behaves_like 'a system note' do
- let(:action) { 'label' }
- end
-
- context 'with added labels' do
- let(:added) { labels }
- let(:removed) { [] }
-
- it 'sets the note text' do
- expect(subject.note).to eq "added ~#{labels[0].id} ~#{labels[1].id} labels"
- end
- end
-
- context 'with removed labels' do
- let(:added) { [] }
- let(:removed) { labels }
-
- it 'sets the note text' do
- expect(subject.note).to eq "removed ~#{labels[0].id} ~#{labels[1].id} labels"
- end
- end
-
- context 'with added and removed labels' do
- let(:added) { [labels[0]] }
- let(:removed) { [labels[1]] }
-
- it 'sets the note text' do
- expect(subject.note).to eq "added ~#{labels[0].id} and removed ~#{labels[1].id} labels"
- end
- end
- end
-
describe '.change_milestone' do
context 'for a project milestone' do
subject { described_class.change_milestone(noteable, project, author, milestone) }
@@ -288,6 +249,30 @@ describe SystemNoteService do
end
end
+ describe '.change_due_date' do
+ subject { described_class.change_due_date(noteable, project, author, due_date) }
+
+ let(:due_date) { Date.today }
+
+ it_behaves_like 'a system note' do
+ let(:action) { 'due_date' }
+ end
+
+ context 'when due date added' do
+ it 'sets the note text' do
+ expect(subject.note).to eq "changed due date to #{Date.today.to_s(:long)}"
+ end
+ end
+
+ context 'when due date removed' do
+ let(:due_date) { nil }
+
+ it 'sets the note text' do
+ expect(subject.note).to eq 'removed due date'
+ end
+ end
+ end
+
describe '.change_status' do
subject { described_class.change_status(noteable, project, author, status, source) }
@@ -725,7 +710,7 @@ describe SystemNoteService do
let(:jira_tracker) { project.jira_service }
let(:commit) { project.commit }
let(:comment_url) { jira_api_comment_url(jira_issue.id) }
- let(:success_message) { "JiraService SUCCESS: Successfully posted to http://jira.example.net." }
+ let(:success_message) { "SUCCESS: Successfully posted to http://jira.example.net." }
before do
stub_jira_urls(jira_issue.id)
diff --git a/spec/services/wikis/create_attachment_service_spec.rb b/spec/services/wikis/create_attachment_service_spec.rb
index 3f4da873ce4..f5899f292c8 100644
--- a/spec/services/wikis/create_attachment_service_spec.rb
+++ b/spec/services/wikis/create_attachment_service_spec.rb
@@ -88,8 +88,30 @@ describe Wikis::CreateAttachmentService do
end
end
- describe 'validations' do
+ describe '#parse_file_name' do
context 'when file_name' do
+ context 'has white spaces' do
+ let(:file_name) { 'file with spaces' }
+
+ it "replaces all of them with '_'" do
+ result = service.execute
+
+ expect(result[:status]).to eq :success
+ expect(result[:result][:file_name]).to eq 'file_with_spaces'
+ end
+ end
+
+ context 'has other invalid characters' do
+ let(:file_name) { "file\twith\tinvalid chars" }
+
+ it "replaces all of them with '_'" do
+ result = service.execute
+
+ expect(result[:status]).to eq :success
+ expect(result[:result][:file_name]).to eq 'file_with_invalid_chars'
+ end
+ end
+
context 'is not present' do
let(:file_name) { nil }
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index c4bb1c13f2e..d1337325973 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -135,6 +135,10 @@ RSpec.configure do |config|
Fog.unmock! if Fog.mock?
end
+ config.after(:example) do
+ Gitlab::CurrentSettings.clear_in_memory_application_settings!
+ end
+
config.before(:example, :mailer) do
reset_delivered_emails!
end
diff --git a/spec/support/features/issuable_slash_commands_shared_examples.rb b/spec/support/features/issuable_quick_actions_shared_examples.rb
index 9b44c532ff6..846e697eb96 100644
--- a/spec/support/features/issuable_slash_commands_shared_examples.rb
+++ b/spec/support/features/issuable_quick_actions_shared_examples.rb
@@ -55,7 +55,7 @@ shared_examples 'issuable record that supports quick actions in its description
describe "note on #{issuable_type}", :js do
before do
- visit public_send("namespace_project_#{issuable_type}_path", project.namespace, project, issuable)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
end
context 'with a note containing commands' do
@@ -121,7 +121,7 @@ shared_examples 'issuable record that supports quick actions in its description
gitlab_sign_out
gitlab_sign_in(guest)
- visit public_send("namespace_project_#{issuable_type}_path", project.namespace, project, issuable)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
end
it "does not close the #{issuable_type}" do
@@ -158,7 +158,7 @@ shared_examples 'issuable record that supports quick actions in its description
gitlab_sign_out
gitlab_sign_in(guest)
- visit public_send("namespace_project_#{issuable_type}_path", project.namespace, project, issuable)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
end
it "does not reopen the #{issuable_type}" do
@@ -190,7 +190,7 @@ shared_examples 'issuable record that supports quick actions in its description
gitlab_sign_out
gitlab_sign_in(guest)
- visit public_send("namespace_project_#{issuable_type}_path", project.namespace, project, issuable)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
end
it "does not change the #{issuable_type} title" do
@@ -285,13 +285,87 @@ shared_examples 'issuable record that supports quick actions in its description
expect(issuable.reload.assignees).to eq [maintainer]
end
end
+
+ context "with a note locking the #{issuable_type} discussion" do
+ before do
+ issuable.update(discussion_locked: false)
+ expect(issuable).not_to be_discussion_locked
+ end
+
+ context "when current user can lock #{issuable_type} discussion" do
+ it "locks the #{issuable_type} discussion" do
+ add_note("/lock")
+
+ expect(page).not_to have_content '/lock'
+ expect(page).to have_content 'Commands applied'
+
+ expect(issuable.reload).to be_discussion_locked
+ end
+ end
+
+ context "when current user cannot lock #{issuable_type}" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ end
+
+ it "does not lock the #{issuable_type} discussion" do
+ add_note("/lock")
+
+ expect(page).not_to have_content 'Commands applied'
+
+ expect(issuable).not_to be_discussion_locked
+ end
+ end
+ end
+
+ context "with a note unlocking the #{issuable_type} discussion" do
+ before do
+ issuable.update(discussion_locked: true)
+ expect(issuable).to be_discussion_locked
+ end
+
+ context "when current user can unlock #{issuable_type} discussion" do
+ it "unlocks the #{issuable_type} discussion" do
+ add_note("/unlock")
+
+ expect(page).not_to have_content '/unlock'
+ expect(page).to have_content 'Commands applied'
+
+ expect(issuable.reload).not_to be_discussion_locked
+ end
+ end
+
+ context "when current user cannot unlock #{issuable_type}" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ end
+
+ it "does not unlock the #{issuable_type} discussion" do
+ add_note("/unlock")
+
+ expect(page).not_to have_content 'Commands applied'
+
+ expect(issuable).to be_discussion_locked
+ end
+ end
+ end
end
describe "preview of note on #{issuable_type}", :js do
it 'removes quick actions from note and explains them' do
create(:user, username: 'bob')
- visit public_send("namespace_project_#{issuable_type}_path", project.namespace, project, issuable)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
page.within('.js-main-target-form') do
fill_in 'note[note]', with: "Awesome!\n/assign @bob "
diff --git a/spec/support/helpers/git_helpers.rb b/spec/support/helpers/git_helpers.rb
new file mode 100644
index 00000000000..fc92bc38561
--- /dev/null
+++ b/spec/support/helpers/git_helpers.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module GitHelpers
+ def project_hook_exists?(project)
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ project_path = project.repository.raw_repository.path
+
+ File.exist?(File.join(project_path, 'hooks', 'post-receive'))
+ end
+ end
+end
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index 3f8e3ae5190..8e8ec574edb 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -107,10 +107,6 @@ module TestEnv
.and_call_original
end
- def disable_pre_receive
- allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, nil])
- end
-
# Clean /tmp/tests
#
# Keeps gitlab-shell and gitlab-test
diff --git a/spec/support/import_export/export_file_helper.rb b/spec/support/import_export/export_file_helper.rb
index 4d925ac77f4..d9ed405baf4 100644
--- a/spec/support/import_export/export_file_helper.rb
+++ b/spec/support/import_export/export_file_helper.rb
@@ -52,7 +52,7 @@ module ExportFileHelper
# Expands the compressed file for an exported project into +tmpdir+
def in_directory_with_expanded_export(project)
Dir.mktmpdir do |tmpdir|
- export_file = project.export_project_path
+ export_file = project.export_file.path
_output, exit_status = Gitlab::Popen.popen(%W{tar -zxf #{export_file} -C #{tmpdir}})
yield(exit_status, tmpdir)
diff --git a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
index 3057845061b..a096627ee62 100644
--- a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
+++ b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
@@ -73,9 +73,13 @@ RSpec.shared_examples 'an editable merge request' do
it 'description has autocomplete', :js do
find('#merge_request_description').native.send_keys('')
- fill_in 'merge_request_description', with: '@'
+ fill_in 'merge_request_description', with: user.to_reference[0..4]
- expect(page).to have_selector('.atwho-view')
+ wait_for_requests
+
+ page.within('.atwho-view') do
+ expect(page).to have_content(user2.name)
+ end
end
it 'has class js-quick-submit in form' do
diff --git a/spec/support/shared_examples/instance_statistics_controllers_shared_examples.rb b/spec/support/shared_examples/instance_statistics_controllers_shared_examples.rb
index 5334af841e1..8ea307c7c61 100644
--- a/spec/support/shared_examples/instance_statistics_controllers_shared_examples.rb
+++ b/spec/support/shared_examples/instance_statistics_controllers_shared_examples.rb
@@ -5,6 +5,8 @@ shared_examples 'instance statistics availability' do
before do
sign_in(user)
+
+ stub_application_setting(usage_ping_enabled: true)
end
describe 'GET #index' do
diff --git a/spec/support/shared_examples/models/label_note_shared_examples.rb b/spec/support/shared_examples/models/label_note_shared_examples.rb
new file mode 100644
index 00000000000..406385c13bd
--- /dev/null
+++ b/spec/support/shared_examples/models/label_note_shared_examples.rb
@@ -0,0 +1,109 @@
+# frozen_string_literal: true
+
+shared_examples 'label note created from events' do
+ def create_event(params = {})
+ event_params = { action: :add, label: label, user: user }
+ resource_key = resource.class.name.underscore.to_s
+ event_params[resource_key] = resource
+
+ build(:resource_label_event, event_params.merge(params))
+ end
+
+ def label_refs(events)
+ labels = events.map(&:label).compact
+
+ labels.map { |l| l.to_reference}.sort.join(' ')
+ end
+
+ let(:time) { Time.now }
+ let(:local_label_ids) { [label.id, label2.id] }
+
+ describe '.from_events' do
+ it 'returns system note with expected attributes' do
+ event = create_event
+
+ note = described_class.from_events([event, create_event])
+
+ expect(note.system).to be true
+ expect(note.author_id).to eq event.user_id
+ expect(note.discussion_id).to eq event.discussion_id
+ expect(note.noteable).to eq event.issuable
+ expect(note.note).to be_present
+ expect(note.note_html).to be_present
+ end
+
+ it 'updates markdown cache if reference is not set yet' do
+ event = create_event(reference: nil)
+
+ described_class.from_events([event])
+
+ expect(event.reference).not_to be_nil
+ end
+
+ it 'updates markdown cache if label was deleted' do
+ event = create_event(reference: 'some_ref', label: nil)
+
+ described_class.from_events([event])
+
+ expect(event.reference).to eq ''
+ end
+
+ it 'returns html note' do
+ events = [create_event(created_at: time)]
+
+ note = described_class.from_events(events)
+
+ expect(note.note_html).to include label.title
+ end
+
+ it 'returns text note for added labels' do
+ events = [create_event(created_at: time),
+ create_event(created_at: time, label: label2),
+ create_event(created_at: time, label: nil)]
+
+ note = described_class.from_events(events)
+
+ expect(note.note).to eq "added #{label_refs(events)} + 1 deleted label"
+ end
+
+ it 'returns text note for removed labels' do
+ events = [create_event(action: :remove, created_at: time),
+ create_event(action: :remove, created_at: time, label: label2),
+ create_event(action: :remove, created_at: time, label: nil)]
+
+ note = described_class.from_events(events)
+
+ expect(note.note).to eq "removed #{label_refs(events)} + 1 deleted label"
+ end
+
+ it 'returns text note for added and removed labels' do
+ add_events = [create_event(created_at: time),
+ create_event(created_at: time, label: nil)]
+
+ remove_events = [create_event(action: :remove, created_at: time),
+ create_event(action: :remove, created_at: time, label: nil)]
+
+ note = described_class.from_events(add_events + remove_events)
+
+ expect(note.note).to eq "added #{label_refs(add_events)} + 1 deleted label and removed #{label_refs(remove_events)} + 1 deleted label"
+ end
+
+ it 'returns text note for cross-project label' do
+ other_label = create(:label)
+ event = create_event(label: other_label)
+
+ note = described_class.from_events([event])
+
+ expect(note.note).to eq "added #{other_label.to_reference(resource_parent)} label"
+ end
+
+ it 'returns text note for cross-group label' do
+ other_label = create(:group_label)
+ event = create_event(label: other_label)
+
+ note = described_class.from_events([event])
+
+ expect(note.note).to eq "added #{other_label.to_reference(other_label.group, target_project: project, full: true)} label"
+ end
+ end
+end
diff --git a/spec/support/sidekiq.rb b/spec/support/sidekiq.rb
index d143014692d..6c4e11910d3 100644
--- a/spec/support/sidekiq.rb
+++ b/spec/support/sidekiq.rb
@@ -1,7 +1,27 @@
require 'sidekiq/testing/inline'
+# If Sidekiq::Testing.inline! is used, SQL transactions done inside
+# Sidekiq worker are included in the SQL query limit (in a real
+# deployment sidekiq worker is executed separately). To avoid
+# increasing SQL limit counter, the request is marked as whitelisted
+# during Sidekiq block
+class DisableQueryLimit
+ def call(worker_instance, msg, queue)
+ transaction = Gitlab::QueryLimiting::Transaction.current
+
+ if !transaction.respond_to?(:whitelisted) || transaction.whitelisted
+ yield
+ else
+ transaction.whitelisted = true
+ yield
+ transaction.whitelisted = false
+ end
+ end
+end
+
Sidekiq::Testing.server_middleware do |chain|
chain.add Gitlab::SidekiqStatus::ServerMiddleware
+ chain.add DisableQueryLimit
end
RSpec.configure do |config|
diff --git a/spec/tasks/gitlab/cleanup_rake_spec.rb b/spec/tasks/gitlab/cleanup_rake_spec.rb
index cc2cca10f58..19794227d9f 100644
--- a/spec/tasks/gitlab/cleanup_rake_spec.rb
+++ b/spec/tasks/gitlab/cleanup_rake_spec.rb
@@ -6,6 +6,8 @@ describe 'gitlab:cleanup rake tasks' do
end
describe 'cleanup namespaces and repos' do
+ let(:gitlab_shell) { Gitlab::Shell.new }
+ let(:storage) { storages.keys.first }
let(:storages) do
{
'default' => Gitlab::GitalyClient::StorageSettings.new(@default_storage_hash.merge('path' => 'tmp/tests/default_storage'))
@@ -17,53 +19,56 @@ describe 'gitlab:cleanup rake tasks' do
end
before do
- FileUtils.mkdir(Settings.absolute('tmp/tests/default_storage'))
allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
end
after do
- FileUtils.rm_rf(Settings.absolute('tmp/tests/default_storage'))
+ Gitlab::GitalyClient::StorageService.new(storage).delete_all_repositories
end
describe 'cleanup:repos' do
before do
- FileUtils.mkdir_p(Settings.absolute('tmp/tests/default_storage/broken/project.git'))
- FileUtils.mkdir_p(Settings.absolute('tmp/tests/default_storage/@hashed/12/34/5678.git'))
+ gitlab_shell.add_namespace(storage, 'broken/project.git')
+ gitlab_shell.add_namespace(storage, '@hashed/12/34/5678.git')
end
it 'moves it to an orphaned path' do
- run_rake_task('gitlab:cleanup:repos')
- repo_list = Dir['tmp/tests/default_storage/broken/*']
+ now = Time.now
+
+ Timecop.freeze(now) do
+ run_rake_task('gitlab:cleanup:repos')
+ repo_list = Gitlab::GitalyClient::StorageService.new(storage).list_directories(depth: 0)
- expect(repo_list.first).to include('+orphaned+')
+ expect(repo_list.last).to include("broken+orphaned+#{now.to_i}")
+ end
end
it 'ignores @hashed repos' do
run_rake_task('gitlab:cleanup:repos')
- expect(Dir.exist?(Settings.absolute('tmp/tests/default_storage/@hashed/12/34/5678.git'))).to be_truthy
+ expect(gitlab_shell.exists?(storage, '@hashed/12/34/5678.git')).to be(true)
end
end
describe 'cleanup:dirs' do
it 'removes missing namespaces' do
- FileUtils.mkdir_p(Settings.absolute("tmp/tests/default_storage/namespace_1/project.git"))
- FileUtils.mkdir_p(Settings.absolute("tmp/tests/default_storage/namespace_2/project.git"))
- allow(Namespace).to receive(:pluck).and_return('namespace_1')
+ gitlab_shell.add_namespace(storage, "namespace_1/project.git")
+ gitlab_shell.add_namespace(storage, "namespace_2/project.git")
+ allow(Namespace).to receive(:pluck).and_return(['namespace_1'])
stub_env('REMOVE', 'true')
run_rake_task('gitlab:cleanup:dirs')
- expect(Dir.exist?(Settings.absolute('tmp/tests/default_storage/namespace_1'))).to be_truthy
- expect(Dir.exist?(Settings.absolute('tmp/tests/default_storage/namespace_2'))).to be_falsey
+ expect(gitlab_shell.exists?(storage, 'namespace_1')).to be(true)
+ expect(gitlab_shell.exists?(storage, 'namespace_2')).to be(false)
end
it 'ignores @hashed directory' do
- FileUtils.mkdir_p(Settings.absolute('tmp/tests/default_storage/@hashed/12/34/5678.git'))
+ gitlab_shell.add_namespace(storage, '@hashed/12/34/5678.git')
run_rake_task('gitlab:cleanup:dirs')
- expect(Dir.exist?(Settings.absolute('tmp/tests/default_storage/@hashed/12/34/5678.git'))).to be_truthy
+ expect(gitlab_shell.exists?(storage, '@hashed/12/34/5678.git')).to be(true)
end
end
end
diff --git a/spec/uploaders/avatar_uploader_spec.rb b/spec/uploaders/avatar_uploader_spec.rb
index b0468bc35ff..6aaec7a4fef 100644
--- a/spec/uploaders/avatar_uploader_spec.rb
+++ b/spec/uploaders/avatar_uploader_spec.rb
@@ -35,5 +35,13 @@ describe AvatarUploader do
it_behaves_like "migrates", to_store: described_class::Store::REMOTE
it_behaves_like "migrates", from_store: described_class::Store::REMOTE, to_store: described_class::Store::LOCAL
+
+ it 'sets the right absolute path' do
+ storage_path = Gitlab.config.uploads.storage_path
+ absolute_path = File.join(storage_path, upload.path)
+
+ expect(uploader.absolute_path.scan(storage_path).size).to eq(1)
+ expect(uploader.absolute_path).to eq(absolute_path)
+ end
end
end
diff --git a/spec/uploaders/namespace_file_uploader_spec.rb b/spec/uploaders/namespace_file_uploader_spec.rb
index 71fe2c353c0..799c6db57fa 100644
--- a/spec/uploaders/namespace_file_uploader_spec.rb
+++ b/spec/uploaders/namespace_file_uploader_spec.rb
@@ -26,6 +26,26 @@ describe NamespaceFileUploader do
upload_path: IDENTIFIER
end
+ context '.base_dir' do
+ it 'returns local storage base_dir without store param' do
+ expect(described_class.base_dir(group)).to eq("uploads/-/system/namespace/#{group.id}")
+ end
+
+ it 'returns local storage base_dir when store param is Store::LOCAL' do
+ expect(described_class.base_dir(group, ObjectStorage::Store::LOCAL)).to eq("uploads/-/system/namespace/#{group.id}")
+ end
+
+ it 'returns remote base_dir when store param is Store::REMOTE' do
+ expect(described_class.base_dir(group, ObjectStorage::Store::REMOTE)).to eq("namespace/#{group.id}")
+ end
+ end
+
+ describe '#workhorse_local_upload_path' do
+ it 'returns the correct path in uploads directory' do
+ expect(described_class.workhorse_local_upload_path).to end_with('/uploads/tmp/uploads')
+ end
+ end
+
describe "#migrate!" do
before do
uploader.store!(fixture_file_upload(File.join('spec/fixtures/doc_sample.txt')))
diff --git a/spec/workers/auto_devops/disable_worker_spec.rb b/spec/workers/auto_devops/disable_worker_spec.rb
new file mode 100644
index 00000000000..800a07e41a5
--- /dev/null
+++ b/spec/workers/auto_devops/disable_worker_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe AutoDevops::DisableWorker, '#perform' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository, :auto_devops) }
+ let(:auto_devops) { project.auto_devops }
+ let(:pipeline) { create(:ci_pipeline, :failed, :auto_devops_source, project: project, user: user) }
+
+ subject { described_class.new }
+
+ before do
+ stub_application_setting(auto_devops_enabled: true)
+ auto_devops.update_attribute(:enabled, nil)
+ end
+
+ it 'disables auto devops for project' do
+ subject.perform(pipeline.id)
+
+ expect(auto_devops.reload.enabled).to eq(false)
+ end
+
+ context 'when project owner is a user' do
+ let(:owner) { create(:user) }
+ let(:namespace) { create(:namespace, owner: owner) }
+ let(:project) { create(:project, :repository, :auto_devops, namespace: namespace) }
+
+ it 'sends an email to pipeline user and project owner' do
+ expect(NotificationService).to receive_message_chain(:new, :autodevops_disabled).with(pipeline, [user.email, owner.email])
+
+ subject.perform(pipeline.id)
+ end
+ end
+
+ context 'when project does not have owner' do
+ let(:group) { create(:group) }
+ let(:project) { create(:project, :repository, :auto_devops, namespace: group) }
+
+ it 'sends an email to pipeline user' do
+ expect(NotificationService).to receive_message_chain(:new, :autodevops_disabled).with(pipeline, [user.email])
+
+ subject.perform(pipeline.id)
+ end
+ end
+
+ context 'when pipeline is not related to a user and project does not have owner' do
+ let(:group) { create(:group) }
+ let(:project) { create(:project, :repository, :auto_devops, namespace: group) }
+ let(:pipeline) { create(:ci_pipeline, :failed, project: project) }
+
+ it 'does not send an email' do
+ expect(NotificationService).not_to receive(:new)
+
+ subject.perform(pipeline.id)
+ end
+ end
+end
diff --git a/spec/workers/delete_container_repository_worker_spec.rb b/spec/workers/delete_container_repository_worker_spec.rb
new file mode 100644
index 00000000000..8c40611a959
--- /dev/null
+++ b/spec/workers/delete_container_repository_worker_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe DeleteContainerRepositoryWorker do
+ let(:registry) { create(:container_repository) }
+ let(:project) { registry.project }
+ let(:user) { project.owner }
+
+ subject { described_class.new }
+
+ describe '#perform' do
+ it 'executes the destroy service' do
+ service = instance_double(Projects::ContainerRepository::DestroyService)
+ expect(service).to receive(:execute)
+ expect(Projects::ContainerRepository::DestroyService).to receive(:new).with(project, user).and_return(service)
+
+ subject.perform(user.id, registry.id)
+ end
+
+ it 'does not raise error when user could not be found' do
+ expect do
+ subject.perform(-1, registry.id)
+ end.not_to raise_error
+ end
+
+ it 'does not raise error when registry could not be found' do
+ expect do
+ subject.perform(user.id, -1)
+ end.not_to raise_error
+ end
+ end
+end
diff --git a/spec/workers/project_service_worker_spec.rb b/spec/workers/project_service_worker_spec.rb
new file mode 100644
index 00000000000..56934f122e4
--- /dev/null
+++ b/spec/workers/project_service_worker_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe ProjectServiceWorker, '#perform' do
+ let(:worker) { described_class.new }
+ let(:service) { JiraService.new }
+
+ before do
+ allow(Service).to receive(:find).and_return(service)
+ end
+
+ it 'executes service with given data' do
+ data = { test: 'test' }
+ expect(service).to receive(:execute).with(data)
+
+ worker.perform(1, data)
+ end
+
+ it 'logs error messages' do
+ allow(service).to receive(:execute).and_raise(StandardError, 'invalid URL')
+ expect(Sidekiq.logger).to receive(:error).with({ class: described_class.name, service_class: service.class.name, message: "invalid URL" })
+
+ worker.perform(1, {})
+ end
+end
diff --git a/vendor/Dockerfile/Node-alpine.Dockerfile b/vendor/Dockerfile/Node-alpine.Dockerfile
index 5b9b495644a..24f92dd92cd 100644
--- a/vendor/Dockerfile/Node-alpine.Dockerfile
+++ b/vendor/Dockerfile/Node-alpine.Dockerfile
@@ -1,15 +1,17 @@
-FROM node:8.11-alpine
+FROM node:10.6-alpine
-WORKDIR /usr/src/app
+# Uncomment if use of `process.dlopen` is necessary
+# apk add --no-cache libc6-compat
+
+ENV PORT 8080
+EXPOSE 8080 # replace this with your application's default port, if necessary
-ARG NODE_ENV
+ARG NODE_ENV=production
ENV NODE_ENV $NODE_ENV
-COPY package.json /usr/src/app/
+WORKDIR /usr/src/app
+COPY package.json .
RUN npm install
+COPY . .
-COPY . /usr/src/app
-
-# replace this with your application's default port
-EXPOSE 8888
CMD [ "npm", "start" ]
diff --git a/vendor/Dockerfile/OpenJDK.Dockerfile b/vendor/Dockerfile/OpenJDK.Dockerfile
index 8a2ae62d93b..c68420b453a 100644
--- a/vendor/Dockerfile/OpenJDK.Dockerfile
+++ b/vendor/Dockerfile/OpenJDK.Dockerfile
@@ -1,8 +1,12 @@
-FROM openjdk:9
+FROM maven:3.5-jdk-11 as BUILD
-COPY . /usr/src/myapp
-WORKDIR /usr/src/myapp
+COPY . /usr/src/app
+RUN mvn --batch-mode -f /usr/src/app/pom.xml clean package
-RUN javac Main.java
+FROM openjdk:11-jdk
+ENV PORT 4567
+EXPOSE 4567
+COPY --from=BUILD /usr/src/app/target /opt/target
+WORKDIR /opt/target
-CMD ["java", "Main"]
+CMD ["/bin/bash", "-c", "find -type f -name '*-with-dependencies.jar' | xargs java -jar"]
diff --git a/vendor/Dockerfile/Ruby-alpine.Dockerfile b/vendor/Dockerfile/Ruby-alpine.Dockerfile
index dffe9a65116..0f748d84b5d 100644
--- a/vendor/Dockerfile/Ruby-alpine.Dockerfile
+++ b/vendor/Dockerfile/Ruby-alpine.Dockerfile
@@ -7,21 +7,21 @@ RUN apk --no-cache add nodejs postgresql-client tzdata
# throw errors if Gemfile has been modified since Gemfile.lock
RUN bundle config --global frozen 1
-RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
-COPY Gemfile Gemfile.lock /usr/src/app/
+COPY Gemfile Gemfile.lock .
# Install build dependencies - required for gems with native dependencies
RUN apk add --no-cache --virtual build-deps build-base postgresql-dev && \
bundle install && \
apk del build-deps
-COPY . /usr/src/app
+COPY . .
# For Sinatra
#EXPOSE 4567
#CMD ["ruby", "./config.rb"]
# For Rails
+ENV PORT 3000
EXPOSE 3000
CMD ["bundle", "exec", "rails", "server"]
diff --git a/vendor/gitignore/Global/Diff.gitignore b/vendor/gitignore/Global/Diff.gitignore
new file mode 100644
index 00000000000..59491b4440c
--- /dev/null
+++ b/vendor/gitignore/Global/Diff.gitignore
@@ -0,0 +1,2 @@
+*.patch
+*.diff
diff --git a/vendor/gitignore/Global/JetBrains.gitignore b/vendor/gitignore/Global/JetBrains.gitignore
index 0d95b087f19..343be1a3b8e 100644
--- a/vendor/gitignore/Global/JetBrains.gitignore
+++ b/vendor/gitignore/Global/JetBrains.gitignore
@@ -8,6 +8,9 @@
.idea/**/dictionaries
.idea/**/shelf
+# Generated files
+.idea/**/contentModel.xml
+
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
diff --git a/vendor/gitignore/Global/MicrosoftOffice.gitignore b/vendor/gitignore/Global/MicrosoftOffice.gitignore
index 0c203662d39..ddcc9cf6e38 100644
--- a/vendor/gitignore/Global/MicrosoftOffice.gitignore
+++ b/vendor/gitignore/Global/MicrosoftOffice.gitignore
@@ -3,6 +3,9 @@
# Word temporary
~$*.doc*
+# Word Auto Backup File
+Backup of *.doc*
+
# Excel temporary
~$*.xls*
diff --git a/vendor/gitignore/KiCad.gitignore b/vendor/gitignore/KiCad.gitignore
index 198392e551e..15fdf72ed48 100644
--- a/vendor/gitignore/KiCad.gitignore
+++ b/vendor/gitignore/KiCad.gitignore
@@ -9,7 +9,6 @@
*~
_autosave-*
*.tmp
-*-cache.lib
*-rescue.lib
*-save.pro
*-save.kicad_pcb
diff --git a/vendor/gitignore/Processing.gitignore b/vendor/gitignore/Processing.gitignore
index 85f269a89f6..333c0e0890a 100644
--- a/vendor/gitignore/Processing.gitignore
+++ b/vendor/gitignore/Processing.gitignore
@@ -1,5 +1,7 @@
.DS_Store
applet
+application.linux-arm64
+application.linux-armv6hf
application.linux32
application.linux64
application.windows32
diff --git a/vendor/gitignore/Python.gitignore b/vendor/gitignore/Python.gitignore
index 894a44cc066..6f7a6d9c3d7 100644
--- a/vendor/gitignore/Python.gitignore
+++ b/vendor/gitignore/Python.gitignore
@@ -38,6 +38,7 @@ pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
+.nox/
.coverage
.coverage.*
.cache
@@ -72,6 +73,10 @@ target/
# Jupyter Notebook
.ipynb_checkpoints
+# IPython
+profile_default/
+ipython_config.py
+
# pyenv
.python-version
@@ -102,3 +107,5 @@ venv.bak/
# mypy
.mypy_cache/
+.dmypy.json
+dmypy.json
diff --git a/vendor/gitignore/Rails.gitignore b/vendor/gitignore/Rails.gitignore
index e62f78e17bc..78eb74fdc26 100644
--- a/vendor/gitignore/Rails.gitignore
+++ b/vendor/gitignore/Rails.gitignore
@@ -47,3 +47,15 @@ bower.json
# Ignore node_modules
node_modules/
+# Ignore precompiled javascript packs
+/public/packs
+/public/packs-test
+
+# Ignore yarn files
+/yarn-error.log
+yarn-debug.log*
+.yarn-integrity
+
+# Ignore uploaded files in development
+/storage/*
+!/storage/.keep \ No newline at end of file
diff --git a/vendor/gitignore/Swift.gitignore b/vendor/gitignore/Swift.gitignore
index b8e04d98e33..7b0d62bc23a 100644
--- a/vendor/gitignore/Swift.gitignore
+++ b/vendor/gitignore/Swift.gitignore
@@ -69,3 +69,10 @@ fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output
+
+# Code Injection
+#
+# After new code Injection tools there's a generated folder /iOSInjectionProject
+# https://github.com/johnno1962/injectionforxcode
+
+iOSInjectionProject/
diff --git a/vendor/gitignore/Symfony.gitignore b/vendor/gitignore/Symfony.gitignore
index d098259ffb0..3dab634c188 100644
--- a/vendor/gitignore/Symfony.gitignore
+++ b/vendor/gitignore/Symfony.gitignore
@@ -15,6 +15,10 @@
!var/logs/.gitkeep
!var/sessions/.gitkeep
+# Logs (Symfony4)
+/var/log/*
+!var/log/.gitkeep
+
# Parameters
/app/config/parameters.yml
/app/config/parameters.ini
diff --git a/vendor/gitignore/TeX.gitignore b/vendor/gitignore/TeX.gitignore
index 79a66f9ebfa..ff87d483645 100644
--- a/vendor/gitignore/TeX.gitignore
+++ b/vendor/gitignore/TeX.gitignore
@@ -188,6 +188,9 @@ sympy-plots-for-*.tex/
*.pytxcode
pythontex-files-*/
+# tcolorbox
+*.listing
+
# thmtools
*.loe
diff --git a/vendor/gitignore/Terraform.gitignore b/vendor/gitignore/Terraform.gitignore
index d9397e2d7d9..a8935803468 100644
--- a/vendor/gitignore/Terraform.gitignore
+++ b/vendor/gitignore/Terraform.gitignore
@@ -13,3 +13,14 @@ crash.log
# version control.
#
# example.tfvars
+
+# Ignore override files as they are usually used to override resources locally and so
+# are not checked in
+override.tf
+override.tf.json
+*_override.tf
+*_override.tf.json
+
+# Include override files you do wish to add to version control using negated pattern
+#
+# !example_override.tf
diff --git a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml
index 893ab9efa2a..0b362fa0bee 100644
--- a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml
@@ -51,6 +51,8 @@ variables:
KUBERNETES_VERSION: 1.8.6
HELM_VERSION: 2.6.1
+ DOCKER_DRIVER: overlay2
+
stages:
- build
- test
@@ -67,8 +69,6 @@ build:
image: docker:stable-git
services:
- docker:stable-dind
- variables:
- DOCKER_DRIVER: overlay2
script:
- setup_docker
- build
@@ -95,8 +95,6 @@ test:
code_quality:
stage: test
image: docker:stable
- variables:
- DOCKER_DRIVER: overlay2
allow_failure: true
services:
- docker:stable-dind
@@ -114,8 +112,6 @@ code_quality:
license_management:
stage: test
image: docker:stable
- variables:
- DOCKER_DRIVER: overlay2
allow_failure: true
services:
- docker:stable-dind
@@ -133,8 +129,6 @@ license_management:
performance:
stage: performance
image: docker:stable
- variables:
- DOCKER_DRIVER: overlay2
allow_failure: true
services:
- docker:stable-dind
@@ -156,8 +150,6 @@ performance:
sast:
stage: test
image: docker:stable
- variables:
- DOCKER_DRIVER: overlay2
allow_failure: true
services:
- docker:stable-dind
@@ -175,8 +167,6 @@ sast:
dependency_scanning:
stage: test
image: docker:stable
- variables:
- DOCKER_DRIVER: overlay2
allow_failure: true
services:
- docker:stable-dind
@@ -194,8 +184,6 @@ dependency_scanning:
container_scanning:
stage: test
image: docker:stable
- variables:
- DOCKER_DRIVER: overlay2
allow_failure: true
services:
- docker:stable-dind
diff --git a/vendor/gitlab-ci-yml/Maven.gitlab-ci.yml b/vendor/gitlab-ci-yml/Maven.gitlab-ci.yml
index 5f9c9b2c965..d61ff239e13 100644
--- a/vendor/gitlab-ci-yml/Maven.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Maven.gitlab-ci.yml
@@ -17,7 +17,7 @@
variables:
# This will supress any download for dependencies and plugins or upload messages which would clutter the console log.
# `showDateTime` will show the passed time in milliseconds. You need to specify `--batch-mode` to make this work.
- MAVEN_OPTS: "-Dhttps.protocols=TLSv1.2 -Dmaven.repo.local=.m2/repository -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN -Dorg.slf4j.simpleLogger.showDateTime=true -Djava.awt.headless=true"
+ MAVEN_OPTS: "-Dhttps.protocols=TLSv1.2 -Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN -Dorg.slf4j.simpleLogger.showDateTime=true -Djava.awt.headless=true"
# As of Maven 3.3.0 instead of this you may define these options in `.mvn/maven.config` so the same config is used
# when running from the command line.
# `installAtEnd` and `deployAtEnd` are only effective with recent version of the corresponding plugins.
diff --git a/vendor/gitlab-ci-yml/Pages/Middleman.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Middleman.gitlab-ci.yml
index 983d7b5250e..9f4cc0574d6 100644
--- a/vendor/gitlab-ci-yml/Pages/Middleman.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Pages/Middleman.gitlab-ci.yml
@@ -1,25 +1,24 @@
# Full project: https://gitlab.com/pages/middleman
-image: ruby:2.4
-variables:
- LANG: "C.UTF-8"
+image: ruby:2.3
cache:
paths:
- vendor
-before_script:
+test:
+ script:
- apt-get update -yqqq
- apt-get install -y nodejs
- bundle install --path vendor
-
-test:
- script:
- bundle exec middleman build
except:
- master
pages:
script:
+ - apt-get update -yqqq
+ - apt-get install -y nodejs
+ - bundle install --path vendor
- bundle exec middleman build
artifacts:
paths:
diff --git a/vendor/gitlab-ci-yml/Swift.gitlab-ci.yml b/vendor/gitlab-ci-yml/Swift.gitlab-ci.yml
index 10d0b05d9f8..ba8a802ba4f 100644
--- a/vendor/gitlab-ci-yml/Swift.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Swift.gitlab-ci.yml
@@ -1,5 +1,5 @@
# Lifted from: https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/
-# This file assumes an own GitLab CI runner, setup on an macOS system.
+# This file assumes an own GitLab CI runner, setup on a macOS system.
stages:
- build
- archive
diff --git a/vendor/licenses.csv b/vendor/licenses.csv
index 0079f1e38e8..85b7d16db54 100644
--- a/vendor/licenses.csv
+++ b/vendor/licenses.csv
@@ -1,22 +1,7 @@
-@babel/code-frame,7.0.0-beta.44,MIT
-@babel/generator,7.0.0-beta.44,MIT
-@babel/helper-function-name,7.0.0-beta.44,MIT
-@babel/helper-get-function-arity,7.0.0-beta.44,MIT
-@babel/helper-split-export-declaration,7.0.0-beta.44,MIT
-@babel/highlight,7.0.0-beta.44,MIT
-@babel/template,7.0.0-beta.44,MIT
-@babel/traverse,7.0.0-beta.44,MIT
-@babel/types,7.0.0-beta.44,MIT
-@gitlab-org/gitlab-svgs,1.27.0,MIT
-@gitlab-org/gitlab-svgs,1.28.0,MIT
-@gitlab-org/gitlab-ui,1.0.5,MIT
+@gitlab-org/gitlab-svgs,1.29.0,MIT
+@gitlab-org/gitlab-ui,1.1.0,MIT
@sindresorhus/is,0.7.0,MIT
-@types/events,1.2.0,MIT
-@types/glob,5.0.35,MIT
@types/jquery,2.0.48,MIT
-@types/minimatch,3.0.3,MIT
-@types/node,10.5.2,MIT
-@types/parse5,5.0.0,MIT
@vue/component-compiler-utils,1.2.1,MIT
@webassemblyjs/ast,1.5.13,MIT
@webassemblyjs/floating-point-hex-parser,1.5.13,MIT
@@ -37,15 +22,11 @@
@webassemblyjs/wast-parser,1.5.13,MIT
@webassemblyjs/wast-printer,1.5.13,MIT
RedCloth,4.3.2,MIT
-abbrev,1.0.9,ISC
abbrev,1.1.1,ISC
accepts,1.3.4,MIT
ace-rails-ap,4.1.2,MIT
-acorn,3.3.0,MIT
-acorn,5.6.2,MIT
acorn,5.7.1,MIT
acorn-dynamic-import,3.0.0,MIT
-acorn-jsx,3.0.1,MIT
actionmailer,4.2.10,MIT
actionpack,4.2.10,MIT
actionview,4.2.10,MIT
@@ -55,79 +36,44 @@ activerecord,4.2.10,MIT
activesupport,4.2.10,MIT
acts-as-taggable-on,5.0.0,MIT
addressable,2.5.2,Apache 2.0
-addressparser,1.0.1,MIT
aes_key_wrap,1.0.1,MIT
-after,0.8.2,MIT
-agent-base,4.2.1,MIT
-ajv,5.5.2,MIT
ajv,6.1.1,MIT
-ajv-keywords,2.1.1,MIT
ajv-keywords,3.1.0,MIT
akismet,2.0.0,MIT
-align-text,0.1.4,MIT
-amdefine,1.0.1,BSD-3-Clause OR MIT
-amqplib,0.5.2,MIT
-ansi-align,2.0.0,ISC
ansi-escapes,1.4.0,MIT
ansi-escapes,3.0.0,MIT
-ansi-html,0.0.7,Apache 2.0
ansi-regex,2.1.1,MIT
ansi-regex,3.0.0,MIT
ansi-styles,2.2.1,MIT
ansi-styles,3.2.1,MIT
anymatch,2.0.0,ISC
-append-transform,0.4.0,MIT
aproba,1.2.0,ISC
are-we-there-yet,1.1.4,ISC
arel,6.0.4,MIT
-argparse,1.0.9,MIT
arr-diff,4.0.0,MIT
arr-flatten,1.1.0,MIT
arr-union,3.1.0,MIT
-array-find,1.0.0,MIT
-array-find-index,1.0.2,MIT
array-flatten,1.1.1,MIT
-array-flatten,2.1.1,MIT
-array-includes,3.0.3,MIT
-array-slice,0.2.3,MIT
-array-union,1.0.2,MIT
array-uniq,1.0.3,MIT
-array-unique,0.2.1,MIT
array-unique,0.3.2,MIT
-arraybuffer.slice,0.0.7,MIT
-arrify,1.0.1,MIT
asana,0.6.0,MIT
asciidoctor,1.5.6.2,MIT
asciidoctor-plantuml,0.0.8,MIT
-asn1,0.2.3,MIT
asn1.js,4.10.1,MIT
assert,1.4.1,MIT
-assert-plus,0.2.0,MIT
-assert-plus,1.0.0,MIT
asset_sync,2.4.0,MIT
assign-symbols,1.0.0,MIT
-ast-types,0.11.3,MIT
-async,1.5.2,MIT
-async,2.6.0,MIT
-async,2.6.1,MIT
async-each,1.0.1,MIT
async-limiter,1.0.0,MIT
-asynckit,0.4.0,MIT
atob,2.0.3,(MIT OR Apache-2.0)
atomic,1.1.99,Apache 2.0
attr_encrypted,3.1.0,MIT
attr_required,1.0.0,MIT
autosize,4.0.0,MIT
-aws-sign2,0.6.0,Apache 2.0
-aws-sign2,0.7.0,Apache 2.0
-aws4,1.6.0,MIT
axiom-types,0.1.1,MIT
-axios,0.15.3,MIT
axios,0.17.1,MIT
-axios-mock-adapter,1.15.0,MIT
babel-code-frame,6.26.0,MIT
babel-core,6.26.3,MIT
-babel-eslint,8.2.3,MIT
babel-generator,6.26.0,MIT
babel-helper-bindify-decorators,6.24.1,MIT
babel-helper-builder-binary-assignment-operator-visitor,6.24.1,MIT
@@ -146,8 +92,6 @@ babel-helpers,6.24.1,MIT
babel-loader,7.1.5,MIT
babel-messages,6.23.0,MIT
babel-plugin-check-es2015-constants,6.22.0,MIT
-babel-plugin-istanbul,4.1.6,New BSD
-babel-plugin-rewire,1.1.0,ISC
babel-plugin-syntax-async-functions,6.13.0,MIT
babel-plugin-syntax-async-generators,6.13.0,MIT
babel-plugin-syntax-class-properties,6.13.0,MIT
@@ -201,46 +145,29 @@ babel-traverse,6.26.0,MIT
babel-types,6.26.0,MIT
babosa,1.0.2,MIT
babylon,6.18.0,MIT
-babylon,7.0.0-beta.44,MIT
-backo2,1.0.2,MIT
balanced-match,1.0.0,MIT
base,0.11.2,MIT
base32,0.3.2,MIT
-base64-arraybuffer,0.1.5,MIT
base64-js,1.2.3,MIT
-base64id,1.0.0,MIT
-batch,0.6.1,MIT
batch-loader,1.2.1,MIT
bcrypt,3.1.12,MIT
-bcrypt-pbkdf,1.0.1,New BSD
bcrypt_pbkdf,1.0.0,MIT
-better-assert,1.0.2,MIT
bfj-node4,5.2.1,MIT
big.js,3.1.3,MIT
binary-extensions,1.11.0,MIT
binaryextensions,2.1.1,MIT
bindata,2.4.3,ruby
-bitsyntax,0.0.4,MIT
-bl,1.1.2,MIT
blackst0ne-mermaid,7.1.0-fixed,MIT
-blob,0.0.4,MIT*
bluebird,3.5.1,MIT
bn.js,4.11.8,MIT
body-parser,1.18.2,MIT
-bonjour,3.5.0,MIT
-boom,2.10.1,New BSD
-boom,4.3.1,New BSD
-boom,5.2.0,New BSD
-bootstrap,4.1.1,MIT
bootstrap,4.1.2,MIT
bootstrap-vue,2.0.0-rc.11,MIT
bootstrap_form,2.7.0,MIT
-boxen,1.3.0,MIT
brace-expansion,1.1.11,MIT
-braces,0.1.5,MIT
braces,2.3.1,MIT
brorand,1.1.0,MIT
-browser,2.2.0,MIT
+browser,2.5.3,MIT
browserify-aes,1.1.1,MIT
browserify-cipher,1.0.0,MIT
browserify-des,1.0.0,MIT
@@ -249,32 +176,17 @@ browserify-sign,4.0.4,ISC
browserify-zlib,0.2.0,MIT
buffer,4.9.1,MIT
buffer-from,1.0.0,MIT
-buffer-indexof,1.1.0,MIT
-buffer-more-ints,0.0.2,MIT
buffer-xor,1.0.3,MIT
builder,3.2.3,MIT
-buildmail,4.0.1,MIT
-builtin-modules,1.1.1,MIT
builtin-status-codes,3.0.0,MIT
-bytes,2.5.0,MIT
bytes,3.0.0,MIT
cacache,10.0.4,ISC
cache-base,1.0.1,MIT
cache-loader,1.2.2,MIT
cacheable-request,2.1.4,MIT
-caller-path,0.1.0,MIT
-callsite,1.0.0,MIT*
-callsites,0.2.0,MIT
-camelcase,1.2.1,MIT
-camelcase,2.1.1,MIT
camelcase,4.1.0,MIT
-camelcase-keys,2.1.0,MIT
-capture-stack-trace,1.0.0,MIT
carrierwave,1.2.3,MIT
-caseless,0.11.0,Apache 2.0
-caseless,0.12.0,Apache 2.0
cause,0.1,MIT
-center-align,0.1.3,MIT
chalk,1.1.3,MIT
chalk,2.4.1,MIT
chardet,0.4.2,MIT
@@ -283,7 +195,6 @@ charenc,0.0.2,New BSD
charlock_holmes,0.7.6,MIT
chart.js,1.0.2,MIT
check-types,7.3.0,MIT
-chokidar,2.0.2,MIT
chokidar,2.0.4,MIT
chownr,1.0.1,ISC
chrome-trace-event,1.0.0,MIT
@@ -291,19 +202,14 @@ chronic,0.10.2,MIT
chronic_duration,0.10.6,MIT
chunky_png,1.3.5,MIT
cipher-base,1.0.4,MIT
-circular-json,0.3.3,MIT
-circular-json,0.5.5,MIT
citrus,3.0.2,MIT
class-utils,0.3.6,MIT
classlist-polyfill,1.2.0,Unlicense
-cli-boxes,1.0.0,MIT
cli-cursor,2.1.0,MIT
cli-width,2.1.0,ISC
clipboard,1.7.1,MIT
-cliui,2.1.0,ISC
cliui,4.0.0,ISC
clone-response,1.0.2,MIT
-co,4.6.0,MIT
code-point-at,1.1.0,MIT
codesandbox-api,0.0.18,MIT
codesandbox-import-util-types,1.2.11,LGPL
@@ -312,31 +218,20 @@ coercible,1.0.0,MIT
collection-visit,1.0.0,MIT
color-convert,1.9.1,MIT
color-name,1.1.2,MIT
-colors,1.1.2,MIT
-combine-lists,1.0.1,MIT
-combined-stream,1.0.6,MIT
commander,2.13.0,MIT
commander,2.15.1,MIT
commondir,1.0.1,MIT
commonmarker,0.17.8,MIT
-component-bind,1.0.0,MIT*
component-emitter,1.2.1,MIT
-component-inherit,0.0.3,MIT*
-compressible,2.0.11,MIT
-compression,1.7.0,MIT
compression-webpack-plugin,1.1.11,MIT
concat-map,0.0.1,MIT
concat-stream,1.6.2,MIT
concurrent-ruby-ext,1.0.5,MIT
-configstore,3.1.1,Simplified BSD
-connect,3.6.6,MIT
-connect-history-api-fallback,1.3.0,MIT
connection_pool,2.2.1,MIT
console-browserify,1.1.0,MIT
console-control-strings,1.1.0,ISC
consolidate,0.15.1,MIT
constants-browserify,1.0.0,MIT
-contains-path,0.1.0,MIT
content-disposition,0.5.2,MIT
content-type,1.0.4,MIT
convert-source-map,1.5.1,MIT
@@ -350,7 +245,6 @@ core-util-is,1.0.2,MIT
crack,0.4.3,MIT
crass,1.0.4,MIT
create-ecdh,4.0.0,MIT
-create-error-class,3.0.2,MIT
create-hash,1.1.3,MIT
create-hmac,1.1.6,MIT
creole,0.5.0,ruby
@@ -358,17 +252,11 @@ cropper,2.3.0,MIT
cross-spawn,5.1.0,MIT
cross-spawn,6.0.5,MIT
crypt,0.0.2,New BSD
-cryptiles,2.0.5,New BSD
-cryptiles,3.1.2,New BSD
crypto-browserify,3.12.0,MIT
-crypto-random-string,1.0.0,MIT
css-loader,1.0.0,MIT
-css-selector-parser,1.3.0,MIT
css-selector-tokenizer,0.7.0,MIT
css_parser,1.5.0,MIT
cssesc,0.1.0,MIT
-currently-unhandled,0.4.1,MIT
-custom-event,1.0.1,MIT
cyclist,0.2.2,MIT*
d3,3.5.17,New BSD
d3,4.12.2,New BSD
@@ -404,13 +292,9 @@ d3-voronoi,1.1.2,New BSD
d3-zoom,1.7.1,New BSD
dagre-d3-renderer,0.4.24,MIT
dagre-layout,0.8.0,MIT
-dashdash,1.14.1,MIT
-data-uri-to-buffer,1.2.0,MIT
-date-format,1.2.0,MIT
date-now,0.1.4,MIT
dateformat,3.0.3,MIT
de-indent,1.0.2,MIT
-debug,2.6.8,MIT
debug,2.6.9,MIT
debug,3.1.0,MIT
debugger-ruby_core_source,1.3.8,MIT
@@ -420,44 +304,27 @@ declarative,0.0.10,MIT
declarative-option,0.1.0,MIT
decode-uri-component,0.2.0,MIT
decompress-response,3.3.0,MIT
-deep-equal,1.0.1,MIT
deep-extend,0.4.2,MIT
-deep-is,0.1.3,MIT
-default-require-extensions,1.0.0,MIT
default_value_for,3.0.2,MIT
-define-properties,1.1.2,MIT
define-property,0.2.5,MIT
define-property,1.0.0,MIT
define-property,2.0.2,MIT
-degenerator,1.0.4,MIT
-del,2.2.2,MIT
-del,3.0.0,MIT
-delayed-stream,1.0.0,MIT
delegate,3.1.2,MIT
delegates,1.0.0,MIT
depd,1.1.1,MIT
-depd,1.1.2,MIT
des.js,1.0.0,MIT
descendants_tracker,0.0.4,MIT
destroy,1.0.4,MIT
detect-indent,4.0.0,MIT
detect-libc,1.0.3,Apache 2.0
-detect-node,2.0.3,ISC
device_detector,1.0.0,LGPL
devise,4.4.3,MIT
devise-two-factor,3.0.0,MIT
-di,0.0.1,MIT
diff,3.5.0,New BSD
diff-lcs,1.3,"MIT,Artistic-2.0,GPL-2.0+"
diffie-hellman,5.0.2,MIT
diffy,3.1.0,MIT
-dns-equal,1.0.0,MIT
-dns-packet,1.2.2,MIT
-dns-txt,2.0.2,MIT
-doctrine,1.5.0,Simplified BSD
-doctrine,2.1.0,Apache 2.0
document-register-element,1.3.0,MIT
-dom-serialize,2.2.1,MIT
dom-serializer,0.1.0,MIT
domain-browser,1.1.7,MIT
domain_name,0.5.20180417,"Simplified BSD,New BSD,Mozilla Public License 2.0"
@@ -468,13 +335,11 @@ domutils,1.6.2,Simplified BSD
doorkeeper,4.3.2,MIT
doorkeeper-openid_connect,1.5.0,MIT
dot-prop,4.2.0,MIT
-double-ended-queue,2.1.0-0,MIT
dropzone,4.2.0,MIT
dropzonejs-rails,0.7.2,MIT
duplexer,0.1.1,MIT
duplexer3,0.1.4,New BSD
duplexify,3.5.3,MIT
-ecc-jsbn,0.1.1,MIT
ed25519,1.2.4,MIT
editions,1.3.4,MIT
ee-first,1.1.1,MIT
@@ -487,102 +352,52 @@ encodeurl,1.0.2,MIT
encoding,0.1.12,MIT
encryptor,3.0.0,MIT
end-of-stream,1.4.1,MIT
-engine.io,3.1.5,MIT
-engine.io-client,3.1.5,MIT
-engine.io-parser,2.1.2,MIT
-enhanced-resolve,0.9.1,MIT
-enhanced-resolve,4.0.0,MIT
enhanced-resolve,4.1.0,MIT
-ent,2.2.0,MIT
entities,1.1.1,Simplified BSD
equalizer,0.0.11,MIT
-errno,0.1.4,MIT
errno,0.1.7,MIT
-error-ex,1.3.0,MIT
erubis,2.7.0,MIT
-es-abstract,1.10.0,MIT
-es-to-primitive,1.1.1,MIT
es6-promise,3.0.2,MIT
-es6-promise,4.2.4,MIT
-es6-promisify,5.0.0,MIT
escape-html,1.0.3,MIT
escape-string-regexp,1.0.5,MIT
escape_utils,1.1.1,MIT
-escodegen,1.8.1,Simplified BSD
-escodegen,1.9.0,Simplified BSD
-eslint,4.12.1,MIT
-eslint-config-airbnb-base,12.1.0,MIT
-eslint-import-resolver-node,0.3.2,MIT
-eslint-import-resolver-webpack,0.10.0,MIT
-eslint-module-utils,2.2.0,MIT
-eslint-plugin-filenames,1.2.0,MIT
-eslint-plugin-html,4.0.3,ISC
-eslint-plugin-import,2.12.0,MIT
-eslint-plugin-jasmine,2.2.0,MIT
-eslint-plugin-promise,3.8.0,ISC
-eslint-plugin-vue,4.5.0,MIT
-eslint-restricted-globals,0.1.1,MIT
eslint-scope,3.7.1,Simplified BSD
-eslint-visitor-keys,1.0.0,Apache 2.0
-espree,3.5.4,Simplified BSD
-esprima,2.7.3,Simplified BSD
-esprima,3.1.3,Simplified BSD
-esprima,4.0.0,Simplified BSD
-esquery,1.0.1,New BSD
esrecurse,4.2.1,Simplified BSD
-estraverse,1.9.3,Simplified BSD
estraverse,4.2.0,Simplified BSD
esutils,2.0.2,Simplified BSD
et-orbi,1.0.3,MIT
etag,1.8.1,MIT
eve-raphael,0.5.0,Apache 2.0
-event-stream,3.3.4,MIT
-eventemitter3,1.2.0,MIT
events,1.1.1,MIT
-eventsource,0.1.6,MIT
evp_bytestokey,1.0.3,MIT
excon,0.62.0,MIT
execa,0.7.0,MIT
execjs,2.6.0,MIT
-expand-braces,0.1.2,MIT
expand-brackets,2.1.4,MIT
-expand-range,0.1.1,MIT
exports-loader,0.7.0,MIT
express,4.16.2,MIT
expression_parser,0.9.0,MIT
-extend,3.0.1,MIT
extend-shallow,2.0.1,MIT
extend-shallow,3.0.2,MIT
external-editor,2.2.0,MIT
external-editor,3.0.0,MIT
extglob,2.0.4,MIT
-extsprintf,1.3.0,MIT
-extsprintf,1.4.0,MIT
faraday,0.12.2,MIT
faraday_middleware,0.12.2,MIT
faraday_middleware-multi_json,0.0.6,MIT
fast-deep-equal,1.0.0,MIT
fast-json-stable-stringify,2.0.0,MIT
-fast-levenshtein,2.0.6,MIT
fast_blank,1.0.0,MIT
fast_gettext,1.6.0,"MIT,ruby"
fastparse,1.1.1,MIT
-faye-websocket,0.10.0,MIT
-faye-websocket,0.11.1,MIT
-ffi,1.9.18,New BSD
+ffi,1.9.25,New BSD
figures,2.0.0,MIT
-file-entry-cache,2.0.0,MIT
file-loader,1.1.11,MIT
-file-uri-to-path,1.0.0,MIT
-fileset,2.0.3,MIT
filesize,3.6.0,New BSD
fill-range,4.0.0,MIT
finalhandler,1.1.0,MIT
find-cache-dir,1.0.0,MIT
-find-root,1.1.0,MIT
-find-up,1.1.2,MIT
find-up,2.1.0,MIT
-flat-cache,1.2.2,MIT
flipper,0.13.0,MIT
flipper-active_record,0.13.0,MIT
flipper-active_support_cache_store,0.13.0,MIT
@@ -597,46 +412,29 @@ fog-local,0.3.1,MIT
fog-openstack,0.1.21,MIT
fog-rackspace,0.1.1,MIT
fog-xml,0.1.3,MIT
-follow-redirects,1.0.0,MIT
follow-redirects,1.2.6,MIT
font-awesome-rails,4.7.0.1,"MIT,SIL Open Font License"
for-in,1.0.2,MIT
-foreach,2.0.5,MIT
-forever-agent,0.6.1,Apache 2.0
-form-data,2.0.0,MIT
-form-data,2.3.2,MIT
formatador,0.2.5,MIT
formdata-polyfill,3.0.11,MIT
forwarded,0.1.2,MIT
fragment-cache,0.2.1,MIT
fresh,0.5.2,MIT
-from,0.1.7,MIT
from2,2.3.0,MIT
-fs-access,1.0.1,MIT
fs-minipass,1.2.5,ISC
fs-write-stream-atomic,1.0.10,ISC
fs.realpath,1.0.0,ISC
fsevents,1.2.4,MIT
-ftp,0.3.10,MIT
-function-bind,1.1.1,MIT
-functional-red-black-tree,1.0.1,MIT
fuzzaldrin-plus,0.5.0,MIT
gauge,2.7.4,ISC
gemojione,3.3.0,MIT
-generate-function,2.0.0,MIT
-generate-object-property,1.2.0,MIT
get-caller-file,1.0.2,ISC
-get-stdin,4.0.1,MIT
get-stream,3.0.0,MIT
-get-uri,2.0.2,MIT
get-value,2.0.6,MIT
get_process_mem,0.2.0,MIT
-getpass,0.1.7,MIT
-gettext-extractor,3.3.2,MIT
-gettext-extractor-vue,4.0.1,MIT
gettext_i18n_rails,1.8.0,MIT
gettext_i18n_rails_js,1.3.0,MIT
-gitaly-proto,0.113.0,MIT
+gitaly-proto,0.117.0,MIT
github-linguist,5.3.3,MIT
github-markup,1.7.0,MIT
gitlab-flowdock-git-hook,1.0.1,MIT
@@ -645,16 +443,11 @@ gitlab-gollum-rugged_adapter,0.4.4.1,MIT
gitlab-grit,2.8.2,MIT
gitlab-markup,1.6.4,MIT
gitlab_omniauth-ldap,2.0.4,MIT
-glob,5.0.15,ISC
glob,7.1.2,ISC
glob-parent,3.1.0,ISC
-global-dirs,0.1.1,MIT
global-modules-path,2.1.0,Apache 2.0
globalid,0.4.1,MIT
-globals,11.5.0,MIT
globals,9.18.0,MIT
-globby,5.0.0,MIT
-globby,6.1.0,MIT
gollum-grit_adapter,1.0.1,MIT
gon,6.2.0,MIT
good-listener,1.2.2,MIT
@@ -662,7 +455,6 @@ google-api-client,0.23.4,Apache 2.0
google-protobuf,3.5.1,New BSD
googleapis-common-protos-types,1.0.1,Apache 2.0
googleauth,0.6.2,Apache 2.0
-got,6.7.1,MIT
got,8.3.0,MIT
gpgme,2.0.13,LGPL-2.1+
graceful-fs,4.1.11,ISC
@@ -676,17 +468,8 @@ graphql,1.8.1,MIT
grpc,1.11.0,Apache 2.0
gzip-size,4.1.0,MIT
hamlit,2.8.8,MIT
-handle-thing,1.2.5,MIT
-handlebars,4.0.6,MIT
hangouts-chat,0.0.5,MIT
-har-schema,2.0.0,ISC
-har-validator,2.0.6,ISC
-har-validator,5.0.3,ISC
-has,1.0.1,MIT
has-ansi,2.0.0,MIT
-has-binary2,1.0.2,MIT
-has-cors,1.1.0,MIT
-has-flag,1.0.0,MIT
has-flag,3.0.0,MIT
has-symbol-support-x,1.3.0,MIT
has-to-string-tag-x,1.3.0,MIT
@@ -701,19 +484,11 @@ hash-sum,1.0.2,MIT
hash.js,1.1.3,MIT
hashie,3.5.7,MIT
hashie-forbidden_attributes,0.1.1,MIT
-hawk,3.1.3,New BSD
-hawk,6.0.2,New BSD
he,1.1.1,MIT
health_check,2.6.0,MIT
hipchat,1.5.2,MIT
-hipchat-notifier,1.1.0,MIT
hmac-drbg,1.0.1,MIT
-hoek,2.16.3,New BSD
-hoek,4.2.1,New BSD
home-or-tmp,2.0.0,MIT
-hosted-git-info,2.2.0,ISC
-hpack.js,2.1.6,MIT
-html-entities,1.2.0,MIT
html-pipeline,2.8.4,MIT
html2text,0.2.0,MIT
htmlentities,4.3.4,MIT
@@ -721,71 +496,47 @@ htmlparser2,3.9.2,MIT
http,2.2.2,MIT
http-cache-semantics,3.8.1,Simplified BSD
http-cookie,1.0.3,MIT
-http-deceiver,1.2.7,MIT
http-errors,1.6.2,MIT
-http-errors,1.6.3,MIT
http-form_data,1.0.3,MIT
-http-proxy,1.16.2,MIT
-http-proxy-agent,2.1.0,MIT
-http-proxy-middleware,0.18.0,MIT
-http-signature,1.1.1,MIT
-http-signature,1.2.0,MIT
http_parser.rb,0.6.0,MIT
httparty,0.13.7,MIT
httpclient,2.8.3,ruby
-httpntlm,1.6.1,MIT
-httpreq,0.4.24,MIT
https-browserify,1.0.0,MIT
-https-proxy-agent,2.2.1,MIT
i18n,0.9.5,MIT
icalendar,2.4.1,ruby
ice_nine,0.11.2,MIT
-iconv-lite,0.4.15,MIT
iconv-lite,0.4.19,MIT
iconv-lite,0.4.23,MIT
icss-replace-symbols,1.1.0,ISC
icss-utils,2.1.0,ISC
ieee754,1.1.11,New BSD
iferr,0.1.5,MIT
-ignore,3.3.8,MIT
-ignore-by-default,1.0.1,ISC
ignore-walk,3.0.1,ISC
immediate,3.0.6,MIT
-import-lazy,2.1.0,MIT
import-local,1.0.0,MIT
imports-loader,0.8.0,MIT
imurmurhash,0.1.4,MIT
-indent-string,2.1.0,MIT
indexes-of,1.0.1,MIT
indexof,0.0.1,MIT*
-inflection,1.12.0,MIT
-inflection,1.3.8,MIT
inflight,1.0.6,ISC
influxdb,0.2.3,MIT
inherits,2.0.1,ISC
inherits,2.0.3,ISC
ini,1.3.5,ISC
inquirer,3.0.6,MIT
-inquirer,3.3.0,MIT
inquirer,6.0.0,MIT
-internal-ip,1.2.0,MIT
interpret,1.1.0,MIT
into-stream,3.1.0,MIT
invariant,2.2.2,New BSD
invert-kv,1.0.0,MIT
-ip,1.1.5,MIT
ipaddr.js,1.6.0,MIT
ipaddress,0.8.3,MIT
is-accessor-descriptor,0.1.6,MIT
is-accessor-descriptor,1.0.0,MIT
-is-arrayish,0.2.1,MIT
is-binary-path,1.0.1,MIT
is-buffer,1.1.6,MIT
-is-builtin-module,1.0.0,MIT
-is-callable,1.1.3,MIT
is-data-descriptor,0.1.4,MIT
is-data-descriptor,1.0.0,MIT
-is-date-object,1.0.1,MIT
is-descriptor,0.1.6,MIT
is-descriptor,1.0.2,MIT
is-extendable,0.1.1,MIT
@@ -796,55 +547,23 @@ is-fullwidth-code-point,1.0.0,MIT
is-fullwidth-code-point,2.0.0,MIT
is-glob,3.1.0,MIT
is-glob,4.0.0,MIT
-is-installed-globally,0.1.0,MIT
-is-my-ip-valid,1.0.0,MIT
-is-my-json-valid,2.17.2,MIT
-is-npm,1.0.0,MIT
-is-number,0.1.1,MIT
is-number,3.0.0,MIT
is-number,4.0.0,MIT
is-obj,1.0.1,MIT
is-object,1.0.1,MIT
is-odd,2.0.0,MIT
-is-path-cwd,1.0.0,MIT
-is-path-in-cwd,1.0.0,MIT
-is-path-inside,1.0.0,MIT
is-plain-obj,1.1.0,MIT
is-plain-object,2.0.4,MIT
is-promise,2.1.0,MIT
-is-property,1.0.2,MIT
-is-redirect,1.0.0,MIT
-is-regex,1.0.4,MIT
-is-resolvable,1.0.0,MIT
is-retry-allowed,1.1.0,MIT
is-stream,1.1.0,MIT
-is-symbol,1.0.1,MIT
-is-typedarray,1.0.0,MIT
-is-utf8,0.2.1,MIT
is-windows,1.0.2,MIT
-is-wsl,1.1.0,MIT
-isarray,0.0.1,MIT
isarray,1.0.0,MIT
-isarray,2.0.1,MIT
-isbinaryfile,3.0.2,MIT
isexe,2.0.0,ISC
isobject,2.1.0,MIT
isobject,3.0.1,MIT
-isstream,0.1.2,MIT
-istanbul,0.4.5,New BSD
-istanbul-api,1.2.1,New BSD
-istanbul-lib-coverage,1.1.1,New BSD
-istanbul-lib-coverage,1.2.0,New BSD
-istanbul-lib-hook,1.1.0,New BSD
-istanbul-lib-instrument,1.10.1,New BSD
-istanbul-lib-report,1.1.2,New BSD
-istanbul-lib-source-maps,1.2.2,New BSD
-istanbul-reports,1.1.3,New BSD
istextorbinary,2.2.1,MIT
isurl,1.0.0,MIT
-jasmine-core,2.9.0,MIT
-jasmine-diff,0.1.3,MIT
-jasmine-jquery,2.1.1,MIT
jed,1.1.1,MIT
jira-ruby,1.4.1,MIT
jquery,3.3.1,MIT
@@ -853,24 +572,15 @@ jquery-ujs,1.2.2,MIT
jquery.waitforimages,2.2.0,MIT
js-cookie,2.1.3,MIT
js-tokens,3.0.2,MIT
-js-yaml,3.11.0,MIT
js_regex,2.2.1,MIT
-jsbn,0.1.1,MIT
jsesc,0.5.0,MIT
jsesc,1.3.0,MIT
-jsesc,2.5.1,MIT
json,1.8.6,ruby
json-buffer,3.0.0,MIT
json-jwt,1.9.4,MIT
json-parse-better-errors,1.0.2,MIT
-json-schema,0.2.3,BSD
json-schema-traverse,0.3.1,MIT
-json-stable-stringify-without-jsonify,1.0.1,MIT
-json-stringify-safe,5.0.1,ISC
-json3,3.3.2,MIT
json5,0.5.1,MIT
-jsonpointer,4.0.1,MIT
-jsprim,1.4.1,MIT
jszip,3.1.3,(MIT OR GPL-3.0)
jszip-utils,0.0.2,MIT or GPLv3
jwt,1.5.6,MIT
@@ -878,35 +588,19 @@ kaminari,1.0.1,MIT
kaminari-actionview,1.0.1,MIT
kaminari-activerecord,1.0.1,MIT
kaminari-core,1.0.1,MIT
-karma,2.0.4,MIT
-karma-chrome-launcher,2.2.0,MIT
-karma-coverage-istanbul-reporter,1.4.2,MIT
-karma-jasmine,1.1.2,MIT
-karma-mocha-reporter,2.2.5,MIT
-karma-sourcemap-loader,0.3.7,MIT
-karma-webpack,4.0.0-beta.0,MIT
katex,0.8.3,MIT
keyv,3.0.0,MIT
kgio,2.10.0,LGPL-2.1+
-killable,1.0.0,ISC
kind-of,3.2.2,MIT
kind-of,4.0.0,MIT
kind-of,5.1.0,MIT
kind-of,6.0.2,MIT
kubeclient,3.1.0,MIT
-latest-version,3.1.0,MIT
-lazy-cache,1.0.4,MIT
lazy-cache,2.0.2,MIT
lcid,1.0.0,MIT
-levn,0.3.0,MIT
-libbase64,0.1.0,MIT
-libmime,3.0.0,MIT
-libqp,1.1.0,MIT
licensee,8.9.2,MIT
lie,3.1.1,MIT
little-plugger,1.1.4,MIT
-load-json-file,1.1.0,MIT
-load-json-file,2.0.0,MIT
loader-runner,2.3.0,MIT
loader-utils,1.1.0,MIT
locale,2.1.2,"ruby,LGPLv3+"
@@ -919,37 +613,22 @@ lodash.debounce,4.0.8,MIT
lodash.escaperegexp,4.1.2,MIT
lodash.get,4.4.2,MIT
lodash.isequal,4.5.0,MIT
-lodash.kebabcase,4.1.1,MIT
lodash.mergewith,4.6.0,MIT
-lodash.snakecase,4.1.1,MIT
lodash.startcase,4.4.0,MIT
-lodash.upperfirst,4.3.1,MIT
-log-symbols,2.2.0,MIT
-log4js,2.11.0,Apache 2.0
logging,2.2.2,MIT
-loggly,1.1.1,MIT
-loglevel,1.4.1,MIT
-loglevelnext,1.0.3,MIT
lograge,0.10.0,MIT
long,3.2.0,Apache 2.0
long,4.0.0,Apache 2.0
-longest,1.0.1,MIT
loofah,2.2.2,MIT
loose-envify,1.3.1,MIT
-loud-rejection,1.6.0,MIT
lowercase-keys,1.0.0,MIT
-lru-cache,2.2.4,MIT
lru-cache,4.1.3,ISC
lz-string,1.4.4,WTFPL
mail,2.7.0,MIT
mail_room,0.9.1,MIT
-mailcomposer,4.0.1,MIT
-mailgun-js,0.18.1,MIT
make-dir,1.2.0,MIT
mamacro,0.0.3,MIT
map-cache,0.2.2,MIT
-map-obj,1.0.1,MIT
-map-stream,0.1.0,MIT
map-visit,1.0.0,MIT
marked,0.3.12,MIT
match-at,0.1.1,MIT
@@ -957,9 +636,7 @@ md5.js,1.3.4,MIT
media-typer,0.3.0,MIT
mem,1.1.0,MIT
memoist,0.16.0,MIT
-memory-fs,0.2.0,MIT
memory-fs,0.4.1,MIT
-meow,3.7.0,MIT
merge-descriptors,1.0.1,MIT
merge-source-map,1.1.0,MIT
method_source,0.9.0,MIT
@@ -967,7 +644,6 @@ methods,1.1.2,MIT
micromatch,3.1.10,MIT
miller-rabin,4.0.1,MIT
mime,1.4.1,MIT
-mime,1.6.0,MIT
mime,2.3.1,MIT
mime-db,1.33.0,MIT
mime-types,2.1.18,MIT
@@ -982,7 +658,6 @@ mini_portile2,2.3.0,MIT
minimalistic-assert,1.0.0,ISC
minimalistic-crypto-utils,1.0.1,MIT
minimatch,3.0.4,ISC
-minimist,0.0.10,MIT
minimist,0.0.8,MIT
minimist,1.2.0,MIT
minipass,2.3.3,ISC
@@ -991,8 +666,8 @@ mississippi,2.0.0,Simplified BSD
mixin-deep,1.3.1,MIT
mkdirp,0.5.1,MIT
moment,2.19.2,MIT
-monaco-editor,0.13.1,MIT
-monaco-editor-webpack-plugin,1.4.0,MIT
+monaco-editor,0.14.3,MIT
+monaco-editor-webpack-plugin,1.5.2,MIT
mousetrap,1.4.6,Apache 2.0
mousetrap-rails,1.4.6,"MIT,Apache"
move-concurrently,1.0.1,ISC
@@ -1000,8 +675,6 @@ ms,2.0.0,MIT
msgpack,1.2.4,Apache 2.0
multi_json,1.13.1,MIT
multi_xml,0.6.0,MIT
-multicast-dns,6.1.1,MIT
-multicast-dns-service-types,1.1.0,MIT
multipart-post,2.0.0,MIT
mustermann,1.0.2,MIT
mustermann-grape,1.0.0,MIT
@@ -1009,53 +682,33 @@ mute-stream,0.0.7,ISC
mysql2,0.4.10,MIT
nan,2.10.0,MIT
nanomatch,1.2.9,MIT
-natural-compare,1.4.0,MIT
needle,2.2.1,MIT
negotiator,0.6.1,MIT
neo-async,2.5.0,MIT
net-ldap,0.16.0,MIT
net-ssh,5.0.1,MIT
-netmask,1.0.6,MIT
netrc,0.11.0,MIT
nice-try,1.0.4,MIT
node-fetch,1.6.3,MIT
-node-forge,0.6.33,New BSD
node-libs-browser,2.1.0,MIT
node-pre-gyp,0.10.0,New BSD
-node-uuid,1.4.8,MIT
-nodemailer,2.7.2,MIT
-nodemailer-direct-transport,3.3.2,MIT
-nodemailer-fetch,1.6.0,MIT
-nodemailer-shared,1.1.0,MIT
-nodemailer-smtp-pool,2.8.2,MIT
-nodemailer-smtp-transport,2.7.2,MIT
-nodemailer-wellknown,0.1.10,MIT
-nodemon,1.18.2,MIT
nokogiri,1.8.4,MIT
nokogumbo,1.5.0,Apache 2.0
-nopt,1.0.10,MIT
-nopt,3.0.6,ISC
nopt,4.0.1,ISC
-normalize-package-data,2.4.0,Simplified BSD
normalize-path,2.1.1,MIT
normalize-url,2.0.1,MIT
npm-bundled,1.0.3,ISC
npm-packlist,1.1.10,ISC
npm-run-path,2.0.2,MIT
npmlog,4.1.2,ISC
-null-check,1.0.0,MIT
number-is-nan,1.0.1,MIT
numerizer,0.1.1,MIT
oauth,0.5.4,MIT
-oauth-sign,0.8.2,Apache 2.0
oauth2,1.4.0,MIT
object-assign,4.1.1,MIT
-object-component,0.0.3,MIT*
object-copy,0.1.0,MIT
-object-keys,1.0.11,MIT
object-visit,1.0.1,MIT
object.pick,1.3.0,MIT
-obuf,1.1.1,MIT
octokit,4.9.0,MIT
omniauth,1.8.1,MIT
omniauth-auth0,2.0.0,MIT
@@ -1076,17 +729,12 @@ omniauth-shibboleth,1.3.0,MIT
omniauth-twitter,1.4.0,MIT
omniauth_crowd,2.2.3,MIT
on-finished,2.3.0,MIT
-on-headers,1.0.1,MIT
once,1.4.0,ISC
onetime,2.0.1,MIT
opencollective,1.0.3,MIT
opener,1.4.3,(WTFPL OR MIT)
opn,4.0.2,MIT
-opn,5.2.0,MIT
-optimist,0.6.1,MIT
-optionator,0.8.2,MIT
org-ruby,0.9.12,MIT
-original,1.0.0,MIT
orm_adapter,0.5.0,MIT
os,0.9.6,MIT
os-browserify,0.3.0,MIT
@@ -1099,34 +747,19 @@ p-finally,1.0.0,MIT
p-is-promise,1.1.0,MIT
p-limit,1.2.0,MIT
p-locate,2.0.0,MIT
-p-map,1.1.1,MIT
p-timeout,2.0.1,MIT
p-try,1.0.0,MIT
-pac-proxy-agent,2.0.2,MIT
-pac-resolver,3.0.0,MIT
-package-json,4.0.1,MIT
pako,1.0.6,(MIT AND Zlib)
parallel-transform,1.1.0,MIT
parse-asn1,5.1.0,ISC
-parse-json,2.2.0,MIT
-parse5,5.0.0,MIT
-parseqs,0.0.5,MIT
-parseuri,0.0.5,MIT
parseurl,1.3.2,MIT
pascalcase,0.1.1,MIT
path-browserify,0.0.0,MIT
path-dirname,1.0.2,MIT
-path-exists,2.1.0,MIT
path-exists,3.0.0,MIT
path-is-absolute,1.0.1,MIT
-path-is-inside,1.0.2,(WTFPL OR MIT)
path-key,2.0.1,MIT
-path-parse,1.0.5,MIT
-path-proxy,1.0.0,MIT
path-to-regexp,0.1.7,MIT
-path-type,1.1.0,MIT
-path-type,2.0.0,MIT
-pause-stream,0.0.11,Apache 2.0
pbkdf2,3.0.14,MIT
peek,1.0.1,MIT
peek-gc,0.0.2,MIT
@@ -1135,23 +768,16 @@ peek-pg,1.3.0,MIT
peek-rblineprof,0.2.0,MIT
peek-redis,1.2.0,MIT
peek-sidekiq,1.0.3,MIT
-performance-now,2.1.0,MIT
pg,0.18.4,"BSD,ruby,GPL"
-pify,2.3.0,MIT
pify,3.0.0,MIT
pikaday,1.6.1,MIT
pinkie,2.0.4,MIT
pinkie-promise,2.0.1,MIT
-pkg-dir,1.0.0,MIT
pkg-dir,2.0.0,MIT
-pluralize,7.0.0,MIT
po_to_json,1.0.1,MIT
-pofile,1.0.11,MIT
popper.js,1.14.3,MIT
-portfinder,1.0.13,MIT
posix-character-classes,0.1.1,MIT
posix-spawn,0.3.13,MIT
-postcss,6.0.22,MIT
postcss,6.0.23,MIT
postcss-modules-extract-imports,1.2.0,ISC
postcss-modules-local-by-default,1.2.0,MIT
@@ -1159,10 +785,8 @@ postcss-modules-scope,1.1.0,ISC
postcss-modules-values,1.3.0,ISC
postcss-selector-parser,3.1.1,MIT
postcss-value-parser,3.3.0,MIT
-prelude-ls,1.1.2,MIT
premailer,1.10.4,New BSD
premailer-rails,1.9.7,MIT
-prepend-http,1.0.4,MIT
prepend-http,2.0.0,MIT
prettier,1.12.1,MIT
prismjs,1.6.0,MIT
@@ -1170,18 +794,11 @@ private,0.1.8,MIT
process,0.11.10,MIT
process-nextick-args,1.0.7,MIT
process-nextick-args,2.0.0,MIT
-progress,2.0.0,MIT
prometheus-client-mmap,0.9.4,Apache 2.0
promise-inflight,1.0.1,ISC
-promisify-call,2.0.4,MIT
proxy-addr,2.0.3,MIT
-proxy-agent,3.0.1,MIT
-proxy-from-env,1.0.0,MIT
-prr,0.0.0,MIT
prr,1.0.1,MIT
-ps-tree,1.1.0,MIT
pseudomap,1.0.2,ISC
-pstree.remy,1.1.0,MIT
public-encrypt,4.0.0,MIT
public_suffix,3.0.2,MIT
pump,2.0.1,MIT
@@ -1189,14 +806,10 @@ pumpify,1.4.0,MIT
punycode,1.3.2,MIT
punycode,1.4.1,MIT
pyu-ruby-sasl,0.0.3.3,MIT
-qjobs,1.2.0,MIT
-qs,6.2.3,New BSD
qs,6.5.1,New BSD
query-string,5.1.1,MIT
querystring,0.2.0,MIT
querystring-es3,0.2.1,MIT
-querystringify,0.0.4,MIT
-querystringify,1.0.0,MIT
rack,1.6.10,MIT
rack-accept,0.4.5,MIT
rack-attack,4.4.1,MIT
@@ -1220,7 +833,6 @@ range-parser,1.2.0,MIT
raphael,2.2.7,MIT
raven-js,3.22.1,Simplified BSD
raw-body,2.3.2,MIT
-raw-body,2.3.3,MIT
raw-loader,0.5.1,MIT
rb-fsevent,0.10.2,MIT
rb-inotify,0.9.10,MIT
@@ -1228,26 +840,16 @@ rbtrace,0.4.10,MIT
rc,1.2.5,(BSD-2-Clause OR MIT OR Apache-2.0)
rdoc,6.0.4,ruby
re2,1.1.1,New BSD
-read-pkg,1.1.0,MIT
-read-pkg,2.0.0,MIT
-read-pkg-up,1.0.1,MIT
-read-pkg-up,2.0.0,MIT
-readable-stream,1.1.14,MIT
readable-stream,2.0.6,MIT
-readable-stream,2.3.4,MIT
readable-stream,2.3.6,MIT
readdirp,2.1.0,MIT
recaptcha,3.0.0,MIT
recursive-open-struct,1.1.0,MIT
redcarpet,3.4.0,MIT
-redent,1.0.0,MIT
-redis,2.8.0,MIT
redis,3.3.5,MIT
redis-actionpack,5.0.2,MIT
redis-activesupport,5.0.4,MIT
-redis-commands,1.3.1,MIT
redis-namespace,1.6.0,MIT
-redis-parser,2.6.0,MIT
redis-rack,2.0.4,MIT
redis-rails,5.0.2,MIT
redis-store,1.4.1,MIT
@@ -1259,28 +861,17 @@ regex-not,1.0.2,MIT
regexp_parser,0.5.0,MIT
regexpu-core,1.0.0,MIT
regexpu-core,2.0.0,MIT
-registry-auth-token,3.3.2,MIT
-registry-url,3.1.0,MIT
regjsgen,0.2.0,MIT
regjsparser,0.1.5,Simplified BSD
remove-trailing-separator,1.1.0,ISC
repeat-element,1.1.2,MIT
-repeat-string,0.2.2,MIT
repeat-string,1.6.1,MIT
repeating,2.0.1,MIT
representable,3.0.4,MIT
-request,2.75.0,Apache 2.0
-request,2.83.0,Apache 2.0
request_store,1.3.1,MIT
-requestretry,1.13.0,MIT
require-directory,2.1.1,MIT
require-main-filename,1.0.1,ISC
-require-uncached,1.0.3,MIT
-requires-port,1.0.0,MIT
-resolve,1.1.7,MIT
-resolve,1.7.1,MIT
resolve-cwd,2.0.0,MIT
-resolve-from,1.0.1,MIT
resolve-from,3.0.0,MIT
resolve-url,0.2.1,MIT
responders,2.4.0,MIT
@@ -1289,7 +880,6 @@ rest-client,2.0.2,MIT
restore-cursor,2.0.0,MIT
ret,0.1.15,MIT
retriable,3.1.2,MIT
-right-align,0.1.3,MIT
rimraf,2.6.2,ISC
rinku,2.0.0,ISC
ripemd160,2.0.1,MIT
@@ -1311,8 +901,6 @@ run-async,2.3.0,MIT
run-queue,1.0.3,ISC
rw,1.3.3,New BSD
rx,4.1.0,Apache 2.0
-rx-lite,4.0.8,Apache 2.0
-rx-lite-aggregates,4.0.8,Apache 2.0
rxjs,6.2.1,Apache 2.0
safe-buffer,5.1.1,MIT
safe-buffer,5.1.2,MIT
@@ -1329,16 +917,12 @@ sax,1.2.4,ISC
schema-utils,0.4.5,MIT
seed-fu,2.3.7,MIT
select,1.1.2,MIT
-select-hose,2.0.0,MIT
select2,3.5.2-browserify,Apache*
select2-rails,3.5.9.3,MIT
-selfsigned,1.10.1,MIT
semver,5.5.0,ISC
-semver-diff,2.1.0,MIT
send,0.16.1,MIT
sentry-raven,2.7.2,Apache 2.0
serialize-javascript,1.4.0,New BSD
-serve-index,1.9.0,MIT
serve-static,1.13.1,MIT
set-blocking,2.0.0,ISC
set-getter,0.1.0,MIT
@@ -1359,101 +943,58 @@ sidekiq-cron,0.6.0,MIT
sidekiq-limit_fetch,3.4.0,MIT
signal-exit,3.0.2,ISC
signet,0.8.1,Apache 2.0
-slack-node,0.2.0,MIT
slack-notifier,1.5.1,MIT
slash,1.0.0,MIT
-slice-ansi,1.0.0,MIT
-smart-buffer,1.1.15,MIT
-smart-buffer,4.0.1,MIT
smooshpack,0.0.48,LGPL
-smtp-connection,2.12.0,MIT
snapdragon,0.8.1,MIT
snapdragon-node,2.1.1,MIT
snapdragon-util,3.0.1,MIT
-sntp,1.0.9,BSD
-sntp,2.1.0,BSD
-socket.io,2.0.4,MIT
-socket.io-adapter,1.1.1,MIT
-socket.io-client,2.0.4,MIT
-socket.io-parser,3.1.2,MIT
-sockjs,0.3.19,MIT
-sockjs-client,1.1.4,MIT
-socks,1.1.10,MIT
-socks,1.1.9,MIT
-socks,2.2.1,MIT
-socks-proxy-agent,3.0.1,MIT
-socks-proxy-agent,4.0.1,MIT
sort-keys,2.0.0,MIT
sortablejs,1.7.0,MIT
source-list-map,2.0.0,MIT
-source-map,0.2.0,New BSD
-source-map,0.4.4,New BSD
source-map,0.5.0,New BSD
-source-map,0.5.6,New BSD
source-map,0.5.7,New BSD
source-map,0.6.1,New BSD
source-map-resolve,0.5.1,MIT
source-map-support,0.4.18,MIT
source-map-url,0.4.0,MIT
-spdx-correct,1.0.2,Apache 2.0
-spdx-expression-parse,1.0.4,(MIT AND CC-BY-3.0)
-spdx-license-ids,1.2.2,Unlicense
-spdy,3.4.7,MIT
-spdy-transport,2.0.20,MIT
-split,0.3.3,MIT
split-string,3.1.0,MIT
-sprintf-js,1.0.3,New BSD
sprockets,3.7.2,MIT
sprockets-rails,3.2.1,MIT
sql.js,0.4.0,MIT
srcset,1.0.0,MIT
sshkey,1.9.0,MIT
-sshpk,1.13.1,MIT
ssri,5.2.4,ISC
state_machines,0.5.0,MIT
state_machines-activemodel,0.5.1,MIT
state_machines-activerecord,0.5.1,MIT
static-extend,0.1.2,MIT
statuses,1.3.1,MIT
-statuses,1.4.0,MIT
statuses,1.5.0,MIT
stickyfilljs,2.0.5,MIT
stream-browserify,2.0.1,MIT
-stream-combiner,0.0.4,MIT
stream-each,1.2.2,MIT
stream-http,2.8.2,MIT
stream-shift,1.0.0,MIT
-streamroller,0.7.0,MIT
strict-uri-encode,1.1.0,MIT
string-width,1.0.2,MIT
string-width,2.1.1,MIT
string_decoder,0.10.31,MIT
-string_decoder,1.0.3,MIT
string_decoder,1.1.1,MIT
stringex,2.8.4,MIT
-stringstream,0.0.5,MIT
strip-ansi,3.0.1,MIT
strip-ansi,4.0.0,MIT
-strip-bom,2.0.0,MIT
-strip-bom,3.0.0,MIT
strip-eof,1.0.0,MIT
-strip-indent,1.0.1,MIT
strip-json-comments,2.0.1,MIT
style-loader,0.21.0,MIT
supports-color,2.0.0,MIT
-supports-color,3.2.3,MIT
supports-color,5.4.0,MIT
svg4everybody,2.1.9,CC0-1.0
sys-filesystem,1.1.6,Artistic 2.0
-table,4.0.2,New BSD
-tapable,0.1.10,MIT
tapable,1.0.0,MIT
tar,4.4.4,ISC
temple,0.8.0,MIT
-term-size,1.2.0,MIT
-test-exclude,4.2.1,ISC
text,1.3.1,MIT
-text-table,0.2.0,MIT
textextensions,2.2.0,MIT
thor,0.19.4,MIT
thread_safe,0.3.6,Apache 2.0
@@ -1462,54 +1003,35 @@ three-orbit-controls,82.1.0,MIT
three-stl-loader,1.0.4,MIT
through,2.3.8,MIT
through2,2.0.3,MIT
-thunkify,2.1.2,MIT
-thunky,0.1.0,MIT*
tilt,2.0.8,MIT
timeago.js,3.0.2,MIT
timed-out,4.0.1,MIT
timers-browserify,2.0.10,MIT
-timespan,2.3.0,MIT
timfel-krb5-auth,0.8.3,LGPL
tiny-emitter,2.0.2,MIT
tmp,0.0.33,MIT
-to-array,0.1.4,MIT
to-arraybuffer,1.0.1,MIT
to-fast-properties,1.0.3,MIT
-to-fast-properties,2.0.0,MIT
to-object-path,0.3.0,MIT
to-regex,3.0.2,MIT
to-regex-range,2.1.1,MIT
toml-rb,1.0.0,MIT
-touch,3.1.0,ISC
-tough-cookie,2.3.3,New BSD
traverse,0.6.6,MIT
-trim-newlines,1.0.0,MIT
trim-right,1.0.1,MIT
trollop,2.1.3,MIT
truncato,0.7.10,MIT
tryer,1.0.0,MIT
-tryit,1.0.3,MIT
tslib,1.9.3,Apache 2.0
-tsscmp,1.0.5,MIT
tty-browserify,0.0.0,MIT
-tunnel-agent,0.4.3,Apache 2.0
-tunnel-agent,0.6.0,Apache 2.0
-tweetnacl,0.14.5,Unlicense
-type-check,0.3.2,MIT
type-is,1.6.16,MIT
typedarray,0.0.6,MIT
-typescript,2.9.2,Apache 2.0
tzinfo,1.2.5,MIT
u2f,0.2.1,MIT
uber,0.1.0,MIT
uglifier,2.7.2,MIT
uglify-es,3.3.9,Simplified BSD
-uglify-js,2.8.29,Simplified BSD
-uglify-to-browserify,1.0.2,MIT
uglifyjs-webpack-plugin,1.2.5,MIT
ultron,1.1.1,MIT
-undefsafe,2.0.2,MIT
-underscore,1.7.0,MIT
underscore,1.9.0,MIT
unf,0.1.4,BSD
unf_ext,0.0.7.5,MIT
@@ -1519,43 +1041,27 @@ union-value,1.0.0,MIT
uniq,1.0.1,MIT
unique-filename,1.1.0,ISC
unique-slug,2.0.0,ISC
-unique-string,1.0.0,MIT
unpipe,1.0.0,MIT
unset-value,1.0.0,MIT
-unzip-response,2.0.1,MIT
-upath,1.0.5,MIT
upath,1.1.0,MIT
-update-notifier,2.3.0,Simplified BSD
urix,0.1.0,MIT
url,0.11.0,MIT
-url-join,4.0.0,MIT
url-loader,1.0.1,MIT
-url-parse,1.0.5,MIT
-url-parse,1.1.9,MIT
-url-parse-lax,1.0.0,MIT
url-parse-lax,3.0.0,MIT
url-to-options,1.0.1,MIT
use,2.0.2,MIT
-useragent,2.2.1,MIT
util,0.10.3,MIT
util-deprecate,1.0.2,MIT
utils-merge,1.0.1,MIT
-uuid,3.2.1,MIT
-uws,9.14.0,Zlib
v8-compile-cache,2.0.0,MIT
-validate-npm-package-license,3.0.1,Apache 2.0
validates_hostname,1.0.6,MIT
-vary,1.1.1,MIT
vary,1.1.2,MIT
-verror,1.10.0,MIT
version_sorter,2.1.0,MIT
virtus,1.0.5,MIT
visibilityjs,1.2.4,MIT
vm-browserify,0.0.4,MIT
vmstat,2.3.0,MIT
-void-elements,2.0.1,MIT
vue,2.5.16,MIT
-vue-eslint-parser,2.0.3,MIT
vue-functional-data-merge,2.0.6,MIT
vue-hot-reload-api,2.3.0,MIT
vue-loader,15.2.4,MIT
@@ -1568,50 +1074,28 @@ vue-virtual-scroll-list,1.2.5,MIT
vuex,3.0.1,MIT
warden,1.2.7,MIT
watchpack,1.5.0,MIT
-wbuf,1.7.2,MIT
webpack,4.16.0,MIT
webpack-bundle-analyzer,2.13.1,MIT
webpack-cli,3.0.8,MIT
-webpack-dev-middleware,3.1.3,MIT
-webpack-dev-server,3.1.4,MIT
-webpack-log,1.2.0,MIT
webpack-rails,0.9.10,MIT
webpack-sources,1.1.0,MIT
webpack-stats-plugin,0.2.1,MIT
-websocket-driver,0.6.5,MIT
-websocket-extensions,0.1.1,MIT
-when,3.7.8,MIT
which,1.3.0,ISC
which-module,2.0.0,ISC
wide-align,1.1.2,ISC
-widest-line,2.0.0,MIT
wikicloth,0.8.1,MIT
-window-size,0.1.0,MIT
-with-callback,1.0.2,MIT
-wordwrap,0.0.2,MIT
-wordwrap,0.0.3,MIT
-wordwrap,1.0.0,MIT
worker-farm,1.5.2,MIT
worker-loader,2.0.0,MIT
wrap-ansi,2.1.0,MIT
wrappy,1.0.2,ISC
-write,0.2.1,MIT
-write-file-atomic,2.3.0,ISC
-ws,3.3.3,MIT
ws,4.0.0,MIT
-xdg-basedir,3.0.0,MIT
xml-simple,1.1.5,ruby
xmlhttprequest,1.8.0,MIT
-xmlhttprequest-ssl,1.5.5,MIT
-xregexp,2.0.0,MIT
xtend,4.0.1,MIT
xterm,3.5.0,MIT
y18n,3.2.1,ISC
y18n,4.0.0,ISC
yallist,2.1.2,ISC
yallist,3.0.2,ISC
-yargs,11.0.0,MIT
yargs,11.1.0,MIT
-yargs,3.10.0,MIT
yargs-parser,9.0.2,ISC
-yeast,0.1.2,MIT
diff --git a/yarn.lock b/yarn.lock
index 6f96e5ff228..fe4702f3a78 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -78,17 +78,13 @@
lodash "^4.2.0"
to-fast-properties "^2.0.0"
-"@gitlab-org/gitlab-svgs@^1.23.0":
- version "1.27.0"
- resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.27.0.tgz#638e70399ebd59e503732177316bb9a18bf7a13f"
+"@gitlab-org/gitlab-svgs@^1.23.0", "@gitlab-org/gitlab-svgs@^1.29.0":
+ version "1.29.0"
+ resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.29.0.tgz#03b65b513f9099bbda6ecf94d673a2952f8c6c70"
-"@gitlab-org/gitlab-svgs@^1.28.0":
- version "1.28.0"
- resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.28.0.tgz#f689dfd46504df0a75027d6dd4ea01a71cd46f88"
-
-"@gitlab-org/gitlab-ui@1.0.5":
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-ui/-/gitlab-ui-1.0.5.tgz#a64b402650494115c8b494a44b72c2d6fbf33fff"
+"@gitlab-org/gitlab-ui@^1.5.0":
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-ui/-/gitlab-ui-1.5.0.tgz#320ba164ba8812ff64f94b1cae79a7b749f5bc03"
dependencies:
"@gitlab-org/gitlab-svgs" "^1.23.0"
bootstrap-vue "^2.0.0-rc.11"
@@ -276,11 +272,7 @@
"@webassemblyjs/wast-parser" "1.5.13"
long "^3.2.0"
-abbrev@1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
-
-abbrev@1.0.x:
+abbrev@1, abbrev@1.0.x:
version "1.0.9"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135"
@@ -307,28 +299,14 @@ acorn@^3.0.4:
version "3.3.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a"
-acorn@^5.0.0, acorn@^5.3.0, acorn@^5.5.0:
- version "5.6.2"
- resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.6.2.tgz#b1da1d7be2ac1b4a327fb9eab851702c5045b4e7"
-
-acorn@^5.6.2:
+acorn@^5.0.0, acorn@^5.3.0, acorn@^5.5.0, acorn@^5.6.2:
version "5.7.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.1.tgz#f095829297706a7c9776958c0afc8930a9b9d9d8"
-addressparser@1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/addressparser/-/addressparser-1.0.1.tgz#47afbe1a2a9262191db6838e4fd1d39b40821746"
-
after@0.8.2:
version "0.8.2"
resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f"
-agent-base@4, agent-base@^4.1.0, agent-base@^4.2.0, agent-base@~4.2.0:
- version "4.2.1"
- resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9"
- dependencies:
- es6-promisify "^5.0.0"
-
ajv-keywords@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762"
@@ -337,7 +315,7 @@ ajv-keywords@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.1.0.tgz#ac2b27939c543e95d2c06e7f7f5c27be4aa543be"
-ajv@^5.1.0, ajv@^5.2.3, ajv@^5.3.0:
+ajv@^5.2.3, ajv@^5.3.0:
version "5.5.2"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965"
dependencies:
@@ -366,16 +344,6 @@ amdefine@>=0.0.4:
version "1.0.1"
resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
-amqplib@^0.5.2:
- version "0.5.2"
- resolved "https://registry.yarnpkg.com/amqplib/-/amqplib-0.5.2.tgz#d2d7313c7ffaa4d10bcf1e6252de4591b6cc7b63"
- dependencies:
- bitsyntax "~0.0.4"
- bluebird "^3.4.6"
- buffer-more-ints "0.0.2"
- readable-stream "1.x >=1.1.9"
- safe-buffer "^5.0.1"
-
ansi-align@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-2.0.0.tgz#c36aeccba563b89ceb556f3690f0b1d9e3547f7f"
@@ -515,18 +483,6 @@ asn1.js@^4.0.0:
inherits "^2.0.1"
minimalistic-assert "^1.0.0"
-asn1@~0.2.3:
- version "0.2.3"
- resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86"
-
-assert-plus@1.0.0, assert-plus@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
-
-assert-plus@^0.2.0:
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234"
-
assert@^1.1.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91"
@@ -537,10 +493,6 @@ assign-symbols@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
-ast-types@0.x.x:
- version "0.11.3"
- resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.11.3.tgz#c20757fe72ee71278ea0ff3d87e5c2ca30d9edf8"
-
async-each@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
@@ -554,21 +506,11 @@ async@1.x, async@^1.4.0, async@^1.5.2:
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
async@^2.0.0, async@^2.1.4:
- version "2.6.0"
- resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4"
- dependencies:
- lodash "^4.14.0"
-
-async@~2.6.0:
version "2.6.1"
resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610"
dependencies:
lodash "^4.17.10"
-asynckit@^0.4.0:
- version "0.4.0"
- resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
-
atob@^2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/atob/-/atob-2.0.3.tgz#19c7a760473774468f20b2d2d03372ad7d4cbf5d"
@@ -577,30 +519,12 @@ autosize@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/autosize/-/autosize-4.0.0.tgz#7a0599b1ba84d73bd7589b0d9da3870152c69237"
-aws-sign2@~0.6.0:
- version "0.6.0"
- resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f"
-
-aws-sign2@~0.7.0:
- version "0.7.0"
- resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
-
-aws4@^1.2.1, aws4@^1.6.0:
- version "1.6.0"
- resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
-
axios-mock-adapter@^1.15.0:
version "1.15.0"
resolved "https://registry.yarnpkg.com/axios-mock-adapter/-/axios-mock-adapter-1.15.0.tgz#fbc06825d8302c95c3334d21023bba996255d45d"
dependencies:
deep-equal "^1.0.1"
-axios@^0.15.3:
- version "0.15.3"
- resolved "https://registry.yarnpkg.com/axios/-/axios-0.15.3.tgz#2c9d638b2e191a08ea1d6cc988eadd6ba5bdc053"
- dependencies:
- follow-redirects "1.0.0"
-
axios@^0.17.1:
version "0.17.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.17.1.tgz#2d8e3e5d0bdbd7327f91bc814f5c57660f81824d"
@@ -1258,12 +1182,6 @@ batch@0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16"
-bcrypt-pbkdf@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d"
- dependencies:
- tweetnacl "^0.14.3"
-
better-assert@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522"
@@ -1290,18 +1208,6 @@ binaryextensions@2:
version "2.1.1"
resolved "https://registry.yarnpkg.com/binaryextensions/-/binaryextensions-2.1.1.tgz#3209a51ca4a4ad541a3b8d3d6a6d5b83a2485935"
-bitsyntax@~0.0.4:
- version "0.0.4"
- resolved "https://registry.yarnpkg.com/bitsyntax/-/bitsyntax-0.0.4.tgz#eb10cc6f82b8c490e3e85698f07e83d46e0cba82"
- dependencies:
- buffer-more-ints "0.0.2"
-
-bl@~1.1.2:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/bl/-/bl-1.1.2.tgz#fdca871a99713aa00d19e3bbba41c44787a65398"
- dependencies:
- readable-stream "~2.0.5"
-
blackst0ne-mermaid@^7.1.0-fixed:
version "7.1.0-fixed"
resolved "https://registry.yarnpkg.com/blackst0ne-mermaid/-/blackst0ne-mermaid-7.1.0-fixed.tgz#3707b3a113d78610e3068e18a588f46b4688de49"
@@ -1317,7 +1223,7 @@ blob@0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.4.tgz#bcf13052ca54463f30f9fc7e95b9a47630a94921"
-bluebird@^3.1.1, bluebird@^3.3.0, bluebird@^3.4.6, bluebird@^3.5.1:
+bluebird@^3.1.1, bluebird@^3.3.0, bluebird@^3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9"
@@ -1351,24 +1257,6 @@ bonjour@^3.5.0:
multicast-dns "^6.0.1"
multicast-dns-service-types "^1.1.0"
-boom@2.x.x:
- version "2.10.1"
- resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f"
- dependencies:
- hoek "2.x.x"
-
-boom@4.x.x:
- version "4.3.1"
- resolved "https://registry.yarnpkg.com/boom/-/boom-4.3.1.tgz#4f8a3005cb4a7e3889f749030fd25b96e01d2e31"
- dependencies:
- hoek "4.x.x"
-
-boom@5.x.x:
- version "5.2.0"
- resolved "https://registry.yarnpkg.com/boom/-/boom-5.2.0.tgz#5dd9da6ee3a5f302077436290cb717d3f4a54e02"
- dependencies:
- hoek "4.x.x"
-
bootstrap-vue@^2.0.0-rc.11:
version "2.0.0-rc.11"
resolved "https://registry.yarnpkg.com/bootstrap-vue/-/bootstrap-vue-2.0.0-rc.11.tgz#47aaa6d2a8d390477de75e636d8ea652b1d03f59"
@@ -1380,14 +1268,14 @@ bootstrap-vue@^2.0.0-rc.11:
popper.js "^1.12.9"
vue-functional-data-merge "^2.0.5"
-bootstrap@^4.1.1:
- version "4.1.2"
- resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.1.2.tgz#aee2a93472e61c471fc79fb475531dcbc87de326"
-
-bootstrap@~4.1.1:
+bootstrap@4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.1.1.tgz#3aec85000fa619085da8d2e4983dfd67cf2114cb"
+bootstrap@^4.1.1:
+ version "4.1.3"
+ resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.1.3.tgz#0eb371af2c8448e8c210411d0cb824a6409a12be"
+
boxen@^1.2.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b"
@@ -1494,10 +1382,6 @@ buffer-indexof@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.0.tgz#f54f647c4f4e25228baa656a2e57e43d5f270982"
-buffer-more-ints@0.0.2:
- version "0.0.2"
- resolved "https://registry.yarnpkg.com/buffer-more-ints/-/buffer-more-ints-0.0.2.tgz#26b3885d10fa13db7fc01aae3aab870199e0124c"
-
buffer-xor@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9"
@@ -1510,18 +1394,6 @@ buffer@^4.3.0:
ieee754 "^1.1.4"
isarray "^1.0.0"
-buildmail@4.0.1:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/buildmail/-/buildmail-4.0.1.tgz#877f7738b78729871c9a105e3b837d2be11a7a72"
- dependencies:
- addressparser "1.0.1"
- libbase64 "0.1.0"
- libmime "3.0.0"
- libqp "1.1.0"
- nodemailer-fetch "1.6.0"
- nodemailer-shared "1.1.0"
- punycode "1.4.1"
-
builtin-modules@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
@@ -1628,14 +1500,6 @@ capture-stack-trace@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz#4a6fa07399c26bba47f0b2496b4d0fb408c5550d"
-caseless@~0.11.0:
- version "0.11.0"
- resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7"
-
-caseless@~0.12.0:
- version "0.12.0"
- resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
-
center-align@^0.1.1:
version "0.1.3"
resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad"
@@ -1643,7 +1507,7 @@ center-align@^0.1.1:
align-text "^0.1.3"
lazy-cache "^1.0.3"
-chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
+chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
dependencies:
@@ -1681,25 +1545,7 @@ check-types@^7.3.0:
version "7.3.0"
resolved "https://registry.yarnpkg.com/check-types/-/check-types-7.3.0.tgz#468f571a4435c24248f5fd0cb0e8d87c3c341e7d"
-chokidar@^2.0.0, chokidar@^2.0.2:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.2.tgz#4dc65139eeb2714977735b6a35d06e97b494dfd7"
- dependencies:
- anymatch "^2.0.0"
- async-each "^1.0.0"
- braces "^2.3.0"
- glob-parent "^3.1.0"
- inherits "^2.0.1"
- is-binary-path "^1.0.0"
- is-glob "^4.0.0"
- normalize-path "^2.1.1"
- path-is-absolute "^1.0.0"
- readdirp "^2.0.0"
- upath "^1.0.0"
- optionalDependencies:
- fsevents "^1.0.0"
-
-chokidar@^2.0.3:
+chokidar@^2.0.0, chokidar@^2.0.2, chokidar@^2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.4.tgz#356ff4e2b0e8e43e322d18a372460bbcf3accd26"
dependencies:
@@ -1739,7 +1585,7 @@ circular-json@^0.3.1:
version "0.3.3"
resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66"
-circular-json@^0.5.4:
+circular-json@^0.5.5:
version "0.5.5"
resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.5.5.tgz#64182ef359042d37cd8e767fc9de878b1e9447d3"
@@ -1851,13 +1697,7 @@ combine-lists@^1.0.0:
dependencies:
lodash "^4.5.0"
-combined-stream@1.0.6, combined-stream@^1.0.5, combined-stream@~1.0.5:
- version "1.0.6"
- resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818"
- dependencies:
- delayed-stream "~1.0.0"
-
-commander@2, commander@^2.13.0, commander@^2.15.1, commander@^2.9.0:
+commander@2, commander@^2.13.0, commander@^2.15.1:
version "2.15.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f"
@@ -2013,7 +1853,7 @@ core-js@~2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.3.0.tgz#fab83fbb0b2d8dc85fa636c4b9d34c75420c6d65"
-core-util-is@1.0.2, core-util-is@~1.0.0:
+core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
@@ -2078,18 +1918,6 @@ cross-spawn@^6.0.5:
version "0.0.2"
resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
-cryptiles@2.x.x:
- version "2.0.5"
- resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8"
- dependencies:
- boom "2.x.x"
-
-cryptiles@3.x.x:
- version "3.1.2"
- resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.2.tgz#a89fbb220f5ce25ec56e8c4aa8a4fd7b5b0d29fe"
- dependencies:
- boom "5.x.x"
-
crypto-browserify@^3.11.0:
version "3.12.0"
resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec"
@@ -2387,16 +2215,6 @@ dagre-layout@^0.8.0:
graphlib "^2.1.1"
lodash "^4.17.4"
-dashdash@^1.12.0:
- version "1.14.1"
- resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
- dependencies:
- assert-plus "^1.0.0"
-
-data-uri-to-buffer@1:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-1.2.0.tgz#77163ea9c20d8641b4707e8f18abdf9a78f34835"
-
date-format@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/date-format/-/date-format-1.2.0.tgz#615e828e233dd1ab9bb9ae0950e0ceccfa6ecad8"
@@ -2413,19 +2231,19 @@ de-indent@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
-debug@2, debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.6, debug@^2.6.8, debug@^2.6.9, debug@~2.6.4, debug@~2.6.6:
- version "2.6.9"
- resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
- dependencies:
- ms "2.0.0"
-
debug@2.6.8:
version "2.6.8"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc"
dependencies:
ms "2.0.0"
-debug@3.1.0, debug@^3.0.1, debug@^3.1.0, debug@~3.1.0:
+debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.6, debug@^2.6.8, debug@^2.6.9:
+ version "2.6.9"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
+ dependencies:
+ ms "2.0.0"
+
+debug@^3.0.1, debug@^3.1.0, debug@~3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
dependencies:
@@ -2493,14 +2311,6 @@ define-property@^2.0.2:
is-descriptor "^1.0.2"
isobject "^3.0.1"
-degenerator@^1.0.4:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/degenerator/-/degenerator-1.0.4.tgz#fcf490a37ece266464d9cc431ab98c5819ced095"
- dependencies:
- ast-types "0.x.x"
- escodegen "1.x.x"
- esprima "3.x.x"
-
del@^2.0.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8"
@@ -2524,10 +2334,6 @@ del@^3.0.0:
pify "^3.0.0"
rimraf "^2.2.8"
-delayed-stream@~1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
-
delegate@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.1.2.tgz#1e1bc6f5cadda6cb6cbf7e6d05d0bcdd5712aebe"
@@ -2540,10 +2346,6 @@ depd@1.1.1, depd@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359"
-depd@~1.1.2:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
-
des.js@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc"
@@ -2666,10 +2468,6 @@ dot-prop@^4.1.0, dot-prop@^4.1.1:
dependencies:
is-obj "^1.0.0"
-double-ended-queue@^2.1.0-0:
- version "2.1.0-0"
- resolved "https://registry.yarnpkg.com/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz#103d3527fd31528f40188130c841efdd78264e5c"
-
dropzone@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/dropzone/-/dropzone-4.2.0.tgz#fbe7acbb9918e0706489072ef663effeef8a79f3"
@@ -2691,12 +2489,6 @@ duplexify@^3.4.2, duplexify@^3.5.3:
readable-stream "^2.0.0"
stream-shift "^1.0.0"
-ecc-jsbn@~0.1.1:
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505"
- dependencies:
- jsbn "~0.1.0"
-
editions@^1.3.3:
version "1.3.4"
resolved "https://registry.yarnpkg.com/editions/-/editions-1.3.4.tgz#3662cb592347c3168eb8e498a0ff73271d67f50b"
@@ -2745,9 +2537,9 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0:
dependencies:
once "^1.4.0"
-engine.io-client@~3.1.0:
- version "3.1.5"
- resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.1.5.tgz#85de17666560327ef1817978f6e3f8101ded2c47"
+engine.io-client@~3.2.0:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.2.1.tgz#6f54c0475de487158a1a7c77d10178708b6add36"
dependencies:
component-emitter "1.2.1"
component-inherit "0.0.3"
@@ -2771,9 +2563,9 @@ engine.io-parser@~2.1.0, engine.io-parser@~2.1.1:
blob "0.0.4"
has-binary2 "~1.0.2"
-engine.io@~3.1.0:
- version "3.1.5"
- resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-3.1.5.tgz#0e7ef9d690eb0b35597f1d4ad02a26ca2dba3845"
+engine.io@~3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-3.2.0.tgz#54332506f42f2edc71690d2f2a42349359f3bf7d"
dependencies:
accepts "~1.3.4"
base64id "1.0.0"
@@ -2781,18 +2573,8 @@ engine.io@~3.1.0:
debug "~3.1.0"
engine.io-parser "~2.1.0"
ws "~3.3.1"
- optionalDependencies:
- uws "~9.14.0"
-
-enhanced-resolve@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.0.0.tgz#e34a6eaa790f62fccd71d93959f56b2b432db10a"
- dependencies:
- graceful-fs "^4.1.2"
- memory-fs "^0.4.0"
- tapable "^1.0.0"
-enhanced-resolve@^4.1.0:
+enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f"
dependencies:
@@ -2816,13 +2598,7 @@ entities@^1.1.1, entities@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0"
-errno@^0.1.3:
- version "0.1.4"
- resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.4.tgz#b896e23a9e5e8ba33871fc996abd3635fc9a1c7d"
- dependencies:
- prr "~0.0.0"
-
-errno@^0.1.4:
+errno@^0.1.3, errno@^0.1.4:
version "0.1.7"
resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618"
dependencies:
@@ -2852,20 +2628,10 @@ es-to-primitive@^1.1.1:
is-date-object "^1.0.1"
is-symbol "^1.0.1"
-es6-promise@^4.0.3:
- version "4.2.4"
- resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.4.tgz#dc4221c2b16518760bd8c39a52d8f356fc00ed29"
-
es6-promise@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.0.2.tgz#010d5858423a5f118979665f46486a95c6ee2bb6"
-es6-promisify@^5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203"
- dependencies:
- es6-promise "^4.0.3"
-
escape-html@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
@@ -2885,17 +2651,6 @@ escodegen@1.8.x:
optionalDependencies:
source-map "~0.2.0"
-escodegen@1.x.x:
- version "1.9.0"
- resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.9.0.tgz#9811a2f265dc1cd3894420ee3717064b632b8852"
- dependencies:
- esprima "^3.1.3"
- estraverse "^4.2.0"
- esutils "^2.0.2"
- optionator "^0.8.1"
- optionalDependencies:
- source-map "~0.5.6"
-
eslint-config-airbnb-base@^12.1.0:
version "12.1.0"
resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-12.1.0.tgz#386441e54a12ccd957b0a92564a4bafebd747944"
@@ -3043,10 +2798,6 @@ esprima@2.7.x, esprima@^2.7.1:
version "2.7.3"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581"
-esprima@3.x.x, esprima@^3.1.3:
- version "3.1.3"
- resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
-
esprima@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804"
@@ -3210,7 +2961,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2:
assign-symbols "^1.0.0"
is-extendable "^1.0.1"
-extend@3, extend@^3.0.0, extend@~3.0.0, extend@~3.0.1:
+extend@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444"
@@ -3243,14 +2994,6 @@ extglob@^2.0.4:
snapdragon "^0.8.1"
to-regex "^3.0.1"
-extsprintf@1.3.0:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
-
-extsprintf@^1.2.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
-
fast-deep-equal@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff"
@@ -3299,10 +3042,6 @@ file-loader@^1.1.11:
loader-utils "^1.0.2"
schema-utils "^0.4.5"
-file-uri-to-path@1:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
-
fileset@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/fileset/-/fileset-2.0.3.tgz#8e7548a96d3cc2327ee5e674168723a333bba2a0"
@@ -3376,12 +3115,6 @@ flush-write-stream@^1.0.0:
inherits "^2.0.1"
readable-stream "^2.0.4"
-follow-redirects@1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.0.0.tgz#8e34298cbd2e176f254effec75a1c78cc849fd37"
- dependencies:
- debug "^2.2.0"
-
follow-redirects@^1.2.5:
version "1.2.6"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.2.6.tgz#4dcdc7e4ab3dd6765a97ff89c3b4c258117c79bf"
@@ -3396,26 +3129,6 @@ foreach@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99"
-forever-agent@~0.6.1:
- version "0.6.1"
- resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
-
-form-data@~2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.0.0.tgz#6f0aebadcc5da16c13e1ecc11137d85f9b883b25"
- dependencies:
- asynckit "^0.4.0"
- combined-stream "^1.0.5"
- mime-types "^2.1.11"
-
-form-data@~2.3.0, form-data@~2.3.1:
- version "2.3.2"
- resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099"
- dependencies:
- asynckit "^0.4.0"
- combined-stream "1.0.6"
- mime-types "^2.1.12"
-
formdata-polyfill@^3.0.11:
version "3.0.11"
resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-3.0.11.tgz#c82b4b4bea3356c0a6752219e54ce1edb2a7fb5b"
@@ -3470,20 +3183,13 @@ fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
-fsevents@^1.0.0, fsevents@^1.2.2:
+fsevents@^1.2.2:
version "1.2.4"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.4.tgz#f41dcb1af2582af3692da36fc55cbd8e1041c426"
dependencies:
nan "^2.9.2"
node-pre-gyp "^0.10.0"
-ftp@~0.3.10:
- version "0.3.10"
- resolved "https://registry.yarnpkg.com/ftp/-/ftp-0.3.10.tgz#9197d861ad8142f3e63d5a83bfe4c59f7330885d"
- dependencies:
- readable-stream "1.1.x"
- xregexp "2.0.0"
-
function-bind@^1.0.2, function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
@@ -3509,16 +3215,6 @@ gauge@~2.7.3:
strip-ansi "^3.0.1"
wide-align "^1.1.0"
-generate-function@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74"
-
-generate-object-property@^1.1.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0"
- dependencies:
- is-property "^1.0.0"
-
get-caller-file@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5"
@@ -3531,27 +3227,10 @@ get-stream@3.0.0, get-stream@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
-get-uri@^2.0.0:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-2.0.2.tgz#5c795e71326f6ca1286f2fc82575cd2bab2af578"
- dependencies:
- data-uri-to-buffer "1"
- debug "2"
- extend "3"
- file-uri-to-path "1"
- ftp "~0.3.10"
- readable-stream "2"
-
get-value@^2.0.3, get-value@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
-getpass@^0.1.1:
- version "0.1.7"
- resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
- dependencies:
- assert-plus "^1.0.0"
-
gettext-extractor-vue@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/gettext-extractor-vue/-/gettext-extractor-vue-4.0.1.tgz#69d2737eb8f1938803ffcf9317133ed59fb2372f"
@@ -3714,26 +3393,6 @@ handlebars@^4.0.1, handlebars@^4.0.3:
optionalDependencies:
uglify-js "^2.6"
-har-schema@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
-
-har-validator@~2.0.6:
- version "2.0.6"
- resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-2.0.6.tgz#cdcbc08188265ad119b6a5a7c8ab70eecfb5d27d"
- dependencies:
- chalk "^1.1.1"
- commander "^2.9.0"
- is-my-json-valid "^2.12.4"
- pinkie-promise "^2.0.0"
-
-har-validator@~5.0.3:
- version "5.0.3"
- resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd"
- dependencies:
- ajv "^5.1.0"
- har-schema "^2.0.0"
-
has-ansi@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
@@ -3829,35 +3488,10 @@ hash.js@^1.0.0, hash.js@^1.0.3:
inherits "^2.0.3"
minimalistic-assert "^1.0.0"
-hawk@~3.1.3:
- version "3.1.3"
- resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4"
- dependencies:
- boom "2.x.x"
- cryptiles "2.x.x"
- hoek "2.x.x"
- sntp "1.x.x"
-
-hawk@~6.0.2:
- version "6.0.2"
- resolved "https://registry.yarnpkg.com/hawk/-/hawk-6.0.2.tgz#af4d914eb065f9b5ce4d9d11c1cb2126eecc3038"
- dependencies:
- boom "4.x.x"
- cryptiles "3.x.x"
- hoek "4.x.x"
- sntp "2.x.x"
-
he@^1.1.0, he@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
-hipchat-notifier@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/hipchat-notifier/-/hipchat-notifier-1.1.0.tgz#b6d249755437c191082367799d3ba9a0f23b231e"
- dependencies:
- lodash "^4.0.0"
- request "^2.0.0"
-
hmac-drbg@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
@@ -3866,14 +3500,6 @@ hmac-drbg@^1.0.0:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"
-hoek@2.x.x:
- version "2.16.3"
- resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
-
-hoek@4.x.x:
- version "4.2.1"
- resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb"
-
home-or-tmp@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"
@@ -3926,22 +3552,6 @@ http-errors@1.6.2, http-errors@~1.6.1, http-errors@~1.6.2:
setprototypeof "1.0.3"
statuses ">= 1.3.1 < 2"
-http-errors@1.6.3:
- version "1.6.3"
- resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d"
- dependencies:
- depd "~1.1.2"
- inherits "2.0.3"
- setprototypeof "1.1.0"
- statuses ">= 1.4.0 < 2"
-
-http-proxy-agent@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405"
- dependencies:
- agent-base "4"
- debug "3.1.0"
-
http-proxy-middleware@~0.18.0:
version "0.18.0"
resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz#0987e6bb5a5606e5a69168d8f967a87f15dd8aab"
@@ -3958,55 +3568,17 @@ http-proxy@^1.13.0, http-proxy@^1.16.2:
eventemitter3 "1.x.x"
requires-port "1.x.x"
-http-signature@~1.1.0:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf"
- dependencies:
- assert-plus "^0.2.0"
- jsprim "^1.2.2"
- sshpk "^1.7.0"
-
-http-signature@~1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
- dependencies:
- assert-plus "^1.0.0"
- jsprim "^1.2.2"
- sshpk "^1.7.0"
-
-httpntlm@1.6.1:
- version "1.6.1"
- resolved "https://registry.yarnpkg.com/httpntlm/-/httpntlm-1.6.1.tgz#ad01527143a2e8773cfae6a96f58656bb52a34b2"
- dependencies:
- httpreq ">=0.4.22"
- underscore "~1.7.0"
-
-httpreq@>=0.4.22:
- version "0.4.24"
- resolved "https://registry.yarnpkg.com/httpreq/-/httpreq-0.4.24.tgz#4335ffd82cd969668a39465c929ac61d6393627f"
-
https-browserify@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
-https-proxy-agent@^2.2.1:
- version "2.2.1"
- resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0"
- dependencies:
- agent-base "^4.1.0"
- debug "^3.1.0"
-
-iconv-lite@0.4, iconv-lite@0.4.23, iconv-lite@^0.4.22, iconv-lite@^0.4.4, iconv-lite@~0.4.13:
+iconv-lite@0.4, iconv-lite@^0.4.17, iconv-lite@^0.4.22, iconv-lite@^0.4.4, iconv-lite@~0.4.13:
version "0.4.23"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
dependencies:
safer-buffer ">= 2.1.2 < 3"
-iconv-lite@0.4.15:
- version "0.4.15"
- resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb"
-
-iconv-lite@0.4.19, iconv-lite@^0.4.17:
+iconv-lite@0.4.19:
version "0.4.19"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
@@ -4082,14 +3654,6 @@ indexof@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d"
-inflection@~1.12.0:
- version "1.12.0"
- resolved "https://registry.yarnpkg.com/inflection/-/inflection-1.12.0.tgz#a200935656d6f5f6bc4dc7502e1aecb703228416"
-
-inflection@~1.3.0:
- version "1.3.8"
- resolved "https://registry.yarnpkg.com/inflection/-/inflection-1.3.8.tgz#cbd160da9f75b14c3cc63578d4f396784bf3014e"
-
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
@@ -4191,7 +3755,7 @@ invert-kv@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
-ip@^1.1.0, ip@^1.1.2, ip@^1.1.4, ip@^1.1.5:
+ip@^1.1.0, ip@^1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a"
@@ -4316,20 +3880,6 @@ is-installed-globally@^0.1.0:
global-dirs "^0.1.0"
is-path-inside "^1.0.0"
-is-my-ip-valid@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz#7b351b8e8edd4d3995d4d066680e664d94696824"
-
-is-my-json-valid@^2.12.4:
- version "2.17.2"
- resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.17.2.tgz#6b2103a288e94ef3de5cf15d29dd85fc4b78d65c"
- dependencies:
- generate-function "^2.0.0"
- generate-object-property "^1.1.0"
- is-my-ip-valid "^1.0.0"
- jsonpointer "^4.0.0"
- xtend "^4.0.0"
-
is-npm@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4"
@@ -4392,10 +3942,6 @@ is-promise@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa"
-is-property@^1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84"
-
is-redirect@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24"
@@ -4424,10 +3970,6 @@ is-symbol@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572"
-is-typedarray@~1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
-
is-utf8@^0.2.0:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
@@ -4440,10 +3982,6 @@ is-wsl@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
-isarray@0.0.1:
- version "0.0.1"
- resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
-
isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
@@ -4470,10 +4008,6 @@ isobject@^3.0.0, isobject@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
-isstream@~0.1.2:
- version "0.1.2"
- resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
-
istanbul-api@^1.1.14:
version "1.2.1"
resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.2.1.tgz#0c60a0515eb11c7d65c6b50bba2c6e999acd8620"
@@ -4490,11 +4024,7 @@ istanbul-api@^1.1.14:
mkdirp "^0.5.1"
once "^1.4.0"
-istanbul-lib-coverage@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.1.tgz#73bfb998885299415c93d38a3e9adf784a77a9da"
-
-istanbul-lib-coverage@^1.2.0:
+istanbul-lib-coverage@^1.1.1, istanbul-lib-coverage@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.0.tgz#f7d8f2e42b97e37fe796114cb0f9d68b5e3a4341"
@@ -4622,10 +4152,6 @@ js-yaml@3.x, js-yaml@^3.7.0, js-yaml@^3.9.1:
argparse "^1.0.7"
esprima "^4.0.0"
-jsbn@~0.1.0:
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
-
jsesc@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b"
@@ -4650,18 +4176,10 @@ json-schema-traverse@^0.3.0:
version "0.3.1"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340"
-json-schema@0.2.3:
- version "0.2.3"
- resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
-
json-stable-stringify-without-jsonify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
-json-stringify-safe@5.0.x, json-stringify-safe@~5.0.1:
- version "5.0.1"
- resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
-
json3@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1"
@@ -4670,19 +4188,6 @@ json5@^0.5.0, json5@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
-jsonpointer@^4.0.0:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9"
-
-jsprim@^1.2.2:
- version "1.4.1"
- resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
- dependencies:
- assert-plus "1.0.0"
- extsprintf "1.3.0"
- json-schema "0.2.3"
- verror "1.10.0"
-
jszip-utils@^0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/jszip-utils/-/jszip-utils-0.0.2.tgz#457d5cbca60a1c2e0706e9da2b544e8e7bc50bf8"
@@ -4715,6 +4220,13 @@ karma-jasmine@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/karma-jasmine/-/karma-jasmine-1.1.2.tgz#394f2b25ffb4a644b9ada6f22d443e2fd08886c3"
+karma-junit-reporter@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/karma-junit-reporter/-/karma-junit-reporter-1.2.0.tgz#4f9c40cedfb1a395f8aef876abf96189917c6396"
+ dependencies:
+ path-is-absolute "^1.0.0"
+ xmlbuilder "8.2.2"
+
karma-mocha-reporter@^2.2.5:
version "2.2.5"
resolved "https://registry.yarnpkg.com/karma-mocha-reporter/-/karma-mocha-reporter-2.2.5.tgz#15120095e8ed819186e47a0b012f3cd741895560"
@@ -4740,9 +4252,9 @@ karma-webpack@^4.0.0-beta.0:
source-map "^0.5.6"
webpack-dev-middleware "^3.0.1"
-karma@^2.0.4:
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/karma/-/karma-2.0.4.tgz#b399785f57e9bab1d3c4384db33fef4dec8ae349"
+karma@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/karma/-/karma-3.0.0.tgz#6da83461a8a28d8224575c3b5b874e271b4730c3"
dependencies:
bluebird "^3.3.0"
body-parser "^1.16.1"
@@ -4759,24 +4271,24 @@ karma@^2.0.4:
http-proxy "^1.13.0"
isbinaryfile "^3.0.0"
lodash "^4.17.4"
- log4js "^2.5.3"
- mime "^1.3.4"
+ log4js "^3.0.0"
+ mime "^2.3.1"
minimatch "^3.0.2"
optimist "^0.6.1"
qjobs "^1.1.4"
range-parser "^1.2.0"
rimraf "^2.6.0"
safe-buffer "^5.0.1"
- socket.io "2.0.4"
+ socket.io "2.1.1"
source-map "^0.6.1"
tmp "0.0.33"
useragent "2.2.1"
-katex@^0.8.3:
- version "0.8.3"
- resolved "https://registry.yarnpkg.com/katex/-/katex-0.8.3.tgz#909d99864baf964c3ccae39c4a99a8e0fb9a1bd0"
+katex@^0.9.0:
+ version "0.9.0"
+ resolved "https://registry.yarnpkg.com/katex/-/katex-0.9.0.tgz#26a7d082c21d53725422d2d71da9b2d8455fbd4a"
dependencies:
- match-at "^0.1.0"
+ match-at "^0.1.1"
keyv@3.0.0:
version "3.0.0"
@@ -4837,22 +4349,6 @@ levn@^0.3.0, levn@~0.3.0:
prelude-ls "~1.1.2"
type-check "~0.3.2"
-libbase64@0.1.0:
- version "0.1.0"
- resolved "https://registry.yarnpkg.com/libbase64/-/libbase64-0.1.0.tgz#62351a839563ac5ff5bd26f12f60e9830bb751e6"
-
-libmime@3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/libmime/-/libmime-3.0.0.tgz#51a1a9e7448ecbd32cda54421675bb21bc093da6"
- dependencies:
- iconv-lite "0.4.15"
- libbase64 "0.1.0"
- libqp "1.1.0"
-
-libqp@1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/libqp/-/libqp-1.1.0.tgz#f5e6e06ad74b794fb5b5b66988bf728ef1dedbe8"
-
lie@~3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e"
@@ -4945,7 +4441,7 @@ lodash@4.17.4:
version "4.17.4"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
-lodash@^4.0.0, lodash@^4.11.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.3.0, lodash@^4.5.0:
+lodash@^4.0.0, lodash@^4.11.1, lodash@^4.17.10, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.3.0, lodash@^4.5.0:
version "4.17.10"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7"
@@ -4955,32 +4451,15 @@ log-symbols@^2.1.0:
dependencies:
chalk "^2.0.1"
-log4js@^2.5.3:
- version "2.11.0"
- resolved "https://registry.yarnpkg.com/log4js/-/log4js-2.11.0.tgz#bf3902eff65c6923d9ce9cfbd2db54160e34005a"
+log4js@^3.0.0:
+ version "3.0.5"
+ resolved "https://registry.yarnpkg.com/log4js/-/log4js-3.0.5.tgz#b80146bfebad68b430d4f3569556d8a6edfef303"
dependencies:
- circular-json "^0.5.4"
+ circular-json "^0.5.5"
date-format "^1.2.0"
debug "^3.1.0"
- semver "^5.5.0"
+ rfdc "^1.1.2"
streamroller "0.7.0"
- optionalDependencies:
- amqplib "^0.5.2"
- axios "^0.15.3"
- hipchat-notifier "^1.1.0"
- loggly "^1.1.0"
- mailgun-js "^0.18.0"
- nodemailer "^2.5.0"
- redis "^2.7.1"
- slack-node "~0.2.0"
-
-loggly@^1.1.0:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/loggly/-/loggly-1.1.1.tgz#0a0fc1d3fa3a5ec44fdc7b897beba2a4695cebee"
- dependencies:
- json-stringify-safe "5.0.x"
- request "2.75.x"
- timespan "2.3.x"
loglevel@^1.4.1:
version "1.4.1"
@@ -5034,27 +4513,6 @@ lz-string@^1.4.4:
version "1.4.4"
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26"
-mailcomposer@4.0.1:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/mailcomposer/-/mailcomposer-4.0.1.tgz#0e1c44b2a07cf740ee17dc149ba009f19cadfeb4"
- dependencies:
- buildmail "4.0.1"
- libmime "3.0.0"
-
-mailgun-js@^0.18.0:
- version "0.18.1"
- resolved "https://registry.yarnpkg.com/mailgun-js/-/mailgun-js-0.18.1.tgz#ee39aa18d7bb598a5ce9ede84afb681defc8a6b0"
- dependencies:
- async "~2.6.0"
- debug "~3.1.0"
- form-data "~2.3.0"
- inflection "~1.12.0"
- is-stream "^1.1.0"
- path-proxy "~1.0.0"
- promisify-call "^2.0.2"
- proxy-agent "~3.0.0"
- tsscmp "~1.0.0"
-
make-dir@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.2.0.tgz#6d6a49eead4aae296c53bbf3a1a008bd6c89469b"
@@ -5087,7 +4545,7 @@ marked@^0.3.12:
version "0.3.12"
resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.12.tgz#7cf25ff2252632f3fe2406bde258e94eee927519"
-match-at@^0.1.0:
+match-at@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/match-at/-/match-at-0.1.1.tgz#25d040d291777704d5e6556bbb79230ec2de0540"
@@ -5177,7 +4635,7 @@ miller-rabin@^4.0.0:
version "1.33.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db"
-mime-types@^2.1.11, mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.17, mime-types@~2.1.18, mime-types@~2.1.7:
+mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.18:
version "2.1.18"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8"
dependencies:
@@ -5187,11 +4645,7 @@ mime@1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6"
-mime@^1.3.4:
- version "1.6.0"
- resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
-
-mime@^2.0.3, mime@^2.1.0:
+mime@^2.0.3, mime@^2.1.0, mime@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/mime/-/mime-2.3.1.tgz#b1621c54d63b97c47d3cfe7f7215f7d64517c369"
@@ -5357,10 +4811,6 @@ neo-async@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.5.0.tgz#76b1c823130cca26acfbaccc8fbaf0a2fa33b18f"
-netmask@^1.0.6:
- version "1.0.6"
- resolved "https://registry.yarnpkg.com/netmask/-/netmask-1.0.6.tgz#20297e89d86f6f6400f250d9f4f6b4c1945fcd35"
-
nice-try@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.4.tgz#d93962f6c52f2c1558c0fbda6d512819f1efe1c4"
@@ -5419,59 +4869,6 @@ node-pre-gyp@^0.10.0:
semver "^5.3.0"
tar "^4"
-node-uuid@~1.4.7:
- version "1.4.8"
- resolved "https://registry.yarnpkg.com/node-uuid/-/node-uuid-1.4.8.tgz#b040eb0923968afabf8d32fb1f17f1167fdab907"
-
-nodemailer-direct-transport@3.3.2:
- version "3.3.2"
- resolved "https://registry.yarnpkg.com/nodemailer-direct-transport/-/nodemailer-direct-transport-3.3.2.tgz#e96fafb90358560947e569017d97e60738a50a86"
- dependencies:
- nodemailer-shared "1.1.0"
- smtp-connection "2.12.0"
-
-nodemailer-fetch@1.6.0:
- version "1.6.0"
- resolved "https://registry.yarnpkg.com/nodemailer-fetch/-/nodemailer-fetch-1.6.0.tgz#79c4908a1c0f5f375b73fe888da9828f6dc963a4"
-
-nodemailer-shared@1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/nodemailer-shared/-/nodemailer-shared-1.1.0.tgz#cf5994e2fd268d00f5cf0fa767a08169edb07ec0"
- dependencies:
- nodemailer-fetch "1.6.0"
-
-nodemailer-smtp-pool@2.8.2:
- version "2.8.2"
- resolved "https://registry.yarnpkg.com/nodemailer-smtp-pool/-/nodemailer-smtp-pool-2.8.2.tgz#2eb94d6cf85780b1b4725ce853b9cbd5e8da8c72"
- dependencies:
- nodemailer-shared "1.1.0"
- nodemailer-wellknown "0.1.10"
- smtp-connection "2.12.0"
-
-nodemailer-smtp-transport@2.7.2:
- version "2.7.2"
- resolved "https://registry.yarnpkg.com/nodemailer-smtp-transport/-/nodemailer-smtp-transport-2.7.2.tgz#03d71c76314f14ac7dbc7bf033a6a6d16d67fb77"
- dependencies:
- nodemailer-shared "1.1.0"
- nodemailer-wellknown "0.1.10"
- smtp-connection "2.12.0"
-
-nodemailer-wellknown@0.1.10:
- version "0.1.10"
- resolved "https://registry.yarnpkg.com/nodemailer-wellknown/-/nodemailer-wellknown-0.1.10.tgz#586db8101db30cb4438eb546737a41aad0cf13d5"
-
-nodemailer@^2.5.0:
- version "2.7.2"
- resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-2.7.2.tgz#f242e649aeeae39b6c7ed740ef7b061c404d30f9"
- dependencies:
- libmime "3.0.0"
- mailcomposer "4.0.1"
- nodemailer-direct-transport "3.3.2"
- nodemailer-shared "1.1.0"
- nodemailer-smtp-pool "2.8.2"
- nodemailer-smtp-transport "2.7.2"
- socks "1.1.9"
-
nodemon@^1.18.2:
version "1.18.2"
resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.18.2.tgz#36b89c790da70c4f270e2cc0718723131bc04abb"
@@ -5563,10 +4960,6 @@ number-is-nan@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
-oauth-sign@~0.8.1, oauth-sign@~0.8.2:
- version "0.8.2"
- resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
-
object-assign@^4.0.1, object-assign@^4.1.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
@@ -5742,29 +5135,6 @@ p-try@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
-pac-proxy-agent@^2.0.1:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-2.0.2.tgz#90d9f6730ab0f4d2607dcdcd4d3d641aa26c3896"
- dependencies:
- agent-base "^4.2.0"
- debug "^3.1.0"
- get-uri "^2.0.0"
- http-proxy-agent "^2.1.0"
- https-proxy-agent "^2.2.1"
- pac-resolver "^3.0.0"
- raw-body "^2.2.0"
- socks-proxy-agent "^3.0.0"
-
-pac-resolver@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/pac-resolver/-/pac-resolver-3.0.0.tgz#6aea30787db0a891704deb7800a722a7615a6f26"
- dependencies:
- co "^4.6.0"
- degenerator "^1.0.4"
- ip "^1.1.5"
- netmask "^1.0.6"
- thunkify "^2.1.2"
-
package-json@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed"
@@ -5860,12 +5230,6 @@ path-parse@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1"
-path-proxy@~1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/path-proxy/-/path-proxy-1.0.0.tgz#18e8a36859fc9d2f1a53b48dee138543c020de5e"
- dependencies:
- inflection "~1.3.0"
-
path-to-regexp@0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
@@ -5900,10 +5264,6 @@ pbkdf2@^3.0.3:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
-performance-now@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
-
pify@^2.0.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
@@ -6003,15 +5363,7 @@ postcss-value-parser@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz#87f38f9f18f774a4ab4c8a232f5c5ce8872a9d15"
-postcss@^6.0.1, postcss@^6.0.14, postcss@^6.0.20:
- version "6.0.22"
- resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.22.tgz#e23b78314905c3b90cbd61702121e7a78848f2a3"
- dependencies:
- chalk "^2.4.1"
- source-map "^0.6.1"
- supports-color "^5.4.0"
-
-postcss@^6.0.23:
+postcss@^6.0.1, postcss@^6.0.14, postcss@^6.0.20, postcss@^6.0.23:
version "6.0.23"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324"
dependencies:
@@ -6065,12 +5417,6 @@ promise-inflight@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
-promisify-call@^2.0.2:
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/promisify-call/-/promisify-call-2.0.4.tgz#d48c2d45652ccccd52801ddecbd533a6d4bd5fba"
- dependencies:
- with-callback "^1.0.2"
-
proxy-addr@~2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341"
@@ -6078,27 +5424,6 @@ proxy-addr@~2.0.2:
forwarded "~0.1.2"
ipaddr.js "1.6.0"
-proxy-agent@~3.0.0:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-3.0.1.tgz#4fb7b61b1476d0fe8e3a3384d90e2460bbded3f9"
- dependencies:
- agent-base "^4.2.0"
- debug "^3.1.0"
- http-proxy-agent "^2.1.0"
- https-proxy-agent "^2.2.1"
- lru-cache "^4.1.2"
- pac-proxy-agent "^2.0.1"
- proxy-from-env "^1.0.0"
- socks-proxy-agent "^4.0.1"
-
-proxy-from-env@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee"
-
-prr@~0.0.0:
- version "0.0.0"
- resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a"
-
prr@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
@@ -6144,26 +5469,18 @@ pumpify@^1.3.3:
inherits "^2.0.3"
pump "^2.0.0"
-punycode@1.3.2:
+punycode@1.3.2, punycode@^1.2.4:
version "1.3.2"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
-punycode@1.4.1, punycode@^1.2.4, punycode@^1.4.1:
- version "1.4.1"
- resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
-
qjobs@^1.1.4:
version "1.2.0"
resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.2.0.tgz#c45e9c61800bd087ef88d7e256423bdd49e5d071"
-qs@6.5.1, qs@~6.5.1:
+qs@6.5.1:
version "6.5.1"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8"
-qs@~6.2.0:
- version "6.2.3"
- resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.3.tgz#1cfcb25c10a9b2b483053ff39f5dfc9233908cfe"
-
query-string@^5.0.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb"
@@ -6224,15 +5541,6 @@ raw-body@2.3.2:
iconv-lite "0.4.19"
unpipe "1.0.0"
-raw-body@^2.2.0:
- version "2.3.3"
- resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.3.tgz#1b324ece6b5706e153855bc1148c65bb7f6ea0c3"
- dependencies:
- bytes "3.0.0"
- http-errors "1.6.3"
- iconv-lite "0.4.23"
- unpipe "1.0.0"
-
raw-loader@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-0.5.1.tgz#0c3d0beaed8a01c966d9787bf778281252a979aa"
@@ -6276,28 +5584,7 @@ read-pkg@^2.0.0:
normalize-package-data "^2.3.2"
path-type "^2.0.0"
-"readable-stream@1 || 2", readable-stream@2, readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.2.9, readable-stream@^2.3.0, readable-stream@^2.3.3:
- version "2.3.4"
- resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.4.tgz#c946c3f47fa7d8eabc0b6150f4a12f69a4574071"
- dependencies:
- core-util-is "~1.0.0"
- inherits "~2.0.3"
- isarray "~1.0.0"
- process-nextick-args "~2.0.0"
- safe-buffer "~5.1.1"
- string_decoder "~1.0.3"
- util-deprecate "~1.0.1"
-
-readable-stream@1.1.x, "readable-stream@1.x >=1.1.9":
- version "1.1.14"
- resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
- dependencies:
- core-util-is "~1.0.0"
- inherits "~2.0.1"
- isarray "0.0.1"
- string_decoder "~0.10.x"
-
-readable-stream@^2.3.6:
+"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.2.9, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.6:
version "2.3.6"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
dependencies:
@@ -6309,7 +5596,7 @@ readable-stream@^2.3.6:
string_decoder "~1.1.1"
util-deprecate "~1.0.1"
-readable-stream@~2.0.5, readable-stream@~2.0.6:
+readable-stream@~2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e"
dependencies:
@@ -6336,22 +5623,6 @@ redent@^1.0.0:
indent-string "^2.1.0"
strip-indent "^1.0.1"
-redis-commands@^1.2.0:
- version "1.3.1"
- resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.3.1.tgz#81d826f45fa9c8b2011f4cd7a0fe597d241d442b"
-
-redis-parser@^2.6.0:
- version "2.6.0"
- resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-2.6.0.tgz#52ed09dacac108f1a631c07e9b69941e7a19504b"
-
-redis@^2.7.1:
- version "2.8.0"
- resolved "https://registry.yarnpkg.com/redis/-/redis-2.8.0.tgz#202288e3f58c49f6079d97af7a10e1303ae14b02"
- dependencies:
- double-ended-queue "^2.1.0-0"
- redis-commands "^1.2.0"
- redis-parser "^2.6.0"
-
regenerate@^1.2.1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.2.tgz#d1941c67bad437e1be76433add5b385f95b19260"
@@ -6440,68 +5711,6 @@ repeating@^2.0.0:
dependencies:
is-finite "^1.0.0"
-request@2.75.x:
- version "2.75.0"
- resolved "https://registry.yarnpkg.com/request/-/request-2.75.0.tgz#d2b8268a286da13eaa5d01adf5d18cc90f657d93"
- dependencies:
- aws-sign2 "~0.6.0"
- aws4 "^1.2.1"
- bl "~1.1.2"
- caseless "~0.11.0"
- combined-stream "~1.0.5"
- extend "~3.0.0"
- forever-agent "~0.6.1"
- form-data "~2.0.0"
- har-validator "~2.0.6"
- hawk "~3.1.3"
- http-signature "~1.1.0"
- is-typedarray "~1.0.0"
- isstream "~0.1.2"
- json-stringify-safe "~5.0.1"
- mime-types "~2.1.7"
- node-uuid "~1.4.7"
- oauth-sign "~0.8.1"
- qs "~6.2.0"
- stringstream "~0.0.4"
- tough-cookie "~2.3.0"
- tunnel-agent "~0.4.1"
-
-request@^2.0.0, request@^2.74.0:
- version "2.83.0"
- resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356"
- dependencies:
- aws-sign2 "~0.7.0"
- aws4 "^1.6.0"
- caseless "~0.12.0"
- combined-stream "~1.0.5"
- extend "~3.0.1"
- forever-agent "~0.6.1"
- form-data "~2.3.1"
- har-validator "~5.0.3"
- hawk "~6.0.2"
- http-signature "~1.2.0"
- is-typedarray "~1.0.0"
- isstream "~0.1.2"
- json-stringify-safe "~5.0.1"
- mime-types "~2.1.17"
- oauth-sign "~0.8.2"
- performance-now "^2.1.0"
- qs "~6.5.1"
- safe-buffer "^5.1.1"
- stringstream "~0.0.5"
- tough-cookie "~2.3.3"
- tunnel-agent "^0.6.0"
- uuid "^3.1.0"
-
-requestretry@^1.2.2:
- version "1.13.0"
- resolved "https://registry.yarnpkg.com/requestretry/-/requestretry-1.13.0.tgz#213ec1006eeb750e8b8ce54176283d15a8d55d94"
- dependencies:
- extend "^3.0.0"
- lodash "^4.15.0"
- request "^2.74.0"
- when "^3.7.7"
-
require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
@@ -6566,6 +5775,10 @@ ret@~0.1.10:
version "0.1.15"
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
+rfdc@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.1.2.tgz#e6e72d74f5dc39de8f538f65e00c36c18018e349"
+
right-align@^0.1.1:
version "0.1.3"
resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef"
@@ -6621,11 +5834,11 @@ rxjs@^6.1.0:
dependencies:
tslib "^1.9.0"
-safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+safe-buffer@5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
-safe-buffer@^5.1.2:
+safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
@@ -6805,12 +6018,6 @@ signal-exit@^3.0.0, signal-exit@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
-slack-node@~0.2.0:
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/slack-node/-/slack-node-0.2.0.tgz#de4b8dddaa8b793f61dbd2938104fdabf37dfa30"
- dependencies:
- requestretry "^1.2.2"
-
slash@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
@@ -6821,14 +6028,6 @@ slice-ansi@1.0.0:
dependencies:
is-fullwidth-code-point "^2.0.0"
-smart-buffer@^1.0.13, smart-buffer@^1.0.4:
- version "1.1.15"
- resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-1.1.15.tgz#7f114b5b65fab3e2a35aa775bb12f0d1c649bf16"
-
-smart-buffer@^4.0.1:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.0.1.tgz#07ea1ca8d4db24eb4cac86537d7d18995221ace3"
-
smooshpack@^0.0.48:
version "0.0.48"
resolved "https://registry.yarnpkg.com/smooshpack/-/smooshpack-0.0.48.tgz#6fbeaaf59226a1fe500f56aa17185eed377d2823"
@@ -6837,13 +6036,6 @@ smooshpack@^0.0.48:
codesandbox-import-utils "^1.2.3"
lodash.isequal "^4.5.0"
-smtp-connection@2.12.0:
- version "2.12.0"
- resolved "https://registry.yarnpkg.com/smtp-connection/-/smtp-connection-2.12.0.tgz#d76ef9127cb23c2259edb1e8349c2e8d5e2d74c1"
- dependencies:
- httpntlm "1.6.1"
- nodemailer-shared "1.1.0"
-
snapdragon-node@^2.0.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b"
@@ -6871,58 +6063,47 @@ snapdragon@^0.8.1:
source-map-resolve "^0.5.0"
use "^2.0.0"
-sntp@1.x.x:
- version "1.0.9"
- resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198"
- dependencies:
- hoek "2.x.x"
-
-sntp@2.x.x:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.1.0.tgz#2c6cec14fedc2222739caf9b5c3d85d1cc5a2cc8"
- dependencies:
- hoek "4.x.x"
-
socket.io-adapter@~1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz#2a805e8a14d6372124dd9159ad4502f8cb07f06b"
-socket.io-client@2.0.4:
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.0.4.tgz#0918a552406dc5e540b380dcd97afc4a64332f8e"
+socket.io-client@2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.1.1.tgz#dcb38103436ab4578ddb026638ae2f21b623671f"
dependencies:
backo2 "1.0.2"
base64-arraybuffer "0.1.5"
component-bind "1.0.0"
component-emitter "1.2.1"
- debug "~2.6.4"
- engine.io-client "~3.1.0"
+ debug "~3.1.0"
+ engine.io-client "~3.2.0"
+ has-binary2 "~1.0.2"
has-cors "1.1.0"
indexof "0.0.1"
object-component "0.0.3"
parseqs "0.0.5"
parseuri "0.0.5"
- socket.io-parser "~3.1.1"
+ socket.io-parser "~3.2.0"
to-array "0.1.4"
-socket.io-parser@~3.1.1:
- version "3.1.2"
- resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.1.2.tgz#dbc2282151fc4faebbe40aeedc0772eba619f7f2"
+socket.io-parser@~3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.2.0.tgz#e7c6228b6aa1f814e6148aea325b51aa9499e077"
dependencies:
component-emitter "1.2.1"
- debug "~2.6.4"
- has-binary2 "~1.0.2"
+ debug "~3.1.0"
isarray "2.0.1"
-socket.io@2.0.4:
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-2.0.4.tgz#c1a4590ceff87ecf13c72652f046f716b29e6014"
+socket.io@2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-2.1.1.tgz#a069c5feabee3e6b214a75b40ce0652e1cfb9980"
dependencies:
- debug "~2.6.6"
- engine.io "~3.1.0"
+ debug "~3.1.0"
+ engine.io "~3.2.0"
+ has-binary2 "~1.0.2"
socket.io-adapter "~1.1.0"
- socket.io-client "2.0.4"
- socket.io-parser "~3.1.1"
+ socket.io-client "2.1.1"
+ socket.io-parser "~3.2.0"
sockjs-client@1.1.4:
version "1.1.4"
@@ -6942,41 +6123,6 @@ sockjs@0.3.19:
faye-websocket "^0.10.0"
uuid "^3.0.1"
-socks-proxy-agent@^3.0.0:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-3.0.1.tgz#2eae7cf8e2a82d34565761539a7f9718c5617659"
- dependencies:
- agent-base "^4.1.0"
- socks "^1.1.10"
-
-socks-proxy-agent@^4.0.1:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-4.0.1.tgz#5936bf8b707a993079c6f37db2091821bffa6473"
- dependencies:
- agent-base "~4.2.0"
- socks "~2.2.0"
-
-socks@1.1.9:
- version "1.1.9"
- resolved "https://registry.yarnpkg.com/socks/-/socks-1.1.9.tgz#628d7e4d04912435445ac0b6e459376cb3e6d691"
- dependencies:
- ip "^1.1.2"
- smart-buffer "^1.0.4"
-
-socks@^1.1.10:
- version "1.1.10"
- resolved "https://registry.yarnpkg.com/socks/-/socks-1.1.10.tgz#5b8b7fc7c8f341c53ed056e929b7bf4de8ba7b5a"
- dependencies:
- ip "^1.1.4"
- smart-buffer "^1.0.13"
-
-socks@~2.2.0:
- version "2.2.1"
- resolved "https://registry.yarnpkg.com/socks/-/socks-2.2.1.tgz#68ad678b3642fbc5d99c64c165bc561eab0215f9"
- dependencies:
- ip "^1.1.5"
- smart-buffer "^4.0.1"
-
sort-keys@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128"
@@ -7021,14 +6167,10 @@ source-map@^0.4.4:
dependencies:
amdefine ">=0.0.4"
-source-map@^0.5.0, source-map@^0.5.7, source-map@~0.5.6:
+source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1:
version "0.5.7"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
-source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1:
- version "0.5.6"
- resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
-
source-map@^0.6.1, source-map@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
@@ -7103,20 +6245,6 @@ srcset@^1.0.0:
array-uniq "^1.0.2"
number-is-nan "^1.0.0"
-sshpk@^1.7.0:
- version "1.13.1"
- resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3"
- dependencies:
- asn1 "~0.2.3"
- assert-plus "^1.0.0"
- dashdash "^1.12.0"
- getpass "^0.1.1"
- optionalDependencies:
- bcrypt-pbkdf "^1.0.0"
- ecc-jsbn "~0.1.1"
- jsbn "~0.1.0"
- tweetnacl "~0.14.0"
-
ssri@^5.2.4:
version "5.2.4"
resolved "https://registry.yarnpkg.com/ssri/-/ssri-5.2.4.tgz#9985e14041e65fc397af96542be35724ac11da52"
@@ -7130,15 +6258,7 @@ static-extend@^0.1.1:
define-property "^0.2.5"
object-copy "^0.1.0"
-"statuses@>= 1.3.1 < 2":
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087"
-
-"statuses@>= 1.4.0 < 2":
- version "1.5.0"
- resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
-
-statuses@~1.3.1:
+"statuses@>= 1.3.1 < 2", statuses@~1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e"
@@ -7218,16 +6338,6 @@ string_decoder@~0.10.x:
version "0.10.31"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
-string_decoder@~1.0.3:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab"
- dependencies:
- safe-buffer "~5.1.0"
-
-stringstream@~0.0.4, stringstream@~0.0.5:
- version "0.0.5"
- resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878"
-
strip-ansi@^3.0.0, strip-ansi@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
@@ -7369,10 +6479,6 @@ through@2, through@^2.3.6, through@~2.3, through@~2.3.1:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
-thunkify@^2.1.2:
- version "2.1.2"
- resolved "https://registry.yarnpkg.com/thunkify/-/thunkify-2.1.2.tgz#faa0e9d230c51acc95ca13a361ac05ca7e04553d"
-
thunky@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/thunky/-/thunky-0.1.0.tgz#bf30146824e2b6e67b0f2d7a4ac8beb26908684e"
@@ -7393,10 +6499,6 @@ timers-browserify@^2.0.4:
dependencies:
setimmediate "^1.0.4"
-timespan@2.3.x:
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/timespan/-/timespan-2.3.0.tgz#4902ce040bd13d845c8f59b27e9d59bad6f39929"
-
tiny-emitter@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.0.2.tgz#82d27468aca5ade8e5fd1e6d22b57dd43ebdfb7c"
@@ -7451,12 +6553,6 @@ touch@^3.1.0:
dependencies:
nopt "~1.0.10"
-tough-cookie@~2.3.0, tough-cookie@~2.3.3:
- version "2.3.3"
- resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561"
- dependencies:
- punycode "^1.4.1"
-
traverse@0.6.6:
version "0.6.6"
resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137"
@@ -7481,28 +6577,10 @@ tslib@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
-tsscmp@~1.0.0:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/tsscmp/-/tsscmp-1.0.5.tgz#7dc4a33af71581ab4337da91d85ca5427ebd9a97"
-
tty-browserify@0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"
-tunnel-agent@^0.6.0:
- version "0.6.0"
- resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
- dependencies:
- safe-buffer "^5.0.1"
-
-tunnel-agent@~0.4.1:
- version "0.4.3"
- resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb"
-
-tweetnacl@^0.14.3, tweetnacl@~0.14.0:
- version "0.14.5"
- resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
-
type-check@~0.3.2:
version "0.3.2"
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72"
@@ -7571,10 +6649,6 @@ underscore@^1.9.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.0.tgz#31dbb314cfcc88f169cd3692d9149d81a00a73e4"
-underscore@~1.7.0:
- version "1.7.0"
- resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.7.0.tgz#6bbaf0877500d36be34ecaa584e0db9fef035209"
-
union-value@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4"
@@ -7621,10 +6695,6 @@ unzip-response@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97"
-upath@^1.0.0:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/upath/-/upath-1.0.5.tgz#02cab9ecebe95bbec6d5fc2566325725ab6d1a73"
-
upath@^1.0.5:
version "1.1.0"
resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.0.tgz#35256597e46a581db4793d0ce47fa9aebfc9fabd"
@@ -7726,12 +6796,8 @@ utils-merge@1.0.1:
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
uuid@^3.0.1, uuid@^3.1.0:
- version "3.2.1"
- resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14"
-
-uws@~9.14.0:
- version "9.14.0"
- resolved "https://registry.yarnpkg.com/uws/-/uws-9.14.0.tgz#fac8386befc33a7a3705cbd58dc47b430ca4dd95"
+ version "3.3.2"
+ resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
v8-compile-cache@^2.0.0:
version "2.0.0"
@@ -7744,22 +6810,10 @@ validate-npm-package-license@^3.0.1:
spdx-correct "~1.0.0"
spdx-expression-parse "~1.0.0"
-vary@~1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.1.tgz#67535ebb694c1d52257457984665323f587e8d37"
-
-vary@~1.1.2:
+vary@~1.1.1, vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
-verror@1.10.0:
- version "1.10.0"
- resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
- dependencies:
- assert-plus "^1.0.0"
- core-util-is "1.0.2"
- extsprintf "^1.2.0"
-
visibilityjs@^1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/visibilityjs/-/visibilityjs-1.2.4.tgz#bff8663da62c8c10ad4ee5ae6a1ae6fac4259d63"
@@ -7995,10 +7049,6 @@ websocket-extensions@>=0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.1.tgz#76899499c184b6ef754377c2dbb0cd6cb55d29e7"
-when@^3.7.7:
- version "3.7.8"
- resolved "https://registry.yarnpkg.com/when/-/when-3.7.8.tgz#c7130b6a7ea04693e842cdc9e7a1f2aa39a39f82"
-
which-module@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
@@ -8025,10 +7075,6 @@ window-size@0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d"
-with-callback@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/with-callback/-/with-callback-1.0.2.tgz#a09629b9a920028d721404fb435bdcff5c91bc21"
-
wordwrap@0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"
@@ -8100,6 +7146,10 @@ xdg-basedir@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"
+xmlbuilder@8.2.2:
+ version "8.2.2"
+ resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-8.2.2.tgz#69248673410b4ba42e1a6136551d2922335aa773"
+
xmlhttprequest-ssl@~1.5.4:
version "1.5.5"
resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e"
@@ -8108,10 +7158,6 @@ xmlhttprequest@1:
version "1.8.0"
resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc"
-xregexp@2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943"
-
xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"