summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml19
-rw-r--r--.gitlab/issue_templates/Doc Review.md20
-rw-r--r--.gitlab/issue_templates/Documentation.md75
-rw-r--r--.gitlab/issue_templates/Feature proposal.md31
-rw-r--r--.gitlab/issue_templates/Security developer workflow.md1
-rw-r--r--.gitlab/merge_request_templates/Change documentation location.md2
-rw-r--r--.gitlab/merge_request_templates/Database changes.md6
-rw-r--r--.gitlab/merge_request_templates/Documentation.md40
-rw-r--r--.stylelintrc107
-rw-r--r--CHANGELOG.md247
-rw-r--r--Dangerfile1
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--Gemfile23
-rw-r--r--Gemfile.lock69
-rw-r--r--PROCESS.md51
-rw-r--r--VERSION2
-rw-r--r--app/assets/javascripts/badges/components/badge_form.vue2
-rw-r--r--app/assets/javascripts/diffs/components/app.vue68
-rw-r--r--app/assets/javascripts/diffs/components/diff_content.vue30
-rw-r--r--app/assets/javascripts/diffs/components/diff_file.vue66
-rw-r--r--app/assets/javascripts/diffs/components/diff_file_header.vue21
-rw-r--r--app/assets/javascripts/diffs/components/diff_line_note_form.vue4
-rw-r--r--app/assets/javascripts/diffs/components/edit_button.vue7
-rw-r--r--app/assets/javascripts/diffs/components/tree_list.vue12
-rw-r--r--app/assets/javascripts/diffs/constants.js6
-rw-r--r--app/assets/javascripts/diffs/store/actions.js17
-rw-r--r--app/assets/javascripts/diffs/store/getters.js10
-rw-r--r--app/assets/javascripts/diffs/store/mutations.js1
-rw-r--r--app/assets/javascripts/diffs/store/utils.js10
-rw-r--r--app/assets/javascripts/emoji/no_emoji_validator.js63
-rw-r--r--app/assets/javascripts/environments/components/environments_table.vue36
-rw-r--r--app/assets/javascripts/environments/mixins/environments_mixin.js17
-rw-r--r--app/assets/javascripts/error_tracking/store/actions.js14
-rw-r--r--app/assets/javascripts/error_tracking_settings/components/app.vue129
-rw-r--r--app/assets/javascripts/error_tracking_settings/components/error_tracking_form.vue91
-rw-r--r--app/assets/javascripts/error_tracking_settings/components/project_dropdown.vue82
-rw-r--r--app/assets/javascripts/error_tracking_settings/index.js27
-rw-r--r--app/assets/javascripts/error_tracking_settings/store/actions.js91
-rw-r--r--app/assets/javascripts/error_tracking_settings/store/getters.js44
-rw-r--r--app/assets/javascripts/error_tracking_settings/store/index.js16
-rw-r--r--app/assets/javascripts/error_tracking_settings/store/mutation_types.js11
-rw-r--r--app/assets/javascripts/error_tracking_settings/store/mutations.js61
-rw-r--r--app/assets/javascripts/error_tracking_settings/store/state.js12
-rw-r--r--app/assets/javascripts/error_tracking_settings/utils.js18
-rw-r--r--app/assets/javascripts/filtered_search/admin_runners_filtered_search_token_keys.js8
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_ajax_filter.js68
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_user.js83
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js19
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_token_keys.js17
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js2
-rw-r--r--app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue10
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js8
-rw-r--r--app/assets/javascripts/gl_dropdown.js18
-rw-r--r--app/assets/javascripts/groups/components/group_item.vue2
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/editor_header.vue2
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/modal.vue14
-rw-r--r--app/assets/javascripts/ide/constants.js16
-rw-r--r--app/assets/javascripts/import_projects/components/import_projects_table.vue101
-rw-r--r--app/assets/javascripts/import_projects/components/import_status.vue47
-rw-r--r--app/assets/javascripts/import_projects/components/imported_project_table_row.vue55
-rw-r--r--app/assets/javascripts/import_projects/components/provider_repo_table_row.vue110
-rw-r--r--app/assets/javascripts/import_projects/constants.js48
-rw-r--r--app/assets/javascripts/import_projects/event_hub.js3
-rw-r--r--app/assets/javascripts/import_projects/index.js47
-rw-r--r--app/assets/javascripts/import_projects/store/actions.js106
-rw-r--r--app/assets/javascripts/import_projects/store/getters.js20
-rw-r--r--app/assets/javascripts/import_projects/store/index.js15
-rw-r--r--app/assets/javascripts/import_projects/store/mutation_types.js11
-rw-r--r--app/assets/javascripts/import_projects/store/mutations.js55
-rw-r--r--app/assets/javascripts/import_projects/store/state.js15
-rw-r--r--app/assets/javascripts/issuable_suggestions/index.js4
-rw-r--r--app/assets/javascripts/jobs/components/sidebar.vue2
-rw-r--r--app/assets/javascripts/labels_select.js32
-rw-r--r--app/assets/javascripts/lib/graphql.js14
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js17
-rw-r--r--app/assets/javascripts/lib/utils/number_utils.js19
-rw-r--r--app/assets/javascripts/lib/utils/poll.js20
-rw-r--r--app/assets/javascripts/main.js3
-rw-r--r--app/assets/javascripts/members.js27
-rw-r--r--app/assets/javascripts/monitoring/components/charts/area.vue3
-rw-r--r--app/assets/javascripts/notes/components/diff_with_note.vue18
-rw-r--r--app/assets/javascripts/notes/components/discussion_filter.vue24
-rw-r--r--app/assets/javascripts/notes/components/discussion_filter_note.vue52
-rw-r--r--app/assets/javascripts/notes/components/discussion_resolve_with_issue_button.vue34
-rw-r--r--app/assets/javascripts/notes/components/note_actions.vue7
-rw-r--r--app/assets/javascripts/notes/components/note_actions/reply_button.vue12
-rw-r--r--app/assets/javascripts/notes/components/note_body.vue1
-rw-r--r--app/assets/javascripts/notes/components/note_header.vue14
-rw-r--r--app/assets/javascripts/notes/components/noteable_discussion.vue97
-rw-r--r--app/assets/javascripts/notes/components/noteable_note.vue21
-rw-r--r--app/assets/javascripts/notes/components/notes_app.vue28
-rw-r--r--app/assets/javascripts/notes/constants.js6
-rw-r--r--app/assets/javascripts/notes/mixins/diff_line_note_form.js10
-rw-r--r--app/assets/javascripts/notes/mixins/resolvable.js4
-rw-r--r--app/assets/javascripts/notes/stores/actions.js59
-rw-r--r--app/assets/javascripts/notes/stores/getters.js5
-rw-r--r--app/assets/javascripts/notes/stores/modules/index.js1
-rw-r--r--app/assets/javascripts/notes/stores/mutation_types.js1
-rw-r--r--app/assets/javascripts/notes/stores/mutations.js11
-rw-r--r--app/assets/javascripts/pages/groups/group_members/index/index.js2
-rw-r--r--app/assets/javascripts/pages/groups/issues/index.js4
-rw-r--r--app/assets/javascripts/pages/import/gitea/status/index.js7
-rw-r--r--app/assets/javascripts/pages/import/github/status/index.js7
-rw-r--r--app/assets/javascripts/pages/projects/issues/index/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/project_members/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/settings/operations/show/index.js5
-rw-r--r--app/assets/javascripts/pages/sessions/new/index.js2
-rw-r--r--app/assets/javascripts/pages/users/user_tabs.js2
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_actions.vue16
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_artifacts.vue23
-rw-r--r--app/assets/javascripts/pipelines/mixins/pipelines.js9
-rw-r--r--app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue2
-rw-r--r--app/assets/javascripts/projects/project_new.js28
-rw-r--r--app/assets/javascripts/releases/store/actions.js4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/deployment.vue14
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/changed_file_icon.vue12
-rw-r--r--app/assets/javascripts/vue_shared/components/ci_icon.vue7
-rw-r--r--app/assets/javascripts/vue_shared/components/diff_viewer/diff_viewer.vue16
-rw-r--r--app/assets/javascripts/vue_shared/components/diff_viewer/viewers/no_preview.vue5
-rw-r--r--app/assets/javascripts/vue_shared/components/diff_viewer/viewers/not_diffable.vue5
-rw-r--r--app/assets/javascripts/vue_shared/components/empty_component.js3
-rw-r--r--app/assets/javascripts/vue_shared/components/file_row.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue116
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/header.vue29
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/suggestions.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/panel_resizer.vue7
-rw-r--r--app/assets/javascripts/vue_shared/components/project_avatar/default.vue10
-rw-r--r--app/assets/javascripts/vue_shared/components/select2_select.vue35
-rw-r--r--app/assets/javascripts/vue_shared/components/table_pagination.vue37
-rw-r--r--app/assets/javascripts/vue_shared/mixins/related_issuable_mixin.js155
-rw-r--r--app/assets/stylesheets/application.scss10
-rw-r--r--app/assets/stylesheets/components/related_items_list.scss3
-rw-r--r--app/assets/stylesheets/framework.scss2
-rw-r--r--app/assets/stylesheets/framework/animations.scss50
-rw-r--r--app/assets/stylesheets/framework/asciidoctor.scss2
-rw-r--r--app/assets/stylesheets/framework/avatar.scss88
-rw-r--r--app/assets/stylesheets/framework/awards.scss24
-rw-r--r--app/assets/stylesheets/framework/blocks.scss6
-rw-r--r--app/assets/stylesheets/framework/buttons.scss5
-rw-r--r--app/assets/stylesheets/framework/common.scss37
-rw-r--r--app/assets/stylesheets/framework/emojis.scss2
-rw-r--r--app/assets/stylesheets/framework/filters.scss11
-rw-r--r--app/assets/stylesheets/framework/forms.scss5
-rw-r--r--app/assets/stylesheets/framework/gfm.scss2
-rw-r--r--app/assets/stylesheets/framework/header.scss14
-rw-r--r--app/assets/stylesheets/framework/lists.scss6
-rw-r--r--app/assets/stylesheets/framework/logo.scss35
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss10
-rw-r--r--app/assets/stylesheets/framework/mixins.scss25
-rw-r--r--app/assets/stylesheets/framework/modal.scss7
-rw-r--r--app/assets/stylesheets/framework/selects.scss2
-rw-r--r--app/assets/stylesheets/framework/spinner.scss51
-rw-r--r--app/assets/stylesheets/framework/system_messages.scss110
-rw-r--r--app/assets/stylesheets/framework/terms.scss1
-rw-r--r--app/assets/stylesheets/framework/toggle.scss14
-rw-r--r--app/assets/stylesheets/framework/typography.scss34
-rw-r--r--app/assets/stylesheets/framework/variables.scss27
-rw-r--r--app/assets/stylesheets/framework/variables_overrides.scss6
-rw-r--r--app/assets/stylesheets/highlight/common.scss18
-rw-r--r--app/assets/stylesheets/highlight/embedded.scss2
-rw-r--r--app/assets/stylesheets/highlight/themes/dark.scss (renamed from app/assets/stylesheets/highlight/dark.scss)6
-rw-r--r--app/assets/stylesheets/highlight/themes/monokai.scss (renamed from app/assets/stylesheets/highlight/monokai.scss)6
-rw-r--r--app/assets/stylesheets/highlight/themes/none.scss (renamed from app/assets/stylesheets/highlight/none.scss)8
-rw-r--r--app/assets/stylesheets/highlight/themes/solarized-dark.scss (renamed from app/assets/stylesheets/highlight/solarized_dark.scss)6
-rw-r--r--app/assets/stylesheets/highlight/themes/solarized-light.scss (renamed from app/assets/stylesheets/highlight/solarized_light.scss)12
-rw-r--r--app/assets/stylesheets/highlight/themes/white.scss3
-rw-r--r--app/assets/stylesheets/highlight/white.scss3
-rw-r--r--app/assets/stylesheets/highlight/white_base.scss80
-rw-r--r--app/assets/stylesheets/mailers/highlighted_diff_email.scss67
-rw-r--r--app/assets/stylesheets/page_bundles/ide.scss19
-rw-r--r--app/assets/stylesheets/page_bundles/xterm.scss6
-rw-r--r--app/assets/stylesheets/pages/boards.scss17
-rw-r--r--app/assets/stylesheets/pages/builds.scss6
-rw-r--r--app/assets/stylesheets/pages/commits.scss4
-rw-r--r--app/assets/stylesheets/pages/cycle_analytics.scss1
-rw-r--r--app/assets/stylesheets/pages/detail_page.scss1
-rw-r--r--app/assets/stylesheets/pages/diff.scss141
-rw-r--r--app/assets/stylesheets/pages/editor.scss3
-rw-r--r--app/assets/stylesheets/pages/graph.scss2
-rw-r--r--app/assets/stylesheets/pages/help.scss2
-rw-r--r--app/assets/stylesheets/pages/import.scss51
-rw-r--r--app/assets/stylesheets/pages/issuable.scss12
-rw-r--r--app/assets/stylesheets/pages/issues.scss2
-rw-r--r--app/assets/stylesheets/pages/labels.scss6
-rw-r--r--app/assets/stylesheets/pages/login.scss3
-rw-r--r--app/assets/stylesheets/pages/members.scss1
-rw-r--r--app/assets/stylesheets/pages/merge_conflicts.scss96
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss22
-rw-r--r--app/assets/stylesheets/pages/milestone.scss4
-rw-r--r--app/assets/stylesheets/pages/note_form.scss8
-rw-r--r--app/assets/stylesheets/pages/notes.scss68
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss1
-rw-r--r--app/assets/stylesheets/pages/profile.scss2
-rw-r--r--app/assets/stylesheets/pages/projects.scss8
-rw-r--r--app/assets/stylesheets/pages/search.scss6
-rw-r--r--app/assets/stylesheets/pages/status.scss4
-rw-r--r--app/assets/stylesheets/pages/todos.scss18
-rw-r--r--app/assets/stylesheets/pages/tree.scss20
-rw-r--r--app/assets/stylesheets/pages/ui_dev_kit.scss2
-rw-r--r--app/controllers/admin/appearances_controller.rb5
-rw-r--r--app/controllers/admin/runners_controller.rb8
-rw-r--r--app/controllers/admin/users_controller.rb12
-rw-r--r--app/controllers/application_controller.rb23
-rw-r--r--app/controllers/clusters/clusters_controller.rb2
-rw-r--r--app/controllers/concerns/issuable_collections.rb1
-rw-r--r--app/controllers/concerns/lfs_request.rb2
-rw-r--r--app/controllers/concerns/notes_actions.rb57
-rw-r--r--app/controllers/concerns/send_file_upload.rb2
-rw-r--r--app/controllers/dashboard/milestones_controller.rb4
-rw-r--r--app/controllers/dashboard/projects_controller.rb8
-rw-r--r--app/controllers/import/gitea_controller.rb20
-rw-r--r--app/controllers/import/github_controller.rb83
-rw-r--r--app/controllers/profiles/preferences_controller.rb10
-rw-r--r--app/controllers/profiles_controller.rb1
-rw-r--r--app/controllers/projects/git_http_controller.rb9
-rw-r--r--app/controllers/projects/graphs_controller.rb9
-rw-r--r--app/controllers/projects/merge_requests/creations_controller.rb6
-rw-r--r--app/controllers/projects/merge_requests/diffs_controller.rb1
-rw-r--r--app/controllers/projects/pages_controller.rb3
-rw-r--r--app/controllers/projects/settings/operations_controller.rb38
-rw-r--r--app/controllers/projects/tree_controller.rb14
-rw-r--r--app/controllers/snippets/notes_controller.rb4
-rw-r--r--app/finders/admin/runners_finder.rb11
-rw-r--r--app/finders/autocomplete/acts_as_taggable_on/tags_finder.rb44
-rw-r--r--app/finders/concerns/finder_methods.rb4
-rw-r--r--app/finders/group_descendants_finder.rb2
-rw-r--r--app/finders/issuable_finder.rb48
-rw-r--r--app/finders/issues_finder.rb11
-rw-r--r--app/finders/projects/daily_statistics_finder.rb21
-rw-r--r--app/graphql/mutations/merge_requests/base.rb3
-rw-r--r--app/graphql/resolvers/base_resolver.rb7
-rw-r--r--app/graphql/resolvers/issues_resolver.rb30
-rw-r--r--app/graphql/resolvers/merge_requests_resolver.rb (renamed from app/graphql/resolvers/merge_request_resolver.rb)21
-rw-r--r--app/graphql/types/issuable_state_enum.rb12
-rw-r--r--app/graphql/types/issue_state_enum.rb8
-rw-r--r--app/graphql/types/issue_type.rb12
-rw-r--r--app/graphql/types/merge_request_state_enum.rb10
-rw-r--r--app/graphql/types/merge_request_type.rb7
-rw-r--r--app/graphql/types/project_type.rb16
-rw-r--r--app/graphql/types/query_type.rb5
-rw-r--r--app/helpers/appearances_helper.rb34
-rw-r--r--app/helpers/application_helper.rb17
-rw-r--r--app/helpers/blob_helper.rb3
-rw-r--r--app/helpers/count_helper.rb2
-rw-r--r--app/helpers/emails_helper.rb38
-rw-r--r--app/helpers/import_helper.rb36
-rw-r--r--app/helpers/issuables_helper.rb2
-rw-r--r--app/helpers/labels_helper.rb4
-rw-r--r--app/helpers/namespaces_helper.rb6
-rw-r--r--app/helpers/preferences_helper.rb4
-rw-r--r--app/helpers/projects_helper.rb14
-rw-r--r--app/mailers/abuse_report_mailer.rb4
-rw-r--r--app/mailers/email_rejection_mailer.rb4
-rw-r--r--app/mailers/repository_check_mailer.rb4
-rw-r--r--app/models/appearance.rb16
-rw-r--r--app/models/board.rb4
-rw-r--r--app/models/board_group_recent_visit.rb9
-rw-r--r--app/models/board_project_recent_visit.rb9
-rw-r--r--app/models/ci/build.rb13
-rw-r--r--app/models/ci/build_trace_chunk.rb4
-rw-r--r--app/models/ci/group_variable.rb1
-rw-r--r--app/models/ci/pipeline.rb75
-rw-r--r--app/models/ci/pipeline_chat_data.rb13
-rw-r--r--app/models/ci/pipeline_enums.rb3
-rw-r--r--app/models/ci/runner.rb1
-rw-r--r--app/models/ci/variable.rb1
-rw-r--r--app/models/clusters/applications/prometheus.rb5
-rw-r--r--app/models/clusters/applications/runner.rb2
-rw-r--r--app/models/clusters/cluster.rb4
-rw-r--r--app/models/clusters/concerns/application_status.rb9
-rw-r--r--app/models/commit_collection.rb4
-rw-r--r--app/models/concerns/closed_at_filterable.rb14
-rw-r--r--app/models/concerns/fast_destroy_all.rb2
-rw-r--r--app/models/concerns/has_ref.rb2
-rw-r--r--app/models/concerns/has_variable.rb6
-rw-r--r--app/models/concerns/iid_routes.rb2
-rw-r--r--app/models/concerns/issuable.rb7
-rw-r--r--app/models/concerns/maskable.rb22
-rw-r--r--app/models/concerns/reactive_caching.rb2
-rw-r--r--app/models/concerns/sha_attribute.rb10
-rw-r--r--app/models/diff_note.rb8
-rw-r--r--app/models/discussion.rb2
-rw-r--r--app/models/environment.rb4
-rw-r--r--app/models/error_tracking/project_error_tracking_setting.rb55
-rw-r--r--app/models/individual_note_discussion.rb8
-rw-r--r--app/models/issue.rb5
-rw-r--r--app/models/merge_request.rb22
-rw-r--r--app/models/merge_request_diff.rb3
-rw-r--r--app/models/network/graph.rb9
-rw-r--r--app/models/notification_recipient.rb2
-rw-r--r--app/models/personal_access_token.rb5
-rw-r--r--app/models/project.rb44
-rw-r--r--app/models/project_daily_statistic.rb10
-rw-r--r--app/models/project_services/jira_service.rb5
-rw-r--r--app/models/project_services/slack_slash_commands_service.rb4
-rw-r--r--app/models/project_services/youtrack_service.rb40
-rw-r--r--app/models/releases/link.rb2
-rw-r--r--app/models/repository.rb6
-rw-r--r--app/models/service.rb1
-rw-r--r--app/models/suggestion.rb5
-rw-r--r--app/models/user.rb7
-rw-r--r--app/policies/board_policy.rb4
-rw-r--r--app/policies/group_policy.rb1
-rw-r--r--app/policies/project_policy.rb3
-rw-r--r--app/presenters/ci/build_runner_presenter.rb42
-rw-r--r--app/presenters/project_presenter.rb4
-rw-r--r--app/serializers/acts_as_taggable_on/tag_entity.rb6
-rw-r--r--app/serializers/acts_as_taggable_on/tag_serializer.rb5
-rw-r--r--app/serializers/diff_file_base_entity.rb21
-rw-r--r--app/serializers/diff_file_entity.rb8
-rw-r--r--app/serializers/diff_viewer_entity.rb8
-rw-r--r--app/serializers/environment_entity.rb1
-rw-r--r--app/serializers/namespace_basic_entity.rb6
-rw-r--r--app/serializers/namespace_serializer.rb5
-rw-r--r--app/serializers/pipeline_entity.rb4
-rw-r--r--app/serializers/project_import_entity.rb13
-rw-r--r--app/serializers/project_serializer.rb12
-rw-r--r--app/serializers/provider_repo_entity.rb25
-rw-r--r--app/serializers/provider_repo_serializer.rb5
-rw-r--r--app/serializers/tree_entity.rb15
-rw-r--r--app/serializers/tree_root_entity.rb27
-rw-r--r--app/serializers/tree_serializer.rb5
-rw-r--r--app/services/applications/create_service.rb8
-rw-r--r--app/services/boards/visits/latest_service.rb12
-rw-r--r--app/services/ci/create_pipeline_service.rb9
-rw-r--r--app/services/ci/pipeline_trigger_service.rb4
-rw-r--r--app/services/clusters/applications/create_service.rb6
-rw-r--r--app/services/clusters/applications/schedule_installation_service.rb31
-rw-r--r--app/services/commits/create_service.rb4
-rw-r--r--app/services/concerns/exclusive_lease_guard.rb2
-rw-r--r--app/services/concerns/users/participable_service.rb15
-rw-r--r--app/services/create_branch_service.rb4
-rw-r--r--app/services/emails/base_service.rb5
-rw-r--r--app/services/emails/create_service.rb9
-rw-r--r--app/services/error_tracking/list_issues_service.rb10
-rw-r--r--app/services/error_tracking/list_projects_service.rb4
-rw-r--r--app/services/git_push_service.rb8
-rw-r--r--app/services/git_tag_push_service.rb6
-rw-r--r--app/services/groups/create_service.rb2
-rw-r--r--app/services/groups/update_service.rb2
-rw-r--r--app/services/issues/build_service.rb8
-rw-r--r--app/services/merge_requests/base_service.rb2
-rw-r--r--app/services/merge_requests/merge_base_service.rb63
-rw-r--r--app/services/merge_requests/merge_service.rb58
-rw-r--r--app/services/merge_requests/merge_to_ref_service.rb76
-rw-r--r--app/services/notes/create_service.rb2
-rw-r--r--app/services/notes/quick_actions_service.rb7
-rw-r--r--app/services/notification_recipient_service.rb7
-rw-r--r--app/services/projects/fetch_statistics_increment_service.rb32
-rw-r--r--app/services/projects/fork_service.rb12
-rw-r--r--app/services/projects/hashed_storage/base_attachment_service.rb51
-rw-r--r--app/services/projects/hashed_storage/base_repository_service.rb22
-rw-r--r--app/services/projects/hashed_storage/migrate_attachments_service.rb49
-rw-r--r--app/services/projects/hashed_storage/migrate_repository_service.rb14
-rw-r--r--app/services/projects/hashed_storage/rollback_attachments_service.rb34
-rw-r--r--app/services/projects/hashed_storage/rollback_repository_service.rb40
-rw-r--r--app/services/projects/hashed_storage/rollback_service.rb37
-rw-r--r--app/services/projects/operations/update_service.rb23
-rw-r--r--app/services/prometheus/adapter_service.rb2
-rw-r--r--app/services/protected_branches/api_service.rb13
-rw-r--r--app/services/protected_branches/legacy_api_update_service.rb19
-rw-r--r--app/services/suggestions/apply_service.rb12
-rw-r--r--app/services/suggestions/create_service.rb10
-rw-r--r--app/services/system_note_service.rb2
-rw-r--r--app/services/users/activity_service.rb7
-rw-r--r--app/validators/url_validator.rb6
-rw-r--r--app/views/admin/appearances/_form.html.haml1
-rw-r--r--app/views/admin/appearances/_system_header_footer_form.html.haml33
-rw-r--r--app/views/admin/groups/_group.html.haml2
-rw-r--r--app/views/admin/groups/show.html.haml2
-rw-r--r--app/views/admin/projects/_projects.html.haml2
-rw-r--r--app/views/admin/runners/_runner.html.haml4
-rw-r--r--app/views/admin/runners/index.html.haml19
-rw-r--r--app/views/clusters/clusters/_form.html.haml4
-rw-r--r--app/views/dashboard/_snippets_head.html.haml4
-rw-r--r--app/views/dashboard/projects/_starred_empty_state.html.haml9
-rw-r--r--app/views/dashboard/projects/starred.html.haml9
-rw-r--r--app/views/dashboard/snippets/index.html.haml12
-rw-r--r--app/views/devise/shared/_signup_box.html.haml6
-rw-r--r--app/views/explore/snippets/index.html.haml2
-rw-r--r--app/views/groups/_home_panel.html.haml2
-rw-r--r--app/views/groups/settings/_general.html.haml2
-rw-r--r--app/views/help/_shortcuts.html.haml12
-rw-r--r--app/views/import/_githubish_status.html.haml59
-rw-r--r--app/views/import/github/new.html.haml2
-rw-r--r--app/views/import/github/status.html.haml4
-rw-r--r--app/views/import/manifest/status.html.haml2
-rw-r--r--app/views/layouts/_head.html.haml2
-rw-r--r--app/views/layouts/_mailer.html.haml4
-rw-r--r--app/views/layouts/application.html.haml4
-rw-r--r--app/views/layouts/devise.html.haml2
-rw-r--r--app/views/layouts/devise_empty.html.haml2
-rw-r--r--app/views/layouts/empty_mailer.html.haml5
-rw-r--r--app/views/layouts/empty_mailer.text.erb5
-rw-r--r--app/views/layouts/mailer.text.erb4
-rw-r--r--app/views/layouts/nav/sidebar/_group.html.haml2
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml2
-rw-r--r--app/views/layouts/notify.html.haml2
-rw-r--r--app/views/layouts/notify.text.erb4
-rw-r--r--app/views/profiles/passwords/new.html.haml13
-rw-r--r--app/views/profiles/preferences/show.html.haml6
-rw-r--r--app/views/profiles/show.html.haml3
-rw-r--r--app/views/projects/_home_panel.html.haml17
-rw-r--r--app/views/projects/_md_preview.html.haml4
-rw-r--r--app/views/projects/_merge_request_merge_settings.html.haml1
-rw-r--r--app/views/projects/blob/_markdown_buttons.html.haml20
-rw-r--r--app/views/projects/branches/_branch.html.haml3
-rw-r--r--app/views/projects/commit/_ajax_signature.html.haml2
-rw-r--r--app/views/projects/commit/_ci_menu.html.haml4
-rw-r--r--app/views/projects/commit/_commit_box.html.haml17
-rw-r--r--app/views/projects/commit/_limit_exceeded_message.html.haml4
-rw-r--r--app/views/projects/commit/_other_user_signature_badge.html.haml4
-rw-r--r--app/views/projects/commit/_same_user_different_email_signature_badge.html.haml5
-rw-r--r--app/views/projects/commit/_signature_badge.html.haml4
-rw-r--r--app/views/projects/commit/_unverified_signature_badge.html.haml4
-rw-r--r--app/views/projects/commit/_verified_signature_badge.html.haml5
-rw-r--r--app/views/projects/commit/pipelines.html.haml2
-rw-r--r--app/views/projects/commit/show.html.haml4
-rw-r--r--app/views/projects/edit.html.haml2
-rw-r--r--app/views/projects/forks/_fork_button.html.haml4
-rw-r--r--app/views/projects/issues/_discussion.html.haml2
-rw-r--r--app/views/projects/issues/_import_export.svg1
-rw-r--r--app/views/projects/issues/_issue.html.haml2
-rw-r--r--app/views/projects/issues/import_csv/_modal.html.haml4
-rw-r--r--app/views/projects/issues/show.html.haml10
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml2
-rw-r--r--app/views/projects/pages/_destroy.haml2
-rw-r--r--app/views/projects/pages/_https_only.html.haml2
-rw-r--r--app/views/projects/pipelines/_info.html.haml4
-rw-r--r--app/views/projects/settings/operations/_error_tracking.html.haml26
-rw-r--r--app/views/projects/settings/operations/show.html.haml3
-rw-r--r--app/views/projects/snippets/index.html.haml18
-rw-r--r--app/views/projects/snippets/new.html.haml4
-rw-r--r--app/views/projects/tags/show.html.haml2
-rw-r--r--app/views/projects/wikis/edit.html.haml3
-rw-r--r--app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml2
-rw-r--r--app/views/shared/_issuable_meta_data.html.haml2
-rw-r--r--app/views/shared/deploy_keys/_form.html.haml2
-rw-r--r--app/views/shared/empty_states/_priority_labels.html.haml2
-rw-r--r--app/views/shared/empty_states/_snippets.html.haml20
-rw-r--r--app/views/shared/groups/_group.html.haml2
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml8
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml2
-rw-r--r--app/views/shared/issuable/_sort_dropdown.html.haml2
-rw-r--r--app/views/shared/projects/_project.html.haml5
-rw-r--r--app/views/shared/snippets/_list.html.haml12
-rw-r--r--app/views/snippets/index.html.haml2
-rw-r--r--app/views/users/_groups.html.haml2
-rw-r--r--app/workers/all_queues.yml6
-rw-r--r--app/workers/build_finished_worker.rb1
-rw-r--r--app/workers/chat_notification_worker.rb33
-rw-r--r--app/workers/expire_pipeline_cache_worker.rb6
-rw-r--r--app/workers/hashed_storage/base_worker.rb21
-rw-r--r--app/workers/hashed_storage/project_migrate_worker.rb26
-rw-r--r--app/workers/hashed_storage/project_rollback_worker.rb26
-rw-r--r--app/workers/hashed_storage/rollbacker_worker.rb16
-rw-r--r--app/workers/project_daily_statistics_worker.rb13
-rw-r--r--app/workers/project_migrate_hashed_storage_worker.rb43
-rw-r--r--app/workers/reactive_caching_worker.rb2
-rw-r--r--babel.config.js (renamed from .babelrc.js)5
-rwxr-xr-xbin/background_jobs9
-rwxr-xr-xbin/secpick8
-rw-r--r--changelogs/README.md10
-rw-r--r--changelogs/unreleased/10097-number-utils.yml5
-rw-r--r--changelogs/unreleased/13784-simple-masking-of-protected-variables-in-logs.yml5
-rw-r--r--changelogs/unreleased/19745-forms-with-task-lists-can-be-overwritten-when-editing-simultaneously.yml5
-rw-r--r--changelogs/unreleased/20084-update-the-spinner-component.yml5
-rw-r--r--changelogs/unreleased/2105-add-setting-for-first-day-of-the-week.yml5
-rw-r--r--changelogs/unreleased/24642-activity_service_optimization.yml5
-rw-r--r--changelogs/unreleased/24680-support-bamboo-api-polymorphism.yml5
-rw-r--r--changelogs/unreleased/24875-label.yml5
-rw-r--r--changelogs/unreleased/25043-empty-states.yml5
-rw-r--r--changelogs/unreleased/25569-changing-wording-to-delete-when-referring-to-removing-a-branch.yml5
-rw-r--r--changelogs/unreleased/26375-markdown-footnotes-not-working.yml5
-rw-r--r--changelogs/unreleased/28500-empty-states-for-profile-page.yml5
-rw-r--r--changelogs/unreleased/30120-add-flat-square-badge-style.yml5
-rw-r--r--changelogs/unreleased/34555-empty-state-for-starred-projects.yml5
-rw-r--r--changelogs/unreleased/35638-move-language-setting-to-preferences.yml5
-rw-r--r--changelogs/unreleased/36445-better-indication-that-an-issue-has-been-moved-or-marked-as-duplicated.yml5
-rw-r--r--changelogs/unreleased/37990-task-list-bracket.yml5
-rw-r--r--changelogs/unreleased/39010-add-left-margin-to-1st-time-contributor-badge.yml5
-rw-r--r--changelogs/unreleased/39676-wiki-api-problems-on-update-parameters-and-500-error.yml5
-rw-r--r--changelogs/unreleased/40396-use-pgroups-for-background-jobs.yml5
-rw-r--r--changelogs/unreleased/40795-set-project-name-on-fork-api.yml5
-rw-r--r--changelogs/unreleased/40997-gitlab-pages-deploy-jobs-have-a-null-status.yml5
-rw-r--r--changelogs/unreleased/42086-project-fetch-statistics-api-http-only.yml5
-rw-r--r--changelogs/unreleased/42769-remove-expansion-hover-animation-from-status-icon-buttons.yml5
-rw-r--r--changelogs/unreleased/43681-display-last-activity-and-created-at-datetimes-for-users-in-admin-users.yml5
-rw-r--r--changelogs/unreleased/44332-add-openid-profile-scopes.yml5
-rw-r--r--changelogs/unreleased/44698-recaptcha.yml5
-rw-r--r--changelogs/unreleased/44740-api-to-verify-a-given-user-has-right-to-merge-a-given-mergerequest.yml5
-rw-r--r--changelogs/unreleased/45779-fix-default-visibility-level-for-projects.yml5
-rw-r--r--changelogs/unreleased/45791-number-of-repositories-usage-ping.yml5
-rw-r--r--changelogs/unreleased/46448-add-timestamps-for-each-stage-of-gitlab-rake-gitlab-backup-restore.yml5
-rw-r--r--changelogs/unreleased/46750-ci-empty-environment-is-created-even-when-a-job-isn-t-run-when-manual.yml5
-rw-r--r--changelogs/unreleased/47007-related-merge-requests-in-issue-design-restyle.yml5
-rw-r--r--changelogs/unreleased/47988-improve-milestone-queries-with-subqueries.yml5
-rw-r--r--changelogs/unreleased/48798-keybinding-mr-diff.yml5
-rw-r--r--changelogs/unreleased/49502-gpg-signature-api-endpoint.yml5
-rw-r--r--changelogs/unreleased/50006-expose-textcolor-from-public-labels-api.yml5
-rw-r--r--changelogs/unreleased/50013-add-browser-platform-flags.yml5
-rw-r--r--changelogs/unreleased/50313-use-kaniko-to-build-containers-in-autodevops.yml5
-rw-r--r--changelogs/unreleased/50352-sort-save.yml5
-rw-r--r--changelogs/unreleased/50433-make-emoji-picker-bigger.yml5
-rw-r--r--changelogs/unreleased/50521-block-emojis-and-symbol-characters-from-user-s-full-names-2.yml5
-rw-r--r--changelogs/unreleased/51754-admin-view-private-personal-snippets.yml5
-rw-r--r--changelogs/unreleased/51759-filter-by-language.yml5
-rw-r--r--changelogs/unreleased/51819-show-feed-toggle-under-system-notes.yml5
-rw-r--r--changelogs/unreleased/51913-api-getting-projects-for-users-with-dot-gets-404.yml5
-rw-r--r--changelogs/unreleased/52198-timer-is-vertically-misaligned-for-delayed-jobs-in-pipeline-actions.yml5
-rw-r--r--changelogs/unreleased/52275-fix-master-to-be-hyperlink.yml5
-rw-r--r--changelogs/unreleased/52278-squash-checkbox-fix.yml5
-rw-r--r--changelogs/unreleased/52347-lines-changed-statistics-is-not-easily-visible-in-mr-changes-view.yml5
-rw-r--r--changelogs/unreleased/52363-modifies-environment-scope-field-on-cluster-page.yml5
-rw-r--r--changelogs/unreleased/52363-ui-changes-to-cluster-and-ado-pages.yml5
-rw-r--r--changelogs/unreleased/52568-external-mr-diffs.yml5
-rw-r--r--changelogs/unreleased/52674-api-v4-projects-project_id-jobs-endpoint-hits-statement-timeout.yml5
-rw-r--r--changelogs/unreleased/52734-styling-of-user-project-and-group-avatars.yml5
-rw-r--r--changelogs/unreleased/52778-don-t-display-pipeline-status-if-pipelines-are-disabled.yml5
-rw-r--r--changelogs/unreleased/52877-ios-publishing-blog-post-and-gitlab-ci-yml-template.yml5
-rw-r--r--changelogs/unreleased/52971-merge-request-file-browser-should-always-be-possible-show-hide.yml5
-rw-r--r--changelogs/unreleased/53104-redesign-group-overview-ui-mvc.yml5
-rw-r--r--changelogs/unreleased/53325-admin-runners-page-fails-with-an-sql-statement-timeout.yml5
-rw-r--r--changelogs/unreleased/53411-remove_personal_access_tokens_token.yml5
-rw-r--r--changelogs/unreleased/53413-externalize-markdown-toolbar-tooltips.yml5
-rw-r--r--changelogs/unreleased/53431-fix-upcoming-milestone-filter-for-groups.yml5
-rw-r--r--changelogs/unreleased/53671-redirect-projects-id-to-project-page.yml5
-rw-r--r--changelogs/unreleased/53676-ip-address-of-gitlab-runner-is-wrong-in-the-runners-description.yml5
-rw-r--r--changelogs/unreleased/53714-inconsistent-text-color-for-labels.yml5
-rw-r--r--changelogs/unreleased/53856-changing-group-visibility-does-not-re-enable-save-button.yml6
-rw-r--r--changelogs/unreleased/53861-api-promote-project-milestone-to-a-group-milestone.yml5
-rw-r--r--changelogs/unreleased/53950-commit-comments-displayed-on-a-merge-request.yml5
-rw-r--r--changelogs/unreleased/53966-make-hashed-storage-migration-safer-and-more-inviting.yml5
-rw-r--r--changelogs/unreleased/54167-rename-project-tags-to-project-topics.yml5
-rw-r--r--changelogs/unreleased/54213-standardize-token-value-capitalization-in-filter-bar.yml5
-rw-r--r--changelogs/unreleased/54250-upstream-kubeclient-redirect-patch.yml5
-rw-r--r--changelogs/unreleased/54484-anchor-links-to-comments-or-system-notes-can-break-with-discussion-filters.yml5
-rw-r--r--changelogs/unreleased/54544-update-project-topics-styling-to-use-badges-design.yml5
-rw-r--r--changelogs/unreleased/54643-lower_issuable_finder_complexity.yml5
-rw-r--r--changelogs/unreleased/54725-fix-emoji-button-active-state.yml5
-rw-r--r--changelogs/unreleased/54796-api-sort-tie-breaker-for-pagination.yml5
-rw-r--r--changelogs/unreleased/54905-milestone-search.yml5
-rw-r--r--changelogs/unreleased/54924-refactor-notes-actions-params.yml5
-rw-r--r--changelogs/unreleased/55057-system-message-to-core.yml5
-rw-r--r--changelogs/unreleased/55098-ui-bug-adding-group-members-with-lower-permissions.yml5
-rw-r--r--changelogs/unreleased/55109-jira-integration-api-doesn-t-respect-available-format.yml5
-rw-r--r--changelogs/unreleased/55111-gitlab-api-does-not-manage-default_branch_protection-3-value.yml5
-rw-r--r--changelogs/unreleased/55242-skeleton-loading-releases.yml5
-rw-r--r--changelogs/unreleased/55312-svg.yml5
-rw-r--r--changelogs/unreleased/55376-related_merge_requests-api-call-returns-merge-requests-that-are-not-related-to-the-issue.yml5
-rw-r--r--changelogs/unreleased/55495-teamcity-use-revision-in-query.yml5
-rw-r--r--changelogs/unreleased/55628-artifacts-from-a-job-defined-after-a-parallel-job-are-not-downloaded.yml5
-rw-r--r--changelogs/unreleased/55703-md-image-borders.yml5
-rw-r--r--changelogs/unreleased/55820-adds-common-name-chart-value.yml5
-rw-r--r--changelogs/unreleased/55884-adjust-emoji-and-cancel-buttons-height-in-user-status-modal-when-emoji-is-changed.yml5
-rw-r--r--changelogs/unreleased/55893-artifacts-download.yml5
-rw-r--r--changelogs/unreleased/55925-if-there-is-only-one-changed-page-in-review-app-go-directly-there.yml5
-rw-r--r--changelogs/unreleased/55945-suggested-change-preview-highlight.yml5
-rw-r--r--changelogs/unreleased/55966-when-ref-is-ambiguous-createpipelineservice-raises-an-error.yml5
-rw-r--r--changelogs/unreleased/56010-user-profile-page-horizonal-whitespace-between-overview-columns-breaks-two-column-layout.yml5
-rw-r--r--changelogs/unreleased/56014-api-merge-request-squash-commit-messages.yml5
-rw-r--r--changelogs/unreleased/56019-archived-stuck.yml5
-rw-r--r--changelogs/unreleased/56036-fix-translation-of-in-in-job-details-sidebar.yml4
-rw-r--r--changelogs/unreleased/56110-cluster-kubernetes-api-500-error-on-post-request.yml5
-rw-r--r--changelogs/unreleased/56172-docs-fix-add-include-to-ci-param-list.yml5
-rw-r--r--changelogs/unreleased/56237-api-truncated-commit-title.yml5
-rw-r--r--changelogs/unreleased/56334-runners-ipv6-address-overlaps-other-values.yml5
-rw-r--r--changelogs/unreleased/56363-inconsitent-file-size-indication-across-different-ci-pages.yml6
-rw-r--r--changelogs/unreleased/56371-don-t-check-confidential-issues-for-spam.yml5
-rw-r--r--changelogs/unreleased/56379-pipeline-stages-job-action-button-icon-is-not-aligned.yml5
-rw-r--r--changelogs/unreleased/56389-remove-unwanted-suggestion-flash-margin.yml5
-rw-r--r--changelogs/unreleased/56398-fix-cluster-installation-loading-state.yml5
-rw-r--r--changelogs/unreleased/56417-update-helm-to-2-12-2.yml5
-rw-r--r--changelogs/unreleased/56477-units-are-appended-to-y-axis-label-on-metrics-dashboard.yml5
-rw-r--r--changelogs/unreleased/56485-implement-graphql-mergerequestsresolver.yml5
-rw-r--r--changelogs/unreleased/56492-implement-new-arguments-state-closed_before-and-closed_after-for-issuesresolver-in-graphql.yml5
-rw-r--r--changelogs/unreleased/56507-sh-bump-katex-0.10.0.yml5
-rw-r--r--changelogs/unreleased/56543-project-lists-further-iteration-improvements.yml5
-rw-r--r--changelogs/unreleased/56547-limit-sidekiq-logging-based-on-argument-size.yml5
-rw-r--r--changelogs/unreleased/56556-fix-markdown-table-border.yml5
-rw-r--r--changelogs/unreleased/56622-admin-settings-cannot-read-property-addeventlistener-of-null.yml5
-rw-r--r--changelogs/unreleased/56636-hashed-storage-afterrenameservice.yml5
-rw-r--r--changelogs/unreleased/56694-mark-group-level-labels-in-label-api-as-such.yml5
-rw-r--r--changelogs/unreleased/56726-fix-n-1-in-issues-and-merge-requests-api.yml5
-rw-r--r--changelogs/unreleased/56764-poor-ui-on-milestone-validation-error-page.yml5
-rw-r--r--changelogs/unreleased/56787-realtime-validation-for-user-fullname-and-username.yml5
-rw-r--r--changelogs/unreleased/56788-unicorn-metric-labels.yml5
-rw-r--r--changelogs/unreleased/56851-blank-values-in-reactive-cache.yml5
-rw-r--r--changelogs/unreleased/56863-system-messages-in-email.yml5
-rw-r--r--changelogs/unreleased/56871-list-issues-error.yml5
-rw-r--r--changelogs/unreleased/56873-only-load-syntax-highlighting-css-when-selected.yml5
-rw-r--r--changelogs/unreleased/56937-edit-knative-domain-after-it-has-been-deployed.yml5
-rw-r--r--changelogs/unreleased/56938-diff-file-headers-on-compare-not-quite-right.yml5
-rw-r--r--changelogs/unreleased/57063-implement-new-arguments-iid-for-issuesresolver-in-graphql.yml5
-rw-r--r--changelogs/unreleased/57101-api-docs-for-hangouts-chat-service-incorrect.yml5
-rw-r--r--changelogs/unreleased/57227-absolute-uri-missing-hierarchical-segment.yml5
-rw-r--r--changelogs/unreleased/57353-git-push-fails-on-large-lfs-files-where-the-push-take-a-long-time.yml5
-rw-r--r--changelogs/unreleased/57410-api-create-release-link-with-ftp-address-return-400-bad-request.yml5
-rw-r--r--changelogs/unreleased/57544-web-ide-new-directory-dialog-shows-file-templates.yml5
-rw-r--r--changelogs/unreleased/57579-gitlab-project-import-fails-sidekiq-undefined-method-import_jid.yml5
-rw-r--r--changelogs/unreleased/57582-dropdown-icon-misalignment-on-issues-list-on-mobile-screen.yml5
-rw-r--r--changelogs/unreleased/57671-fix_merge_request_base_pipeline.yml5
-rw-r--r--changelogs/unreleased/57712-project-import-error-user-expected-got-hash.yml5
-rw-r--r--changelogs/unreleased/57734-improve-label-dropdown-selection-performance.yml5
-rw-r--r--changelogs/unreleased/57768-remove-vertical-line.yml5
-rw-r--r--changelogs/unreleased/57784-make-closed-duplicate-and-closed-moved-button-a-link-to-target.yml5
-rw-r--r--changelogs/unreleased/57785-create-project-template-for-netlify.yml5
-rw-r--r--changelogs/unreleased/57788-project-labels-tooltip-missing.yml5
-rw-r--r--changelogs/unreleased/57794-project-template-for-net.yml5
-rw-r--r--changelogs/unreleased/57905-etag-caching-probably-broken-since-11-5-0.yml5
-rw-r--r--changelogs/unreleased/57991-frontend-pagination-needs-to-handle-cases-where-the-x-total-pages-header-isn-t-present.yml5
-rw-r--r--changelogs/unreleased/58020-fix-merge-api-endpoint-param.yml5
-rw-r--r--changelogs/unreleased/58082-project-template-for-go-micro.yml5
-rw-r--r--changelogs/unreleased/58098-auto-devops-postgres-version-variable.yml5
-rw-r--r--changelogs/unreleased/58149-fix-read-list-board-policy.yml6
-rw-r--r--changelogs/unreleased/58274-folder-icon-in-tags-page.yml5
-rw-r--r--changelogs/unreleased/8688-recursive-pipelines-ce-backport.yml5
-rw-r--r--changelogs/unreleased/9841-geo-unable-to-compare-branches-on-secondary.yml5
-rw-r--r--changelogs/unreleased/Projects--dropdown-is-misaligned-on-issue-boards-page.yml5
-rw-r--r--changelogs/unreleased/ab-54270-github-iid.yml5
-rw-r--r--changelogs/unreleased/ac-pages-subgroups.yml5
-rw-r--r--changelogs/unreleased/actioncontroller-parameters-deprecations.yml5
-rw-r--r--changelogs/unreleased/add-badge-count-to-projects-and-groups.yml5
-rw-r--r--changelogs/unreleased/add-project-level-config-for-prospective-merge-pipelines-ce.yml5
-rw-r--r--changelogs/unreleased/add-related-merge-request-count-to-api-response.yml5
-rw-r--r--changelogs/unreleased/add-title-attribute-to-file-row.yml5
-rw-r--r--changelogs/unreleased/add-uniqueness-validation-to-url-column-in-releases-link-model.yml5
-rw-r--r--changelogs/unreleased/add-youtrack-integration.yml5
-rw-r--r--changelogs/unreleased/adrianmoisey-GITLAB_PAGES_PREDEFINED_VARIABLES.yml5
-rw-r--r--changelogs/unreleased/adriel-remove-feature-flag.yml5
-rw-r--r--changelogs/unreleased/allow-maintainers-to-remove-pages.yml5
-rw-r--r--changelogs/unreleased/an-dtracing-test-for-invalid-tracers.yml5
-rw-r--r--changelogs/unreleased/an-gilab-process-name.yml5
-rw-r--r--changelogs/unreleased/an-opentracing-active-record-tracing.yml5
-rw-r--r--changelogs/unreleased/an-opentracing-factory.yml5
-rw-r--r--changelogs/unreleased/an-opentracing-propagation.yml5
-rw-r--r--changelogs/unreleased/an-opentracing-render-tracing.yml5
-rw-r--r--changelogs/unreleased/api-group-labels.yml5
-rw-r--r--changelogs/unreleased/api-nested-group-permission.yml5
-rw-r--r--changelogs/unreleased/api-tags-search.yml5
-rw-r--r--changelogs/unreleased/api-wiki-dot-slug.yml5
-rw-r--r--changelogs/unreleased/auto-devops-custom-domains.yml5
-rw-r--r--changelogs/unreleased/auto-devops-kubectl-1-11-6.yml5
-rw-r--r--changelogs/unreleased/backup_aws_sse-c.yml5
-rw-r--r--changelogs/unreleased/backup_restore_fix_issue_46891.yml5
-rw-r--r--changelogs/unreleased/bump-ingress-chart-112.yml5
-rw-r--r--changelogs/unreleased/bvl-fix-race-condition-creating-signature.yml5
-rw-r--r--changelogs/unreleased/change-badges-example-to-pipeline.yml5
-rw-r--r--changelogs/unreleased/changelogs-readme.yml5
-rw-r--r--changelogs/unreleased/chore-update-js-regex.yml5
-rw-r--r--changelogs/unreleased/cleanup-leagcy-artifact-migration.yml5
-rw-r--r--changelogs/unreleased/cluster_application_version_updated.yml5
-rw-r--r--changelogs/unreleased/cluster_status_for_ugprading.yml5
-rw-r--r--changelogs/unreleased/consistent-pagination.yml5
-rw-r--r--changelogs/unreleased/container-repository-cleanup-api.yml5
-rw-r--r--changelogs/unreleased/custom-helm-chart-repo.yml5
-rw-r--r--changelogs/unreleased/deprecated-force-reload.yml6
-rw-r--r--changelogs/unreleased/diff-file-finder.yml5
-rw-r--r--changelogs/unreleased/diff-tree-collapse-directories.yml5
-rw-r--r--changelogs/unreleased/diff-tree-resizable.yml5
-rw-r--r--changelogs/unreleased/dm-copy-suggestion-as-gfm.yml5
-rw-r--r--changelogs/unreleased/dm-trim-discussion-truncated-line-first-chars.yml5
-rw-r--r--changelogs/unreleased/docs-push-mirror-GitLab-GitHub.yml5
-rw-r--r--changelogs/unreleased/dz-sort-labels-alphabetically.yml5
-rw-r--r--changelogs/unreleased/expire-job-artifacts-worker.yml5
-rw-r--r--changelogs/unreleased/expose-merge-ref-to-runner.yml5
-rw-r--r--changelogs/unreleased/feature-gb-enable-ci-persisted-stages-by-default.yml5
-rw-r--r--changelogs/unreleased/feature-runner-tag-filter-for-admin-view.yml5
-rw-r--r--changelogs/unreleased/features-document-graphicsmagick-source-installation.yml5
-rw-r--r--changelogs/unreleased/filter-confidential-issues.yml5
-rw-r--r--changelogs/unreleased/filter-note-parameters.yml5
-rw-r--r--changelogs/unreleased/fix-39759-new-project-icon-vertical-align.yml5
-rw-r--r--changelogs/unreleased/fix-403-page-is-rendered-but-404-is-the-response.yml5
-rw-r--r--changelogs/unreleased/fix-49388.yml5
-rw-r--r--changelogs/unreleased/fix-55956-oversized-dropdown-button-custom-notifications.yml5
-rw-r--r--changelogs/unreleased/fix-56558-move-primary-button.yml5
-rw-r--r--changelogs/unreleased/fix-auto-devops-domain-title-on-admin-settings.yml5
-rw-r--r--changelogs/unreleased/fix-badges-logs.yml5
-rw-r--r--changelogs/unreleased/fix-repo-settings-file-upload-error.yml5
-rw-r--r--changelogs/unreleased/fix_jira_integration_VCS1019.yml5
-rw-r--r--changelogs/unreleased/fj-55882-fix-files-api-content-disposition.yml5
-rw-r--r--changelogs/unreleased/force-redeploy-on-updated-secrets.yml5
-rw-r--r--changelogs/unreleased/gitaly-update-1-13-0.yml5
-rw-r--r--changelogs/unreleased/gitaly-update-1.18.0.yml5
-rw-r--r--changelogs/unreleased/gitlab-workhorse-update-8.1.0.yml5
-rw-r--r--changelogs/unreleased/gitlab_kubernetes_helm_bump.yml5
-rw-r--r--changelogs/unreleased/gt-externalize-app-views-clusters.yml5
-rw-r--r--changelogs/unreleased/gt-externalize-app-views-email_rejection_mailer.yml5
-rw-r--r--changelogs/unreleased/gt-externalize-app-views-instance_statistics.yml5
-rw-r--r--changelogs/unreleased/gt-externalize-app-views-projects-ci.yml5
-rw-r--r--changelogs/unreleased/gt-externalize-app-views-projects-commit.yml5
-rw-r--r--changelogs/unreleased/gt-externalize-app-views-projects-milestones.yml5
-rw-r--r--changelogs/unreleased/gt-externalize-app-views-projects-pages_domains.yml5
-rw-r--r--changelogs/unreleased/gt-externalize-app-views-projects-project_members.yml5
-rw-r--r--changelogs/unreleased/gt-externalize-app-views-sent_notifications.yml5
-rw-r--r--changelogs/unreleased/gt-remove-unused-button-class.yml5
-rw-r--r--changelogs/unreleased/gt-rename-gray-theme-color-variables.yml5
-rw-r--r--changelogs/unreleased/gt-update-new-password-breadcrumb.yml5
-rw-r--r--changelogs/unreleased/gt-update-operations-settings-breadcrumb-trail.yml5
-rw-r--r--changelogs/unreleased/gt-update-string-struture-for-group-runners.yml5
-rw-r--r--changelogs/unreleased/helm-2-12-3.yml5
-rw-r--r--changelogs/unreleased/hnk-master-patch-61932.yml5
-rw-r--r--changelogs/unreleased/homepage-proj-descr-cutoff.yml5
-rw-r--r--changelogs/unreleased/import-go-to-project-cta.yml5
-rw-r--r--changelogs/unreleased/improve-snippets-empty-state.yml5
-rw-r--r--changelogs/unreleased/include-ci-yaml.yml5
-rw-r--r--changelogs/unreleased/introduce-environment-search-endpoint.yml5
-rw-r--r--changelogs/unreleased/iss-32584-preserve-line-number-fragment-after-redirect.yml6
-rw-r--r--changelogs/unreleased/issue_55744.yml5
-rw-r--r--changelogs/unreleased/jc-fix-set-project-writable.yml5
-rw-r--r--changelogs/unreleased/jej-avoid-csrf-check-on-saml-failure.yml5
-rw-r--r--changelogs/unreleased/jej-feature-gates-can-be-set-by-group-path.yml5
-rw-r--r--changelogs/unreleased/jira-link-mention-compact.yml5
-rw-r--r--changelogs/unreleased/jlenny-AddPagesTemplates.yml5
-rw-r--r--changelogs/unreleased/jlenny-NewAndroidTemplate.yml5
-rw-r--r--changelogs/unreleased/jprovazn-remove-redcarpet.yml5
-rw-r--r--changelogs/unreleased/kinolaev-master-patch-87865.yml5
-rw-r--r--changelogs/unreleased/knative-list.yml5
-rw-r--r--changelogs/unreleased/knative-show-page.yml5
-rw-r--r--changelogs/unreleased/local-markdown-version-bkp3.yml5
-rw-r--r--changelogs/unreleased/mg-fix-bad-cluster-update-entrypoint.yml5
-rw-r--r--changelogs/unreleased/monospace-registry-tags.yml5
-rw-r--r--changelogs/unreleased/move-job-cancel-btn.yml5
-rw-r--r--changelogs/unreleased/move-permission-check-manual-actions-on-deployments.yml5
-rw-r--r--changelogs/unreleased/move_chatops_to_core.yml5
-rw-r--r--changelogs/unreleased/mr-file-tree-blob-truncate-improvements.yml5
-rw-r--r--changelogs/unreleased/mr-rebase-failing-tests.yml5
-rw-r--r--changelogs/unreleased/not-run-pipeline-on-empty-merge-request.yml5
-rw-r--r--changelogs/unreleased/notebook-multiple-outputs.yml5
-rw-r--r--changelogs/unreleased/notes-awards-double-tooltip-fix.yml5
-rw-r--r--changelogs/unreleased/osw-create-and-store-merge-ref-for-mrs.yml5
-rw-r--r--changelogs/unreleased/osw-enforces-project-removal-with-past-failed-attempts.yml5
-rw-r--r--changelogs/unreleased/osw-fetch-latest-version-when-creating-suggestions.yml5
-rw-r--r--changelogs/unreleased/osw-fix-bottom-expansion-diff-comment.yml5
-rw-r--r--changelogs/unreleased/osw-merge-refs-refreshing-api.yml5
-rw-r--r--changelogs/unreleased/patch-38.yml5
-rw-r--r--changelogs/unreleased/patch-45.yml5
-rw-r--r--changelogs/unreleased/persist-source-sha-and-target-sha-for-pipelines.yml5
-rw-r--r--changelogs/unreleased/pl-serialize-ac-parameters.yml5
-rw-r--r--changelogs/unreleased/profile-project-empty-state.yml5
-rw-r--r--changelogs/unreleased/raise-on-unfiltered-params.yml5
-rw-r--r--changelogs/unreleased/rd-update-last_activity_on-on-logins-and-browsing-activity-54947.yml5
-rw-r--r--changelogs/unreleased/refactor-56366-extract-resolve-discussion-button.yml5
-rw-r--r--changelogs/unreleased/refactor-56367-extract-resolve-with-issue-button-component.yml5
-rw-r--r--changelogs/unreleased/refactor-56369-extract-jump-to-next-discussion-button.yml5
-rw-r--r--changelogs/unreleased/refactor-56370-extract-reply-placeholder-component.yml5
-rw-r--r--changelogs/unreleased/refactor-merge-request-between-pipeline-and-build.yml5
-rw-r--r--changelogs/unreleased/remove-cancel-all-button-in-job-list-view.yml5
-rw-r--r--changelogs/unreleased/remove-diff-coloring.yml5
-rw-r--r--changelogs/unreleased/remove-gap-between-mr-tabs-and-file-header.yml5
-rw-r--r--changelogs/unreleased/remove-second-primary-button-on-wiki-edit.yml5
-rw-r--r--changelogs/unreleased/rs-admin-user-case-insensitive.yml5
-rw-r--r--changelogs/unreleased/search-title.yml5
-rw-r--r--changelogs/unreleased/security-22076-sanitize-url-in-names.yml6
-rw-r--r--changelogs/unreleased/security-2770-verify-bundle-import-files.yml5
-rw-r--r--changelogs/unreleased/security-55320-stored-xss-in-user-status.yml5
-rw-r--r--changelogs/unreleased/security-stored-xss-via-katex.yml5
-rw-r--r--changelogs/unreleased/sh-bump-fog-gem.yml5
-rw-r--r--changelogs/unreleased/sh-disable-nil-user-id-identity-validation.yml5
-rw-r--r--changelogs/unreleased/sh-encode-content-disposition.yml5
-rw-r--r--changelogs/unreleased/sh-fix-backfill-project-repo-migration.yml5
-rw-r--r--changelogs/unreleased/sh-fix-bitbucket-server-error-handling.yml5
-rw-r--r--changelogs/unreleased/sh-fix-cpp-templates-404.yml5
-rw-r--r--changelogs/unreleased/sh-fix-double-xhr-pipelines.yml5
-rw-r--r--changelogs/unreleased/sh-fix-import-redirect-vulnerability.yml5
-rw-r--r--changelogs/unreleased/sh-fix-issue-58103.yml5
-rw-r--r--changelogs/unreleased/sh-fix-pages-zip-constant.yml5
-rw-r--r--changelogs/unreleased/sh-fix-snippet-uploads-path-lookup.yml5
-rw-r--r--changelogs/unreleased/sh-fix-upload-snippets-with-relative-url-root.yml5
-rw-r--r--changelogs/unreleased/sh-import-source-branch-github-forks.yml5
-rw-r--r--changelogs/unreleased/sh-include-project-path-for-internal-api.yml5
-rw-r--r--changelogs/unreleased/sh-issue-53419-fix.yml5
-rw-r--r--changelogs/unreleased/sh-preload-associations-for-group-api.yml5
-rw-r--r--changelogs/unreleased/sh-remove-nplusone-admin-runners-tags.yml5
-rw-r--r--changelogs/unreleased/sh-wip-fix-duplicate-env-xhr.yml5
-rw-r--r--changelogs/unreleased/shared_with_group_path.yml5
-rw-r--r--changelogs/unreleased/support-chunking-in-client.yml5
-rw-r--r--changelogs/unreleased/support-only-changes-on-mr-pipelines.yml5
-rw-r--r--changelogs/unreleased/test-permissions.yml5
-rw-r--r--changelogs/unreleased/tooltips-to-top.yml5
-rw-r--r--changelogs/unreleased/tr-error-tracking-project-selection.yml5
-rw-r--r--changelogs/unreleased/twang2218-gitlab-ce-i18n-extract-app-views-search.yml5
-rw-r--r--changelogs/unreleased/update-gitaly.yml5
-rw-r--r--changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-1-45.yml5
-rw-r--r--changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-2-0.yml5
-rw-r--r--changelogs/unreleased/update-gitlab-styles.yml5
-rw-r--r--changelogs/unreleased/update-pages-config-only-when-changed.yml5
-rw-r--r--changelogs/unreleased/update-pages-extensionless-urls.yml5
-rw-r--r--changelogs/unreleased/update-sidekiq-cron.yml6
-rw-r--r--changelogs/unreleased/update-smooshpack.yml5
-rw-r--r--changelogs/unreleased/update-spriteicon-from-icon-on-profile.yml5
-rw-r--r--changelogs/unreleased/update-ui-admin-appearance.yml5
-rw-r--r--changelogs/unreleased/update-workhorse-8-2-0.yml5
-rw-r--r--changelogs/unreleased/use-deployment-relation-to-fetch-environment-ce.yml5
-rw-r--r--changelogs/unreleased/use_upgrade_install_for_helm_apps.yml5
-rw-r--r--changelogs/unreleased/web-ide-commit-header-icon-alignment-fix.yml5
-rw-r--r--changelogs/unreleased/web-ide-default-editor.yml5
-rw-r--r--changelogs/unreleased/winh-add-list-dropdown-height.yml5
-rw-r--r--changelogs/unreleased/workhorse-8-3-0.yml5
-rw-r--r--changelogs/unreleased/yoginth-avatar-on-settings-sidebar.yml5
-rw-r--r--changelogs/unreleased/zj-feature-gate-set-project-path.yml5
-rw-r--r--changelogs/unreleased/zj-load-languages-from-database.yml5
-rw-r--r--config/application.rb4
-rw-r--r--config/gitlab.yml.example2
-rw-r--r--config/initializers/1_settings.rb2
-rw-r--r--config/initializers/fog_core_patch.rb52
-rw-r--r--config/initializers/graphql.rb4
-rw-r--r--config/initializers/sidekiq.rb6
-rw-r--r--config/locales/en.yml4
-rw-r--r--config/routes/admin.rb4
-rw-r--r--config/routes/group.rb2
-rw-r--r--config/routes/import.rb4
-rw-r--r--config/routes/project.rb3
-rw-r--r--config/sidekiq_queues.yml3
-rw-r--r--config/webpack.config.js12
-rw-r--r--danger/changelog/Dangerfile6
-rw-r--r--danger/commit_messages/Dangerfile243
-rw-r--r--danger/documentation/Dangerfile41
-rw-r--r--danger/plugins/helper.rb37
-rw-r--r--danger/roulette/Dangerfile81
-rw-r--r--db/migrate/20180209115333_create_chatops_tables.rb26
-rw-r--r--db/migrate/20180314145917_add_header_and_footer_banners_to_appearances_table.rb18
-rw-r--r--db/migrate/20181205171941_create_project_daily_statistics.rb18
-rw-r--r--db/migrate/20190206193120_add_index_to_tags.rb18
-rw-r--r--db/migrate/20190215154930_add_merge_pipelines_enabled_to_ci_cd_settings.rb11
-rw-r--r--db/migrate/20190218134158_add_masked_to_ci_variables.rb21
-rw-r--r--db/migrate/20190218134209_add_masked_to_ci_group_variables.rb21
-rw-r--r--db/migrate/20190220142344_add_email_header_and_footer_enabled_flag_to_appearances_table.rb17
-rw-r--r--db/migrate/20190220150130_add_extra_shas_to_ci_pipelines.rb12
-rw-r--r--db/migrate/20190228092516_clean_up_noteable_id_for_notes_on_commits.rb33
-rw-r--r--db/migrate/limits_to_mysql.rb13
-rw-r--r--db/post_migrate/20181101091005_steal_digest_column.rb17
-rw-r--r--db/post_migrate/20181101091124_remove_token_from_personal_access_tokens.rb11
-rw-r--r--db/post_migrate/20190301081611_migrate_project_migrate_sidekiq_queue.rb17
-rw-r--r--db/schema.rb35
-rw-r--r--doc/README.md3
-rw-r--r--doc/administration/auth/authentiq.md3
-rw-r--r--doc/administration/auth/ldap.md3
-rw-r--r--doc/administration/auth/okta.md1
-rw-r--r--doc/administration/container_registry.md1
-rw-r--r--doc/administration/high_availability/gitlab.md8
-rw-r--r--doc/administration/high_availability/redis.md9
-rw-r--r--doc/administration/high_availability/redis_source.md4
-rw-r--r--doc/administration/incoming_email.md385
-rw-r--r--doc/administration/index.md30
-rw-r--r--doc/administration/integration/plantuml.md1
-rw-r--r--doc/administration/merge_request_diffs.md33
-rw-r--r--doc/administration/monitoring/performance/grafana_configuration.md2
-rw-r--r--doc/administration/monitoring/performance/performance_bar.md6
-rw-r--r--doc/administration/monitoring/prometheus/gitlab_metrics.md5
-rw-r--r--doc/administration/operations/cleaning_up_redis_sessions.md1
-rw-r--r--doc/administration/operations/index.md24
-rw-r--r--doc/administration/pages/index.md12
-rw-r--r--doc/administration/pages/source.md13
-rw-r--r--doc/administration/polling.md18
-rw-r--r--doc/administration/raketasks/maintenance.md1
-rw-r--r--doc/administration/repository_storage_types.md1
-rw-r--r--doc/administration/restart_gitlab.md1
-rw-r--r--doc/api/README.md207
-rw-r--r--doc/api/award_emoji.md11
-rw-r--r--doc/api/boards.md6
-rw-r--r--doc/api/commits.md43
-rw-r--r--doc/api/container_registry.md1
-rw-r--r--doc/api/discussions.md1
-rw-r--r--doc/api/features.md3
-rw-r--r--doc/api/group_labels.md6
-rw-r--r--doc/api/group_milestones.md1
-rw-r--r--doc/api/import.md2
-rw-r--r--doc/api/issues.md23
-rw-r--r--doc/api/labels.md33
-rw-r--r--doc/api/lint.md2
-rw-r--r--doc/api/merge_requests.md40
-rw-r--r--doc/api/milestones.md15
-rw-r--r--doc/api/oauth2.md1
-rw-r--r--doc/api/pages_domains.md12
-rw-r--r--doc/api/project_import_export.md27
-rw-r--r--doc/api/project_snippets.md1
-rw-r--r--doc/api/project_statistics.md49
-rw-r--r--doc/api/project_templates.md1
-rw-r--r--doc/api/projects.md2
-rw-r--r--doc/api/protected_branches.md1
-rw-r--r--doc/api/releases/index.md16
-rw-r--r--doc/api/releases/links.md1
-rw-r--r--doc/api/repository_files.md1
-rw-r--r--doc/api/runners.md40
-rw-r--r--doc/api/search.md2
-rw-r--r--doc/api/services.md43
-rw-r--r--doc/api/sidekiq_metrics.md1
-rw-r--r--doc/api/tags.md1
-rw-r--r--doc/api/templates/gitignores.md12
-rw-r--r--doc/api/users.md1
-rw-r--r--doc/api/wikis.md1
-rw-r--r--doc/articles/openshift_and_gitlab/index.md1
-rw-r--r--doc/ci/README.md247
-rw-r--r--doc/ci/caching/index.md7
-rw-r--r--doc/ci/chatops/README.md61
-rw-r--r--doc/ci/chatops/img/gitlab-chatops-icon-small.pngbin0 -> 2922 bytes
-rw-r--r--doc/ci/chatops/img/gitlab-chatops-icon.pngbin0 -> 12308 bytes
-rw-r--r--doc/ci/docker/README.md8
-rw-r--r--doc/ci/docker/using_docker_images.md4
-rw-r--r--doc/ci/environments.md10
-rw-r--r--doc/ci/examples/README.md1
-rw-r--r--doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md2
-rw-r--r--doc/ci/examples/deployment/composer-npm-deploy.md1
-rw-r--r--doc/ci/examples/end_to_end_testing_webdriverio/img/deployed_dependency_update.pngbin0 -> 67788 bytes
-rw-r--r--doc/ci/examples/end_to_end_testing_webdriverio/index.md251
-rw-r--r--doc/ci/examples/laravel_with_gitlab_and_envoy/index.md7
-rw-r--r--doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md4
-rw-r--r--doc/ci/merge_request_pipelines/index.md4
-rw-r--r--doc/ci/pipelines.md2
-rw-r--r--doc/ci/runners/README.md67
-rw-r--r--doc/ci/ssh_keys/README.md2
-rw-r--r--doc/ci/variables/README.md4
-rw-r--r--doc/ci/yaml/README.md48
-rw-r--r--doc/customization/help_message.md13
-rw-r--r--doc/customization/help_message/help_text.pngbin0 -> 86118 bytes
-rw-r--r--doc/customization/help_message/help_text_on_help_page.pngbin0 -> 24355 bytes
-rw-r--r--doc/customization/index.md18
-rw-r--r--doc/customization/libravatar.md4
-rw-r--r--doc/customization/system_header_and_footer_messages.md22
-rw-r--r--doc/customization/system_header_and_footer_messages/appearance.pngbin0 -> 124214 bytes
-rw-r--r--doc/customization/system_header_and_footer_messages/custom_header_footer.pngbin0 -> 484001 bytes
-rw-r--r--doc/customization/system_header_and_footer_messages/sign_up_custom_header_and_footer.pngbin0 -> 360832 bytes
-rw-r--r--doc/customization/welcome_message.md13
-rw-r--r--doc/development/README.md1
-rw-r--r--doc/development/api_graphql_styleguide.md24
-rw-r--r--doc/development/architecture.md23
-rw-r--r--doc/development/automatic_ce_ee_merge.md20
-rw-r--r--doc/development/code_review.md18
-rw-r--r--doc/development/contributing/index.md2
-rw-r--r--doc/development/contributing/issue_workflow.md18
-rw-r--r--doc/development/database_debugging.md3
-rw-r--r--doc/development/diffs.md5
-rw-r--r--doc/development/distributed_tracing.md182
-rw-r--r--doc/development/documentation/feature-change-workflow.md189
-rw-r--r--doc/development/documentation/improvement-workflow.md38
-rw-r--r--doc/development/documentation/index.md4
-rw-r--r--doc/development/documentation/site_architecture/global_nav.md3
-rw-r--r--doc/development/documentation/site_architecture/index.md2
-rw-r--r--doc/development/documentation/structure.md4
-rw-r--r--doc/development/documentation/styleguide.md20
-rw-r--r--doc/development/documentation/workflow.md6
-rw-r--r--doc/development/ee_features.md92
-rw-r--r--doc/development/fe_guide/accessibility.md1
-rw-r--r--doc/development/fe_guide/design_patterns.md1
-rw-r--r--doc/development/fe_guide/development_process.md8
-rw-r--r--doc/development/fe_guide/droplab/droplab.md1
-rw-r--r--doc/development/fe_guide/droplab/plugins/filter.md2
-rw-r--r--doc/development/fe_guide/droplab/plugins/input_setter.md3
-rw-r--r--doc/development/fe_guide/graphql.md30
-rw-r--r--doc/development/fe_guide/index.md19
-rw-r--r--doc/development/fe_guide/performance.md12
-rw-r--r--doc/development/fe_guide/security.md1
-rw-r--r--doc/development/fe_guide/style_guide_js.md22
-rw-r--r--doc/development/fe_guide/vue.md2
-rw-r--r--doc/development/feature_flags.md6
-rw-r--r--doc/development/file_storage.md1
-rw-r--r--doc/development/i18n/merging_translations.md3
-rw-r--r--doc/development/i18n/proofreader.md3
-rw-r--r--doc/development/img/distributed_tracing_jaeger_ui.pngbin0 -> 1032713 bytes
-rw-r--r--doc/development/img/distributed_tracing_performance_bar.pngbin0 -> 108809 bytes
-rw-r--r--doc/development/import_export.md25
-rw-r--r--doc/development/kubernetes.md126
-rw-r--r--doc/development/logging.md30
-rw-r--r--doc/development/migration_style_guide.md1
-rw-r--r--doc/development/new_fe_guide/development/accessibility.md3
-rw-r--r--doc/development/new_fe_guide/development/testing.md1
-rw-r--r--doc/development/new_fe_guide/event_tracking.md74
-rw-r--r--doc/development/new_fe_guide/index.md4
-rw-r--r--doc/development/new_fe_guide/style/html.md4
-rw-r--r--doc/development/new_fe_guide/style/javascript.md42
-rw-r--r--doc/development/ordering_table_columns.md2
-rw-r--r--doc/development/performance.md17
-rw-r--r--doc/development/policies.md47
-rw-r--r--doc/development/polling.md1
-rw-r--r--doc/development/profiling.md8
-rw-r--r--doc/development/rake_tasks.md2
-rw-r--r--doc/development/shell_commands.md5
-rw-r--r--doc/development/testing_guide/best_practices.md22
-rw-r--r--doc/development/testing_guide/ci.md4
-rw-r--r--doc/development/testing_guide/end_to_end_tests.md16
-rw-r--r--doc/development/testing_guide/frontend_testing.md5
-rw-r--r--doc/development/testing_guide/review_apps.md32
-rw-r--r--doc/development/testing_guide/testing_levels.md4
-rw-r--r--doc/gitlab-basics/create-your-ssh-keys.md12
-rw-r--r--doc/install/aws/index.md5
-rw-r--r--doc/install/azure/index.md227
-rw-r--r--doc/install/installation.md6
-rw-r--r--doc/install/kubernetes/gitlab_chart.md2
-rw-r--r--doc/install/kubernetes/gitlab_omnibus.md12
-rw-r--r--doc/install/kubernetes/gitlab_runner_chart.md17
-rw-r--r--doc/install/kubernetes/index.md2
-rw-r--r--doc/install/kubernetes/preparation/eks.md3
-rw-r--r--doc/install/kubernetes/preparation/networking.md4
-rw-r--r--doc/install/kubernetes/preparation/tiller.md18
-rw-r--r--doc/install/kubernetes/preparation/tools_installation.md2
-rw-r--r--doc/install/openshift_and_gitlab/index.md2
-rw-r--r--doc/install/requirements.md2
-rw-r--r--doc/integration/README.md3
-rw-r--r--doc/integration/akismet.md1
-rw-r--r--doc/integration/auth0.md14
-rw-r--r--doc/integration/cas.md2
-rw-r--r--doc/integration/external-issue-tracker.md5
-rw-r--r--doc/integration/facebook.md2
-rw-r--r--doc/integration/github.md5
-rw-r--r--doc/integration/omniauth.md1
-rw-r--r--doc/integration/saml.md1
-rw-r--r--doc/integration/slash_commands.md4
-rw-r--r--doc/public_access/public_access.md2
-rw-r--r--doc/raketasks/backup_restore.md29
-rw-r--r--doc/raketasks/user_management.md2
-rw-r--r--doc/security/img/ssh_keys_restricted_key_icon.pngbin0 -> 4887 bytes
-rw-r--r--doc/security/ssh_keys_restrictions.md8
-rw-r--r--doc/system_hooks/system_hooks.md6
-rw-r--r--doc/topics/autodevops/index.md29
-rw-r--r--doc/topics/autodevops/quick_start_guide.md14
-rw-r--r--doc/topics/git/numerous_undo_possibilities_in_git/index.md8
-rw-r--r--doc/university/glossary/README.md1
-rw-r--r--doc/university/high-availability/aws/README.md15
-rw-r--r--doc/university/support/README.md1
-rw-r--r--doc/university/training/end-user/README.md5
-rw-r--r--doc/university/training/topics/git_log.md1
-rw-r--r--doc/university/training/topics/rollback_commits.md1
-rw-r--r--doc/university/training/topics/stash.md2
-rw-r--r--doc/update/mysql_to_postgresql.md7
-rw-r--r--doc/update/restore_after_failure.md1
-rw-r--r--doc/update/upgrading_postgresql_using_slony.md1
-rw-r--r--doc/user/abuse_reports.md1
-rw-r--r--doc/user/admin_area/index.md29
-rw-r--r--doc/user/admin_area/monitoring/health_check.md1
-rw-r--r--doc/user/admin_area/settings/continuous_integration.md6
-rw-r--r--doc/user/admin_area/settings/index.md4
-rw-r--r--doc/user/discussions/index.md37
-rw-r--r--doc/user/gitlab_com/index.md2
-rw-r--r--doc/user/group/clusters/index.md1
-rw-r--r--doc/user/group/index.md16
-rw-r--r--doc/user/index.md28
-rw-r--r--doc/user/instance_statistics/convdev.md16
-rw-r--r--doc/user/markdown.md18
-rw-r--r--doc/user/permissions.md6
-rw-r--r--doc/user/profile/account/two_factor_authentication.md4
-rw-r--r--doc/user/profile/index.md5
-rw-r--r--doc/user/profile/preferences.md6
-rw-r--r--doc/user/project/clusters/eks_and_gitlab/index.md2
-rw-r--r--doc/user/project/clusters/index.md140
-rw-r--r--doc/user/project/clusters/runbooks/index.md8
-rw-r--r--doc/user/project/clusters/serverless/img/app-domain.pngbin209263 -> 0 bytes
-rw-r--r--doc/user/project/clusters/serverless/img/dns-entry.pngbin19583 -> 66116 bytes
-rw-r--r--doc/user/project/clusters/serverless/img/install-knative.pngbin13003 -> 86225 bytes
-rw-r--r--doc/user/project/clusters/serverless/img/serverless-page.pngbin62369 -> 191568 bytes
-rw-r--r--doc/user/project/clusters/serverless/index.md69
-rw-r--r--doc/user/project/container_registry.md1
-rw-r--r--doc/user/project/cycle_analytics.md5
-rw-r--r--doc/user/project/description_templates.md1
-rw-r--r--doc/user/project/import/bitbucket_server.md12
-rw-r--r--doc/user/project/import/fogbugz.md2
-rw-r--r--doc/user/project/import/github.md2
-rw-r--r--doc/user/project/index.md46
-rw-r--r--doc/user/project/integrations/bamboo.md1
-rw-r--r--doc/user/project/integrations/irker.md8
-rw-r--r--doc/user/project/integrations/project_services.md1
-rw-r--r--doc/user/project/integrations/redmine.md6
-rw-r--r--doc/user/project/integrations/webhooks.md13
-rw-r--r--doc/user/project/integrations/youtrack.md31
-rw-r--r--doc/user/project/issue_board.md10
-rw-r--r--doc/user/project/issues/automatic_issue_closing.md1
-rw-r--r--doc/user/project/issues/create_new_issue.md4
-rw-r--r--doc/user/project/issues/due_dates.md1
-rw-r--r--doc/user/project/issues/index.md2
-rw-r--r--doc/user/project/issues/issues_functionalities.md18
-rw-r--r--doc/user/project/operations/error_tracking.md7
-rw-r--r--doc/user/project/pages/getting_started_part_four.md10
-rw-r--r--doc/user/project/pages/getting_started_part_one.md42
-rw-r--r--doc/user/project/pages/getting_started_part_three.md30
-rw-r--r--doc/user/project/pages/getting_started_part_two.md34
-rw-r--r--doc/user/project/pages/index.md8
-rw-r--r--doc/user/project/pages/introduction.md4
-rw-r--r--doc/user/project/pipelines/job_artifacts.md2
-rw-r--r--doc/user/project/pipelines/schedules.md2
-rw-r--r--doc/user/project/pipelines/settings.md4
-rw-r--r--doc/user/project/protected_tags.md3
-rw-r--r--doc/user/project/quick_actions.md1
-rw-r--r--doc/user/project/repository/gpg_signed_commits/index.md5
-rw-r--r--doc/user/project/repository/index.md18
-rw-r--r--doc/user/project/repository/web_editor.md1
-rw-r--r--doc/user/project/settings/index.md2
-rw-r--r--doc/user/project/web_ide/index.md22
-rw-r--r--doc/user/reserved_names.md1
-rw-r--r--doc/workflow/forking_workflow.md1
-rw-r--r--doc/workflow/gitlab_flow.md458
-rw-r--r--doc/workflow/shortcuts.md2
-rw-r--r--doc/workflow/time_tracking.md2
-rw-r--r--jest.config.js10
-rw-r--r--lib/api/api.rb1
-rw-r--r--lib/api/commits.rb16
-rw-r--r--lib/api/entities.rb98
-rw-r--r--lib/api/features.rb1
-rw-r--r--lib/api/helpers.rb8
-rw-r--r--lib/api/helpers/internal_helpers.rb8
-rw-r--r--lib/api/helpers/pagination.rb75
-rw-r--r--lib/api/helpers/runner.rb2
-rw-r--r--lib/api/internal.rb9
-rw-r--r--lib/api/issues.rb19
-rw-r--r--lib/api/merge_requests.rb31
-rw-r--r--lib/api/notes.rb2
-rw-r--r--lib/api/project_milestones.rb17
-rw-r--r--lib/api/project_statistics.rb23
-rw-r--r--lib/api/project_templates.rb5
-rw-r--r--lib/api/projects.rb8
-rw-r--r--lib/api/runners.rb6
-rw-r--r--lib/api/services.rb28
-rw-r--r--lib/api/users.rb2
-rw-r--r--lib/api/wikis.rb13
-rw-r--r--lib/banzai/filter/footnote_filter.rb23
-rw-r--r--lib/banzai/filter/relative_link_filter.rb5
-rw-r--r--lib/bitbucket_server/connection.rb1
-rw-r--r--lib/feature.rb10
-rw-r--r--lib/gitlab/chat.rb10
-rw-r--r--lib/gitlab/chat/command.rb94
-rw-r--r--lib/gitlab/chat/output.rb93
-rw-r--r--lib/gitlab/chat/responder.rb22
-rw-r--r--lib/gitlab/chat/responder/base.rb40
-rw-r--r--lib/gitlab/chat/responder/slack.rb80
-rw-r--r--lib/gitlab/ci/build/policy/changes.rb2
-rw-r--r--lib/gitlab/ci/build/policy/refs.rb8
-rw-r--r--lib/gitlab/ci/config/entry/global.rb3
-rw-r--r--lib/gitlab/ci/config/entry/include.rb23
-rw-r--r--lib/gitlab/ci/config/entry/includes.rb32
-rw-r--r--lib/gitlab/ci/pipeline/chain/build.rb2
-rw-r--r--lib/gitlab/ci/pipeline/chain/command.rb5
-rw-r--r--lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs.rb8
-rw-r--r--lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml64
-rw-r--r--lib/gitlab/ci/templates/iOS-Fastlane.gitlab-ci.yml28
-rw-r--r--lib/gitlab/ci/variables/collection/item.rb10
-rw-r--r--lib/gitlab/danger/helper.rb136
-rw-r--r--lib/gitlab/danger/teammate.rb42
-rw-r--r--lib/gitlab/diff/file.rb4
-rw-r--r--lib/gitlab/etag_caching/router.rb8
-rw-r--r--lib/gitlab/git/repository.rb6
-rw-r--r--lib/gitlab/gitaly_client.rb25
-rw-r--r--lib/gitlab/gitaly_client/operation_service.rb19
-rw-r--r--lib/gitlab/graphql/authorize.rb15
-rw-r--r--lib/gitlab/graphql/authorize/authorize_resource.rb17
-rw-r--r--lib/gitlab/graphql/authorize/instrumentation.rb28
-rw-r--r--lib/gitlab/hashed_storage/migrator.rb36
-rw-r--r--lib/gitlab/hashed_storage/rake_helper.rb12
-rw-r--r--lib/gitlab/import_export/import_export.yml2
-rw-r--r--lib/gitlab/import_export/shared.rb2
-rw-r--r--lib/gitlab/json_cache.rb16
-rw-r--r--lib/gitlab/kubernetes/helm.rb4
-rw-r--r--lib/gitlab/lfs_token.rb13
-rw-r--r--lib/gitlab/metrics/instrumentation.rb4
-rw-r--r--lib/gitlab/metrics/method_call.rb2
-rw-r--r--lib/gitlab/metrics/methods.rb6
-rw-r--r--lib/gitlab/metrics/requests_rack_middleware.rb6
-rw-r--r--lib/gitlab/metrics/samplers/influx_sampler.rb4
-rw-r--r--lib/gitlab/metrics/samplers/ruby_sampler.rb10
-rw-r--r--lib/gitlab/metrics/samplers/unicorn_sampler.rb4
-rw-r--r--lib/gitlab/metrics/sidekiq_metrics_exporter.rb2
-rw-r--r--lib/gitlab/metrics/subscribers/rails_cache.rb4
-rw-r--r--lib/gitlab/metrics/transaction.rb4
-rw-r--r--lib/gitlab/project_template.rb9
-rw-r--r--lib/gitlab/shell.rb13
-rw-r--r--lib/gitlab/sidekiq_middleware/memory_killer.rb69
-rw-r--r--lib/gitlab/sidekiq_middleware/shutdown.rb135
-rw-r--r--lib/gitlab/slash_commands/application_help.rb25
-rw-r--r--lib/gitlab/slash_commands/command.rb3
-rw-r--r--lib/gitlab/slash_commands/presenters/error.rb17
-rw-r--r--lib/gitlab/slash_commands/presenters/run.rb33
-rw-r--r--lib/gitlab/slash_commands/run.rb44
-rw-r--r--lib/gitlab/tracing.rb9
-rw-r--r--lib/gitlab/usage_data.rb12
-rw-r--r--lib/sentry/client.rb2
-rw-r--r--lib/tasks/dev.rake1
-rw-r--r--lib/tasks/gitlab/info.rake4
-rw-r--r--lib/tasks/gitlab/storage.rake50
-rw-r--r--locale/gitlab.pot326
-rw-r--r--package.json35
-rw-r--r--qa/Gemfile1
-rw-r--r--qa/Gemfile.lock3
-rw-r--r--qa/README.md17
-rw-r--r--qa/qa.rb5
-rw-r--r--qa/qa/ce/strategy.rb5
-rw-r--r--qa/qa/git/repository.rb5
-rw-r--r--qa/qa/page/alert/auto_devops_alert.rb13
-rw-r--r--qa/qa/page/base.rb25
-rw-r--r--qa/qa/page/group/show.rb2
-rw-r--r--qa/qa/page/label/index.rb4
-rw-r--r--qa/qa/page/main/login.rb6
-rw-r--r--qa/qa/page/project/import/github.rb30
-rw-r--r--qa/qa/page/project/issue/show.rb29
-rw-r--r--qa/qa/page/project/job/show.rb27
-rw-r--r--qa/qa/page/project/operations/kubernetes/show.rb13
-rw-r--r--qa/qa/page/project/pipeline/show.rb12
-rw-r--r--qa/qa/page/project/settings/deploy_keys.rb4
-rw-r--r--qa/qa/resource/kubernetes_cluster.rb10
-rw-r--r--qa/qa/resource/project_imported_from_github.rb2
-rw-r--r--qa/qa/runtime/browser.rb2
-rw-r--r--qa/qa/runtime/namespace.rb4
-rw-r--r--qa/qa/service/kubernetes_cluster.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb29
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb7
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb283
-rw-r--r--qa/qa/support/page/logging.rb13
-rw-r--r--qa/qa/support/retrier.rb28
-rw-r--r--qa/spec/git/repository_spec.rb4
-rw-r--r--qa/spec/page/logging_spec.rb15
-rw-r--r--qa/spec/spec_helper.rb87
-rw-r--r--qa/spec/spec_helper_spec.rb373
-rwxr-xr-xscripts/build_assets_image4
-rwxr-xr-xscripts/lint-changelog-yaml2
-rwxr-xr-xscripts/lint-doc.sh2
-rwxr-xr-xscripts/static-analysis1
-rw-r--r--spec/config/application_spec.rb34
-rw-r--r--spec/controllers/admin/appearances_controller_spec.rb91
-rw-r--r--spec/controllers/admin/runners_controller_spec.rb19
-rw-r--r--spec/controllers/admin/users_controller_spec.rb25
-rw-r--r--spec/controllers/application_controller_spec.rb8
-rw-r--r--spec/controllers/concerns/issuable_collections_spec.rb4
-rw-r--r--spec/controllers/concerns/send_file_upload_spec.rb19
-rw-r--r--spec/controllers/dashboard/milestones_controller_spec.rb9
-rw-r--r--spec/controllers/groups/clusters_controller_spec.rb2
-rw-r--r--spec/controllers/groups_controller_spec.rb4
-rw-r--r--spec/controllers/import/gitea_controller_spec.rb8
-rw-r--r--spec/controllers/import/github_controller_spec.rb4
-rw-r--r--spec/controllers/profiles/preferences_controller_spec.rb3
-rw-r--r--spec/controllers/projects/autocomplete_sources_controller_spec.rb38
-rw-r--r--spec/controllers/projects/environments_controller_spec.rb10
-rw-r--r--spec/controllers/projects/graphs_controller_spec.rb16
-rw-r--r--spec/controllers/projects/merge_requests/diffs_controller_spec.rb13
-rw-r--r--spec/controllers/projects/notes_controller_spec.rb20
-rw-r--r--spec/controllers/projects/pages_controller_spec.rb12
-rw-r--r--spec/controllers/projects/settings/operations_controller_spec.rb63
-rw-r--r--spec/factories/ci/group_variables.rb1
-rw-r--r--spec/factories/ci/variables.rb1
-rw-r--r--spec/factories/import_states.rb (renamed from spec/factories/import_state.rb)0
-rw-r--r--spec/factories/merge_requests.rb9
-rw-r--r--spec/factories/personal_access_tokens.rb5
-rw-r--r--spec/factories/project_daily_statistics.rb8
-rw-r--r--spec/factories/projects.rb14
-rw-r--r--spec/factories/users.rb11
-rw-r--r--spec/features/admin/admin_appearance_spec.rb32
-rw-r--r--spec/features/admin/admin_runners_spec.rb50
-rw-r--r--spec/features/dashboard/projects_spec.rb11
-rw-r--r--spec/features/dashboard/shortcuts_spec.rb2
-rw-r--r--spec/features/dashboard/snippets_spec.rb15
-rw-r--r--spec/features/display_system_header_and_footer_bar_spec.rb137
-rw-r--r--spec/features/group_variables_spec.rb2
-rw-r--r--spec/features/groups/labels/create_spec.rb23
-rw-r--r--spec/features/groups/labels/index_spec.rb14
-rw-r--r--spec/features/issuables/issuable_list_spec.rb24
-rw-r--r--spec/features/issues/filtered_search/dropdown_hint_spec.rb11
-rw-r--r--spec/features/issues/filtered_search/search_bar_spec.rb4
-rw-r--r--spec/features/issues/filtered_search/visual_tokens_spec.rb21
-rw-r--r--spec/features/issues/gfm_autocomplete_spec.rb84
-rw-r--r--spec/features/merge_request/maintainer_edits_fork_spec.rb2
-rw-r--r--spec/features/merge_request/user_creates_image_diff_notes_spec.rb5
-rw-r--r--spec/features/merge_request/user_creates_merge_request_spec.rb2
-rw-r--r--spec/features/merge_request/user_posts_diff_notes_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb8
-rw-r--r--spec/features/profiles/user_visits_profile_preferences_page_spec.rb24
-rw-r--r--spec/features/project_variables_spec.rb2
-rw-r--r--spec/features/projects/blobs/edit_spec.rb6
-rw-r--r--spec/features/projects/branches_spec.rb32
-rw-r--r--spec/features/projects/environments/environments_spec.rb17
-rw-r--r--spec/features/projects/files/user_creates_files_spec.rb2
-rw-r--r--spec/features/projects/files/user_edits_files_spec.rb2
-rw-r--r--spec/features/projects/pages_spec.rb107
-rw-r--r--spec/features/projects/pipelines/pipeline_spec.rb2
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb2
-rw-r--r--spec/features/projects/services/user_activates_issue_tracker_spec.rb35
-rw-r--r--spec/features/projects/services/user_activates_youtrack_spec.rb89
-rw-r--r--spec/features/projects/settings/forked_project_settings_spec.rb1
-rw-r--r--spec/features/projects/settings/operations_settings_spec.rb77
-rw-r--r--spec/features/projects/wiki/markdown_preview_spec.rb7
-rw-r--r--spec/features/projects/wiki/user_creates_wiki_page_spec.rb8
-rw-r--r--spec/features/projects/wiki/user_updates_wiki_page_spec.rb7
-rw-r--r--spec/features/projects/wiki/user_views_wiki_page_spec.rb7
-rw-r--r--spec/features/projects_spec.rb4
-rw-r--r--spec/features/users/signup_spec.rb28
-rw-r--r--spec/finders/admin/runners_finder_spec.rb8
-rw-r--r--spec/finders/autocomplete/acts_as_taggable_on/tags_finder_spec.rb66
-rw-r--r--spec/finders/concerns/finder_methods_spec.rb22
-rw-r--r--spec/finders/issues_finder_spec.rb113
-rw-r--r--spec/fixtures/api/schemas/entities/diff_viewer.json11
-rw-r--r--spec/fixtures/api/schemas/environment.json1
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/group_labels.json1
-rw-r--r--spec/fixtures/security-reports/master/gl-container-scanning-report.json23
-rw-r--r--spec/fixtures/trace/sample_trace18
-rw-r--r--spec/frontend/__mocks__/file_mock.js1
-rw-r--r--spec/frontend/gfm_auto_complete_spec.js (renamed from spec/javascripts/gfm_auto_complete_spec.js)173
-rw-r--r--spec/frontend/issuable_suggestions/components/app_spec.js (renamed from spec/javascripts/issuable_suggestions/components/app_spec.js)0
-rw-r--r--spec/frontend/issuable_suggestions/components/item_spec.js (renamed from spec/javascripts/issuable_suggestions/components/item_spec.js)0
-rw-r--r--spec/frontend/issuable_suggestions/mock_data.js (renamed from spec/javascripts/issuable_suggestions/mock_data.js)0
-rw-r--r--spec/frontend/lib/utils/ajax_cache_spec.js (renamed from spec/javascripts/lib/utils/ajax_cache_spec.js)85
-rw-r--r--spec/frontend/notes/components/__snapshots__/discussion_jump_to_next_button_spec.js.snap20
-rw-r--r--spec/frontend/notes/components/discussion_jump_to_next_button_spec.js30
-rw-r--r--spec/frontend/test_setup.js18
-rw-r--r--spec/graphql/features/authorization_spec.rb106
-rw-r--r--spec/graphql/resolvers/base_resolver_spec.rb31
-rw-r--r--spec/graphql/resolvers/issues_resolver_spec.rb71
-rw-r--r--spec/graphql/resolvers/merge_requests_resolver_spec.rb (renamed from spec/graphql/resolvers/merge_request_resolver_spec.rb)34
-rw-r--r--spec/graphql/types/issuable_state_enum_spec.rb9
-rw-r--r--spec/graphql/types/issue_state_enum_spec.rb9
-rw-r--r--spec/graphql/types/merge_request_state_enum_spec.rb13
-rw-r--r--spec/graphql/types/project_type_spec.rb6
-rw-r--r--spec/helpers/appearances_helper_spec.rb76
-rw-r--r--spec/helpers/blob_helper_spec.rb8
-rw-r--r--spec/helpers/emails_helper_spec.rb54
-rw-r--r--spec/helpers/import_helper_spec.rb57
-rw-r--r--spec/helpers/labels_helper_spec.rb13
-rw-r--r--spec/helpers/preferences_helper_spec.rb7
-rw-r--r--spec/helpers/projects_helper_spec.rb50
-rw-r--r--spec/javascripts/badges/store/actions_spec.js4
-rw-r--r--spec/javascripts/commit/pipelines/pipelines_spec.js2
-rw-r--r--spec/javascripts/diffs/components/app_spec.js332
-rw-r--r--spec/javascripts/diffs/components/changed_files_dropdown_spec.js1
-rw-r--r--spec/javascripts/diffs/components/compare_versions_dropdown_spec.js153
-rw-r--r--spec/javascripts/diffs/components/diff_content_spec.js26
-rw-r--r--spec/javascripts/diffs/components/diff_file_header_spec.js104
-rw-r--r--spec/javascripts/diffs/components/diff_file_spec.js35
-rw-r--r--spec/javascripts/diffs/components/edit_button_spec.js62
-rw-r--r--spec/javascripts/diffs/components/hidden_files_warning_spec.js49
-rw-r--r--spec/javascripts/diffs/components/tree_list_spec.js12
-rw-r--r--spec/javascripts/diffs/mock_data/diff_discussions.js8
-rw-r--r--spec/javascripts/diffs/mock_data/diff_file.js2
-rw-r--r--spec/javascripts/diffs/store/actions_spec.js38
-rw-r--r--spec/javascripts/diffs/store/getters_spec.js24
-rw-r--r--spec/javascripts/diffs/store/mutations_spec.js10
-rw-r--r--spec/javascripts/diffs/store/utils_spec.js34
-rw-r--r--spec/javascripts/environments/environment_table_spec.js220
-rw-r--r--spec/javascripts/environments/environments_app_spec.js2
-rw-r--r--spec/javascripts/environments/folder/environments_folder_view_spec.js2
-rw-r--r--spec/javascripts/error_tracking_settings/components/app_spec.js63
-rw-r--r--spec/javascripts/error_tracking_settings/components/error_tracking_form_spec.js91
-rw-r--r--spec/javascripts/error_tracking_settings/components/project_dropdown_spec.js109
-rw-r--r--spec/javascripts/error_tracking_settings/mock.js92
-rw-r--r--spec/javascripts/error_tracking_settings/store/actions_spec.js191
-rw-r--r--spec/javascripts/error_tracking_settings/store/getters_spec.js93
-rw-r--r--spec/javascripts/error_tracking_settings/store/mutation_spec.js82
-rw-r--r--spec/javascripts/error_tracking_settings/utils_spec.js29
-rw-r--r--spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js11
-rw-r--r--spec/javascripts/fixtures/autocomplete_sources.rb40
-rw-r--r--spec/javascripts/fixtures/blob.rb1
-rw-r--r--spec/javascripts/fixtures/commit.rb1
-rw-r--r--spec/javascripts/fixtures/deploy_keys.rb2
-rw-r--r--spec/javascripts/fixtures/groups.rb2
-rw-r--r--spec/javascripts/fixtures/issues.rb2
-rw-r--r--spec/javascripts/fixtures/merge_requests.rb7
-rw-r--r--spec/javascripts/fixtures/projects.rb12
-rw-r--r--spec/javascripts/fixtures/snippet.rb4
-rw-r--r--spec/javascripts/fixtures/u2f.rb3
-rw-r--r--spec/javascripts/helpers/vuex_action_helper.js5
-rw-r--r--spec/javascripts/ide/components/new_dropdown/modal_spec.js9
-rw-r--r--spec/javascripts/import_projects/components/import_projects_table_spec.js186
-rw-r--r--spec/javascripts/import_projects/components/imported_project_table_row_spec.js50
-rw-r--r--spec/javascripts/import_projects/components/provider_repo_table_row_spec.js91
-rw-r--r--spec/javascripts/import_projects/store/actions_spec.js284
-rw-r--r--spec/javascripts/import_projects/store/getters_spec.js83
-rw-r--r--spec/javascripts/import_projects/store/mutations_spec.js34
-rw-r--r--spec/javascripts/lib/utils/common_utils_spec.js8
-rw-r--r--spec/javascripts/lib/utils/number_utility_spec.js11
-rw-r--r--spec/javascripts/lib/utils/poll_spec.js32
-rw-r--r--spec/javascripts/monitoring/charts/area_spec.js4
-rw-r--r--spec/javascripts/notes/components/discussion_filter_note_spec.js93
-rw-r--r--spec/javascripts/notes/components/discussion_filter_spec.js22
-rw-r--r--spec/javascripts/notes/components/discussion_jump_to_next_button_spec.js33
-rw-r--r--spec/javascripts/notes/components/discussion_resolve_with_issue_button_spec.js31
-rw-r--r--spec/javascripts/notes/components/note_actions/reply_button_spec.js22
-rw-r--r--spec/javascripts/notes/components/note_app_spec.js17
-rw-r--r--spec/javascripts/notes/components/note_form_spec.js157
-rw-r--r--spec/javascripts/notes/components/noteable_discussion_spec.js39
-rw-r--r--spec/javascripts/notes/stores/actions_spec.js152
-rw-r--r--spec/javascripts/notes/stores/getters_spec.js8
-rw-r--r--spec/javascripts/notes/stores/mutation_spec.js25
-rw-r--r--spec/javascripts/pipelines/pipelines_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/components/deployment_spec.js81
-rw-r--r--spec/javascripts/vue_mr_widget/mr_widget_options_spec.js2
-rw-r--r--spec/javascripts/vue_shared/components/changed_file_icon_spec.js21
-rw-r--r--spec/javascripts/vue_shared/components/diff_viewer/diff_viewer_spec.js4
-rw-r--r--spec/javascripts/vue_shared/components/issue/related_issuable_item_spec.js194
-rw-r--r--spec/javascripts/vue_shared/components/issue/related_issuable_mock_data.js111
-rw-r--r--spec/javascripts/vue_shared/components/panel_resizer_spec.js9
-rw-r--r--spec/javascripts/vue_shared/components/table_pagination_spec.js194
-rw-r--r--spec/lib/api/helpers/pagination_spec.rb155
-rw-r--r--spec/lib/banzai/filter/external_issue_reference_filter_spec.rb36
-rw-r--r--spec/lib/banzai/filter/footnote_filter_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/client_spec.rb4
-rw-r--r--spec/lib/feature_spec.rb5
-rw-r--r--spec/lib/gitlab/background_migration/digest_column_spec.rb4
-rw-r--r--spec/lib/gitlab/chat/command_spec.rb77
-rw-r--r--spec/lib/gitlab/chat/output_spec.rb101
-rw-r--r--spec/lib/gitlab/chat/responder/base_spec.rb48
-rw-r--r--spec/lib/gitlab/chat/responder/slack_spec.rb77
-rw-r--r--spec/lib/gitlab/chat/responder_spec.rb32
-rw-r--r--spec/lib/gitlab/chat_spec.rb23
-rw-r--r--spec/lib/gitlab/ci/build/policy/changes_spec.rb56
-rw-r--r--spec/lib/gitlab/ci/build/policy/refs_spec.rb14
-rw-r--r--spec/lib/gitlab/ci/config/entry/global_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/build_spec.rb11
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/command_spec.rb48
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs_spec.rb33
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/variables/collection/item_spec.rb20
-rw-r--r--spec/lib/gitlab/ci/variables/collection_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb79
-rw-r--r--spec/lib/gitlab/current_settings_spec.rb2
-rw-r--r--spec/lib/gitlab/danger/helper_spec.rb303
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb2
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb35
-rw-r--r--spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb18
-rw-r--r--spec/lib/gitlab/graphql/authorize/instrumentation_spec.rb67
-rw-r--r--spec/lib/gitlab/graphql/authorize_spec.rb20
-rw-r--r--spec/lib/gitlab/hashed_storage/migrator_spec.rb114
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml2
-rw-r--r--spec/lib/gitlab/import_export/project.json20
-rw-r--r--spec/lib/gitlab/import_export/project_tree_saver_spec.rb8
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml2
-rw-r--r--spec/lib/gitlab/import_export/shared_spec.rb10
-rw-r--r--spec/lib/gitlab/issuable_metadata_spec.rb8
-rw-r--r--spec/lib/gitlab/json_cache_spec.rb28
-rw-r--r--spec/lib/gitlab/kubernetes/helm/pod_spec.rb2
-rw-r--r--spec/lib/gitlab/lfs_token_spec.rb36
-rw-r--r--spec/lib/gitlab/profiler_spec.rb8
-rw-r--r--spec/lib/gitlab/project_template_spec.rb9
-rw-r--r--spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb4
-rw-r--r--spec/lib/gitlab/serializer/pagination_spec.rb10
-rw-r--r--spec/lib/gitlab/shell_spec.rb1
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb63
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/shutdown_spec.rb88
-rw-r--r--spec/lib/gitlab/slash_commands/application_help_spec.rb19
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/error_spec.rb15
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/run_spec.rb79
-rw-r--r--spec/lib/gitlab/slash_commands/run_spec.rb123
-rw-r--r--spec/lib/gitlab/tracing_spec.rb15
-rw-r--r--spec/lib/object_storage/direct_upload_spec.rb2
-rw-r--r--spec/lib/sentry/client_spec.rb2
-rw-r--r--spec/mailers/abuse_report_mailer_spec.rb25
-rw-r--r--spec/mailers/email_rejection_mailer_spec.rb16
-rw-r--r--spec/mailers/emails/auto_devops_spec.rb3
-rw-r--r--spec/mailers/emails/issues_spec.rb9
-rw-r--r--spec/mailers/notify_spec.rb108
-rw-r--r--spec/mailers/repository_check_mailer_spec.rb7
-rw-r--r--spec/migrations/clean_up_noteable_id_for_notes_on_commits_spec.rb34
-rw-r--r--spec/models/appearance_spec.rb33
-rw-r--r--spec/models/board_group_recent_visit_spec.rb22
-rw-r--r--spec/models/board_project_recent_visit_spec.rb22
-rw-r--r--spec/models/ci/build_spec.rb293
-rw-r--r--spec/models/ci/build_trace_chunk_spec.rb4
-rw-r--r--spec/models/ci/group_variable_spec.rb1
-rw-r--r--spec/models/ci/pipeline_spec.rb274
-rw-r--r--spec/models/ci/variable_spec.rb1
-rw-r--r--spec/models/clusters/applications/helm_spec.rb7
-rw-r--r--spec/models/clusters/applications/ingress_spec.rb12
-rw-r--r--spec/models/clusters/applications/knative_spec.rb24
-rw-r--r--spec/models/clusters/applications/prometheus_spec.rb73
-rw-r--r--spec/models/clusters/applications/runner_spec.rb16
-rw-r--r--spec/models/clusters/cluster_spec.rb6
-rw-r--r--spec/models/commit_collection_spec.rb14
-rw-r--r--spec/models/concerns/has_ref_spec.rb20
-rw-r--r--spec/models/concerns/has_variable_spec.rb2
-rw-r--r--spec/models/concerns/maskable_spec.rb76
-rw-r--r--spec/models/concerns/reactive_caching_spec.rb12
-rw-r--r--spec/models/concerns/sortable_spec.rb2
-rw-r--r--spec/models/concerns/token_authenticatable_spec.rb235
-rw-r--r--spec/models/environment_spec.rb22
-rw-r--r--spec/models/error_tracking/project_error_tracking_setting_spec.rb53
-rw-r--r--spec/models/issue_spec.rb9
-rw-r--r--spec/models/merge_request_spec.rb26
-rw-r--r--spec/models/project_daily_statistic_spec.rb7
-rw-r--r--spec/models/project_services/jira_service_spec.rb3
-rw-r--r--spec/models/project_services/slack_slash_commands_service_spec.rb7
-rw-r--r--spec/models/project_services/youtrack_service_spec.rb38
-rw-r--r--spec/models/project_spec.rb100
-rw-r--r--spec/models/prometheus_metric_spec.rb13
-rw-r--r--spec/models/releases/link_spec.rb24
-rw-r--r--spec/models/repository_spec.rb23
-rw-r--r--spec/models/user_spec.rb29
-rw-r--r--spec/policies/board_policy_spec.rb67
-rw-r--r--spec/policies/group_policy_spec.rb4
-rw-r--r--spec/policies/project_policy_spec.rb1
-rw-r--r--spec/presenters/ci/build_runner_presenter_spec.rb68
-rw-r--r--spec/requests/api/commits_spec.rb40
-rw-r--r--spec/requests/api/features_spec.rb34
-rw-r--r--spec/requests/api/graphql/project/issues_spec.rb34
-rw-r--r--spec/requests/api/group_variables_spec.rb4
-rw-r--r--spec/requests/api/internal_spec.rb5
-rw-r--r--spec/requests/api/issues_spec.rb186
-rw-r--r--spec/requests/api/keys_spec.rb2
-rw-r--r--spec/requests/api/labels_spec.rb10
-rw-r--r--spec/requests/api/merge_request_diffs_spec.rb4
-rw-r--r--spec/requests/api/merge_requests_spec.rb104
-rw-r--r--spec/requests/api/namespaces_spec.rb2
-rw-r--r--spec/requests/api/project_milestones_spec.rb74
-rw-r--r--spec/requests/api/project_statistics_spec.rb62
-rw-r--r--spec/requests/api/project_templates_spec.rb28
-rw-r--r--spec/requests/api/projects_spec.rb80
-rw-r--r--spec/requests/api/runner_spec.rb103
-rw-r--r--spec/requests/api/runners_spec.rb45
-rw-r--r--spec/requests/api/search_spec.rb4
-rw-r--r--spec/requests/api/users_spec.rb28
-rw-r--r--spec/requests/api/variables_spec.rb4
-rw-r--r--spec/requests/api/wikis_spec.rb40
-rw-r--r--spec/routing/import_routing_spec.rb15
-rw-r--r--spec/serializers/diff_file_entity_spec.rb8
-rw-r--r--spec/serializers/environment_serializer_spec.rb9
-rw-r--r--spec/serializers/namespace_basic_entity_spec.rb18
-rw-r--r--spec/serializers/namespace_serializer_spec.rb9
-rw-r--r--spec/serializers/pipeline_serializer_spec.rb14
-rw-r--r--spec/serializers/project_import_entity_spec.rb22
-rw-r--r--spec/serializers/project_serializer_spec.rb44
-rw-r--r--spec/serializers/provider_repo_entity_spec.rb24
-rw-r--r--spec/serializers/provider_repo_serializer_spec.rb9
-rw-r--r--spec/services/boards/visits/latest_service_spec.rb12
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb38
-rw-r--r--spec/services/clusters/applications/create_service_spec.rb222
-rw-r--r--spec/services/clusters/applications/schedule_installation_service_spec.rb77
-rw-r--r--spec/services/error_tracking/list_issues_service_spec.rb26
-rw-r--r--spec/services/error_tracking/list_projects_service_spec.rb8
-rw-r--r--spec/services/merge_requests/create_service_spec.rb4
-rw-r--r--spec/services/merge_requests/merge_service_spec.rb12
-rw-r--r--spec/services/merge_requests/merge_to_ref_service_spec.rb201
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb4
-rw-r--r--spec/services/notes/create_service_spec.rb9
-rw-r--r--spec/services/notification_service_spec.rb2
-rw-r--r--spec/services/projects/fetch_statistics_increment_service_spec.rb36
-rw-r--r--spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb6
-rw-r--r--spec/services/projects/hashed_storage/migrate_repository_service_spec.rb12
-rw-r--r--spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb100
-rw-r--r--spec/services/projects/hashed_storage/rollback_repository_service_spec.rb111
-rw-r--r--spec/services/projects/hashed_storage/rollback_service_spec.rb57
-rw-r--r--spec/services/projects/operations/update_service_spec.rb50
-rw-r--r--spec/services/prometheus/adapter_service_spec.rb10
-rw-r--r--spec/services/suggestions/apply_service_spec.rb11
-rw-r--r--spec/services/suggestions/create_service_spec.rb47
-rw-r--r--spec/services/system_note_service_spec.rb9
-rw-r--r--spec/services/users/activity_service_spec.rb12
-rw-r--r--spec/services/web_hook_service_spec.rb2
-rw-r--r--spec/support/controllers/githubish_import_controller_shared_examples.rb103
-rw-r--r--spec/support/features/variable_list_shared_examples.rb8
-rw-r--r--spec/support/helpers/graphql_helpers.rb13
-rw-r--r--spec/support/helpers/reactive_caching_helpers.rb2
-rw-r--r--spec/support/helpers/stub_feature_flags.rb27
-rw-r--r--spec/support/helpers/test_env.rb15
-rw-r--r--spec/support/matchers/graphql_matchers.rb4
-rw-r--r--spec/support/matchers/not_changed_matcher.rb3
-rw-r--r--spec/support/shared_contexts/services_shared_context.rb2
-rw-r--r--spec/support/shared_examples/graphql/issuable_state_shared_examples.rb5
-rw-r--r--spec/support/shared_examples/models/cluster_application_status_shared_examples.rb13
-rw-r--r--spec/support/shared_examples/notify_shared_examples.rb28
-rw-r--r--spec/support/shared_examples/requests/api/merge_requests_list.rb32
-rw-r--r--spec/support/shared_examples/requests/api/notes.rb42
-rw-r--r--spec/support/shared_examples/serializers/diff_file_entity_examples.rb8
-rw-r--r--spec/support/shared_examples/services/boards/issues_move_service.rb16
-rw-r--r--spec/tasks/gitlab/shell_rake_spec.rb2
-rw-r--r--spec/views/layouts/_head.html.haml_spec.rb8
-rw-r--r--spec/views/projects/commits/_commit.html.haml_spec.rb59
-rw-r--r--spec/views/projects/issues/_merge_requests_status.html.haml_spec.rb6
-rw-r--r--spec/views/projects/issues/show.html.haml_spec.rb20
-rw-r--r--spec/views/projects/settings/operations/show.html.haml_spec.rb1
-rw-r--r--spec/workers/build_finished_worker_spec.rb19
-rw-r--r--spec/workers/chat_notification_worker_spec.rb91
-rw-r--r--spec/workers/hashed_storage/project_migrate_worker_spec.rb (renamed from spec/workers/project_migrate_hashed_storage_worker_spec.rb)2
-rw-r--r--spec/workers/hashed_storage/project_rollback_worker_spec.rb50
-rw-r--r--spec/workers/hashed_storage/rollbacker_worker_spec.rb27
-rw-r--r--spec/workers/project_daily_statistics_worker_spec.rb35
-rw-r--r--spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb2
-rw-r--r--vendor/project_templates/dotnetcore.tar.gzbin0 -> 44710 bytes
-rw-r--r--vendor/project_templates/gomicro.tar.gzbin0 -> 52756 bytes
-rw-r--r--vendor/project_templates/nfgitbook.tar.gzbin0 -> 139877 bytes
-rw-r--r--vendor/project_templates/nfhexo.tar.gzbin0 -> 663568 bytes
-rw-r--r--vendor/project_templates/nfhugo.tar.gzbin0 -> 1292761 bytes
-rw-r--r--vendor/project_templates/nfjekyll.tar.gzbin0 -> 154419 bytes
-rw-r--r--vendor/project_templates/nfplainhtml.tar.gzbin0 -> 132509 bytes
-rw-r--r--yarn.lock2739
1584 files changed, 25521 insertions, 8910 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 1ea9ce1f497..2329c9c6edc 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -509,6 +509,7 @@ rspec-mysql:
parallel: 50
.rspec-quarantine: &rspec-quarantine
+ retry: 0
script:
- export CACHE_CLASSES=true
- scripts/gitaly-test-spawn
@@ -668,8 +669,8 @@ gitlab:assets:compile:
- //@gitlab/gitlabhq
- //@gitlab/gitlab-ee
tags:
- - gitlab-org-delivery
- - high-cpu
+ - docker
+ - gitlab-org
karma:
<<: *dedicated-no-docs-pull-cache-job
@@ -1044,6 +1045,20 @@ review-qa-all:
- gitlab-qa Test::Instance::Any "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}"
when: manual
+review-performance:
+ <<: *review-qa-base
+ script:
+ - mkdir gitlab-exporter
+ - wget -O ./gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/master/index.js
+ - mkdir sitespeed-results
+ - docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:6.3.1 --plugins.add ./gitlab-exporter --outputFolder sitespeed-results "$CI_ENVIRONMENT_URL"
+ - mv sitespeed-results/data/performance.json performance.json
+ artifacts:
+ paths:
+ - sitespeed-results/
+ reports:
+ performance: performance.json
+
review-stop:
<<: *review-base
<<: *single-script-job
diff --git a/.gitlab/issue_templates/Doc Review.md b/.gitlab/issue_templates/Doc Review.md
new file mode 100644
index 00000000000..14ab0c82d59
--- /dev/null
+++ b/.gitlab/issue_templates/Doc Review.md
@@ -0,0 +1,20 @@
+<!-- This issue requests a technical writer review as required for documentation
+ content that was merged without one. -->
+
+<!-- NOTE: Please add a DevOps stage label (format `devops:<stage_name>`)
+ and assign the technical writer who is
+ [listed for that stage](https://about.gitlab.com/handbook/product/categories/#devops-stages). -->
+
+
+## References
+
+Merged MR that introduced documentation requiring review:
+
+Related issue(s):
+
+## Further Details
+
+<!-- Any additional context, questions, or notes for the technical writer. -->
+
+
+/label ~Documentation ~docs-review
diff --git a/.gitlab/issue_templates/Documentation.md b/.gitlab/issue_templates/Documentation.md
index b33ed5bcaa8..c0919aeeda4 100644
--- a/.gitlab/issue_templates/Documentation.md
+++ b/.gitlab/issue_templates/Documentation.md
@@ -1,54 +1,53 @@
-<!--See the general documentation guidelines https://docs.gitlab.com/ee/development/documentation -->
+<!--
-<!-- Mention "documentation" or "docs" in the issue title -->
+* Use this issue template for suggesting new docs or updates to existing docs.
+ Note: Doc work as part of feature development is covered in the Feature Request template.
+
+* For issues related to features of the docs.gitlab.com site, see
+ https://gitlab.com/gitlab-com/gitlab-docs/issues/
-<!-- Use this description template for new docs or updates to existing docs. -->
+* For information about documentation content and process, see
+ https://docs.gitlab.com/ee/development/documentation/ -->
-<!-- Check the documentation structure guidelines for guidance: https://docs.gitlab.com/ee/development/documentation/structure.html-->
+### Type of issue
-- [ ] Documents Feature A <!-- feature name -->
-- [ ] Follow-up from: #XXX, !YYY <!-- Mention related issues, MRs, and epics when available -->
+<!-- Un-comment the line for the applicable doc issue type to add its label.
+ Note that all text on that line is deleted upon issue creation. -->
+<!-- /label ~"docs:fix" - Correction or clarification needed. -->
+<!-- /label ~"docs:new" - New doc needed to cover a new topic or use case. -->
+<!-- /label ~"docs:improvement" - Improving an existing doc; e.g. adding a diagram, adding or rewording text, resolving redundancies, cross-linking, etc. -->
+<!-- /label ~"docs:revamp" - Review a page or group of pages in order to plan and implement major improvements/rewrites. -->
+<!-- /label ~"docs:other" - Anything else. -->
-## New doc or update?
+### Problem to solve
-<!-- Mark either of these boxes: -->
+<!-- Include the following detail as necessary:
+* What product or feature(s) affected?
+* What docs or doc section affected? Include links or paths.
+* Is there a problem with a specific document, or a feature/process that's not addressed sufficiently in docs?
+* Any other ideas or requests?
+-->
-- [ ] New documentation
-- [ ] Update existing documentation
+### Further details
-## Checklists
+<!--
+* Any concepts, procedures, reference info we could add to make it easier to successfully use GitLab?
+* Include use cases, benefits, and/or goals for this work.
+* If adding content: What audience is it intended for? (What roles and scenarios?)
+ For ideas, see personas at https://design.gitlab.com/research/personas or the persona labels at
+ https://gitlab.com/groups/gitlab-org/-/labels?utf8=%E2%9C%93&subscribed=&search=persona%3A
+-->
-### Product Manager
+### Proposal
-<!-- Reference: https://docs.gitlab.com/ee/development/documentation/workflow.html#1-product-manager-s-role-in-the-documentation-process -->
+<!-- Further specifics for how can we solve the problem. -->
-- [ ] Add the correct labels
-- [ ] Add the correct milestone
-- [ ] Indicate the correct document/directory for this feature <!-- (ping the tech writers for help if you're not sure) -->
-- [ ] Fill the doc blurb below
+### Who can address the issue
-#### Documentation blurb
+<!-- What if any special expertise is required to resolve this issue? -->
-<!-- Documentation template: https://docs.gitlab.com/ee/development/documentation/structure.html#documentation-template-for-new-docs -->
+### Other links/references
-- Doc **title**
-
- <!-- write the doc title here -->
-
-- Feature **overview/description**
-
- <!-- Write the feature overview here -->
-
-- Feature **use cases**
-
- <!-- Write the use cases here -->
-
-### Developer
-
-<!-- Reference: https://docs.gitlab.com/ee/development/documentation/workflow.html#2-developer-s-role-in-the-documentation-process -->
-
-- [ ] Copy the doc blurb above and paste it into the doc
-- [ ] Write the tutorial - explain how to use the feature
-- [ ] Submit the MR using the appropriate MR description template
+<!-- E.g. related GitLab issues/MRs -->
/label ~Documentation
diff --git a/.gitlab/issue_templates/Feature proposal.md b/.gitlab/issue_templates/Feature proposal.md
index 1bb8d33ff63..eef1e877ff2 100644
--- a/.gitlab/issue_templates/Feature proposal.md
+++ b/.gitlab/issue_templates/Feature proposal.md
@@ -1,45 +1,54 @@
### Problem to solve
-<!--- What problem do we solve? -->
+<!-- What problem do we solve? -->
### Target audience
-<!--- For whom are we doing this? Include a [persona](https://design.gitlab.com/research/personas)
+<!--- For whom are we doing this? Include a [persona](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/)
listed below, if applicable, along with its [label](https://gitlab.com/groups/gitlab-org/-/labels?utf8=%E2%9C%93&subscribed=&search=persona%3A),
or define a specific company role, e.g. "Release Manager".
Existing personas are: (copy relevant personas out of this comment, and delete any persona that does not apply)
-- Parker, Product Manager, https://design.gitlab.com/research/personas#persona-parker
+- Parker, Product Manager, https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas#parker-product-manager
/label ~"Persona: Product Manager"
-- Delaney, Development Team Lead, https://design.gitlab.com/research/personas#persona-delaney
+- Delaney, Development Team Lead, https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas#delaney-development-team-lead
/label ~"Persona: Development Team Lead"
-- Sasha, Software Developer, https://design.gitlab.com/research/personas#persona-sasha
+- Sasha, Software Developer, https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas#sasha-software-developer
/label ~"Persona: Software developer"
-- Devon, DevOps Engineer, https://design.gitlab.com/research/personas#persona-devon
+- Devon, DevOps Engineer, https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas#devon-devops-engineer
/label ~"Persona: DevOps Engineer"
-- Sidney, Systems Administrator, https://design.gitlab.com/research/personas#persona-sidney
+- Sidney, Systems Administrator, https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas#sidney-systems-administrator
/label ~"Persona: Systems Administrator"
-- Sam, Security Analyst, https://design.gitlab.com/research/personas#persona-sam
+- Sam, Security Analyst, https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas#sam-security-analyst
/label ~"Persona: Security Analyst"
-->
### Further details
-<!--- Include use cases, benefits, and/or goals (contributes to our vision?) -->
+<!-- Include use cases, benefits, and/or goals (contributes to our vision?) -->
### Proposal
-<!--- How are we going to solve the problem? Try to include the user journey! -->
+<!-- How are we going to solve the problem? Try to include the user journey! https://about.gitlab.com/handbook/journeys/#user-journey -->
+
+### Permissions and Security
+
+<!-- What permissions are required to perform the described actions? Are they consistent with the existing permissions as documented for users, groups, and projects as appropriate? Is the proposed behavior consistent between the UI, API, and other access methods (e.g. email replies)? -->
+
+### Documentation
+
+<!-- See the Feature Change Documentation Workflow https://docs.gitlab.com/ee/development/documentation/feature-change-workflow.html
+Add all known Documentation Requirements here, per https://docs.gitlab.com/ee/development/documentation/feature-change-workflow.html#documentation-requirements -->
### What does success look like, and how can we measure that?
-<!--- Define both the success metrics and acceptance criteria. Note that success metrics indicate the desired business outcomes, while acceptance criteria indicate when the solution is working correctly. If there is no way to measure success, link to an issue that will implement a way to measure this -->
+<!-- Define both the success metrics and acceptance criteria. Note that success metrics indicate the desired business outcomes, while acceptance criteria indicate when the solution is working correctly. If there is no way to measure success, link to an issue that will implement a way to measure this. -->
### Links / references
diff --git a/.gitlab/issue_templates/Security developer workflow.md b/.gitlab/issue_templates/Security developer workflow.md
index aaa16145399..9946651075f 100644
--- a/.gitlab/issue_templates/Security developer workflow.md
+++ b/.gitlab/issue_templates/Security developer workflow.md
@@ -30,6 +30,7 @@ Set the title to: `Description of the original issue`
#### Documentation and final details
- [ ] Check the topic on #security to see when the next release is going to happen and add a link to the [links section](#links)
+- [ ] Add links to this issue and your MRs in the description of the security release issue
- [ ] Find out the versions affected (the Git history of the files affected may help you with this) and add them to the [details section](#details)
- [ ] Fill in any upgrade notes that users may need to take into account in the [details section](#details)
- [ ] Add Yes/No and further details if needed to the migration and settings columns in the [details section](#details)
diff --git a/.gitlab/merge_request_templates/Change documentation location.md b/.gitlab/merge_request_templates/Change documentation location.md
index b4a6d2bd3b4..c80af95d5e5 100644
--- a/.gitlab/merge_request_templates/Change documentation location.md
+++ b/.gitlab/merge_request_templates/Change documentation location.md
@@ -26,7 +26,7 @@ https://docs.gitlab.com/ce/development/documentation/index.html#changing-documen
to the new document if there are any Disqus comments on the old document thread.
- [ ] Update the link in `features.yml` (if applicable)
- [ ] If working on CE and the `ee-compat-check` jobs fails, submit an MR to EE
- with the changes as well (https://docs.gitlab.com/ce/development/writing_documentation.html#cherry-picking-from-ce-to-ee).
+ with the changes as well (https://docs.gitlab.com/ce/development/documentation/index.html#cherry-picking-from-ce-to-ee).
- [ ] Ping one of the technical writers for review.
/label ~Documentation
diff --git a/.gitlab/merge_request_templates/Database changes.md b/.gitlab/merge_request_templates/Database changes.md
index 354393b60e0..3f457174492 100644
--- a/.gitlab/merge_request_templates/Database changes.md
+++ b/.gitlab/merge_request_templates/Database changes.md
@@ -16,7 +16,7 @@ Add a description of your merge request here.
## Database checklist
-- [ ] Conforms to the [database guides](https://docs.gitlab.com/ee/development/README.html#databases-guides)
+- [ ] Conforms to the [database guides](https://docs.gitlab.com/ee/development/README.html#database-guides)
When adding migrations:
@@ -49,10 +49,10 @@ When removing columns, tables, indexes or other structures:
## General checklist
- [ ] [Changelog entry](https://docs.gitlab.com/ee/development/changelog.html) added, if necessary
-- [ ] [Documentation created/updated](https://docs.gitlab.com/ee/development/documentation/index.html#contributing-to-docs)
+- [ ] [Documentation created/updated](https://docs.gitlab.com/ee/development/documentation/)
- [ ] [Tests added for this feature/bug](https://docs.gitlab.com/ee/development/testing_guide/index.html)
- [ ] Conforms to the [code review guidelines](https://docs.gitlab.com/ee/development/code_review.html)
- [ ] Conforms to the [merge request performance guidelines](https://docs.gitlab.com/ee/development/merge_request_performance_guidelines.html)
-- [ ] Conforms to the [style guides](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/CONTRIBUTING.md#style-guides)
+- [ ] Conforms to the [style guides](https://docs.gitlab.com/ee/development/contributing/style_guides.html)
/label ~database
diff --git a/.gitlab/merge_request_templates/Documentation.md b/.gitlab/merge_request_templates/Documentation.md
index 8b7e7119790..ba9624aeeab 100644
--- a/.gitlab/merge_request_templates/Documentation.md
+++ b/.gitlab/merge_request_templates/Documentation.md
@@ -1,33 +1,39 @@
-<!--See the general documentation guidelines https://docs.gitlab.com/ee/development/documentation -->
+<!-- Follow the documentation workflow https://docs.gitlab.com/ee/development/documentation/workflow.html -->
+<!-- Additional information is located at https://docs.gitlab.com/ee/development/documentation/ -->
<!-- Mention "documentation" or "docs" in the MR title -->
-
-<!-- Use this description template for new docs or updates to existing docs. For changing documentation location use the "Change documentation location" template -->
+<!-- For changing documentation location use the "Change documentation location" template -->
## What does this MR do?
-<!-- Briefly describe what this MR is about -->
+<!-- Briefly describe what this MR is about. -->
## Related issues
-<!-- Mention the issue(s) this MR closes or is related to -->
-
-Closes
+<!-- Link related issues below. Insert the issue link or reference after the word "Closes" if merging this should automatically close it. -->
## Author's checklist
-- [ ] [Apply the correct labels and milestone](https://docs.gitlab.com/ee/development/documentation/workflow.html#2-developer-s-role-in-the-documentation-process)
-- [ ] Crosslink the document from the higher-level index
-- [ ] Crosslink the document from other subject-related docs
-- [ ] Feature moving tiers? Make sure the change is also reflected in [`features.yml`](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/features.yml)
-- [ ] Correctly apply the product [badges](https://docs.gitlab.com/ee/development/documentation/styleguide.html#product-badges) and [tiers](https://docs.gitlab.com/ee/development/documentation/styleguide.html#gitlab-versions-and-tiers)
-- [ ] [Port the MR to EE (or backport from CE)](https://docs.gitlab.com/ee/development/documentation/index.html#cherry-picking-from-ce-to-ee): _always recommended, required when the `ee-compat-check` job fails_
+- [ ] Follow the [Documentation Guidelines](https://docs.gitlab.com/ee/development/documentation/) and [Style Guide](https://docs.gitlab.com/ee/development/documentation/styleguide.html).
+- [ ] Link docs to and from the higher-level index page, plus other related docs where helpful.
+- [ ] Apply the ~Documentation label.
## Review checklist
-- [ ] Your team's review (required)
-- [ ] PM's review (recommended, but not a blocker)
-- [ ] Technical writer's review (required)
-- [ ] Merge the EE-MR first, CE-MR afterwards
+All reviewers can help ensure accuracy, clarity, completeness, and adherence to the [Documentation Guidelines](https://docs.gitlab.com/ee/development/documentation/) and [Style Guide](https://docs.gitlab.com/ee/development/documentation/styleguide.html).
+
+**1. Primary Reviewer**
+
+* [ ] Review by a code reviewer or other selected colleague to confirm accuracy, clarity, and completeness. This can be skipped for minor fixes without substantive content changes.
+
+**2. Technical Writer**
+
+* [ ] Optional: Technical writer review. If not requested for this MR, must be scheduled post-merge. To request for this MR, assign the writer listed for the applicable [DevOps stage](https://about.gitlab.com/handbook/product/categories/#devops-stages).
+
+**3. Maintainer**
+
+1. [ ] Review by assigned maintainer, who can always request/require the above reviews. Maintainer's review can occur before or after a technical writer review.
+1. [ ] Ensure a release milestone is set and that you merge the equivalent EE MR before the CE MR if both exist.
+1. [ ] If there has not been a technical writer review, [create an issue for one using the Doc Review template](https://gitlab.com/gitlab-org/gitlab-ce/issues/new?issuable_template=Doc%20Review).
/label ~Documentation
diff --git a/.stylelintrc b/.stylelintrc
new file mode 100644
index 00000000000..04784a0a11a
--- /dev/null
+++ b/.stylelintrc
@@ -0,0 +1,107 @@
+{
+ "plugins":[
+ "stylelint-scss"
+ ],
+ "rules":{
+ "at-rule-blacklist":[
+ "debug"
+ ],
+ "at-rule-no-unknown":null,
+ "at-rule-no-vendor-prefix":true,
+ "block-no-empty":true,
+ "block-opening-brace-space-before":"always",
+ "color-hex-case":"lower",
+ "color-hex-length":"short",
+ "color-named":"never",
+ "color-no-invalid-hex":true,
+ "declaration-bang-space-after":"never",
+ "declaration-bang-space-before":"always",
+ "declaration-block-semicolon-newline-after":"always",
+ "declaration-block-semicolon-space-before":"never",
+ "declaration-block-single-line-max-declarations":1,
+ "declaration-block-trailing-semicolon":"always",
+ "declaration-colon-space-after":"always-single-line",
+ "declaration-colon-space-before":"never",
+ "declaration-property-value-blacklist":{
+ "border":[
+ "none"
+ ],
+ "border-top":[
+ "none"
+ ],
+ "border-right":[
+ "none"
+ ],
+ "border-bottom":[
+ "none"
+ ],
+ "border-left":[
+ "none"
+ ]
+ },
+ "function-comma-space-after":"always-single-line",
+ "function-parentheses-space-inside":"never",
+ "function-url-quotes":"always",
+ "indentation":2,
+ "length-zero-no-unit":true,
+ "max-nesting-depth":[
+ 3,
+ {
+ "ignoreAtRules":[
+ "each",
+ "media",
+ "supports",
+ "include"
+ ],
+ "severity":"warning"
+ }
+ ],
+ "media-feature-name-no-vendor-prefix":true,
+ "media-feature-parentheses-space-inside":"never",
+ "no-missing-end-of-source-newline":true,
+ "number-leading-zero":"always",
+ "number-no-trailing-zeros":true,
+ "property-no-unknown":true,
+ "property-no-vendor-prefix":true,
+ "rule-empty-line-before":[
+ "always-multi-line",
+ {
+ "except":[
+ "first-nested"
+ ],
+ "ignore":[
+ "after-comment"
+ ]
+ }
+ ],
+ "scss/at-extend-no-missing-placeholder":[true,{ "severity": "warning" }],
+ "scss/at-function-pattern":"^[a-z]+([a-z0-9-]+[a-z0-9]+)?$",
+ "scss/at-import-no-partial-leading-underscore":true,
+ "scss/at-import-partial-extension-blacklist":[
+ "scss"
+ ],
+ "scss/at-mixin-pattern":"^[a-z]+([a-z0-9-]+[a-z0-9]+)?$",
+ "scss/at-rule-no-unknown":true,
+ "scss/dollar-variable-colon-space-after":"always",
+ "scss/dollar-variable-colon-space-before":"never",
+ "scss/dollar-variable-pattern":"^[_]?[a-z]+([a-z0-9-]+[a-z0-9]+)?$",
+ "scss/percent-placeholder-pattern":"^[a-z]+([a-z0-9-]+[a-z0-9]+)?$",
+ "scss/selector-no-redundant-nesting-selector":true,
+ "selector-class-pattern":[
+ "^[a-z0-9\\-]+$",
+ {
+ "message":"Selector should be written in lowercase with hyphens (selector-class-pattern)",
+ "severity": "warning"
+ },
+ ],
+ "selector-list-comma-newline-after":"always",
+ "selector-max-compound-selectors":[5, { "severity": "warning" }],
+ "selector-max-id":1,
+ "selector-no-vendor-prefix":true,
+ "selector-pseudo-element-colon-notation":"double",
+ "selector-pseudo-element-no-unknown":true,
+ "shorthand-property-no-redundant-values":true,
+ "string-quotes":"single",
+ "value-no-vendor-prefix":true
+ }
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e220d61b316..feda5e0835b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,253 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 11.8.0 (2019-02-22)
+
+### Security (7 changes, 1 of them is from the community)
+
+- Sanitize user full name to clean up any URL to prevent mail clients from auto-linking URLs. !2793
+- Update Helm to 2.12.2 to address Helm client vulnerability. !24418 (Takuya Noguchi)
+- Use sanitized user status message for user popover.
+- Validate bundle files before unpacking them.
+- Alias GitHub and BitBucket OAuth2 callback URLs.
+- Fixed XSS content in KaTex links.
+- Disallows unauthorized users from accessing the pipelines section.
+
+### Removed (2 changes, 1 of them is from the community)
+
+- Removed deprecated Redcarpet markdown engine.
+- Remove Cancel all jobs button in general jobs list view. (Jordi Llull)
+
+### Fixed (84 changes, 20 of them are from the community)
+
+- Fix ambiguous brackets in task lists. !18514 (Jared Deckard <jared.deckard@gmail.com>)
+- Fix lost line number when navigating to a specific line in a protected file before authenticating. !19165 (Scott Escue)
+- Fix suboptimal handling of checkbox and radio input events causing group general settings submit button to stay disabled after changing its visibility. !23022
+- Fix upcoming milestones filter not including group milestones. !23098 (Heinrich Lee Yu)
+- Update runner admin page to make description field larger. !23593 (Sascha Reynolds)
+- Fix Bitbucket Server import not allowing personal projects. !23601
+- Fix bug causing repository mirror settings UI to break. !23712
+- Fix foreground color for labels to ensure consistency of label appearance. !23873 (Nathan Friend)
+- Resolve In Merge Request diff screen, master is not a hyperlink. !23874
+- Show the correct error page when access is denied. !23932
+- Increase reliability and performance of toggling task items. !23938
+- Modify file restore to rectify tar issue. !24000
+- Fix default visibility_level for new projects. !24120 (Fabian Schneider @fabsrc)
+- Footnotes now render properly in markdown. !24168
+- Emoji and cancel button are taller than input in set user status modal. !24173 (Dhiraj Bodicherla)
+- Adjusts duplicated line when commenting on unfolded diff lines (in the bottom). !24201
+- Adjust height of "Add list" dropdown in issue boards. !24227
+- Improves restriction of multiple Kubernetes clusters through API. !24251
+- Fix files/blob api endpoints content disposition. !24267
+- Cleanup stale +deleted repo paths on project removal (adjusts project removal bug). !24269
+- Handle regular job dependencies next to parallelized job dependencies. !24273
+- Proper align Projects dropdown on issue boards page. !24277 (Johann Hubert Sonntagbauer)
+- Resolve When merging an MR, the squash checkbox isnt always supported. !24296
+- Fix Bitbucket Server importer error handling. !24343
+- Fix syntax highlighting for suggested changes preview. !24358
+- API: Support dots in wiki slugs. !24383 (Robert Schilling)
+- Show CI artifact file size with 3 significant digits on 'browse job artifacts' page. !24387
+- API: Support username with dots. !24395 (Robert Schilling)
+- API: Fix default_branch_protection admin setting. !24398 (Robert Schilling)
+- Remove unwanted margin above suggested changes. !24419
+- Prevent checking protected_ref? for ambiguous refs. !24437
+- Update metrics environment dropdown to show complete option set. !24441
+- Fix empty labels of CI builds for gitlab-pages on pipeline page. !24451
+- Do not run spam checks on confidential issues. !24453
+- Upgrade KaTeX to version 0.10.0. !24478 (Andrew Harmon)
+- Avoid overwriting default jaeger values with nil. !24482
+- Display SAML failure messages instead of expecting CSRF token. !24509
+- Adjust vertical alignment for project visibility icons. !24511 (Martin Hobert)
+- Load initUserInternalRegexPlaceholder only when required. !24522
+- Hashed Storage: `AfterRenameService` was receiving the wrong `old_path` under some circunstances. !24526
+- Resolve Runners IPv6 address overlaps other values. !24531
+- Fix 404s with snippet uploads in object storage. !24550
+- Fixed oversized custom project notification selector dropdown. !24557
+- Allow users with full private access to read private personal snippets. !24560
+- Resolve Pipeline stages job action button icon is not aligned. !24577
+- Fix cluster page non-interactive on form validation error. !24583
+- Fix 404s for snippet uploads when relative URL root used. !24588
+- Fix markdown table border. !24601
+- Fix CSS grid on a new Project/Group Milestone. !24614 (Takuya Noguchi)
+- Prevent unload when Recaptcha is open. !24625
+- Clean up unicorn sampler metric labels. !24626 (bjk-gitlab)
+- Support bamboo api polymorphism. !24680 (Alex Lossent)
+- Ensure Cert Manager works with Auto DevOps URLs greater than 64 bytes. !24683
+- Fix failed LDAP logins when nil user_id present. !24749
+- fix display comment avatars issue in IE 11. !24777 (Gokhan Apaydin)
+- Fix template labels not being created on new projects. !24803
+- Fix cluster installation processing spinner. !24814
+- Append prioritized label before pagination. !24815
+- Resolve UI bug adding group members with lower permissions. !24820
+- Make `ActionController::Parameters` serializable for sidekiq jobs. !24864
+- Fix Jira Service password validation on project integration services. !24896 (Daniel Juarez)
+- Fix potential Addressable::URI::InvalidURIError. !24908
+- Update Workhorse to v8.2.0. !24909
+- Encode Content-Disposition filenames. !24919
+- Avoid race conditions when creating GpgSignature. !24939
+- Create the source branch for a GitHub import. !25064
+- Fix suggested changes syntax highlighting. !25116
+- Fix counts in milestones dashboard. !25230
+- Fixes incorrect TLD validation errors for Kubernetes cluster domain. !25262
+- Fix 403 errors when adding an assignee list in project boards. !25263
+- Prevent Auto DevOps from trying to deploy without a domain name. !25308
+- Fix uninitialized constant with GitLab Pages.
+- Increase line height of project summaries. (gfyoung)
+- Remove extra space between MR tab bar and sticky file headers.
+- Correct spacing for comparison page.
+- Update CI YAML param table with include.
+- Return bottom border on MR Tabs.
+- Fixes z-index and margins of archived alert in job page.
+- Fixes archived sticky top bar without perfomance bar.
+- Fixed rebase button not showing in merge request widget.
+- Fixed double tooltips on note awards buttons.
+- Allow suggestions to be copied and pasted as GFM.
+- Fix bug that caused Suggestion Markdown toolbar button to insert snippet with leading +/-/<space>.
+- Moved primary button for labels to follow the design patterns used on rest of the site. (Martin Hobert)
+
+### Changed (37 changes, 11 of them are from the community)
+
+- Change spawning of tooltips to be top by default. !21223
+- Standardize filter value capitlization in filter bar in both issues and boards pages. !23846 (obahareth)
+- Refresh group overview to match project overview. !23866
+- Build number does not need to be tweaked anymore for the TeamCity integration to work properly. !23898
+- Added empty project illustration and updated text to user profile overview. !23973 (Fernando Arias)
+- Modified Knative list view to provide more details. !24072 (Chris Baumbauer)
+- Move cancel & new issue button on job page. !24074
+- Make issuable empty states actionable. !24077
+- Fix code search when text is larger than max gRPC message size. !24111
+- Update string structure for available group runners. !24187 (George Tsiolis)
+- Remove multilingual translation from the word "in" in the job details sidebar. !24192 (Nathan Friend)
+- Fix duplicate project disk path in BackfillLegacyProjectRepositories. !24213
+- Ensured links to a comment or system note anchor resolves to the right note if a user has a discussion filter. !24228
+- Remove expansion hover animation from pipeline status icon buttons. !24268 (Nathan Friend)
+- Redesigned related merge requests in issue page. !24270
+- Return the maximum group access level in the projects API. !24403
+- Update project topics styling to use badges design. !24415
+- Display "commented" only for commit discussions on merge requests. !24427
+- Upgrade js-regex gem to version 3.1. !24433 (rroger)
+- Prevent Sidekiq arguments over 10 KB in size from being logged to JSON. !24493
+- Added Avatar in the settings sidebar. !24515 (Yoginth)
+- Refresh empty states for profile page tabs. !24549
+- remove red/green colors from diff view of no-color syntax theme. !24582 (khm)
+- Get remote IP address of runner. !24624
+- Update last_activity_on for Users on some main GET endpoints. !24642
+- Update metrics dashboard graph design. !24653
+- Update to GitLab SVG icon from Font Awesome in profile for location and work. !24671 (Yoginth)
+- Add template for Android with Fastlane. !24722
+- Display timestamps to messages printed by gitlab:backup:restore rake tasks. (Will Chandler)
+- Show MR statistics in diff comparisons.
+- Make possible to toggle file tree while scrolling through diffs.
+- Use delete instead of remove when referring to `git branch -D`.
+- Add folder header to files in merge request tree list.
+- Added fuzzy file finder to merge requests.
+- Collapse directory structure in merge request file tree.
+- Adds skeleton loading to releases page.
+- Support multiple outputs in jupyter notebooks.
+
+### Performance (8 changes, 1 of them is from the community)
+
+- Remove unused button classes `btn-create` and `comment-btn`. !23232 (George Tsiolis)
+- [API] Omit `X-Total` and `X-Total-Pages` headers when items count is more than 10,000. !23931
+- Improve efficiency of GitHub importer by reducing amount of locks needed. !24102
+- Improve milestone queries using subqueries instead of separate queries for ids. !24325
+- Efficiently remove expired artifacts in `ExpireBuildArtifactsWorker`. !24450
+- Eliminate N+1 queries in /api/groups/:id. !24513
+- Use deployment relation to get an environment name. !24890
+- Do not reload daemon if configuration file of pages does not change.
+
+### Added (35 changes, 18 of them are from the community)
+
+- Add badge count to projects. !18425 (George Tsiolis)
+- API: Add support for group labels. !21368 (Robert Schilling)
+- Add setting for first day of the week. !22755 (Fabian Schneider @fabsrc)
+- Pages for subgroups. !23505
+- Add support for customer provided encryption keys for Amazon S3 remote backups. !23797 (Pepijn Van Eeckhoudt)
+- Add Knative detailed view. !23863 (Chris Baumbauer)
+- Add group full path to project's shared_with_groups. !24052 (Mathieu Parent)
+- Added feature to specify a custom Auto DevOps chart repository. !24162 (walkafwalka)
+- Add flat-square badge style. !24172 (Fabian Schneider @fabsrc)
+- Display last activity and created at datetimes for users. !24181
+- Allow setting of feature gates per project. !24184
+- Save issues/merge request sorting options to backend. !24198
+- Added support for custom hosts/domains to Auto DevOps. !24248 (walkafwalka)
+- Adds milestone search. !24265 (Jacopo Beschi @jacopo-beschi)
+- Allow merge request diffs to be placed into an object store. !24276
+- Add Container Registry API with cleanup function. !24303
+- GitLab now supports the profile and email scopes from OpenID Connect. !24335 (Goten Xiao)
+- Add 'in' filter that modifies scope of 'search' filter to issues and merge requests API. !24350 (Hiroyuki Sato)
+- Add `with_programming_language` filter for projects to API. !24377 (Dylan MacKenzie)
+- API: Support searching for tags. !24385 (Robert Schilling)
+- Document graphicsmagick installation for source installation. !24404 (Alexis Reigel)
+- Redirect GET projects/:id to project page. !24467
+- Indicate on Issue Status if an Issue was Moved. !24470
+- Redeploy Auto DevOps deployment on variable updates. !24498 (walkafwalka)
+- Don't create new merge request pipeline without commits. !24503 (Hiroyuki Sato)
+- Add GitLab Pages predefined CI variables 'CI_PAGES_DOMAIN' and 'CI_PAGES_URL'. !24504 (Adrian Moisey)
+- Moves domain setting from Auto DevOps to Cluster's page. !24580
+- API allows setting the squash commit message when squashing a merge request. !24784
+- Added ability to upgrade cluster applications. !24789
+- Add argument iids for issues in GraphQL. !24802
+- Add repositories count to usage ping data. !24823
+- Add support for extensionless pages URLs. !24876
+- Add templates for most popular Pages templates. !24906
+- Introduce Internal API for searching environment names. !24923
+- Allow admins to invalidate markdown texts by setting local markdown version.
+
+### Other (50 changes, 18 of them are from the community)
+
+- Externalize strings from `/app/views/projects/project_members`. !23227 (Tao Wang)
+- Add CSS & JS global flags to represent browser and platform. !24017
+- Fix deprecation: Passing an argument to force an association to reload is now deprecated. !24136 (Jasper Maes)
+- Cleanup legacy artifact background migration. !24144
+- Bump kubectl in Auto DevOps to 1.11.6. !24176
+- Conditionally initialize the global opentracing tracer. !24186
+- Remove horizontal whitespace on user profile overview on small breakpoints. !24189
+- Bump nginx-ingress chart to 1.1.2. !24203
+- Use monospace font for registry table tag id and tag name. !24205
+- Rename project tags to project topics. !24219
+- Add uniqueness validation to url column in Releases::Link model. !24223
+- Update sidekiq-cron to 1.0.4 and use fugit to replace rufus-scheduler to parse cron syntax. !24235
+- Adds inter-service OpenTracing propagation. !24239
+- Fixes Auto DevOps title on CI/CD admin settings. !24249
+- Upgrade kubeclient to 4.2.2 and swap out monkey-patch to disallow redirects. !24284
+- i18n: externalize strings from 'app/views/search'. !24297 (Tao Wang)
+- Fix several ActionController::Parameters deprecations. !24332 (Jasper Maes)
+- Remove all `$theme-gray-{weight}` variables in favor of `$gray-{weight}`. !24333 (George Tsiolis)
+- Update gitlab-styles to 2.5.1. !24336 (Jasper Maes)
+- Modifies environment scope UI on cluster page. !24376
+- Extract process_name from GitLab::Sentry. !24422
+- Upgrade Gitaly to 1.13.0. !24429
+- Actually set raise_on_unfiltered_parameters to true. !24443 (Jasper Maes)
+- Refactored NoteableDiscussion by extracting ResolveDiscussionButton. !24505 (Martin Hobert)
+- Extracted JumpToNextDiscussionButton to its own component. !24506 (Martin Hobert)
+- Extracted ReplyPlaceholder to its own component. !24507 (Martin Hobert)
+- Block emojis and symbol characters from users full names. !24523
+- Update GitLab Runner Helm Chart to 0.1.45. !24564
+- Updated docs for fields in pushing mirror from GitLab to GitHub. !24566 (Joseph Yu)
+- Upgrade gitlab-workhorse to 8.1.0. !24571
+- Externalize strings from `/app/views/sent_notifications`. !24576 (George Tsiolis)
+- Adds tracing support for ActiveRecord notifications. !24604
+- Externalize strings from `/app/views/projects/ci`. !24617 (George Tsiolis)
+- Move permission check of manual actions of deployments. !24660
+- Externalize strings from `/app/views/clusters`. !24666 (George Tsiolis)
+- Update UI for admin appearance settings. !24685
+- Externalize strings from `/app/views/projects/pages_domains`. !24723 (George Tsiolis)
+- Externalize strings from `/app/views/projects/milestones`. !24726 (George Tsiolis)
+- Add OpenTracing instrumentation for Action View Render events. !24728
+- Expose version for each application in cluster_status JSON endpoint. !24791
+- Externalize strings from `/app/views/instance_statistics`. !24809 (George Tsiolis)
+- Update cluster application version on updated and installed status. !24810
+- Project list UI improvements. !24855
+- Externalize strings from `/app/views/email_rejection_mailer`. !24869 (George Tsiolis)
+- Update Gitaly to v1.17.0. !24873
+- Update Workhorse to v8.3.0. !24959
+- Upgrade gitaly to 1.18.0. !24981
+- Update Workhorse to v8.3.1.
+- Upgraded Codesandbox smooshpack package.
+- Creates mixin to reduce code duplication between CE and EE in graph component.
+
+
## 11.7.5 (2019-02-06)
### Fixed (8 changes)
diff --git a/Dangerfile b/Dangerfile
index 6a2c5cf2773..715a2bcbbae 100644
--- a/Dangerfile
+++ b/Dangerfile
@@ -11,3 +11,4 @@ danger.import_dangerfile(path: 'danger/commit_messages')
danger.import_dangerfile(path: 'danger/duplicate_yarn_dependencies')
danger.import_dangerfile(path: 'danger/prettier')
danger.import_dangerfile(path: 'danger/eslint')
+danger.import_dangerfile(path: 'danger/roulette')
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 66e2ae6c25c..3500250a4b0 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-1.19.1
+1.21.0
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index 917d38ec9f9..acd405b1d62 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-8.4.4
+8.6.0
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index 2bf50aaf17a..56b6be4ebb2 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-8.3.0
+8.3.1
diff --git a/Gemfile b/Gemfile
index 452860d60b4..0178ca9f352 100644
--- a/Gemfile
+++ b/Gemfile
@@ -94,13 +94,15 @@ gem 'carrierwave', '~> 1.3'
gem 'mini_magick'
# for backups
-gem 'fog-aws', '~> 2.0.1'
-gem 'fog-core', '~> 1.44'
-gem 'fog-google', '~> 1.7.1'
-gem 'fog-local', '~> 0.3'
-gem 'fog-openstack', '~> 0.1'
+gem 'fog-aws', '~> 3.3'
+# Locked until fog-google resolves https://github.com/fog/fog-google/issues/421.
+# Also see config/initializers/fog_core_patch.rb.
+gem 'fog-core', '= 2.1.0'
+gem 'fog-google', '~> 1.8'
+gem 'fog-local', '~> 0.6'
+gem 'fog-openstack', '~> 1.0'
gem 'fog-rackspace', '~> 0.1.1'
-gem 'fog-aliyun', '~> 0.2.0'
+gem 'fog-aliyun', '~> 0.3'
# for Google storage
gem 'google-api-client', '~> 0.23'
@@ -143,7 +145,7 @@ gem 'diffy', '~> 3.1.0'
gem 'rack', '2.0.6'
group :unicorn do
- gem 'unicorn', '~> 5.1.0'
+ gem 'unicorn', '~> 5.4.1'
gem 'unicorn-worker-killer', '~> 0.4.4'
end
@@ -184,7 +186,7 @@ gem 're2', '~> 1.1.1'
# Misc
-gem 'version_sorter', '~> 2.1.0'
+gem 'version_sorter', '~> 2.2.4'
# Export Ruby Regex to Javascript
gem 'js_regex', '~> 3.1'
@@ -380,7 +382,7 @@ group :test do
gem 'shoulda-matchers', '~> 3.1.2', require: false
gem 'email_spec', '~> 2.2.0'
gem 'json-schema', '~> 2.8.0'
- gem 'webmock', '~> 2.3.2'
+ gem 'webmock', '~> 3.5.1'
gem 'rails-controller-testing'
gem 'sham_rack', '~> 1.3.6'
gem 'concurrent-ruby', '~> 1.1'
@@ -419,7 +421,8 @@ group :ed25519 do
end
# Gitaly GRPC client
-gem 'gitaly-proto', '~> 1.10.0', require: 'gitaly'
+gem 'gitaly-proto', '~> 1.12.0', require: 'gitaly'
+
gem 'grpc', '~> 1.15.0'
gem 'google-protobuf', '~> 3.6'
diff --git a/Gemfile.lock b/Gemfile.lock
index 92dd5dac0ca..74a06581da2 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -218,32 +218,33 @@ GEM
flowdock (0.7.1)
httparty (~> 0.7)
multi_json
- fog-aliyun (0.2.0)
- fog-core (~> 1.27)
- fog-json (~> 1.0)
+ fog-aliyun (0.3.3)
+ fog-core
+ fog-json
ipaddress (~> 0.8)
xml-simple (~> 1.1)
- fog-aws (2.0.1)
- fog-core (~> 1.38)
- fog-json (~> 1.0)
+ fog-aws (3.3.0)
+ fog-core (~> 2.1)
+ fog-json (~> 1.1)
fog-xml (~> 0.1)
ipaddress (~> 0.8)
- fog-core (1.45.0)
+ fog-core (2.1.0)
builder
excon (~> 0.58)
formatador (~> 0.2)
- fog-google (1.7.1)
- fog-core
- fog-json
- fog-xml
+ mime-types
+ fog-google (1.8.2)
+ fog-core (<= 2.1.0)
+ fog-json (~> 1.2)
+ fog-xml (~> 0.1.0)
google-api-client (~> 0.23.0)
- fog-json (1.0.2)
- fog-core (~> 1.0)
+ fog-json (1.2.0)
+ fog-core
multi_json (~> 1.10)
- fog-local (0.3.1)
- fog-core (~> 1.27)
- fog-openstack (0.1.21)
- fog-core (>= 1.40)
+ fog-local (0.6.0)
+ fog-core (>= 1.27, < 3.0)
+ fog-openstack (1.0.8)
+ fog-core (~> 2.1)
fog-json (>= 1.0)
ipaddress (>= 0.8)
fog-rackspace (0.1.1)
@@ -278,7 +279,7 @@ GEM
gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
- gitaly-proto (1.10.0)
+ gitaly-proto (1.12.0)
grpc (~> 1.0)
github-markup (1.7.0)
gitlab-default_value_for (3.1.1)
@@ -357,7 +358,7 @@ GEM
thor
tilt
hangouts-chat (0.0.5)
- hashdiff (0.3.4)
+ hashdiff (0.3.8)
hashie (3.5.7)
hashie-forbidden_attributes (0.1.1)
hashie (>= 3.0)
@@ -422,7 +423,7 @@ GEM
activerecord
kaminari-core (= 1.0.1)
kaminari-core (1.0.1)
- kgio (2.10.0)
+ kgio (2.11.2)
knapsack (1.17.0)
rake
kubeclient (4.2.2)
@@ -666,7 +667,7 @@ GEM
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
rainbow (3.0.0)
- raindrops (0.18.0)
+ raindrops (0.19.0)
rake (12.3.2)
rb-fsevent (0.10.2)
rb-inotify (0.9.10)
@@ -898,7 +899,7 @@ GEM
unf_ext
unf_ext (0.0.7.5)
unicode-display_width (1.3.2)
- unicorn (5.1.0)
+ unicorn (5.4.1)
kgio (~> 2.6)
raindrops (~> 0.7)
unicorn-worker-killer (0.4.4)
@@ -916,7 +917,7 @@ GEM
validates_hostname (1.0.6)
activerecord (>= 3.0)
activesupport (>= 3.0)
- version_sorter (2.1.0)
+ version_sorter (2.2.4)
virtus (1.0.5)
axiom-types (~> 0.1)
coercible (~> 1.0)
@@ -925,7 +926,7 @@ GEM
vmstat (2.3.0)
warden (1.2.7)
rack (>= 1.0)
- webmock (2.3.2)
+ webmock (3.5.1)
addressable (>= 2.3.6)
crack (>= 0.3.2)
hashdiff
@@ -1002,12 +1003,12 @@ DEPENDENCIES
flipper-active_record (~> 0.13.0)
flipper-active_support_cache_store (~> 0.13.0)
flowdock (~> 0.7)
- fog-aliyun (~> 0.2.0)
- fog-aws (~> 2.0.1)
- fog-core (~> 1.44)
- fog-google (~> 1.7.1)
- fog-local (~> 0.3)
- fog-openstack (~> 0.1)
+ fog-aliyun (~> 0.3)
+ fog-aws (~> 3.3)
+ fog-core (= 2.1.0)
+ fog-google (~> 1.8)
+ fog-local (~> 0.6)
+ fog-openstack (~> 1.0)
fog-rackspace (~> 0.1.1)
font-awesome-rails (~> 4.7)
foreman (~> 0.84.0)
@@ -1017,7 +1018,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
- gitaly-proto (~> 1.10.0)
+ gitaly-proto (~> 1.12.0)
github-markup (~> 1.7.0)
gitlab-default_value_for (~> 3.1.1)
gitlab-markup (~> 1.6.5)
@@ -1169,13 +1170,13 @@ DEPENDENCIES
u2f (~> 0.2.1)
uglifier (~> 2.7.2)
unf (~> 0.1.4)
- unicorn (~> 5.1.0)
+ unicorn (~> 5.4.1)
unicorn-worker-killer (~> 0.4.4)
validates_hostname (~> 1.0.6)
- version_sorter (~> 2.1.0)
+ version_sorter (~> 2.2.4)
virtus (~> 1.0.1)
vmstat (~> 2.3.0)
- webmock (~> 2.3.2)
+ webmock (~> 3.5.1)
webpack-rails (~> 0.9.10)
wikicloth (= 0.8.1)
diff --git a/PROCESS.md b/PROCESS.md
index 7fdac098807..1f99cebe081 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -108,7 +108,19 @@ Merge requests that make changes hidden behind a feature flag, or remove an
existing feature flag because a feature is deemed stable, may be merged (and
picked into the stable branches) up to the 19th of the month. Such merge
requests should have the ~"feature flag" label assigned, and don't require a
-corresponding exception request to be created.
+corresponding exception request to be created.
+
+A level of common sense should be applied when deciding whether to have a feature
+behind a feature flag off or on by default.
+
+The following guideliness can be applied to help make this decision:
+
+* If the feature is not fully ready or functioning, the feature flag should be disabled by default.
+* If the feature is ready but there are concerns about performance or impact, the feature flag should be enabled by default, but
+disabled via chatops before deployment on GitLab.com environments. If the performance concern is confirmed, the final release should have the feature flag disabled by default.
+* In most other cases, the feature flag can be enabled by default.
+
+For more information on rolling out changes using feature flags, read [through the documentation](https://docs.gitlab.com/ee/development/rolling_out_changes_using_feature_flags.html).
In order to build the final package and present the feature for self-hosted
customers, the feature flag should be removed. This should happen before the
@@ -156,8 +168,12 @@ on behalf of the community member.
Every new feature or change should be shipped with its corresponding documentation
in accordance with the
-[documentation process](https://docs.gitlab.com/ee/development/documentation/workflow.html)
-and [structure](https://docs.gitlab.com/ee/development/documentation/structure.html).
+[documentation process](https://docs.gitlab.com/ee/development/documentation/feature-change-workflow.html)
+and [structure](https://docs.gitlab.com/ee/development/documentation/structure.html) guides.
+Note that a technical writer will review all changes to documentation. This can occur
+in the same MR as the feature code, but [if there is not sufficient time or need,
+it can be planned via a follow-up issue for doc review](https://docs.gitlab.com/ee/development/documentation/feature-change-workflow.html#1-product-managers-role),
+and another MR, if needed. Regardless, complete docs must be merged with code by the freeze.
#### What happens if these deadlines are missed?
@@ -186,8 +202,6 @@ and to prevent any last minute surprises.
Merge requests should still be complete, following the [definition of done][done].
-#### Feature merge requests
-
If a merge request is not ready, but the developers and Product Manager
responsible for the feature think it is essential that it is in the release,
they can [ask for an exception](#asking-for-an-exception) in advance. This is
@@ -202,34 +216,17 @@ information, see
[Automatic CE->EE merge][automatic_ce_ee_merge] and
[Guidelines for implementing Enterprise Edition features][ee_features].
-#### Documentation merge requests
-
-Documentation is part of the product and must be shipped with the feature.
-
-The single exception for the feature freeze is documentation, and it can
-be left to be **merged up to the 14th** if:
-
-* There is a follow-up issue to add documentation.
-* It is assigned to the developer writing documentation for this feature, and they
- are aware of it.
-* It is in the correct milestone, with the labels ~Documentation, ~Deliverable,
-~missed-deliverable, and "pick into X.Y" applied.
-* It must be reviewed and approved by a technical writer.
-
-For more information read the process for
-[documentation shipped late](https://docs.gitlab.com/ee/development/documentation/workflow.html#documentation-shipped-late).
-
### After the 7th
Once the stable branch is frozen, the only MRs that can be cherry-picked into
the stable branch are:
* Fixes for [regressions](#regressions) where the affected version `xx.x` in `regression:xx.x` is the current release. See [Managing bugs](#managing-bugs) section.
-* Fixes for security issues
-* Fixes or improvements to automated QA scenarios
-* [Documentation updates](https://docs.gitlab.com/ee/development/documentation/workflow.html#documentation-shipped-late) for changes in the same release
-* New or updated translations (as long as they do not touch application code)
-* Changes that are behind a feature flag and have the ~"feature flag" label
+* Fixes for security issues.
+* Fixes or improvements to automated QA scenarios.
+* [Documentation improvements](https://docs.gitlab.com/ee/development/documentation/workflow.html) for feature changes made in the same release, though initial docs for these features should have already been merged by the freeze, as required.
+* New or updated translations (as long as they do not touch application code).
+* Changes that are behind a feature flag and have the ~"feature flag" label.
During the feature freeze all merge requests that are meant to go into the
upcoming release should have the correct milestone assigned _and_ the
diff --git a/VERSION b/VERSION
index 106400c03a2..00a946e3be3 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-11.8.0-pre
+11.9.0-pre
diff --git a/app/assets/javascripts/badges/components/badge_form.vue b/app/assets/javascripts/badges/components/badge_form.vue
index 85a15b38de1..df74eb2c2f7 100644
--- a/app/assets/javascripts/badges/components/badge_form.vue
+++ b/app/assets/javascripts/badges/components/badge_form.vue
@@ -90,7 +90,7 @@ export default {
},
badgeImageUrlExample() {
const exampleUrl =
- 'https://example.gitlab.com/%{project_path}/badges/%{default_branch}/badge.svg';
+ 'https://example.gitlab.com/%{project_path}/badges/%{default_branch}/pipeline.svg';
return sprintf(s__('Badges|e.g. %{exampleUrl}'), {
exampleUrl,
});
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue
index f0ce2579ee7..1fc2b7fe859 100644
--- a/app/assets/javascripts/diffs/components/app.vue
+++ b/app/assets/javascripts/diffs/components/app.vue
@@ -4,6 +4,8 @@ import Icon from '~/vue_shared/components/icon.vue';
import { __ } from '~/locale';
import createFlash from '~/flash';
import { GlLoadingIcon } from '@gitlab/ui';
+import PanelResizer from '~/vue_shared/components/panel_resizer.vue';
+import Mousetrap from 'mousetrap';
import eventHub from '../../notes/event_hub';
import CompareVersions from './compare_versions.vue';
import DiffFile from './diff_file.vue';
@@ -11,6 +13,13 @@ import NoChanges from './no_changes.vue';
import HiddenFilesWarning from './hidden_files_warning.vue';
import CommitWidget from './commit_widget.vue';
import TreeList from './tree_list.vue';
+import {
+ TREE_LIST_WIDTH_STORAGE_KEY,
+ INITIAL_TREE_WIDTH,
+ MIN_TREE_WIDTH,
+ MAX_TREE_WIDTH,
+ TREE_HIDE_STATS_WIDTH,
+} from '../constants';
export default {
name: 'DiffsApp',
@@ -23,6 +32,7 @@ export default {
CommitWidget,
TreeList,
GlLoadingIcon,
+ PanelResizer,
},
props: {
endpoint: {
@@ -54,8 +64,12 @@ export default {
},
},
data() {
+ const treeWidth =
+ parseInt(localStorage.getItem(TREE_LIST_WIDTH_STORAGE_KEY), 10) || INITIAL_TREE_WIDTH;
+
return {
assignedDiscussions: false,
+ treeWidth,
};
},
computed: {
@@ -74,7 +88,7 @@ export default {
emailPatchPath: state => state.diffs.emailPatchPath,
}),
...mapState('diffs', ['showTreeList', 'isLoading', 'startVersion']),
- ...mapGetters('diffs', ['isParallelView']),
+ ...mapGetters('diffs', ['isParallelView', 'currentDiffIndex']),
...mapGetters(['isNotesFetched', 'getNoteableData']),
targetBranch() {
return {
@@ -96,6 +110,9 @@ export default {
this.startVersion.version_index === this.mergeRequestDiff.version_index)
);
},
+ hideFileStats() {
+ return this.treeWidth <= TREE_HIDE_STATS_WIDTH;
+ },
},
watch: {
diffViewType() {
@@ -133,6 +150,7 @@ export default {
},
beforeDestroy() {
eventHub.$off('fetchDiffData', this.fetchData);
+ this.removeEventListeners();
},
methods: {
...mapActions(['startTaskList']),
@@ -142,6 +160,8 @@ export default {
'startRenderDiffsQueue',
'assignDiscussionsToDiff',
'setHighlightedRow',
+ 'cacheTreeListWidth',
+ 'scrollToFile',
]),
fetchData() {
this.fetchDiffFiles()
@@ -180,10 +200,40 @@ export default {
this.$nextTick(() => {
window.mrTabs.resetViewContainer();
window.mrTabs.expandViewContainer(this.showTreeList);
+ this.setEventListeners();
});
+ } else {
+ this.removeEventListeners();
+ }
+ },
+ setEventListeners() {
+ Mousetrap.bind(['[', 'k', ']', 'j'], (e, combo) => {
+ switch (combo) {
+ case '[':
+ case 'k':
+ this.jumpToFile(-1);
+ break;
+ case ']':
+ case 'j':
+ this.jumpToFile(+1);
+ break;
+ default:
+ break;
+ }
+ });
+ },
+ removeEventListeners() {
+ Mousetrap.unbind(['[', 'k', ']', 'j']);
+ },
+ jumpToFile(step) {
+ const targetIndex = this.currentDiffIndex + step;
+ if (targetIndex >= 0 && targetIndex < this.diffFiles.length) {
+ this.scrollToFile(this.diffFiles[targetIndex].file_path);
}
},
},
+ minTreeWidth: MIN_TREE_WIDTH,
+ maxTreeWidth: MAX_TREE_WIDTH,
};
</script>
@@ -209,7 +259,21 @@ export default {
:data-can-create-note="getNoteableData.current_user.can_create_note"
class="files d-flex prepend-top-default"
>
- <div v-show="showTreeList" class="diff-tree-list"><tree-list /></div>
+ <div
+ v-show="showTreeList"
+ :style="{ width: `${treeWidth}px` }"
+ class="diff-tree-list js-diff-tree-list"
+ >
+ <panel-resizer
+ :size.sync="treeWidth"
+ :start-size="treeWidth"
+ :min-size="$options.minTreeWidth"
+ :max-size="$options.maxTreeWidth"
+ side="right"
+ @resize-end="cacheTreeListWidth"
+ />
+ <tree-list :hide-file-stats="hideFileStats" />
+ </div>
<div class="diff-files-holder">
<commit-widget v-if="commit" :commit="commit" />
<template v-if="renderDiffFiles">
diff --git a/app/assets/javascripts/diffs/components/diff_content.vue b/app/assets/javascripts/diffs/components/diff_content.vue
index 6dc2f5d3f68..cb92093db32 100644
--- a/app/assets/javascripts/diffs/components/diff_content.vue
+++ b/app/assets/javascripts/diffs/components/diff_content.vue
@@ -1,7 +1,8 @@
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue';
-import EmptyFileViewer from '~/vue_shared/components/diff_viewer/viewers/empty_file.vue';
+import NotDiffableViewer from '~/vue_shared/components/diff_viewer/viewers/not_diffable.vue';
+import NoPreviewViewer from '~/vue_shared/components/diff_viewer/viewers/no_preview.vue';
import InlineDiffView from './inline_diff_view.vue';
import ParallelDiffView from './parallel_diff_view.vue';
import NoteForm from '../../notes/components/note_form.vue';
@@ -9,6 +10,7 @@ import ImageDiffOverlay from './image_diff_overlay.vue';
import DiffDiscussions from './diff_discussions.vue';
import { IMAGE_DIFF_POSITION_TYPE } from '../constants';
import { getDiffMode } from '../store/utils';
+import { diffViewerModes } from '~/ide/constants';
export default {
components: {
@@ -18,7 +20,8 @@ export default {
NoteForm,
DiffDiscussions,
ImageDiffOverlay,
- EmptyFileViewer,
+ NotDiffableViewer,
+ NoPreviewViewer,
},
props: {
diffFile: {
@@ -42,11 +45,17 @@ export default {
diffMode() {
return getDiffMode(this.diffFile);
},
+ diffViewerMode() {
+ return this.diffFile.viewer.name;
+ },
isTextFile() {
- return this.diffFile.viewer.name === 'text';
+ return this.diffViewerMode === diffViewerModes.text;
+ },
+ noPreview() {
+ return this.diffViewerMode === diffViewerModes.no_preview;
},
- errorMessage() {
- return this.diffFile.viewer.error;
+ notDiffable() {
+ return this.diffViewerMode === diffViewerModes.not_diffable;
},
diffFileCommentForm() {
return this.getCommentFormForDiffFile(this.diffFile.file_hash);
@@ -78,11 +87,10 @@ export default {
<template>
<div class="diff-content">
- <div v-if="!errorMessage" class="diff-viewer">
+ <div class="diff-viewer">
<template v-if="isTextFile">
- <empty-file-viewer v-if="diffFile.empty" />
<inline-diff-view
- v-else-if="isInlineView"
+ v-if="isInlineView"
:diff-file="diffFile"
:diff-lines="diffFile.highlighted_diff_lines || []"
:help-page-path="helpPagePath"
@@ -94,9 +102,12 @@ export default {
:help-page-path="helpPagePath"
/>
</template>
+ <not-diffable-viewer v-else-if="notDiffable" />
+ <no-preview-viewer v-else-if="noPreview" />
<diff-viewer
v-else
:diff-mode="diffMode"
+ :diff-viewer-mode="diffViewerMode"
:new-path="diffFile.new_path"
:new-sha="diffFile.diff_refs.head_sha"
:old-path="diffFile.old_path"
@@ -132,8 +143,5 @@ export default {
</div>
</diff-viewer>
</div>
- <div v-else class="diff-viewer">
- <div class="nothing-here-block" v-html="errorMessage"></div>
- </div>
</div>
</template>
diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue
index 449f7007077..1141a197c6a 100644
--- a/app/assets/javascripts/diffs/components/diff_file.vue
+++ b/app/assets/javascripts/diffs/components/diff_file.vue
@@ -7,6 +7,7 @@ import { GlLoadingIcon } from '@gitlab/ui';
import eventHub from '../../notes/event_hub';
import DiffFileHeader from './diff_file_header.vue';
import DiffContent from './diff_content.vue';
+import { diffViewerErrors } from '~/ide/constants';
export default {
components: {
@@ -33,15 +34,13 @@ export default {
return {
isLoadingCollapsedDiff: false,
forkMessageVisible: false,
+ isCollapsed: this.file.viewer.collapsed || false,
};
},
computed: {
...mapState('diffs', ['currentDiffFileId']),
...mapGetters(['isNotesFetched']),
...mapGetters('diffs', ['getDiffFileDiscussions']),
- isCollapsed() {
- return this.file.collapsed || false;
- },
viewBlobLink() {
return sprintf(
__('You can %{linkStart}view the blob%{linkEnd} instead.'),
@@ -52,17 +51,6 @@ export default {
false,
);
},
- showExpandMessage() {
- return (
- this.isCollapsed ||
- (!this.file.highlighted_diff_lines &&
- !this.isLoadingCollapsedDiff &&
- !this.file.too_large &&
- this.file.text &&
- !this.file.renamed_file &&
- !this.file.mode_changed)
- );
- },
showLoadingIcon() {
return this.isLoadingCollapsedDiff || (!this.file.renderIt && !this.isCollapsed);
},
@@ -73,9 +61,15 @@ export default {
this.file.parallel_diff_lines.length > 0
);
},
+ isFileTooLarge() {
+ return this.file.viewer.error === diffViewerErrors.too_large;
+ },
+ errorMessage() {
+ return this.file.viewer.error_message;
+ },
},
watch: {
- 'file.collapsed': function fileCollapsedWatch(newVal, oldVal) {
+ isCollapsed: function fileCollapsedWatch(newVal, oldVal) {
if (!newVal && oldVal && !this.hasDiffLines) {
this.handleLoadCollapsedDiff();
}
@@ -85,13 +79,13 @@ export default {
eventHub.$on(`loadCollapsedDiff/${this.file.file_hash}`, this.handleLoadCollapsedDiff);
},
methods: {
- ...mapActions('diffs', ['loadCollapsedDiff', 'assignDiscussionsToDiff']),
+ ...mapActions('diffs', ['loadCollapsedDiff', 'assignDiscussionsToDiff', 'setRenderIt']),
handleToggle() {
if (!this.hasDiffLines) {
this.handleLoadCollapsedDiff();
} else {
- this.file.collapsed = !this.file.collapsed;
- this.file.renderIt = true;
+ this.isCollapsed = !this.isCollapsed;
+ this.setRenderIt(this.file);
}
},
handleLoadCollapsedDiff() {
@@ -100,8 +94,8 @@ export default {
this.loadCollapsedDiff(this.file)
.then(() => {
this.isLoadingCollapsedDiff = false;
- this.file.collapsed = false;
- this.file.renderIt = true;
+ this.isCollapsed = false;
+ this.setRenderIt(this.file);
})
.then(() => {
requestIdleCallback(
@@ -164,21 +158,25 @@ export default {
Cancel
</button>
</div>
-
- <diff-content
- v-if="!isCollapsed && file.renderIt"
- :class="{ hidden: isCollapsed || file.too_large }"
- :diff-file="file"
- :help-page-path="helpPagePath"
- />
<gl-loading-icon v-if="showLoadingIcon" class="diff-content loading" />
- <div v-else-if="showExpandMessage" class="nothing-here-block diff-collapsed">
- {{ __('This diff is collapsed.') }}
- <a class="click-to-expand js-click-to-expand" href="#" @click.prevent="handleToggle">{{
- __('Click to expand it.')
- }}</a>
- </div>
- <div v-if="file.too_large" class="nothing-here-block diff-collapsed js-too-large-diff">
+ <template v-else>
+ <div v-if="errorMessage" class="diff-viewer">
+ <div class="nothing-here-block" v-html="errorMessage"></div>
+ </div>
+ <div v-else-if="isCollapsed" class="nothing-here-block diff-collapsed">
+ {{ __('This diff is collapsed.') }}
+ <a class="click-to-expand js-click-to-expand" href="#" @click.prevent="handleToggle">{{
+ __('Click to expand it.')
+ }}</a>
+ </div>
+ <diff-content
+ v-else
+ :class="{ hidden: isCollapsed || isFileTooLarge }"
+ :diff-file="file"
+ :help-page-path="helpPagePath"
+ />
+ </template>
+ <div v-if="isFileTooLarge" class="nothing-here-block diff-collapsed js-too-large-diff">
{{ __('This source diff could not be displayed because it is too large.') }}
<span v-html="viewBlobLink"></span>
</div>
diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue
index 60586d4a607..4d33ad23f39 100644
--- a/app/assets/javascripts/diffs/components/diff_file_header.vue
+++ b/app/assets/javascripts/diffs/components/diff_file_header.vue
@@ -8,6 +8,7 @@ import FileIcon from '~/vue_shared/components/file_icon.vue';
import { GlTooltipDirective } from '@gitlab/ui';
import { truncateSha } from '~/lib/utils/text_utility';
import { __, s__, sprintf } from '~/locale';
+import { diffViewerModes } from '~/ide/constants';
import EditButton from './edit_button.vue';
import DiffStats from './diff_stats.vue';
@@ -118,6 +119,12 @@ export default {
gfmCopyText() {
return `\`${this.diffFile.file_path}\``;
},
+ isFileRenamed() {
+ return this.diffFile.viewer.name === diffViewerModes.renamed;
+ },
+ isModeChanged() {
+ return this.diffFile.viewer.name === diffViewerModes.mode_changed;
+ },
},
mounted() {
polyfillSticky(this.$refs.header);
@@ -165,7 +172,7 @@ export default {
aria-hidden="true"
css-classes="js-file-icon append-right-5"
/>
- <span v-if="diffFile.renamed_file">
+ <span v-if="isFileRenamed">
<strong
v-gl-tooltip
:title="diffFile.old_path"
@@ -193,7 +200,7 @@ export default {
css-class="btn-default btn-transparent btn-clipboard"
/>
- <small v-if="diffFile.mode_changed" ref="fileMode">
+ <small v-if="isModeChanged" ref="fileMode">
{{ diffFile.a_mode }} → {{ diffFile.b_mode }}
</small>
@@ -229,11 +236,15 @@ export default {
<a
v-if="diffFile.replaced_view_path"
:href="diffFile.replaced_view_path"
- class="btn view-file js-view-file"
+ class="btn view-file js-view-replaced-file"
v-html="viewReplacedFileButtonText"
>
</a>
- <a :href="diffFile.view_path" class="btn view-file js-view-file" v-html="viewFileButtonText">
+ <a
+ :href="diffFile.view_path"
+ class="btn view-file js-view-file-button"
+ v-html="viewFileButtonText"
+ >
</a>
<a
@@ -243,7 +254,7 @@ export default {
:title="`View on ${diffFile.formatted_external_url}`"
target="_blank"
rel="noopener noreferrer"
- class="btn btn-file-option"
+ class="btn btn-file-option js-external-url"
>
<icon name="external-link" />
</a>
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 18edbe286ba..bb66ab36283 100644
--- a/app/assets/javascripts/diffs/components/diff_line_note_form.vue
+++ b/app/assets/javascripts/diffs/components/diff_line_note_form.vue
@@ -1,6 +1,7 @@
<script>
import { mapState, mapGetters, mapActions } from 'vuex';
import { s__ } from '~/locale';
+import diffLineNoteFormMixin from 'ee_else_ce/notes/mixins/diff_line_note_form';
import noteForm from '../../notes/components/note_form.vue';
import autosave from '../../notes/mixins/autosave';
import { DIFF_NOTE_TYPE } from '../constants';
@@ -9,7 +10,7 @@ export default {
components: {
noteForm,
},
- mixins: [autosave],
+ mixins: [autosave, diffLineNoteFormMixin],
props: {
diffFileHash: {
type: String,
@@ -103,6 +104,7 @@ export default {
:help-page-path="helpPagePath"
save-button-title="Comment"
class="diff-comment-form"
+ @handleFormUpdateAddToReview="addToReview"
@cancelForm="handleCancelCommentForm"
@handleFormUpdate="handleSaveNote"
/>
diff --git a/app/assets/javascripts/diffs/components/edit_button.vue b/app/assets/javascripts/diffs/components/edit_button.vue
index 5d38d545ce8..803f23b9170 100644
--- a/app/assets/javascripts/diffs/components/edit_button.vue
+++ b/app/assets/javascripts/diffs/components/edit_button.vue
@@ -17,12 +17,7 @@ export default {
},
methods: {
handleEditClick(evt) {
- if (!this.canCurrentUserFork || this.canModifyBlob) {
- // if we can Edit, do default Edit button behavior
- return;
- }
-
- if (this.canCurrentUserFork) {
+ if (this.canCurrentUserFork && !this.canModifyBlob) {
evt.preventDefault();
this.$emit('showForkMessage');
}
diff --git a/app/assets/javascripts/diffs/components/tree_list.vue b/app/assets/javascripts/diffs/components/tree_list.vue
index 7e00b994541..8fc3af15bea 100644
--- a/app/assets/javascripts/diffs/components/tree_list.vue
+++ b/app/assets/javascripts/diffs/components/tree_list.vue
@@ -13,6 +13,12 @@ export default {
Icon,
FileRow,
},
+ props: {
+ hideFileStats: {
+ type: Boolean,
+ required: true,
+ },
+ },
data() {
return {
search: '',
@@ -40,6 +46,9 @@ export default {
return acc;
}, []);
},
+ fileRowExtraComponent() {
+ return this.hideFileStats ? null : FileRowStats;
+ },
},
methods: {
...mapActions('diffs', ['toggleTreeOpen', 'scrollToFile', 'toggleFileFinder']),
@@ -48,7 +57,6 @@ export default {
},
},
shortcutKeyCharacter: `${/Mac/i.test(navigator.userAgent) ? '&#8984;' : 'Ctrl'}+P`,
- FileRowStats,
diffTreeFiltering: gon.features && gon.features.diffTreeFiltering,
};
</script>
@@ -98,7 +106,7 @@ export default {
:file="file"
:level="0"
:hide-extra-on-tree="true"
- :extra-component="$options.FileRowStats"
+ :extra-component="fileRowExtraComponent"
:show-changed-icon="true"
@toggleTreeOpen="toggleTreeOpen"
@clickFile="scrollToFile"
diff --git a/app/assets/javascripts/diffs/constants.js b/app/assets/javascripts/diffs/constants.js
index bd188d9de9e..7002655ea49 100644
--- a/app/assets/javascripts/diffs/constants.js
+++ b/app/assets/javascripts/diffs/constants.js
@@ -36,3 +36,9 @@ export const MR_TREE_SHOW_KEY = 'mr_tree_show';
export const TREE_TYPE = 'tree';
export const TREE_LIST_STORAGE_KEY = 'mr_diff_tree_list';
export const WHITESPACE_STORAGE_KEY = 'mr_show_whitespace';
+export const TREE_LIST_WIDTH_STORAGE_KEY = 'mr_tree_list_width';
+
+export const INITIAL_TREE_WIDTH = 320;
+export const MIN_TREE_WIDTH = 240;
+export const MAX_TREE_WIDTH = 400;
+export const TREE_HIDE_STATS_WIDTH = 260;
diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js
index 7fb66ce433b..c40775c3259 100644
--- a/app/assets/javascripts/diffs/store/actions.js
+++ b/app/assets/javascripts/diffs/store/actions.js
@@ -16,7 +16,9 @@ import {
MR_TREE_SHOW_KEY,
TREE_LIST_STORAGE_KEY,
WHITESPACE_STORAGE_KEY,
+ TREE_LIST_WIDTH_STORAGE_KEY,
} from '../constants';
+import { diffViewerModes } from '~/ide/constants';
export const setBaseConfig = ({ commit }, options) => {
const { endpoint, projectPath } = options;
@@ -50,7 +52,9 @@ export const fetchDiffFiles = ({ state, commit }) => {
};
export const setHighlightedRow = ({ commit }, lineCode) => {
+ const fileHash = lineCode.split('_')[0];
commit(types.SET_HIGHLIGHTED_ROW, lineCode);
+ commit(types.UPDATE_CURRENT_DIFF_FILE_ID, fileHash);
};
// This is adding line discussions to the actual lines in the diff tree
@@ -91,7 +95,7 @@ export const renderFileForDiscussionId = ({ commit, rootState, state }, discussi
commit(types.RENDER_FILE, file);
}
- if (file.collapsed) {
+ if (file.viewer.collapsed) {
eventHub.$emit(`loadCollapsedDiff/${file.file_hash}`);
scrollToElement(document.getElementById(file.file_hash));
} else {
@@ -105,7 +109,8 @@ export const startRenderDiffsQueue = ({ state, commit }) => {
const checkItem = () =>
new Promise(resolve => {
const nextFile = state.diffFiles.find(
- file => !file.renderIt && (!file.collapsed || !file.text),
+ file =>
+ !file.renderIt && (!file.viewer.collapsed || !file.viewer.name === diffViewerModes.text),
);
if (nextFile) {
@@ -128,6 +133,8 @@ export const startRenderDiffsQueue = ({ state, commit }) => {
return checkItem();
};
+export const setRenderIt = ({ commit }, file) => commit(types.RENDER_FILE, file);
+
export const setInlineDiffViewType = ({ commit }) => {
commit(types.SET_DIFF_VIEW_TYPE, INLINE_DIFF_VIEW_TYPE);
@@ -257,8 +264,6 @@ export const scrollToFile = ({ state, commit }, path) => {
document.location.hash = fileHash;
commit(types.UPDATE_CURRENT_DIFF_FILE_ID, fileHash);
-
- setTimeout(() => commit(types.UPDATE_CURRENT_DIFF_FILE_ID, ''), 1000);
};
export const toggleShowTreeList = ({ commit, state }) => {
@@ -300,5 +305,9 @@ export const toggleFileFinder = ({ commit }, visible) => {
commit(types.TOGGLE_FILE_FINDER_VISIBLE, visible);
};
+export const cacheTreeListWidth = (_, size) => {
+ localStorage.setItem(TREE_LIST_WIDTH_STORAGE_KEY, size);
+};
+
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/diffs/store/getters.js b/app/assets/javascripts/diffs/store/getters.js
index 0e1ad654a2b..bc27e263bff 100644
--- a/app/assets/javascripts/diffs/store/getters.js
+++ b/app/assets/javascripts/diffs/store/getters.js
@@ -4,7 +4,8 @@ export const isParallelView = state => state.diffViewType === PARALLEL_DIFF_VIEW
export const isInlineView = state => state.diffViewType === INLINE_DIFF_VIEW_TYPE;
-export const hasCollapsedFile = state => state.diffFiles.some(file => file.collapsed);
+export const hasCollapsedFile = state =>
+ state.diffFiles.some(file => file.viewer && file.viewer.collapsed);
export const commitId = state => (state.commit && state.commit.id ? state.commit.id : null);
@@ -99,5 +100,12 @@ export const diffFilesLength = state => state.diffFiles.length;
export const getCommentFormForDiffFile = state => fileHash =>
state.commentForms.find(form => form.fileHash === fileHash);
+/**
+ * Returns index of a currently selected diff in diffFiles
+ * @returns {number}
+ */
+export const currentDiffIndex = state =>
+ Math.max(0, state.diffFiles.findIndex(diff => diff.file_hash === state.currentDiffFileId));
+
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js
index 7bbafe66199..5a27388863c 100644
--- a/app/assets/javascripts/diffs/store/mutations.js
+++ b/app/assets/javascripts/diffs/store/mutations.js
@@ -144,6 +144,7 @@ export default {
if (left || right) {
return {
+ ...line,
left: line.left ? mapDiscussions(line.left) : null,
right: line.right ? mapDiscussions(line.right, () => !left) : null,
};
diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js
index effb6202327..247d1e65fea 100644
--- a/app/assets/javascripts/diffs/store/utils.js
+++ b/app/assets/javascripts/diffs/store/utils.js
@@ -1,6 +1,6 @@
import _ from 'underscore';
-import { diffModes } from '~/ide/constants';
import { truncatePathMiddleToLength } from '~/lib/utils/text_utility';
+import { diffModes, diffViewerModes } from '~/ide/constants';
import {
LINE_POSITION_LEFT,
LINE_POSITION_RIGHT,
@@ -161,6 +161,7 @@ export function addContextLines(options) {
const normalizedParallelLines = contextLines.map(line => ({
left: line,
right: line,
+ line_code: line.line_code,
}));
if (options.bottom) {
@@ -247,7 +248,8 @@ export function prepareDiffData(diffData) {
Object.assign(file, {
renderIt: showingLines < LINES_TO_BE_RENDERED_DIRECTLY,
- collapsed: file.text && showingLines > MAX_LINES_TO_BE_RENDERED,
+ collapsed:
+ file.viewer.name === diffViewerModes.text && showingLines > MAX_LINES_TO_BE_RENDERED,
discussions: [],
});
}
@@ -403,7 +405,9 @@ export const getDiffMode = diffFile => {
const diffModeKey = Object.keys(diffModes).find(key => diffFile[`${key}_file`]);
return (
diffModes[diffModeKey] ||
- (diffFile.mode_changed && diffModes.mode_changed) ||
+ (diffFile.viewer &&
+ diffFile.viewer.name === diffViewerModes.mode_changed &&
+ diffViewerModes.mode_changed) ||
diffModes.replaced
);
};
diff --git a/app/assets/javascripts/emoji/no_emoji_validator.js b/app/assets/javascripts/emoji/no_emoji_validator.js
new file mode 100644
index 00000000000..0fd4dd74953
--- /dev/null
+++ b/app/assets/javascripts/emoji/no_emoji_validator.js
@@ -0,0 +1,63 @@
+import { __ } from '~/locale';
+import emojiRegex from 'emoji-regex';
+
+const invalidInputClass = 'gl-field-error-outline';
+
+export default class NoEmojiValidator {
+ constructor(opts = {}) {
+ const container = opts.container || '';
+ this.noEmojiEmelents = document.querySelectorAll(`${container} .js-block-emoji`);
+
+ this.noEmojiEmelents.forEach(element =>
+ element.addEventListener('input', this.eventHandler.bind(this)),
+ );
+ }
+
+ eventHandler(event) {
+ this.inputDomElement = event.target;
+ this.inputErrorMessage = this.inputDomElement.nextSibling;
+
+ const { value } = this.inputDomElement;
+
+ this.validatePattern(value);
+ this.setValidationStateAndMessage();
+ }
+
+ validatePattern(value) {
+ const pattern = emojiRegex();
+ this.hasEmojis = new RegExp(pattern).test(value);
+
+ if (this.hasEmojis) {
+ this.inputDomElement.setCustomValidity(__('Invalid input, please avoid emojis'));
+ } else {
+ this.inputDomElement.setCustomValidity('');
+ }
+ }
+
+ setValidationStateAndMessage() {
+ if (!this.inputDomElement.checkValidity()) {
+ this.setInvalidState();
+ } else {
+ this.clearFieldValidationState();
+ }
+ }
+
+ clearFieldValidationState() {
+ this.inputDomElement.classList.remove(invalidInputClass);
+ this.inputErrorMessage.classList.add('hide');
+ }
+
+ setInvalidState() {
+ this.inputDomElement.classList.add(invalidInputClass);
+ this.setErrorMessage();
+ }
+
+ setErrorMessage() {
+ if (this.hasEmojis) {
+ this.inputErrorMessage.innerHTML = this.inputDomElement.validationMessage;
+ } else {
+ this.inputErrorMessage.innerHTML = this.inputDomElement.title;
+ }
+ this.inputErrorMessage.classList.remove('hide');
+ }
+}
diff --git a/app/assets/javascripts/environments/components/environments_table.vue b/app/assets/javascripts/environments/components/environments_table.vue
index e2c304de00a..eef141a07ba 100644
--- a/app/assets/javascripts/environments/components/environments_table.vue
+++ b/app/assets/javascripts/environments/components/environments_table.vue
@@ -3,6 +3,7 @@
* Render environments table.
*/
import { GlLoadingIcon } from '@gitlab/ui';
+import _ from 'underscore';
import environmentItem from './environment_item.vue';
export default {
@@ -24,6 +25,15 @@ export default {
default: false,
},
},
+ computed: {
+ sortedEnvironments() {
+ return this.sortEnvironments(this.environments).map(env =>
+ this.shouldRenderFolderContent(env)
+ ? { ...env, children: this.sortEnvironments(env.children) }
+ : env,
+ );
+ },
+ },
methods: {
folderUrl(model) {
return `${window.location.pathname}/folders/${model.folderName}`;
@@ -31,6 +41,30 @@ export default {
shouldRenderFolderContent(env) {
return env.isFolder && env.isOpen && env.children && env.children.length > 0;
},
+ sortEnvironments(environments) {
+ /*
+ * The sorting algorithm should sort in the following priorities:
+ *
+ * 1. folders first,
+ * 2. last updated descending,
+ * 3. by name ascending,
+ *
+ * the sorting algorithm must:
+ *
+ * 1. Sort by name ascending,
+ * 2. Reverse (sort by name descending),
+ * 3. Sort by last deployment ascending,
+ * 4. Reverse (last deployment descending, name ascending),
+ * 5. Put folders first.
+ */
+ return _.chain(environments)
+ .sortBy(env => (env.isFolder ? env.folderName : env.name))
+ .reverse()
+ .sortBy(env => (env.last_deployment ? env.last_deployment.created_at : '0000'))
+ .reverse()
+ .sortBy(env => (env.isFolder ? -1 : 1))
+ .value();
+ },
},
};
</script>
@@ -53,7 +87,7 @@ export default {
{{ s__('Environments|Updated') }}
</div>
</div>
- <template v-for="(model, i) in environments" :model="model">
+ <template v-for="(model, i) in sortedEnvironments" :model="model">
<div
is="environment-item"
:key="`environment-item-${i}`"
diff --git a/app/assets/javascripts/environments/mixins/environments_mixin.js b/app/assets/javascripts/environments/mixins/environments_mixin.js
index e81a1525df0..9d83840c87c 100644
--- a/app/assets/javascripts/environments/mixins/environments_mixin.js
+++ b/app/assets/javascripts/environments/mixins/environments_mixin.js
@@ -43,7 +43,11 @@ export default {
saveData(resp) {
this.isLoading = false;
- if (_.isEqual(resp.config.params, this.requestData)) {
+ // Prevent the absence of the nested flag from causing mismatches
+ const response = this.filterNilValues(resp.config.params);
+ const request = this.filterNilValues(this.requestData);
+
+ if (_.isEqual(response, request)) {
this.store.storeAvailableCount(resp.data.available_count);
this.store.storeStoppedCount(resp.data.stopped_count);
this.store.storeEnvironments(resp.data.environments);
@@ -51,6 +55,10 @@ export default {
}
},
+ filterNilValues(obj) {
+ return _.omit(obj, value => _.isUndefined(value) || _.isNull(value));
+ },
+
/**
* Handles URL and query parameter changes.
* When the user uses the pagination or the tabs,
@@ -64,10 +72,9 @@ export default {
// fetch new data
return this.service
.fetchEnvironments(this.requestData)
- .then(response => this.successCallback(response))
- .then(() => {
- // restart polling
- this.poll.restart({ data: this.requestData });
+ .then(response => {
+ this.successCallback(response);
+ this.poll.enable({ data: this.requestData, response });
})
.catch(() => {
this.errorCallback();
diff --git a/app/assets/javascripts/error_tracking/store/actions.js b/app/assets/javascripts/error_tracking/store/actions.js
index 2e192c958ba..11aec312368 100644
--- a/app/assets/javascripts/error_tracking/store/actions.js
+++ b/app/assets/javascripts/error_tracking/store/actions.js
@@ -2,7 +2,7 @@ import Service from '../services';
import * as types from './mutation_types';
import createFlash from '~/flash';
import Poll from '~/lib/utils/poll';
-import { __ } from '~/locale';
+import { __, sprintf } from '~/locale';
let eTagPoll;
@@ -19,9 +19,17 @@ export function startPolling({ commit }, endpoint) {
commit(types.SET_EXTERNAL_URL, data.external_url);
commit(types.SET_LOADING, false);
},
- errorCallback: () => {
+ errorCallback: response => {
+ let errorMessage = '';
+ if (response && response.data && response.data.message) {
+ errorMessage = response.data.message;
+ }
commit(types.SET_LOADING, false);
- createFlash(__('Failed to load errors from Sentry'));
+ createFlash(
+ sprintf(__(`Failed to load errors from Sentry. Error message: %{errorMessage}`), {
+ errorMessage,
+ }),
+ );
},
});
diff --git a/app/assets/javascripts/error_tracking_settings/components/app.vue b/app/assets/javascripts/error_tracking_settings/components/app.vue
new file mode 100644
index 00000000000..50eb3e63b7c
--- /dev/null
+++ b/app/assets/javascripts/error_tracking_settings/components/app.vue
@@ -0,0 +1,129 @@
+<script>
+import { mapActions, mapGetters, mapState } from 'vuex';
+import { GlButton } from '@gitlab/ui';
+import ProjectDropdown from './project_dropdown.vue';
+import ErrorTrackingForm from './error_tracking_form.vue';
+
+export default {
+ components: { ProjectDropdown, ErrorTrackingForm, GlButton },
+ props: {
+ initialApiHost: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ initialEnabled: {
+ type: String,
+ required: true,
+ },
+ initialProject: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ initialToken: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ listProjectsEndpoint: {
+ type: String,
+ required: true,
+ },
+ operationsSettingsEndpoint: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ ...mapGetters([
+ 'dropdownLabel',
+ 'hasProjects',
+ 'invalidProjectLabel',
+ 'isProjectInvalid',
+ 'projectSelectionLabel',
+ ]),
+ ...mapState([
+ 'apiHost',
+ 'connectError',
+ 'connectSuccessful',
+ 'enabled',
+ 'projects',
+ 'selectedProject',
+ 'settingsLoading',
+ 'token',
+ ]),
+ },
+ created() {
+ this.setInitialState({
+ apiHost: this.initialApiHost,
+ enabled: this.initialEnabled,
+ project: this.initialProject,
+ token: this.initialToken,
+ listProjectsEndpoint: this.listProjectsEndpoint,
+ operationsSettingsEndpoint: this.operationsSettingsEndpoint,
+ });
+ },
+ methods: {
+ ...mapActions([
+ 'fetchProjects',
+ 'setInitialState',
+ 'updateApiHost',
+ 'updateEnabled',
+ 'updateSelectedProject',
+ 'updateSettings',
+ 'updateToken',
+ ]),
+ handleSubmit() {
+ this.updateSettings();
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <div class="form-check form-group">
+ <input
+ id="error-tracking-enabled"
+ :checked="enabled"
+ class="form-check-input"
+ type="checkbox"
+ @change="updateEnabled($event.target.checked)"
+ />
+ <label class="form-check-label" for="error-tracking-enabled">{{
+ s__('ErrorTracking|Active')
+ }}</label>
+ </div>
+ <error-tracking-form
+ :api-host="apiHost"
+ :connect-error="connectError"
+ :connect-successful="connectSuccessful"
+ :token="token"
+ @handle-connect="fetchProjects"
+ @update-api-host="updateApiHost"
+ @update-token="updateToken"
+ />
+ <div class="form-group">
+ <project-dropdown
+ :has-projects="hasProjects"
+ :invalid-project-label="invalidProjectLabel"
+ :is-project-invalid="isProjectInvalid"
+ :dropdown-label="dropdownLabel"
+ :project-selection-label="projectSelectionLabel"
+ :projects="projects"
+ :selected-project="selectedProject"
+ :token="token"
+ @select-project="updateSelectedProject"
+ />
+ </div>
+ <gl-button
+ :disabled="settingsLoading"
+ class="js-error-tracking-button"
+ variant="success"
+ @click="handleSubmit"
+ >
+ {{ __('Save changes') }}
+ </gl-button>
+ </div>
+</template>
diff --git a/app/assets/javascripts/error_tracking_settings/components/error_tracking_form.vue b/app/assets/javascripts/error_tracking_settings/components/error_tracking_form.vue
new file mode 100644
index 00000000000..060d8e25227
--- /dev/null
+++ b/app/assets/javascripts/error_tracking_settings/components/error_tracking_form.vue
@@ -0,0 +1,91 @@
+<script>
+import { GlButton, GlFormInput } from '@gitlab/ui';
+import Icon from '~/vue_shared/components/icon.vue';
+
+export default {
+ components: { GlButton, GlFormInput, Icon },
+ props: {
+ apiHost: {
+ type: String,
+ required: true,
+ },
+ connectError: {
+ type: Boolean,
+ required: true,
+ },
+ connectSuccessful: {
+ type: Boolean,
+ required: true,
+ },
+ token: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ tokenInputState() {
+ return this.connectError ? false : null;
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <div class="form-group">
+ <label class="label-bold" for="error-tracking-api-host">{{ __('Sentry API URL') }}</label>
+ <div class="row">
+ <div class="col-8 col-md-9 gl-pr-0">
+ <gl-form-input
+ id="error-tracking-api-host"
+ :value="apiHost"
+ placeholder="https://mysentryserver.com"
+ @input="$emit('update-api-host', $event)"
+ />
+ </div>
+ </div>
+ <p class="form-text text-muted">
+ {{ s__('ErrorTracking|Find your hostname in your Sentry account settings page') }}
+ </p>
+ </div>
+ <div class="form-group" :class="{ 'gl-show-field-errors': connectError }">
+ <label class="label-bold" for="error-tracking-token">{{
+ s__('ErrorTracking|Auth Token')
+ }}</label>
+ <div class="row">
+ <div class="col-8 col-md-9 gl-pr-0">
+ <gl-form-input
+ id="error-tracking-token"
+ :value="token"
+ :state="tokenInputState"
+ @input="$emit('update-token', $event)"
+ />
+ </div>
+ <div class="col-4 col-md-3 gl-pl-0">
+ <gl-button
+ class="js-error-tracking-connect prepend-left-5"
+ @click="$emit('handle-connect')"
+ >
+ {{ __('Connect') }}
+ </gl-button>
+ <icon
+ v-show="connectSuccessful"
+ class="js-error-tracking-connect-success prepend-left-5 text-success align-middle"
+ :aria-label="__('Projects Successfully Retrieved')"
+ name="check-circle"
+ />
+ </div>
+ </div>
+ <p v-if="connectError" class="gl-field-error">
+ {{ s__('ErrorTracking|Connection has failed. Re-check Auth Token and try again.') }}
+ </p>
+ <p v-else class="form-text text-muted">
+ {{
+ s__(
+ "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects",
+ )
+ }}
+ </p>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/error_tracking_settings/components/project_dropdown.vue b/app/assets/javascripts/error_tracking_settings/components/project_dropdown.vue
new file mode 100644
index 00000000000..82df02afafd
--- /dev/null
+++ b/app/assets/javascripts/error_tracking_settings/components/project_dropdown.vue
@@ -0,0 +1,82 @@
+<script>
+import { GlDropdown, GlDropdownHeader, GlDropdownItem } from '@gitlab/ui';
+import Icon from '~/vue_shared/components/icon.vue';
+import { getDisplayName } from '../utils';
+
+export default {
+ components: {
+ GlDropdown,
+ GlDropdownHeader,
+ GlDropdownItem,
+ Icon,
+ },
+ props: {
+ dropdownLabel: {
+ type: String,
+ required: true,
+ },
+ hasProjects: {
+ type: Boolean,
+ required: true,
+ },
+ invalidProjectLabel: {
+ type: String,
+ required: true,
+ },
+ isProjectInvalid: {
+ type: Boolean,
+ required: true,
+ },
+ projects: {
+ type: Array,
+ required: true,
+ },
+ selectedProject: {
+ type: Object,
+ required: false,
+ default: null,
+ },
+ projectSelectionLabel: {
+ type: String,
+ required: true,
+ },
+ token: {
+ type: String,
+ required: true,
+ },
+ },
+ methods: {
+ getDisplayName,
+ },
+};
+</script>
+
+<template>
+ <div :class="{ 'gl-show-field-errors': isProjectInvalid }">
+ <label class="label-bold" for="project-dropdown">{{ __('Project') }}</label>
+ <div class="row">
+ <gl-dropdown
+ id="project-dropdown"
+ class="col-8 col-md-9 gl-pr-0"
+ :disabled="!hasProjects"
+ menu-class="w-100 mw-100"
+ toggle-class="dropdown-menu-toggle w-100 gl-field-error-outline"
+ :text="dropdownLabel"
+ >
+ <gl-dropdown-item
+ v-for="project in projects"
+ :key="`${project.organizationSlug}.${project.slug}`"
+ class="w-100"
+ @click="$emit('select-project', project)"
+ >{{ getDisplayName(project) }}</gl-dropdown-item
+ >
+ </gl-dropdown>
+ </div>
+ <p v-if="isProjectInvalid" class="js-project-dropdown-error gl-field-error">
+ {{ invalidProjectLabel }}
+ </p>
+ <p v-else-if="!hasProjects" class="js-project-dropdown-label form-text text-muted">
+ {{ projectSelectionLabel }}
+ </p>
+ </div>
+</template>
diff --git a/app/assets/javascripts/error_tracking_settings/index.js b/app/assets/javascripts/error_tracking_settings/index.js
new file mode 100644
index 00000000000..ce315963723
--- /dev/null
+++ b/app/assets/javascripts/error_tracking_settings/index.js
@@ -0,0 +1,27 @@
+import Vue from 'vue';
+import ErrorTrackingSettings from './components/app.vue';
+import createStore from './store';
+
+export default () => {
+ const formContainerEl = document.querySelector('.js-error-tracking-form');
+ const {
+ dataset: { apiHost, enabled, project, token, listProjectsEndpoint, operationsSettingsEndpoint },
+ } = formContainerEl;
+
+ return new Vue({
+ el: formContainerEl,
+ store: createStore(),
+ render(createElement) {
+ return createElement(ErrorTrackingSettings, {
+ props: {
+ initialApiHost: apiHost,
+ initialEnabled: enabled,
+ initialProject: project,
+ initialToken: token,
+ listProjectsEndpoint,
+ operationsSettingsEndpoint,
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/error_tracking_settings/store/actions.js b/app/assets/javascripts/error_tracking_settings/store/actions.js
new file mode 100644
index 00000000000..95105797807
--- /dev/null
+++ b/app/assets/javascripts/error_tracking_settings/store/actions.js
@@ -0,0 +1,91 @@
+import { __ } from '~/locale';
+import axios from '~/lib/utils/axios_utils';
+import { refreshCurrentPage } from '~/lib/utils/url_utility';
+import createFlash from '~/flash';
+import { transformFrontendSettings } from '../utils';
+import * as types from './mutation_types';
+
+export const requestProjects = ({ commit }) => {
+ commit(types.RESET_CONNECT);
+};
+
+export const receiveProjectsSuccess = ({ commit }, projects) => {
+ commit(types.UPDATE_CONNECT_SUCCESS);
+ commit(types.RECEIVE_PROJECTS, projects);
+};
+
+export const receiveProjectsError = ({ commit }) => {
+ commit(types.UPDATE_CONNECT_ERROR);
+ commit(types.CLEAR_PROJECTS);
+};
+
+export const fetchProjects = ({ dispatch, state }) => {
+ dispatch('requestProjects');
+ return axios
+ .post(state.listProjectsEndpoint, {
+ error_tracking_setting: {
+ api_host: state.apiHost,
+ token: state.token,
+ },
+ })
+ .then(({ data: { projects } }) => {
+ dispatch('receiveProjectsSuccess', projects);
+ })
+ .catch(() => {
+ dispatch('receiveProjectsError');
+ });
+};
+
+export const requestSettings = ({ commit }) => {
+ commit(types.UPDATE_SETTINGS_LOADING, true);
+};
+
+export const receiveSettingsError = ({ commit }, { response = {} }) => {
+ const message = response.data && response.data.message ? response.data.message : '';
+
+ createFlash(`${__('There was an error saving your changes.')} ${message}`, 'alert');
+ commit(types.UPDATE_SETTINGS_LOADING, false);
+};
+
+export const updateSettings = ({ dispatch, state }) => {
+ dispatch('requestSettings');
+ return axios
+ .patch(state.operationsSettingsEndpoint, {
+ project: {
+ error_tracking_setting_attributes: {
+ ...transformFrontendSettings(state),
+ },
+ },
+ })
+ .then(() => {
+ refreshCurrentPage();
+ })
+ .catch(err => {
+ dispatch('receiveSettingsError', err);
+ });
+};
+
+export const updateApiHost = ({ commit }, apiHost) => {
+ commit(types.UPDATE_API_HOST, apiHost);
+ commit(types.RESET_CONNECT);
+};
+
+export const updateEnabled = ({ commit }, enabled) => {
+ commit(types.UPDATE_ENABLED, enabled);
+};
+
+export const updateToken = ({ commit }, token) => {
+ commit(types.UPDATE_TOKEN, token);
+ commit(types.RESET_CONNECT);
+};
+
+export const updateSelectedProject = ({ commit }, selectedProject) => {
+ commit(types.UPDATE_SELECTED_PROJECT, selectedProject);
+};
+
+export const setInitialState = ({ commit }, data) => {
+ commit(types.SET_INITIAL_STATE, data);
+};
+
+// prevent babel-plugin-rewire from generating an invalid default during karma tests
+export default () => {};
diff --git a/app/assets/javascripts/error_tracking_settings/store/getters.js b/app/assets/javascripts/error_tracking_settings/store/getters.js
new file mode 100644
index 00000000000..a008b181907
--- /dev/null
+++ b/app/assets/javascripts/error_tracking_settings/store/getters.js
@@ -0,0 +1,44 @@
+import _ from 'underscore';
+import { __, s__, sprintf } from '~/locale';
+import { getDisplayName } from '../utils';
+
+export const hasProjects = state => !!state.projects && state.projects.length > 0;
+
+export const isProjectInvalid = (state, getters) =>
+ !!state.selectedProject &&
+ getters.hasProjects &&
+ !state.projects.some(project => _.isMatch(state.selectedProject, project));
+
+export const dropdownLabel = (state, getters) => {
+ if (state.selectedProject !== null) {
+ return getDisplayName(state.selectedProject);
+ }
+ if (!getters.hasProjects) {
+ return s__('ErrorTracking|No projects available');
+ }
+ return s__('ErrorTracking|Select project');
+};
+
+export const invalidProjectLabel = state => {
+ if (state.selectedProject) {
+ return sprintf(
+ __('Project "%{name}" is no longer available. Select another project to continue.'),
+ {
+ name: state.selectedProject.name,
+ },
+ );
+ }
+ return '';
+};
+
+export const projectSelectionLabel = state => {
+ if (state.token) {
+ return s__(
+ "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown.",
+ );
+ }
+ return s__('ErrorTracking|To enable project selection, enter a valid Auth Token');
+};
+
+// prevent babel-plugin-rewire from generating an invalid default during karma tests
+export default () => {};
diff --git a/app/assets/javascripts/error_tracking_settings/store/index.js b/app/assets/javascripts/error_tracking_settings/store/index.js
new file mode 100644
index 00000000000..560f265a2ea
--- /dev/null
+++ b/app/assets/javascripts/error_tracking_settings/store/index.js
@@ -0,0 +1,16 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import createState from './state';
+import * as actions from './actions';
+import * as getters from './getters';
+import mutations from './mutations';
+
+Vue.use(Vuex);
+
+export default () =>
+ new Vuex.Store({
+ state: createState(),
+ actions,
+ getters,
+ mutations,
+ });
diff --git a/app/assets/javascripts/error_tracking_settings/store/mutation_types.js b/app/assets/javascripts/error_tracking_settings/store/mutation_types.js
new file mode 100644
index 00000000000..b4f8a237947
--- /dev/null
+++ b/app/assets/javascripts/error_tracking_settings/store/mutation_types.js
@@ -0,0 +1,11 @@
+export const CLEAR_PROJECTS = 'CLEAR_PROJECTS';
+export const SET_INITIAL_STATE = 'SET_INITIAL_STATE';
+export const RECEIVE_PROJECTS = 'RECEIVE_PROJECTS';
+export const RESET_CONNECT = 'RESET_CONNECT';
+export const UPDATE_API_HOST = 'UPDATE_API_HOST';
+export const UPDATE_CONNECT_ERROR = 'UPDATE_CONNECT_ERROR';
+export const UPDATE_CONNECT_SUCCESS = 'UPDATE_CONNECT_SUCCESS';
+export const UPDATE_ENABLED = 'UPDATE_ENABLED';
+export const UPDATE_SELECTED_PROJECT = 'UPDATE_SELECTED_PROJECT';
+export const UPDATE_SETTINGS_LOADING = 'UPDATE_SETTINGS_LOADING';
+export const UPDATE_TOKEN = 'UPDATE_TOKEN';
diff --git a/app/assets/javascripts/error_tracking_settings/store/mutations.js b/app/assets/javascripts/error_tracking_settings/store/mutations.js
new file mode 100644
index 00000000000..4089d1ee94e
--- /dev/null
+++ b/app/assets/javascripts/error_tracking_settings/store/mutations.js
@@ -0,0 +1,61 @@
+import _ from 'underscore';
+import { convertObjectPropsToCamelCase, parseBoolean } from '~/lib/utils/common_utils';
+import * as types from './mutation_types';
+import { projectKeys } from '../utils';
+
+export default {
+ [types.CLEAR_PROJECTS](state) {
+ state.projects = [];
+ },
+ [types.RECEIVE_PROJECTS](state, projects) {
+ state.projects = projects
+ .map(convertObjectPropsToCamelCase)
+ // The `pick` strips out extra properties returned from Sentry.
+ // Such properties could be problematic later, e.g. when checking whether `projects` contains `selectedProject`
+ .map(project => _.pick(project, projectKeys));
+ },
+ [types.RESET_CONNECT](state) {
+ state.connectSuccessful = false;
+ state.connectError = false;
+ },
+ [types.SET_INITIAL_STATE](
+ state,
+ { apiHost, enabled, project, token, listProjectsEndpoint, operationsSettingsEndpoint },
+ ) {
+ state.enabled = parseBoolean(enabled);
+ state.apiHost = apiHost;
+ state.token = token;
+ state.listProjectsEndpoint = listProjectsEndpoint;
+ state.operationsSettingsEndpoint = operationsSettingsEndpoint;
+
+ if (project) {
+ state.selectedProject = _.pick(
+ convertObjectPropsToCamelCase(JSON.parse(project)),
+ projectKeys,
+ );
+ }
+ },
+ [types.UPDATE_API_HOST](state, apiHost) {
+ state.apiHost = apiHost;
+ },
+ [types.UPDATE_ENABLED](state, enabled) {
+ state.enabled = enabled;
+ },
+ [types.UPDATE_TOKEN](state, token) {
+ state.token = token;
+ },
+ [types.UPDATE_SELECTED_PROJECT](state, selectedProject) {
+ state.selectedProject = selectedProject;
+ },
+ [types.UPDATE_SETTINGS_LOADING](state, settingsLoading) {
+ state.settingsLoading = settingsLoading;
+ },
+ [types.UPDATE_CONNECT_SUCCESS](state) {
+ state.connectSuccessful = true;
+ state.connectError = false;
+ },
+ [types.UPDATE_CONNECT_ERROR](state) {
+ state.connectSuccessful = false;
+ state.connectError = true;
+ },
+};
diff --git a/app/assets/javascripts/error_tracking_settings/store/state.js b/app/assets/javascripts/error_tracking_settings/store/state.js
new file mode 100644
index 00000000000..98219d33f4d
--- /dev/null
+++ b/app/assets/javascripts/error_tracking_settings/store/state.js
@@ -0,0 +1,12 @@
+export default () => ({
+ apiHost: '',
+ enabled: false,
+ token: '',
+ projects: [],
+ selectedProject: null,
+ settingsLoading: false,
+ connectSuccessful: false,
+ connectError: false,
+ listProjectsEndpoint: '',
+ operationsSettingsEndpoint: '',
+});
diff --git a/app/assets/javascripts/error_tracking_settings/utils.js b/app/assets/javascripts/error_tracking_settings/utils.js
new file mode 100644
index 00000000000..6613e04ee0e
--- /dev/null
+++ b/app/assets/javascripts/error_tracking_settings/utils.js
@@ -0,0 +1,18 @@
+export const projectKeys = ['name', 'organizationName', 'organizationSlug', 'slug'];
+
+export const transformFrontendSettings = ({ apiHost, enabled, token, selectedProject }) => {
+ const project = selectedProject
+ ? {
+ slug: selectedProject.slug,
+ name: selectedProject.name,
+ organization_name: selectedProject.organizationName,
+ organization_slug: selectedProject.organizationSlug,
+ }
+ : null;
+
+ return { api_host: apiHost || null, enabled, token: token || null, project };
+};
+
+export const getDisplayName = project => `${project.organizationName} | ${project.name}`;
+
+export default () => {};
diff --git a/app/assets/javascripts/filtered_search/admin_runners_filtered_search_token_keys.js b/app/assets/javascripts/filtered_search/admin_runners_filtered_search_token_keys.js
index 934375023ba..691d165c585 100644
--- a/app/assets/javascripts/filtered_search/admin_runners_filtered_search_token_keys.js
+++ b/app/assets/javascripts/filtered_search/admin_runners_filtered_search_token_keys.js
@@ -17,6 +17,14 @@ const tokenKeys = [
icon: 'cube',
tag: 'type',
},
+ {
+ key: 'tag',
+ type: 'array',
+ param: 'name[]',
+ symbol: '~',
+ icon: 'tag',
+ tag: '~tag',
+ },
];
const AdminRunnersFilteredSearchTokenKeys = new FilteredSearchTokenKeys(tokenKeys);
diff --git a/app/assets/javascripts/filtered_search/dropdown_ajax_filter.js b/app/assets/javascripts/filtered_search/dropdown_ajax_filter.js
new file mode 100644
index 00000000000..b27bb63c220
--- /dev/null
+++ b/app/assets/javascripts/filtered_search/dropdown_ajax_filter.js
@@ -0,0 +1,68 @@
+import createFlash from '../flash';
+import AjaxFilter from '../droplab/plugins/ajax_filter';
+import FilteredSearchDropdown from './filtered_search_dropdown';
+import DropdownUtils from './dropdown_utils';
+import FilteredSearchTokenizer from './filtered_search_tokenizer';
+import { __ } from '~/locale';
+
+export default class DropdownAjaxFilter extends FilteredSearchDropdown {
+ constructor(options = {}) {
+ const { tokenKeys, endpoint, symbol } = options;
+
+ super(options);
+
+ this.tokenKeys = tokenKeys;
+ this.endpoint = endpoint;
+ this.symbol = symbol;
+
+ this.config = {
+ AjaxFilter: this.ajaxFilterConfig(),
+ };
+ }
+
+ ajaxFilterConfig() {
+ return {
+ endpoint: `${gon.relative_url_root || ''}${this.endpoint}`,
+ searchKey: 'search',
+ searchValueFunction: this.getSearchInput.bind(this),
+ loadingTemplate: this.loadingTemplate,
+ onError() {
+ createFlash(__('An error occurred fetching the dropdown data.'));
+ },
+ };
+ }
+
+ itemClicked(e) {
+ super.itemClicked(e, selected =>
+ selected.querySelector('.dropdown-light-content').innerText.trim(),
+ );
+ }
+
+ renderContent(forceShowList = false) {
+ this.droplab.changeHookList(this.hookId, this.dropdown, [AjaxFilter], this.config);
+ super.renderContent(forceShowList);
+ }
+
+ getSearchInput() {
+ const query = DropdownUtils.getSearchInput(this.input);
+ const { lastToken } = FilteredSearchTokenizer.processTokens(query, this.tokenKeys.get());
+
+ let value = lastToken || '';
+
+ if (value[0] === this.symbol) {
+ value = value.slice(1);
+ }
+
+ // Removes the first character if it is a quotation so that we can search
+ // with multiple words
+ if (value[0] === '"' || value[0] === "'") {
+ value = value.slice(1);
+ }
+
+ return value;
+ }
+
+ init() {
+ this.droplab.addHook(this.input, this.dropdown, [AjaxFilter], this.config).init();
+ }
+}
diff --git a/app/assets/javascripts/filtered_search/dropdown_user.js b/app/assets/javascripts/filtered_search/dropdown_user.js
index d5027590bb7..f1e7be6bde1 100644
--- a/app/assets/javascripts/filtered_search/dropdown_user.js
+++ b/app/assets/javascripts/filtered_search/dropdown_user.js
@@ -1,54 +1,34 @@
-import Flash from '../flash';
-import AjaxFilter from '../droplab/plugins/ajax_filter';
-import FilteredSearchDropdown from './filtered_search_dropdown';
import { addClassIfElementExists } from '../lib/utils/dom_utils';
-import DropdownUtils from './dropdown_utils';
-import FilteredSearchTokenizer from './filtered_search_tokenizer';
+import DropdownAjaxFilter from './dropdown_ajax_filter';
-export default class DropdownUser extends FilteredSearchDropdown {
+export default class DropdownUser extends DropdownAjaxFilter {
constructor(options = {}) {
- const { tokenKeys } = options;
- super(options);
- this.config = {
- AjaxFilter: {
- endpoint: `${gon.relative_url_root || ''}/autocomplete/users.json`,
- searchKey: 'search',
- params: {
- active: true,
- group_id: this.getGroupId(),
- project_id: this.getProjectId(),
- current_user: true,
- },
- searchValueFunction: this.getSearchInput.bind(this),
- loadingTemplate: this.loadingTemplate,
- onLoadingFinished: () => {
- this.hideCurrentUser();
- },
- onError() {
- /* eslint-disable no-new */
- new Flash('An error occurred fetching the dropdown data.');
- /* eslint-enable no-new */
- },
+ super({
+ ...options,
+ endpoint: '/autocomplete/users.json',
+ symbol: '@',
+ });
+ }
+
+ ajaxFilterConfig() {
+ return {
+ ...super.ajaxFilterConfig(),
+ params: {
+ active: true,
+ group_id: this.getGroupId(),
+ project_id: this.getProjectId(),
+ current_user: true,
+ },
+ onLoadingFinished: () => {
+ this.hideCurrentUser();
},
};
- this.tokenKeys = tokenKeys;
}
hideCurrentUser() {
addClassIfElementExists(this.dropdown.querySelector('.js-current-user'), 'hidden');
}
- itemClicked(e) {
- super.itemClicked(e, selected =>
- selected.querySelector('.dropdown-light-content').innerText.trim(),
- );
- }
-
- renderContent(forceShowList = false) {
- this.droplab.changeHookList(this.hookId, this.dropdown, [AjaxFilter], this.config);
- super.renderContent(forceShowList);
- }
-
getGroupId() {
return this.input.getAttribute('data-group-id');
}
@@ -56,27 +36,4 @@ export default class DropdownUser extends FilteredSearchDropdown {
getProjectId() {
return this.input.getAttribute('data-project-id');
}
-
- getSearchInput() {
- const query = DropdownUtils.getSearchInput(this.input);
- const { lastToken } = FilteredSearchTokenizer.processTokens(query, this.tokenKeys.get());
-
- let value = lastToken || '';
-
- if (value[0] === '@') {
- value = value.slice(1);
- }
-
- // Removes the first character if it is a quotation so that we can search
- // with multiple words
- if (value[0] === '"' || value[0] === "'") {
- value = value.slice(1);
- }
-
- return value;
- }
-
- init() {
- this.droplab.addHook(this.input, this.dropdown, [AjaxFilter], this.config).init();
- }
}
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
index 57ec6603d80..57847d4ad9f 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
@@ -7,6 +7,7 @@ import DropdownHint from './dropdown_hint';
import DropdownEmoji from './dropdown_emoji';
import DropdownNonUser from './dropdown_non_user';
import DropdownUser from './dropdown_user';
+import DropdownAjaxFilter from './dropdown_ajax_filter';
import NullDropdown from './null_dropdown';
import FilteredSearchVisualTokens from './filtered_search_visual_tokens';
@@ -96,6 +97,11 @@ export default class FilteredSearchDropdownManager {
gl: DropdownNonUser,
element: this.container.querySelector('#js-dropdown-wip'),
},
+ confidential: {
+ reference: null,
+ gl: DropdownNonUser,
+ element: this.container.querySelector('#js-dropdown-confidential'),
+ },
status: {
reference: null,
gl: NullDropdown,
@@ -106,6 +112,15 @@ export default class FilteredSearchDropdownManager {
gl: NullDropdown,
element: this.container.querySelector('#js-dropdown-admin-runner-type'),
},
+ tag: {
+ reference: null,
+ gl: DropdownAjaxFilter,
+ extraArguments: {
+ endpoint: this.getRunnerTagsEndpoint(),
+ symbol: '~',
+ },
+ element: this.container.querySelector('#js-dropdown-runner-tag'),
+ },
};
supportedTokens.forEach(type => {
@@ -141,6 +156,10 @@ export default class FilteredSearchDropdownManager {
return endpoint;
}
+ getRunnerTagsEndpoint() {
+ return `${this.baseEndpoint}/admin/runners/tag_list.json`;
+ }
+
static addWordToInput(tokenName, tokenValue = '', clicked = false, options = {}) {
const { uppercaseTokenName = false, capitalizeTokenValue = false } = options;
const input = FilteredSearchContainer.container.querySelector('.filtered-search');
diff --git a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js
index b70da240833..48534bdf815 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js
@@ -72,6 +72,23 @@ export default class FilteredSearchTokenKeys {
);
}
+ addExtraTokensForIssues() {
+ const confidentialToken = {
+ key: 'confidential',
+ type: 'string',
+ param: '',
+ symbol: '',
+ icon: 'eye-slash',
+ tag: 'Yes or No',
+ lowercaseValueOnSubmit: true,
+ uppercaseTokenName: false,
+ capitalizeTokenValue: true,
+ };
+
+ this.tokenKeys.push(confidentialToken);
+ this.tokenKeysWithAlternative.push(confidentialToken);
+ }
+
addExtraTokensForMergeRequests() {
const wipToken = {
key: 'wip',
diff --git a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
index fba31f16d65..5090b0bdc3c 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
@@ -163,7 +163,7 @@ export default class FilteredSearchVisualTokens {
const tokenValueElement = tokenValueContainer.querySelector('.value');
tokenValueElement.innerText = tokenValue;
- if (tokenValue === 'none' || tokenValue === 'any') {
+ if (['none', 'any'].includes(tokenValue.toLowerCase())) {
return;
}
diff --git a/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue b/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue
index 2cbc7c7077b..42d14b65b3a 100644
--- a/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue
+++ b/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue
@@ -82,8 +82,14 @@ export default {
<li class="frequent-items-list-item-container">
<a :href="webUrl" class="clearfix">
<div class="frequent-items-item-avatar-container">
- <img v-if="hasAvatar" :src="avatarUrl" class="avatar s32" />
- <identicon v-else :entity-id="itemId" :entity-name="itemName" size-class="s32" />
+ <img v-if="hasAvatar" :src="avatarUrl" class="avatar rect-avatar s32" />
+ <identicon
+ v-else
+ :entity-id="itemId"
+ :entity-name="itemName"
+ size-class="s32"
+ class="rect-avatar"
+ />
</div>
<div class="frequent-items-item-metadata-container">
<div :title="itemName" class="frequent-items-item-title" v-html="highlightedItemName"></div>
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index 570d3b712e0..c81e754df4c 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -195,11 +195,15 @@ class GfmAutoComplete {
title += ` (${m.count})`;
}
+ const GROUP_TYPE = 'Group';
+
const autoCompleteAvatar = m.avatar_url || m.username.charAt(0).toUpperCase();
+
+ const rectAvatarClass = m.type === GROUP_TYPE ? 'rect-avatar' : '';
const imgAvatar = `<img src="${m.avatar_url}" alt="${
m.username
- }" class="avatar avatar-inline center s26"/>`;
- const txtAvatar = `<div class="avatar center avatar-inline s26">${autoCompleteAvatar}</div>`;
+ }" class="avatar ${rectAvatarClass} avatar-inline center s26"/>`;
+ const txtAvatar = `<div class="avatar ${rectAvatarClass} center avatar-inline s26">${autoCompleteAvatar}</div>`;
return {
username: m.username,
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index a8ac2f510a4..27d8669b256 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -656,23 +656,7 @@ GitLabDropdown = (function() {
if (this.options.renderMenu) {
return this.options.renderMenu(html);
} else {
- var ul = document.createElement('ul');
-
- for (var i = 0; i < html.length; i += 1) {
- var el = html[i];
-
- if (el instanceof $) {
- el = el.get(0);
- }
-
- if (typeof el === 'string') {
- ul.innerHTML += el;
- } else {
- ul.appendChild(el);
- }
- }
-
- return ul;
+ return $('<ul>').append(html);
}
};
diff --git a/app/assets/javascripts/groups/components/group_item.vue b/app/assets/javascripts/groups/components/group_item.vue
index 688bd37cc56..d5130cd331d 100644
--- a/app/assets/javascripts/groups/components/group_item.vue
+++ b/app/assets/javascripts/groups/components/group_item.vue
@@ -88,7 +88,7 @@ export default {
</div>
<div
:class="{ 'content-loading': group.isChildrenLoading }"
- class="avatar-container s24 d-none d-sm-flex"
+ class="avatar-container rect-avatar s24 d-none d-sm-flex"
>
<a :href="group.relativePath" class="no-expand">
<img v-if="hasAvatar" :src="group.avatarUrl" class="avatar s24" />
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/editor_header.vue b/app/assets/javascripts/ide/components/commit_sidebar/editor_header.vue
index 5119dbf32eb..11d5d9639b6 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/editor_header.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/editor_header.vue
@@ -44,7 +44,7 @@ export default {
<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" class="ml-0" />
+ <changed-file-icon :file="activeFile" :is-centered="false" />
<div class="ml-auto">
<button
v-if="!isStaged"
diff --git a/app/assets/javascripts/ide/components/new_dropdown/modal.vue b/app/assets/javascripts/ide/components/new_dropdown/modal.vue
index 04ecd4ba4e7..c9c4e9e86f8 100644
--- a/app/assets/javascripts/ide/components/new_dropdown/modal.vue
+++ b/app/assets/javascripts/ide/components/new_dropdown/modal.vue
@@ -51,8 +51,11 @@ export default {
return __('Create file');
},
- isCreatingNew() {
- return this.entryModal.type !== modalTypes.rename;
+ isCreatingNewFile() {
+ return this.entryModal.type === 'blob';
+ },
+ placeholder() {
+ return this.isCreatingNewFile ? 'dir/file_name' : 'dir/';
},
},
methods: {
@@ -107,9 +110,12 @@ export default {
v-model="entryName"
type="text"
class="form-control qa-full-file-path"
- placeholder="/dir/file_name"
+ :placeholder="placeholder"
/>
- <ul v-if="isCreatingNew" class="prepend-top-default list-inline qa-template-list">
+ <ul
+ v-if="isCreatingNewFile"
+ class="file-templates prepend-top-default list-inline qa-template-list"
+ >
<li v-for="(template, index) in templateTypes" :key="index" class="list-inline-item">
<button
type="button"
diff --git a/app/assets/javascripts/ide/constants.js b/app/assets/javascripts/ide/constants.js
index 804ebae4555..7c560c89695 100644
--- a/app/assets/javascripts/ide/constants.js
+++ b/app/assets/javascripts/ide/constants.js
@@ -24,6 +24,22 @@ export const diffModes = {
mode_changed: 'mode_changed',
};
+export const diffViewerModes = Object.freeze({
+ not_diffable: 'not_diffable',
+ no_preview: 'no_preview',
+ added: 'added',
+ deleted: 'deleted',
+ renamed: 'renamed',
+ mode_changed: 'mode_changed',
+ text: 'text',
+ image: 'image',
+});
+
+export const diffViewerErrors = Object.freeze({
+ too_large: 'too_large',
+ stored_externally: 'server_side_but_stored_externally',
+});
+
export const rightSidebarViews = {
pipelines: { name: 'pipelines-list', keepAlive: true },
jobsDetail: { name: 'jobs-detail', keepAlive: false },
diff --git a/app/assets/javascripts/import_projects/components/import_projects_table.vue b/app/assets/javascripts/import_projects/components/import_projects_table.vue
new file mode 100644
index 00000000000..777f8fa6691
--- /dev/null
+++ b/app/assets/javascripts/import_projects/components/import_projects_table.vue
@@ -0,0 +1,101 @@
+<script>
+import { mapActions, mapState, mapGetters } from 'vuex';
+import { GlLoadingIcon } from '@gitlab/ui';
+import LoadingButton from '~/vue_shared/components/loading_button.vue';
+import { __, sprintf } from '~/locale';
+import ImportedProjectTableRow from './imported_project_table_row.vue';
+import ProviderRepoTableRow from './provider_repo_table_row.vue';
+import eventHub from '../event_hub';
+
+export default {
+ name: 'ImportProjectsTable',
+ components: {
+ ImportedProjectTableRow,
+ ProviderRepoTableRow,
+ LoadingButton,
+ GlLoadingIcon,
+ },
+ props: {
+ providerTitle: {
+ type: String,
+ required: true,
+ },
+ },
+
+ computed: {
+ ...mapState(['importedProjects', 'providerRepos', 'isLoadingRepos']),
+ ...mapGetters(['isImportingAnyRepo', 'hasProviderRepos', 'hasImportedProjects']),
+
+ emptyStateText() {
+ return sprintf(__('No %{providerTitle} repositories available to import'), {
+ providerTitle: this.providerTitle,
+ });
+ },
+
+ fromHeaderText() {
+ return sprintf(__('From %{providerTitle}'), { providerTitle: this.providerTitle });
+ },
+ },
+
+ mounted() {
+ return this.fetchRepos();
+ },
+
+ beforeDestroy() {
+ this.stopJobsPolling();
+ this.clearJobsEtagPoll();
+ },
+
+ methods: {
+ ...mapActions(['fetchRepos', 'fetchJobs', 'stopJobsPolling', 'clearJobsEtagPoll']),
+
+ importAll() {
+ eventHub.$emit('importAll');
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <div class="d-flex justify-content-between align-items-end flex-wrap mb-3">
+ <p class="light text-nowrap mt-2 my-sm-0">
+ {{ s__('ImportProjects|Select the projects you want to import') }}
+ </p>
+ <loading-button
+ container-class="btn btn-success js-import-all"
+ :loading="isImportingAnyRepo"
+ :label="__('Import all repositories')"
+ :disabled="!hasProviderRepos"
+ type="button"
+ @click="importAll"
+ />
+ </div>
+ <gl-loading-icon
+ v-if="isLoadingRepos"
+ class="js-loading-button-icon import-projects-loading-icon"
+ :size="4"
+ />
+ <div v-else-if="hasProviderRepos || hasImportedProjects" class="table-responsive">
+ <table class="table import-table">
+ <thead>
+ <th class="import-jobs-from-col">{{ fromHeaderText }}</th>
+ <th class="import-jobs-to-col">{{ __('To GitLab') }}</th>
+ <th class="import-jobs-status-col">{{ __('Status') }}</th>
+ <th class="import-jobs-cta-col"></th>
+ </thead>
+ <tbody>
+ <imported-project-table-row
+ v-for="project in importedProjects"
+ :key="project.id"
+ :project="project"
+ />
+ <provider-repo-table-row v-for="repo in providerRepos" :key="repo.id" :repo="repo" />
+ </tbody>
+ </table>
+ </div>
+ <div v-else class="text-center">
+ <strong>{{ emptyStateText }}</strong>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/import_projects/components/import_status.vue b/app/assets/javascripts/import_projects/components/import_status.vue
new file mode 100644
index 00000000000..9e3347a657f
--- /dev/null
+++ b/app/assets/javascripts/import_projects/components/import_status.vue
@@ -0,0 +1,47 @@
+<script>
+import { GlLoadingIcon } from '@gitlab/ui';
+import CiIcon from '~/vue_shared/components/ci_icon.vue';
+import STATUS_MAP from '../constants';
+
+export default {
+ name: 'ImportStatus',
+ components: {
+ CiIcon,
+ GlLoadingIcon,
+ },
+ props: {
+ status: {
+ type: String,
+ required: true,
+ },
+ },
+
+ computed: {
+ mappedStatus() {
+ return STATUS_MAP[this.status];
+ },
+
+ ciIconStatus() {
+ const { icon } = this.mappedStatus;
+
+ return {
+ icon: `status_${icon}`,
+ group: icon,
+ };
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <gl-loading-icon
+ v-if="mappedStatus.loadingIcon"
+ :inline="true"
+ :class="mappedStatus.textClass"
+ class="align-middle mr-2"
+ />
+ <ci-icon v-else css-classes="align-middle mr-2" :status="ciIconStatus" />
+ <span :class="mappedStatus.textClass">{{ mappedStatus.text }}</span>
+ </div>
+</template>
diff --git a/app/assets/javascripts/import_projects/components/imported_project_table_row.vue b/app/assets/javascripts/import_projects/components/imported_project_table_row.vue
new file mode 100644
index 00000000000..ab2bd87ee9f
--- /dev/null
+++ b/app/assets/javascripts/import_projects/components/imported_project_table_row.vue
@@ -0,0 +1,55 @@
+<script>
+import ImportStatus from './import_status.vue';
+import { STATUSES } from '../constants';
+
+export default {
+ name: 'ImportedProjectTableRow',
+ components: {
+ ImportStatus,
+ },
+ props: {
+ project: {
+ type: Object,
+ required: true,
+ },
+ },
+
+ computed: {
+ displayFullPath() {
+ return this.project.fullPath.replace(/^\//, '');
+ },
+
+ isFinished() {
+ return this.project.importStatus === STATUSES.FINISHED;
+ },
+ },
+};
+</script>
+
+<template>
+ <tr class="js-imported-project import-row">
+ <td>
+ <a
+ :href="project.providerLink"
+ rel="noreferrer noopener"
+ target="_blank"
+ class="js-provider-link"
+ >
+ {{ project.importSource }}
+ </a>
+ </td>
+ <td class="js-full-path">{{ displayFullPath }}</td>
+ <td><import-status :status="project.importStatus" /></td>
+ <td>
+ <a
+ v-if="isFinished"
+ class="btn btn-default js-go-to-project"
+ :href="project.fullPath"
+ rel="noreferrer noopener"
+ target="_blank"
+ >
+ {{ __('Go to project') }}
+ </a>
+ </td>
+ </tr>
+</template>
diff --git a/app/assets/javascripts/import_projects/components/provider_repo_table_row.vue b/app/assets/javascripts/import_projects/components/provider_repo_table_row.vue
new file mode 100644
index 00000000000..7cc29fa1b91
--- /dev/null
+++ b/app/assets/javascripts/import_projects/components/provider_repo_table_row.vue
@@ -0,0 +1,110 @@
+<script>
+import { mapState, mapGetters, mapActions } from 'vuex';
+import Select2Select from '~/vue_shared/components/select2_select.vue';
+import { __ } from '~/locale';
+import LoadingButton from '~/vue_shared/components/loading_button.vue';
+import eventHub from '../event_hub';
+import { STATUSES } from '../constants';
+import ImportStatus from './import_status.vue';
+
+export default {
+ name: 'ProviderRepoTableRow',
+ components: {
+ Select2Select,
+ LoadingButton,
+ ImportStatus,
+ },
+ props: {
+ repo: {
+ type: Object,
+ required: true,
+ },
+ },
+
+ data() {
+ return {
+ targetNamespace: this.$store.state.defaultTargetNamespace,
+ newName: this.repo.sanitizedName,
+ };
+ },
+
+ computed: {
+ ...mapState(['namespaces', 'reposBeingImported', 'ciCdOnly']),
+
+ ...mapGetters(['namespaceSelectOptions']),
+
+ importButtonText() {
+ return this.ciCdOnly ? __('Connect') : __('Import');
+ },
+
+ select2Options() {
+ return {
+ data: this.namespaceSelectOptions,
+ containerCssClass:
+ 'import-namespace-select js-namespace-select qa-project-namespace-select',
+ };
+ },
+
+ isLoadingImport() {
+ return this.reposBeingImported.includes(this.repo.id);
+ },
+
+ status() {
+ return this.isLoadingImport ? STATUSES.SCHEDULING : STATUSES.NONE;
+ },
+ },
+
+ created() {
+ eventHub.$on('importAll', () => this.importRepo());
+ },
+
+ methods: {
+ ...mapActions(['fetchImport']),
+
+ importRepo() {
+ return this.fetchImport({
+ newName: this.newName,
+ targetNamespace: this.targetNamespace,
+ repo: this.repo,
+ });
+ },
+ },
+};
+</script>
+
+<template>
+ <tr class="qa-project-import-row js-provider-repo import-row">
+ <td>
+ <a
+ :href="repo.providerLink"
+ rel="noreferrer noopener"
+ target="_blank"
+ class="js-provider-link"
+ >
+ {{ repo.fullName }}
+ </a>
+ </td>
+ <td class="d-flex flex-wrap flex-lg-nowrap">
+ <select2-select v-model="targetNamespace" :options="select2Options" />
+ <span class="px-2 import-slash-divider d-flex justify-content-center align-items-center"
+ >/</span
+ >
+ <input
+ v-model="newName"
+ type="text"
+ class="form-control import-project-name-input js-new-name qa-project-path-field"
+ />
+ </td>
+ <td><import-status :status="status" /></td>
+ <td>
+ <button
+ v-if="!isLoadingImport"
+ type="button"
+ class="qa-import-button js-import-button btn btn-default"
+ @click="importRepo"
+ >
+ {{ importButtonText }}
+ </button>
+ </td>
+ </tr>
+</template>
diff --git a/app/assets/javascripts/import_projects/constants.js b/app/assets/javascripts/import_projects/constants.js
new file mode 100644
index 00000000000..ad33ca158d2
--- /dev/null
+++ b/app/assets/javascripts/import_projects/constants.js
@@ -0,0 +1,48 @@
+import { __ } from '../locale';
+
+// The `scheduling` status is only present on the client-side,
+// it is used as the status when we are requesting to start an import.
+
+export const STATUSES = {
+ FINISHED: 'finished',
+ FAILED: 'failed',
+ SCHEDULED: 'scheduled',
+ STARTED: 'started',
+ NONE: 'none',
+ SCHEDULING: 'scheduling',
+};
+
+const STATUS_MAP = {
+ [STATUSES.FINISHED]: {
+ icon: 'success',
+ text: __('Done'),
+ textClass: 'text-success',
+ },
+ [STATUSES.FAILED]: {
+ icon: 'failed',
+ text: __('Failed'),
+ textClass: 'text-danger',
+ },
+ [STATUSES.SCHEDULED]: {
+ icon: 'pending',
+ text: __('Scheduled'),
+ textClass: 'text-warning',
+ },
+ [STATUSES.STARTED]: {
+ icon: 'running',
+ text: __('Running…'),
+ textClass: 'text-info',
+ },
+ [STATUSES.NONE]: {
+ icon: 'created',
+ text: __('Not started'),
+ textClass: 'text-muted',
+ },
+ [STATUSES.SCHEDULING]: {
+ loadingIcon: true,
+ text: __('Scheduling'),
+ textClass: 'text-warning',
+ },
+};
+
+export default STATUS_MAP;
diff --git a/app/assets/javascripts/import_projects/event_hub.js b/app/assets/javascripts/import_projects/event_hub.js
new file mode 100644
index 00000000000..0948c2e5352
--- /dev/null
+++ b/app/assets/javascripts/import_projects/event_hub.js
@@ -0,0 +1,3 @@
+import Vue from 'vue';
+
+export default new Vue();
diff --git a/app/assets/javascripts/import_projects/index.js b/app/assets/javascripts/import_projects/index.js
new file mode 100644
index 00000000000..5c77484aee1
--- /dev/null
+++ b/app/assets/javascripts/import_projects/index.js
@@ -0,0 +1,47 @@
+import Vue from 'vue';
+import { mapActions } from 'vuex';
+import Translate from '../vue_shared/translate';
+import ImportProjectsTable from './components/import_projects_table.vue';
+import { parseBoolean } from '../lib/utils/common_utils';
+import store from './store';
+
+Vue.use(Translate);
+
+export default function mountImportProjectsTable(mountElement) {
+ if (!mountElement) return undefined;
+
+ const {
+ reposPath,
+ provider,
+ providerTitle,
+ canSelectNamespace,
+ jobsPath,
+ importPath,
+ ciCdOnly,
+ } = mountElement.dataset;
+
+ return new Vue({
+ el: mountElement,
+ store,
+
+ created() {
+ this.setInitialData({
+ reposPath,
+ provider,
+ jobsPath,
+ importPath,
+ defaultTargetNamespace: gon.current_username,
+ ciCdOnly: parseBoolean(ciCdOnly),
+ canSelectNamespace: parseBoolean(canSelectNamespace),
+ });
+ },
+
+ methods: {
+ ...mapActions(['setInitialData']),
+ },
+
+ render(createElement) {
+ return createElement(ImportProjectsTable, { props: { providerTitle } });
+ },
+ });
+}
diff --git a/app/assets/javascripts/import_projects/store/actions.js b/app/assets/javascripts/import_projects/store/actions.js
new file mode 100644
index 00000000000..c44500937cc
--- /dev/null
+++ b/app/assets/javascripts/import_projects/store/actions.js
@@ -0,0 +1,106 @@
+import Visibility from 'visibilityjs';
+import * as types from './mutation_types';
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+import Poll from '~/lib/utils/poll';
+import createFlash from '~/flash';
+import { s__, sprintf } from '~/locale';
+import axios from '~/lib/utils/axios_utils';
+
+let eTagPoll;
+
+export const clearJobsEtagPoll = () => {
+ eTagPoll = null;
+};
+export const stopJobsPolling = () => {
+ if (eTagPoll) eTagPoll.stop();
+};
+export const restartJobsPolling = () => {
+ if (eTagPoll) eTagPoll.restart();
+};
+
+export const setInitialData = ({ commit }, data) => commit(types.SET_INITIAL_DATA, data);
+
+export const requestRepos = ({ commit }, repos) => commit(types.REQUEST_REPOS, repos);
+export const receiveReposSuccess = ({ commit }, repos) =>
+ commit(types.RECEIVE_REPOS_SUCCESS, repos);
+export const receiveReposError = ({ commit }) => commit(types.RECEIVE_REPOS_ERROR);
+export const fetchRepos = ({ state, dispatch }) => {
+ dispatch('requestRepos');
+
+ return axios
+ .get(state.reposPath)
+ .then(({ data }) =>
+ dispatch('receiveReposSuccess', convertObjectPropsToCamelCase(data, { deep: true })),
+ )
+ .then(() => dispatch('fetchJobs'))
+ .catch(() => {
+ createFlash(
+ sprintf(s__('ImportProjects|Requesting your %{provider} repositories failed'), {
+ provider: state.provider,
+ }),
+ );
+
+ dispatch('receiveReposError');
+ });
+};
+
+export const requestImport = ({ commit, state }, repoId) => {
+ if (!state.reposBeingImported.includes(repoId)) commit(types.REQUEST_IMPORT, repoId);
+};
+export const receiveImportSuccess = ({ commit }, { importedProject, repoId }) =>
+ commit(types.RECEIVE_IMPORT_SUCCESS, { importedProject, repoId });
+export const receiveImportError = ({ commit }, repoId) =>
+ commit(types.RECEIVE_IMPORT_ERROR, repoId);
+export const fetchImport = ({ state, dispatch }, { newName, targetNamespace, repo }) => {
+ dispatch('requestImport', repo.id);
+
+ return axios
+ .post(state.importPath, {
+ ci_cd_only: state.ciCdOnly,
+ new_name: newName,
+ repo_id: repo.id,
+ target_namespace: targetNamespace,
+ })
+ .then(({ data }) =>
+ dispatch('receiveImportSuccess', {
+ importedProject: convertObjectPropsToCamelCase(data, { deep: true }),
+ repoId: repo.id,
+ }),
+ )
+ .catch(() => {
+ createFlash(s__('ImportProjects|Importing the project failed'));
+
+ dispatch('receiveImportError', { repoId: repo.id });
+ });
+};
+
+export const receiveJobsSuccess = ({ commit }, updatedProjects) =>
+ commit(types.RECEIVE_JOBS_SUCCESS, updatedProjects);
+export const fetchJobs = ({ state, dispatch }) => {
+ if (eTagPoll) return;
+
+ eTagPoll = new Poll({
+ resource: {
+ fetchJobs: () => axios.get(state.jobsPath),
+ },
+ method: 'fetchJobs',
+ successCallback: ({ data }) =>
+ dispatch('receiveJobsSuccess', convertObjectPropsToCamelCase(data, { deep: true })),
+ errorCallback: () => createFlash(s__('ImportProjects|Updating the imported projects failed')),
+ });
+
+ if (!Visibility.hidden()) {
+ eTagPoll.makeRequest();
+ }
+
+ Visibility.change(() => {
+ if (!Visibility.hidden()) {
+ dispatch('restartJobsPolling');
+ } else {
+ dispatch('stopJobsPolling');
+ }
+ });
+};
+
+// prevent babel-plugin-rewire from generating an invalid default during karma tests
+export default () => {};
diff --git a/app/assets/javascripts/import_projects/store/getters.js b/app/assets/javascripts/import_projects/store/getters.js
new file mode 100644
index 00000000000..f03474a8404
--- /dev/null
+++ b/app/assets/javascripts/import_projects/store/getters.js
@@ -0,0 +1,20 @@
+export const namespaceSelectOptions = state => {
+ const serializedNamespaces = state.namespaces.map(({ fullPath }) => ({
+ id: fullPath,
+ text: fullPath,
+ }));
+
+ return [
+ { text: 'Groups', children: serializedNamespaces },
+ {
+ text: 'Users',
+ children: [{ id: state.defaultTargetNamespace, text: state.defaultTargetNamespace }],
+ },
+ ];
+};
+
+export const isImportingAnyRepo = state => state.reposBeingImported.length > 0;
+
+export const hasProviderRepos = state => state.providerRepos.length > 0;
+
+export const hasImportedProjects = state => state.importedProjects.length > 0;
diff --git a/app/assets/javascripts/import_projects/store/index.js b/app/assets/javascripts/import_projects/store/index.js
new file mode 100644
index 00000000000..6ac9bfd8189
--- /dev/null
+++ b/app/assets/javascripts/import_projects/store/index.js
@@ -0,0 +1,15 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import state from './state';
+import * as actions from './actions';
+import * as getters from './getters';
+import mutations from './mutations';
+
+Vue.use(Vuex);
+
+export default new Vuex.Store({
+ state: state(),
+ actions,
+ mutations,
+ getters,
+});
diff --git a/app/assets/javascripts/import_projects/store/mutation_types.js b/app/assets/javascripts/import_projects/store/mutation_types.js
new file mode 100644
index 00000000000..6ba3fd6f29e
--- /dev/null
+++ b/app/assets/javascripts/import_projects/store/mutation_types.js
@@ -0,0 +1,11 @@
+export const SET_INITIAL_DATA = 'SET_INITIAL_DATA';
+
+export const REQUEST_REPOS = 'REQUEST_REPOS';
+export const RECEIVE_REPOS_SUCCESS = 'RECEIVE_REPOS_SUCCESS';
+export const RECEIVE_REPOS_ERROR = 'RECEIVE_REPOS_ERROR';
+
+export const REQUEST_IMPORT = 'REQUEST_IMPORT';
+export const RECEIVE_IMPORT_SUCCESS = 'RECEIVE_IMPORT_SUCCESS';
+export const RECEIVE_IMPORT_ERROR = 'RECEIVE_IMPORT_ERROR';
+
+export const RECEIVE_JOBS_SUCCESS = 'RECEIVE_JOBS_SUCCESS';
diff --git a/app/assets/javascripts/import_projects/store/mutations.js b/app/assets/javascripts/import_projects/store/mutations.js
new file mode 100644
index 00000000000..b88de0268e7
--- /dev/null
+++ b/app/assets/javascripts/import_projects/store/mutations.js
@@ -0,0 +1,55 @@
+import Vue from 'vue';
+import * as types from './mutation_types';
+
+export default {
+ [types.SET_INITIAL_DATA](state, data) {
+ Object.assign(state, data);
+ },
+
+ [types.REQUEST_REPOS](state) {
+ state.isLoadingRepos = true;
+ },
+
+ [types.RECEIVE_REPOS_SUCCESS](state, { importedProjects, providerRepos, namespaces }) {
+ state.isLoadingRepos = false;
+
+ state.importedProjects = importedProjects;
+ state.providerRepos = providerRepos;
+ state.namespaces = namespaces;
+ },
+
+ [types.RECEIVE_REPOS_ERROR](state) {
+ state.isLoadingRepos = false;
+ },
+
+ [types.REQUEST_IMPORT](state, repoId) {
+ state.reposBeingImported.push(repoId);
+ },
+
+ [types.RECEIVE_IMPORT_SUCCESS](state, { importedProject, repoId }) {
+ const existingRepoIndex = state.reposBeingImported.indexOf(repoId);
+ if (state.reposBeingImported.includes(repoId))
+ state.reposBeingImported.splice(existingRepoIndex, 1);
+
+ const providerRepoIndex = state.providerRepos.findIndex(
+ providerRepo => providerRepo.id === repoId,
+ );
+ state.providerRepos.splice(providerRepoIndex, 1);
+ state.importedProjects.unshift(importedProject);
+ },
+
+ [types.RECEIVE_IMPORT_ERROR](state, repoId) {
+ const repoIndex = state.reposBeingImported.indexOf(repoId);
+ if (state.reposBeingImported.includes(repoId)) state.reposBeingImported.splice(repoIndex, 1);
+ },
+
+ [types.RECEIVE_JOBS_SUCCESS](state, updatedProjects) {
+ updatedProjects.forEach(updatedProject => {
+ const existingProject = state.importedProjects.find(
+ importedProject => importedProject.id === updatedProject.id,
+ );
+
+ Vue.set(existingProject, 'importStatus', updatedProject.importStatus);
+ });
+ },
+};
diff --git a/app/assets/javascripts/import_projects/store/state.js b/app/assets/javascripts/import_projects/store/state.js
new file mode 100644
index 00000000000..637fef6e53c
--- /dev/null
+++ b/app/assets/javascripts/import_projects/store/state.js
@@ -0,0 +1,15 @@
+export default () => ({
+ reposPath: '',
+ importPath: '',
+ jobsPath: '',
+ currentProjectId: '',
+ provider: '',
+ currentUsername: '',
+ importedProjects: [],
+ providerRepos: [],
+ namespaces: [],
+ reposBeingImported: [],
+ isLoadingRepos: false,
+ canSelectNamespace: false,
+ ciCdOnly: false,
+});
diff --git a/app/assets/javascripts/issuable_suggestions/index.js b/app/assets/javascripts/issuable_suggestions/index.js
index 2c80cf1797a..40916c9d27f 100644
--- a/app/assets/javascripts/issuable_suggestions/index.js
+++ b/app/assets/javascripts/issuable_suggestions/index.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
-import defaultClient from '~/lib/graphql';
+import createDefaultClient from '~/lib/graphql';
import App from './components/app.vue';
Vue.use(VueApollo);
@@ -10,7 +10,7 @@ export default function() {
const issueTitle = document.getElementById('issue_title');
const { projectPath } = el.dataset;
const apolloProvider = new VueApollo({
- defaultClient,
+ defaultClient: createDefaultClient(),
});
return new Vue({
diff --git a/app/assets/javascripts/jobs/components/sidebar.vue b/app/assets/javascripts/jobs/components/sidebar.vue
index a2141dc3760..1691ac62100 100644
--- a/app/assets/javascripts/jobs/components/sidebar.vue
+++ b/app/assets/javascripts/jobs/components/sidebar.vue
@@ -110,7 +110,7 @@ export default {
<div class="sidebar-container">
<div class="blocks-container">
<div class="block d-flex flex-nowrap align-items-center">
- <h4 class="my-0 mr-2">{{ job.name }}</h4>
+ <h4 class="my-0 mr-2 text-break-word">{{ job.name }}</h4>
<div class="flex-grow-1 flex-shrink-0 text-right">
<gl-link
v-if="job.retry_path"
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index f7a611fbca0..cca4927c115 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -199,8 +199,8 @@ export default class LabelsSelect {
.catch(() => flash(__('Error fetching labels.')));
},
renderRow: function(label, instance) {
- var $a,
- $li,
+ var linkEl,
+ listItemEl,
color,
colorEl,
indeterminate,
@@ -209,12 +209,11 @@ export default class LabelsSelect {
spacing,
i,
marked,
- dropdownName,
dropdownValue;
- $li = $('<li>');
- $a = $('<a href="#">');
+
selectedClass = [];
removesAll = label.id <= 0 || label.id == null;
+
if ($dropdown.hasClass('js-filter-bulk-update')) {
indeterminate = $dropdown.data('indeterminate') || [];
marked = $dropdown.data('marked') || [];
@@ -233,7 +232,6 @@ export default class LabelsSelect {
}
} else {
if (this.id(label)) {
- dropdownName = $dropdown.data('fieldName');
dropdownValue = this.id(label)
.toString()
.replace(/'/g, "\\'");
@@ -241,7 +239,7 @@ export default class LabelsSelect {
if (
$form.find(
"input[type='hidden'][name='" +
- dropdownName +
+ this.fieldName +
"'][value='" +
dropdownValue +
"']",
@@ -251,24 +249,34 @@ export default class LabelsSelect {
}
}
- if ($dropdown.hasClass('js-multiselect') && removesAll) {
+ if (this.multiSelect && removesAll) {
selectedClass.push('dropdown-clear-active');
}
}
+
if (label.color) {
colorEl =
"<span class='dropdown-label-box' style='background: " + label.color + "'></span>";
} else {
colorEl = '';
}
+
+ linkEl = document.createElement('a');
+ linkEl.href = '#';
+
// We need to identify which items are actually labels
if (label.id) {
selectedClass.push('label-item');
- $a.attr('data-label-id', label.id);
+ linkEl.dataset.labelId = label.id;
}
- $a.addClass(selectedClass.join(' ')).html(`${colorEl} ${_.escape(label.title)}`);
- // Return generated html
- return $li.html($a).prop('outerHTML');
+
+ linkEl.className = selectedClass.join(' ');
+ linkEl.innerHTML = `${colorEl} ${_.escape(label.title)}`;
+
+ listItemEl = document.createElement('li');
+ listItemEl.appendChild(linkEl);
+
+ return listItemEl;
},
search: {
fields: ['title'],
diff --git a/app/assets/javascripts/lib/graphql.js b/app/assets/javascripts/lib/graphql.js
index 20a0f142d9e..64e4e899f44 100644
--- a/app/assets/javascripts/lib/graphql.js
+++ b/app/assets/javascripts/lib/graphql.js
@@ -1,9 +1,11 @@
import ApolloClient from 'apollo-boost';
import csrf from '~/lib/utils/csrf';
-export default new ApolloClient({
- uri: `${gon.relative_url_root}/api/graphql`,
- headers: {
- [csrf.headerKey]: csrf.token,
- },
-});
+export default (clientState = {}) =>
+ new ApolloClient({
+ uri: `${gon.relative_url_root}/api/graphql`,
+ headers: {
+ [csrf.headerKey]: csrf.token,
+ },
+ clientState,
+ });
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index 0ceff10a02a..a73cdb73690 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -130,7 +130,7 @@ export const isInViewport = (el, offset = {}) => {
rect.top >= (top || 0) &&
rect.left >= (left || 0) &&
rect.bottom <= window.innerHeight &&
- rect.right <= window.innerWidth
+ parseInt(rect.right, 10) <= window.innerWidth
);
};
@@ -456,21 +456,6 @@ export const historyPushState = newUrl => {
export const parseBoolean = value => (value && value.toString()) === 'true';
/**
- * Converts permission provided as strings to booleans.
- *
- * @param {String} string
- * @returns {Boolean}
- */
-export const convertPermissionToBoolean = permission => {
- if (process.env.NODE_ENV !== 'production') {
- // eslint-disable-next-line no-console
- console.warn('convertPermissionToBoolean is deprecated! Please use parseBoolean instead.');
- }
-
- return parseBoolean(permission);
-};
-
-/**
* @callback backOffCallback
* @param {Function} next
* @param {Function} stop
diff --git a/app/assets/javascripts/lib/utils/number_utils.js b/app/assets/javascripts/lib/utils/number_utils.js
index 2ccc51c35f7..19c4de6083d 100644
--- a/app/assets/javascripts/lib/utils/number_utils.js
+++ b/app/assets/javascripts/lib/utils/number_utils.js
@@ -80,3 +80,22 @@ export function numberToHumanSize(size) {
}
return `${bytesToGiB(size).toFixed(2)} GiB`;
}
+
+/**
+ * A simple method that returns the value of a + b
+ * It seems unessesary, but when combined with a reducer it
+ * adds up all the values in an array.
+ *
+ * e.g. `[1, 2, 3, 4, 5].reduce(sum) // => 15`
+ *
+ * @param {Float} a
+ * @param {Float} b
+ * @example
+ * // return 15
+ * [1, 2, 3, 4, 5].reduce(sum);
+ *
+ * // returns 6
+ * Object.values([{a: 1, b: 2, c: 3].reduce(sum);
+ * @returns {Float} The summed value
+ */
+export const sum = (a = 0, b = 0) => a + b;
diff --git a/app/assets/javascripts/lib/utils/poll.js b/app/assets/javascripts/lib/utils/poll.js
index 198711cf427..a900ff34bf5 100644
--- a/app/assets/javascripts/lib/utils/poll.js
+++ b/app/assets/javascripts/lib/utils/poll.js
@@ -63,6 +63,10 @@ export default class Poll {
const headers = normalizeHeaders(response.headers);
const pollInterval = parseInt(headers[this.intervalHeader], 10);
if (pollInterval > 0 && successCodes.indexOf(response.status) !== -1 && this.canPoll) {
+ if (this.timeoutID) {
+ clearTimeout(this.timeoutID);
+ }
+
this.timeoutID = setTimeout(() => {
this.makeRequest();
}, pollInterval);
@@ -101,15 +105,25 @@ export default class Poll {
}
/**
- * Restarts polling after it has been stoped
+ * Enables polling after it has been stopped
*/
- restart(options) {
- // update data
+ enable(options) {
if (options && options.data) {
this.options.data = options.data;
}
this.canPoll = true;
+
+ if (options && options.response) {
+ this.checkConditions(options.response);
+ }
+ }
+
+ /**
+ * Restarts polling after it has been stopped and makes a request
+ */
+ restart(options) {
+ this.enable(options);
this.makeRequest();
}
}
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index 63db4938cd7..1b722c0505a 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -78,7 +78,6 @@ function deferredInitialisation() {
initUserPopovers();
if (document.querySelector('.search')) initSearchAutocomplete();
- if (document.querySelector('#js-peek')) initPerformanceBar({ container: '#js-peek' });
addSelectOnFocusBehaviour('.js-select-on-focus');
@@ -145,6 +144,8 @@ document.addEventListener('DOMContentLoaded', () => {
const $sidebarGutterToggle = $('.js-sidebar-toggle');
let bootstrapBreakpoint = bp.getBreakpointSize();
+ if (document.querySelector('#js-peek')) initPerformanceBar({ container: '#js-peek' });
+
initLayoutNav();
// Set the default path for all cookies to GitLab's root directory
diff --git a/app/assets/javascripts/members.js b/app/assets/javascripts/members.js
index bd263c75a3d..af2697444f2 100644
--- a/app/assets/javascripts/members.js
+++ b/app/assets/javascripts/members.js
@@ -16,25 +16,33 @@ export default class Members {
gl.utils.disableButtonIfEmptyField('#user_ids', 'input[name=commit]', 'change');
}
+ dropdownClicked(options) {
+ this.formSubmit(null, options.$el);
+ }
+
+ // eslint-disable-next-line class-methods-use-this
+ dropdownToggleLabel(selected, $el) {
+ return $el.text();
+ }
+
+ // eslint-disable-next-line class-methods-use-this
+ dropdownIsSelectable(selected, $el) {
+ return !$el.hasClass('is-active');
+ }
+
initGLDropdown() {
$('.js-member-permissions-dropdown').each((i, btn) => {
const $btn = $(btn);
$btn.glDropdown({
selectable: true,
- isSelectable(selected, $el) {
- return !$el.hasClass('is-active');
- },
+ isSelectable: (selected, $el) => this.dropdownIsSelectable(selected, $el),
fieldName: $btn.data('fieldName'),
id(selected, $el) {
return $el.data('id');
},
- toggleLabel(selected, $el) {
- return $el.text();
- },
- clicked: options => {
- this.formSubmit(null, options.$el);
- },
+ toggleLabel: (selected, $el) => this.dropdownToggleLabel(selected, $el, $btn),
+ clicked: options => this.dropdownClicked(options),
});
});
}
@@ -55,6 +63,7 @@ export default class Members {
$toggle.enable();
$dateInput.enable();
}
+
// eslint-disable-next-line class-methods-use-this
getMemberListItems($el) {
const $memberListItem = $el.is('.member') ? $el : $(`#${$el.data('elId')}`);
diff --git a/app/assets/javascripts/monitoring/components/charts/area.vue b/app/assets/javascripts/monitoring/components/charts/area.vue
index 14c02db7bcc..9e031b03579 100644
--- a/app/assets/javascripts/monitoring/components/charts/area.vue
+++ b/app/assets/javascripts/monitoring/components/charts/area.vue
@@ -139,8 +139,7 @@ export default {
return this.graphData.queries.map(query => query.label).join(', ');
},
yAxisLabel() {
- const [query] = this.graphData.queries;
- return `${this.graphData.y_label} (${query.unit})`;
+ return `${this.graphData.y_label}`;
},
},
watch: {
diff --git a/app/assets/javascripts/notes/components/diff_with_note.vue b/app/assets/javascripts/notes/components/diff_with_note.vue
index 376d4114efd..d8947e8ca50 100644
--- a/app/assets/javascripts/notes/components/diff_with_note.vue
+++ b/app/assets/javascripts/notes/components/diff_with_note.vue
@@ -5,6 +5,7 @@ import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue';
import ImageDiffOverlay from '~/diffs/components/image_diff_overlay.vue';
import { GlSkeletonLoading } from '@gitlab/ui';
import { getDiffMode } from '~/diffs/store/utils';
+import { diffViewerModes } from '~/ide/constants';
export default {
components: {
@@ -31,6 +32,12 @@ export default {
diffMode() {
return getDiffMode(this.discussion.diff_file);
},
+ diffViewerMode() {
+ return this.discussion.diff_file.viewer.name;
+ },
+ isTextFile() {
+ return this.diffViewerMode === diffViewerModes.text;
+ },
hasTruncatedDiffLines() {
return (
this.discussion.truncated_diff_lines && this.discussion.truncated_diff_lines.length !== 0
@@ -58,18 +65,14 @@ export default {
</script>
<template>
- <div :class="{ 'text-file': discussion.diff_file.text }" class="diff-file file-holder">
+ <div :class="{ 'text-file': isTextFile }" class="diff-file file-holder">
<diff-file-header
:discussion-path="discussion.discussion_path"
:diff-file="discussion.diff_file"
:can-current-user-fork="false"
- :expanded="!discussion.diff_file.collapsed"
+ :expanded="!discussion.diff_file.viewer.collapsed"
/>
- <div
- v-if="discussion.diff_file.text"
- :class="$options.userColorSchemeClass"
- class="diff-content code"
- >
+ <div v-if="isTextFile" :class="$options.userColorSchemeClass" class="diff-content code">
<table>
<template v-if="hasTruncatedDiffLines">
<tr
@@ -109,6 +112,7 @@ export default {
<div v-else>
<diff-viewer
:diff-mode="diffMode"
+ :diff-viewer-mode="diffViewerMode"
:new-path="discussion.diff_file.new_path"
:new-sha="discussion.diff_file.diff_refs.head_sha"
:old-path="discussion.diff_file.old_path"
diff --git a/app/assets/javascripts/notes/components/discussion_filter.vue b/app/assets/javascripts/notes/components/discussion_filter.vue
index e03d6e9cd02..31164f74201 100644
--- a/app/assets/javascripts/notes/components/discussion_filter.vue
+++ b/app/assets/javascripts/notes/components/discussion_filter.vue
@@ -7,7 +7,9 @@ import {
DISCUSSION_FILTERS_DEFAULT_VALUE,
HISTORY_ONLY_FILTER_VALUE,
DISCUSSION_TAB_LABEL,
+ DISCUSSION_FILTER_TYPES,
} from '../constants';
+import notesEventHub from '../event_hub';
export default {
components: {
@@ -46,6 +48,7 @@ export default {
this.toggleFilters(currentTab);
}
+ notesEventHub.$on('dropdownSelect', this.selectFilter);
window.addEventListener('hashchange', this.handleLocationHash);
this.handleLocationHash();
},
@@ -53,6 +56,7 @@ export default {
this.toggleCommentsForm();
},
destroyed() {
+ notesEventHub.$off('dropdownSelect', this.selectFilter);
window.removeEventListener('hashchange', this.handleLocationHash);
},
methods: {
@@ -86,12 +90,23 @@ export default {
this.setTargetNoteHash(hash);
}
},
+ filterType(value) {
+ if (value === 0) {
+ return DISCUSSION_FILTER_TYPES.ALL;
+ } else if (value === 1) {
+ return DISCUSSION_FILTER_TYPES.COMMENTS;
+ }
+ return DISCUSSION_FILTER_TYPES.HISTORY;
+ },
},
};
</script>
<template>
- <div v-if="displayFilters" class="discussion-filter-container d-inline-block align-bottom">
+ <div
+ v-if="displayFilters"
+ class="discussion-filter-container js-discussion-filter-container d-inline-block align-bottom"
+ >
<button
id="discussion-filter-dropdown"
ref="dropdownToggle"
@@ -102,12 +117,17 @@ export default {
{{ currentFilter.title }} <icon name="chevron-down" />
</button>
<div
+ ref="dropdownMenu"
class="dropdown-menu dropdown-menu-selectable dropdown-menu-right"
aria-labelledby="discussion-filter-dropdown"
>
<div class="dropdown-content">
<ul>
- <li v-for="filter in filters" :key="filter.value">
+ <li
+ v-for="filter in filters"
+ :key="filter.value"
+ :data-filter-type="filterType(filter.value)"
+ >
<button
:class="{ 'is-active': filter.value === currentValue }"
class="qa-filter-options"
diff --git a/app/assets/javascripts/notes/components/discussion_filter_note.vue b/app/assets/javascripts/notes/components/discussion_filter_note.vue
new file mode 100644
index 00000000000..46661e06f6d
--- /dev/null
+++ b/app/assets/javascripts/notes/components/discussion_filter_note.vue
@@ -0,0 +1,52 @@
+<script>
+import { GlButton } from '@gitlab/ui';
+import Icon from '~/vue_shared/components/icon.vue';
+import { __, sprintf } from '~/locale';
+
+import notesEventHub from '../event_hub';
+
+export default {
+ components: {
+ GlButton,
+ Icon,
+ },
+ computed: {
+ timelineContent() {
+ return sprintf(
+ __(
+ "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options.",
+ ),
+ {
+ startTag: `<b>`,
+ endTag: `</b>`,
+ },
+ false,
+ );
+ },
+ },
+ methods: {
+ selectFilter(value) {
+ notesEventHub.$emit('dropdownSelect', value);
+ },
+ },
+};
+</script>
+
+<template>
+ <li class="timeline-entry note note-wrapper discussion-filter-note js-discussion-filter-note">
+ <div class="timeline-icon">
+ <icon name="comment" />
+ </div>
+ <div class="timeline-content">
+ <div v-html="timelineContent"></div>
+ <div class="discussion-filter-actions mt-2">
+ <gl-button variant="default" @click="selectFilter(0)">
+ {{ __('Show all activity') }}
+ </gl-button>
+ <gl-button variant="default" @click="selectFilter(1)">
+ {{ __('Show comments only') }}
+ </gl-button>
+ </div>
+ </div>
+ </li>
+</template>
diff --git a/app/assets/javascripts/notes/components/discussion_resolve_with_issue_button.vue b/app/assets/javascripts/notes/components/discussion_resolve_with_issue_button.vue
new file mode 100644
index 00000000000..e413398696a
--- /dev/null
+++ b/app/assets/javascripts/notes/components/discussion_resolve_with_issue_button.vue
@@ -0,0 +1,34 @@
+<script>
+import Icon from '~/vue_shared/components/icon.vue';
+import { GlTooltipDirective, GlButton } from '@gitlab/ui';
+
+export default {
+ name: 'ResolveWithIssueButton',
+ components: {
+ Icon,
+ GlButton,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ props: {
+ url: {
+ type: String,
+ required: true,
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="btn-group" role="group">
+ <gl-button
+ v-gl-tooltip
+ :href="url"
+ :title="s__('MergeRequests|Resolve this discussion in a new issue')"
+ class="new-issue-for-discussion discussion-create-issue-btn"
+ >
+ <icon name="issue-new" />
+ </gl-button>
+ </div>
+</template>
diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue
index 969d5b69c25..de1ea0f58d6 100644
--- a/app/assets/javascripts/notes/components/note_actions.vue
+++ b/app/assets/javascripts/notes/components/note_actions.vue
@@ -23,11 +23,6 @@ export default {
type: [String, Number],
required: true,
},
- discussionId: {
- type: String,
- required: false,
- default: '',
- },
noteUrl: {
type: String,
required: false,
@@ -176,7 +171,7 @@ export default {
v-if="showReplyButton"
ref="replyButton"
class="js-reply-button"
- :note-id="discussionId"
+ @startReplying="$emit('startReplying')"
/>
<div v-if="canEdit" class="note-actions-item">
<button
diff --git a/app/assets/javascripts/notes/components/note_actions/reply_button.vue b/app/assets/javascripts/notes/components/note_actions/reply_button.vue
index b2f9d7f128a..f50cab81efe 100644
--- a/app/assets/javascripts/notes/components/note_actions/reply_button.vue
+++ b/app/assets/javascripts/notes/components/note_actions/reply_button.vue
@@ -1,5 +1,4 @@
<script>
-import { mapActions } from 'vuex';
import { GlTooltipDirective, GlButton } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
@@ -12,15 +11,6 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
- props: {
- noteId: {
- type: String,
- required: true,
- },
- },
- methods: {
- ...mapActions(['convertToDiscussion']),
- },
};
</script>
@@ -32,7 +22,7 @@ export default {
class="note-action-button"
variant="transparent"
:title="__('Reply to comment')"
- @click="convertToDiscussion(noteId)"
+ @click="$emit('startReplying')"
>
<icon name="comment" css-classes="link-highlight" />
</gl-button>
diff --git a/app/assets/javascripts/notes/components/note_body.vue b/app/assets/javascripts/notes/components/note_body.vue
index ff303d0f55a..fb1d98355b3 100644
--- a/app/assets/javascripts/notes/components/note_body.vue
+++ b/app/assets/javascripts/notes/components/note_body.vue
@@ -95,6 +95,7 @@ export default {
<div ref="note-body" :class="{ 'js-task-list-container': canEdit }" class="note-body">
<suggestions
v-if="hasSuggestion && !isEditing"
+ class="note-text md"
:suggestions="note.suggestions"
:note-html="note.note_html"
:line-type="lineType"
diff --git a/app/assets/javascripts/notes/components/note_header.vue b/app/assets/javascripts/notes/components/note_header.vue
index 7b39901024d..68b753a4abf 100644
--- a/app/assets/javascripts/notes/components/note_header.vue
+++ b/app/assets/javascripts/notes/components/note_header.vue
@@ -69,7 +69,7 @@ export default {
type="button"
@click="handleToggle"
>
- <i :class="toggleChevronClass" class="fa" aria-hidden="true"> </i>
+ <i :class="toggleChevronClass" class="fa" aria-hidden="true"></i>
{{ __('Toggle discussion') }}
</button>
</div>
@@ -81,19 +81,18 @@ export default {
:data-user-id="author.id"
:data-username="author.username"
>
+ <slot name="note-header-info"></slot>
<span class="note-header-author-name">{{ author.name }}</span>
<span v-if="author.status_tooltip_html" v-html="author.status_tooltip_html"></span>
- <span class="note-headline-light"> @{{ author.username }} </span>
+ <span class="note-headline-light">@{{ author.username }}</span>
</a>
- <span v-else> {{ __('A deleted user') }} </span>
+ <span v-else>{{ __('A deleted user') }}</span>
<span class="note-headline-light">
<span class="note-headline-meta">
<span class="system-note-message"> <slot></slot> </span>
<template v-if="createdAt">
<span class="system-note-separator">
- <template v-if="actionText">
- {{ actionText }}
- </template>
+ <template v-if="actionText">{{ actionText }}</template>
</span>
<a
:href="noteTimestampLink"
@@ -107,8 +106,7 @@ export default {
class="fa fa-spinner fa-spin editing-spinner"
aria-label="Comment is being updated"
aria-hidden="true"
- >
- </i>
+ ></i>
</span>
</span>
</div>
diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue
index b7e9f7c2028..fc51998935d 100644
--- a/app/assets/javascripts/notes/components/noteable_discussion.vue
+++ b/app/assets/javascripts/notes/components/noteable_discussion.vue
@@ -6,6 +6,7 @@ import { truncateSha } from '~/lib/utils/text_utility';
import { s__, __, sprintf } from '~/locale';
import systemNote from '~/vue_shared/components/notes/system_note.vue';
import icon from '~/vue_shared/components/icon.vue';
+import diffLineNoteFormMixin from 'ee_else_ce/notes/mixins/diff_line_note_form';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import Flash from '../../flash';
import { SYSTEM_NOTE } from '../constants';
@@ -25,7 +26,9 @@ import noteable from '../mixins/noteable';
import resolvable from '../mixins/resolvable';
import discussionNavigation from '../mixins/discussion_navigation';
import ReplyPlaceholder from './discussion_reply_placeholder.vue';
+import ResolveWithIssueButton from './discussion_resolve_with_issue_button.vue';
import jumpToNextDiscussionButton from './discussion_jump_to_next_button.vue';
+import eventHub from '../event_hub';
export default {
name: 'NoteableDiscussion',
@@ -43,13 +46,15 @@ export default {
ReplyPlaceholder,
placeholderNote,
placeholderSystemNote,
+ ResolveWithIssueButton,
systemNote,
+ DraftNote: () => import('ee_component/batch_comments/components/draft_note.vue'),
TimelineEntryItem,
},
directives: {
GlTooltip: GlTooltipDirective,
},
- mixins: [autosave, noteable, resolvable, discussionNavigation],
+ mixins: [autosave, noteable, resolvable, discussionNavigation, diffLineNoteFormMixin],
props: {
discussion: {
type: Object,
@@ -93,6 +98,7 @@ export default {
},
computed: {
...mapGetters([
+ 'convertedDisscussionIds',
'getNoteableData',
'nextUnresolvedDiscussionId',
'unresolvedDiscussionsCount',
@@ -232,6 +238,9 @@ export default {
url: this.discussion.discussion_path,
};
},
+ resolveWithIssuePath() {
+ return !this.discussionResolved && this.discussion.resolve_with_issue_path;
+ },
},
watch: {
isReplying() {
@@ -245,6 +254,12 @@ export default {
}
},
},
+ created() {
+ eventHub.$on('startReplying', this.onStartReplying);
+ },
+ beforeDestroy() {
+ eventHub.$off('startReplying', this.onStartReplying);
+ },
methods: {
...mapActions([
'saveNote',
@@ -252,6 +267,7 @@ export default {
'removePlaceholderNotes',
'toggleResolveNote',
'expandDiscussion',
+ 'removeConvertedDiscussion',
]),
truncateSha,
componentName(note) {
@@ -291,6 +307,10 @@ export default {
}
}
+ if (this.convertedDisscussionIds.includes(this.discussion.id)) {
+ this.removeConvertedDiscussion(this.discussion.id);
+ }
+
this.isReplying = false;
this.resetAutoSave();
},
@@ -301,6 +321,10 @@ export default {
note: { note: noteText },
};
+ if (this.convertedDisscussionIds.includes(this.discussion.id)) {
+ postData.return_discussion = true;
+ }
+
if (this.discussion.for_commit) {
postData.note_project_id = this.discussion.project_id;
}
@@ -340,6 +364,11 @@ Please check your network connection and try again.`;
deleteNoteHandler(note) {
this.$emit('noteDeleted', this.discussion, note);
},
+ onStartReplying(discussionId) {
+ if (this.discussion.id === discussionId) {
+ this.showReplyForm();
+ }
+ },
},
};
</script>
@@ -358,30 +387,32 @@ Please check your network connection and try again.`;
:img-size="40"
/>
</div>
- <note-header
- :author="author"
- :created-at="initialDiscussion.created_at"
- :note-id="initialDiscussion.id"
- :include-toggle="true"
- :expanded="discussion.expanded"
- @toggleHandler="toggleDiscussionHandler"
- >
- <span v-html="actionText"></span>
- </note-header>
- <note-edited-text
- v-if="discussion.resolved"
- :edited-at="discussion.resolved_at"
- :edited-by="discussion.resolved_by"
- :action-text="resolvedText"
- class-name="discussion-headline-light js-discussion-headline"
- />
- <note-edited-text
- v-else-if="lastUpdatedAt"
- :edited-at="lastUpdatedAt"
- :edited-by="lastUpdatedBy"
- action-text="Last updated"
- class-name="discussion-headline-light js-discussion-headline"
- />
+ <div class="timeline-content">
+ <note-header
+ :author="author"
+ :created-at="initialDiscussion.created_at"
+ :note-id="initialDiscussion.id"
+ :include-toggle="true"
+ :expanded="discussion.expanded"
+ @toggleHandler="toggleDiscussionHandler"
+ >
+ <span v-html="actionText"></span>
+ </note-header>
+ <note-edited-text
+ v-if="discussion.resolved"
+ :edited-at="discussion.resolved_at"
+ :edited-by="discussion.resolved_by"
+ :action-text="resolvedText"
+ class-name="discussion-headline-light js-discussion-headline"
+ />
+ <note-edited-text
+ v-else-if="lastUpdatedAt"
+ :edited-at="lastUpdatedAt"
+ :edited-by="lastUpdatedBy"
+ action-text="Last updated"
+ class-name="discussion-headline-light js-discussion-headline"
+ />
+ </div>
</div>
<div v-if="shouldShowDiscussions" class="discussion-body">
<component
@@ -400,6 +431,7 @@ Please check your network connection and try again.`;
:help-page-path="helpPagePath"
:show-reply-button="canReply"
@handleDeleteNote="deleteNoteHandler"
+ @startReplying="showReplyForm"
>
<note-edited-text
v-if="discussion.resolved"
@@ -462,16 +494,10 @@ Please check your network connection and try again.`;
class="btn-group discussion-actions ml-sm-2"
role="group"
>
- <div v-if="!discussionResolved" class="btn-group" role="group">
- <a
- v-gl-tooltip
- :href="discussion.resolve_with_issue_path"
- :title="s__('MergeRequests|Resolve this discussion in a new issue')"
- class="new-issue-for-discussion btn btn-default discussion-create-issue-btn"
- >
- <icon name="issue-new" />
- </a>
- </div>
+ <resolve-with-issue-button
+ v-if="resolveWithIssuePath"
+ :url="resolveWithIssuePath"
+ />
<jump-to-next-discussion-button
v-if="shouldShowJumpToNextDiscussion"
@onClick="jumpToNextDiscussion"
@@ -486,6 +512,7 @@ Please check your network connection and try again.`;
:is-editing="false"
:line="diffLine"
save-button-title="Comment"
+ @handleFormUpdateAddToReview="addReplyToReview"
@handleFormUpdate="saveReply"
@cancelForm="cancelReplyForm"
/>
diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue
index 56108a58010..5fa0ab3de98 100644
--- a/app/assets/javascripts/notes/components/noteable_note.vue
+++ b/app/assets/javascripts/notes/components/noteable_note.vue
@@ -29,11 +29,6 @@ export default {
type: Object,
required: true,
},
- discussion: {
- type: Object,
- required: false,
- default: null,
- },
line: {
type: Object,
required: false,
@@ -49,6 +44,11 @@ export default {
required: false,
default: () => null,
},
+ showReplyButton: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
return {
@@ -91,13 +91,6 @@ export default {
}
return '';
},
- showReplyButton() {
- if (!this.discussion || !this.getNoteableData.current_user.can_create_note) {
- return false;
- }
-
- return this.discussion.individual_note && !this.commentsDisabled;
- },
actionText() {
if (!this.commit) {
return '';
@@ -226,7 +219,7 @@ export default {
:class="classNameBindings"
:data-award-url="note.toggle_award_path"
:data-note-id="note.id"
- class="note note-wrapper"
+ class="note note-wrapper qa-noteable-note-item"
>
<div v-once class="timeline-icon">
<user-avatar-link
@@ -260,10 +253,10 @@ export default {
:is-resolved="note.resolved"
:is-resolving="isResolving"
:resolved-by="note.resolved_by"
- :discussion-id="discussionId"
@handleEdit="editHandler"
@handleDelete="deleteHandler"
@handleResolve="resolveHandler"
+ @startReplying="$emit('startReplying')"
/>
</div>
<div class="timeline-discussion-body">
diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue
index 6d72b72e628..e2bd59f7631 100644
--- a/app/assets/javascripts/notes/components/notes_app.vue
+++ b/app/assets/javascripts/notes/components/notes_app.vue
@@ -6,6 +6,7 @@ import * as constants from '../constants';
import eventHub from '../event_hub';
import noteableNote from './noteable_note.vue';
import noteableDiscussion from './noteable_discussion.vue';
+import discussionFilterNote from './discussion_filter_note.vue';
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';
@@ -24,6 +25,7 @@ export default {
placeholderNote,
placeholderSystemNote,
skeletonLoadingContainer,
+ discussionFilterNote,
},
props: {
noteableData: {
@@ -60,9 +62,11 @@ export default {
...mapGetters([
'isNotesFetched',
'discussions',
+ 'convertedDisscussionIds',
'getNotesDataByProp',
'isLoading',
'commentsDisabled',
+ 'getNoteableData',
]),
noteableType() {
return this.noteableData.noteableType;
@@ -78,6 +82,9 @@ export default {
return this.discussions;
},
+ canReply() {
+ return this.getNoteableData.current_user.can_create_note && !this.commentsDisabled;
+ },
},
watch: {
shouldShow() {
@@ -85,8 +92,15 @@ export default {
this.fetchNotes();
}
},
+ allDiscussions() {
+ if (this.discussonsCount) {
+ this.discussonsCount.textContent = this.allDiscussions.length;
+ }
+ },
},
created() {
+ this.discussonsCount = document.querySelector('.js-discussions-count');
+
this.setNotesData(this.notesData);
this.setNoteableData(this.noteableData);
this.setUserData(this.userData);
@@ -128,6 +142,7 @@ export default {
'setNotesFetchedState',
'expandDiscussion',
'startTaskList',
+ 'convertToDiscussion',
]),
fetchNotes() {
if (this.isFetching) return null;
@@ -175,6 +190,11 @@ export default {
}
}
},
+ startReplying(discussionId) {
+ return this.convertToDiscussion(discussionId)
+ .then(() => this.$nextTick())
+ .then(() => eventHub.$emit('startReplying', discussionId));
+ },
},
systemNote: constants.SYSTEM_NOTE,
};
@@ -193,7 +213,9 @@ export default {
/>
<placeholder-note v-else :key="discussion.id" :note="discussion.notes[0]" />
</template>
- <template v-else-if="discussion.individual_note">
+ <template
+ v-else-if="discussion.individual_note && !convertedDisscussionIds.includes(discussion.id)"
+ >
<system-note
v-if="discussion.notes[0].system"
:key="discussion.id"
@@ -203,7 +225,8 @@ export default {
v-else
:key="discussion.id"
:note="discussion.notes[0]"
- :discussion="discussion"
+ :show-reply-button="canReply"
+ @startReplying="startReplying(discussion.id)"
/>
</template>
<noteable-discussion
@@ -214,6 +237,7 @@ export default {
:help-page-path="helpPagePath"
/>
</template>
+ <discussion-filter-note v-show="commentsDisabled" />
</ul>
<comment-form v-if="!commentsDisabled" :noteable-type="noteableType" />
diff --git a/app/assets/javascripts/notes/constants.js b/app/assets/javascripts/notes/constants.js
index 78d365fe94b..fba3db8542c 100644
--- a/app/assets/javascripts/notes/constants.js
+++ b/app/assets/javascripts/notes/constants.js
@@ -24,3 +24,9 @@ export const NOTEABLE_TYPE_MAPPING = {
MergeRequest: MERGE_REQUEST_NOTEABLE_TYPE,
Epic: EPIC_NOTEABLE_TYPE,
};
+
+export const DISCUSSION_FILTER_TYPES = {
+ ALL: 'all',
+ COMMENTS: 'comments',
+ HISTORY: 'history',
+};
diff --git a/app/assets/javascripts/notes/mixins/diff_line_note_form.js b/app/assets/javascripts/notes/mixins/diff_line_note_form.js
new file mode 100644
index 00000000000..188556e8921
--- /dev/null
+++ b/app/assets/javascripts/notes/mixins/diff_line_note_form.js
@@ -0,0 +1,10 @@
+export default {
+ computed: {
+ draftForDiscussion: () => () => ({}),
+ },
+ methods: {
+ showDraft: () => false,
+ addReplyToReview: () => {},
+ addToReview: () => {},
+ },
+};
diff --git a/app/assets/javascripts/notes/mixins/resolvable.js b/app/assets/javascripts/notes/mixins/resolvable.js
index 8edf3d088bb..2329727bca2 100644
--- a/app/assets/javascripts/notes/mixins/resolvable.js
+++ b/app/assets/javascripts/notes/mixins/resolvable.js
@@ -31,6 +31,10 @@ export default {
},
methods: {
resolveHandler(resolvedState = false) {
+ if (this.note && this.note.isDraft) {
+ return this.$emit('toggleResolveStatus');
+ }
+
this.isResolving = true;
const isResolved = this.discussionResolved || resolvedState;
const discussion = this.resolveAsThread;
diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js
index ff65f14d529..1a0dba69a7c 100644
--- a/app/assets/javascripts/notes/stores/actions.js
+++ b/app/assets/javascripts/notes/stores/actions.js
@@ -83,12 +83,44 @@ export const updateNote = ({ commit, dispatch }, { endpoint, note }) =>
dispatch('startTaskList');
});
-export const replyToDiscussion = ({ commit }, { endpoint, data }) =>
+export const updateOrCreateNotes = ({ commit, state, getters, dispatch }, notes) => {
+ const { notesById } = getters;
+
+ notes.forEach(note => {
+ if (notesById[note.id]) {
+ commit(types.UPDATE_NOTE, note);
+ } else if (note.type === constants.DISCUSSION_NOTE || note.type === constants.DIFF_NOTE) {
+ const discussion = utils.findNoteObjectById(state.discussions, note.discussion_id);
+
+ if (discussion) {
+ commit(types.ADD_NEW_REPLY_TO_DISCUSSION, note);
+ } else if (note.type === constants.DIFF_NOTE) {
+ dispatch('fetchDiscussions', { path: state.notesData.discussionsPath });
+ } else {
+ commit(types.ADD_NEW_NOTE, note);
+ }
+ } else {
+ commit(types.ADD_NEW_NOTE, note);
+ }
+ });
+};
+
+export const replyToDiscussion = ({ commit, state, getters, dispatch }, { endpoint, data }) =>
service
.replyToDiscussion(endpoint, data)
.then(res => res.json())
.then(res => {
- commit(types.ADD_NEW_REPLY_TO_DISCUSSION, res);
+ if (res.discussion) {
+ commit(types.UPDATE_DISCUSSION, res.discussion);
+
+ updateOrCreateNotes({ commit, state, getters, dispatch }, res.discussion.notes);
+
+ dispatch('updateMergeRequestWidget');
+ dispatch('startTaskList');
+ dispatch('updateResolvableDiscussonsCounts');
+ } else {
+ commit(types.ADD_NEW_REPLY_TO_DISCUSSION, res);
+ }
return res;
});
@@ -262,25 +294,7 @@ export const saveNote = ({ commit, dispatch }, noteData) => {
const pollSuccessCallBack = (resp, commit, state, getters, dispatch) => {
if (resp.notes && resp.notes.length) {
- const { notesById } = getters;
-
- resp.notes.forEach(note => {
- if (notesById[note.id]) {
- commit(types.UPDATE_NOTE, note);
- } else if (note.type === constants.DISCUSSION_NOTE || note.type === constants.DIFF_NOTE) {
- const discussion = utils.findNoteObjectById(state.discussions, note.discussion_id);
-
- if (discussion) {
- commit(types.ADD_NEW_REPLY_TO_DISCUSSION, note);
- } else if (note.type === constants.DIFF_NOTE) {
- dispatch('fetchDiscussions', { path: state.notesData.discussionsPath });
- } else {
- commit(types.ADD_NEW_NOTE, note);
- }
- } else {
- commit(types.ADD_NEW_NOTE, note);
- }
- });
+ updateOrCreateNotes({ commit, state, getters, dispatch }, resp.notes);
dispatch('startTaskList');
}
@@ -429,5 +443,8 @@ export const submitSuggestion = (
export const convertToDiscussion = ({ commit }, noteId) =>
commit(types.CONVERT_TO_DISCUSSION, noteId);
+export const removeConvertedDiscussion = ({ commit }, noteId) =>
+ commit(types.REMOVE_CONVERTED_DISCUSSION, noteId);
+
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js
index 0ffc0cb2593..fcc8889b0c7 100644
--- a/app/assets/javascripts/notes/stores/getters.js
+++ b/app/assets/javascripts/notes/stores/getters.js
@@ -4,6 +4,8 @@ import { collapseSystemNotes } from './collapse_utils';
export const discussions = state => collapseSystemNotes(state.discussions);
+export const convertedDisscussionIds = state => state.convertedDisscussionIds;
+
export const targetNoteHash = state => state.targetNoteHash;
export const getNotesData = state => state.notesData;
@@ -189,6 +191,9 @@ export const firstUnresolvedDiscussionId = (state, getters) => diffOrder => {
return getters.unresolvedDiscussionsIdsByDate[0];
};
+export const getDiscussion = state => discussionId =>
+ state.discussions.find(discussion => discussion.id === discussionId);
+
export const commentsDisabled = state => state.commentsDisabled;
// prevent babel-plugin-rewire from generating an invalid default during karma tests
diff --git a/app/assets/javascripts/notes/stores/modules/index.js b/app/assets/javascripts/notes/stores/modules/index.js
index 887e6d22b06..6168aeae35d 100644
--- a/app/assets/javascripts/notes/stores/modules/index.js
+++ b/app/assets/javascripts/notes/stores/modules/index.js
@@ -5,6 +5,7 @@ import mutations from '../mutations';
export default () => ({
state: {
discussions: [],
+ convertedDisscussionIds: [],
targetNoteHash: null,
lastFetchedAt: null,
diff --git a/app/assets/javascripts/notes/stores/mutation_types.js b/app/assets/javascripts/notes/stores/mutation_types.js
index 2bffedad336..796370920bb 100644
--- a/app/assets/javascripts/notes/stores/mutation_types.js
+++ b/app/assets/javascripts/notes/stores/mutation_types.js
@@ -18,6 +18,7 @@ export const SET_NOTES_LOADING_STATE = 'SET_NOTES_LOADING_STATE';
export const DISABLE_COMMENTS = 'DISABLE_COMMENTS';
export const APPLY_SUGGESTION = 'APPLY_SUGGESTION';
export const CONVERT_TO_DISCUSSION = 'CONVERT_TO_DISCUSSION';
+export const REMOVE_CONVERTED_DISCUSSION = 'REMOVE_CONVERTED_DISCUSSION';
// DISCUSSION
export const COLLAPSE_DISCUSSION = 'COLLAPSE_DISCUSSION';
diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js
index d167f8ef421..ae6f8b7790a 100644
--- a/app/assets/javascripts/notes/stores/mutations.js
+++ b/app/assets/javascripts/notes/stores/mutations.js
@@ -266,7 +266,14 @@ export default {
},
[types.CONVERT_TO_DISCUSSION](state, discussionId) {
- const discussion = utils.findNoteObjectById(state.discussions, discussionId);
- Object.assign(discussion, { individual_note: false });
+ const convertedDisscussionIds = [...state.convertedDisscussionIds, discussionId];
+ Object.assign(state, { convertedDisscussionIds });
+ },
+
+ [types.REMOVE_CONVERTED_DISCUSSION](state, discussionId) {
+ const convertedDisscussionIds = [...state.convertedDisscussionIds];
+
+ convertedDisscussionIds.splice(convertedDisscussionIds.indexOf(discussionId), 1);
+ Object.assign(state, { convertedDisscussionIds });
},
};
diff --git a/app/assets/javascripts/pages/groups/group_members/index/index.js b/app/assets/javascripts/pages/groups/group_members/index/index.js
index c22a164cd4e..e4f4c3b574e 100644
--- a/app/assets/javascripts/pages/groups/group_members/index/index.js
+++ b/app/assets/javascripts/pages/groups/group_members/index/index.js
@@ -1,7 +1,7 @@
/* eslint-disable no-new */
import memberExpirationDate from '~/member_expiration_date';
-import Members from '~/members';
+import Members from 'ee_else_ce/members';
import UsersSelect from '~/users_select';
document.addEventListener('DOMContentLoaded', () => {
diff --git a/app/assets/javascripts/pages/groups/issues/index.js b/app/assets/javascripts/pages/groups/issues/index.js
index 736c6a62610..35d4b034654 100644
--- a/app/assets/javascripts/pages/groups/issues/index.js
+++ b/app/assets/javascripts/pages/groups/issues/index.js
@@ -1,9 +1,11 @@
import projectSelect from '~/project_select';
import initFilteredSearch from '~/pages/search/init_filtered_search';
import { FILTERED_SEARCH } from '~/pages/constants';
-import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
+import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys';
document.addEventListener('DOMContentLoaded', () => {
+ IssuableFilteredSearchTokenKeys.addExtraTokensForIssues();
+
initFilteredSearch({
page: FILTERED_SEARCH.ISSUES,
isGroupDecendent: true,
diff --git a/app/assets/javascripts/pages/import/gitea/status/index.js b/app/assets/javascripts/pages/import/gitea/status/index.js
new file mode 100644
index 00000000000..dcd84f0faf9
--- /dev/null
+++ b/app/assets/javascripts/pages/import/gitea/status/index.js
@@ -0,0 +1,7 @@
+import mountImportProjectsTable from '~/import_projects';
+
+document.addEventListener('DOMContentLoaded', () => {
+ const mountElement = document.getElementById('import-projects-mount-element');
+
+ mountImportProjectsTable(mountElement);
+});
diff --git a/app/assets/javascripts/pages/import/github/status/index.js b/app/assets/javascripts/pages/import/github/status/index.js
new file mode 100644
index 00000000000..dcd84f0faf9
--- /dev/null
+++ b/app/assets/javascripts/pages/import/github/status/index.js
@@ -0,0 +1,7 @@
+import mountImportProjectsTable from '~/import_projects';
+
+document.addEventListener('DOMContentLoaded', () => {
+ const mountElement = document.getElementById('import-projects-mount-element');
+
+ mountImportProjectsTable(mountElement);
+});
diff --git a/app/assets/javascripts/pages/projects/issues/index/index.js b/app/assets/javascripts/pages/projects/issues/index/index.js
index a56c0bb6be8..8bf0c2edc71 100644
--- a/app/assets/javascripts/pages/projects/issues/index/index.js
+++ b/app/assets/javascripts/pages/projects/issues/index/index.js
@@ -4,11 +4,13 @@ import IssuableIndex from '~/issuable_index';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import UsersSelect from '~/users_select';
import initFilteredSearch from '~/pages/search/init_filtered_search';
-import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
+import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys';
import { FILTERED_SEARCH } from '~/pages/constants';
import { ISSUABLE_INDEX } from '~/pages/projects/constants';
document.addEventListener('DOMContentLoaded', () => {
+ IssuableFilteredSearchTokenKeys.addExtraTokensForIssues();
+
initFilteredSearch({
page: FILTERED_SEARCH.ISSUES,
filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys,
diff --git a/app/assets/javascripts/pages/projects/project_members/index.js b/app/assets/javascripts/pages/projects/project_members/index.js
index adbe744290a..f39765818e7 100644
--- a/app/assets/javascripts/pages/projects/project_members/index.js
+++ b/app/assets/javascripts/pages/projects/project_members/index.js
@@ -1,7 +1,7 @@
+import Members from 'ee_else_ce/members';
import memberExpirationDate from '../../../member_expiration_date';
import UsersSelect from '../../../users_select';
import groupsSelect from '../../../groups_select';
-import Members from '../../../members';
document.addEventListener('DOMContentLoaded', () => {
memberExpirationDate('.js-access-expiration-date-groups');
diff --git a/app/assets/javascripts/pages/projects/settings/operations/show/index.js b/app/assets/javascripts/pages/projects/settings/operations/show/index.js
new file mode 100644
index 00000000000..73c745179be
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/settings/operations/show/index.js
@@ -0,0 +1,5 @@
+import mountErrorTrackingForm from '~/error_tracking_settings';
+
+document.addEventListener('DOMContentLoaded', () => {
+ mountErrorTrackingForm();
+});
diff --git a/app/assets/javascripts/pages/sessions/new/index.js b/app/assets/javascripts/pages/sessions/new/index.js
index d54bff88f70..e1a3f42a71f 100644
--- a/app/assets/javascripts/pages/sessions/new/index.js
+++ b/app/assets/javascripts/pages/sessions/new/index.js
@@ -1,5 +1,6 @@
import $ from 'jquery';
import UsernameValidator from './username_validator';
+import NoEmojiValidator from '../../../emoji/no_emoji_validator';
import SigninTabsMemoizer from './signin_tabs_memoizer';
import OAuthRememberMe from './oauth_remember_me';
import preserveUrlFragment from './preserve_url_fragment';
@@ -7,6 +8,7 @@ import preserveUrlFragment from './preserve_url_fragment';
document.addEventListener('DOMContentLoaded', () => {
new UsernameValidator(); // eslint-disable-line no-new
new SigninTabsMemoizer(); // eslint-disable-line no-new
+ new NoEmojiValidator(); // eslint-disable-line no-new
new OAuthRememberMe({
container: $('.omniauth-container'),
diff --git a/app/assets/javascripts/pages/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js
index 39cd891c111..636308c5401 100644
--- a/app/assets/javascripts/pages/users/user_tabs.js
+++ b/app/assets/javascripts/pages/users/user_tabs.js
@@ -221,7 +221,7 @@ export default class UserTabs {
const monthsAgo = UserTabs.getVisibleCalendarPeriod($calendarWrap);
const calendarActivitiesPath = $calendarWrap.data('calendarActivitiesPath');
const utcOffset = $calendarWrap.data('utcOffset');
- const calendarHint = __('Issues, merge requests, pushes and comments.');
+ const calendarHint = __('Issues, merge requests, pushes, and comments.');
$calendarWrap.html(CALENDAR_TEMPLATE);
diff --git a/app/assets/javascripts/pipelines/components/pipelines_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_actions.vue
index 0152e2fbe04..244d332f38f 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_actions.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_actions.vue
@@ -59,28 +59,30 @@ export default {
</script>
<template>
<div class="btn-group">
- <gl-button
+ <button
v-gl-tooltip
+ type="button"
:disabled="isLoading"
class="dropdown-new btn btn-default js-pipeline-dropdown-manual-actions"
- title="Manual job"
+ :title="__('Manual job')"
data-toggle="dropdown"
- aria-label="Manual job"
+ :aria-label="__('Manual job')"
>
- <icon name="play" class="icon-play" /> <i class="fa fa-caret-down" aria-hidden="true"> </i>
+ <icon name="play" class="icon-play" />
+ <i class="fa fa-caret-down" aria-hidden="true"></i>
<gl-loading-icon v-if="isLoading" />
- </gl-button>
+ </button>
<ul class="dropdown-menu dropdown-menu-right">
<li v-for="action in actions" :key="action.path">
<gl-button
:class="{ disabled: isActionDisabled(action) }"
:disabled="isActionDisabled(action)"
- class="js-pipeline-action-link no-btn btn"
+ class="js-pipeline-action-link no-btn btn d-flex align-items-center justify-content-between flex-wrap"
@click="onClickAction(action)"
>
{{ action.name }}
- <span v-if="action.scheduled_at" class="pull-right">
+ <span v-if="action.scheduled_at">
<icon name="clock" />
<gl-countdown :end-date-string="action.scheduled_at" />
</span>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue b/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue
index 908b10afee6..2ab0ad4d013 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue
@@ -1,5 +1,5 @@
<script>
-import { GlLink, GlButton, GlTooltipDirective } from '@gitlab/ui';
+import { GlLink, GlTooltipDirective } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
export default {
@@ -9,7 +9,6 @@ export default {
components: {
Icon,
GlLink,
- GlButton,
},
props: {
artifacts: {
@@ -21,20 +20,22 @@ export default {
</script>
<template>
<div class="btn-group" role="group">
- <gl-button
+ <button
v-gl-tooltip
- class="dropdown-toggle build-artifacts js-pipeline-dropdown-download"
- title="Artifacts"
+ type="button"
+ class="dropdown-toggle build-artifacts btn btn-default js-pipeline-dropdown-download"
+ :title="__('Artifacts')"
data-toggle="dropdown"
- aria-label="Artifacts"
+ :aria-label="__('Artifacts')"
>
- <icon name="download" /> <i class="fa fa-caret-down" aria-hidden="true"> </i>
- </gl-button>
+ <icon name="download" />
+ <i class="fa fa-caret-down" aria-hidden="true"></i>
+ </button>
<ul class="dropdown-menu dropdown-menu-right">
<li v-for="(artifact, i) in artifacts" :key="i">
- <gl-link :href="artifact.path" rel="nofollow" download>
- Download {{ artifact.name }} artifacts
- </gl-link>
+ <gl-link :href="artifact.path" rel="nofollow" download
+ >Download {{ artifact.name }} artifacts</gl-link
+ >
</li>
</ul>
</div>
diff --git a/app/assets/javascripts/pipelines/mixins/pipelines.js b/app/assets/javascripts/pipelines/mixins/pipelines.js
index 32bfa47e5f2..3cc9d0a3a4e 100644
--- a/app/assets/javascripts/pipelines/mixins/pipelines.js
+++ b/app/assets/javascripts/pipelines/mixins/pipelines.js
@@ -27,11 +27,7 @@ export default {
},
computed: {
shouldRenderPagination() {
- return (
- !this.isLoading &&
- this.state.pipelines.length &&
- this.state.pageInfo.total > this.state.pageInfo.perPage
- );
+ return !this.isLoading;
},
},
beforeMount() {
@@ -94,8 +90,7 @@ export default {
this.isLoading = false;
this.successCallback(response);
- // restart polling
- this.poll.restart({ data: this.requestData });
+ this.poll.enable({ data: this.requestData, response });
})
.catch(() => {
this.isLoading = false;
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 5f8a4946f4a..fd5d5f86401 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
@@ -34,7 +34,7 @@ export default {
},
errorMessage() {
return sprintf(
- s__('ClusterIntegration|An error occured while trying to fetch project zones: %{error}'),
+ s__('ClusterIntegration|An error occurred while trying to fetch project zones: %{error}'),
{ error: this.gapiError },
);
},
diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js
index d65e73a3f9c..1ade1811033 100644
--- a/app/assets/javascripts/projects/project_new.js
+++ b/app/assets/javascripts/projects/project_new.js
@@ -125,6 +125,14 @@ const bindEvents = () => {
text: 'Spring',
icon: '.template-option .icon-spring',
},
+ dotnetcore: {
+ text: '.NET Core',
+ icon: '.template-option .icon-dotnet',
+ },
+ gomicro: {
+ text: 'Go Micro',
+ icon: '.template-option .icon-gomicro',
+ },
hugo: {
text: 'Pages/Hugo',
icon: '.template-option .icon-hugo',
@@ -145,6 +153,26 @@ const bindEvents = () => {
text: 'Pages/Hexo',
icon: '.template-option .icon-hexo',
},
+ nfhugo: {
+ text: 'Netlify/Hugo',
+ icon: '.template-option .icon-netlify',
+ },
+ nfjekyll: {
+ text: 'Netlify/Jekyll',
+ icon: '.template-option .icon-netlify',
+ },
+ nfplainhtml: {
+ text: 'Netlify/Plain HTML',
+ icon: '.template-option .icon-netlify',
+ },
+ nfgitbook: {
+ text: 'Netlify/GitBook',
+ icon: '.template-option .icon-netlify',
+ },
+ nfhexo: {
+ text: 'Netlify/Hexo',
+ icon: '.template-option .icon-netlify',
+ },
};
const selectedTemplate = templates[value];
diff --git a/app/assets/javascripts/releases/store/actions.js b/app/assets/javascripts/releases/store/actions.js
index baa2251403e..e0a922d5ef6 100644
--- a/app/assets/javascripts/releases/store/actions.js
+++ b/app/assets/javascripts/releases/store/actions.js
@@ -11,7 +11,7 @@ export const requestReleases = ({ commit }) => commit(types.REQUEST_RELEASES);
/**
* Fetches the main endpoint.
* Will dispatch requestNamespace action before starting the request.
- * Will dispatch receiveNamespaceSuccess if the request is successfull
+ * Will dispatch receiveNamespaceSuccess if the request is successful
* Will dispatch receiveNamesapceError if the request returns an error
*
* @param {String} projectId
@@ -30,7 +30,7 @@ export const receiveReleasesSuccess = ({ commit }, data) =>
export const receiveReleasesError = ({ commit }) => {
commit(types.RECEIVE_RELEASES_ERROR);
- createFlash(__('An error occured while fetching the releases. Please try again.'));
+ createFlash(__('An error occurred while fetching the releases. Please try again.'));
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
index 2f2a37347af..da0a9483f8e 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
@@ -54,6 +54,12 @@ export default {
deployTimeago() {
return this.timeFormated(this.deployment.deployed_at);
},
+ deploymentExternalUrl() {
+ if (this.deployment.changes && this.deployment.changes.length === 1) {
+ return this.deployment.changes[0].external_url;
+ }
+ return this.deployment.external_url;
+ },
hasExternalUrls() {
return !!(this.deployment.external_url && this.deployment.external_url_formatted);
},
@@ -78,7 +84,7 @@ export default {
: '';
},
shouldRenderDropdown() {
- return this.deployment.changes && this.deployment.changes.length > 0;
+ return this.deployment.changes && this.deployment.changes.length > 1;
},
showMemoryUsage() {
return this.hasMetrics && this.showMetrics;
@@ -154,12 +160,12 @@ export default {
v-if="shouldRenderDropdown"
class="js-mr-wigdet-deployment-dropdown inline"
:items="deployment.changes"
- :main-action-link="deployment.external_url"
+ :main-action-link="deploymentExternalUrl"
filter-key="path"
>
<template slot="mainAction" slot-scope="slotProps">
<review-app-link
- :link="deployment.external_url"
+ :link="deploymentExternalUrl"
:css-class="`deploy-link js-deploy-url inline ${slotProps.className}`"
/>
</template>
@@ -183,7 +189,7 @@ export default {
</filtered-search-dropdown>
<review-app-link
v-else
- :link="deployment.external_url"
+ :link="deploymentExternalUrl"
css-class="js-deploy-url js-deploy-url-feature-flag deploy-link btn btn-default btn-sm inlin"
/>
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue
index a1d3a09cca4..33963d5e1e6 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue
@@ -73,14 +73,14 @@ export default {
<gl-button
:aria-label="ariaLabel"
variant="blank"
- class="commit-edit-toggle mr-2"
+ class="commit-edit-toggle square s24 mr-2"
@click.stop="toggle()"
>
<icon :name="collapseIcon" :size="16" />
</gl-button>
<span v-if="expanded">{{ __('Collapse') }}</span>
<span v-else>
- <span v-html="message"></span>
+ <span class="vertical-align-middle" v-html="message"></span>
<gl-button variant="link" class="modify-message-button">
{{ modifyLinkMessage }}
</gl-button>
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
index abbbe19c5ef..57c4dfbe3b7 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
@@ -315,7 +315,7 @@ export default {
:endpoint="mr.testResultsPath"
/>
- <div class="mr-widget-section p-0">
+ <div class="mr-widget-section">
<component :is="componentName" :mr="mr" :service="service" />
<section v-if="shouldRenderCollaborationStatus" class="mr-info-list mr-links">
diff --git a/app/assets/javascripts/vue_shared/components/changed_file_icon.vue b/app/assets/javascripts/vue_shared/components/changed_file_icon.vue
index bb7710f708e..e9ab6f5ba7a 100644
--- a/app/assets/javascripts/vue_shared/components/changed_file_icon.vue
+++ b/app/assets/javascripts/vue_shared/components/changed_file_icon.vue
@@ -37,6 +37,11 @@ export default {
required: false,
default: 12,
},
+ isCentered: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
},
computed: {
changedIcon() {
@@ -78,7 +83,12 @@ export default {
</script>
<template>
- <span v-gl-tooltip.right :title="tooltipTitle" class="file-changed-icon ml-auto">
+ <span
+ v-gl-tooltip.right
+ :title="tooltipTitle"
+ :class="{ 'ml-auto': isCentered }"
+ class="file-changed-icon"
+ >
<icon v-if="showIcon" :name="changedIcon" :size="size" :css-classes="changedIconClass" />
</span>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/ci_icon.vue b/app/assets/javascripts/vue_shared/components/ci_icon.vue
index b8eb555106f..2f498c4fa2a 100644
--- a/app/assets/javascripts/vue_shared/components/ci_icon.vue
+++ b/app/assets/javascripts/vue_shared/components/ci_icon.vue
@@ -46,6 +46,11 @@ export default {
required: false,
default: false,
},
+ cssClasses: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
computed: {
cssClass() {
@@ -59,5 +64,5 @@ export default {
};
</script>
<template>
- <span :class="cssClass"> <icon :name="icon" :size="size" /> </span>
+ <span :class="cssClass"> <icon :name="icon" :size="size" :css-classes="cssClasses" /> </span>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/diff_viewer/diff_viewer.vue b/app/assets/javascripts/vue_shared/components/diff_viewer/diff_viewer.vue
index 75c66ed850b..ebb253ff422 100644
--- a/app/assets/javascripts/vue_shared/components/diff_viewer/diff_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/diff_viewer/diff_viewer.vue
@@ -1,6 +1,5 @@
<script>
-import { diffModes } from '~/ide/constants';
-import { viewerInformationForPath } from '../content_viewer/lib/viewer_utils';
+import { diffViewerModes, diffModes } from '~/ide/constants';
import ImageDiffViewer from './viewers/image_diff_viewer.vue';
import DownloadDiffViewer from './viewers/download_diff_viewer.vue';
import RenamedFile from './viewers/renamed.vue';
@@ -12,6 +11,10 @@ export default {
type: String,
required: true,
},
+ diffViewerMode: {
+ type: String,
+ required: true,
+ },
newPath: {
type: String,
required: true,
@@ -46,7 +49,7 @@ export default {
},
computed: {
viewer() {
- if (this.diffMode === diffModes.renamed) {
+ if (this.diffViewerMode === diffViewerModes.renamed) {
return RenamedFile;
} else if (this.diffMode === diffModes.mode_changed) {
return ModeChanged;
@@ -54,11 +57,8 @@ export default {
if (!this.newPath) return null;
- const previewInfo = viewerInformationForPath(this.newPath);
- if (!previewInfo) return DownloadDiffViewer;
-
- switch (previewInfo.id) {
- case 'image':
+ switch (this.diffViewerMode) {
+ case diffViewerModes.image:
return ImageDiffViewer;
default:
return DownloadDiffViewer;
diff --git a/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/no_preview.vue b/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/no_preview.vue
new file mode 100644
index 00000000000..c5cdddf2f64
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/no_preview.vue
@@ -0,0 +1,5 @@
+<template>
+ <div class="nothing-here-block">
+ {{ __('No preview for this file type') }}
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/not_diffable.vue b/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/not_diffable.vue
new file mode 100644
index 00000000000..d4d3038f066
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/not_diffable.vue
@@ -0,0 +1,5 @@
+<template>
+ <div class="nothing-here-block">
+ {{ __('This diff was suppressed by a .gitattributes entry.') }}
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/empty_component.js b/app/assets/javascripts/vue_shared/components/empty_component.js
new file mode 100644
index 00000000000..e4402020096
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/empty_component.js
@@ -0,0 +1,3 @@
+export default {
+ render: () => null,
+};
diff --git a/app/assets/javascripts/vue_shared/components/file_row.vue b/app/assets/javascripts/vue_shared/components/file_row.vue
index f54033efc54..0cbcdbf2eb4 100644
--- a/app/assets/javascripts/vue_shared/components/file_row.vue
+++ b/app/assets/javascripts/vue_shared/components/file_row.vue
@@ -136,6 +136,7 @@ export default {
<div
v-else
:class="fileClass"
+ :title="file.name"
class="file-row"
role="button"
@click="clickFile"
diff --git a/app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue b/app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue
new file mode 100644
index 00000000000..27cfa8abb24
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue
@@ -0,0 +1,116 @@
+<script>
+import { GlTooltipDirective } from '@gitlab/ui';
+import { __, sprintf } from '~/locale';
+import IssueMilestone from '~/vue_shared/components/issue/issue_milestone.vue';
+import IssueAssignees from '~/vue_shared/components/issue/issue_assignees.vue';
+import relatedIssuableMixin from '~/vue_shared/mixins/related_issuable_mixin';
+
+export default {
+ name: 'IssueItem',
+ components: {
+ IssueMilestone,
+ IssueAssignees,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ mixins: [relatedIssuableMixin],
+ props: {
+ canReorder: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ computed: {
+ stateTitle() {
+ return sprintf(
+ '<span class="bold">%{state}</span> %{timeInWords}<br/><span class="text-tertiary">%{timestamp}</span>',
+ {
+ state: this.isOpen ? __('Opened') : __('Closed'),
+ timeInWords: this.isOpen ? this.createdAtInWords : this.closedAtInWords,
+ timestamp: this.isOpen ? this.createdAtTimestamp : this.closedAtTimestamp,
+ },
+ );
+ },
+ },
+};
+</script>
+
+<template>
+ <div
+ :class="{
+ 'issuable-info-container': !canReorder,
+ 'card-body': canReorder,
+ }"
+ class="item-body"
+ >
+ <div class="item-contents">
+ <div class="item-title d-flex align-items-center">
+ <icon
+ v-if="hasState"
+ v-tooltip
+ :css-classes="iconClass"
+ :name="iconName"
+ :size="16"
+ :title="stateTitle"
+ :aria-label="state"
+ data-html="true"
+ />
+ <icon
+ v-if="confidential"
+ v-gl-tooltip
+ name="eye-slash"
+ :size="16"
+ :title="__('Confidential')"
+ class="confidential-icon append-right-4"
+ :aria-label="__('Confidential')"
+ />
+ <a :href="computedPath" class="sortable-link">{{ title }}</a>
+ </div>
+ <div class="item-meta">
+ <div class="d-flex align-items-center item-path-id">
+ <icon
+ v-if="hasState"
+ v-tooltip
+ :css-classes="iconClass"
+ :name="iconName"
+ :size="16"
+ :title="stateTitle"
+ :aria-label="state"
+ data-html="true"
+ />
+ <span v-tooltip :title="itemPath" class="path-id-text">{{ itemPath }}</span>
+ {{ pathIdSeparator }}{{ itemId }}
+ </div>
+ <div class="item-meta-child d-flex align-items-center">
+ <issue-milestone
+ v-if="hasMilestone"
+ :milestone="milestone"
+ class="d-flex align-items-center item-milestone"
+ />
+ <slot name="dueDate"></slot>
+ <slot name="weight"></slot>
+ </div>
+ <issue-assignees
+ v-if="assignees.length"
+ :assignees="assignees"
+ class="item-assignees d-inline-flex"
+ />
+ </div>
+ </div>
+ <button
+ v-if="canRemove"
+ ref="removeButton"
+ v-tooltip
+ :disabled="removeDisabled"
+ type="button"
+ class="btn btn-default btn-svg btn-item-remove js-issue-item-remove-button qa-remove-issue-button"
+ title="Remove"
+ aria-label="Remove"
+ @click="onRemoveRequest"
+ >
+ <icon :size="16" class="btn-item-remove-icon" name="close" />
+ </button>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue
index dbfa32cd0ce..cc6ecdb0395 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue
@@ -79,7 +79,7 @@ export default {
<ul class="nav-links clearfix">
<li :class="{ active: !previewMarkdown }" class="md-header-tab">
<button class="js-write-link" tabindex="-1" type="button" @click="writeMarkdownTab($event)">
- Write
+ {{ __('Write') }}
</button>
</li>
<li :class="{ active: previewMarkdown }" class="md-header-tab">
@@ -89,36 +89,41 @@ export default {
type="button"
@click="previewMarkdownTab($event)"
>
- Preview
+ {{ __('Preview') }}
</button>
</li>
<li :class="{ active: !previewMarkdown }" class="md-header-toolbar">
- <toolbar-button tag="**" button-title="Add bold text" icon="bold" />
- <toolbar-button tag="*" button-title="Add italic text" icon="italic" />
- <toolbar-button :prepend="true" tag="> " button-title="Insert a quote" icon="quote" />
- <toolbar-button tag="`" tag-block="```" button-title="Insert code" icon="code" />
+ <toolbar-button tag="**" :button-title="__('Add bold text')" icon="bold" />
+ <toolbar-button tag="*" :button-title="__('Add italic text')" icon="italic" />
+ <toolbar-button
+ :prepend="true"
+ tag="> "
+ :button-title="__('Insert a quote')"
+ icon="quote"
+ />
+ <toolbar-button tag="`" tag-block="```" :button-title="__('Insert code')" icon="code" />
<toolbar-button
tag="[{text}](url)"
tag-select="url"
- button-title="Add a link"
+ :button-title="__('Add a link')"
icon="link"
/>
<toolbar-button
:prepend="true"
tag="* "
- button-title="Add a bullet list"
+ :button-title="__('Add a bullet list')"
icon="list-bulleted"
/>
<toolbar-button
:prepend="true"
tag="1. "
- button-title="Add a numbered list"
+ :button-title="__('Add a numbered list')"
icon="list-numbered"
/>
<toolbar-button
:prepend="true"
tag="* [ ] "
- button-title="Add a task list"
+ :button-title="__('Add a task list')"
icon="task-done"
/>
<toolbar-button
@@ -139,11 +144,11 @@ export default {
/>
<button
v-gl-tooltip
- aria-label="Go full screen"
+ :aria-label="__('Go full screen')"
class="toolbar-btn toolbar-fullscreen-btn js-zen-enter"
data-container="body"
tabindex="-1"
- title="Go full screen"
+ :title="__('Go full screen')"
type="button"
>
<icon name="screen-full" />
diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
index c33665c24f6..dcda701f049 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
@@ -130,6 +130,6 @@ export default {
<template>
<div>
<div class="flash-container js-suggestions-flash"></div>
- <div v-show="isRendered" ref="container" class="note-text md" v-html="noteHtml"></div>
+ <div v-show="isRendered" ref="container" v-html="noteHtml"></div>
</div>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/panel_resizer.vue b/app/assets/javascripts/vue_shared/components/panel_resizer.vue
index bf736a378dd..8d81940eb91 100644
--- a/app/assets/javascripts/vue_shared/components/panel_resizer.vue
+++ b/app/assets/javascripts/vue_shared/components/panel_resizer.vue
@@ -28,11 +28,12 @@ export default {
data() {
return {
size: this.startSize,
+ isDragging: false,
};
},
computed: {
className() {
- return `drag-${this.side}`;
+ return [`position-${this.side}-0`, { 'is-dragging': this.isDragging }];
},
cursorStyle() {
if (this.enabled) {
@@ -57,6 +58,7 @@ export default {
startDrag(e) {
if (this.enabled) {
e.preventDefault();
+ this.isDragging = true;
this.startPos = e.clientX;
this.currentStartSize = this.size;
document.addEventListener('mousemove', this.drag);
@@ -80,6 +82,7 @@ export default {
},
endDrag(e) {
e.preventDefault();
+ this.isDragging = false;
document.removeEventListener('mousemove', this.drag);
this.$emit('resize-end', this.size);
},
@@ -91,7 +94,7 @@ export default {
<div
:class="className"
:style="cursorStyle"
- class="drag-handle"
+ class="position-absolute position-top-0 position-bottom-0 drag-handle"
@mousedown="startDrag"
@dblclick="resetSize"
></div>
diff --git a/app/assets/javascripts/vue_shared/components/project_avatar/default.vue b/app/assets/javascripts/vue_shared/components/project_avatar/default.vue
index b399c232937..881b5059d2a 100644
--- a/app/assets/javascripts/vue_shared/components/project_avatar/default.vue
+++ b/app/assets/javascripts/vue_shared/components/project_avatar/default.vue
@@ -26,7 +26,7 @@ export default {
</script>
<template>
- <span :class="sizeClass" class="avatar-container project-avatar">
+ <span :class="sizeClass" class="avatar-container rect-avatar project-avatar">
<project-avatar-image
v-if="project.avatar_url"
:link-href="project.path"
@@ -34,6 +34,12 @@ export default {
:img-alt="project.name"
:img-size="size"
/>
- <identicon v-else :entity-id="project.id" :entity-name="project.name" :size-class="sizeClass" />
+ <identicon
+ v-else
+ :entity-id="project.id"
+ :entity-name="project.name"
+ :size-class="sizeClass"
+ class="rect-avatar"
+ />
</span>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/select2_select.vue b/app/assets/javascripts/vue_shared/components/select2_select.vue
new file mode 100644
index 00000000000..3074ea859cc
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/select2_select.vue
@@ -0,0 +1,35 @@
+<script>
+import $ from 'jquery';
+import 'select2/select2';
+
+export default {
+ name: 'Select2Select',
+ props: {
+ options: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ value: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+
+ mounted() {
+ $(this.$refs.dropdownInput)
+ .val(this.value)
+ .select2(this.options)
+ .on('change', event => this.$emit('input', event.target.value));
+ },
+
+ beforeDestroy() {
+ $(this.$refs.dropdownInput).select2('destroy');
+ },
+};
+</script>
+
+<template>
+ <input ref="dropdownInput" type="hidden" />
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/table_pagination.vue b/app/assets/javascripts/vue_shared/components/table_pagination.vue
index 2a34b4630f2..8e0b08032f7 100644
--- a/app/assets/javascripts/vue_shared/components/table_pagination.vue
+++ b/app/assets/javascripts/vue_shared/components/table_pagination.vue
@@ -54,15 +54,14 @@ export default {
return this.pageInfo.nextPage;
},
getItems() {
- const total = this.pageInfo.totalPages;
- const { page } = this.pageInfo;
+ const { totalPages, nextPage, previousPage, page } = this.pageInfo;
const items = [];
if (page > 1) {
items.push({ title: FIRST, first: true });
}
- if (page > 1) {
+ if (previousPage) {
items.push({ title: PREV, prev: true });
} else {
items.push({ title: PREV, disabled: true, prev: true });
@@ -70,32 +69,34 @@ export default {
if (page > UI_LIMIT) items.push({ title: SPREAD, separator: true });
- const start = Math.max(page - PAGINATION_UI_BUTTON_LIMIT, 1);
- const end = Math.min(page + PAGINATION_UI_BUTTON_LIMIT, total);
+ if (totalPages) {
+ const start = Math.max(page - PAGINATION_UI_BUTTON_LIMIT, 1);
+ const end = Math.min(page + PAGINATION_UI_BUTTON_LIMIT, totalPages);
- for (let i = start; i <= end; i += 1) {
- const isActive = i === page;
- items.push({ title: i, active: isActive, page: true });
- }
+ for (let i = start; i <= end; i += 1) {
+ const isActive = i === page;
+ items.push({ title: i, active: isActive, page: true });
+ }
- if (total - page > PAGINATION_UI_BUTTON_LIMIT) {
- items.push({ title: SPREAD, separator: true, page: true });
+ if (totalPages - page > PAGINATION_UI_BUTTON_LIMIT) {
+ items.push({ title: SPREAD, separator: true, page: true });
+ }
}
- if (page === total) {
- items.push({ title: NEXT, disabled: true, next: true });
- } else if (total - page >= 1) {
+ if (nextPage) {
items.push({ title: NEXT, next: true });
+ } else {
+ items.push({ title: NEXT, disabled: true, next: true });
}
- if (total - page >= 1) {
+ if (totalPages && totalPages - page >= 1) {
items.push({ title: LAST, last: true });
}
return items;
},
showPagination() {
- return this.pageInfo.totalPages > 1;
+ return this.pageInfo.nextPage || this.pageInfo.previousPage;
},
},
methods: {
@@ -149,9 +150,9 @@ export default {
}"
class="page-item"
>
- <a class="page-link" @click.prevent="changePage(item.title, item.disabled)">
+ <button type="button" class="page-link" @click="changePage(item.title, item.disabled)">
{{ item.title }}
- </a>
+ </button>
</li>
</ul>
</div>
diff --git a/app/assets/javascripts/vue_shared/mixins/related_issuable_mixin.js b/app/assets/javascripts/vue_shared/mixins/related_issuable_mixin.js
new file mode 100644
index 00000000000..455ae832234
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/mixins/related_issuable_mixin.js
@@ -0,0 +1,155 @@
+import _ from 'underscore';
+import { formatDate } from '~/lib/utils/datetime_utility';
+import tooltip from '~/vue_shared/directives/tooltip';
+import icon from '~/vue_shared/components/icon.vue';
+import timeagoMixin from '~/vue_shared/mixins/timeago';
+
+const mixins = {
+ data() {
+ return {
+ removeDisabled: false,
+ };
+ },
+ props: {
+ idKey: {
+ type: Number,
+ required: true,
+ },
+ displayReference: {
+ type: String,
+ required: true,
+ },
+ pathIdSeparator: {
+ type: String,
+ required: true,
+ },
+ eventNamespace: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ confidential: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ title: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ path: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ state: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ createdAt: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ closedAt: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ milestone: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ dueDate: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ assignees: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ weight: {
+ type: Number,
+ required: false,
+ default: 0,
+ },
+ canRemove: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ components: {
+ icon,
+ },
+ directives: {
+ tooltip,
+ },
+ mixins: [timeagoMixin],
+ computed: {
+ hasState() {
+ return this.state && this.state.length > 0;
+ },
+ isOpen() {
+ return this.state === 'opened';
+ },
+ isClosed() {
+ return this.state === 'closed';
+ },
+ hasTitle() {
+ return this.title.length > 0;
+ },
+ hasMilestone() {
+ return !_.isEmpty(this.milestone);
+ },
+ iconName() {
+ return this.isOpen ? 'issue-open-m' : 'issue-close';
+ },
+ iconClass() {
+ return this.isOpen ? 'issue-token-state-icon-open' : 'issue-token-state-icon-closed';
+ },
+ computedLinkElementType() {
+ return this.path.length > 0 ? 'a' : 'span';
+ },
+ computedPath() {
+ return this.path.length ? this.path : null;
+ },
+ itemPath() {
+ return this.displayReference.split(this.pathIdSeparator)[0];
+ },
+ itemId() {
+ return this.displayReference.split(this.pathIdSeparator).pop();
+ },
+ createdAtInWords() {
+ return this.createdAt ? this.timeFormated(this.createdAt) : '';
+ },
+ createdAtTimestamp() {
+ return this.createdAt ? formatDate(new Date(this.createdAt)) : '';
+ },
+ closedAtInWords() {
+ return this.closedAt ? this.timeFormated(this.closedAt) : '';
+ },
+ closedAtTimestamp() {
+ return this.closedAt ? formatDate(new Date(this.closedAt)) : '';
+ },
+ },
+ methods: {
+ onRemoveRequest() {
+ let namespacePrefix = '';
+ if (this.eventNamespace && this.eventNamespace.length > 0) {
+ namespacePrefix = `${this.eventNamespace}`;
+ }
+
+ this.$emit(`${namespacePrefix}RemoveRequest`, this.idKey);
+
+ this.removeDisabled = true;
+ },
+ },
+};
+
+export default mixins;
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index bdf20866197..83ad8766cb5 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -40,16 +40,6 @@
@import "components/**/*";
/*
- * Code highlight
- */
-@import "highlight/dark";
-@import "highlight/monokai";
-@import "highlight/solarized_dark";
-@import "highlight/solarized_light";
-@import "highlight/white";
-@import "highlight/none";
-
-/*
* Styles for JS behaviors.
*/
@import "behaviors";
diff --git a/app/assets/stylesheets/components/related_items_list.scss b/app/assets/stylesheets/components/related_items_list.scss
index 048a5c0300c..edf7b26ebaa 100644
--- a/app/assets/stylesheets/components/related_items_list.scss
+++ b/app/assets/stylesheets/components/related_items_list.scss
@@ -195,8 +195,7 @@ $item-weight-max-width: 48px;
}
.mr-status-wrapper,
-.mr-ci-status
- {
+.mr-ci-status {
line-height: 0;
}
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index 62d471bc30c..216877a4461 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -65,3 +65,5 @@
@import 'framework/terms';
@import 'framework/read_more';
@import 'framework/flex_grid';
+@import 'framework/system_messages';
+@import "framework/spinner";
diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss
index 4fb787887a1..257d788873c 100644
--- a/app/assets/stylesheets/framework/animations.scss
+++ b/app/assets/stylesheets/framework/animations.scss
@@ -27,7 +27,7 @@
&.flipOutY,
&.bounceIn,
&.bounceOut {
- @include webkit-prefix(animation-duration, .75s);
+ @include webkit-prefix(animation-duration, 0.75s);
}
&.short {
@@ -63,56 +63,44 @@
//
// Pass in any number of transitions
@mixin transition($transitions...) {
- $unfoldedTransitions: ();
+ $unfolded-transitions: ();
@each $transition in $transitions {
- $unfoldedTransitions: append($unfoldedTransitions, unfoldTransition($transition), comma);
+ $unfolded-transitions: append($unfolded-transitions, unfold-transition($transition), comma);
}
- transition: $unfoldedTransitions;
+ transition: $unfolded-transitions;
}
-@mixin disableAllAnimation {
+@mixin disable-all-animation {
/*CSS transitions*/
- -o-transition-property: none !important;
- -moz-transition-property: none !important;
- -ms-transition-property: none !important;
- -webkit-transition-property: none !important;
transition-property: none !important;
/*CSS transforms*/
- -o-transform: none !important;
- -moz-transform: none !important;
- -ms-transform: none !important;
- -webkit-transform: none !important;
transform: none !important;
/*CSS animations*/
- -webkit-animation: none !important;
- -moz-animation: none !important;
- -o-animation: none !important;
- -ms-animation: none !important;
animation: none !important;
}
-@function unfoldTransition ($transition) {
+@function unfold-transition ($transition) {
// Default values
$property: all;
$duration: $general-hover-transition-duration;
$easing: $general-hover-transition-curve; // Browser default is ease, which is what we want
$delay: null; // Browser default is 0, which is what we want
- $defaultProperties: ($property, $duration, $easing, $delay);
+ $default-properties: ($property, $duration, $easing, $delay);
// Grab transition properties if they exist
- $unfoldedTransition: ();
- @for $i from 1 through length($defaultProperties) {
+ $unfolded-transition: ();
+ @for $i from 1 through length($default-properties) {
$p: null;
@if $i <= length($transition) {
$p: nth($transition, $i);
} @else {
- $p: nth($defaultProperties, $i);
+ $p: nth($default-properties, $i);
}
- $unfoldedTransition: append($unfoldedTransition, $p);
+ $unfolded-transition: append($unfolded-transition, $p);
}
- @return $unfoldedTransition;
+ @return $unfolded-transition;
}
.btn {
@@ -202,7 +190,7 @@ a {
}
}
- [class^="skeleton-line-"] {
+ [class^='skeleton-line-'] {
position: relative;
background-color: $gray-100;
height: 10px;
@@ -218,13 +206,11 @@ a {
animation: blockTextShine 1s linear infinite forwards;
background-repeat: no-repeat;
background-size: cover;
- background-image: linear-gradient(
- to right,
- $gray-100 0%,
- $gray-50 20%,
- $gray-100 40%,
- $gray-100 100%
- );
+ background-image: linear-gradient(to right,
+ $gray-100 0%,
+ $gray-50 20%,
+ $gray-100 40%,
+ $gray-100 100%);
height: 10px;
}
}
diff --git a/app/assets/stylesheets/framework/asciidoctor.scss b/app/assets/stylesheets/framework/asciidoctor.scss
index 62493c32833..1586265d40e 100644
--- a/app/assets/stylesheets/framework/asciidoctor.scss
+++ b/app/assets/stylesheets/framework/asciidoctor.scss
@@ -1,7 +1,7 @@
.admonitionblock td.icon {
width: 1%;
- [class^="fa icon-"] {
+ [class^='fa icon-'] {
@extend .fa-2x;
}
diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss
index e132aa4c216..af79a4d9392 100644
--- a/app/assets/stylesheets/framework/avatar.scss
+++ b/app/assets/stylesheets/framework/avatar.scss
@@ -45,7 +45,6 @@
display: inline-block;
margin-left: 2px;
flex-shrink: 0;
- -webkit-flex-shrink: 0;
&.s16 { margin-right: 4px; }
&.s24 { margin-right: 4px; }
@@ -74,20 +73,48 @@
background-color: $gray-darker;
// Sizes
- &.s16 { font-size: 12px; line-height: 1.33; }
- &.s24 { font-size: 13px; line-height: 1.8; }
- &.s26 { font-size: 20px; line-height: 1.33; }
- &.s32 { font-size: 20px; line-height: 30px; }
- &.s40 { font-size: 16px; line-height: 38px; }
- &.s48 { font-size: 20px; line-height: 46px; }
- &.s60 { font-size: 32px; line-height: 58px; }
- &.s64 { font-size: 32px; line-height: 64px; }
- &.s70 { font-size: 34px; line-height: 70px; }
- &.s90 { font-size: 36px; line-height: 88px; }
- &.s100 { font-size: 36px; line-height: 98px; }
- &.s110 { font-size: 40px; line-height: 108px; font-weight: $gl-font-weight-normal; }
- &.s140 { font-size: 72px; line-height: 138px; }
- &.s160 { font-size: 96px; line-height: 158px; }
+ &.s16 { font-size: 12px;
+ line-height: 1.33; }
+
+ &.s24 { font-size: 13px;
+ line-height: 1.8; }
+
+ &.s26 { font-size: 20px;
+ line-height: 1.33; }
+
+ &.s32 { font-size: 20px;
+ line-height: 30px; }
+
+ &.s40 { font-size: 16px;
+ line-height: 38px; }
+
+ &.s48 { font-size: 20px;
+ line-height: 46px; }
+
+ &.s60 { font-size: 32px;
+ line-height: 58px; }
+
+ &.s64 { font-size: 32px;
+ line-height: 64px; }
+
+ &.s70 { font-size: 34px;
+ line-height: 70px; }
+
+ &.s90 { font-size: 36px;
+ line-height: 88px; }
+
+ &.s100 { font-size: 36px;
+ line-height: 98px; }
+
+ &.s110 { font-size: 40px;
+ line-height: 108px;
+ font-weight: $gl-font-weight-normal; }
+
+ &.s140 { font-size: 72px;
+ line-height: 138px; }
+
+ &.s160 { font-size: 96px;
+ line-height: 158px; }
// Background colors
&.bg1 { background-color: $identicon-red; }
@@ -120,8 +147,35 @@
align-self: center;
}
- &.s40 { min-width: 40px; min-height: 40px; }
- &.s64 { min-width: 64px; min-height: 64px; }
+ &.s40 { min-width: 40px;
+ min-height: 40px; }
+
+ &.s64 { min-width: 64px;
+ min-height: 64px; }
+}
+
+.rect-avatar {
+ border-radius: $border-radius-small;
+ &.s16 { border-radius: $border-radius-small; }
+ &.s18 { border-radius: $border-radius-small; }
+ &.s19 { border-radius: $border-radius-small; }
+ &.s20 { border-radius: $border-radius-small; }
+ &.s24 { border-radius: $border-radius-default; }
+ &.s26 { border-radius: $border-radius-default; }
+ &.s32 { border-radius: $border-radius-default; }
+ &.s36 { border-radius: $border-radius-default; }
+ &.s40 { border-radius: $border-radius-default; }
+ &.s46 { border-radius: $border-radius-default; }
+ &.s48 { border-radius: $border-radius-large; }
+ &.s60 { border-radius: $border-radius-large; }
+ &.s64 { border-radius: $border-radius-large; }
+ &.s70 { border-radius: $border-radius-large; }
+ &.s90 { border-radius: $border-radius-large; }
+ &.s96 { border-radius: $border-radius-large; }
+ &.s100 { border-radius: $border-radius-large; }
+ &.s110 { border-radius: $border-radius-large; }
+ &.s140 { border-radius: $border-radius-large; }
+ &.s160 { border-radius: $border-radius-large; }
}
.avatar-counter {
diff --git a/app/assets/stylesheets/framework/awards.scss b/app/assets/stylesheets/framework/awards.scss
index ad650d45314..648e1944388 100644
--- a/app/assets/stylesheets/framework/awards.scss
+++ b/app/assets/stylesheets/framework/awards.scss
@@ -15,7 +15,7 @@
margin-top: 3px;
padding: $gl-padding;
z-index: 300;
- width: 300px;
+ width: $award-emoji-width;
font-size: 14px;
background-color: $white-light;
border: 1px solid $border-white-light;
@@ -23,9 +23,9 @@
box-shadow: 0 6px 12px $award-emoji-menu-shadow;
pointer-events: none;
opacity: 0;
- transform: scale(.2);
+ transform: scale(0.2);
transform-origin: 0 -45px;
- transition: .3s cubic-bezier(.67, .06, .19, 1.44);
+ transition: 0.3s cubic-bezier(0.67, 0.06, 0.19, 1.44);
transition-property: transform, opacity;
&.is-rendered {
@@ -55,10 +55,14 @@
transform: none;
}
}
+
+ @include media-breakpoint-down(xs) {
+ width: $award-emoji-width-xs;
+ }
}
.emoji-search {
- background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAFu0lEQVRIia1WTahkVxH+quqce7vf6zdvJpHoIlkYJ2SiJiIokmQjgoGgIAaEIYuYXWICgojiwkmC4taFwhjcyIDusogEIwwiSSCKPwsdwzAg0SjJ9Izzk5n3+nXfe8+pqizOvd395scfsJqi6dPnnDr11Vc/NJ1OwUTosqJLCmYCHCAC2mSHs+ojZv6AO46Y+20AhIneJsafhPhXVZSXDk7qi+aOLhtQNuBmQtcarAKjTXpn2+l3u2yPunvZSABRucjcAV/eMZuM48/Go/g1d19kc4wq+e8MZjWkbI/P5t2P3RFFbv7SQdyBlBUx8N8OTuqjMcof+N94yMPrY2DMm/ytnb32J0QrY+6AqsHM4Q64O9SKDmerKDD3Oy/tNL9vk342CC8RuU6n0ymCMHb22scu7zQngtASOjUHE1BX4UUAv4b7Ow6qiXCXuz/UdvogAAweDY943/b4cAz0ZlYHXeMsnT07RVb7wMUr8ykI4H5HVkMd5Rcb4/jNURVOL5qErAaAUUdCCIJ5kx5q2nw8m39ImEAAsjpE6PStB0YfMcd1wqqG3Xn7A3PfZyyKnNjaqD4fmE/fCNKshirIyY1xvI+Av6g5QIAIIWX7cJPssboSiBBEeKmsZne0Sb8kzAUWNYyq8NvbDo0fZ6beqxuLmqOOMr/lwOh+YXpXtbjERGja9JyZ9+HxpXKb9Gj5oywRESbj+Cj1ENG1QViTGBl1FbC1We1tbVRfHWIoQkhqH9xbpE92XUbb6VJZ1R4crjRz1JWcDMJvLdoMcyAEhjuwHo8Bfndg3mbszhOY+adVlMtD3po51OwzIQiEaams7oeJhxRw1FFOVpFRRUYIhMBAFRnjOsC8IFHHUA4TQQhgAqpAiIFfGbxkIqj54ayGbL7UoOqHCniAEKHLNr26l+D9wQJzeUwMAnfHvEnLECzZRwRV++d60ptjW9VLZeolEJG6GwCCE0CFVNB+Ay0NEqoQYG4YYFu7B8IEVRt3uRzy/osIoLV9QZimWXGHUMFdmI6M64DUF2Je88R9VZqCSP+QlcF5k+4tCzSsXaqjINuK6UyE0+s/mk6/qFq8oAIL9pqMLhkGsNrOyoOIlszust3aJv0U9+kFdwjTGwWl1YdF+KWlQSZ0Se/psj8yGVdg5tJyfH96EBWmLtoEMwMzMFt031NzGWLLzKhC+KV7H5ZeeaMOPxemma2x68puc0LN3+/u6LJiePS6MKHvn4wu6cPzJj0hsioeMfDrEvjv5r6W9gBvjKJujuKzQ0URIZj75NylvT+mbHfXQa4rwAMaVRTMm/SFyzvNy0yF6+4AM+1ubcSnqkAIUjQKl1RKSbE5jt+vovx1MBqF0WW7/d1Z80ab9BtmuJ3Xk5cJKds9TZt/uLPXvtiTrQ+dIwqfAejUvM1os6FNikXKUHfQ+ekUsXT5u85enJ0CaBSkkGEo1syUQ+DfMdE/4GA1uzupf9zdbzhOmLsF4efHVXjaHHAzmDtGdQRd/Nc5wAEJjNki3XfhyvwVNz80xANrht3LsENY9cBBdN1L9GUyyvFRFZ42t75sBvCQRykbRlU4tT2pPxoCvzx09d4GmPs200M6wKdWSDGK8mppYSWdhAlt0qeaLv+IadXU9/Evq4FAZ8ej+LmtcTxaRX4NWI0Uag5Vg1p5MYg8BnlhXIdPHDow+vTWZvVMVttXDLqkTzZdPj6Qii6cP1cSvIdl3iQkNYyi9HH0I22y+93tY3DcQkTZgQtM+POoCr8x97eylkmtrgKuztrvXJ21x/aNKuqIkZ/fntRfCdcTfhUTAIhRzoDojJD0aSNLLwMzmpT7+JaLtyf1MwDo6qz9djFaUq3t9MlFmy/c1OCSceY9fMsVaL9mvH9ocXdkdWxv1scAePG0THAhMOaLdOw/Gvxfxb1w4eCapyIENUcV5M3/u8FitAxZ25P6GAHT3UX39Srw+QOb1ZffA98Dl2Wy1BYkAAAAAElFTkSuQmCC");
+ background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAFu0lEQVRIia1WTahkVxH+quqce7vf6zdvJpHoIlkYJ2SiJiIokmQjgoGgIAaEIYuYXWICgojiwkmC4taFwhjcyIDusogEIwwiSSCKPwsdwzAg0SjJ9Izzk5n3+nXfe8+pqizOvd395scfsJqi6dPnnDr11Vc/NJ1OwUTosqJLCmYCHCAC2mSHs+ojZv6AO46Y+20AhIneJsafhPhXVZSXDk7qi+aOLhtQNuBmQtcarAKjTXpn2+l3u2yPunvZSABRucjcAV/eMZuM48/Go/g1d19kc4wq+e8MZjWkbI/P5t2P3RFFbv7SQdyBlBUx8N8OTuqjMcof+N94yMPrY2DMm/ytnb32J0QrY+6AqsHM4Q64O9SKDmerKDD3Oy/tNL9vk342CC8RuU6n0ymCMHb22scu7zQngtASOjUHE1BX4UUAv4b7Ow6qiXCXuz/UdvogAAweDY943/b4cAz0ZlYHXeMsnT07RVb7wMUr8ykI4H5HVkMd5Rcb4/jNURVOL5qErAaAUUdCCIJ5kx5q2nw8m39ImEAAsjpE6PStB0YfMcd1wqqG3Xn7A3PfZyyKnNjaqD4fmE/fCNKshirIyY1xvI+Av6g5QIAIIWX7cJPssboSiBBEeKmsZne0Sb8kzAUWNYyq8NvbDo0fZ6beqxuLmqOOMr/lwOh+YXpXtbjERGja9JyZ9+HxpXKb9Gj5oywRESbj+Cj1ENG1QViTGBl1FbC1We1tbVRfHWIoQkhqH9xbpE92XUbb6VJZ1R4crjRz1JWcDMJvLdoMcyAEhjuwHo8Bfndg3mbszhOY+adVlMtD3po51OwzIQiEaams7oeJhxRw1FFOVpFRRUYIhMBAFRnjOsC8IFHHUA4TQQhgAqpAiIFfGbxkIqj54ayGbL7UoOqHCniAEKHLNr26l+D9wQJzeUwMAnfHvEnLECzZRwRV++d60ptjW9VLZeolEJG6GwCCE0CFVNB+Ay0NEqoQYG4YYFu7B8IEVRt3uRzy/osIoLV9QZimWXGHUMFdmI6M64DUF2Je88R9VZqCSP+QlcF5k+4tCzSsXaqjINuK6UyE0+s/mk6/qFq8oAIL9pqMLhkGsNrOyoOIlszust3aJv0U9+kFdwjTGwWl1YdF+KWlQSZ0Se/psj8yGVdg5tJyfH96EBWmLtoEMwMzMFt031NzGWLLzKhC+KV7H5ZeeaMOPxemma2x68puc0LN3+/u6LJiePS6MKHvn4wu6cPzJj0hsioeMfDrEvjv5r6W9gBvjKJujuKzQ0URIZj75NylvT+mbHfXQa4rwAMaVRTMm/SFyzvNy0yF6+4AM+1ubcSnqkAIUjQKl1RKSbE5jt+vovx1MBqF0WW7/d1Z80ab9BtmuJ3Xk5cJKds9TZt/uLPXvtiTrQ+dIwqfAejUvM1os6FNikXKUHfQ+ekUsXT5u85enJ0CaBSkkGEo1syUQ+DfMdE/4GA1uzupf9zdbzhOmLsF4efHVXjaHHAzmDtGdQRd/Nc5wAEJjNki3XfhyvwVNz80xANrht3LsENY9cBBdN1L9GUyyvFRFZ42t75sBvCQRykbRlU4tT2pPxoCvzx09d4GmPs200M6wKdWSDGK8mppYSWdhAlt0qeaLv+IadXU9/Evq4FAZ8ej+LmtcTxaRX4NWI0Uag5Vg1p5MYg8BnlhXIdPHDow+vTWZvVMVttXDLqkTzZdPj6Qii6cP1cSvIdl3iQkNYyi9HH0I22y+93tY3DcQkTZgQtM+POoCr8x97eylkmtrgKuztrvXJ21x/aNKuqIkZ/fntRfCdcTfhUTAIhRzoDojJD0aSNLLwMzmpT7+JaLtyf1MwDo6qz9djFaUq3t9MlFmy/c1OCSceY9fMsVaL9mvH9ocXdkdWxv1scAePG0THAhMOaLdOw/Gvxfxb1w4eCapyIENUcV5M3/u8FitAxZ25P6GAHT3UX39Srw+QOb1ZffA98Dl2Wy1BYkAAAAAElFTkSuQmCC');
background-repeat: no-repeat;
background-position: right 5px center;
background-size: 16px;
@@ -86,7 +90,7 @@
background: none;
border: 0;
border-radius: $border-radius-base;
- transition: transform .15s cubic-bezier(.3, 0, .2, 2);
+ transition: transform 0.15s cubic-bezier(0.3, 0, 0.2, 2);
&:hover {
background-color: transparent;
@@ -229,10 +233,10 @@
height: $default-icon-size;
width: $default-icon-size;
border-radius: 50%;
+ }
- path {
- fill: $border-gray-normal;
- }
+ path {
+ fill: $border-gray-normal;
}
}
@@ -243,6 +247,10 @@
left: 10px;
bottom: 6px;
opacity: 0;
+
+ path {
+ fill: $award-emoji-positive-add-lines;
+ }
}
.award-control-text {
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index 43b7c26b272..e6c55252b24 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -86,11 +86,8 @@
}
.block-controls {
- display: -webkit-flex;
display: flex;
- -webkit-justify-content: flex-end;
justify-content: flex-end;
- -webkit-flex: 1;
flex: 1;
.control {
@@ -153,7 +150,7 @@
display: inline-block;
margin-left: 5px;
font-size: 18px;
- color: color("gray");
+ color: color('gray');
}
p {
@@ -228,7 +225,6 @@
}
.group-info {
-
h1 {
display: inline;
font-weight: $gl-font-weight-normal;
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index d164cc56e44..a4af84f8d27 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -166,7 +166,8 @@
@include btn-outline($white-light, $green-600, $green-500, $green-500, $white-light, $green-600, $green-600, $green-700);
}
- &.btn-remove {
+ &.btn-remove,
+ &.btn-danger {
@include btn-outline($white-light, $red-500, $red-500, $red-500, $white-light, $red-600, $red-600, $red-700);
}
@@ -394,8 +395,6 @@
cursor: default;
&:active {
- -moz-box-shadow: inset 0 0 0 $white-light;
- -webkit-box-shadow: inset 0 0 0 $white-light;
box-shadow: inset 0 0 0 $white-light;
}
}
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index c5c3b66438c..d0e33dfc853 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -48,7 +48,20 @@
color: $brand-info;
}
-.hint { font-style: italic; color: $gl-gray-400; }
+.text-break-word {
+ word-break: break-all;
+}
+
+.text-underline,
+.text-underline:hover {
+ text-decoration: underline;
+}
+
+.hint {
+ font-style: italic;
+ color: $gl-gray-400;
+}
+
.light { color: $gl-text-color; }
.slead {
@@ -149,13 +162,14 @@ p.time {
text-shadow: none;
}
-.thin_area {
+.thin-area {
height: 150px;
}
// Fix issue with notes & lists creating a bunch of bottom borders.
li.note {
img { max-width: 100%; }
+
.note-title {
li {
border-bottom: 0 !important;
@@ -326,7 +340,7 @@ img.emoji {
.disabled-content {
pointer-events: none;
- opacity: .5;
+ opacity: 0.5;
}
.break-word {
@@ -391,6 +405,7 @@ img.emoji {
.flex-no-shrink { flex-shrink: 0; }
.ws-initial { white-space: initial; }
.overflow-auto { overflow: auto; }
+
.d-flex-center {
display: flex;
align-items: center;
@@ -442,3 +457,19 @@ img.emoji {
.position-left-0 { left: 0; }
.position-right-0 { right: 0; }
.position-top-0 { top: 0; }
+
+.drag-handle {
+ width: 4px;
+
+ &:hover {
+ background-color: $white-normal;
+ }
+
+ &.is-dragging {
+ background-color: $gray-600;
+ }
+}
+
+.cursor-pointer {
+ cursor: pointer;
+}
diff --git a/app/assets/stylesheets/framework/emojis.scss b/app/assets/stylesheets/framework/emojis.scss
index be85e03430e..13c5541da92 100644
--- a/app/assets/stylesheets/framework/emojis.scss
+++ b/app/assets/stylesheets/framework/emojis.scss
@@ -2,7 +2,7 @@ gl-emoji {
font-style: normal;
display: inline-flex;
vertical-align: middle;
- font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+ font-family: 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
font-size: 1.4em;
line-height: 1em;
}
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index f48b3ddc912..e5b529ae11d 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -50,19 +50,15 @@
}
.filtered-search-wrapper {
- display: -webkit-flex;
display: flex;
@include media-breakpoint-down(xs) {
- -webkit-flex-direction: column;
flex-direction: column;
}
.tokens-container {
- display: -webkit-flex;
display: flex;
flex: 1;
- -webkit-flex: 1;
padding-left: 12px;
position: relative;
margin-bottom: 0;
@@ -82,21 +78,18 @@
.input-token:only-child,
.input-token:last-child {
flex: 1;
- -webkit-flex: 1;
max-width: inherit;
}
}
.filtered-search-token,
.filtered-search-term {
- display: -webkit-flex;
display: flex;
flex-shrink: 0;
margin-top: 4px;
margin-bottom: 4px;
.selectable {
- display: -webkit-flex;
display: flex;
}
@@ -176,7 +169,6 @@
}
.scroll-container {
- display: -webkit-flex;
display: flex;
overflow-x: auto;
white-space: nowrap;
@@ -186,7 +178,6 @@
.filtered-search-box {
position: relative;
flex: 1;
- display: -webkit-flex;
display: flex;
width: 100%;
min-width: 0;
@@ -194,7 +185,6 @@
background-color: $white-light;
@include media-breakpoint-down(xs) {
- -webkit-flex: 1 1 auto;
flex: 1 1 auto;
margin-bottom: 10px;
}
@@ -349,7 +339,6 @@
}
.filter-dropdown-container {
- display: -webkit-flex;
display: flex;
.dropdown-toggle {
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index cbf9ee24ec5..3b1d1d67509 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -156,8 +156,6 @@ label {
.select-control {
padding-left: 10px;
padding-right: 10px;
- -webkit-appearance: none;
- -moz-appearance: none;
appearance: none;
&::-ms-expand {
@@ -178,7 +176,8 @@ label {
font-weight: $gl-font-weight-normal;
}
-.form-control::-webkit-input-placeholder {
+
+.form-control::placeholder {
color: $gl-text-color-tertiary;
}
diff --git a/app/assets/stylesheets/framework/gfm.scss b/app/assets/stylesheets/framework/gfm.scss
index 50d4298d418..6943bfbc3d0 100644
--- a/app/assets/stylesheets/framework/gfm.scss
+++ b/app/assets/stylesheets/framework/gfm.scss
@@ -32,7 +32,7 @@
height: $chip-size;
background: $white-light;
background-image: linear-gradient(135deg, $gray-dark 25%, transparent 0%, transparent 75%, $gray-dark 0%),
- linear-gradient(135deg, $gray-dark 25%, transparent 0%, transparent 75%, $gray-dark 0%);
+ linear-gradient(135deg, $gray-dark 25%, transparent 0%, transparent 75%, $gray-dark 0%);
background-size: $bg-size $bg-size;
background-position: 0 0, $bg-pos $bg-pos;
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 23dcc1817b1..f5ed6621c55 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -39,7 +39,6 @@
.header-content {
width: 100%;
- display: -webkit-flex;
display: flex;
justify-content: space-between;
position: relative;
@@ -47,11 +46,8 @@
padding-left: 0;
.title-container {
- display: -webkit-flex;
display: flex;
- -webkit-align-items: stretch;
align-items: stretch;
- -webkit-flex: 1 1 auto;
flex: 1 1 auto;
padding-top: 0;
overflow: visible;
@@ -60,7 +56,6 @@
.title {
padding-right: 0;
color: currentColor;
- display: -webkit-flex;
display: flex;
position: relative;
margin: 0;
@@ -85,7 +80,6 @@
}
a {
- display: -webkit-flex;
display: flex;
align-items: center;
padding: 2px 8px;
@@ -173,7 +167,6 @@
.navbar-nav {
@include media-breakpoint-down(xs) {
- display: -webkit-flex;
display: flex;
padding-right: 10px;
flex-direction: row;
@@ -258,7 +251,6 @@
> li {
> a,
> button {
- display: -webkit-flex;
display: flex;
align-items: center;
justify-content: center;
@@ -294,7 +286,6 @@
}
.navbar-sub-nav {
- display: -webkit-flex;
display: flex;
margin: 0 0 0 6px;
@@ -326,14 +317,12 @@
}
.breadcrumbs {
- display: -webkit-flex;
display: flex;
min-height: $breadcrumb-min-height;
color: $gl-text-color;
}
.breadcrumbs-container {
- display: -webkit-flex;
display: flex;
width: 100%;
position: relative;
@@ -344,7 +333,6 @@
}
.breadcrumbs-links {
- -webkit-flex: 1;
flex: 1;
min-width: 0;
align-self: center;
@@ -379,7 +367,6 @@
}
.breadcrumbs-list {
- display: -webkit-flex;
display: flex;
margin-bottom: 0;
line-height: 16px;
@@ -430,7 +417,6 @@
}
.breadcrumbs-extra {
- display: -webkit-flex;
display: flex;
flex: 0 0 auto;
margin-left: auto;
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index d9d4a210f5f..1a74e06a75d 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -15,7 +15,7 @@
word-wrap: break-word;
&::after {
- content: " ";
+ content: ' ';
display: table;
clear: both;
}
@@ -167,7 +167,7 @@ ul.content-list {
}
.no-comments {
- opacity: .5;
+ opacity: 0.5;
}
}
@@ -196,8 +196,6 @@ ul.content-list {
// Content list using flexbox
.flex-list {
.flex-row {
- display: -webkit-flex;
- display: -ms-flexbox;
display: flex;
align-items: center;
white-space: nowrap;
diff --git a/app/assets/stylesheets/framework/logo.scss b/app/assets/stylesheets/framework/logo.scss
index 429cfbe7235..c5feefb8c54 100644
--- a/app/assets/stylesheets/framework/logo.scss
+++ b/app/assets/stylesheets/framework/logo.scss
@@ -9,7 +9,6 @@
}
.tanuki-logo {
-
.tanuki-left-ear,
.tanuki-right-ear,
.tanuki-nose {
@@ -34,7 +33,9 @@
.tanuki-left-cheek {
@include include-keyframes(animate-tanuki-left-cheek) {
- 0%, 10%, 100% {
+ 0%,
+ 10%,
+ 100% {
fill: lighten($tanuki-yellow, 25%);
}
@@ -46,11 +47,13 @@
.tanuki-left-eye {
@include include-keyframes(animate-tanuki-left-eye) {
- 10%, 80% {
+ 10%,
+ 80% {
fill: $tanuki-orange;
}
- 20%, 90% {
+ 20%,
+ 90% {
fill: lighten($tanuki-orange, 25%);
}
}
@@ -58,11 +61,13 @@
.tanuki-left-ear {
@include include-keyframes(animate-tanuki-left-ear) {
- 10%, 80% {
+ 10%,
+ 80% {
fill: $tanuki-red;
}
- 20%, 90% {
+ 20%,
+ 90% {
fill: lighten($tanuki-red, 25%);
}
}
@@ -70,11 +75,13 @@
.tanuki-nose {
@include include-keyframes(animate-tanuki-nose) {
- 20%, 70% {
+ 20%,
+ 70% {
fill: $tanuki-red;
}
- 30%, 80% {
+ 30%,
+ 80% {
fill: lighten($tanuki-red, 25%);
}
}
@@ -82,11 +89,13 @@
.tanuki-right-eye {
@include include-keyframes(animate-tanuki-right-eye) {
- 30%, 60% {
+ 30%,
+ 60% {
fill: $tanuki-orange;
}
- 40%, 70% {
+ 40%,
+ 70% {
fill: lighten($tanuki-orange, 25%);
}
}
@@ -94,11 +103,13 @@
.tanuki-right-ear {
@include include-keyframes(animate-tanuki-right-ear) {
- 30%, 60% {
+ 30%,
+ 60% {
fill: $tanuki-red;
}
- 40%, 70% {
+ 40%,
+ 70% {
fill: lighten($tanuki-red, 25%);
}
}
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index f708a26bb32..d6c4e68f68f 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -149,14 +149,6 @@
margin: 10px 0;
}
- // Border around images in issue and MR comments.
- img:not(.emoji) {
- border: 1px solid $white-normal;
- padding: 5px;
- margin: 5px 0;
- // Ensure that image does not exceed viewport
- max-height: calc(100vh - 100px);
- }
table:not(.js-syntax-highlight) {
@include markdown-table;
@@ -228,7 +220,7 @@
.cur {
.avatar {
- @include disableAllAnimation;
+ @include disable-all-animation;
border: 1px solid $white-light;
}
}
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index 9837b1a6bd0..fda7b6d5467 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -36,10 +36,6 @@
width: fit-content;
}
- tbody {
- background-color: $white-light;
- }
-
tr {
th {
border-bottom: solid 2px $gl-gray-100;
@@ -117,11 +113,6 @@
}
}
-@mixin dark-diff-match-line {
- color: $dark-diff-match-bg;
- background: $dark-diff-match-color;
-}
-
@mixin webkit-prefix($property, $value) {
#{'-webkit-' + $property}: $value;
#{$property}: $value;
@@ -129,16 +120,13 @@
/* http://phrappe.com/css/conditional-css-for-webkit-based-browsers/ */
@mixin on-webkit-only {
+ /* stylelint-disable-next-line media-feature-name-no-vendor-prefix */
@media screen and (-webkit-min-device-pixel-ratio: 0) {
@content;
}
}
@mixin keyframes($animation-name) {
- @-webkit-keyframes #{$animation-name} {
- @content;
- }
-
@keyframes #{$animation-name} {
@content;
}
@@ -178,12 +166,10 @@
width: 43px;
height: 30px;
transition-duration: 0.3s;
- -webkit-transform: translateZ(0);
- background: linear-gradient(
- to $gradient-direction,
- $gradient-color 45%,
- rgba($gradient-color, 0.4)
- );
+ transform: translateZ(0);
+ background: linear-gradient(to $gradient-direction,
+ $gradient-color 45%,
+ rgba($gradient-color, 0.4));
&.scrolling {
visibility: visible;
@@ -279,7 +265,6 @@
border: 1px solid $border-color;
color: $gl-text-color;
position: sticky;
- position: -webkit-sticky;
top: $header-height;
padding: $grid-size;
diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss
index ace46e32b18..3703b7568c8 100644
--- a/app/assets/stylesheets/framework/modal.scss
+++ b/app/assets/stylesheets/framework/modal.scss
@@ -111,10 +111,11 @@ body.modal-open {
flex-grow: 1;
height: 56px;
padding: $gl-btn-padding $gl-btn-padding 0;
+ text-align: right;
- > svg {
- float: right;
- height: 100%;
+ .illustration {
+ height: inherit;
+ width: initial;
}
}
}
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index bcd601e198a..ac673eafdc7 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -32,7 +32,7 @@
}
&::after {
- content: "\f078";
+ content: '\f078';
position: absolute;
z-index: 1;
text-align: center;
diff --git a/app/assets/stylesheets/framework/spinner.scss b/app/assets/stylesheets/framework/spinner.scss
new file mode 100644
index 00000000000..91fe75075dc
--- /dev/null
+++ b/app/assets/stylesheets/framework/spinner.scss
@@ -0,0 +1,51 @@
+@mixin spinner-color($color) {
+ border-color: rgba($color, 0.25);
+ border-top-color: $color;
+}
+
+@mixin spinner-size($size, $border-width) {
+ width: $size;
+ height: $size;
+ border-width: $border-width;
+ @include webkit-prefix(transform-origin, 50% 50% calc((#{$size} / 2) + #{$border-width}));
+}
+
+@keyframes spinner-rotate {
+ 0% {
+ transform: rotate(0);
+ }
+
+ 100% {
+ transform: rotate(360deg);
+ }
+}
+
+.spinner {
+ border-radius: 50%;
+ position: relative;
+ margin: 0 auto;
+ animation-name: spinner-rotate;
+ animation-duration: 0.6s;
+ animation-timing-function: linear;
+ animation-iteration-count: infinite;
+ border-style: solid;
+ display: inline-flex;
+ @include spinner-size(16px, 2px);
+ @include spinner-color($orange-600);
+
+ &.spinner-md {
+ @include spinner-size(32px, 3px);
+ }
+
+ &.spinner-lg {
+ @include spinner-size(64px, 4px);
+ }
+
+ &.spinner-dark {
+ @include spinner-color($gray-700);
+ }
+
+ &.spinner-light {
+ @include spinner-color($white);
+ }
+}
diff --git a/app/assets/stylesheets/framework/system_messages.scss b/app/assets/stylesheets/framework/system_messages.scss
new file mode 100644
index 00000000000..3d66136938f
--- /dev/null
+++ b/app/assets/stylesheets/framework/system_messages.scss
@@ -0,0 +1,110 @@
+.header-message,
+.footer-message {
+ padding: 0 15px;
+ border: 1px solid transparent;
+ border-radius: 0;
+ position: fixed;
+ left: 0;
+ width: 100%;
+ text-align: center;
+ margin: 0;
+ z-index: 1000;
+
+ p {
+ @include str-truncated(100%);
+ margin-top: 0;
+ margin-bottom: 0;
+ }
+}
+
+.header-message {
+ top: 0;
+ height: $system-header-height;
+ line-height: $system-header-height;
+}
+
+.footer-message {
+ bottom: 0;
+ height: $system-footer-height;
+ line-height: $system-footer-height;
+}
+
+.with-performance-bar {
+ .header-message {
+ top: $performance-bar-height;
+ }
+}
+
+// System Header
+.with-system-header {
+ // main navigation
+ // login page
+ .navbar-gitlab,
+ .fixed-top {
+ top: $system-header-height;
+ }
+
+ // left sidebar eg: project page
+ // right sidebar eg: MR page
+ .nav-sidebar,
+ .right-sidebar {
+ top: $system-header-height + $header-height;
+ }
+
+ .content-wrapper {
+ margin-top: $system-header-height + $header-height;
+ }
+
+ // Performance Bar
+ // System Header
+ &.with-performance-bar {
+ // main navigation
+ header.navbar-gitlab {
+ top: $performance-bar-height + $system-header-height;
+ }
+
+ .layout-page {
+ margin-top: $header-height + $performance-bar-height + $system-header-height;
+ }
+
+ // left sidebar eg: project page
+ // right sidebar eg: MR page
+ .nav-sidebar,
+ .right-sidebar {
+ top: $header-height + $performance-bar-height + $system-header-height;
+ }
+ }
+}
+
+// System Footer
+.with-system-footer {
+ // left sidebar eg: project page
+ // right sidebar eg: mr page
+ .nav-sidebar,
+ .right-sidebar,
+ // navless pages' footer eg: login page
+ // navless pages' footer border eg: login page
+ &.devise-layout-html body .footer-container,
+ &.devise-layout-html body hr.footer-fixed {
+ bottom: $system-footer-height;
+ }
+}
+
+.fullscreen-layout {
+ .header-message,
+ .footer-message {
+ position: static;
+ top: auto;
+ bottom: auto;
+ }
+
+ .content-wrapper {
+ .with-system-header & {
+ margin-top: 0;
+ }
+
+ .with-system-footer & {
+ margin-top: 0;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/framework/terms.scss b/app/assets/stylesheets/framework/terms.scss
index 3f4be8829d7..b07d6023127 100644
--- a/app/assets/stylesheets/framework/terms.scss
+++ b/app/assets/stylesheets/framework/terms.scss
@@ -13,7 +13,6 @@
.card {
.card-header {
- display: -webkit-flex;
display: flex;
align-items: center;
justify-content: space-between;
diff --git a/app/assets/stylesheets/framework/toggle.scss b/app/assets/stylesheets/framework/toggle.scss
index 8258da07e4d..5f8ac3b7e37 100644
--- a/app/assets/stylesheets/framework/toggle.scss
+++ b/app/assets/stylesheets/framework/toggle.scss
@@ -34,7 +34,7 @@
background: $gl-gray-400;
border-radius: 12px;
padding: 3px;
- transition: all .4s ease;
+ transition: all 0.4s ease;
&::selection,
&::before::selection,
@@ -52,7 +52,7 @@
left: 0;
border-radius: 9px;
background: $feature-toggle-color;
- transition: all .2s ease;
+ transition: all 0.2s ease;
&,
.toggle-icon-svg {
@@ -135,12 +135,18 @@
}
@keyframes animate-enabled {
- 0%, 35% { opacity: 0; }
+ 0%,
+
+ 35% { opacity: 0; }
+
100% { opacity: 1; }
}
@keyframes animate-disabled {
- 0%, 35% { opacity: 0; }
+ 0%,
+
+ 35% { opacity: 0; }
+
100% { opacity: 1; }
}
}
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index a08639936c0..55ce0d7004e 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -2,7 +2,7 @@
color: $gl-text-color;
word-wrap: break-word;
- [dir="auto"] {
+ [dir='auto'] {
text-align: initial;
}
@@ -49,13 +49,6 @@
word-wrap: normal;
}
- // Multi-line code blocks should scroll horizontally
- pre {
- code {
- white-space: pre;
- }
- }
-
kbd {
display: inline-block;
padding: 3px 5px;
@@ -166,6 +159,10 @@
overflow-x: auto;
border-radius: 2px;
+ // Multi-line code blocks should scroll horizontally
+ code {
+ white-space: pre;
+ }
&.plain-readme {
background: none;
@@ -227,8 +224,8 @@
}
}
- a[href*="/uploads/"],
- a[href*="storage.googleapis.com/google-code-attachments/"] {
+ a[href*='/uploads/'],
+ a[href*='storage.googleapis.com/google-code-attachments/'] {
&::before {
margin-right: 4px;
@@ -236,7 +233,7 @@
font-size: inherit;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
- content: "\f0c6";
+ content: '\f0c6';
}
&:hover::before {
@@ -303,11 +300,10 @@ body {
}
.page-title-empty {
- margin-top: 0;
+ margin: 12px 0;
line-height: 1.3;
font-size: 1.25em;
font-weight: $gl-font-weight-bold;
- margin: 12px 0;
}
h1,
@@ -375,6 +371,16 @@ code {
.md:not(.use-csslab) {
@include md-typography;
+
+ &:not(.wiki) {
+ img:not(.emoji) {
+ border: 1px solid $white-normal;
+ padding: 5px;
+ margin: 5px 0;
+ // Ensure that image does not exceed viewport
+ max-height: calc(100vh - 100px);
+ }
+ }
}
/**
@@ -417,6 +423,7 @@ h4 {
/**
* form text input i.e. search bar, comments, forms, etc.
*/
+/* stylelint-disable selector-no-vendor-prefix */
input,
textarea {
&::-webkit-input-placeholder {
@@ -441,5 +448,6 @@ textarea {
color: $gl-text-color-tertiary;
}
}
+/* stylelint-enable */
.lh-100 { line-height: 1; }
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 96dab609a13..1639e73a66a 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -41,13 +41,13 @@ $t-gray-a-04: rgba($black, 0.04);
$t-gray-a-06: rgba($black, 0.06);
$t-gray-a-08: rgba($black, 0.08);
-$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;
+$gl-gray-100: #ddd;
+$gl-gray-200: #ccc;
+$gl-gray-350: #aaa;
+$gl-gray-400: #999;
+$gl-gray-500: #777;
+$gl-gray-600: #666;
+$gl-gray-700: #555;
$green-50: #f1fdf6;
$green-100: #dcf5e7;
@@ -100,7 +100,7 @@ $red-950: #4b140b;
$gray-50: #fafafa;
$gray-100: #f2f2f2;
$gray-200: #dfdfdf;
-$gray-300: #cccccc;
+$gray-300: #ccc;
$gray-400: #bababa;
$gray-500: #a7a7a7;
$gray-600: #919191;
@@ -251,7 +251,7 @@ $gl-padding-top: 10px;
$gl-sidebar-padding: 22px;
$gl-bar-padding: 3px;
$input-horizontal-padding: 12px;
-$browserScrollbarSize: 10px;
+$browser-scrollbar-size: 10px;
/*
* Misc
@@ -265,6 +265,7 @@ $container-text-max-width: 540px;
$gl-avatar-size: 40px;
$border-radius-default: 4px;
$border-radius-small: 2px;
+$border-radius-large: 8px;
$default-icon-size: 18px;
$layout-link-gray: #7e7c7c;
$btn-side-margin: 10px;
@@ -276,6 +277,8 @@ $general-hover-transition-duration: 100ms;
$general-hover-transition-curve: linear;
$highlight-changes-color: rgb(235, 255, 232);
$performance-bar-height: 35px;
+$system-header-height: 35px;
+$system-footer-height: $system-header-height;
$flash-height: 52px;
$context-header-height: 60px;
$breadcrumb-min-height: 48px;
@@ -320,8 +323,8 @@ $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);
-$diff-image-info-color: gray;
-$diff-view-modes-color: gray;
+$diff-image-info-color: #808080;
+$diff-view-modes-color: #808080;
$diff-view-modes-border: #c1c1c1;
$diff-jagged-border-gradient-color: darken($white-normal, 8%);
@@ -405,6 +408,8 @@ $status-icon-size: 22px;
$award-emoji-menu-shadow: rgba(0, 0, 0, 0.175);
$award-emoji-positive-add-bg: #fed159;
$award-emoji-positive-add-lines: #bb9c13;
+$award-emoji-width: 376px;
+$award-emoji-width-xs: 300px;
/*
* Search Box
diff --git a/app/assets/stylesheets/framework/variables_overrides.scss b/app/assets/stylesheets/framework/variables_overrides.scss
index 1dfe2a69a2f..79d52932719 100644
--- a/app/assets/stylesheets/framework/variables_overrides.scss
+++ b/app/assets/stylesheets/framework/variables_overrides.scss
@@ -37,9 +37,11 @@ $h6-font-size: 14px;
$spacer: $grid-size;
$spacers: (
0: 0,
- 1: ($spacer * .5),
+ 1: ($spacer * 0.5),
2: ($spacer),
3: ($spacer * 2),
4: ($spacer * 3),
- 5: ($spacer * 4)
+ 5: ($spacer * 4),
+ 6: ($spacer * 8)
);
+$pagination-color: $gl-text-color;
diff --git a/app/assets/stylesheets/highlight/common.scss b/app/assets/stylesheets/highlight/common.scss
new file mode 100644
index 00000000000..ac3214a07d9
--- /dev/null
+++ b/app/assets/stylesheets/highlight/common.scss
@@ -0,0 +1,18 @@
+@import '../framework/variables';
+
+@mixin diff-background($background, $idiff, $border) {
+ background: $background;
+
+ &.line_content span.idiff {
+ background: $idiff;
+ }
+
+ &.diff-line-num {
+ border-color: $border;
+ }
+}
+
+@mixin dark-diff-match-line {
+ color: $dark-diff-match-bg;
+ background: $dark-diff-match-color;
+}
diff --git a/app/assets/stylesheets/highlight/embedded.scss b/app/assets/stylesheets/highlight/embedded.scss
index 44c8a1d39ec..74364ee4ddb 100644
--- a/app/assets/stylesheets/highlight/embedded.scss
+++ b/app/assets/stylesheets/highlight/embedded.scss
@@ -1,3 +1,3 @@
.code {
- @import "white_base";
+ @import 'white_base';
}
diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/themes/dark.scss
index 604f806dc58..16893dd047e 100644
--- a/app/assets/stylesheets/highlight/dark.scss
+++ b/app/assets/stylesheets/highlight/themes/dark.scss
@@ -1,5 +1,7 @@
/* https://github.com/MozMorris/tomorrow-pygments */
+@import "../common";
+
/*
* Dark syntax colors
*/
@@ -125,7 +127,7 @@ $dark-il: #de935f;
.diff-line-num.new,
.line_content.new {
- @include diff_background($dark-new-bg, $dark-new-idiff, $dark-border);
+ @include diff-background($dark-new-bg, $dark-new-idiff, $dark-border);
&::before,
a {
@@ -135,7 +137,7 @@ $dark-il: #de935f;
.diff-line-num.old,
.line_content.old {
- @include diff_background($dark-old-bg, $dark-old-idiff, $dark-border);
+ @include diff-background($dark-old-bg, $dark-old-idiff, $dark-border);
&::before,
a {
diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/themes/monokai.scss
index 8e2720511da..37fe61b925c 100644
--- a/app/assets/stylesheets/highlight/monokai.scss
+++ b/app/assets/stylesheets/highlight/themes/monokai.scss
@@ -1,5 +1,7 @@
/* https://github.com/richleland/pygments-css/blob/master/monokai.css */
+@import "../common";
+
/*
* Monokai Colors
*/
@@ -125,7 +127,7 @@ $monokai-gi: #a6e22e;
.diff-line-num.new,
.line_content.new {
- @include diff_background($monokai-new-bg, $monokai-new-idiff, $monokai-diff-border);
+ @include diff-background($monokai-new-bg, $monokai-new-idiff, $monokai-diff-border);
&::before,
a {
@@ -135,7 +137,7 @@ $monokai-gi: #a6e22e;
.diff-line-num.old,
.line_content.old {
- @include diff_background($monokai-old-bg, $monokai-old-idiff, $monokai-diff-border);
+ @include diff-background($monokai-old-bg, $monokai-old-idiff, $monokai-diff-border);
&::before,
a {
diff --git a/app/assets/stylesheets/highlight/none.scss b/app/assets/stylesheets/highlight/themes/none.scss
index 7ced4e82e66..b4217aac37a 100644
--- a/app/assets/stylesheets/highlight/none.scss
+++ b/app/assets/stylesheets/highlight/themes/none.scss
@@ -2,9 +2,9 @@
* None Syntax Colors
*/
+@import "../common";
-
-@mixin matchLine {
+@mixin match-line {
color: $black-transparent;
background-color: $white-normal;
}
@@ -45,7 +45,7 @@
&.match .line_content,
.new-nonewline.line_content,
.old-nonewline.line_content {
- @include matchLine;
+ @include match-line;
}
.diff-line-num {
@@ -121,7 +121,7 @@
}
&.match {
- @include matchLine;
+ @include match-line;
}
&.hll:not(.empty-cell) {
diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/themes/solarized-dark.scss
index cd1f0f6650f..a4e9eda22c9 100644
--- a/app/assets/stylesheets/highlight/solarized_dark.scss
+++ b/app/assets/stylesheets/highlight/themes/solarized-dark.scss
@@ -1,5 +1,7 @@
/* https://gist.github.com/qguv/7936275 */
+@import "../common";
+
/*
* Solarized dark colors
*/
@@ -129,7 +131,7 @@ $solarized-dark-il: #2aa198;
.diff-line-num.new,
.line_content.new {
- @include diff_background($solarized-dark-new-bg, $solarized-dark-new-idiff, $solarized-dark-border);
+ @include diff-background($solarized-dark-new-bg, $solarized-dark-new-idiff, $solarized-dark-border);
&::before,
a {
@@ -139,7 +141,7 @@ $solarized-dark-il: #2aa198;
.diff-line-num.old,
.line_content.old {
- @include diff_background($solarized-dark-old-bg, $solarized-dark-old-idiff, $solarized-dark-border);
+ @include diff-background($solarized-dark-old-bg, $solarized-dark-old-idiff, $solarized-dark-border);
&::before,
a {
diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/themes/solarized-light.scss
index 09c3ea36414..b604d1ccb6c 100644
--- a/app/assets/stylesheets/highlight/solarized_light.scss
+++ b/app/assets/stylesheets/highlight/themes/solarized-light.scss
@@ -1,5 +1,7 @@
/* https://gist.github.com/qguv/7936275 */
+@import "../common";
+
/*
* Solarized light syntax colors
*/
@@ -90,7 +92,7 @@ $solarized-light-vg: #268bd2;
$solarized-light-vi: #268bd2;
$solarized-light-il: #2aa198;
-@mixin matchLine {
+@mixin match-line {
color: $black-transparent;
background: $solarized-light-matchline-bg;
}
@@ -125,7 +127,7 @@ $solarized-light-il: #2aa198;
&.match .line_content,
&.old-nonewline .line_content,
&.new-nonewline .line_content {
- @include matchLine;
+ @include match-line;
}
td.diff-line-num.hll:not(.empty-cell),
@@ -136,7 +138,7 @@ $solarized-light-il: #2aa198;
.diff-line-num.new,
.line_content.new {
- @include diff_background($solarized-light-new-bg,
+ @include diff-background($solarized-light-new-bg,
$solarized-light-new-idiff, $solarized-light-border);
&::before,
@@ -147,7 +149,7 @@ $solarized-light-il: #2aa198;
.diff-line-num.old,
.line_content.old {
- @include diff_background($solarized-light-old-bg, $solarized-light-old-idiff, $solarized-light-border);
+ @include diff-background($solarized-light-old-bg, $solarized-light-old-idiff, $solarized-light-border);
&::before,
a {
@@ -168,7 +170,7 @@ $solarized-light-il: #2aa198;
}
.line_content.match {
- @include matchLine;
+ @include match-line;
}
&:not(.diff-expanded) + .diff-expanded,
diff --git a/app/assets/stylesheets/highlight/themes/white.scss b/app/assets/stylesheets/highlight/themes/white.scss
new file mode 100644
index 00000000000..7239086f649
--- /dev/null
+++ b/app/assets/stylesheets/highlight/themes/white.scss
@@ -0,0 +1,3 @@
+.code.white {
+ @import "../white_base";
+}
diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss
deleted file mode 100644
index 355c8d223f7..00000000000
--- a/app/assets/stylesheets/highlight/white.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-.code.white {
- @import "white_base";
-}
diff --git a/app/assets/stylesheets/highlight/white_base.scss b/app/assets/stylesheets/highlight/white_base.scss
index 90a5250c247..ee0ec94c636 100644
--- a/app/assets/stylesheets/highlight/white_base.scss
+++ b/app/assets/stylesheets/highlight/white_base.scss
@@ -1,5 +1,7 @@
/* https://github.com/aahan/pygments-github-style */
+@import './common';
+
/*
* White Syntax Colors
*/
@@ -35,16 +37,16 @@ $white-kt: #458;
$white-m: #099;
$white-s: #d14;
$white-n: #333;
-$white-na: teal;
+$white-na: #008080;
$white-nb: #0086b3;
$white-nc: #458;
-$white-no: teal;
-$white-ni: purple;
+$white-no: #008080;
+$white-ni: #800080;
$white-ne: #900;
$white-nf: #900;
$white-nn: #555;
-$white-nt: navy;
-$white-nv: teal;
+$white-nt: #000080;
+$white-nv: #008080;
$white-w: #bbb;
$white-mf: #099;
$white-mh: #099;
@@ -62,20 +64,20 @@ $white-sr: #009926;
$white-s1: #d14;
$white-ss: #990073;
$white-bp: #999;
-$white-vc: teal;
-$white-vg: teal;
-$white-vi: teal;
+$white-vc: #008080;
+$white-vg: #008080;
+$white-vi: #008080;
$white-il: #099;
$white-gc-color: #999;
$white-gc-bg: #eaf2f5;
-@mixin matchLine {
+@mixin match-line {
color: $black-transparent;
background-color: $gray-light;
}
- // Line numbers
+// Line numbers
.line-numbers,
.diff-line-num {
background-color: $gray-light;
@@ -101,11 +103,10 @@ pre.code,
// Diff line
.line_holder {
-
&.match .line_content,
.new-nonewline.line_content,
.old-nonewline.line_content {
- @include matchLine;
+ @include match-line;
}
.diff-line-num {
@@ -185,7 +186,7 @@ pre.code,
}
&.match {
- @include matchLine;
+ @include match-line;
}
&.hll:not(.empty-cell) {
@@ -199,25 +200,38 @@ pre .hll {
background-color: $white-pre-hll-bg !important;
}
- // Search result highlight
+// Search result highlight
span.highlight_word {
background-color: $white-highlight !important;
}
- // Links to URLs, emails, or dependencies
+// Links to URLs, emails, or dependencies
.line a {
color: $white-nb;
}
.hll { background-color: $white-hll-bg; }
-.c { color: $white-c; font-style: italic; }
-.err { color: $white-err; background-color: $white-err-bg; }
+
+.c { color: $white-c;
+ font-style: italic; }
+
+.err { color: $white-err;
+ background-color: $white-err-bg; }
.k { font-weight: $gl-font-weight-bold; }
.o { font-weight: $gl-font-weight-bold; }
-.cm { color: $white-cm; font-style: italic; }
-.cp { color: $white-cp; font-weight: $gl-font-weight-bold; }
-.c1 { color: $white-c1; font-style: italic; }
-.cs { color: $white-cs; font-weight: $gl-font-weight-bold; font-style: italic; }
+
+.cm { color: $white-cm;
+ font-style: italic; }
+
+.cp { color: $white-cp;
+ font-weight: $gl-font-weight-bold; }
+
+.c1 { color: $white-c1;
+ font-style: italic; }
+
+.cs { color: $white-cs;
+ font-weight: $gl-font-weight-bold;
+ font-style: italic; }
.gd {
color: $white-gd;
@@ -246,24 +260,34 @@ span.highlight_word {
.go { color: $white-go; }
.gp { color: $white-gp; }
.gs { font-weight: $gl-font-weight-bold; }
-.gu { color: $white-gu; font-weight: $gl-font-weight-bold; }
+
+.gu { color: $white-gu;
+ font-weight: $gl-font-weight-bold; }
.gt { color: $white-gt; }
.kc { font-weight: $gl-font-weight-bold; }
.kd { font-weight: $gl-font-weight-bold; }
.kn { font-weight: $gl-font-weight-bold; }
.kp { font-weight: $gl-font-weight-bold; }
.kr { font-weight: $gl-font-weight-bold; }
-.kt { color: $white-kt; font-weight: $gl-font-weight-bold; }
+
+.kt { color: $white-kt;
+ font-weight: $gl-font-weight-bold; }
.m { color: $white-m; }
.s { color: $white-s; }
.n { color: $white-n; }
.na { color: $white-na; }
.nb { color: $white-nb; }
-.nc { color: $white-nc; font-weight: $gl-font-weight-bold; }
+
+.nc { color: $white-nc;
+ font-weight: $gl-font-weight-bold; }
.no { color: $white-no; }
.ni { color: $white-ni; }
-.ne { color: $white-ne; font-weight: $gl-font-weight-bold; }
-.nf { color: $white-nf; font-weight: $gl-font-weight-bold; }
+
+.ne { color: $white-ne;
+ font-weight: $gl-font-weight-bold; }
+
+.nf { color: $white-nf;
+ font-weight: $gl-font-weight-bold; }
.nn { color: $white-nn; }
.nt { color: $white-nt; }
.nv { color: $white-nv; }
@@ -289,4 +313,6 @@ span.highlight_word {
.vg { color: $white-vg; }
.vi { color: $white-vi; }
.il { color: $white-il; }
-.gc { color: $white-gc-color; background-color: $white-gc-bg; }
+
+.gc { color: $white-gc-color;
+ background-color: $white-gc-bg; }
diff --git a/app/assets/stylesheets/mailers/highlighted_diff_email.scss b/app/assets/stylesheets/mailers/highlighted_diff_email.scss
index 8b234a5a656..33c114838c2 100644
--- a/app/assets/stylesheets/mailers/highlighted_diff_email.scss
+++ b/app/assets/stylesheets/mailers/highlighted_diff_email.scss
@@ -1,4 +1,4 @@
-@import "framework/variables";
+@import 'framework/variables';
// This file is largely copied from `highlight/white.scss`, but modified to
// avoid all descendant selectors (`table td`). This is because the CSS inlining
@@ -40,16 +40,16 @@ $highlighted-kt: #458;
$highlighted-m: #099;
$highlighted-s: #d14;
$highlighted-n: #333;
-$highlighted-na: teal;
+$highlighted-na: #008080;
$highlighted-nb: #0086b3;
$highlighted-nc: #458;
-$highlighted-no: teal;
-$highlighted-ni: purple;
+$highlighted-no: #008080;
+$highlighted-ni: #800080;
$highlighted-ne: #900;
$highlighted-nf: #900;
$highlighted-nn: #555;
-$highlighted-nt: navy;
-$highlighted-nv: teal;
+$highlighted-nt: #000080;
+$highlighted-nv: #008080;
$highlighted-w: #bbb;
$highlighted-mf: #099;
$highlighted-mh: #099;
@@ -67,9 +67,9 @@ $highlighted-sr: #009926;
$highlighted-s1: #d14;
$highlighted-ss: #990073;
$highlighted-bp: #999;
-$highlighted-vc: teal;
-$highlighted-vg: teal;
-$highlighted-vi: teal;
+$highlighted-vc: #008080;
+$highlighted-vg: #008080;
+$highlighted-vi: #008080;
$highlighted-il: #099;
$highlighted-gc: #999;
$highlighted-gc-bg: #eaf2f5;
@@ -151,14 +151,27 @@ span.highlight_word {
}
.hll { background-color: $highlighted-hll-bg; }
-.c { color: $highlighted-c; font-style: italic; }
-.err { color: $highlighted-err; background-color: $highlighted-err-bg; }
+
+.c { color: $highlighted-c;
+ font-style: italic; }
+
+.err { color: $highlighted-err;
+ background-color: $highlighted-err-bg; }
.k { font-weight: $gl-font-weight-bold; }
.o { font-weight: $gl-font-weight-bold; }
-.cm { color: $highlighted-cm; font-style: italic; }
-.cp { color: $highlighted-cp; font-weight: $gl-font-weight-bold; }
-.c1 { color: $highlighted-c1; font-style: italic; }
-.cs { color: $highlighted-cs; font-weight: $gl-font-weight-bold; font-style: italic; }
+
+.cm { color: $highlighted-cm;
+ font-style: italic; }
+
+.cp { color: $highlighted-cp;
+ font-weight: $gl-font-weight-bold; }
+
+.c1 { color: $highlighted-c1;
+ font-style: italic; }
+
+.cs { color: $highlighted-cs;
+ font-weight: $gl-font-weight-bold;
+ font-style: italic; }
.gd {
color: $highlighted-gd;
@@ -187,24 +200,34 @@ span.highlight_word {
.go { color: $highlighted-go; }
.gp { color: $highlighted-gp; }
.gs { font-weight: $gl-font-weight-bold; }
-.gu { color: $highlighted-gu; font-weight: $gl-font-weight-bold; }
+
+.gu { color: $highlighted-gu;
+ font-weight: $gl-font-weight-bold; }
.gt { color: $highlighted-gt; }
.kc { font-weight: $gl-font-weight-bold; }
.kd { font-weight: $gl-font-weight-bold; }
.kn { font-weight: $gl-font-weight-bold; }
.kp { font-weight: $gl-font-weight-bold; }
.kr { font-weight: $gl-font-weight-bold; }
-.kt { color: $highlighted-kt; font-weight: $gl-font-weight-bold; }
+
+.kt { color: $highlighted-kt;
+ font-weight: $gl-font-weight-bold; }
.m { color: $highlighted-m; }
.s { color: $highlighted-s; }
.n { color: $highlighted-n; }
.na { color: $highlighted-na; }
.nb { color: $highlighted-nb; }
-.nc { color: $highlighted-nc; font-weight: $gl-font-weight-bold; }
+
+.nc { color: $highlighted-nc;
+ font-weight: $gl-font-weight-bold; }
.no { color: $highlighted-no; }
.ni { color: $highlighted-ni; }
-.ne { color: $highlighted-ne; font-weight: $gl-font-weight-bold; }
-.nf { color: $highlighted-nf; font-weight: $gl-font-weight-bold; }
+
+.ne { color: $highlighted-ne;
+ font-weight: $gl-font-weight-bold; }
+
+.nf { color: $highlighted-nf;
+ font-weight: $gl-font-weight-bold; }
.nn { color: $highlighted-nn; }
.nt { color: $highlighted-nt; }
.nv { color: $highlighted-nv; }
@@ -230,4 +253,6 @@ span.highlight_word {
.vg { color: $highlighted-vg; }
.vi { color: $highlighted-vi; }
.il { color: $highlighted-il; }
-.gc { color: $highlighted-gc; background-color: $highlighted-gc-bg; }
+
+.gc { color: $highlighted-gc;
+ background-color: $highlighted-gc-bg; }
diff --git a/app/assets/stylesheets/page_bundles/ide.scss b/app/assets/stylesheets/page_bundles/ide.scss
index 2ac98b5d18f..a80158943c6 100644
--- a/app/assets/stylesheets/page_bundles/ide.scss
+++ b/app/assets/stylesheets/page_bundles/ide.scss
@@ -682,25 +682,6 @@ $ide-commit-header-height: 48px;
flex: 1;
}
-.drag-handle {
- position: absolute;
- top: 0;
- bottom: 0;
- width: 4px;
-
- &:hover {
- background-color: $white-normal;
- }
-
- &.drag-right {
- right: 0;
- }
-
- &.drag-left {
- left: 0;
- }
-}
-
.ide-commit-list-container {
display: flex;
flex: 1;
diff --git a/app/assets/stylesheets/page_bundles/xterm.scss b/app/assets/stylesheets/page_bundles/xterm.scss
index 7f040ac9b96..de3f2a1177d 100644
--- a/app/assets/stylesheets/page_bundles/xterm.scss
+++ b/app/assets/stylesheets/page_bundles/xterm.scss
@@ -6,11 +6,11 @@
$black: #000;
$red: #ea1010;
- $green: #009900;
- $yellow: #999900;
+ $green: #090;
+ $yellow: #990;
$blue: #0073e6;
$magenta: #d411d4;
- $cyan: #009999;
+ $cyan: #099;
$white: #ccc;
$l-black: #373b41;
$l-red: #ff6161;
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index a9324ba2ed0..81216b2b98e 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -3,7 +3,6 @@
}
.user-can-drag {
- cursor: -webkit-grab;
cursor: grab;
}
@@ -12,12 +11,8 @@
opacity: 1 !important;
* {
- -webkit-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
user-select: none;
// !important to make sure no style can override this when dragging
- cursor: -webkit-grabbing !important;
cursor: grabbing !important;
}
}
@@ -501,7 +496,6 @@
}
.add-issues-modal {
- display: -webkit-flex;
display: flex;
position: fixed;
top: 0;
@@ -513,9 +507,7 @@
}
.add-issues-container {
- display: -webkit-flex;
display: flex;
- -webkit-flex-direction: column;
flex-direction: column;
width: 90vw;
height: 85vh;
@@ -528,16 +520,12 @@
box-shadow: 0 2px 12px rgba($black, 0.5);
.empty-state {
- display: -webkit-flex;
display: flex;
- -webkit-flex: 1;
flex: 1;
margin-top: 0;
&.add-issues-empty-state-filter {
- -webkit-flex-direction: column;
flex-direction: column;
- -webkit-justify-content: center;
justify-content: center;
}
@@ -566,11 +554,9 @@
}
.add-issues-search {
- display: -webkit-flex;
display: flex;
.issues-filters {
- -webkit-flex: 1;
flex: 1;
}
}
@@ -588,9 +574,7 @@
}
.add-issues-list {
- display: -webkit-flex;
display: flex;
- -webkit-flex: 1;
flex: 1;
padding-top: 3px;
margin-left: -$gl-vert-padding;
@@ -609,7 +593,6 @@
}
.add-issues-list-loading {
- -webkit-align-self: center;
align-self: center;
width: 100%;
padding-left: $gl-vert-padding;
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 65f46e3852a..fa5a182243c 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -75,7 +75,11 @@
@include build-trace-top-bar(35px);
&.has-archived-block {
- top: $header-height + $performance-bar-height + 28px;
+ top: $header-height + 28px;
+
+ .with-performance-bar & {
+ top: $header-height + $performance-bar-height + 28px;
+ }
}
&.affix {
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index 11966931a6c..670e320dbc2 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -342,11 +342,11 @@
}
&.invalid {
- @include status-color($gray-dark, color("gray"), $gray-darkest);
+ @include status-color($gray-dark, color('gray'), $gray-darkest);
border-color: $gray-darkest;
&:not(span):hover {
- color: color("gray");
+ color: color('gray');
}
}
}
diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss
index ec2108b15be..2b932d164a5 100644
--- a/app/assets/stylesheets/pages/cycle_analytics.scss
+++ b/app/assets/stylesheets/pages/cycle_analytics.scss
@@ -330,7 +330,6 @@
// Custom Styles for stage items
.item-build-component {
-
.item-title {
.icon-build-status {
float: left;
diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss
index 37ed5ae674a..cb5f1a84005 100644
--- a/app/assets/stylesheets/pages/detail_page.scss
+++ b/app/assets/stylesheets/pages/detail_page.scss
@@ -34,7 +34,6 @@
.detail-page-header-actions {
align-self: center;
- flex-shrink: 0;
flex: 0 0 auto;
@include media-breakpoint-down(xs) {
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index e3b98b26a11..0dbbe9e4c25 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -9,7 +9,6 @@
@media (min-width: map-get($grid-breakpoints, md)) {
$mr-file-header-top: $mr-version-controls-height + $header-height + $mr-tabs-height;
- position: -webkit-sticky;
position: sticky;
top: $mr-file-header-top;
z-index: 102;
@@ -239,22 +238,18 @@
img {
border: 1px solid $white-light;
- background-image: linear-gradient(
- 45deg,
- $border-color 25%,
- transparent 25%,
- transparent 75%,
- $border-color 75%,
- $border-color 100%
- ),
- linear-gradient(
- 45deg,
- $border-color 25%,
- transparent 25%,
- transparent 75%,
- $border-color 75%,
- $border-color 100%
- );
+ background-image: linear-gradient(45deg,
+ $border-color 25%,
+ transparent 25%,
+ transparent 75%,
+ $border-color 75%,
+ $border-color 100%),
+ linear-gradient(45deg,
+ $border-color 25%,
+ transparent 25%,
+ transparent 75%,
+ $border-color 75%,
+ $border-color 100%);
background-size: 10px 10px;
background-position: 0 0, 5px 5px;
max-width: 100%;
@@ -510,10 +505,10 @@
.diff-stats {
align-items: center;
- padding: 0 .25rem;
+ padding: 0 0.25rem;
.diff-stats-group {
- padding: 0 .25rem;
+ padding: 0 0.25rem;
}
svg.diff-stats-icon {
@@ -522,7 +517,7 @@
&.is-compare-versions-header {
.diff-stats-group {
- padding: 0 .5rem;
+ padding: 0 0.5rem;
}
}
}
@@ -602,18 +597,6 @@
}
}
-@mixin diff_background($background, $idiff, $border) {
- background: $background;
-
- &.line_content span.idiff {
- background: $idiff;
- }
-
- &.diff-line-num {
- border-color: $border;
- }
-}
-
.files {
.diff-file:last-child {
margin-bottom: 0;
@@ -738,7 +721,6 @@
}
@include media-breakpoint-up(sm) {
- position: -webkit-sticky;
position: sticky;
top: $header-height;
background-color: $white-light;
@@ -846,34 +828,26 @@
width: 100%;
height: 10px;
background-color: $white-light;
- background-image: linear-gradient(
- 45deg,
- transparent,
- transparent 73%,
- $diff-jagged-border-gradient-color 75%,
- $white-light 80%
- ),
- linear-gradient(
- 225deg,
- transparent,
- transparent 73%,
- $diff-jagged-border-gradient-color 75%,
- $white-light 80%
- ),
- linear-gradient(
- 135deg,
- transparent,
- transparent 73%,
- $diff-jagged-border-gradient-color 75%,
- $white-light 80%
- ),
- linear-gradient(
- -45deg,
- transparent,
- transparent 73%,
- $diff-jagged-border-gradient-color 75%,
- $white-light 80%
- );
+ background-image: linear-gradient(45deg,
+ transparent,
+ transparent 73%,
+ $diff-jagged-border-gradient-color 75%,
+ $white-light 80%),
+ linear-gradient(225deg,
+ transparent,
+ transparent 73%,
+ $diff-jagged-border-gradient-color 75%,
+ $white-light 80%),
+ linear-gradient(135deg,
+ transparent,
+ transparent 73%,
+ $diff-jagged-border-gradient-color 75%,
+ $white-light 80%),
+ linear-gradient(-45deg,
+ transparent,
+ transparent 73%,
+ $diff-jagged-border-gradient-color 75%,
+ $white-light 80%);
background-position: 5px 5px, 0 5px, 0 5px, 5px 5px;
background-size: 10px 10px;
background-repeat: repeat;
@@ -916,7 +890,7 @@
}
}
-.files:not([data-can-create-note="true"]) .frame {
+.files:not([data-can-create-note='true']) .frame {
cursor: auto;
}
@@ -925,15 +899,14 @@
.btn-transparent.image-diff-overlay-add-comment {
position: relative;
cursor: image-url('illustrations/image_comment_light_cursor.svg')
- $image-comment-cursor-left-offset $image-comment-cursor-top-offset,
+ $image-comment-cursor-left-offset $image-comment-cursor-top-offset,
auto;
// Retina cursor
- cursor: -webkit-image-set(
- image-url('illustrations/image_comment_light_cursor.svg') 1x,
- image-url('illustrations/image_comment_light_cursor@2x.svg') 2x
- )
- $image-comment-cursor-left-offset $image-comment-cursor-top-offset,
+ // scss-lint:disable DuplicateProperty
+ cursor: image-set(image-url('illustrations/image_comment_light_cursor.svg') 1x,
+ image-url('illustrations/image_comment_light_cursor@2x.svg') 2x)
+ $image-comment-cursor-left-offset $image-comment-cursor-top-offset,
auto;
.comment-indicator {
@@ -1038,12 +1011,29 @@
}
.diff-tree-list {
- width: 320px;
+ position: sticky;
+ $top-pos: $header-height + $mr-tabs-height + $mr-version-controls-height + 10px;
+ top: $header-height + $mr-tabs-height + $mr-version-controls-height + 10px;
+ max-height: calc(100vh - #{$top-pos});
+ padding-right: $gl-padding;
+ z-index: 202;
+
+ .with-performance-bar & {
+ $performance-bar-top-pos: $performance-bar-height + $top-pos;
+ top: $performance-bar-top-pos;
+ max-height: calc(100vh - #{$performance-bar-top-pos});
+ }
+
+ .drag-handle {
+ bottom: 16px;
+ transform: translateX(-6px);
+ }
}
.diff-files-holder {
flex: 1;
min-width: 0;
+ z-index: 201;
}
.compare-versions-container {
@@ -1051,23 +1041,12 @@
}
.tree-list-holder {
- position: -webkit-sticky;
- position: sticky;
- $top-pos: $header-height + $mr-tabs-height + $mr-version-controls-height + 10px;
- top: $header-height + $mr-tabs-height + $mr-version-controls-height + 10px;
- max-height: calc(100vh - #{$top-pos});
- padding-right: $gl-padding;
+ height: 100%;
.file-row {
margin-left: 0;
margin-right: 0;
}
-
- .with-performance-bar & {
- $performance-bar-top-pos: $performance-bar-height + $top-pos;
- top: $performance-bar-top-pos;
- max-height: calc(100vh - #{$performance-bar-top-pos});
- }
}
.tree-list-scroll {
diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss
index 5a988b184b6..655b297295a 100644
--- a/app/assets/stylesheets/pages/editor.scss
+++ b/app/assets/stylesheets/pages/editor.scss
@@ -182,9 +182,8 @@
.template-selector-dropdowns-wrap {
display: inline-block;
- margin-left: 8px;
- vertical-align: top;
margin: 5px 0 0 8px;
+ vertical-align: top;
@media(max-width: map-get($grid-breakpoints, md)-1) {
display: block;
diff --git a/app/assets/stylesheets/pages/graph.scss b/app/assets/stylesheets/pages/graph.scss
index 83b1680512d..3febf4cf826 100644
--- a/app/assets/stylesheets/pages/graph.scss
+++ b/app/assets/stylesheets/pages/graph.scss
@@ -71,12 +71,10 @@
.svg-graph-container-with-grab {
cursor: grab;
- cursor: -webkit-grab;
}
.svg-graph-container-grabbed {
cursor: grabbing;
- cursor: -webkit-grabbing;
}
@keyframes flickerAnimation {
diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss
index 2c23f31c240..161d4dbfb22 100644
--- a/app/assets/stylesheets/pages/help.scss
+++ b/app/assets/stylesheets/pages/help.scss
@@ -30,7 +30,7 @@
.key {
@extend .badge.badge-pill;
background-color: $label-inverse-bg;
- font: 11px Consolas, "Liberation Mono", Menlo, Courier, monospace;
+ font: 11px Consolas, 'Liberation Mono', Menlo, Courier, monospace;
padding: 3px 5px;
}
}
diff --git a/app/assets/stylesheets/pages/import.scss b/app/assets/stylesheets/pages/import.scss
index a4f76a9495a..7f800367cad 100644
--- a/app/assets/stylesheets/pages/import.scss
+++ b/app/assets/stylesheets/pages/import.scss
@@ -1,20 +1,51 @@
-.import-jobs-from-col,
.import-jobs-to-col {
- width: 40%;
+ width: 39%;
}
.import-jobs-status-col {
- width: 20%;
+ width: 15%;
}
-.btn-import {
- .loading-icon {
- display: none;
+.import-jobs-cta-col {
+ width: 1%;
+}
+
+.import-project-name-input {
+ border-radius: 0 $border-radius-default $border-radius-default 0;
+ position: relative;
+ left: -1px;
+ max-width: 300px;
+}
+
+.import-namespace-select {
+ width: auto !important;
+
+ > .select2-choice {
+ border-radius: $border-radius-default 0 0 $border-radius-default;
+ position: relative;
+ left: 1px;
}
+}
- &.is-loading {
- .loading-icon {
- display: inline-block;
- }
+.import-slash-divider {
+ background-color: $gray-lightest;
+ border: 1px solid $border-color;
+}
+
+.import-row {
+ height: 55px;
+}
+
+.import-table {
+ .import-jobs-from-col,
+ .import-jobs-to-col,
+ .import-jobs-status-col,
+ .import-jobs-cta-col {
+ border-bottom-width: 1px;
+ padding-left: $gl-padding;
}
}
+
+.import-projects-loading-icon {
+ margin-top: $gl-padding-32;
+}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index e0bdc1341b1..3796ef47c32 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -136,7 +136,7 @@
color: $blue-800;
.avatar {
- border-color: rgba($gray-normal, .2);
+ border-color: rgba($gray-normal, 0.2);
}
}
@@ -223,7 +223,7 @@
}
a.edit-link:not([href]):hover {
- color: rgba($gray-normal, .2);
+ color: rgba($gray-normal, 0.2);
}
.lock-edit, // uses same style, different js behaviour
@@ -711,14 +711,11 @@
.issuable-list {
li {
-
.issue-box {
- display: -webkit-flex;
display: flex;
}
.issuable-info-container {
- -webkit-flex: 1;
flex: 1;
display: flex;
padding-right: $gl-padding;
@@ -813,7 +810,6 @@
.sidebar-collapsed-icon {
-
> .stopwatch-svg {
display: inline-block;
}
@@ -871,11 +867,11 @@
}
.help-state-toggle-enter-active {
- transition: all .8s ease;
+ transition: all 0.8s ease;
}
.help-state-toggle-leave-active {
- transition: all .5s ease;
+ transition: all 0.5s ease;
}
.help-state-toggle-enter,
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 0037364978c..9f30495a7ef 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -58,8 +58,6 @@ form.edit-issue {
}
ul.related-merge-requests > li {
- display: -ms-flexbox;
- display: -webkit-flex;
display: flex;
align-items: center;
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index 2372640277e..75d219320ef 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -95,13 +95,11 @@
.prioritized-labels & {
box-shadow: 0 1px 2px $issue-boards-card-shadow;
cursor: move;
- cursor: -webkit-grab;
- cursor: -moz-grab;
+ cursor: grab;
border: 0;
&:active {
- cursor: -webkit-grabbing;
- cursor: -moz-grabbing;
+ cursor: grabbing;
}
}
}
diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss
index 67d7a8175ac..22a515cbdaa 100644
--- a/app/assets/stylesheets/pages/login.scss
+++ b/app/assets/stylesheets/pages/login.scss
@@ -120,7 +120,6 @@
}
.new-session-tabs {
- display: -webkit-flex;
display: flex;
box-shadow: 0 0 0 1px $border-color;
border-top-right-radius: $border-radius-default;
@@ -190,7 +189,7 @@
margin-top: 16px;
}
- input[type="submit"] {
+ input[type='submit'] {
@extend .btn-block;
margin-bottom: 0;
}
diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss
index 99609a96976..eb32beb0972 100644
--- a/app/assets/stylesheets/pages/members.scss
+++ b/app/assets/stylesheets/pages/members.scss
@@ -27,7 +27,6 @@
.controls {
@include media-breakpoint-up(sm) {
- display: -webkit-flex;
display: flex;
}
diff --git a/app/assets/stylesheets/pages/merge_conflicts.scss b/app/assets/stylesheets/pages/merge_conflicts.scss
index e0f7d075fc7..278a9014458 100644
--- a/app/assets/stylesheets/pages/merge_conflicts.scss
+++ b/app/assets/stylesheets/pages/merge_conflicts.scss
@@ -20,81 +20,81 @@ $colors: (
white-header-not-chosen : #f0f0f0,
white-line-not-chosen : $gray-light,
- dark-header-head-neutral : rgba(#3f3, .2),
- dark-line-head-neutral : rgba(#3f3, .1),
+ dark-header-head-neutral : rgba(#3f3, 0.2),
+ dark-line-head-neutral : rgba(#3f3, 0.1),
dark-button-head-neutral : #40874f,
- dark-header-head-chosen : rgba(#3f3, .33),
- dark-line-head-chosen : rgba(#3f3, .2),
+ dark-header-head-chosen : rgba(#3f3, 0.33),
+ dark-line-head-chosen : rgba(#3f3, 0.2),
dark-button-head-chosen : #258537,
- dark-header-origin-neutral : rgba(#2878c9, .4),
- dark-line-origin-neutral : rgba(#2878c9, .3),
+ dark-header-origin-neutral : rgba(#2878c9, 0.4),
+ dark-line-origin-neutral : rgba(#2878c9, 0.3),
dark-button-origin-neutral : #2a5c8c,
- dark-header-origin-chosen : rgba(#2878c9, .6),
- dark-line-origin-chosen : rgba(#2878c9, .4),
+ dark-header-origin-chosen : rgba(#2878c9, 0.6),
+ dark-line-origin-chosen : rgba(#2878c9, 0.4),
dark-button-origin-chosen : #1d6cbf,
- dark-header-not-chosen : rgba(#fff, .25),
- dark-line-not-chosen : rgba(#fff, .1),
+ dark-header-not-chosen : rgba(#fff, 0.25),
+ dark-line-not-chosen : rgba(#fff, 0.1),
- monokai-header-head-neutral : rgba(#a6e22e, .25),
- monokai-line-head-neutral : rgba(#a6e22e, .1),
+ monokai-header-head-neutral : rgba(#a6e22e, 0.25),
+ monokai-line-head-neutral : rgba(#a6e22e, 0.1),
monokai-button-head-neutral : #376b20,
- monokai-header-head-chosen : rgba(#a6e22e, .4),
- monokai-line-head-chosen : rgba(#a6e22e, .25),
+ monokai-header-head-chosen : rgba(#a6e22e, 0.4),
+ monokai-line-head-chosen : rgba(#a6e22e, 0.25),
monokai-button-head-chosen : #39800d,
- monokai-header-origin-neutral : rgba(#60d9f1, .35),
- monokai-line-origin-neutral : rgba(#60d9f1, .15),
+ monokai-header-origin-neutral : rgba(#60d9f1, 0.35),
+ monokai-line-origin-neutral : rgba(#60d9f1, 0.15),
monokai-button-origin-neutral : #38848c,
- monokai-header-origin-chosen : rgba(#60d9f1, .5),
- monokai-line-origin-chosen : rgba(#60d9f1, .35),
+ monokai-header-origin-chosen : rgba(#60d9f1, 0.5),
+ monokai-line-origin-chosen : rgba(#60d9f1, 0.35),
monokai-button-origin-chosen : #3ea4b2,
- monokai-header-not-chosen : rgba(#76715d, .24),
- monokai-line-not-chosen : rgba(#76715d, .1),
+ monokai-header-not-chosen : rgba(#76715d, 0.24),
+ monokai-line-not-chosen : rgba(#76715d, 0.1),
- solarized-light-header-head-neutral : rgba(#859900, .37),
- solarized-light-line-head-neutral : rgba(#859900, .2),
+ solarized-light-header-head-neutral : rgba(#859900, 0.37),
+ solarized-light-line-head-neutral : rgba(#859900, 0.2),
solarized-light-button-head-neutral : #afb262,
- solarized-light-header-head-chosen : rgba(#859900, .5),
- solarized-light-line-head-chosen : rgba(#859900, .37),
+ solarized-light-header-head-chosen : rgba(#859900, 0.5),
+ solarized-light-line-head-chosen : rgba(#859900, 0.37),
solarized-light-button-head-chosen : #94993d,
- solarized-light-header-origin-neutral : rgba(#2878c9, .37),
- solarized-light-line-origin-neutral : rgba(#2878c9, .15),
+ solarized-light-header-origin-neutral : rgba(#2878c9, 0.37),
+ solarized-light-line-origin-neutral : rgba(#2878c9, 0.15),
solarized-light-button-origin-neutral : #60a1bf,
- solarized-light-header-origin-chosen : rgba(#2878c9, .6),
- solarized-light-line-origin-chosen : rgba(#2878c9, .37),
+ solarized-light-header-origin-chosen : rgba(#2878c9, 0.6),
+ solarized-light-line-origin-chosen : rgba(#2878c9, 0.37),
solarized-light-button-origin-chosen : #2482b2,
- solarized-light-header-not-chosen : rgba(#839496, .37),
- solarized-light-line-not-chosen : rgba(#839496, .2),
+ solarized-light-header-not-chosen : rgba(#839496, 0.37),
+ solarized-light-line-not-chosen : rgba(#839496, 0.2),
- solarized-dark-header-head-neutral : rgba(#859900, .35),
- solarized-dark-line-head-neutral : rgba(#859900, .15),
+ solarized-dark-header-head-neutral : rgba(#859900, 0.35),
+ solarized-dark-line-head-neutral : rgba(#859900, 0.15),
solarized-dark-button-head-neutral : #376b20,
- solarized-dark-header-head-chosen : rgba(#859900, .5),
- solarized-dark-line-head-chosen : rgba(#859900, .35),
+ solarized-dark-header-head-chosen : rgba(#859900, 0.5),
+ solarized-dark-line-head-chosen : rgba(#859900, 0.35),
solarized-dark-button-head-chosen : #39800d,
- solarized-dark-header-origin-neutral : rgba(#2878c9, .35),
- solarized-dark-line-origin-neutral : rgba(#2878c9, .15),
+ solarized-dark-header-origin-neutral : rgba(#2878c9, 0.35),
+ solarized-dark-line-origin-neutral : rgba(#2878c9, 0.15),
solarized-dark-button-origin-neutral : #086799,
- solarized-dark-header-origin-chosen : rgba(#2878c9, .6),
- solarized-dark-line-origin-chosen : rgba(#2878c9, .35),
+ solarized-dark-header-origin-chosen : rgba(#2878c9, 0.6),
+ solarized-dark-line-origin-chosen : rgba(#2878c9, 0.35),
solarized-dark-button-origin-chosen : #0082cc,
- solarized_dark_header_not_chosen : rgba(#839496, .25),
- solarized_dark_line_not_chosen : rgba(#839496, .15),
+ solarized_dark_header_not_chosen : rgba(#839496, 0.25),
+ solarized_dark_line_not_chosen : rgba(#839496, 0.15),
none_header_head_neutral : $gray-normal,
none_line_head_neutral : $gray-normal,
@@ -210,26 +210,20 @@ $colors: (
}
#conflicts {
-
.white {
- @include color-scheme('white')
- }
+ @include color-scheme('white'); }
.dark {
- @include color-scheme('dark')
- }
+ @include color-scheme('dark'); }
.monokai {
- @include color-scheme('monokai')
- }
+ @include color-scheme('monokai'); }
.solarized-light {
- @include color-scheme('solarized-light')
- }
+ @include color-scheme('solarized-light'); }
.solarized-dark {
- @include color-scheme('solarized-dark')
- }
+ @include color-scheme('solarized-dark'); }
.diff-wrap-lines .line_content {
white-space: normal;
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 135730d71e9..e73d1a1289d 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -82,7 +82,6 @@
}
.mr-widget-body,
-.mr-widget-section,
.mr-widget-content,
.mr-widget-footer {
padding: $gl-padding;
@@ -492,11 +491,9 @@
.merge-request {
padding: 10px 0 10px 15px;
position: relative;
- display: -webkit-flex;
display: flex;
.issuable-info-container {
- -webkit-flex: 1;
flex: 1;
}
@@ -735,9 +732,11 @@
.mr-version-controls {
position: relative;
- z-index: 103;
+ z-index: 203;
background: $gray-light;
color: $gl-text-color;
+ margin-top: -1px;
+ border-top: 1px solid $border-color;
.mr-version-menus-container {
display: flex;
@@ -785,11 +784,9 @@
}
@include media-breakpoint-up(md) {
- position: -webkit-sticky;
position: sticky;
top: $header-height + $mr-tabs-height;
width: 100%;
- border-top: 1px solid $border-color;
&.is-fileTreeOpen {
margin-left: -16px;
@@ -808,16 +805,12 @@
.merge-request-tabs-holder {
top: $header-height;
- z-index: 200;
+ z-index: 300;
background-color: $white-light;
-
- @include media-breakpoint-down(md) {
- border-bottom: 1px solid $border-color;
- }
+ border-bottom: 1px solid $border-color;
@include media-breakpoint-up(sm) {
position: sticky;
- position: -webkit-sticky;
}
&.affix {
@@ -1019,3 +1012,8 @@
z-index: 99999;
background: $black-transparent;
}
+
+.source-branch-removal-status {
+ padding-left: 50px;
+ padding-bottom: $gl-padding;
+}
diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss
index 15f3a2ef4a8..3ca8e943a3a 100644
--- a/app/assets/stylesheets/pages/milestone.scss
+++ b/app/assets/stylesheets/pages/milestone.scss
@@ -67,18 +67,14 @@ $status-box-line-height: 26px;
.card-header {
line-height: $line-height-base;
padding: 14px 16px;
- display: -webkit-flex;
display: flex;
.title {
- -webkit-flex: 1;
- -webkit-flex-grow: 1;
flex: 1;
flex-grow: 2;
}
.counter {
- -webkit-flex: 1;
flex: 0;
padding-left: 16px;
}
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 51f755c67af..3343b55d24b 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -58,7 +58,7 @@
border: 1px solid $border-color;
border-radius: $border-radius-base;
transition: border-color ease-in-out 0.15s,
- box-shadow ease-in-out 0.15s;
+ box-shadow ease-in-out 0.15s;
&.is-focused {
@extend .form-control:focus;
@@ -72,7 +72,7 @@
&.is-dropzone-hover {
border-color: $green-500;
box-shadow: 0 0 2px $black-transparent,
- 0 0 4px $green-500-focus;
+ 0 0 4px $green-500-focus;
.comment-toolbar,
.nav-links {
@@ -84,9 +84,7 @@
.md-header .nav-links {
display: flex;
- display: -webkit-flex;
flex-flow: row wrap;
- -webkit-flex-flow: row wrap;
width: 100%;
.float-right {
@@ -444,7 +442,7 @@ table {
.uploading-error-message {
@include media-breakpoint-down(xs) {
&::after {
- content: "\a";
+ content: '\a';
white-space: pre;
}
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 23b9e4f9416..72f48e98c24 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -303,26 +303,6 @@ $note-form-margin-left: 72px;
}
}
- .timeline-icon {
- float: left;
- display: flex;
- align-items: center;
- background-color: $white-light;
- width: $system-note-icon-size;
- height: $system-note-icon-size;
- border: 1px solid $border-color;
- border-radius: $system-note-icon-size;
- margin: -6px $gl-padding 0 0;
-
- svg {
- width: $system-note-svg-size;
- height: $system-note-svg-size;
- fill: $gray-darkest;
- display: block;
- margin: 0 auto;
- }
- }
-
.timeline-content {
@include notes-media('min', map-get($grid-breakpoints, sm)) {
margin-left: 30px;
@@ -380,6 +360,37 @@ $note-form-margin-left: 72px;
}
}
}
+
+ .system-note,
+ .discussion-filter-note {
+ .timeline-icon {
+ float: left;
+ display: flex;
+ align-items: center;
+ background-color: $white-light;
+ width: $system-note-icon-size;
+ height: $system-note-icon-size;
+ border: 1px solid $border-color;
+ border-radius: $system-note-icon-size;
+ margin: -6px $gl-padding 0 0;
+
+ svg {
+ width: $system-note-svg-size;
+ height: $system-note-svg-size;
+ fill: $gray-darkest;
+ display: block;
+ margin: 0 auto;
+ }
+ }
+ }
+
+ .discussion-filter-note {
+ .timeline-icon {
+ width: $system-note-icon-size + 6;
+ height: $system-note-icon-size + 6;
+ margin-top: -8px;
+ }
+ }
}
// Diff code in discussion view
@@ -494,11 +505,6 @@ $note-form-margin-left: 72px;
.discussion-notes {
margin-left: 0;
border-left: 0;
-
- .notes {
- position: relative;
- @include vertical-line(52px);
- }
}
.note-wrapper {
@@ -550,6 +556,11 @@ $note-form-margin-left: 72px;
.note-header-info {
padding-bottom: 0;
}
+
+ .timeline-content {
+ overflow-x: auto;
+ overflow-y: hidden;
+ }
}
.unresolved {
@@ -597,7 +608,6 @@ $note-form-margin-left: 72px;
.note-headline-meta {
display: inline-block;
- white-space: nowrap;
.system-note-message {
white-space: normal;
@@ -607,6 +617,10 @@ $note-form-margin-left: 72px;
color: $gl-text-color-disabled;
}
+ .note-timestamp {
+ white-space: nowrap;
+ }
+
a:hover {
text-decoration: underline;
}
@@ -902,7 +916,6 @@ $note-form-margin-left: 72px;
}
.discussion-filter-container {
-
.btn > svg {
width: $gl-col-padding;
height: $gl-col-padding;
@@ -924,7 +937,6 @@ $note-form-margin-left: 72px;
//This needs to be deleted when Snippet/Commit comments are convered to Vue
// See https://gitlab.com/gitlab-org/gitlab-ce/issues/53918#note_117038785
.unstyled-comments {
-
.discussion-header {
padding: $gl-padding;
border-bottom: 1px solid $border-color;
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index e676d48c1f4..acc562f0769 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -994,7 +994,6 @@ button.mini-pipeline-graph-dropdown-toggle {
* Top arrow in the dropdown in the mini pipeline graph
*/
.mini-pipeline-graph-dropdown-menu {
-
&::before,
&::after {
content: '';
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index a1e847009fc..ab26259c007 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -270,11 +270,9 @@
position: static;
padding: 0 16px;
margin-bottom: 20px;
- display: -webkit-flex;
display: flex;
.btn {
- -webkit-flex-grow: 1;
flex-grow: 1;
&:first-child {
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 66866aedfba..1349845f300 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -571,9 +571,7 @@
.import-buttons {
padding-left: 0;
- display: -webkit-flex;
display: flex;
- -webkit-flex-wrap: wrap;
flex-wrap: wrap;
.btn {
@@ -704,8 +702,8 @@
.scrolling-tabs-container {
.scrolling-tabs {
margin-top: $gl-padding-8;
- margin-bottom: $gl-padding-8 - $browserScrollbarSize;
- padding-bottom: $browserScrollbarSize;
+ margin-bottom: $gl-padding-8 - $browser-scrollbar-size;
+ padding-bottom: $browser-scrollbar-size;
flex-wrap: wrap;
border-bottom: 0;
}
@@ -713,7 +711,7 @@
.fade-left,
.fade-right {
top: 0;
- height: calc(100% - #{$browserScrollbarSize});
+ height: calc(100% - #{$browser-scrollbar-size});
.fa {
top: 50%;
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index 149c3254d84..20bdc6596e9 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -84,9 +84,7 @@ input[type='checkbox']:hover {
.search-icon {
transition: color $default-transition-duration;
- -webkit-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
+ user-select: none;
}
.clear-icon {
@@ -185,13 +183,11 @@ input[type='checkbox']:hover {
.search-holder {
@include media-breakpoint-up(sm) {
- display: -webkit-flex;
display: flex;
}
.search-field-holder,
.project-filter-form {
- -webkit-flex: 1 0 auto;
flex: 1 0 auto;
position: relative;
margin-right: 0;
diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss
index 7d59dd3b5d1..f4d568d02ac 100644
--- a/app/assets/stylesheets/pages/status.scss
+++ b/app/assets/stylesheets/pages/status.scss
@@ -33,7 +33,7 @@
border-color: $gl-text-color;
&:not(span):hover {
- background-color: rgba($gl-text-color, .07);
+ background-color: rgba($gl-text-color, 0.07);
}
}
@@ -54,7 +54,7 @@
border-color: $gl-text-color-secondary;
&:not(span):hover {
- background-color: rgba($gl-text-color-secondary, .07);
+ background-color: rgba($gl-text-color-secondary, 0.07);
}
}
}
diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss
index 3fc37e20c36..2a1e8345755 100644
--- a/app/assets/stylesheets/pages/todos.scss
+++ b/app/assets/stylesheets/pages/todos.scss
@@ -6,9 +6,7 @@
.todos-list > .todo {
// workaround because we cannot use border-colapse
border-top: 1px solid transparent;
- display: -webkit-flex;
display: flex;
- -webkit-flex-direction: row;
flex-direction: row;
&:hover {
@@ -29,23 +27,18 @@
.todo-avatar,
.todo-actions {
@include transition(opacity);
- -webkit-flex: 0 0 auto;
flex: 0 0 auto;
}
.todo-actions {
- display: -webkit-flex;
display: flex;
- -webkit-justify-content: center;
justify-content: center;
- -webkit-flex-direction: column;
flex-direction: column;
margin-left: 10px;
min-width: 55px;
}
.todo-item {
- -webkit-flex: 0 1 100%;
flex: 0 1 100%;
min-width: 0;
}
@@ -60,13 +53,13 @@
.todo-avatar,
.todo-item {
- opacity: .6;
+ opacity: 0.6;
}
}
.todo-avatar,
.todo-item {
- opacity: .2;
+ opacity: 0.2;
}
.btn {
@@ -82,7 +75,6 @@
display: flex;
> .title-item {
- -webkit-flex: 0 0 auto;
flex: 0 0 auto;
margin: 0 2px;
@@ -96,7 +88,6 @@
}
.todo-label {
- -webkit-flex: 0 1 auto;
flex: 0 1 auto;
overflow: hidden;
text-overflow: ellipsis;
@@ -222,23 +213,19 @@
}
.todos-empty {
- display: -webkit-flex;
display: flex;
- -webkit-flex-direction: column;
flex-direction: column;
max-width: 900px;
margin-left: auto;
margin-right: auto;
@include media-breakpoint-up(sm) {
- -webkit-flex-direction: row;
flex-direction: row;
padding-top: 80px;
}
}
.todos-empty-content {
- -webkit-align-self: center;
align-self: center;
max-width: 480px;
margin-right: 20px;
@@ -252,7 +239,6 @@
@include media-breakpoint-up(sm) {
width: 300px;
margin-right: 0;
- -webkit-order: 2;
order: 2;
}
}
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index a46b8679a42..5664f46484e 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -172,26 +172,6 @@
text-decoration: inherit;
}
}
-
- .tree_commit {
- max-width: 320px;
-
- .str-truncated {
- max-width: 100%;
- }
- }
-
- .tree_time_ago {
- min-width: 135px;
- }
- }
-
- .tree_author {
- padding-right: 8px;
-
- .commit-author-name {
- color: $gl-text-color;
- }
}
.tree-truncated-warning {
diff --git a/app/assets/stylesheets/pages/ui_dev_kit.scss b/app/assets/stylesheets/pages/ui_dev_kit.scss
index 84c617c7ec0..7744fd814d0 100644
--- a/app/assets/stylesheets/pages/ui_dev_kit.scss
+++ b/app/assets/stylesheets/pages/ui_dev_kit.scss
@@ -10,7 +10,7 @@
margin-bottom: 15px;
&::before {
- content: "Example";
+ content: 'Example';
color: $ui-dev-kit-example-color;
}
}
diff --git a/app/controllers/admin/appearances_controller.rb b/app/controllers/admin/appearances_controller.rb
index e3226c86b0b..189fee98aa0 100644
--- a/app/controllers/admin/appearances_controller.rb
+++ b/app/controllers/admin/appearances_controller.rb
@@ -74,6 +74,11 @@ class Admin::AppearancesController < Admin::ApplicationController
favicon_cache
new_project_guidelines
updated_by
+ header_message
+ footer_message
+ message_background_color
+ message_font_color
+ email_header_and_footer_enabled
]
end
end
diff --git a/app/controllers/admin/runners_controller.rb b/app/controllers/admin/runners_controller.rb
index 0b6ff491c66..8a00408001e 100644
--- a/app/controllers/admin/runners_controller.rb
+++ b/app/controllers/admin/runners_controller.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class Admin::RunnersController < Admin::ApplicationController
- before_action :runner, except: :index
+ before_action :runner, except: [:index, :tag_list]
def index
finder = Admin::RunnersFinder.new(params: params)
@@ -48,6 +48,12 @@ class Admin::RunnersController < Admin::ApplicationController
end
end
+ def tag_list
+ tags = Autocomplete::ActsAsTaggableOn::TagsFinder.new(params: params).execute
+
+ render json: ActsAsTaggableOn::TagSerializer.new.represent(tags)
+ end
+
private
def runner
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index e93be1c1ba2..bfa7c7d0109 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -1,11 +1,13 @@
# frozen_string_literal: true
class Admin::UsersController < Admin::ApplicationController
+ include RoutableActions
+
before_action :user, except: [:index, :new, :create]
before_action :check_impersonation_availability, only: :impersonate
def index
- @users = User.order_name_asc.filter(params[:filter])
+ @users = User.filter_items(params[:filter]).order_name_asc
@users = @users.search_with_secondary_emails(params[:search_query]) if params[:search_query].present?
@users = @users.sort_by_attribute(@sort = params[:sort])
@users = @users.page(params[:page])
@@ -177,11 +179,13 @@ class Admin::UsersController < Admin::ApplicationController
user == current_user
end
- # rubocop: disable CodeReuse/ActiveRecord
def user
- @user ||= User.find_by!(username: params[:id])
+ @user ||= find_routable!(User, params[:id])
+ end
+
+ def build_canonical_path(user)
+ url_for(safe_params.merge(id: user.to_param))
end
- # rubocop: enable CodeReuse/ActiveRecord
def redirect_back_or_admin_user(options = {})
redirect_back_or_default(default: default_route, options: options)
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index af0b0c64814..b7eb6af6d67 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -43,7 +43,10 @@ class ApplicationController < ActionController::Base
:git_import_enabled?, :gitlab_project_import_enabled?,
:manifest_import_enabled?
+ # Adds `no-store` to the DEFAULT_CACHE_CONTROL, to prevent security
+ # concerns due to caching private data.
DEFAULT_GITLAB_CACHE_CONTROL = "#{ActionDispatch::Http::Cache::Response::DEFAULT_CACHE_CONTROL}, no-store".freeze
+ DEFAULT_GITLAB_CONTROL_NO_CACHE = "#{DEFAULT_GITLAB_CACHE_CONTROL}, no-cache".freeze
rescue_from Encoding::CompatibilityError do |exception|
log_exception(exception)
@@ -235,9 +238,9 @@ class ApplicationController < ActionController::Base
end
def no_cache_headers
- response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate"
- response.headers["Pragma"] = "no-cache"
- response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT"
+ headers['Cache-Control'] = DEFAULT_GITLAB_CONTROL_NO_CACHE
+ headers['Pragma'] = 'no-cache' # HTTP 1.0 compatibility
+ headers['Expires'] = 'Fri, 01 Jan 1990 00:00:00 GMT'
end
def default_headers
@@ -247,10 +250,16 @@ class ApplicationController < ActionController::Base
headers['X-Content-Type-Options'] = 'nosniff'
if current_user
- # Adds `no-store` to the DEFAULT_CACHE_CONTROL, to prevent security
- # concerns due to caching private data.
- headers['Cache-Control'] = DEFAULT_GITLAB_CACHE_CONTROL
- headers["Pragma"] = "no-cache" # HTTP 1.0 compatibility
+ headers['Cache-Control'] = default_cache_control
+ headers['Pragma'] = 'no-cache' # HTTP 1.0 compatibility
+ end
+ end
+
+ def default_cache_control
+ if request.xhr?
+ ActionDispatch::Http::Cache::Response::DEFAULT_CACHE_CONTROL
+ else
+ DEFAULT_GITLAB_CACHE_CONTROL
end
end
diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb
index 3bd91b71d92..68a2a83f0de 100644
--- a/app/controllers/clusters/clusters_controller.rb
+++ b/app/controllers/clusters/clusters_controller.rb
@@ -24,7 +24,7 @@ class Clusters::ClustersController < Clusters::BaseController
# Note: We are paginating through an array here but this should OK as:
#
# In CE, we can have a maximum group nesting depth of 21, so including
- # project cluster, we can have max 22 clusters for a group hierachy.
+ # project cluster, we can have max 22 clusters for a group hierarchy.
# In EE (Premium) we can have any number, as multiple clusters are
# supported, but the number of clusters are fairly low currently.
#
diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb
index 07d0bf16d93..c529aabf797 100644
--- a/app/controllers/concerns/issuable_collections.rb
+++ b/app/controllers/concerns/issuable_collections.rb
@@ -91,6 +91,7 @@ module IssuableCollections
options = {
scope: params[:scope],
state: params[:state],
+ confidential: Gitlab::Utils.to_boolean(params[:confidential]),
sort: set_sort_order
}
diff --git a/app/controllers/concerns/lfs_request.rb b/app/controllers/concerns/lfs_request.rb
index 5572c3cee2d..57e444319e0 100644
--- a/app/controllers/concerns/lfs_request.rb
+++ b/app/controllers/concerns/lfs_request.rb
@@ -123,7 +123,7 @@ module LfsRequest
(authentication_abilities || []).include?(capability)
end
- # Overriden in EE
+ # Overridden in EE
def limit_exceeded?
false
end
diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb
index 0319948a12f..b4fee93713b 100644
--- a/app/controllers/concerns/notes_actions.rb
+++ b/app/controllers/concerns/notes_actions.rb
@@ -6,7 +6,6 @@ module NotesActions
extend ActiveSupport::Concern
included do
- prepend_before_action :normalize_create_params, only: [:create]
before_action :set_polling_interval_header, only: [:index]
before_action :require_noteable!, only: [:index, :create]
before_action :authorize_admin_note!, only: [:update, :destroy]
@@ -44,12 +43,7 @@ module NotesActions
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def create
- create_params = note_params.merge(
- merge_request_diff_head_sha: params[:merge_request_diff_head_sha],
- in_reply_to_discussion_id: params[:in_reply_to_discussion_id]
- )
-
- @note = Notes::CreateService.new(note_project, current_user, create_params).execute
+ @note = Notes::CreateService.new(note_project, current_user, create_note_params).execute
respond_to do |format|
format.json do
@@ -78,7 +72,7 @@ module NotesActions
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def update
- @note = Notes::UpdateService.new(project, current_user, note_params).execute(note)
+ @note = Notes::UpdateService.new(project, current_user, update_note_params).execute(note)
prepare_notes_for_rendering([@note])
respond_to do |format|
@@ -196,24 +190,36 @@ module NotesActions
return access_denied! unless can?(current_user, :admin_note, note)
end
- def note_params
+ def create_note_params
params.require(:note).permit(
- :project_id,
- :noteable_type,
- :noteable_id,
- :commit_id,
- :noteable,
:type,
-
:note,
- :attachment,
+ :line_code, # LegacyDiffNote
+ :position # DiffNote
+ ).tap do |create_params|
+ create_params.merge!(
+ params.permit(:merge_request_diff_head_sha, :in_reply_to_discussion_id)
+ )
- # LegacyDiffNote
- :line_code,
+ # These params are also sent by the client but we need to set these based on
+ # target_type and target_id because we're checking permissions based on that
+ create_params[:noteable_type] = params[:target_type].classify
+
+ case params[:target_type]
+ when 'commit'
+ create_params[:commit_id] = params[:target_id]
+ when 'merge_request'
+ create_params[:noteable_id] = params[:target_id]
+ # Notes on MergeRequest can have an extra `commit_id` context
+ create_params[:commit_id] = params.dig(:note, :commit_id)
+ else
+ create_params[:noteable_id] = params[:target_id]
+ end
+ end
+ end
- # DiffNote
- :position
- )
+ def update_note_params
+ params.require(:note).permit(:note)
end
def set_polling_interval_header
@@ -248,15 +254,6 @@ module NotesActions
DiscussionSerializer.new(project: project, noteable: noteable, current_user: current_user, note_entity: ProjectNoteEntity)
end
- # Avoids checking permissions in the wrong object - this ensures that the object we checked permissions for
- # is the object we're actually creating a note in.
- def normalize_create_params
- params[:note].try do |note|
- note[:noteable_id] = params[:target_id]
- note[:noteable_type] = params[:target_type].classify
- end
- end
-
def note_project
strong_memoize(:note_project) do
next nil unless project
diff --git a/app/controllers/concerns/send_file_upload.rb b/app/controllers/concerns/send_file_upload.rb
index 9ca54c5519b..28e4cece548 100644
--- a/app/controllers/concerns/send_file_upload.rb
+++ b/app/controllers/concerns/send_file_upload.rb
@@ -3,7 +3,7 @@
module SendFileUpload
def send_upload(file_upload, send_params: {}, redirect_params: {}, attachment: nil, proxy: false, disposition: 'attachment')
if attachment
- response_disposition = ::Gitlab::ContentDisposition.format(disposition: 'attachment', filename: attachment)
+ response_disposition = ::Gitlab::ContentDisposition.format(disposition: disposition, filename: attachment)
# Response-Content-Type will not override an existing Content-Type in
# Google Cloud Storage, so the metadata needs to be cleared on GCS for
diff --git a/app/controllers/dashboard/milestones_controller.rb b/app/controllers/dashboard/milestones_controller.rb
index 9484e4d30cd..912036da0ea 100644
--- a/app/controllers/dashboard/milestones_controller.rb
+++ b/app/controllers/dashboard/milestones_controller.rb
@@ -25,8 +25,6 @@ class Dashboard::MilestonesController < Dashboard::ApplicationController
private
def group_milestones
- groups = GroupsFinder.new(current_user, all_available: false).execute
-
DashboardGroupMilestone.build_collection(groups, params)
end
@@ -45,6 +43,6 @@ class Dashboard::MilestonesController < Dashboard::ApplicationController
end
def groups
- @groups ||= GroupsFinder.new(current_user, state_all: true).execute
+ @groups ||= GroupsFinder.new(current_user, all_available: false).execute
end
end
diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb
index b1d224d026f..b044affd4e8 100644
--- a/app/controllers/dashboard/projects_controller.rb
+++ b/app/controllers/dashboard/projects_controller.rb
@@ -13,7 +13,13 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
@projects = load_projects(params.merge(non_public: true))
respond_to do |format|
- format.html
+ format.html do
+ # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37434
+ # Also https://gitlab.com/gitlab-org/gitlab-ce/issues/40260
+ Gitlab::GitalyClient.allow_n_plus_1_calls do
+ render
+ end
+ end
format.atom do
load_events
render layout: 'xml.atom'
diff --git a/app/controllers/import/gitea_controller.rb b/app/controllers/import/gitea_controller.rb
index f067ef625aa..68ad8650dba 100644
--- a/app/controllers/import/gitea_controller.rb
+++ b/app/controllers/import/gitea_controller.rb
@@ -1,8 +1,10 @@
# frozen_string_literal: true
class Import::GiteaController < Import::GithubController
+ extend ::Gitlab::Utils::Override
+
def new
- if session[access_token_key].present? && session[host_key].present?
+ if session[access_token_key].present? && provider_url.present?
redirect_to status_import_url
end
end
@@ -12,8 +14,8 @@ class Import::GiteaController < Import::GithubController
super
end
+ # Must be defined or it will 404
def status
- @gitea_host_url = session[host_key]
super
end
@@ -23,25 +25,33 @@ class Import::GiteaController < Import::GithubController
:"#{provider}_host_url"
end
- # Overridden methods
+ override :provider
def provider
:gitea
end
+ override :provider_url
+ def provider_url
+ session[host_key]
+ end
+
# Gitea is not yet an OAuth provider
# See https://github.com/go-gitea/gitea/issues/27
+ override :logged_in_with_provider?
def logged_in_with_provider?
false
end
+ override :provider_auth
def provider_auth
- if session[access_token_key].blank? || session[host_key].blank?
+ if session[access_token_key].blank? || provider_url.blank?
redirect_to new_import_gitea_url,
alert: 'You need to specify both an Access Token and a Host URL.'
end
end
+ override :client_options
def client_options
- { host: session[host_key], api_version: 'v1' }
+ { host: provider_url, api_version: 'v1' }
end
end
diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb
index 3fbc0817e95..aa4aa0fbdac 100644
--- a/app/controllers/import/github_controller.rb
+++ b/app/controllers/import/github_controller.rb
@@ -1,8 +1,11 @@
# frozen_string_literal: true
class Import::GithubController < Import::BaseController
+ include ImportHelper
+
before_action :verify_import_enabled
- before_action :provider_auth, only: [:status, :jobs, :create]
+ before_action :provider_auth, only: [:status, :realtime_changes, :create]
+ before_action :expire_etag_cache, only: [:status, :create]
rescue_from Octokit::Unauthorized, with: :provider_unauthorized
@@ -24,30 +27,37 @@ class Import::GithubController < Import::BaseController
redirect_to status_import_url
end
- # rubocop: disable CodeReuse/ActiveRecord
def status
- @repos = client.repos
- @already_added_projects = find_already_added_projects(provider)
- already_added_projects_names = @already_added_projects.pluck(:import_source)
-
- @repos.reject! { |repo| already_added_projects_names.include? repo.full_name }
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- def jobs
- render json: find_jobs(provider)
+ # Request repos to display error page if provider token is invalid
+ # Improving in https://gitlab.com/gitlab-org/gitlab-ce/issues/55585
+ client_repos
+
+ respond_to do |format|
+ format.json do
+ render json: { imported_projects: serialized_imported_projects,
+ provider_repos: serialized_provider_repos,
+ namespaces: serialized_namespaces }
+ end
+ format.html
+ end
end
def create
result = Import::GithubService.new(client, current_user, import_params).execute(access_params, provider)
if result[:status] == :success
- render json: ProjectSerializer.new.represent(result[:project])
+ render json: serialized_imported_projects(result[:project])
else
render json: { errors: result[:message] }, status: result[:http_status]
end
end
+ def realtime_changes
+ Gitlab::PollingInterval.set_header(response, interval: 3_000)
+
+ render json: find_jobs(provider)
+ end
+
private
def import_params
@@ -58,10 +68,45 @@ class Import::GithubController < Import::BaseController
[:repo_id, :new_name, :target_namespace]
end
+ def serialized_imported_projects(projects = already_added_projects)
+ ProjectSerializer.new.represent(projects, serializer: :import, provider_url: provider_url)
+ end
+
+ def serialized_provider_repos
+ repos = client_repos.reject { |repo| already_added_project_names.include? repo.full_name }
+ ProviderRepoSerializer.new(current_user: current_user).represent(repos, provider: provider, provider_url: provider_url)
+ end
+
+ def serialized_namespaces
+ NamespaceSerializer.new.represent(namespaces)
+ end
+
+ def already_added_projects
+ @already_added_projects ||= find_already_added_projects(provider)
+ end
+
+ def already_added_project_names
+ @already_added_projects_names ||= already_added_projects.pluck(:import_source) # rubocop:disable CodeReuse/ActiveRecord
+ end
+
+ def namespaces
+ current_user.manageable_groups_with_routes
+ end
+
+ def expire_etag_cache
+ Gitlab::EtagCaching::Store.new.tap do |store|
+ store.touch(realtime_changes_path)
+ end
+ end
+
def client
@client ||= Gitlab::LegacyGithubImport::Client.new(session[access_token_key], client_options)
end
+ def client_repos
+ @client_repos ||= client.repos
+ end
+
def verify_import_enabled
render_404 unless import_enabled?
end
@@ -74,6 +119,10 @@ class Import::GithubController < Import::BaseController
__send__("#{provider}_import_enabled?") # rubocop:disable GitlabSecurity/PublicSend
end
+ def realtime_changes_path
+ public_send("realtime_changes_import_#{provider}_path", format: :json) # rubocop:disable GitlabSecurity/PublicSend
+ end
+
def new_import_url
public_send("new_import_#{provider}_url", extra_import_params) # rubocop:disable GitlabSecurity/PublicSend
end
@@ -105,6 +154,14 @@ class Import::GithubController < Import::BaseController
:github
end
+ def provider_url
+ strong_memoize(:provider_url) do
+ provider = Gitlab::Auth::OAuth::Provider.config_for('github')
+
+ provider&.dig('url').presence || 'https://github.com'
+ end
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def logged_in_with_provider?
current_user.identities.exists?(provider: provider)
diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb
index 94002095739..0227af2c266 100644
--- a/app/controllers/profiles/preferences_controller.rb
+++ b/app/controllers/profiles/preferences_controller.rb
@@ -37,6 +37,14 @@ class Profiles::PreferencesController < Profiles::ApplicationController
end
def preferences_param_names
- [:color_scheme_id, :layout, :dashboard, :project_view, :theme_id, :first_day_of_week]
+ [
+ :color_scheme_id,
+ :layout,
+ :dashboard,
+ :project_view,
+ :theme_id,
+ :first_day_of_week,
+ :preferred_language
+ ]
end
end
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index 15248d2d08f..b9c52618d4b 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -104,7 +104,6 @@ class ProfilesController < Profiles::ApplicationController
:username,
:website_url,
:organization,
- :preferred_language,
:private_profile,
:include_private_contributions,
status: [:emoji, :message]
diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb
index 30e436365de..0c5328fc941 100644
--- a/app/controllers/projects/git_http_controller.rb
+++ b/app/controllers/projects/git_http_controller.rb
@@ -20,6 +20,8 @@ class Projects::GitHttpController < Projects::GitHttpClientController
# POST /foo/bar.git/git-upload-pack (git pull)
def git_upload_pack
+ enqueue_fetch_statistics_update
+
render_ok
end
@@ -67,6 +69,13 @@ class Projects::GitHttpController < Projects::GitHttpClientController
render plain: exception.message, status: :service_unavailable
end
+ def enqueue_fetch_statistics_update
+ return if wiki?
+ return unless project.daily_statistics_enabled?
+
+ ProjectDailyStatisticsWorker.perform_async(project.id)
+ end
+
def access
@access ||= access_klass.new(access_actor, project,
'http', authentication_abilities: authentication_abilities,
diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb
index 925b6ed9bfd..c80fce513f6 100644
--- a/app/controllers/projects/graphs_controller.rb
+++ b/app/controllers/projects/graphs_controller.rb
@@ -45,7 +45,14 @@ class Projects::GraphsController < Projects::ApplicationController
end
def get_languages
- @languages = @project.repository.languages
+ @languages =
+ if @project.repository_languages.present?
+ @project.repository_languages.map do |lang|
+ { value: lang.share, label: lang.name, color: lang.color, highlight: lang.color }
+ end
+ else
+ @project.repository.languages
+ end
end
def fetch_graph
diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb
index 5639402a1e9..32cefe54613 100644
--- a/app/controllers/projects/merge_requests/creations_controller.rb
+++ b/app/controllers/projects/merge_requests/creations_controller.rb
@@ -89,7 +89,11 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
def build_merge_request
params[:merge_request] ||= ActionController::Parameters.new(source_project: @project)
- @merge_request = ::MergeRequests::BuildService.new(project, current_user, merge_request_params.merge(diff_options: diff_options)).execute
+
+ # Gitaly N+1 issue: https://gitlab.com/gitlab-org/gitlab-ce/issues/58096
+ Gitlab::GitalyClient.allow_n_plus_1_calls do
+ @merge_request = ::MergeRequests::BuildService.new(project, current_user, merge_request_params.merge(diff_options: diff_options)).execute
+ end
end
def define_new_vars
diff --git a/app/controllers/projects/merge_requests/diffs_controller.rb b/app/controllers/projects/merge_requests/diffs_controller.rb
index ddffbb17ace..518d41bd3fb 100644
--- a/app/controllers/projects/merge_requests/diffs_controller.rb
+++ b/app/controllers/projects/merge_requests/diffs_controller.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
class Projects::MergeRequests::DiffsController < Projects::MergeRequests::ApplicationController
- include DiffForPath
include DiffHelper
include RendersNotes
diff --git a/app/controllers/projects/pages_controller.rb b/app/controllers/projects/pages_controller.rb
index d0e35bee986..73e629ab7c3 100644
--- a/app/controllers/projects/pages_controller.rb
+++ b/app/controllers/projects/pages_controller.rb
@@ -5,7 +5,8 @@ class Projects::PagesController < Projects::ApplicationController
before_action :require_pages_enabled!
before_action :authorize_read_pages!, only: [:show]
- before_action :authorize_update_pages!, except: [:show]
+ before_action :authorize_update_pages!, except: [:show, :destroy]
+ before_action :authorize_remove_pages!, only: [:destroy]
# rubocop: disable CodeReuse/ActiveRecord
def show
diff --git a/app/controllers/projects/settings/operations_controller.rb b/app/controllers/projects/settings/operations_controller.rb
index 521ec2acebb..7276964b6e1 100644
--- a/app/controllers/projects/settings/operations_controller.rb
+++ b/app/controllers/projects/settings/operations_controller.rb
@@ -14,16 +14,37 @@ module Projects
def update
result = ::Projects::Operations::UpdateService.new(project, current_user, update_params).execute
+ render_update_response(result)
+ end
+
+ private
+
+ # overridden in EE
+ def render_update_response(result)
+ respond_to do |format|
+ format.json do
+ render_update_json_response(result)
+ end
+ end
+ end
+
+ def render_update_json_response(result)
if result[:status] == :success
flash[:notice] = _('Your changes have been saved')
- redirect_to project_settings_operations_path(@project)
+ render json: {
+ status: result[:status]
+ }
else
- render 'show'
+ render(
+ status: result[:http_status] || :bad_request,
+ json: {
+ status: result[:status],
+ message: result[:message]
+ }
+ )
end
end
- private
-
def error_tracking_setting
@error_tracking_setting ||= project.error_tracking_setting ||
project.build_error_tracking_setting
@@ -35,7 +56,14 @@ module Projects
# overridden in EE
def permitted_project_params
- { error_tracking_setting_attributes: [:enabled, :api_url, :token] }
+ {
+ error_tracking_setting_attributes: [
+ :enabled,
+ :api_host,
+ :token,
+ project: [:slug, :name, :organization_slug, :organization_name]
+ ]
+ }
end
def check_license
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
index 3fe300dcfc0..edebfc55c17 100644
--- a/app/controllers/projects/tree_controller.rb
+++ b/app/controllers/projects/tree_controller.rb
@@ -31,20 +31,6 @@ class Projects::TreeController < Projects::ApplicationController
lfs_blob_ids
@last_commit = @repository.last_commit_for_path(@commit.id, @tree.path) || @commit
end
-
- format.js do
- # Disable cache so browser history works
- no_cache_headers
- end
-
- format.json do
- page_title @path.presence || _("Files"), @ref, @project.full_name
-
- # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/38261
- Gitlab::GitalyClient.allow_n_plus_1_calls do
- render json: TreeSerializer.new(project: @project, repository: @repository, ref: @ref).represent(@tree)
- end
- end
end
end
diff --git a/app/controllers/snippets/notes_controller.rb b/app/controllers/snippets/notes_controller.rb
index 091bcb1253d..eee14b0faf4 100644
--- a/app/controllers/snippets/notes_controller.rb
+++ b/app/controllers/snippets/notes_controller.rb
@@ -26,10 +26,6 @@ class Snippets::NotesController < ApplicationController
# rubocop: enable CodeReuse/ActiveRecord
alias_method :noteable, :snippet
- def note_params
- super.merge(noteable_id: params[:snippet_id])
- end
-
def finder_params
params.merge(last_fetched_at: last_fetched_at, target_id: snippet.id, target_type: 'personal_snippet')
end
diff --git a/app/finders/admin/runners_finder.rb b/app/finders/admin/runners_finder.rb
index fbb1cfc5c66..b2799565f57 100644
--- a/app/finders/admin/runners_finder.rb
+++ b/app/finders/admin/runners_finder.rb
@@ -11,10 +11,11 @@ class Admin::RunnersFinder < UnionFinder
search!
filter_by_status!
filter_by_runner_type!
+ filter_by_tag_list!
sort!
paginate!
- @runners
+ @runners.with_tags
end
def sort_key
@@ -44,6 +45,14 @@ class Admin::RunnersFinder < UnionFinder
filter_by!(:type_type, Ci::Runner::AVAILABLE_TYPES)
end
+ def filter_by_tag_list!
+ tag_list = @params[:tag_name].presence
+
+ if tag_list
+ @runners = @runners.tagged_with(tag_list)
+ end
+ end
+
def sort!
@runners = @runners.order_by(sort_key)
end
diff --git a/app/finders/autocomplete/acts_as_taggable_on/tags_finder.rb b/app/finders/autocomplete/acts_as_taggable_on/tags_finder.rb
new file mode 100644
index 00000000000..f38c187799c
--- /dev/null
+++ b/app/finders/autocomplete/acts_as_taggable_on/tags_finder.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module Autocomplete
+ module ActsAsTaggableOn
+ class TagsFinder
+ LIMIT = 20
+
+ def initialize(params:)
+ @params = params
+ end
+
+ def execute
+ tags = all_tags
+ tags = filter_by_name(tags)
+ limit(tags)
+ end
+
+ private
+
+ def all_tags
+ ::ActsAsTaggableOn::Tag.all
+ end
+
+ def filter_by_name(tags)
+ return tags unless search
+ return tags.none if search.empty?
+
+ if search.length >= Gitlab::SQL::Pattern::MIN_CHARS_FOR_PARTIAL_MATCHING
+ tags.named_like(search)
+ else
+ tags.named(search)
+ end
+ end
+
+ def limit(tags)
+ tags.limit(LIMIT) # rubocop: disable CodeReuse/ActiveRecord
+ end
+
+ def search
+ @params[:search]
+ end
+ end
+ end
+end
diff --git a/app/finders/concerns/finder_methods.rb b/app/finders/concerns/finder_methods.rb
index 5290313585f..8de3276184d 100644
--- a/app/finders/concerns/finder_methods.rb
+++ b/app/finders/concerns/finder_methods.rb
@@ -3,13 +3,13 @@
module FinderMethods
# rubocop: disable CodeReuse/ActiveRecord
def find_by!(*args)
- raise_not_found_unless_authorized execute.find_by!(*args)
+ raise_not_found_unless_authorized execute.reorder(nil).find_by!(*args)
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def find_by(*args)
- if_authorized execute.find_by(*args)
+ if_authorized execute.reorder(nil).find_by(*args)
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/finders/group_descendants_finder.rb b/app/finders/group_descendants_finder.rb
index 96a36db7ec8..ec340f38450 100644
--- a/app/finders/group_descendants_finder.rb
+++ b/app/finders/group_descendants_finder.rb
@@ -134,7 +134,7 @@ class GroupDescendantsFinder
def subgroups
return Group.none unless Group.supports_nested_objects?
- # When filtering subgroups, we want to find all matches withing the tree of
+ # When filtering subgroups, we want to find all matches within the tree of
# descendants to show to the user
groups = if params[:filter]
subgroups_matching_filter
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 23af2e0521c..072d07e0ed2 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -78,13 +78,15 @@ class IssuableFinder
items = init_collection
items = filter_items(items)
- # This has to be last as we may use a CTE as an optimization fence
- # by passing the attempt_group_search_optimizations param and
- # enabling the use_cte_for_group_issues_search feature flag
+ # This has to be last as we use a CTE as an optimization fence
+ # for counts by passing the force_cte param and enabling the
+ # attempt_group_search_optimizations feature flag
# https://www.postgresql.org/docs/current/static/queries-with.html
items = by_search(items)
- sort(items)
+ items = sort(items) unless use_cte_for_count?
+
+ items
end
def filter_items(items)
@@ -94,6 +96,7 @@ class IssuableFinder
items = by_scope(items)
items = by_created_at(items)
items = by_updated_at(items)
+ items = by_closed_at(items)
items = by_state(items)
items = by_group(items)
items = by_assignee(items)
@@ -116,8 +119,9 @@ class IssuableFinder
#
# rubocop: disable CodeReuse/ActiveRecord
def count_by_state
- count_params = params.merge(state: nil, sort: nil)
+ count_params = params.merge(state: nil, sort: nil, force_cte: true)
finder = self.class.new(current_user, count_params)
+
counts = Hash.new(0)
# Searching by label includes a GROUP BY in the query, but ours will be last
@@ -127,8 +131,11 @@ class IssuableFinder
#
# This does not apply when we are using a CTE for the search, as the labels
# GROUP BY is inside the subquery in that case, so we set labels_count to 1.
+ #
+ # We always use CTE when searching in Groups if the feature flag is enabled,
+ # but never when searching in Projects.
labels_count = label_names.any? ? label_names.count : 1
- labels_count = 1 if use_cte_for_search?
+ labels_count = 1 if use_cte_for_count?
finder.execute.reorder(nil).group(:state).count.each do |key, value|
counts[count_key(key)] += value / labels_count
@@ -304,27 +311,31 @@ class IssuableFinder
def use_subquery_for_search?
strong_memoize(:use_subquery_for_search) do
- attempt_group_search_optimizations? &&
- Feature.enabled?(:use_subquery_for_group_issues_search, default_enabled: true)
+ !force_cte? && attempt_group_search_optimizations?
end
end
- def use_cte_for_search?
- strong_memoize(:use_cte_for_search) do
- attempt_group_search_optimizations? &&
- !use_subquery_for_search? &&
- Feature.enabled?(:use_cte_for_group_issues_search, default_enabled: true)
+ def use_cte_for_count?
+ strong_memoize(:use_cte_for_count) do
+ force_cte? && attempt_group_search_optimizations?
end
end
private
+ def force_cte?
+ !!params[:force_cte]
+ end
+
def init_collection
klass.all
end
def attempt_group_search_optimizations?
- search && Gitlab::Database.postgresql? && params[:attempt_group_search_optimizations]
+ search &&
+ Gitlab::Database.postgresql? &&
+ params[:attempt_group_search_optimizations] &&
+ Feature.enabled?(:attempt_group_search_optimizations, default_enabled: true)
end
def count_key(value)
@@ -353,6 +364,13 @@ class IssuableFinder
items
end
+ def by_closed_at(items)
+ items = items.closed_after(params[:closed_after]) if params[:closed_after].present?
+ items = items.closed_before(params[:closed_before]) if params[:closed_before].present?
+
+ items
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def by_state(items)
case params[:state].to_s
@@ -403,7 +421,7 @@ class IssuableFinder
def by_search(items)
return items unless search
- if use_cte_for_search?
+ if use_cte_for_count?
cte = Gitlab::SQL::RecursiveCTE.new(klass.table_name)
cte << items
diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb
index a0504ca0879..cb44575d6f1 100644
--- a/app/finders/issues_finder.rb
+++ b/app/finders/issues_finder.rb
@@ -69,7 +69,16 @@ class IssuesFinder < IssuableFinder
end
def filter_items(items)
- by_due_date(super)
+ issues = super
+ issues = by_due_date(issues)
+ issues = by_confidential(issues)
+ issues
+ end
+
+ def by_confidential(items)
+ return items if params[:confidential].nil?
+
+ params[:confidential] ? items.confidential_only : items.public_only
end
def by_due_date(items)
diff --git a/app/finders/projects/daily_statistics_finder.rb b/app/finders/projects/daily_statistics_finder.rb
new file mode 100644
index 00000000000..912c23107bc
--- /dev/null
+++ b/app/finders/projects/daily_statistics_finder.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Projects
+ class DailyStatisticsFinder
+ attr_reader :project
+
+ def initialize(project)
+ @project = project
+ end
+
+ def fetches
+ ProjectDailyStatistic.of_project(project)
+ .of_last_30_days
+ .sorted_by_date_desc
+ end
+
+ def total_fetch_count
+ fetches.sum_fetch_count
+ end
+ end
+end
diff --git a/app/graphql/mutations/merge_requests/base.rb b/app/graphql/mutations/merge_requests/base.rb
index 54f01c99d78..7d0cb777ad1 100644
--- a/app/graphql/mutations/merge_requests/base.rb
+++ b/app/graphql/mutations/merge_requests/base.rb
@@ -25,7 +25,8 @@ module Mutations
def find_object(project_path:, iid:)
project = resolve_project(full_path: project_path)
- resolver = Resolvers::MergeRequestResolver.new(object: project, context: context)
+ resolver = Resolvers::MergeRequestsResolver
+ .single.new(object: project, context: context)
resolver.resolve(iid: iid)
end
diff --git a/app/graphql/resolvers/base_resolver.rb b/app/graphql/resolvers/base_resolver.rb
index 459933af9d3..063def75d38 100644
--- a/app/graphql/resolvers/base_resolver.rb
+++ b/app/graphql/resolvers/base_resolver.rb
@@ -2,5 +2,12 @@
module Resolvers
class BaseResolver < GraphQL::Schema::Resolver
+ def self.single
+ @single ||= Class.new(self) do
+ def resolve(**args)
+ super.first
+ end
+ end
+ end
end
end
diff --git a/app/graphql/resolvers/issues_resolver.rb b/app/graphql/resolvers/issues_resolver.rb
index 95e66fb3b7c..b98d8bd1fff 100644
--- a/app/graphql/resolvers/issues_resolver.rb
+++ b/app/graphql/resolvers/issues_resolver.rb
@@ -2,12 +2,37 @@
module Resolvers
class IssuesResolver < BaseResolver
- extend ActiveSupport::Concern
+ argument :iid, GraphQL::ID_TYPE,
+ required: false,
+ description: 'The IID of the issue, e.g., "1"'
argument :iids, [GraphQL::ID_TYPE],
required: false,
description: 'The list of IIDs of issues, e.g., [1, 2]'
-
+ argument :state, Types::IssuableStateEnum,
+ required: false,
+ description: "Current state of Issue"
+ argument :label_name, GraphQL::STRING_TYPE.to_list_type,
+ required: false,
+ description: "Labels applied to the Issue"
+ argument :created_before, Types::TimeType,
+ required: false,
+ description: "Issues created before this date"
+ argument :created_after, Types::TimeType,
+ required: false,
+ description: "Issues created after this date"
+ argument :updated_before, Types::TimeType,
+ required: false,
+ description: "Issues updated before this date"
+ argument :updated_after, Types::TimeType,
+ required: false,
+ description: "Issues updated after this date"
+ argument :closed_before, Types::TimeType,
+ required: false,
+ description: "Issues closed before this date"
+ argument :closed_after, Types::TimeType,
+ required: false,
+ description: "Issues closed after this date"
argument :search, GraphQL::STRING_TYPE,
required: false
argument :sort, Types::Sort,
@@ -22,6 +47,7 @@ module Resolvers
# Will need to be be made group & namespace aware with
# https://gitlab.com/gitlab-org/gitlab-ce/issues/54520
args[:project_id] = project.id
+ args[:iids] ||= [args[:iid]].compact
IssuesFinder.new(context[:current_user], args).execute
end
diff --git a/app/graphql/resolvers/merge_request_resolver.rb b/app/graphql/resolvers/merge_requests_resolver.rb
index d047ce9e3a1..90795c797ac 100644
--- a/app/graphql/resolvers/merge_request_resolver.rb
+++ b/app/graphql/resolvers/merge_requests_resolver.rb
@@ -1,19 +1,30 @@
# frozen_string_literal: true
module Resolvers
- class MergeRequestResolver < BaseResolver
+ class MergeRequestsResolver < BaseResolver
argument :iid, GraphQL::ID_TYPE,
- required: true,
- description: 'The IID of the merge request, e.g., "1"'
+ required: false,
+ description: 'The IID of the merge request, e.g., "1"'
+
+ argument :iids, [GraphQL::ID_TYPE],
+ required: false,
+ description: 'The list of IIDs of issues, e.g., [1, 2]'
type Types::MergeRequestType, null: true
alias_method :project, :object
- # rubocop: disable CodeReuse/ActiveRecord
- def resolve(iid:)
+ def resolve(**args)
return unless project.present?
+ args[:iids] ||= [args[:iid]].compact
+
+ args[:iids].map { |iid| batch_load(iid) }
+ .select(&:itself) # .compact doesn't work on BatchLoader
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def batch_load(iid)
BatchLoader.for(iid.to_s).batch(key: project) do |iids, loader, args|
args[:key].merge_requests.where(iid: iids).each do |mr|
loader.call(mr.iid.to_s, mr)
diff --git a/app/graphql/types/issuable_state_enum.rb b/app/graphql/types/issuable_state_enum.rb
new file mode 100644
index 00000000000..f2f6d6c6cab
--- /dev/null
+++ b/app/graphql/types/issuable_state_enum.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module Types
+ class IssuableStateEnum < BaseEnum
+ graphql_name 'IssuableState'
+ description 'State of a GitLab issue or merge request'
+
+ value 'opened'
+ value 'closed'
+ value 'locked'
+ end
+end
diff --git a/app/graphql/types/issue_state_enum.rb b/app/graphql/types/issue_state_enum.rb
new file mode 100644
index 00000000000..6521407fc9d
--- /dev/null
+++ b/app/graphql/types/issue_state_enum.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+module Types
+ class IssueStateEnum < IssuableStateEnum
+ graphql_name 'IssueState'
+ description 'State of a GitLab issue'
+ end
+end
diff --git a/app/graphql/types/issue_type.rb b/app/graphql/types/issue_type.rb
index a8f2f7914a8..5ad3ea52930 100644
--- a/app/graphql/types/issue_type.rb
+++ b/app/graphql/types/issue_type.rb
@@ -11,22 +11,20 @@ module Types
field :iid, GraphQL::ID_TYPE, null: false
field :title, GraphQL::STRING_TYPE, null: false
field :description, GraphQL::STRING_TYPE, null: true
- field :state, GraphQL::STRING_TYPE, null: false
+ field :state, IssueStateEnum, null: false
field :author, Types::UserType,
null: false,
- resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, obj.author_id).find } do
- authorize :read_user
- end
+ resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, obj.author_id).find },
+ authorize: :read_user
field :assignees, Types::UserType.connection_type, null: true
field :labels, Types::LabelType.connection_type, null: true
field :milestone, Types::MilestoneType,
null: true,
- resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Milestone, obj.milestone_id).find } do
- authorize :read_milestone
- end
+ resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Milestone, obj.milestone_id).find },
+ authorize: :read_milestone
field :due_date, Types::TimeType, null: true
field :confidential, GraphQL::BOOLEAN_TYPE, null: false
diff --git a/app/graphql/types/merge_request_state_enum.rb b/app/graphql/types/merge_request_state_enum.rb
new file mode 100644
index 00000000000..92f52726ab3
--- /dev/null
+++ b/app/graphql/types/merge_request_state_enum.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module Types
+ class MergeRequestStateEnum < IssuableStateEnum
+ graphql_name 'MergeRequestState'
+ description 'State of a GitLab merge request'
+
+ value 'merged'
+ end
+end
diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb
index 47b915b451e..1ed27a14e33 100644
--- a/app/graphql/types/merge_request_type.rb
+++ b/app/graphql/types/merge_request_type.rb
@@ -12,7 +12,7 @@ module Types
field :iid, GraphQL::ID_TYPE, null: false
field :title, GraphQL::STRING_TYPE, null: false
field :description, GraphQL::STRING_TYPE, null: true
- field :state, GraphQL::STRING_TYPE, null: true
+ field :state, MergeRequestStateEnum, null: false
field :created_at, Types::TimeType, null: false
field :updated_at, Types::TimeType, null: false
field :source_project, Types::ProjectType, null: true
@@ -38,7 +38,6 @@ module Types
field :should_be_rebased, GraphQL::BOOLEAN_TYPE, method: :should_be_rebased?, null: false
field :rebase_commit_sha, GraphQL::STRING_TYPE, null: true
field :rebase_in_progress, GraphQL::BOOLEAN_TYPE, method: :rebase_in_progress?, null: false
- field :diff_head_sha, GraphQL::STRING_TYPE, null: true
field :merge_commit_message, GraphQL::STRING_TYPE, method: :default_merge_commit_message, null: true, deprecation_reason: "Renamed to defaultMergeCommitMessage"
field :default_merge_commit_message, GraphQL::STRING_TYPE, null: true
field :merge_ongoing, GraphQL::BOOLEAN_TYPE, method: :merge_ongoing?, null: false
@@ -49,9 +48,7 @@ module Types
field :downvotes, GraphQL::INT_TYPE, null: false
field :subscribed, GraphQL::BOOLEAN_TYPE, method: :subscribed?, null: false
- field :head_pipeline, Types::Ci::PipelineType, null: true, method: :actual_head_pipeline do
- authorize :read_pipeline
- end
+ field :head_pipeline, Types::Ci::PipelineType, null: true, method: :actual_head_pipeline, authorize: :read_pipeline
field :pipelines, Types::Ci::PipelineType.connection_type,
resolver: Resolvers::MergeRequestPipelinesResolver
end
diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb
index 050706f97be..3ef0cc5020c 100644
--- a/app/graphql/types/project_type.rb
+++ b/app/graphql/types/project_type.rb
@@ -66,18 +66,28 @@ module Types
field :only_allow_merge_if_all_discussions_are_resolved, GraphQL::BOOLEAN_TYPE, null: true
field :printing_merge_request_link_enabled, GraphQL::BOOLEAN_TYPE, null: true
+ field :merge_requests,
+ Types::MergeRequestType.connection_type,
+ null: true,
+ resolver: Resolvers::MergeRequestsResolver,
+ authorize: :read_merge_request
+
field :merge_request,
Types::MergeRequestType,
null: true,
- resolver: Resolvers::MergeRequestResolver do
- authorize :read_merge_request
- end
+ resolver: Resolvers::MergeRequestsResolver.single,
+ authorize: :read_merge_request
field :issues,
Types::IssueType.connection_type,
null: true,
resolver: Resolvers::IssuesResolver
+ field :issue,
+ Types::IssueType,
+ null: true,
+ resolver: Resolvers::IssuesResolver.single
+
field :pipelines,
Types::Ci::PipelineType.connection_type,
null: false,
diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb
index 7c41716b82a..954bcc0a5a3 100644
--- a/app/graphql/types/query_type.rb
+++ b/app/graphql/types/query_type.rb
@@ -7,9 +7,8 @@ module Types
field :project, Types::ProjectType,
null: true,
resolver: Resolvers::ProjectResolver,
- description: "Find a project" do
- authorize :read_project
- end
+ description: "Find a project",
+ authorize: :read_project
field :echo, GraphQL::STRING_TYPE, null: false, function: Functions::Echo.new
end
diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb
index 7fbbbb04154..c0db9910143 100644
--- a/app/helpers/appearances_helper.rb
+++ b/app/helpers/appearances_helper.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
module AppearancesHelper
+ include MarkupHelper
+
def brand_title
current_appearance&.title.presence || default_brand_title
end
@@ -40,4 +42,36 @@ module AppearancesHelper
render 'shared/logo_type.svg'
end
end
+
+ def header_message
+ return unless current_appearance&.show_header?
+
+ class_names = []
+ class_names << 'with-performance-bar' if performance_bar_enabled?
+
+ render_message(:header_message, class_names: class_names)
+ end
+
+ def footer_message
+ return unless current_appearance&.show_footer?
+
+ render_message(:footer_message)
+ end
+
+ private
+
+ def render_message(field_sym, class_names: [], style: message_style)
+ class_names << field_sym.to_s.dasherize
+
+ content_tag :div, class: class_names, style: style do
+ markdown_field(current_appearance, field_sym)
+ end
+ end
+
+ def message_style
+ style = []
+ style << "background-color: #{current_appearance.message_background_color};"
+ style << "color: #{current_appearance.message_font_color}"
+ style.join
+ end
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 9efa84b02f0..ffa5719fefb 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -214,12 +214,19 @@ module ApplicationHelper
class_names = []
class_names << 'issue-boards-page' if current_controller?(:boards)
class_names << 'with-performance-bar' if performance_bar_enabled?
-
+ class_names << system_message_class
class_names
end
- # EE feature: System header and footer, unavailable in CE
def system_message_class
+ class_names = []
+
+ return class_names unless appearance
+
+ class_names << 'with-system-header' if appearance.show_header?
+ class_names << 'with-system-footer' if appearance.show_footer?
+
+ class_names
end
# Returns active css class when condition returns true
@@ -292,4 +299,10 @@ module ApplicationHelper
snippets: snippets_project_autocomplete_sources_path(object)
}
end
+
+ private
+
+ def appearance
+ ::Appearance.current
+ end
end
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 23d6684a8e6..3e1bb9af5cc 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -31,12 +31,13 @@ module BlobHelper
edit_button_tag(blob,
common_classes,
_('Edit'),
- edit_blob_path(project, ref, path, options),
+ Feature.enabled?(:web_ide_default) ? ide_edit_path(project, ref, path, options) : edit_blob_path(project, ref, path, options),
project,
ref)
end
def ide_edit_button(project = @project, ref = @ref, path = @path, options = {})
+ return if Feature.enabled?(:web_ide_default)
return unless blob = readable_blob(options, path, project, ref)
edit_button_tag(blob,
diff --git a/app/helpers/count_helper.rb b/app/helpers/count_helper.rb
index 13839474e1f..62bb2e4da23 100644
--- a/app/helpers/count_helper.rb
+++ b/app/helpers/count_helper.rb
@@ -13,7 +13,7 @@ module CountHelper
# memberships, and deducting 1 for each root of the fork network.
# This might be inacurate as the root of the fork network might have been deleted.
#
- # This makes querying this information a lot more effecient and it should be
+ # This makes querying this information a lot more efficient and it should be
# accurate enough for the instance wide statistics
def approximate_fork_count_with_delimiters(count_data)
fork_network_count = count_data[ForkNetwork]
diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb
index dedc58f482b..96471d15aac 100644
--- a/app/helpers/emails_helper.rb
+++ b/app/helpers/emails_helper.rb
@@ -131,4 +131,42 @@ module EmailsHelper
project.id.to_s + "." + project_path_as_domain + "." + Gitlab.config.gitlab.host
end
+
+ def html_header_message
+ return unless show_header?
+
+ render_message(:header_message, style: '')
+ end
+
+ def html_footer_message
+ return unless show_footer?
+
+ render_message(:footer_message, style: '')
+ end
+
+ def text_header_message
+ return unless show_header?
+
+ strip_tags(render_message(:header_message, style: ''))
+ end
+
+ def text_footer_message
+ return unless show_footer?
+
+ strip_tags(render_message(:footer_message, style: ''))
+ end
+
+ private
+
+ def show_footer?
+ email_header_and_footer_enabled? && current_appearance&.show_footer?
+ end
+
+ def show_header?
+ email_header_and_footer_enabled? && current_appearance&.show_header?
+ end
+
+ def email_header_and_footer_enabled?
+ current_appearance&.email_header_and_footer_enabled?
+ end
end
diff --git a/app/helpers/import_helper.rb b/app/helpers/import_helper.rb
index d3befd87ccc..3d494c3de6a 100644
--- a/app/helpers/import_helper.rb
+++ b/app/helpers/import_helper.rb
@@ -18,10 +18,8 @@ module ImportHelper
"#{namespace}/#{name}"
end
- def provider_project_link(provider, full_path)
- url = __send__("#{provider}_project_url", full_path) # rubocop:disable GitlabSecurity/PublicSend
-
- link_to full_path, url, target: '_blank', rel: 'noopener noreferrer'
+ def provider_project_link_url(provider_url, full_path)
+ Gitlab::Utils.append_path(provider_url, full_path)
end
def import_will_timeout_message(_ci_cd_only)
@@ -46,10 +44,6 @@ module ImportHelper
_('Please wait while we import the repository for you. Refresh at will.')
end
- def import_github_title
- _('Import repositories from GitHub')
- end
-
def import_github_authorize_message
_('To import GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:')
end
@@ -73,30 +67,4 @@ module ImportHelper
_('Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token.').html_safe % { github_integration_link: github_integration_link }
end
end
-
- def import_githubish_choose_repository_message
- _('Choose which repositories you want to import.')
- end
-
- def import_all_githubish_repositories_button_label
- _('Import all repositories')
- end
-
- private
-
- def github_project_url(full_path)
- Gitlab::Utils.append_path(github_root_url, full_path)
- end
-
- def github_root_url
- strong_memoize(:github_url) do
- provider = Gitlab::Auth::OAuth::Provider.config_for('github')
-
- provider&.dig('url').presence || 'https://github.com'
- end
- end
-
- def gitea_project_url(full_path)
- Gitlab::Utils.append_path(@gitea_host_url, full_path)
- end
end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 1a471034972..af28e6fcb93 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -200,7 +200,7 @@ module IssuablesHelper
author_output
end
- output << content_tag(:span, (issuable_first_contribution_icon if issuable.first_contribution?), class: 'has-tooltip', title: _('1st contribution!'))
+ output << content_tag(:span, (issuable_first_contribution_icon if issuable.first_contribution?), class: 'has-tooltip prepend-left-4', 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 prepend-left-8")
output << content_tag(:span, (issuable.task_status_short if issuable.tasks?), id: "task_status_short", class: "d-md-none")
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index 39f661b5f0c..bd53add80ca 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -227,6 +227,10 @@ module LabelsHelper
"#{action} at #{level} level"
end
+ def labels_sorted_by_title(labels)
+ labels.sort_by(&:title)
+ end
+
# Required for Banzai::Filter::LabelReferenceFilter
module_function :render_colored_label, :text_color_for_bg, :escape_once
end
diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb
index 6c65e573307..ea3bcfc791a 100644
--- a/app/helpers/namespaces_helper.rb
+++ b/app/helpers/namespaces_helper.rb
@@ -5,11 +5,8 @@ module NamespacesHelper
params.dig(:project, :namespace_id) || params[:namespace_id]
end
- # rubocop: disable CodeReuse/ActiveRecord
def namespaces_options(selected = :current_user, display_path: false, groups: nil, extra_group: nil, groups_only: false)
- groups ||= current_user.manageable_groups
- .eager_load(:route)
- .order('routes.path')
+ groups ||= current_user.manageable_groups_with_routes
users = [current_user.namespace]
selected_id = selected
@@ -43,7 +40,6 @@ module NamespacesHelper
grouped_options_for_select(options, selected_id)
end
- # rubocop: enable CodeReuse/ActiveRecord
def namespace_icon(namespace, size = 40)
if namespace.is_a?(Group)
diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb
index bc1742e8167..eed529f93db 100644
--- a/app/helpers/preferences_helper.rb
+++ b/app/helpers/preferences_helper.rb
@@ -62,6 +62,10 @@ module PreferencesHelper
Gitlab::ColorSchemes.for_user(current_user).css_class
end
+ def language_choices
+ Gitlab::I18n::AVAILABLE_LANGUAGES.map { |value, label| [label, value] }
+ end
+
private
# Ensure that anyone adding new options updates `DASHBOARD_CHOICES` too
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index c400302cda3..2ac8ddc5244 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -284,6 +284,20 @@ module ProjectsHelper
can?(current_user, :read_environment, @project)
end
+ def error_tracking_setting_project_json
+ setting = @project.error_tracking_setting
+
+ return if setting.blank? || setting.project_slug.blank? ||
+ setting.organization_slug.blank?
+
+ {
+ name: setting.project_name,
+ organization_name: setting.organization_name,
+ organization_slug: setting.organization_slug,
+ slug: setting.project_slug
+ }.to_json
+ end
+
private
def get_project_nav_tabs(project, current_user)
diff --git a/app/mailers/abuse_report_mailer.rb b/app/mailers/abuse_report_mailer.rb
index e032f568913..e0aa66e6de3 100644
--- a/app/mailers/abuse_report_mailer.rb
+++ b/app/mailers/abuse_report_mailer.rb
@@ -1,6 +1,10 @@
# frozen_string_literal: true
class AbuseReportMailer < BaseMailer
+ layout 'empty_mailer'
+
+ helper EmailsHelper
+
def notify(abuse_report_id)
return unless deliverable?
diff --git a/app/mailers/email_rejection_mailer.rb b/app/mailers/email_rejection_mailer.rb
index 45fc5a6c383..d743533b1bc 100644
--- a/app/mailers/email_rejection_mailer.rb
+++ b/app/mailers/email_rejection_mailer.rb
@@ -1,6 +1,10 @@
# frozen_string_literal: true
class EmailRejectionMailer < BaseMailer
+ layout 'empty_mailer'
+
+ helper EmailsHelper
+
def rejection(reason, original_raw, can_retry = false)
@reason = reason
@original_message = Mail::Message.new(original_raw)
diff --git a/app/mailers/repository_check_mailer.rb b/app/mailers/repository_check_mailer.rb
index 145169be8a6..a24d3476d0e 100644
--- a/app/mailers/repository_check_mailer.rb
+++ b/app/mailers/repository_check_mailer.rb
@@ -2,6 +2,10 @@
class RepositoryCheckMailer < BaseMailer
# rubocop: disable CodeReuse/ActiveRecord
+ layout 'empty_mailer'
+
+ helper EmailsHelper
+
def notify(failed_count)
@message =
if failed_count == 1
diff --git a/app/models/appearance.rb b/app/models/appearance.rb
index ff1ecfda684..bdee9b2b73c 100644
--- a/app/models/appearance.rb
+++ b/app/models/appearance.rb
@@ -8,12 +8,20 @@ class Appearance < ActiveRecord::Base
cache_markdown_field :description
cache_markdown_field :new_project_guidelines
+ cache_markdown_field :header_message, pipeline: :broadcast_message
+ cache_markdown_field :footer_message, pipeline: :broadcast_message
validates :logo, file_size: { maximum: 1.megabyte }
validates :header_logo, file_size: { maximum: 1.megabyte }
+ validates :message_background_color, allow_blank: true, color: true
+ validates :message_font_color, allow_blank: true, color: true
validate :single_appearance_row, on: :create
+ default_value_for :message_background_color, '#E75E40'
+ default_value_for :message_font_color, '#FFFFFF'
+ default_value_for :email_header_and_footer_enabled, false
+
mount_uploader :logo, AttachmentUploader
mount_uploader :header_logo, AttachmentUploader
mount_uploader :favicon, FaviconUploader
@@ -41,6 +49,14 @@ class Appearance < ActiveRecord::Base
logo_system_path(favicon, 'favicon')
end
+ def show_header?
+ header_message.present?
+ end
+
+ def show_footer?
+ footer_message.present?
+ end
+
private
def logo_system_path(logo, mount_type)
diff --git a/app/models/board.rb b/app/models/board.rb
index a137863456c..758a71d6903 100644
--- a/app/models/board.rb
+++ b/app/models/board.rb
@@ -21,6 +21,10 @@ class Board < ActiveRecord::Base
group_id.present?
end
+ def project_board?
+ project_id.present?
+ end
+
def backlog_list
lists.merge(List.backlog).take
end
diff --git a/app/models/board_group_recent_visit.rb b/app/models/board_group_recent_visit.rb
index 92abbb67222..f5b75270595 100644
--- a/app/models/board_group_recent_visit.rb
+++ b/app/models/board_group_recent_visit.rb
@@ -10,7 +10,7 @@ class BoardGroupRecentVisit < ActiveRecord::Base
validates :group, presence: true
validates :board, presence: true
- scope :by_user_group, -> (user, group) { where(user: user, group: group).order(:updated_at) }
+ scope :by_user_group, -> (user, group) { where(user: user, group: group) }
def self.visited!(user, board)
visit = find_or_create_by(user: user, group: board.group, board: board)
@@ -19,7 +19,10 @@ class BoardGroupRecentVisit < ActiveRecord::Base
retry
end
- def self.latest(user, group)
- by_user_group(user, group).last
+ def self.latest(user, group, count: nil)
+ visits = by_user_group(user, group).order(updated_at: :desc)
+ visits = visits.preload(:board) if count && count > 1
+
+ visits.first(count)
end
end
diff --git a/app/models/board_project_recent_visit.rb b/app/models/board_project_recent_visit.rb
index 7cffff906d8..2a1b14b3ae0 100644
--- a/app/models/board_project_recent_visit.rb
+++ b/app/models/board_project_recent_visit.rb
@@ -10,7 +10,7 @@ class BoardProjectRecentVisit < ActiveRecord::Base
validates :project, presence: true
validates :board, presence: true
- scope :by_user_project, -> (user, project) { where(user: user, project: project).order(:updated_at) }
+ scope :by_user_project, -> (user, project) { where(user: user, project: project) }
def self.visited!(user, board)
visit = find_or_create_by(user: user, project: board.project, board: board)
@@ -19,7 +19,10 @@ class BoardProjectRecentVisit < ActiveRecord::Base
retry
end
- def self.latest(user, project)
- by_user_project(user, project).last
+ def self.latest(user, project, count: nil)
+ visits = by_user_project(user, project).order(updated_at: :desc)
+ visits = visits.preload(:board) if count && count > 1
+
+ visits.first(count)
end
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 6b2b7e77180..65962fba14d 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -46,6 +46,7 @@ module Ci
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
+ delegate :merge_request_event?, to: :pipeline
##
# Since Gitlab 11.5, deployments records started being created right after
@@ -441,11 +442,13 @@ module Ci
# All variables, including persisted environment variables.
#
def variables
- Gitlab::Ci::Variables::Collection.new
- .concat(persisted_variables)
- .concat(scoped_variables)
- .concat(persisted_environment_variables)
- .to_runner_variables
+ strong_memoize(:variables) do
+ Gitlab::Ci::Variables::Collection.new
+ .concat(persisted_variables)
+ .concat(scoped_variables)
+ .concat(persisted_environment_variables)
+ .to_runner_variables
+ end
end
##
diff --git a/app/models/ci/build_trace_chunk.rb b/app/models/ci/build_trace_chunk.rb
index da08214963f..75017f224a0 100644
--- a/app/models/ci/build_trace_chunk.rb
+++ b/app/models/ci/build_trace_chunk.rb
@@ -18,7 +18,7 @@ module Ci
FailedToPersistDataError = Class.new(StandardError)
# Note: The ordering of this enum is related to the precedence of persist store.
- # The bottom item takes the higest precedence, and the top item takes the lowest precedence.
+ # The bottom item takes the highest precedence, and the top item takes the lowest precedence.
enum data_store: {
redis: 1,
database: 2,
@@ -115,7 +115,7 @@ module Ci
current_data = get_data
unless current_data&.bytesize.to_i == CHUNK_SIZE
- raise FailedToPersistDataError, 'Data is not fullfilled in a bucket'
+ raise FailedToPersistDataError, 'Data is not fulfilled in a bucket'
end
old_store_class = self.class.get_store_class(data_store)
diff --git a/app/models/ci/group_variable.rb b/app/models/ci/group_variable.rb
index 492d1d0329e..323ff560564 100644
--- a/app/models/ci/group_variable.rb
+++ b/app/models/ci/group_variable.rb
@@ -5,6 +5,7 @@ module Ci
extend Gitlab::Ci::Model
include HasVariable
include Presentable
+ include Maskable
belongs_to :group, class_name: "::Group"
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index acef5d2e643..317f965da1c 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -12,6 +12,10 @@ module Ci
include AtomicInternalId
include EnumWithNil
include HasRef
+ include ShaAttribute
+
+ sha_attribute :source_sha
+ sha_attribute :target_sha
belongs_to :project, inverse_of: :all_pipelines
belongs_to :user
@@ -47,6 +51,8 @@ module Ci
has_many :auto_canceled_pipelines, class_name: 'Ci::Pipeline', foreign_key: 'auto_canceled_by_id'
has_many :auto_canceled_jobs, class_name: 'CommitStatus', foreign_key: 'auto_canceled_by_id'
+ has_one :chat_data, class_name: 'Ci::PipelineChatData'
+
accepts_nested_attributes_for :variables, reject_if: :persisted?
delegate :id, to: :project, prefix: true
@@ -54,9 +60,9 @@ module Ci
validates :sha, presence: { unless: :importing? }
validates :ref, presence: { unless: :importing? }
- validates :merge_request, presence: { if: :merge_request? }
- validates :merge_request, absence: { unless: :merge_request? }
- validates :tag, inclusion: { in: [false], if: :merge_request? }
+ validates :merge_request, presence: { if: :merge_request_event? }
+ validates :merge_request, absence: { unless: :merge_request_event? }
+ validates :tag, inclusion: { in: [false], if: :merge_request_event? }
validates :status, presence: { unless: :importing? }
validate :valid_commit_sha, unless: :importing?
validates :source, exclusion: { in: %w(unknown), unless: :importing? }, on: :create
@@ -173,7 +179,7 @@ module Ci
scope :sort_by_merge_request_pipelines, -> do
sql = 'CASE ci_pipelines.source WHEN (?) THEN 0 ELSE 1 END, ci_pipelines.id DESC'
- query = ActiveRecord::Base.send(:sanitize_sql_array, [sql, sources[:merge_request]]) # rubocop:disable GitlabSecurity/PublicSend
+ query = ActiveRecord::Base.send(:sanitize_sql_array, [sql, sources[:merge_request_event]]) # rubocop:disable GitlabSecurity/PublicSend
order(query)
end
@@ -189,6 +195,22 @@ module Ci
.sort_by_merge_request_pipelines
end
+ scope :triggered_by_merge_request, -> (merge_request) do
+ where(source: :merge_request_event, merge_request: merge_request)
+ end
+
+ scope :detached_merge_request_pipelines, -> (merge_request) do
+ triggered_by_merge_request(merge_request).where(target_sha: nil)
+ end
+
+ scope :merge_request_pipelines, -> (merge_request) do
+ triggered_by_merge_request(merge_request).where.not(target_sha: nil)
+ end
+
+ scope :mergeable_merge_request_pipelines, -> (merge_request) do
+ triggered_by_merge_request(merge_request).where(target_sha: merge_request.target_branch_sha)
+ end
+
# Returns the pipelines in descending order (= newest first), optionally
# limited to a number of references.
#
@@ -315,7 +337,7 @@ module Ci
def ordered_stages
return legacy_stages unless complete?
- if Feature.enabled?('ci_pipeline_persisted_stages')
+ if Feature.enabled?('ci_pipeline_persisted_stages', default_enabled: true)
stages
else
legacy_stages
@@ -395,10 +417,6 @@ module Ci
@commit ||= Commit.lazy(project, sha)
end
- def branch?
- super && !merge_request?
- end
-
def stuck?
pending_builds.any?(&:stuck?)
end
@@ -621,7 +639,9 @@ module Ci
variables.append(key: 'CI_COMMIT_TITLE', value: git_commit_full_title.to_s)
variables.append(key: 'CI_COMMIT_DESCRIPTION', value: git_commit_description.to_s)
- if merge_request? && merge_request
+ if merge_request_event? && merge_request
+ variables.append(key: 'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA', value: source_sha.to_s)
+ variables.append(key: 'CI_MERGE_REQUEST_TARGET_BRANCH_SHA', value: target_sha.to_s)
variables.concat(merge_request.predefined_variables)
end
end
@@ -649,10 +669,10 @@ module Ci
# All the merge requests for which the current pipeline runs/ran against
def all_merge_requests
@all_merge_requests ||=
- if merge_request?
- project.merge_requests.where(id: merge_request_id)
+ if merge_request_event?
+ MergeRequest.where(id: merge_request_id)
else
- project.merge_requests.where(source_branch: ref)
+ MergeRequest.where(source_project_id: project_id, source_branch: ref)
end
end
@@ -687,9 +707,18 @@ module Ci
end
end
+ # Returns the modified paths.
+ #
+ # The returned value is
+ # * Array: List of modified paths that should be evaluated
+ # * nil: Modified path can not be evaluated
def modified_paths
strong_memoize(:modified_paths) do
- push_details.modified_paths
+ if merge_request_event?
+ merge_request.modified_paths
+ elsif branch_updated?
+ push_details.modified_paths
+ end
end
end
@@ -697,6 +726,22 @@ module Ci
ref == project.default_branch
end
+ def triggered_by_merge_request?
+ merge_request_event? && merge_request_id.present?
+ end
+
+ def detached_merge_request_pipeline?
+ triggered_by_merge_request? && target_sha.nil?
+ end
+
+ def merge_request_pipeline?
+ triggered_by_merge_request? && target_sha.present?
+ end
+
+ def mergeable_merge_request_pipeline?
+ triggered_by_merge_request? && target_sha == merge_request.target_branch_sha
+ end
+
private
def ci_yaml_from_repo
@@ -728,7 +773,7 @@ module Ci
end
def git_ref
- if merge_request?
+ if merge_request_event?
##
# In the future, we're going to change this ref to
# merge request's merged reference, such as "refs/merge-requests/:iid/merge".
diff --git a/app/models/ci/pipeline_chat_data.rb b/app/models/ci/pipeline_chat_data.rb
new file mode 100644
index 00000000000..8d37500fec5
--- /dev/null
+++ b/app/models/ci/pipeline_chat_data.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Ci
+ class PipelineChatData < ActiveRecord::Base
+ self.table_name = 'ci_pipeline_chat_data'
+
+ belongs_to :chat_name
+
+ validates :pipeline_id, presence: true
+ validates :chat_name_id, presence: true
+ validates :response_url, presence: true
+ end
+end
diff --git a/app/models/ci/pipeline_enums.rb b/app/models/ci/pipeline_enums.rb
index 2994aaae4aa..571c4271475 100644
--- a/app/models/ci/pipeline_enums.rb
+++ b/app/models/ci/pipeline_enums.rb
@@ -22,7 +22,8 @@ module Ci
schedule: 4,
api: 5,
external: 6,
- merge_request: 10
+ chat: 8,
+ merge_request_event: 10
}
end
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 5aae31de6e2..d82e11bbb89 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -97,6 +97,7 @@ module Ci
scope :order_contacted_at_asc, -> { order(contacted_at: :asc) }
scope :order_created_at_desc, -> { order(created_at: :desc) }
+ scope :with_tags, -> { preload(:tags) }
validate :tag_constraints
validates :access_level, presence: true
diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb
index 524d79014f8..64836ea4fa4 100644
--- a/app/models/ci/variable.rb
+++ b/app/models/ci/variable.rb
@@ -5,6 +5,7 @@ module Ci
extend Gitlab::Ci::Model
include HasVariable
include Presentable
+ include Maskable
belongs_to :project
diff --git a/app/models/clusters/applications/prometheus.rb b/app/models/clusters/applications/prometheus.rb
index 52c440ffb2f..fa7ce363531 100644
--- a/app/models/clusters/applications/prometheus.rb
+++ b/app/models/clusters/applications/prometheus.rb
@@ -6,7 +6,6 @@ module Clusters
include PrometheusAdapter
VERSION = '6.7.3'
- READY_STATUS = [:installed, :updating, :updated, :update_errored].freeze
self.table_name = 'clusters_applications_prometheus'
@@ -25,10 +24,6 @@ module Clusters
end
end
- def ready?
- READY_STATUS.include?(status_name)
- end
-
def chart
'stable/prometheus'
end
diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb
index f17da0bb7b1..941551dadaa 100644
--- a/app/models/clusters/applications/runner.rb
+++ b/app/models/clusters/applications/runner.rb
@@ -3,7 +3,7 @@
module Clusters
module Applications
class Runner < ActiveRecord::Base
- VERSION = '0.1.45'.freeze
+ VERSION = '0.2.0'.freeze
self.table_name = 'clusters_applications_runners'
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index 7025fc2cc02..be3e6a05e1e 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -50,7 +50,7 @@ module Clusters
validates :name, cluster_name: true
validates :cluster_type, presence: true
- validates :domain, allow_blank: true, hostname: { allow_numeric_hostname: true, require_valid_tld: true }
+ validates :domain, allow_blank: true, hostname: { allow_numeric_hostname: true }
validate :restrict_modification, on: :update
validate :no_groups, unless: :group_type?
@@ -99,7 +99,7 @@ module Clusters
where('NOT EXISTS (?)', subquery)
end
- scope :with_knative_installed, -> { joins(:application_knative).merge(Clusters::Applications::Knative.installed) }
+ scope :with_knative_installed, -> { joins(:application_knative).merge(Clusters::Applications::Knative.available) }
scope :preload_knative, -> {
preload(
diff --git a/app/models/clusters/concerns/application_status.rb b/app/models/clusters/concerns/application_status.rb
index 5c0164831bc..1273ed83abe 100644
--- a/app/models/clusters/concerns/application_status.rb
+++ b/app/models/clusters/concerns/application_status.rb
@@ -6,7 +6,14 @@ module Clusters
extend ActiveSupport::Concern
included do
- scope :installed, -> { where(status: self.state_machines[:status].states[:installed].value) }
+ scope :available, -> do
+ where(
+ status: [
+ self.state_machines[:status].states[:installed].value,
+ self.state_machines[:status].states[:updated].value
+ ]
+ )
+ end
state_machine :status, initial: :not_installable do
state :not_installable, value: -2
diff --git a/app/models/commit_collection.rb b/app/models/commit_collection.rb
index 42ec5b5e664..a9a2e9c81eb 100644
--- a/app/models/commit_collection.rb
+++ b/app/models/commit_collection.rb
@@ -20,8 +20,8 @@ class CommitCollection
commits.each(&block)
end
- def committers
- emails = without_merge_commits.map(&:committer_email).uniq
+ def authors
+ emails = without_merge_commits.map(&:author_email).uniq
User.by_any_email(emails)
end
diff --git a/app/models/concerns/closed_at_filterable.rb b/app/models/concerns/closed_at_filterable.rb
new file mode 100644
index 00000000000..239c2e47611
--- /dev/null
+++ b/app/models/concerns/closed_at_filterable.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module ClosedAtFilterable
+ extend ActiveSupport::Concern
+
+ included do
+ scope :closed_before, ->(date) { where(scoped_table[:closed_at].lteq(date)) }
+ scope :closed_after, ->(date) { where(scoped_table[:closed_at].gteq(date)) }
+
+ def self.scoped_table
+ arel_table.alias(table_name)
+ end
+ end
+end
diff --git a/app/models/concerns/fast_destroy_all.rb b/app/models/concerns/fast_destroy_all.rb
index 1e3afd641ed..f862031bce0 100644
--- a/app/models/concerns/fast_destroy_all.rb
+++ b/app/models/concerns/fast_destroy_all.rb
@@ -11,7 +11,7 @@
# it is difficult to accomplish it.
#
# This module defines a format to use `delete_all` and delete associated external data.
-# Here is an exmaple
+# Here is an example
#
# Situation
# - `Project` has many `Ci::BuildTraceChunk` through `Ci::Build`
diff --git a/app/models/concerns/has_ref.rb b/app/models/concerns/has_ref.rb
index d7089294efc..e0a48a930cb 100644
--- a/app/models/concerns/has_ref.rb
+++ b/app/models/concerns/has_ref.rb
@@ -4,7 +4,7 @@ module HasRef
extend ActiveSupport::Concern
def branch?
- !tag?
+ !tag? && !merge_request_event?
end
def git_ref
diff --git a/app/models/concerns/has_variable.rb b/app/models/concerns/has_variable.rb
index dfbe413a878..2ec42a1029b 100644
--- a/app/models/concerns/has_variable.rb
+++ b/app/models/concerns/has_variable.rb
@@ -21,9 +21,9 @@ module HasVariable
def key=(new_key)
super(new_key.to_s.strip)
end
+ end
- def to_runner_variable
- { key: key, value: value, public: false }
- end
+ def to_runner_variable
+ { key: key, value: value, public: false }
end
end
diff --git a/app/models/concerns/iid_routes.rb b/app/models/concerns/iid_routes.rb
index b7f99e845ca..3eeb29b6595 100644
--- a/app/models/concerns/iid_routes.rb
+++ b/app/models/concerns/iid_routes.rb
@@ -4,7 +4,7 @@ module IidRoutes
##
# This automagically enforces all related routes to use `iid` instead of `id`
# If you want to use `iid` for some routes and `id` for other routes, this module should not to be included,
- # instead you should define `iid` or `id` explictly at each route generators. e.g. pipeline_path(project.id, pipeline.iid)
+ # instead you should define `iid` or `id` explicitly at each route generators. e.g. pipeline_path(project.id, pipeline.iid)
def to_param
iid.to_s
end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 0a77fbeba08..670103bc3f3 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -23,11 +23,12 @@ module Issuable
include Sortable
include CreatedAtFilterable
include UpdatedAtFilterable
+ include ClosedAtFilterable
# This object is used to gather issuable meta data for displaying
# upvotes, downvotes, notes and closing merge requests count for issues and merge requests
# lists avoiding n+1 queries and improving performance.
- IssuableMeta = Struct.new(:upvotes, :downvotes, :notes_count, :merge_requests_count)
+ IssuableMeta = Struct.new(:upvotes, :downvotes, :user_notes_count, :merge_requests_count)
included do
cache_markdown_field :title, pipeline: :single_line
@@ -35,8 +36,8 @@ module Issuable
redact_field :description
- belongs_to :author, class_name: "User"
- belongs_to :updated_by, class_name: "User"
+ belongs_to :author, class_name: 'User'
+ belongs_to :updated_by, class_name: 'User'
belongs_to :last_edited_by, class_name: 'User'
belongs_to :milestone
diff --git a/app/models/concerns/maskable.rb b/app/models/concerns/maskable.rb
new file mode 100644
index 00000000000..8793f0ec965
--- /dev/null
+++ b/app/models/concerns/maskable.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Maskable
+ extend ActiveSupport::Concern
+
+ # * Single line
+ # * No escape characters
+ # * No variables
+ # * No spaces
+ # * Minimal length of 8 characters
+ # * Absolutely no fun is allowed
+ REGEX = /\A\w{8,}\z/
+
+ included do
+ validates :masked, inclusion: { in: [true, false] }
+ validates :value, format: { with: REGEX }, if: :masked?
+ end
+
+ def to_runner_variable
+ super.merge(masked: masked?)
+ end
+end
diff --git a/app/models/concerns/reactive_caching.rb b/app/models/concerns/reactive_caching.rb
index d3572875fb3..de77ca3e963 100644
--- a/app/models/concerns/reactive_caching.rb
+++ b/app/models/concerns/reactive_caching.rb
@@ -76,7 +76,7 @@ module ReactiveCaching
begin
data = Rails.cache.read(full_reactive_cache_key(*args))
- yield data if data.present?
+ yield data unless data.nil?
rescue InvalidateReactiveCache
refresh_reactive_cache!(*args)
nil
diff --git a/app/models/concerns/sha_attribute.rb b/app/models/concerns/sha_attribute.rb
index e51b4e22c96..a479bef993c 100644
--- a/app/models/concerns/sha_attribute.rb
+++ b/app/models/concerns/sha_attribute.rb
@@ -16,6 +16,8 @@ module ShaAttribute
# the column is the correct type. In production it should behave like any other attribute.
# See https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/5502 for more discussion
def validate_binary_column_exists!(name)
+ return unless database_exists?
+
unless table_exists?
warn "WARNING: sha_attribute #{name.inspect} is invalid since the table doesn't exist - you may need to run database migrations"
return
@@ -35,5 +37,13 @@ module ShaAttribute
Gitlab::AppLogger.error "ShaAttribute initialization: #{error.message}"
raise
end
+
+ def database_exists?
+ ActiveRecord::Base.connection
+
+ true
+ rescue
+ false
+ end
end
end
diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb
index 279603496b0..805092e527a 100644
--- a/app/models/diff_note.rb
+++ b/app/models/diff_note.rb
@@ -41,6 +41,14 @@ class DiffNote < Note
create_note_diff_file(creation_params)
end
+ # Returns the diff file from `position`
+ def latest_diff_file
+ strong_memoize(:latest_diff_file) do
+ position.diff_file(project.repository)
+ end
+ end
+
+ # Returns the diff file from `original_position`
def diff_file
strong_memoize(:diff_file) do
enqueue_diff_file_creation_job if should_create_diff_file?
diff --git a/app/models/discussion.rb b/app/models/discussion.rb
index f2678e0597d..32529ebf71d 100644
--- a/app/models/discussion.rb
+++ b/app/models/discussion.rb
@@ -17,8 +17,6 @@ class Discussion
:for_commit?,
:for_merge_request?,
- :save,
-
to: :first_note
def project_id
diff --git a/app/models/environment.rb b/app/models/environment.rb
index 1fc088b12ae..87bdb52b58b 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -243,6 +243,10 @@ class Environment < ActiveRecord::Base
self.environment_type || self.name
end
+ def name_without_type
+ @name_without_type ||= name.delete_prefix("#{environment_type}/")
+ end
+
def deployment_platform
strong_memoize(:deployment_platform) do
project.deployment_platform(environment: self.name)
diff --git a/app/models/error_tracking/project_error_tracking_setting.rb b/app/models/error_tracking/project_error_tracking_setting.rb
index 31084c54bdc..1e2bd3bda7f 100644
--- a/app/models/error_tracking/project_error_tracking_setting.rb
+++ b/app/models/error_tracking/project_error_tracking_setting.rb
@@ -2,19 +2,30 @@
module ErrorTracking
class ProjectErrorTrackingSetting < ActiveRecord::Base
+ include Gitlab::Utils::StrongMemoize
include ReactiveCaching
+ API_URL_PATH_REGEXP = %r{
+ \A
+ (?<prefix>/api/0/projects/+)
+ (?:
+ (?<organization>[^/]+)/+
+ (?<project>[^/]+)/*
+ )?
+ \z
+ }x
+
self.reactive_cache_key = ->(setting) { [setting.class.model_name.singular, setting.project_id] }
belongs_to :project
validates :api_url, length: { maximum: 255 }, public_url: true, url: { enforce_sanitization: true, ascii_only: true }, allow_nil: true
- validates :api_url, presence: true, if: :enabled
+ validates :api_url, presence: { message: 'is a required field' }, if: :enabled
validate :validate_api_url_path, if: :enabled
- validates :token, presence: true, if: :enabled
+ validates :token, presence: { message: 'is a required field' }, if: :enabled
attr_encrypted :token,
mode: :per_attribute_iv,
@@ -23,6 +34,11 @@ module ErrorTracking
after_save :clear_reactive_cache!
+ def api_url=(value)
+ super
+ clear_memoization(:api_url_slugs)
+ end
+
def project_name
super || project_name_from_slug
end
@@ -40,6 +56,8 @@ module ErrorTracking
end
def self.build_api_url_from(api_host:, project_slug:, organization_slug:)
+ return if api_host.blank?
+
uri = Addressable::URI.parse("#{api_host}/api/0/projects/#{organization_slug}/#{project_slug}/")
uri.path = uri.path.squeeze('/')
@@ -58,7 +76,7 @@ module ErrorTracking
def list_sentry_issues(opts = {})
with_reactive_cache('list_issues', opts.stringify_keys) do |result|
- { issues: result }
+ result
end
end
@@ -69,8 +87,10 @@ module ErrorTracking
def calculate_reactive_cache(request, opts)
case request
when 'list_issues'
- sentry_client.list_issues(**opts.symbolize_keys)
+ { issues: sentry_client.list_issues(**opts.symbolize_keys) }
end
+ rescue Sentry::Client::Error => e
+ { error: e.message }
end
# http://HOST/api/0/projects/ORG/PROJECT
@@ -98,34 +118,39 @@ module ErrorTracking
end
def project_slug_from_api_url
- extract_slug(:project)
+ api_url_slug(:project)
end
def organization_slug_from_api_url
- extract_slug(:organization)
+ api_url_slug(:organization)
+ end
+
+ def api_url_slug(capture)
+ slugs = strong_memoize(:api_url_slugs) { extract_api_url_slugs || {} }
+ slugs[capture]
end
- def extract_slug(capture)
+ def extract_api_url_slugs
return if api_url.blank?
begin
url = Addressable::URI.parse(api_url)
rescue Addressable::URI::InvalidURIError
- return nil
+ return
end
- @slug_match ||= url.path.match(%r{^/api/0/projects/+(?<organization>[^/]+)/+(?<project>[^/|$]+)}) || {}
- @slug_match[capture]
+ url.path.match(API_URL_PATH_REGEXP)
end
def validate_api_url_path
return if api_url.blank?
- begin
- unless Addressable::URI.parse(api_url).path.starts_with?('/api/0/projects')
- errors.add(:api_url, 'path needs to start with /api/0/projects')
- end
- rescue Addressable::URI::InvalidURIError
+ unless api_url_slug(:prefix)
+ return errors.add(:api_url, 'is invalid')
+ end
+
+ unless api_url_slug(:organization)
+ errors.add(:project, 'is a required field')
end
end
end
diff --git a/app/models/individual_note_discussion.rb b/app/models/individual_note_discussion.rb
index aab0ff93468..b4a661ae5b4 100644
--- a/app/models/individual_note_discussion.rb
+++ b/app/models/individual_note_discussion.rb
@@ -17,8 +17,12 @@ class IndividualNoteDiscussion < Discussion
noteable.supports_replying_to_individual_notes? && Feature.enabled?(:reply_to_individual_notes)
end
- def convert_to_discussion!
- first_note.becomes!(Discussion.note_class).to_discussion
+ def convert_to_discussion!(save: false)
+ first_note.becomes!(Discussion.note_class).to_discussion.tap do
+ # Save needs to be called on first_note instead of the transformed note
+ # because of https://gitlab.com/gitlab-org/gitlab-ce/issues/57324
+ first_note.save if save
+ end
end
def reply_attributes
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 182c5d3d4b0..071ad50fddc 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -66,6 +66,7 @@ class Issue < ActiveRecord::Base
scope :preload_associations, -> { preload(:labels, project: :namespace) }
scope :public_only, -> { where(confidential: false) }
+ scope :confidential_only, -> { where(confidential: true) }
after_save :expire_etag_cache
after_save :ensure_metrics, unless: :imported?
@@ -262,6 +263,10 @@ class Issue < ActiveRecord::Base
end
# rubocop: enable CodeReuse/ServiceClass
+ def merge_requests_count
+ merge_requests_closing_issues.count
+ end
+
private
def ensure_metrics
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 2035bffd829..1468ae1c34a 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -286,12 +286,12 @@ class MergeRequest < ActiveRecord::Base
work_in_progress?(title) ? title : "WIP: #{title}"
end
- def committers
- @committers ||= commits.committers
+ def commit_authors
+ @commit_authors ||= commits.authors
end
def authors
- User.from_union([committers, User.where(id: self.author_id)])
+ User.from_union([commit_authors, User.where(id: self.author_id)])
end
# Verifies if title has changed not taking into account WIP prefix
@@ -764,6 +764,16 @@ class MergeRequest < ActiveRecord::Base
true
end
+ def mergeable_to_ref?
+ return false if merged?
+ return false if broken?
+
+ # Given the `merge_ref_path` will have the same
+ # state the `target_branch` would have. Ideally
+ # we need to check if it can be merged to it.
+ project.repository.can_be_merged?(diff_head_sha, target_branch)
+ end
+
def ff_merge_possible?
project.repository.ancestor?(target_branch_sha, diff_head_sha)
end
@@ -1077,6 +1087,10 @@ class MergeRequest < ActiveRecord::Base
"refs/#{Repository::REF_MERGE_REQUEST}/#{iid}/head"
end
+ def merge_ref_path
+ "refs/#{Repository::REF_MERGE_REQUEST}/#{iid}/merge"
+ end
+
def in_locked_state
begin
lock_mr
@@ -1322,7 +1336,7 @@ class MergeRequest < ActiveRecord::Base
def base_pipeline
@base_pipeline ||= project.ci_pipelines
.order(id: :desc)
- .find_by(sha: diff_base_sha)
+ .find_by(sha: diff_base_sha, ref: target_branch)
end
def discussions_rendered_on_frontend?
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 712347e76ed..e286a4e57f2 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -12,9 +12,6 @@ class MergeRequestDiff < ActiveRecord::Base
# Don't display more than 100 commits at once
COMMITS_SAFE_SIZE = 100
- ignore_column :st_commits,
- :st_diffs
-
belongs_to :merge_request
manual_inverse_association :merge_request, :merge_request_diff
diff --git a/app/models/network/graph.rb b/app/models/network/graph.rb
index 6da3bb7bfb7..ecbeb24ee0a 100644
--- a/app/models/network/graph.rb
+++ b/app/models/network/graph.rb
@@ -40,9 +40,12 @@ module Network
# Get commits from repository
#
def collect_commits
- find_commits(count_to_display_commit_in_center).map do |commit|
- # Decorate with app/model/network/commit.rb
- Network::Commit.new(commit)
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/58013
+ Gitlab::GitalyClient.allow_n_plus_1_calls do
+ find_commits(count_to_display_commit_in_center).map do |commit|
+ # Decorate with app/model/network/commit.rb
+ Network::Commit.new(commit)
+ end
end
end
diff --git a/app/models/notification_recipient.rb b/app/models/notification_recipient.rb
index 9f16eefe074..481c1d963c6 100644
--- a/app/models/notification_recipient.rb
+++ b/app/models/notification_recipient.rb
@@ -153,7 +153,7 @@ class NotificationRecipient
user.global_notification_setting
end
- # Returns the notificaton_setting of the lowest group in hierarchy with non global level
+ # Returns the notification_setting of the lowest group in hierarchy with non global level
def closest_non_global_group_notification_settting
return unless @group
return if indexed_group_notification_settings.empty?
diff --git a/app/models/personal_access_token.rb b/app/models/personal_access_token.rb
index 73a58f2420e..ed78a46eaf3 100644
--- a/app/models/personal_access_token.rb
+++ b/app/models/personal_access_token.rb
@@ -2,8 +2,11 @@
class PersonalAccessToken < ActiveRecord::Base
include Expirable
+ include IgnorableColumn
include TokenAuthenticatable
- add_authentication_token_field :token, digest: true, fallback: true
+
+ add_authentication_token_field :token, digest: true
+ ignore_column :token
REDIS_EXPIRY_TIME = 3.minutes
diff --git a/app/models/project.rb b/app/models/project.rb
index c72d3a3b725..00592c108db 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -160,6 +160,7 @@ class Project < ActiveRecord::Base
has_one :pushover_service
has_one :jira_service
has_one :redmine_service
+ has_one :youtrack_service
has_one :custom_issue_tracker_service
has_one :bugzilla_service
has_one :gitlab_issue_tracker_service, inverse_of: :project
@@ -248,10 +249,10 @@ class Project < ActiveRecord::Base
has_many :container_repositories, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :commit_statuses
- # The relation :all_pipelines is intented to be used when we want to get the
+ # The relation :all_pipelines is intended to be used when we want to get the
# whole list of pipelines associated to the project
has_many :all_pipelines, class_name: 'Ci::Pipeline', inverse_of: :project
- # The relation :ci_pipelines is intented to be used when we want to get only
+ # The relation :ci_pipelines is intended to be used when we want to get only
# those pipeline which are directly related to CI. There are
# other pipelines, like webide ones, that we won't retrieve
# if we use this relation.
@@ -305,6 +306,7 @@ class Project < ActiveRecord::Base
delegate :group_runners_enabled, :group_runners_enabled=, :group_runners_enabled?, to: :ci_cd_settings
delegate :group_clusters_enabled?, to: :group, allow_nil: true
delegate :root_ancestor, to: :namespace, allow_nil: true
+ delegate :last_pipeline, to: :commit, allow_nil: true
# Validations
validates :creator, presence: true, on: :create
@@ -595,6 +597,14 @@ class Project < ActiveRecord::Base
end
end
+ def ci_pipelines
+ if builds_enabled?
+ super
+ else
+ super.external
+ end
+ end
+
# returns all ancestor-groups upto but excluding the given namespace
# when no namespace is given, all ancestors upto the top are returned
def ancestors_upto(top = nil, hierarchy_order: nil)
@@ -629,6 +639,10 @@ class Project < ActiveRecord::Base
auto_devops&.enabled.nil? && !(Gitlab::CurrentSettings.auto_devops_enabled? || Feature.enabled?(:force_autodevops_on_by_default, self))
end
+ def daily_statistics_enabled?
+ Feature.enabled?(:project_daily_statistics, self, default_enabled: true)
+ end
+
def empty_repo?
repository.empty?
end
@@ -1206,7 +1220,7 @@ class Project < ActiveRecord::Base
"#{web_url}.git"
end
- # Is overriden in EE
+ # Is overridden in EE
def lfs_http_url_to_repo(_)
http_url_to_repo
end
@@ -1829,7 +1843,7 @@ class Project < ActiveRecord::Base
# Set repository as writable again
def set_repository_writable!
with_lock do
- update_column(repository_read_only, false)
+ update_column(:repository_read_only, false)
end
end
@@ -1916,6 +1930,14 @@ class Project < ActiveRecord::Base
persisted? && path_changed?
end
+ def human_merge_method
+ if merge_method == :ff
+ 'Fast-forward'
+ else
+ merge_method.to_s.humanize
+ end
+ end
+
def merge_method
if self.merge_requests_ff_only_enabled
:ff
@@ -1948,9 +1970,19 @@ class Project < ActiveRecord::Base
return unless storage_upgradable?
if git_transfer_in_progress?
- ProjectMigrateHashedStorageWorker.perform_in(Gitlab::ReferenceCounter::REFERENCE_EXPIRE_TIME, id)
+ HashedStorage::ProjectMigrateWorker.perform_in(Gitlab::ReferenceCounter::REFERENCE_EXPIRE_TIME, id)
+ else
+ HashedStorage::ProjectMigrateWorker.perform_async(id)
+ end
+ end
+
+ def rollback_to_legacy_storage!
+ return if legacy_storage?
+
+ if git_transfer_in_progress?
+ HashedStorage::ProjectRollbackWorker.perform_in(Gitlab::ReferenceCounter::REFERENCE_EXPIRE_TIME, id)
else
- ProjectMigrateHashedStorageWorker.perform_async(id)
+ HashedStorage::ProjectRollbackWorker.perform_async(id)
end
end
diff --git a/app/models/project_daily_statistic.rb b/app/models/project_daily_statistic.rb
new file mode 100644
index 00000000000..ff115dd010f
--- /dev/null
+++ b/app/models/project_daily_statistic.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+class ProjectDailyStatistic < ActiveRecord::Base
+ belongs_to :project
+
+ scope :of_project, -> (project) { where(project: project) }
+ scope :of_last_30_days, -> { where('date >= ?', 29.days.ago.utc.to_date) }
+ scope :sorted_by_date_desc, -> { order(project_id: :desc, date: :desc) }
+ scope :sum_fetch_count, -> { sum(:fetch_count) }
+end
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 9066a0b7f1d..f7064d5aaea 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -215,7 +215,7 @@ class JiraService < IssueTrackerService
end
def add_issue_solved_comment(issue, commit_id, commit_url)
- link_title = "GitLab: Solved by commit #{commit_id}."
+ link_title = "Solved by commit #{commit_id}."
comment = "Issue solved with [#{commit_id}|#{commit_url}]."
link_props = build_remote_link_props(url: commit_url, title: link_title, resolved: true)
send_message(issue, comment, link_props)
@@ -230,7 +230,7 @@ class JiraService < IssueTrackerService
project_name = data[:project][:name]
message = "[#{user_name}|#{user_url}] mentioned this issue in [a #{entity_name} of #{project_name}|#{entity_url}]:\n'#{entity_title.chomp}'"
- link_title = "GitLab: Mentioned on #{entity_name} - #{entity_title}"
+ link_title = "#{entity_name.capitalize} - #{entity_title}"
link_props = build_remote_link_props(url: entity_url, title: link_title)
unless comment_exists?(issue, message)
@@ -278,6 +278,7 @@ class JiraService < IssueTrackerService
{
GlobalID: 'GitLab',
+ relationship: 'mentioned on',
object: {
url: url,
title: title,
diff --git a/app/models/project_services/slack_slash_commands_service.rb b/app/models/project_services/slack_slash_commands_service.rb
index 6c82e088231..6a454070fe2 100644
--- a/app/models/project_services/slack_slash_commands_service.rb
+++ b/app/models/project_services/slack_slash_commands_service.rb
@@ -22,6 +22,10 @@ class SlackSlashCommandsService < SlashCommandsService
end
end
+ def chat_responder
+ ::Gitlab::Chat::Responder::Slack
+ end
+
private
def format(text)
diff --git a/app/models/project_services/youtrack_service.rb b/app/models/project_services/youtrack_service.rb
new file mode 100644
index 00000000000..957be685aea
--- /dev/null
+++ b/app/models/project_services/youtrack_service.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+class YoutrackService < IssueTrackerService
+ validates :project_url, :issues_url, presence: true, public_url: true, if: :activated?
+
+ prop_accessor :description, :project_url, :issues_url
+
+ # {PROJECT-KEY}-{NUMBER} Examples: YT-1, PRJ-1
+ def self.reference_pattern(only_long: false)
+ if only_long
+ /(?<issue>\b[A-Z][A-Za-z0-9_]*-\d+)/
+ else
+ /(?<issue>\b[A-Z][A-Za-z0-9_]*-\d+)|(#{Issue.reference_prefix}(?<issue>\d+))/
+ end
+ end
+
+ def title
+ 'YouTrack'
+ end
+
+ def description
+ if self.properties && self.properties['description'].present?
+ self.properties['description']
+ else
+ 'YouTrack issue tracker'
+ end
+ end
+
+ def self.to_param
+ 'youtrack'
+ end
+
+ def fields
+ [
+ { type: 'text', name: 'description', placeholder: description },
+ { type: 'text', name: 'project_url', placeholder: 'Project url', required: true },
+ { type: 'text', name: 'issues_url', placeholder: 'Issue url', required: true }
+ ]
+ end
+end
diff --git a/app/models/releases/link.rb b/app/models/releases/link.rb
index 6f639e5a7b2..6c507c47752 100644
--- a/app/models/releases/link.rb
+++ b/app/models/releases/link.rb
@@ -6,7 +6,7 @@ module Releases
belongs_to :release
- validates :url, presence: true, url: true, uniqueness: { scope: :release }
+ validates :url, presence: true, url: { protocols: %w(http https ftp) }, uniqueness: { scope: :release }
validates :name, presence: true, uniqueness: { scope: :release }
scope :sorted, -> { order(created_at: :desc) }
diff --git a/app/models/repository.rb b/app/models/repository.rb
index ed55a6e572b..cd761a29618 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -854,6 +854,12 @@ class Repository
end
end
+ def merge_to_ref(user, source_sha, merge_request, target_ref, message)
+ branch = merge_request.target_branch
+
+ raw.merge_to_ref(user, source_sha, branch, target_ref, message)
+ end
+
def ff_merge(user, source, target_branch, merge_request: nil)
their_commit_id = commit(source)&.id
raise 'Invalid merge source' if their_commit_id.nil?
diff --git a/app/models/service.rb b/app/models/service.rb
index 3461e0bfe70..da523bfa426 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -266,6 +266,7 @@ class Service < ActiveRecord::Base
prometheus
pushover
redmine
+ youtrack
slack_slash_commands
slack
teamcity
diff --git a/app/models/suggestion.rb b/app/models/suggestion.rb
index 7eee4fbbe5f..09034646bff 100644
--- a/app/models/suggestion.rb
+++ b/app/models/suggestion.rb
@@ -19,11 +19,6 @@ class Suggestion < ApplicationRecord
position.file_path
end
- def diff_file
- repository = project.repository
- position.diff_file(repository)
- end
-
# For now, suggestions only serve as a way to send patches that
# will change a single line (being able to apply multiple in the same place),
# which explains `from_line` and `to_line` being the same line.
diff --git a/app/models/user.rb b/app/models/user.rb
index 24101eda0b1..ee51c35d576 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -275,6 +275,7 @@ class User < ApplicationRecord
scope :confirmed, -> { where.not(confirmed_at: nil) }
scope :by_username, -> (usernames) { iwhere(username: Array(usernames).map(&:to_s)) }
scope :for_todos, -> (todos) { where(id: todos.select(:user_id)) }
+ scope :with_emails, -> { preload(:emails) }
# Limits the users to those that have TODOs, optionally in the given state.
#
@@ -387,7 +388,7 @@ class User < ApplicationRecord
find_by(id: user_id)
end
- def filter(filter_name)
+ def filter_items(filter_name)
case filter_name
when 'admins'
admins
@@ -1167,6 +1168,10 @@ class User < ApplicationRecord
Gitlab::ObjectHierarchy.new(owned_or_maintainers_groups).base_and_descendants
end
+ def manageable_groups_with_routes
+ manageable_groups.eager_load(:route).order('routes.path')
+ end
+
def namespaces
namespace_ids = groups.pluck(:id)
namespace_ids.push(namespace.id)
diff --git a/app/policies/board_policy.rb b/app/policies/board_policy.rb
index 46db008421f..4bf1e7bd3e1 100644
--- a/app/policies/board_policy.rb
+++ b/app/policies/board_policy.rb
@@ -4,10 +4,12 @@ class BoardPolicy < BasePolicy
delegate { @subject.parent }
condition(:is_group_board) { @subject.group_board? }
+ condition(:is_project_board) { @subject.project_board? }
- rule { is_group_board ? can?(:read_group) : can?(:read_project) }.enable :read_parent
+ rule { is_project_board & can?(:read_project) }.enable :read_parent
rule { is_group_board & can?(:read_group) }.policy do
+ enable :read_parent
enable :read_milestone
enable :read_issue
end
diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb
index c25766a5af8..298769c0eb8 100644
--- a/app/policies/group_policy.rb
+++ b/app/policies/group_policy.rb
@@ -54,6 +54,7 @@ class GroupPolicy < BasePolicy
rule { has_projects }.policy do
enable :read_group
+ enable :read_list
enable :read_label
end
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index cadbc5ae009..533782104ac 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -152,7 +152,6 @@ class ProjectPolicy < BasePolicy
enable :remove_fork_project
enable :destroy_merge_request
enable :destroy_issue
- enable :remove_pages
enable :set_issue_iid
enable :set_issue_created_at
@@ -271,6 +270,7 @@ class ProjectPolicy < BasePolicy
enable :admin_pages
enable :read_pages
enable :update_pages
+ enable :remove_pages
enable :read_cluster
enable :add_cluster
enable :create_cluster
@@ -278,6 +278,7 @@ class ProjectPolicy < BasePolicy
enable :admin_cluster
enable :create_environment_terminal
enable :destroy_release
+ enable :daily_statistics
end
rule { (mirror_available & can?(:admin_project)) | admin }.enable :admin_remote_mirror
diff --git a/app/presenters/ci/build_runner_presenter.rb b/app/presenters/ci/build_runner_presenter.rb
index 300f85e1e9d..29656b17183 100644
--- a/app/presenters/ci/build_runner_presenter.rb
+++ b/app/presenters/ci/build_runner_presenter.rb
@@ -2,6 +2,11 @@
module Ci
class BuildRunnerPresenter < SimpleDelegator
+ include Gitlab::Utils::StrongMemoize
+
+ RUNNER_REMOTE_TAG_PREFIX = 'refs/tags/'.freeze
+ RUNNER_REMOTE_BRANCH_PREFIX = 'refs/remotes/origin/'.freeze
+
def artifacts
return unless options[:artifacts]
@@ -11,6 +16,35 @@ module Ci
list.flatten.compact
end
+ def ref_type
+ if tag
+ 'tag'
+ else
+ 'branch'
+ end
+ end
+
+ def git_depth
+ strong_memoize(:git_depth) do
+ git_depth = variables&.find { |variable| variable[:key] == 'GIT_DEPTH' }&.dig(:value)
+ git_depth.to_i
+ end
+ end
+
+ def refspecs
+ specs = []
+
+ if git_depth > 0
+ specs << refspec_for_branch(ref) if branch? || merge_request_event?
+ specs << refspec_for_tag(ref) if tag?
+ else
+ specs << refspec_for_branch
+ specs << refspec_for_tag
+ end
+
+ specs
+ end
+
private
def create_archive(artifacts)
@@ -41,5 +75,13 @@ module Ci
}
end
end
+
+ def refspec_for_branch(ref = '*')
+ "+#{Gitlab::Git::BRANCH_REF_PREFIX}#{ref}:#{RUNNER_REMOTE_BRANCH_PREFIX}#{ref}"
+ end
+
+ def refspec_for_tag(ref = '*')
+ "+#{Gitlab::Git::TAG_REF_PREFIX}#{ref}:#{RUNNER_REMOTE_TAG_PREFIX}#{ref}"
+ end
end
end
diff --git a/app/presenters/project_presenter.rb b/app/presenters/project_presenter.rb
index 4cac90c2567..000b7c433a2 100644
--- a/app/presenters/project_presenter.rb
+++ b/app/presenters/project_presenter.rb
@@ -315,6 +315,10 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
project.tag_list.take(MAX_TOPICS_TO_SHOW) # rubocop: disable CodeReuse/ActiveRecord
end
+ def topics_not_shown
+ project.tag_list - topics_to_show
+ end
+
def count_of_extra_topics_not_shown
if project.tag_list.count > MAX_TOPICS_TO_SHOW
project.tag_list.count - MAX_TOPICS_TO_SHOW
diff --git a/app/serializers/acts_as_taggable_on/tag_entity.rb b/app/serializers/acts_as_taggable_on/tag_entity.rb
new file mode 100644
index 00000000000..d4e4b69f8fa
--- /dev/null
+++ b/app/serializers/acts_as_taggable_on/tag_entity.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+class ActsAsTaggableOn::TagEntity < Grape::Entity
+ expose :id
+ expose :name
+end
diff --git a/app/serializers/acts_as_taggable_on/tag_serializer.rb b/app/serializers/acts_as_taggable_on/tag_serializer.rb
new file mode 100644
index 00000000000..87f53606aa1
--- /dev/null
+++ b/app/serializers/acts_as_taggable_on/tag_serializer.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class ActsAsTaggableOn::TagSerializer < BaseSerializer
+ entity ActsAsTaggableOn::TagEntity
+end
diff --git a/app/serializers/diff_file_base_entity.rb b/app/serializers/diff_file_base_entity.rb
index 06a8db78476..d8630165e49 100644
--- a/app/serializers/diff_file_base_entity.rb
+++ b/app/serializers/diff_file_base_entity.rb
@@ -27,9 +27,13 @@ class DiffFileBaseEntity < Grape::Entity
next unless merge_request.source_project
- project_edit_blob_path(merge_request.source_project,
- tree_join(merge_request.source_branch, diff_file.new_path),
- options)
+ if Feature.enabled?(:web_ide_default)
+ ide_edit_path(merge_request.source_project, merge_request.source_branch, diff_file.new_path)
+ else
+ project_edit_blob_path(merge_request.source_project,
+ tree_join(merge_request.source_branch, diff_file.new_path),
+ options)
+ end
end
expose :old_path_html do |diff_file|
@@ -72,17 +76,20 @@ class DiffFileBaseEntity < Grape::Entity
expose :old_path
expose :new_path
expose :new_file?, as: :new_file
- expose :collapsed?, as: :collapsed
- expose :text?, as: :text
+ expose :renamed_file?, as: :renamed_file
+ expose :deleted_file?, as: :deleted_file
+
expose :diff_refs
+
expose :stored_externally?, as: :stored_externally
expose :external_storage
- expose :renamed_file?, as: :renamed_file
- expose :deleted_file?, as: :deleted_file
+
expose :mode_changed?, as: :mode_changed
expose :a_mode
expose :b_mode
+ expose :viewer, using: DiffViewerEntity
+
private
def memoized_submodule_links(diff_file)
diff --git a/app/serializers/diff_file_entity.rb b/app/serializers/diff_file_entity.rb
index b0aaec3326d..01ee7af37ed 100644
--- a/app/serializers/diff_file_entity.rb
+++ b/app/serializers/diff_file_entity.rb
@@ -4,12 +4,10 @@ class DiffFileEntity < DiffFileBaseEntity
include CommitsHelper
include IconsHelper
- expose :too_large?, as: :too_large
- expose :empty?, as: :empty
expose :added_lines
expose :removed_lines
- expose :load_collapsed_diff_url, if: -> (diff_file, options) { diff_file.text? && options[:merge_request] } do |diff_file|
+ expose :load_collapsed_diff_url, if: -> (diff_file, options) { diff_file.viewer.collapsed? && options[:merge_request] } do |diff_file|
merge_request = options[:merge_request]
project = merge_request.target_project
@@ -36,10 +34,6 @@ class DiffFileEntity < DiffFileBaseEntity
project_blob_path(project, tree_join(diff_file.content_sha, diff_file.new_path))
end
- expose :viewer, using: DiffViewerEntity do |diff_file|
- diff_file.rich_viewer || diff_file.simple_viewer
- end
-
expose :replaced_view_path, if: -> (_, options) { options[:merge_request] } do |diff_file|
image_diff = diff_file.rich_viewer && diff_file.rich_viewer.partial_name == 'image'
image_replaced = diff_file.old_content_sha && diff_file.old_content_sha != diff_file.content_sha
diff --git a/app/serializers/diff_viewer_entity.rb b/app/serializers/diff_viewer_entity.rb
index 587fa2347fd..45faca6cb2f 100644
--- a/app/serializers/diff_viewer_entity.rb
+++ b/app/serializers/diff_viewer_entity.rb
@@ -1,10 +1,8 @@
# frozen_string_literal: true
class DiffViewerEntity < Grape::Entity
- # Partial name refers directly to a Rails feature, let's avoid
- # using this on the frontend.
expose :partial_name, as: :name
- expose :error do |diff_viewer|
- diff_viewer.render_error_message
- end
+ expose :render_error, as: :error
+ expose :render_error_message, as: :error_message
+ expose :collapsed?, as: :collapsed
end
diff --git a/app/serializers/environment_entity.rb b/app/serializers/environment_entity.rb
index 4a7d13915dd..76248e6470e 100644
--- a/app/serializers/environment_entity.rb
+++ b/app/serializers/environment_entity.rb
@@ -8,6 +8,7 @@ class EnvironmentEntity < Grape::Entity
expose :state
expose :external_url
expose :environment_type
+ expose :name_without_type
expose :last_deployment, using: DeploymentEntity
expose :stop_action_available?, as: :has_stop_action
diff --git a/app/serializers/namespace_basic_entity.rb b/app/serializers/namespace_basic_entity.rb
new file mode 100644
index 00000000000..8bcbb2bca60
--- /dev/null
+++ b/app/serializers/namespace_basic_entity.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+class NamespaceBasicEntity < Grape::Entity
+ expose :id
+ expose :full_path
+end
diff --git a/app/serializers/namespace_serializer.rb b/app/serializers/namespace_serializer.rb
new file mode 100644
index 00000000000..bf3f154b558
--- /dev/null
+++ b/app/serializers/namespace_serializer.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class NamespaceSerializer < BaseSerializer
+ entity NamespaceBasicEntity
+end
diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb
index 29b1a6c244b..c2990cb5159 100644
--- a/app/serializers/pipeline_entity.rb
+++ b/app/serializers/pipeline_entity.rb
@@ -23,7 +23,7 @@ class PipelineEntity < Grape::Entity
expose :latest?, as: :latest
expose :stuck?, as: :stuck
expose :auto_devops_source?, as: :auto_devops
- expose :merge_request?, as: :merge_request
+ expose :merge_request_event?, as: :merge_request
expose :has_yaml_errors?, as: :yaml_errors
expose :can_retry?, as: :retryable
expose :can_cancel?, as: :cancelable
@@ -49,7 +49,7 @@ class PipelineEntity < Grape::Entity
expose :tag?, as: :tag
expose :branch?, as: :branch
- expose :merge_request?, as: :merge_request
+ expose :merge_request_event?, as: :merge_request
end
expose :commit, using: CommitEntity
diff --git a/app/serializers/project_import_entity.rb b/app/serializers/project_import_entity.rb
new file mode 100644
index 00000000000..9b51af685e7
--- /dev/null
+++ b/app/serializers/project_import_entity.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class ProjectImportEntity < ProjectEntity
+ include ImportHelper
+
+ expose :import_source
+ expose :import_status
+ expose :human_import_status_name
+
+ expose :provider_link do |project, options|
+ provider_project_link_url(options[:provider_url], project[:import_source])
+ end
+end
diff --git a/app/serializers/project_serializer.rb b/app/serializers/project_serializer.rb
index 23b96c2fc9e..52ac2fa0e09 100644
--- a/app/serializers/project_serializer.rb
+++ b/app/serializers/project_serializer.rb
@@ -1,5 +1,15 @@
# frozen_string_literal: true
class ProjectSerializer < BaseSerializer
- entity ProjectEntity
+ def represent(project, opts = {})
+ entity =
+ case opts[:serializer]
+ when :import
+ ProjectImportEntity
+ else
+ ProjectEntity
+ end
+
+ super(project, opts, entity)
+ end
end
diff --git a/app/serializers/provider_repo_entity.rb b/app/serializers/provider_repo_entity.rb
new file mode 100644
index 00000000000..d70aaa91324
--- /dev/null
+++ b/app/serializers/provider_repo_entity.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class ProviderRepoEntity < Grape::Entity
+ include ImportHelper
+
+ expose :id
+ expose :full_name
+ expose :owner_name do |provider_repo, options|
+ owner_name(provider_repo, options[:provider])
+ end
+
+ expose :sanitized_name do |provider_repo|
+ sanitize_project_name(provider_repo[:name])
+ end
+
+ expose :provider_link do |provider_repo, options|
+ provider_project_link_url(options[:provider_url], provider_repo[:full_name])
+ end
+
+ private
+
+ def owner_name(provider_repo, provider)
+ provider_repo.dig(:owner, :login) if provider == :github
+ end
+end
diff --git a/app/serializers/provider_repo_serializer.rb b/app/serializers/provider_repo_serializer.rb
new file mode 100644
index 00000000000..8a73f6fe6df
--- /dev/null
+++ b/app/serializers/provider_repo_serializer.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class ProviderRepoSerializer < BaseSerializer
+ entity ProviderRepoEntity
+end
diff --git a/app/serializers/tree_entity.rb b/app/serializers/tree_entity.rb
deleted file mode 100644
index 9b7dc80e1d9..00000000000
--- a/app/serializers/tree_entity.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-class TreeEntity < Grape::Entity
- include RequestAwareEntity
-
- expose :id, :path, :name, :mode
-
- expose :icon do |tree|
- IconsHelper.file_type_icon_class('folder', tree.mode, tree.name)
- end
-
- expose :url do |tree|
- project_tree_path(request.project, File.join(request.ref, tree.path))
- end
-end
diff --git a/app/serializers/tree_root_entity.rb b/app/serializers/tree_root_entity.rb
deleted file mode 100644
index f1cfcd943d8..00000000000
--- a/app/serializers/tree_root_entity.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-# frozen_string_literal: true
-
-# TODO: Inherit from TreeEntity, when `Tree` implements `id` and `name` like `Gitlab::Git::Tree`.
-class TreeRootEntity < Grape::Entity
- include RequestAwareEntity
-
- expose :path
-
- expose :trees, using: TreeEntity
- expose :blobs, using: BlobEntity
- expose :submodules, using: SubmoduleEntity
-
- expose :parent_tree_url do |tree|
- path = tree.path.sub(%r{\A/}, '')
- next unless path.present?
-
- path_segments = path.split('/')
- path_segments.pop
- parent_tree_path = path_segments.join('/')
-
- project_tree_path(request.project, File.join(request.ref, parent_tree_path))
- end
-
- expose :last_commit_path do |tree|
- logs_file_project_ref_path(request.project, request.ref, tree.path)
- end
-end
diff --git a/app/serializers/tree_serializer.rb b/app/serializers/tree_serializer.rb
deleted file mode 100644
index 536b8ab1ae2..00000000000
--- a/app/serializers/tree_serializer.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-# frozen_string_literal: true
-
-class TreeSerializer < BaseSerializer
- entity TreeRootEntity
-end
diff --git a/app/services/applications/create_service.rb b/app/services/applications/create_service.rb
index b6c30da4d3a..dff0d9696f8 100644
--- a/app/services/applications/create_service.rb
+++ b/app/services/applications/create_service.rb
@@ -2,16 +2,16 @@
module Applications
class CreateService
- # rubocop: disable CodeReuse/ActiveRecord
+ attr_reader :current_user, :params
+
def initialize(current_user, params)
@current_user = current_user
- @params = params.except(:ip_address)
+ @params = params.except(:ip_address) # rubocop: disable CodeReuse/ActiveRecord
end
- # rubocop: enable CodeReuse/ActiveRecord
# EE would override and use `request` arg
def execute(request)
- Doorkeeper::Application.create(@params)
+ Doorkeeper::Application.create(params)
end
end
end
diff --git a/app/services/boards/visits/latest_service.rb b/app/services/boards/visits/latest_service.rb
index 9e4c77a6317..d8de08c5844 100644
--- a/app/services/boards/visits/latest_service.rb
+++ b/app/services/boards/visits/latest_service.rb
@@ -6,11 +6,13 @@ module Boards
def execute
return nil unless current_user
- if parent.is_a?(Group)
- BoardGroupRecentVisit.latest(current_user, parent)
- else
- BoardProjectRecentVisit.latest(current_user, parent)
- end
+ recent_visit_model.latest(current_user, parent, count: params[:count])
+ end
+
+ private
+
+ def recent_visit_model
+ parent.is_a?(Group) ? BoardGroupRecentVisit : BoardProjectRecentVisit
end
end
end
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index 354e53a367c..8973c5ffc9e 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -25,7 +25,9 @@ module Ci
origin_ref: params[:ref],
checkout_sha: params[:checkout_sha],
after_sha: params[:after],
- before_sha: params[:before],
+ before_sha: params[:before], # The base SHA of the source branch (i.e merge_request.diff_base_sha).
+ source_sha: params[:source_sha], # The HEAD SHA of the source branch (i.e merge_request.diff_head_sha).
+ target_sha: params[:target_sha], # The HEAD SHA of the target branch.
trigger_request: trigger_request,
schedule: schedule,
merge_request: merge_request,
@@ -36,6 +38,7 @@ module Ci
project: project,
current_user: current_user,
push_options: params[:push_options],
+ chat_data: params[:chat_data],
**extra_options(options))
sequence = Gitlab::Ci::Pipeline::Chain::Sequence
@@ -111,10 +114,10 @@ module Ci
def extra_options(options = {})
# In Ruby 2.4, even when options is empty, f(**options) doesn't work when f
# doesn't have any parameters. We reproduce the Ruby 2.5 behavior by
- # checking explicitely that no arguments are given.
+ # checking explicitly that no arguments are given.
raise ArgumentError if options.any?
- {} # overriden in EE
+ {} # overridden in EE
end
end
end
diff --git a/app/services/ci/pipeline_trigger_service.rb b/app/services/ci/pipeline_trigger_service.rb
index 4ba3f5fb8ba..2dbb7c3917d 100644
--- a/app/services/ci/pipeline_trigger_service.rb
+++ b/app/services/ci/pipeline_trigger_service.rb
@@ -38,11 +38,11 @@ module Ci
end
def create_pipeline_from_job(job)
- # overriden in EE
+ # overridden in EE
end
def job_from_token
- # overriden in EE
+ # overridden in EE
end
def variables
diff --git a/app/services/clusters/applications/create_service.rb b/app/services/clusters/applications/create_service.rb
index 92c2c1b9834..12f8c849d41 100644
--- a/app/services/clusters/applications/create_service.rb
+++ b/app/services/clusters/applications/create_service.rb
@@ -27,9 +27,11 @@ module Clusters
application.oauth_application = create_oauth_application(application, request)
end
- application.save!
+ worker = application.updateable? ? ClusterUpgradeAppWorker : ClusterInstallAppWorker
- Clusters::Applications::ScheduleInstallationService.new(application).execute
+ application.make_scheduled!
+
+ worker.perform_async(application.name, application.id)
end
end
diff --git a/app/services/clusters/applications/schedule_installation_service.rb b/app/services/clusters/applications/schedule_installation_service.rb
deleted file mode 100644
index 15c93f1e79b..00000000000
--- a/app/services/clusters/applications/schedule_installation_service.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# frozen_string_literal: true
-
-module Clusters
- module Applications
- class ScheduleInstallationService
- attr_reader :application
-
- def initialize(application)
- @application = application
- end
-
- def execute
- application.updateable? ? schedule_upgrade : schedule_install
- end
-
- private
-
- def schedule_upgrade
- application.make_scheduled!
-
- ClusterUpgradeAppWorker.perform_async(application.name, application.id)
- end
-
- def schedule_install
- application.make_scheduled!
-
- ClusterInstallAppWorker.perform_async(application.name, application.id)
- end
- end
- end
-end
diff --git a/app/services/commits/create_service.rb b/app/services/commits/create_service.rb
index 34593e12bd5..a3b87c20761 100644
--- a/app/services/commits/create_service.rb
+++ b/app/services/commits/create_service.rb
@@ -45,7 +45,7 @@ module Commits
def validate!
validate_permissions!
validate_on_branch!
- validate_branch_existance!
+ validate_branch_existence!
validate_new_branch_name! if different_branch?
end
@@ -64,7 +64,7 @@ module Commits
end
end
- def validate_branch_existance!
+ def validate_branch_existence!
if !project.empty_repo? && different_branch? && repository.branch_exists?(@branch_name)
raise_error("A branch called '#{@branch_name}' already exists. Switch to that branch in order to make changes")
end
diff --git a/app/services/concerns/exclusive_lease_guard.rb b/app/services/concerns/exclusive_lease_guard.rb
index 28879d2d67f..2cb73555d85 100644
--- a/app/services/concerns/exclusive_lease_guard.rb
+++ b/app/services/concerns/exclusive_lease_guard.rb
@@ -42,7 +42,7 @@ module ExclusiveLeaseGuard
def lease_timeout
raise NotImplementedError,
- "#{self.class.name} does not implement #{__method__}"
+ "#{self.class.name} does not implement #{__method__}"
end
def lease_release?
diff --git a/app/services/concerns/users/participable_service.rb b/app/services/concerns/users/participable_service.rb
index 5b408bd96c7..6713b6617ae 100644
--- a/app/services/concerns/users/participable_service.rb
+++ b/app/services/concerns/users/participable_service.rb
@@ -11,7 +11,7 @@ module Users
def noteable_owner
return [] unless noteable && noteable.author.present?
- [as_hash(noteable.author)]
+ [user_as_hash(noteable.author)]
end
def participants_in_noteable
@@ -23,21 +23,24 @@ module Users
def sorted(users)
users.uniq.to_a.compact.sort_by(&:username).map do |user|
- as_hash(user)
+ user_as_hash(user)
end
end
def groups
current_user.authorized_groups.sort_by(&:path).map do |group|
- count = group.users.count
- { username: group.full_path, name: group.full_name, count: count, avatar_url: group.avatar_url }
+ group_as_hash(group)
end
end
private
- def as_hash(user)
- { username: user.username, name: user.name, avatar_url: user.avatar_url }
+ def user_as_hash(user)
+ { type: user.class.name, username: user.username, name: user.name, avatar_url: user.avatar_url }
+ end
+
+ def group_as_hash(group)
+ { type: group.class.name, username: group.full_path, name: group.full_name, avatar_url: group.avatar_url, count: group.users.count }
end
end
end
diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb
index 65208b07e27..110e589e30d 100644
--- a/app/services/create_branch_service.rb
+++ b/app/services/create_branch_service.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
class CreateBranchService < BaseService
- def execute(branch_name, ref)
- create_master_branch if project.empty_repo?
+ def execute(branch_name, ref, create_master_if_empty: true)
+ create_master_branch if create_master_if_empty && project.empty_repo?
result = ValidateNewBranchService.new(project, current_user)
.execute(branch_name)
diff --git a/app/services/emails/base_service.rb b/app/services/emails/base_service.rb
index 988215ffc78..99324638300 100644
--- a/app/services/emails/base_service.rb
+++ b/app/services/emails/base_service.rb
@@ -2,10 +2,11 @@
module Emails
class BaseService
- attr_reader :current_user
+ attr_reader :current_user, :params, :user
def initialize(current_user, params = {})
- @current_user, @params = current_user, params.dup
+ @current_user = current_user
+ @params = params.dup
@user = params.delete(:user)
end
end
diff --git a/app/services/emails/create_service.rb b/app/services/emails/create_service.rb
index 56925a724fe..dc06a5caa40 100644
--- a/app/services/emails/create_service.rb
+++ b/app/services/emails/create_service.rb
@@ -3,12 +3,11 @@
module Emails
class CreateService < ::Emails::BaseService
def execute(extra_params = {})
- skip_confirmation = @params.delete(:skip_confirmation)
+ skip_confirmation = params.delete(:skip_confirmation)
- email = @user.emails.create(@params.merge(extra_params))
-
- email&.confirm if skip_confirmation && current_user.admin?
- email
+ user.emails.create(params.merge(extra_params)).tap do |email|
+ email&.confirm if skip_confirmation && current_user.admin?
+ end
end
end
end
diff --git a/app/services/error_tracking/list_issues_service.rb b/app/services/error_tracking/list_issues_service.rb
index 4cc35cfa4a8..a6c6bec9598 100644
--- a/app/services/error_tracking/list_issues_service.rb
+++ b/app/services/error_tracking/list_issues_service.rb
@@ -6,15 +6,19 @@ module ErrorTracking
DEFAULT_LIMIT = 20
def execute
- return error('not enabled') unless enabled?
- return error('access denied') unless can_read?
+ return error('Error Tracking is not enabled') unless enabled?
+ return error('Access denied', :unauthorized) unless can_read?
result = project_error_tracking_setting
.list_sentry_issues(issue_status: issue_status, limit: limit)
# our results are not yet ready
unless result
- return error('not ready', :no_content)
+ return error('Not ready. Try again later', :no_content)
+ end
+
+ if result[:error].present?
+ return error(result[:error], :bad_request)
end
success(issues: result[:issues])
diff --git a/app/services/error_tracking/list_projects_service.rb b/app/services/error_tracking/list_projects_service.rb
index c6e8be0f2be..4e92353a13c 100644
--- a/app/services/error_tracking/list_projects_service.rb
+++ b/app/services/error_tracking/list_projects_service.rb
@@ -28,8 +28,8 @@ module ErrorTracking
(project.error_tracking_setting || project.build_error_tracking_setting).tap do |setting|
setting.api_url = ErrorTracking::ProjectErrorTrackingSetting.build_api_url_from(
api_host: params[:api_host],
- organization_slug: nil,
- project_slug: nil
+ organization_slug: 'org',
+ project_slug: 'proj'
)
setting.token = params[:token]
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index 9ecee7c6156..f387c749a21 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -140,7 +140,7 @@ class GitPushService < BaseService
.perform_async(project.id, current_user.id, params[:oldrev], params[:newrev], params[:ref])
EventCreateService.new.push(project, current_user, build_push_data)
- Ci::CreatePipelineService.new(project, current_user, build_push_data).execute(:push)
+ Ci::CreatePipelineService.new(project, current_user, build_push_data).execute(:push, pipeline_options)
project.execute_hooks(build_push_data.dup, :push_hooks)
project.execute_services(build_push_data.dup, :push_hooks)
@@ -231,4 +231,10 @@ class GitPushService < BaseService
def last_pushed_commits
@last_pushed_commits ||= @push_commits.last(PROCESS_COMMIT_LIMIT)
end
+
+ private
+
+ def pipeline_options
+ {} # to be overridden in EE
+ end
end
diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb
index 03fcf614c64..e39b3603c6c 100644
--- a/app/services/git_tag_push_service.rb
+++ b/app/services/git_tag_push_service.rb
@@ -10,7 +10,7 @@ class GitTagPushService < BaseService
@push_data = build_push_data
EventCreateService.new.push(project, current_user, push_data)
- Ci::CreatePipelineService.new(project, current_user, push_data).execute(:push)
+ Ci::CreatePipelineService.new(project, current_user, push_data).execute(:push, pipeline_options)
SystemHooksService.new.execute_hooks(build_system_push_data, :tag_push_hooks)
project.execute_hooks(push_data.dup, :tag_push_hooks)
@@ -59,4 +59,8 @@ class GitTagPushService < BaseService
[],
'')
end
+
+ def pipeline_options
+ {} # to be overridden in EE
+ end
end
diff --git a/app/services/groups/create_service.rb b/app/services/groups/create_service.rb
index 55a3b9fa7b1..99ead467f74 100644
--- a/app/services/groups/create_service.rb
+++ b/app/services/groups/create_service.rb
@@ -33,7 +33,7 @@ module Groups
private
def after_build_hook(group, params)
- # overriden in EE
+ # overridden in EE
end
def create_chat_team?
diff --git a/app/services/groups/update_service.rb b/app/services/groups/update_service.rb
index 9ff1da270e2..787445180f0 100644
--- a/app/services/groups/update_service.rb
+++ b/app/services/groups/update_service.rb
@@ -31,7 +31,7 @@ module Groups
private
def before_assignment_hook(group, params)
- # overriden in EE
+ # overridden in EE
end
def after_update
diff --git a/app/services/issues/build_service.rb b/app/services/issues/build_service.rb
index 52b45f1b2ce..3fb2c2b3007 100644
--- a/app/services/issues/build_service.rb
+++ b/app/services/issues/build_service.rb
@@ -57,9 +57,11 @@ module Issues
end
def issue_params
- @issue_params ||= issue_params_with_info_from_discussions.merge(whitelisted_issue_params)
+ @issue_params ||= build_issue_params
end
+ private
+
def whitelisted_issue_params
if can?(current_user, :admin_issue, project)
params.slice(:title, :description, :milestone_id)
@@ -67,5 +69,9 @@ module Issues
params.slice(:title, :description)
end
end
+
+ def build_issue_params
+ issue_params_with_info_from_discussions.merge(whitelisted_issue_params)
+ end
end
end
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index ac51fee0b3f..11ede5223e5 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -67,7 +67,7 @@ module MergeRequests
Ci::CreatePipelineService
.new(merge_request.source_project, user, ref: merge_request.source_branch)
- .execute(:merge_request,
+ .execute(:merge_request_event,
ignore_skip_ci: true,
save_on_errors: false,
merge_request: merge_request)
diff --git a/app/services/merge_requests/merge_base_service.rb b/app/services/merge_requests/merge_base_service.rb
new file mode 100644
index 00000000000..095bdca5472
--- /dev/null
+++ b/app/services/merge_requests/merge_base_service.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+module MergeRequests
+ class MergeBaseService < MergeRequests::BaseService
+ include Gitlab::Utils::StrongMemoize
+
+ MergeError = Class.new(StandardError)
+
+ attr_reader :merge_request
+
+ # Overridden in EE.
+ def hooks_validation_pass?(_merge_request)
+ true
+ end
+
+ # Overridden in EE.
+ def hooks_validation_error(_merge_request)
+ # No-op
+ end
+
+ def source
+ if merge_request.squash
+ squash_sha!
+ else
+ merge_request.diff_head_sha
+ end
+ end
+
+ private
+
+ # Overridden in EE.
+ def error_check!
+ # No-op
+ end
+
+ def raise_error(message)
+ raise MergeError, message
+ end
+
+ def handle_merge_error(*args)
+ # No-op
+ end
+
+ def commit_message
+ params[:commit_message] ||
+ merge_request.default_merge_commit_message
+ end
+
+ def squash_sha!
+ strong_memoize(:squash_sha) do
+ params[:merge_request] = merge_request
+ squash_result = ::MergeRequests::SquashService.new(project, current_user, params).execute
+
+ case squash_result[:status]
+ when :success
+ squash_result[:squash_sha]
+ when :error
+ raise ::MergeRequests::MergeService::MergeError, squash_result[:message]
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index 449997bcf07..8241e408ce5 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -7,13 +7,7 @@ module MergeRequests
# mark merge request as merged and execute all hooks and notifications
# Executed when you do merge via GitLab UI
#
- class MergeService < MergeRequests::BaseService
- include Gitlab::Utils::StrongMemoize
-
- MergeError = Class.new(StandardError)
-
- attr_reader :merge_request, :source
-
+ class MergeService < MergeRequests::MergeBaseService
delegate :merge_jid, :state, to: :@merge_request
def execute(merge_request)
@@ -24,7 +18,7 @@ module MergeRequests
@merge_request = merge_request
- error_check!
+ validate!
merge_request.in_locked_state do
if commit
@@ -38,22 +32,22 @@ module MergeRequests
handle_merge_error(log_message: e.message, save_message_on_model: true)
end
- def source
- if merge_request.squash
- squash_sha!
- else
- merge_request.diff_head_sha
- end
- end
+ private
- # Overridden in EE.
- def hooks_validation_pass?(_merge_request)
- true
+ def validate!
+ authorization_check!
+ error_check!
end
- private
+ def authorization_check!
+ unless @merge_request.can_be_merged_by?(current_user)
+ raise_error('You are not allowed to merge this merge request')
+ end
+ end
def error_check!
+ super
+
error =
if @merge_request.should_be_rebased?
'Only fast-forward merge is allowed for your project. Please update your source branch'
@@ -63,7 +57,7 @@ module MergeRequests
'No source for merge'
end
- raise MergeError, error if error
+ raise_error(error) if error
end
def commit
@@ -73,36 +67,20 @@ module MergeRequests
if commit_id
log_info("Git merge finished on JID #{merge_jid} commit #{commit_id}")
else
- raise MergeError, 'Conflicts detected during merge'
+ raise_error('Conflicts detected during merge')
end
merge_request.update!(merge_commit_sha: commit_id)
end
- def squash_sha!
- strong_memoize(:squash_sha) do
- params[:merge_request] = merge_request
- squash_result = ::MergeRequests::SquashService.new(project, current_user, params).execute
-
- case squash_result[:status]
- when :success
- squash_result[:squash_sha]
- when :error
- raise ::MergeRequests::MergeService::MergeError, squash_result[:message]
- end
- end
- end
-
def try_merge
- message = params[:commit_message] || merge_request.default_merge_commit_message
-
- repository.merge(current_user, source, merge_request, message)
+ repository.merge(current_user, source, merge_request, commit_message)
rescue Gitlab::Git::PreReceiveError => e
handle_merge_error(log_message: e.message)
- raise MergeError, 'Something went wrong during merge pre-receive hook'
+ raise_error('Something went wrong during merge pre-receive hook')
rescue => e
handle_merge_error(log_message: e.message)
- raise MergeError, 'Something went wrong during merge'
+ raise_error('Something went wrong during merge')
ensure
merge_request.update!(in_progress_merge_commit_sha: nil)
end
diff --git a/app/services/merge_requests/merge_to_ref_service.rb b/app/services/merge_requests/merge_to_ref_service.rb
new file mode 100644
index 00000000000..586652ae44e
--- /dev/null
+++ b/app/services/merge_requests/merge_to_ref_service.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+module MergeRequests
+ # Performs the merge between source SHA and the target branch. Instead
+ # of writing the result to the MR target branch, it targets the `target_ref`.
+ #
+ # Ideally this should leave the `target_ref` state with the same state the
+ # target branch would have if we used the regular `MergeService`, but without
+ # every side-effect that comes with it (MR updates, mails, source branch
+ # deletion, etc). This service should be kept idempotent (i.e. can
+ # be executed regardless of the `target_ref` current state).
+ #
+ class MergeToRefService < MergeRequests::MergeBaseService
+ def execute(merge_request)
+ @merge_request = merge_request
+
+ validate!
+
+ commit_id = commit
+
+ raise_error('Conflicts detected during merge') unless commit_id
+
+ success(commit_id: commit_id)
+ rescue MergeError => error
+ error(error.message)
+ end
+
+ private
+
+ def validate!
+ authorization_check!
+ error_check!
+ end
+
+ def error_check!
+ super
+
+ error =
+ if Feature.disabled?(:merge_to_tmp_merge_ref_path, project)
+ 'Feature is not enabled'
+ elsif !merge_method_supported?
+ "#{project.human_merge_method} to #{target_ref} is currently not supported."
+ elsif !hooks_validation_pass?(merge_request)
+ hooks_validation_error(merge_request)
+ elsif @merge_request.should_be_rebased?
+ 'Fast-forward merge is not possible. Please update your source branch.'
+ elsif !@merge_request.mergeable_to_ref?
+ "Merge request is not mergeable to #{target_ref}"
+ elsif !source
+ 'No source for merge'
+ end
+
+ raise_error(error) if error
+ end
+
+ def authorization_check!
+ unless Ability.allowed?(current_user, :admin_merge_request, project)
+ raise_error("You are not allowed to merge to this ref")
+ end
+ end
+
+ def target_ref
+ merge_request.merge_ref_path
+ end
+
+ def commit
+ repository.merge_to_ref(current_user, source, merge_request, target_ref, commit_message)
+ rescue Gitlab::Git::PreReceiveError => error
+ raise MergeError, error.message
+ end
+
+ def merge_method_supported?
+ [:merge, :rebase_merge].include?(project.merge_method)
+ end
+ end
+end
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index b975c3a8cb6..5a6e7338b42 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -35,7 +35,7 @@ module Notes
if !only_commands && note.save
if note.part_of_discussion? && note.discussion.can_convert_to_discussion?
- note.discussion.convert_to_discussion!.save(touch: false)
+ note.discussion.convert_to_discussion!(save: true)
end
todo_service.new_note(note, current_user)
diff --git a/app/services/notes/quick_actions_service.rb b/app/services/notes/quick_actions_service.rb
index 7ee9732040d..985a03060bd 100644
--- a/app/services/notes/quick_actions_service.rb
+++ b/app/services/notes/quick_actions_service.rb
@@ -7,9 +7,14 @@ module Notes
'MergeRequest' => MergeRequests::UpdateService,
'Commit' => Commits::TagService
}.freeze
+ private_constant :UPDATE_SERVICES
+
+ def self.update_services
+ UPDATE_SERVICES
+ end
def self.noteable_update_service(note)
- UPDATE_SERVICES[note.noteable_type]
+ update_services[note.noteable_type]
end
def self.supported?(note)
diff --git a/app/services/notification_recipient_service.rb b/app/services/notification_recipient_service.rb
index 68cdc69023a..56f11b31110 100644
--- a/app/services/notification_recipient_service.rb
+++ b/app/services/notification_recipient_service.rb
@@ -249,6 +249,7 @@ module NotificationRecipientService
attr_reader :action
attr_reader :previous_assignee
attr_reader :skip_current_user
+
def initialize(target, current_user, action:, custom_action: nil, previous_assignee: nil, skip_current_user: true)
@target = target
@current_user = current_user
@@ -258,9 +259,13 @@ module NotificationRecipientService
@skip_current_user = skip_current_user
end
+ def add_watchers
+ add_project_watchers
+ end
+
def build!
add_participants(current_user)
- add_project_watchers
+ add_watchers
add_custom_notifications
# Re-assign is considered as a mention of the new assignee
diff --git a/app/services/projects/fetch_statistics_increment_service.rb b/app/services/projects/fetch_statistics_increment_service.rb
new file mode 100644
index 00000000000..8644e6bf313
--- /dev/null
+++ b/app/services/projects/fetch_statistics_increment_service.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Projects
+ class FetchStatisticsIncrementService
+ attr_reader :project
+
+ def initialize(project)
+ @project = project
+ end
+
+ def execute
+ increment_fetch_count_sql = <<~SQL
+ INSERT INTO #{table_name} (project_id, date, fetch_count)
+ VALUES (#{project.id}, '#{Date.today}', 1)
+ SQL
+
+ increment_fetch_count_sql += if Gitlab::Database.postgresql?
+ "ON CONFLICT (project_id, date) DO UPDATE SET fetch_count = #{table_name}.fetch_count + 1"
+ else
+ "ON DUPLICATE KEY UPDATE fetch_count = #{table_name}.fetch_count + 1"
+ end
+
+ ActiveRecord::Base.connection.execute(increment_fetch_count_sql)
+ end
+
+ private
+
+ def table_name
+ ProjectDailyStatistic.table_name
+ end
+ end
+end
diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb
index 91091c4393d..fc234bafc57 100644
--- a/app/services/projects/fork_service.rb
+++ b/app/services/projects/fork_service.rb
@@ -38,8 +38,8 @@ module Projects
new_params = {
visibility_level: allowed_visibility_level,
description: @project.description,
- name: @project.name,
- path: @project.path,
+ name: target_name,
+ path: target_path,
shared_runners_enabled: @project.shared_runners_enabled,
namespace_id: target_namespace.id,
fork_network: fork_network,
@@ -94,6 +94,14 @@ module Projects
Projects::ForksCountService.new(@project).refresh_cache
end
+ def target_path
+ @target_path ||= @params[:path] || @project.path
+ end
+
+ def target_name
+ @target_name ||= @params[:name] || @project.name
+ end
+
def target_namespace
@target_namespace ||= @params[:namespace] || current_user.namespace
end
diff --git a/app/services/projects/hashed_storage/base_attachment_service.rb b/app/services/projects/hashed_storage/base_attachment_service.rb
new file mode 100644
index 00000000000..828ab616bab
--- /dev/null
+++ b/app/services/projects/hashed_storage/base_attachment_service.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+module Projects
+ module HashedStorage
+ AttachmentMigrationError = Class.new(StandardError)
+
+ AttachmentCannotMoveError = Class.new(StandardError)
+
+ class BaseAttachmentService < BaseService
+ # Returns the disk_path value before the execution
+ attr_reader :old_disk_path
+
+ # Returns the disk_path value after the execution
+ attr_reader :new_disk_path
+
+ # Returns the logger currently in use
+ attr_reader :logger
+
+ # Return whether this operation was skipped or not
+ #
+ # @return [Boolean] true if skipped of false otherwise
+ def skipped?
+ @skipped
+ end
+
+ protected
+
+ def move_folder!(old_path, new_path)
+ unless File.directory?(old_path)
+ logger.info("Skipped attachments move from '#{old_path}' to '#{new_path}', source path doesn't exist or is not a directory (PROJECT_ID=#{project.id})")
+ @skipped = true
+
+ return true
+ end
+
+ if File.exist?(new_path)
+ logger.error("Cannot move attachments from '#{old_path}' to '#{new_path}', target path already exist (PROJECT_ID=#{project.id})")
+ raise AttachmentCannotMoveError, "Target path '#{new_path}' already exists"
+ end
+
+ # Create base path folder on the new storage layout
+ FileUtils.mkdir_p(File.dirname(new_path))
+
+ FileUtils.mv(old_path, new_path)
+ logger.info("Project attachments moved from '#{old_path}' to '#{new_path}' (PROJECT_ID=#{project.id})")
+
+ true
+ end
+ end
+ end
+end
diff --git a/app/services/projects/hashed_storage/base_repository_service.rb b/app/services/projects/hashed_storage/base_repository_service.rb
index 761c81d776f..f97a28b8c3b 100644
--- a/app/services/projects/hashed_storage/base_repository_service.rb
+++ b/app/services/projects/hashed_storage/base_repository_service.rb
@@ -2,11 +2,8 @@
module Projects
module HashedStorage
- # Returned when there is an error with the Hashed Storage migration
- RepositoryMigrationError = Class.new(StandardError)
-
- # Returned when there is an error with the Hashed Storage rollback
- RepositoryRollbackError = Class.new(StandardError)
+ # Returned when repository can't be made read-only because there is already a git transfer in progress
+ RepositoryInUseError = Class.new(StandardError)
class BaseRepositoryService < BaseService
include Gitlab::ShellAdapter
@@ -38,7 +35,10 @@ module Projects
# project was not originally empty.
if !from_exists && !to_exists
logger.warn "Can't find a repository on either source or target paths for #{project.full_path} (ID=#{project.id}) ..."
- return false
+
+ # We return true so we still reflect the change in the database.
+ # Next time the repository is (re)created it will be under the new storage layout
+ return true
elsif !from_exists
# Repository have been moved already.
return true
@@ -52,6 +52,16 @@ module Projects
move_repository(new_disk_path, old_disk_path)
move_repository("#{new_disk_path}.wiki", old_wiki_disk_path)
end
+
+ def try_to_set_repository_read_only!
+ # Mitigate any push operation to start during migration
+ unless project.set_repository_read_only!
+ migration_error = "Target repository '#{old_disk_path}' cannot be made read-only as there is a git transfer in progress"
+ logger.error migration_error
+
+ raise RepositoryInUseError, migration_error
+ end
+ end
end
end
end
diff --git a/app/services/projects/hashed_storage/migrate_attachments_service.rb b/app/services/projects/hashed_storage/migrate_attachments_service.rb
index 03e0685d2cd..9eaeb6eb4e7 100644
--- a/app/services/projects/hashed_storage/migrate_attachments_service.rb
+++ b/app/services/projects/hashed_storage/migrate_attachments_service.rb
@@ -2,62 +2,37 @@
module Projects
module HashedStorage
- AttachmentMigrationError = Class.new(StandardError)
-
- class MigrateAttachmentsService < BaseService
- attr_reader :logger, :old_disk_path, :new_disk_path
-
+ class MigrateAttachmentsService < BaseAttachmentService
def initialize(project, old_disk_path, logger: nil)
@project = project
@logger = logger || Rails.logger
@old_disk_path = old_disk_path
- @new_disk_path = project.disk_path
@skipped = false
end
def execute
origin = FileUploader.absolute_base_dir(project)
- # It's possible that old_disk_path does not match project.disk_path. For example, that happens when we rename a project
+ # It's possible that old_disk_path does not match project.disk_path.
+ # For example, that happens when we rename a project
origin.sub!(/#{Regexp.escape(project.full_path)}\z/, old_disk_path)
project.storage_version = ::Project::HASHED_STORAGE_FEATURES[:attachments]
target = FileUploader.absolute_base_dir(project)
- result = move_folder!(origin, target)
- project.save!
-
- if result && block_given?
- yield
- end
-
- result
- end
-
- def skipped?
- @skipped
- end
+ @new_disk_path = project.disk_path
- private
+ result = move_folder!(origin, target)
- def move_folder!(old_path, new_path)
- unless File.directory?(old_path)
- logger.info("Skipped attachments migration from '#{old_path}' to '#{new_path}', source path doesn't exist or is not a directory (PROJECT_ID=#{project.id})")
- @skipped = true
- return true
- end
+ if result
+ project.save!
- if File.exist?(new_path)
- logger.error("Cannot migrate attachments from '#{old_path}' to '#{new_path}', target path already exist (PROJECT_ID=#{project.id})")
- raise AttachmentMigrationError, "Target path '#{new_path}' already exist"
+ yield if block_given?
+ else
+ # Rollback changes
+ project.rollback!
end
- # Create hashed storage base path folder
- FileUtils.mkdir_p(File.dirname(new_path))
-
- FileUtils.mv(old_path, new_path)
- logger.info("Migrated project attachments from '#{old_path}' to '#{new_path}' (PROJECT_ID=#{project.id})")
-
- true
+ result
end
end
end
diff --git a/app/services/projects/hashed_storage/migrate_repository_service.rb b/app/services/projects/hashed_storage/migrate_repository_service.rb
index 9c672283c7e..5afa8732c0a 100644
--- a/app/services/projects/hashed_storage/migrate_repository_service.rb
+++ b/app/services/projects/hashed_storage/migrate_repository_service.rb
@@ -15,7 +15,7 @@ module Projects
result = move_repository(old_disk_path, new_disk_path)
if move_wiki
- result &&= move_repository("#{old_wiki_disk_path}", "#{new_disk_path}.wiki")
+ result &&= move_repository(old_wiki_disk_path, "#{new_disk_path}.wiki")
end
if result
@@ -35,18 +35,6 @@ module Projects
result
end
-
- private
-
- def try_to_set_repository_read_only!
- # Mitigate any push operation to start during migration
- unless project.set_repository_read_only!
- migration_error = "Target repository '#{old_disk_path}' cannot be made read-only as there is a git transfer in progress"
- logger.error migration_error
-
- raise RepositoryMigrationError, migration_error
- end
- end
end
end
end
diff --git a/app/services/projects/hashed_storage/rollback_attachments_service.rb b/app/services/projects/hashed_storage/rollback_attachments_service.rb
new file mode 100644
index 00000000000..6c370ac47e9
--- /dev/null
+++ b/app/services/projects/hashed_storage/rollback_attachments_service.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Projects
+ module HashedStorage
+ class RollbackAttachmentsService < BaseAttachmentService
+ def initialize(project, logger: nil)
+ @project = project
+ @logger = logger || Rails.logger
+ @old_disk_path = project.disk_path
+ end
+
+ def execute
+ origin = FileUploader.absolute_base_dir(project)
+ project.storage_version = ::Project::HASHED_STORAGE_FEATURES[:repository]
+ target = FileUploader.absolute_base_dir(project)
+
+ @new_disk_path = FileUploader.base_dir(project)
+
+ result = move_folder!(origin, target)
+
+ if result
+ project.save!
+
+ yield if block_given?
+ else
+ # Rollback changes
+ project.rollback!
+ end
+
+ result
+ end
+ end
+ end
+end
diff --git a/app/services/projects/hashed_storage/rollback_repository_service.rb b/app/services/projects/hashed_storage/rollback_repository_service.rb
new file mode 100644
index 00000000000..b5c971c70a5
--- /dev/null
+++ b/app/services/projects/hashed_storage/rollback_repository_service.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Projects
+ module HashedStorage
+ class RollbackRepositoryService < BaseRepositoryService
+ def execute
+ try_to_set_repository_read_only!
+
+ @old_storage_version = project.storage_version
+ project.storage_version = nil
+ project.ensure_storage_path_exists
+
+ @new_disk_path = project.disk_path
+
+ result = move_repository(old_disk_path, new_disk_path)
+
+ if move_wiki
+ result &&= move_repository(old_wiki_disk_path, "#{new_disk_path}.wiki")
+ end
+
+ if result
+ project.write_repository_config
+ project.track_project_repository
+ else
+ rollback_folder_move
+ project.storage_version = ::Project::HASHED_STORAGE_FEATURES[:repository]
+ end
+
+ project.repository_read_only = false
+ project.save!
+
+ if result && block_given?
+ yield
+ end
+
+ result
+ end
+ end
+ end
+end
diff --git a/app/services/projects/hashed_storage/rollback_service.rb b/app/services/projects/hashed_storage/rollback_service.rb
new file mode 100644
index 00000000000..25767f5de5e
--- /dev/null
+++ b/app/services/projects/hashed_storage/rollback_service.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module Projects
+ module HashedStorage
+ class RollbackService < BaseService
+ attr_reader :logger, :old_disk_path
+
+ def initialize(project, old_disk_path, logger: nil)
+ @project = project
+ @old_disk_path = old_disk_path
+ @logger = logger || Rails.logger
+ end
+
+ def execute
+ # Rollback attachments from Hashed Storage to Legacy
+ if project.hashed_storage?(:attachments)
+ return false unless rollback_attachments
+ end
+
+ # Rollback repository from Hashed Storage to Legacy
+ if project.hashed_storage?(:repository)
+ rollback_repository
+ end
+ end
+
+ private
+
+ def rollback_attachments
+ HashedStorage::RollbackAttachmentsService.new(project, logger: logger).execute
+ end
+
+ def rollback_repository
+ HashedStorage::RollbackRepositoryService.new(project, old_disk_path, logger: logger).execute
+ end
+ end
+ end
+end
diff --git a/app/services/projects/operations/update_service.rb b/app/services/projects/operations/update_service.rb
index abd6d8de750..aedf79c86d7 100644
--- a/app/services/projects/operations/update_service.rb
+++ b/app/services/projects/operations/update_service.rb
@@ -12,7 +12,28 @@ module Projects
private
def project_update_params
- params.slice(:error_tracking_setting_attributes)
+ error_tracking_params
+ end
+
+ def error_tracking_params
+ settings = params[:error_tracking_setting_attributes]
+ return {} if settings.blank?
+
+ api_url = ErrorTracking::ProjectErrorTrackingSetting.build_api_url_from(
+ api_host: settings[:api_host],
+ project_slug: settings.dig(:project, :slug),
+ organization_slug: settings.dig(:project, :organization_slug)
+ )
+
+ {
+ error_tracking_setting_attributes: {
+ api_url: api_url,
+ token: settings[:token],
+ enabled: settings[:enabled],
+ project_name: settings.dig(:project, :name),
+ organization_name: settings.dig(:project, :organization_name)
+ }
+ }
end
end
end
diff --git a/app/services/prometheus/adapter_service.rb b/app/services/prometheus/adapter_service.rb
index a791845ba20..3be958e1613 100644
--- a/app/services/prometheus/adapter_service.rb
+++ b/app/services/prometheus/adapter_service.rb
@@ -30,7 +30,7 @@ module Prometheus
return unless deployment_platform.respond_to?(:cluster)
cluster = deployment_platform.cluster
- return unless cluster.application_prometheus&.ready?
+ return unless cluster.application_prometheus&.available?
cluster.application_prometheus
end
diff --git a/app/services/protected_branches/api_service.rb b/app/services/protected_branches/api_service.rb
index 9b85e13107b..1b13dace5f2 100644
--- a/app/services/protected_branches/api_service.rb
+++ b/app/services/protected_branches/api_service.rb
@@ -3,16 +3,15 @@
module ProtectedBranches
class ApiService < BaseService
def create
- @push_params = AccessLevelParams.new(:push, params)
- @merge_params = AccessLevelParams.new(:merge, params)
+ ::ProtectedBranches::CreateService.new(@project, @current_user, protected_branch_params).execute
+ end
- protected_branch_params = {
+ def protected_branch_params
+ {
name: params[:name],
- push_access_levels_attributes: @push_params.access_levels,
- merge_access_levels_attributes: @merge_params.access_levels
+ push_access_levels_attributes: AccessLevelParams.new(:push, params).access_levels,
+ merge_access_levels_attributes: AccessLevelParams.new(:merge, params).access_levels
}
-
- ::ProtectedBranches::CreateService.new(@project, @current_user, protected_branch_params).execute
end
end
end
diff --git a/app/services/protected_branches/legacy_api_update_service.rb b/app/services/protected_branches/legacy_api_update_service.rb
index da8bf2ce02a..7cb8d41818f 100644
--- a/app/services/protected_branches/legacy_api_update_service.rb
+++ b/app/services/protected_branches/legacy_api_update_service.rb
@@ -6,30 +6,31 @@
# lives in this service.
module ProtectedBranches
class LegacyApiUpdateService < BaseService
+ attr_reader :protected_branch, :developers_can_push, :developers_can_merge
+
def execute(protected_branch)
+ @protected_branch = protected_branch
@developers_can_push = params.delete(:developers_can_push)
@developers_can_merge = params.delete(:developers_can_merge)
- @protected_branch = protected_branch
-
protected_branch.transaction do
delete_redundant_access_levels
- case @developers_can_push
+ case developers_can_push
when true
params[:push_access_levels_attributes] = [{ access_level: Gitlab::Access::DEVELOPER }]
when false
params[:push_access_levels_attributes] = [{ access_level: Gitlab::Access::MAINTAINER }]
end
- case @developers_can_merge
+ case developers_can_merge
when true
params[:merge_access_levels_attributes] = [{ access_level: Gitlab::Access::DEVELOPER }]
when false
params[:merge_access_levels_attributes] = [{ access_level: Gitlab::Access::MAINTAINER }]
end
- service = ProtectedBranches::UpdateService.new(@project, @current_user, @params)
+ service = ProtectedBranches::UpdateService.new(project, current_user, params)
service.execute(protected_branch)
end
end
@@ -37,12 +38,12 @@ module ProtectedBranches
private
def delete_redundant_access_levels
- unless @developers_can_merge.nil?
- @protected_branch.merge_access_levels.destroy_all # rubocop: disable DestroyAll
+ unless developers_can_merge.nil?
+ protected_branch.merge_access_levels.destroy_all # rubocop: disable DestroyAll
end
- unless @developers_can_push.nil?
- @protected_branch.push_access_levels.destroy_all # rubocop: disable DestroyAll
+ unless developers_can_push.nil?
+ protected_branch.push_access_levels.destroy_all # rubocop: disable DestroyAll
end
end
end
diff --git a/app/services/suggestions/apply_service.rb b/app/services/suggestions/apply_service.rb
index 1f720fc835f..f778c5aa5f5 100644
--- a/app/services/suggestions/apply_service.rb
+++ b/app/services/suggestions/apply_service.rb
@@ -15,7 +15,13 @@ module Suggestions
return error('The file has been changed')
end
- params = file_update_params(suggestion)
+ diff_file = suggestion.note.latest_diff_file
+
+ unless diff_file
+ return error('The file was not found')
+ end
+
+ params = file_update_params(suggestion, diff_file)
result = ::Files::UpdateService.new(suggestion.project, @current_user, params).execute
if result[:status] == :success
@@ -38,8 +44,8 @@ module Suggestions
suggestion.position.head_sha == suggestion.noteable.source_branch_sha
end
- def file_update_params(suggestion)
- blob = suggestion.diff_file.new_blob
+ def file_update_params(suggestion, diff_file)
+ blob = diff_file.new_blob
file_path = suggestion.file_path
branch_name = suggestion.branch
file_content = new_file_content(suggestion, blob)
diff --git a/app/services/suggestions/create_service.rb b/app/services/suggestions/create_service.rb
index 77e958cbe0c..c7ac2452c53 100644
--- a/app/services/suggestions/create_service.rb
+++ b/app/services/suggestions/create_service.rb
@@ -9,6 +9,10 @@ module Suggestions
def execute
return unless @note.supports_suggestion?
+ diff_file = @note.latest_diff_file
+
+ return unless diff_file
+
suggestions = Banzai::SuggestionsParser.parse(@note.note)
# For single line suggestion we're only looking forward to
@@ -20,7 +24,7 @@ module Suggestions
rows =
suggestions.map.with_index do |suggestion, index|
- from_content = changing_lines(comment_line, comment_line)
+ from_content = changing_lines(diff_file, comment_line, comment_line)
# The parsed suggestion doesn't have information about the correct
# ending characters (we may have a line break, or not), so we take
@@ -44,8 +48,8 @@ module Suggestions
private
- def changing_lines(from_line, to_line)
- @note.diff_file.new_blob_lines_between(from_line, to_line).join
+ def changing_lines(diff_file, from_line, to_line)
+ diff_file.new_blob_lines_between(from_line, to_line).join
end
def line_break_chars(line)
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index ec6c306227b..ea8ac7e4656 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -360,7 +360,7 @@ module SystemNoteService
# author - User performing the change
# branch_type - 'source' or 'target'
# old_branch - old branch name
- # new_branch - new branch nmae
+ # new_branch - new branch name
#
# Example Note text:
#
diff --git a/app/services/users/activity_service.rb b/app/services/users/activity_service.rb
index db03ba8756f..e50840a9158 100644
--- a/app/services/users/activity_service.rb
+++ b/app/services/users/activity_service.rb
@@ -26,12 +26,15 @@ module Users
def record_activity
return if Gitlab::Database.read_only?
+ today = Date.today
+
+ return if @user.last_activity_on == today
+
lease = Gitlab::ExclusiveLease.new("acitvity_service:#{@user.id}",
timeout: LEASE_TIMEOUT)
return unless lease.try_obtain
- @user.update_attribute(:last_activity_on, Date.today)
- Rails.logger.debug("Recorded activity: #{@activity} for User ID: #{@user.id} (username: #{@user.username})")
+ @user.update_attribute(:last_activity_on, today)
end
end
end
diff --git a/app/validators/url_validator.rb b/app/validators/url_validator.rb
index d3e32818dc7..3fd015c3cf5 100644
--- a/app/validators/url_validator.rb
+++ b/app/validators/url_validator.rb
@@ -93,6 +93,12 @@ class UrlValidator < ActiveModel::EachValidator
end
def allow_setting_local_requests?
+ # We cannot use Gitlab::CurrentSettings as ApplicationSetting itself
+ # uses UrlValidator to validate urls. This ends up in a cycle
+ # when Gitlab::CurrentSettings creates an ApplicationSetting which then
+ # calls this validator.
+ #
+ # See https://gitlab.com/gitlab-org/gitlab-ee/issues/9833
ApplicationSetting.current&.allow_local_requests_from_hooks_and_services?
end
end
diff --git a/app/views/admin/appearances/_form.html.haml b/app/views/admin/appearances/_form.html.haml
index 77e84abd76e..a5f34d0dab2 100644
--- a/app/views/admin/appearances/_form.html.haml
+++ b/app/views/admin/appearances/_form.html.haml
@@ -42,6 +42,7 @@
%br
Images with incorrect dimensions are not resized automatically, and may result in unexpected behavior.
+ = render partial: 'admin/appearances/system_header_footer_form', locals: { form: f }
%hr
.row
diff --git a/app/views/admin/appearances/_system_header_footer_form.html.haml b/app/views/admin/appearances/_system_header_footer_form.html.haml
new file mode 100644
index 00000000000..4301ebd05af
--- /dev/null
+++ b/app/views/admin/appearances/_system_header_footer_form.html.haml
@@ -0,0 +1,33 @@
+- form = local_assigns.fetch(:form)
+
+%hr
+.row
+ .col-lg-4.profile-settings-sidebar
+ %h4.prepend-top-0
+ = _('System header and footer')
+
+ .col-lg-8
+ .form-group
+ = form.label :header_message, _('Header message'), class: 'col-form-label label-bold'
+ = form.text_area :header_message, placeholder: _('State your message to activate'), class: "form-control js-autosize"
+ .form-group
+ = form.label :footer_message, _('Footer message'), class: 'col-form-label label-bold'
+ = form.text_area :footer_message, placeholder: _('State your message to activate'), class: "form-control js-autosize"
+ .form-group
+ .form-check
+ = form.check_box :email_header_and_footer_enabled, class: 'form-check-input'
+ = form.label :email_header_and_footer_enabled, class: 'label-bold' do
+ = _('Enable header and footer in emails')
+
+ .hint
+ = _('Add header and footer to emails. Please note that color settings will only be applied within the application interface')
+
+ .form-group.js-toggle-colors-container
+ %button.btn.btn-link.js-toggle-colors-link{ type: 'button' }
+ = _('Customize colors')
+ .form-group.js-toggle-colors-container.hide
+ = form.label :message_background_color, _('Background Color'), class: 'col-form-label label-bold'
+ = form.color_field :message_background_color, class: "form-control"
+ .form-group.js-toggle-colors-container.hide
+ = form.label :message_font_color, _('Font Color'), class: 'col-form-label label-bold'
+ = form.color_field :message_font_color, class: "form-control"
diff --git a/app/views/admin/groups/_group.html.haml b/app/views/admin/groups/_group.html.haml
index 0a688b90f3a..395c469255e 100644
--- a/app/views/admin/groups/_group.html.haml
+++ b/app/views/admin/groups/_group.html.haml
@@ -22,7 +22,7 @@
%span.visibility-icon.has-tooltip{ data: { container: 'body', placement: 'left' }, title: visibility_icon_description(group) }
= visibility_level_icon(group.visibility_level, fw: false)
- .avatar-container.s40
+ .avatar-container.rect-avatar.s40
= group_icon(group, class: "avatar s40 d-none d-sm-block")
.title
= link_to [:admin, group], class: 'group-name' do
diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml
index 93da87538bc..00d255846f9 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -15,7 +15,7 @@
= _('Group info:')
%ul.content-list
%li
- .avatar-container.s60
+ .avatar-container.rect-avatar.s60
= group_icon(@group, class: "avatar s60")
%li
%span.light= _('Name:')
diff --git a/app/views/admin/projects/_projects.html.haml b/app/views/admin/projects/_projects.html.haml
index 50296a2afe7..5bc695aa7b5 100644
--- a/app/views/admin/projects/_projects.html.haml
+++ b/app/views/admin/projects/_projects.html.haml
@@ -19,7 +19,7 @@
.title
= link_to(admin_namespace_project_path(project.namespace, project)) do
.dash-project-avatar
- .avatar-container.s40
+ .avatar-container.rect-avatar.s40
= project_icon(project, alt: '', class: 'avatar project-avatar s40', width: 40, height: 40)
%span.project-full-name
%span.namespace-name
diff --git a/app/views/admin/runners/_runner.html.haml b/app/views/admin/runners/_runner.html.haml
index ec57eb1ed08..ecf2b1d60ba 100644
--- a/app/views/admin/runners/_runner.html.haml
+++ b/app/views/admin/runners/_runner.html.haml
@@ -44,12 +44,12 @@
.table-section.section-5
.table-mobile-header{ role: 'rowheader' }= _('Jobs')
.table-mobile-content
- = runner.builds.count(:all)
+ = limited_counter_with_delimiter(runner.builds)
.table-section.section-10.section-wrap
.table-mobile-header{ role: 'rowheader' }= _('Tags')
.table-mobile-content
- - runner.tag_list.sort.each do |tag|
+ - runner.tags.map(&:name).sort.each do |tag|
%span.badge.badge-primary
= tag
diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml
index 81380587fd2..2e23b748edb 100644
--- a/app/views/admin/runners/index.html.haml
+++ b/app/views/admin/runners/index.html.haml
@@ -92,6 +92,25 @@
= button_tag class: %w[btn btn-link] do
= runner_type.titleize
+ #js-dropdown-admin-runner-type.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul{ data: { dropdown: true } }
+ - Ci::Runner::AVAILABLE_TYPES.each do |runner_type|
+ %li.filter-dropdown-item{ data: { value: runner_type } }
+ = button_tag class: %w[btn btn-link] do
+ = runner_type.titleize
+
+ #js-dropdown-runner-tag.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul{ data: { dropdown: true } }
+ %li.filter-dropdown-item{ data: { value: 'none' } }
+ %button.btn.btn-link
+ = _('No Tag')
+ %li.divider.droplab-item-ignore
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
+ %li.filter-dropdown-item
+ %button.btn.btn-link.js-data-value
+ %span.dropdown-light-content
+ {{name}}
+
= button_tag class: %w[clear-search hidden] do
= icon('times')
.filter-dropdown-container
diff --git a/app/views/clusters/clusters/_form.html.haml b/app/views/clusters/clusters/_form.html.haml
index 7acd9ce0562..9fb91a39387 100644
--- a/app/views/clusters/clusters/_form.html.haml
+++ b/app/views/clusters/clusters/_form.html.haml
@@ -28,7 +28,7 @@
.form-group
%h5= s_('ClusterIntegration|Base domain')
- = field.text_field :base_domain, class: 'col-md-6 form-control js-select-on-focus'
+ = field.text_field :base_domain, class: 'col-md-6 form-control js-select-on-focus qa-base-domain'
.form-text.text-muted
- auto_devops_url = help_page_path('topics/autodevops/index')
- auto_devops_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: auto_devops_url }
@@ -43,4 +43,4 @@
- if can?(current_user, :update_cluster, @cluster)
.form-group
- = field.submit _('Save changes'), class: 'btn btn-success'
+ = field.submit _('Save changes'), class: 'btn btn-success qa-save-domain'
diff --git a/app/views/dashboard/_snippets_head.html.haml b/app/views/dashboard/_snippets_head.html.haml
index 8d99f84755a..a05d0190efb 100644
--- a/app/views/dashboard/_snippets_head.html.haml
+++ b/app/views/dashboard/_snippets_head.html.haml
@@ -1,9 +1,9 @@
.page-title-holder
%h1.page-title= _('Snippets')
- - if current_user
+ - if current_user && current_user.snippets.any? || @snippets.any?
.page-title-controls
- = link_to "New snippet", new_snippet_path, class: "btn btn-success", title: "New snippet"
+ = link_to _("New snippet"), new_snippet_path, class: "btn btn-success", title: _("New snippet")
.top-area
%ul.nav-links.nav.nav-tabs
diff --git a/app/views/dashboard/projects/_starred_empty_state.html.haml b/app/views/dashboard/projects/_starred_empty_state.html.haml
new file mode 100644
index 00000000000..bea27f1a456
--- /dev/null
+++ b/app/views/dashboard/projects/_starred_empty_state.html.haml
@@ -0,0 +1,9 @@
+.row.empty-state
+ .col-12
+ .svg-content.svg-250
+ = image_tag 'illustrations/starred_empty.svg'
+ .text-content
+ %h4.text-center
+ = s_("StarredProjectsEmptyState|You don't have starred projects yet.")
+ %p.text-secondary
+ = s_("StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page.")
diff --git a/app/views/dashboard/projects/starred.html.haml b/app/views/dashboard/projects/starred.html.haml
index ad08409c8fe..3a45f6df017 100644
--- a/app/views/dashboard/projects/starred.html.haml
+++ b/app/views/dashboard/projects/starred.html.haml
@@ -1,8 +1,8 @@
- @hide_top_links = true
- @no_container = true
-- breadcrumb_title "Projects"
-- page_title "Starred Projects"
-- header_title "Projects", dashboard_projects_path
+- breadcrumb_title _("Projects")
+- page_title _("Starred Projects")
+- header_title _("Projects"), dashboard_projects_path
= render_if_exists "shared/gold_trial_callout"
@@ -13,5 +13,4 @@
- if params[:filter_projects] || any_projects?(@projects)
= render 'projects'
- else
- %h3.page-title You don't have starred projects yet
- %p.slead Visit project page and press on star icon and it will appear on this page.
+ = render 'starred_empty_state'
diff --git a/app/views/dashboard/snippets/index.html.haml b/app/views/dashboard/snippets/index.html.haml
index 6eb067da95c..b649fe91c24 100644
--- a/app/views/dashboard/snippets/index.html.haml
+++ b/app/views/dashboard/snippets/index.html.haml
@@ -3,6 +3,14 @@
- header_title "Snippets", dashboard_snippets_path
= render 'dashboard/snippets_head'
-= render partial: 'snippets/snippets_scope_menu', locals: { include_private: true }
+- if current_user.snippets.exists?
+ = render partial: 'snippets/snippets_scope_menu', locals: { include_private: true }
-= render partial: 'snippets/snippets', locals: { link_project: true }
+.d-block.d-sm-none
+ &nbsp;
+ = link_to _("New snippet"), new_snippet_path, class: "btn btn-success btn-block", title: _("New snippet")
+
+- if current_user.snippets.exists?
+ = render partial: 'shared/snippets/list', locals: { link_project: true }
+- else
+ = render 'shared/empty_states/snippets', button_path: new_snippet_path
diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml
index 004a3528d4b..9c7ca6ebbd4 100644
--- a/app/views/devise/shared/_signup_box.html.haml
+++ b/app/views/devise/shared/_signup_box.html.haml
@@ -3,12 +3,12 @@
= form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name), html: { class: "new_new_user gl-show-field-errors", "aria-live" => "assertive" }) do |f|
.devise-errors
= devise_error_messages!
- .form-group
+ .name.form-group
= f.label :name, 'Full name', class: 'label-bold'
- = f.text_field :name, class: "form-control top qa-new-user-name", required: true, title: "This field is required."
+ = f.text_field :name, class: "form-control top qa-new-user-name js-block-emoji", required: true, title: _("This field is required.")
.username.form-group
= f.label :username, class: 'label-bold'
- = f.text_field :username, class: "form-control middle qa-new-user-username", pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: 'Please create a username with only alphanumeric characters.'
+ = f.text_field :username, class: "form-control middle qa-new-user-username js-block-emoji", pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: _("Please create a username with only alphanumeric characters.")
%p.validation-error.hide Username is already taken.
%p.validation-success.hide Username is available.
%p.validation-pending.hide Checking username availability...
diff --git a/app/views/explore/snippets/index.html.haml b/app/views/explore/snippets/index.html.haml
index 94fc4ac21d2..d23c8301b10 100644
--- a/app/views/explore/snippets/index.html.haml
+++ b/app/views/explore/snippets/index.html.haml
@@ -7,4 +7,4 @@
- else
= render 'explore/head'
-= render partial: 'snippets/snippets', locals: { link_project: true }
+= render partial: 'shared/snippets/list', locals: { link_project: true }
diff --git a/app/views/groups/_home_panel.html.haml b/app/views/groups/_home_panel.html.haml
index 3a8d95f44d1..39c0c113793 100644
--- a/app/views/groups/_home_panel.html.haml
+++ b/app/views/groups/_home_panel.html.haml
@@ -3,7 +3,7 @@
.group-home-panel
.row.mb-3
.home-panel-title-row.col-md-12.col-lg-6.d-flex
- .avatar-container.home-panel-avatar.append-right-default.float-none
+ .avatar-container.rect-avatar.s64.home-panel-avatar.append-right-default.float-none
= group_icon(@group, class: 'avatar avatar-tile s64', width: 64, height: 64)
.d-flex.flex-column.flex-wrap.align-items-baseline
.d-inline-flex.align-items-baseline
diff --git a/app/views/groups/settings/_general.html.haml b/app/views/groups/settings/_general.html.haml
index 0424ece037d..9ed71d19d32 100644
--- a/app/views/groups/settings/_general.html.haml
+++ b/app/views/groups/settings/_general.html.haml
@@ -20,7 +20,7 @@
= render_if_exists 'shared/repository_size_limit_setting', form: f, type: :group
.form-group.prepend-top-default.append-bottom-20
- .avatar-container.s90
+ .avatar-container.rect-avatar.s90
= group_icon(@group, alt: '', class: 'avatar group-avatar s90')
= f.label :avatar, _('Group avatar'), class: 'label-bold d-block'
= render 'shared/choose_group_avatar_button', f: f
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index 28ffb2dd63c..efb3815b257 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -356,6 +356,18 @@
%td.shortcut
%kbd l
%td Change Label
+ %tr
+ %td.shortcut
+ %kbd ]
+ \/
+ %kbd j
+ %td Move to next file
+ %tr
+ %td.shortcut
+ %kbd [
+ \/
+ %kbd k
+ %td Move to previous file
%tbody.hidden-shortcut{ style: 'display:none' }
%tr
%th
diff --git a/app/views/import/_githubish_status.html.haml b/app/views/import/_githubish_status.html.haml
index f4a29ed18dc..b05c039c85c 100644
--- a/app/views/import/_githubish_status.html.haml
+++ b/app/views/import/_githubish_status.html.haml
@@ -1,56 +1,9 @@
- provider = local_assigns.fetch(:provider)
- provider_title = Gitlab::ImportSources.title(provider)
-%p.light
- = import_githubish_choose_repository_message
-%hr
-%p
- = button_tag class: "btn btn-import btn-success js-import-all" do
- = import_all_githubish_repositories_button_label
- = icon("spinner spin", class: "loading-icon")
-
-.table-responsive
- %table.table.import-jobs
- %colgroup.import-jobs-from-col
- %colgroup.import-jobs-to-col
- %colgroup.import-jobs-status-col
- %thead
- %tr
- %th= _('From %{provider_title}') % { provider_title: provider_title }
- %th= _('To GitLab')
- %th= _('Status')
- %tbody
- - @already_added_projects.each do |project|
- %tr{ id: "project_#{project.id}", class: project_status_css_class(project.import_status) }
- %td
- = provider_project_link(provider, project.import_source)
- %td
- = link_to project.full_path, [project.namespace.becomes(Namespace), project]
- %td.job-status
- = render 'import/project_status', project: project
-
- - @repos.each do |repo|
- %tr{ id: "repo_#{repo.id}", data: { qa: { repo_path: repo.full_name } } }
- %td
- = provider_project_link(provider, repo.full_name)
- %td.import-target
- %fieldset.row
- .input-group
- .project-path.input-group-prepend
- - if current_user.can_select_namespace?
- - selected = params[:namespace_id] || :current_user
- - opts = current_user.can_create_group? ? { extra_group: Group.new(name: repo.owner.login, path: repo.owner.login) } : {}
- = select_tag :namespace_id, namespaces_options(selected, opts.merge({ display_path: true })), { class: 'input-group-text select2 js-select-namespace qa-project-namespace-select', tabindex: 1 }
- - else
- = text_field_tag :path, current_user.namespace_path, class: "input-group-text input-large form-control", tabindex: 1, disabled: true
- %span.input-group-prepend
- .input-group-text /
- = text_field_tag :path, sanitize_project_name(repo.name), class: "input-mini form-control", tabindex: 2, autofocus: true, required: true
- %td.import-actions.job-status
- = button_tag class: "btn btn-import js-add-to-import" do
- = has_ci_cd_only_params? ? _('Connect') : _('Import')
- = icon("spinner spin", class: "loading-icon")
-
-.js-importer-status{ data: { jobs_import_path: url_for([:jobs, :import, provider]),
- import_path: url_for([:import, provider]),
- ci_cd_only: has_ci_cd_only_params?.to_s } }
+#import-projects-mount-element{ data: { provider: provider, provider_title: provider_title,
+ can_select_namespace: current_user.can_select_namespace?.to_s,
+ ci_cd_only: has_ci_cd_only_params?.to_s,
+ repos_path: url_for([:status, :import, provider, format: :json]),
+ jobs_path: url_for([:realtime_changes, :import, provider, format: :json]),
+ import_path: url_for([:import, provider, format: :json]) } }
diff --git a/app/views/import/github/new.html.haml b/app/views/import/github/new.html.haml
index 6ff25f2c842..cf32c5c9387 100644
--- a/app/views/import/github/new.html.haml
+++ b/app/views/import/github/new.html.haml
@@ -4,7 +4,7 @@
- header_title _("Projects"), root_path
%h3.page-title
- = icon 'github', text: import_github_title
+ = icon 'github', text: _('Import repositories from GitHub')
- if github_import_configured?
%p
diff --git a/app/views/import/github/status.html.haml b/app/views/import/github/status.html.haml
index be057be6d1a..ee295e70cce 100644
--- a/app/views/import/github/status.html.haml
+++ b/app/views/import/github/status.html.haml
@@ -2,7 +2,7 @@
- page_title title
- breadcrumb_title title
- header_title _("Projects"), root_path
-%h3.page-title
- = icon 'github', text: import_github_title
+%h3.page-title.mb-0
+ = icon 'github', class: 'fa-2x', text: _('Import repositories from GitHub')
= render 'import/githubish_status', provider: 'github'
diff --git a/app/views/import/manifest/status.html.haml b/app/views/import/manifest/status.html.haml
index 5b2e1005398..3d4abc32b88 100644
--- a/app/views/import/manifest/status.html.haml
+++ b/app/views/import/manifest/status.html.haml
@@ -7,7 +7,7 @@
%p
= button_tag class: "btn btn-import btn-success js-import-all" do
- = import_all_githubish_repositories_button_label
+ = _('Import all repositories')
= icon("spinner spin", class: "loading-icon")
.table-responsive
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index 0bb2363f65a..11e83ddfe64 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -38,6 +38,8 @@
= stylesheet_link_tag 'performance_bar' if performance_bar_enabled?
= stylesheet_link_tag 'csslab' if Feature.enabled?(:csslab)
+ = stylesheet_link_tag "highlight/themes/#{user_color_scheme}", media: "all"
+
= Gon::Base.render_data
- if content_for?(:library_javascripts)
diff --git a/app/views/layouts/_mailer.html.haml b/app/views/layouts/_mailer.html.haml
index 26fd34347ec..e13490ed410 100644
--- a/app/views/layouts/_mailer.html.haml
+++ b/app/views/layouts/_mailer.html.haml
@@ -52,6 +52,7 @@
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#6b4fbb;height:4px;font-size:4px;line-height:4px;" }
%tr.header
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
+ = html_header_message
= header_logo
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;" }
@@ -72,3 +73,6 @@
= _("You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}").html_safe % { host: Gitlab.config.gitlab.host, manage_notifications_link: manage_notifications_link, help_link: help_link }
= yield :additional_footer
+ %tr
+ %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
+ = html_footer_message
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 4373240001e..043cca6ad38 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -5,7 +5,9 @@
= render "layouts/init_auto_complete" if @gfm_form
= render "layouts/init_client_detection_flags"
= render 'peek/bar'
- = render partial: "layouts/header/default", locals: { project: @project, group: @group }
+ = header_message
+ = render partial: "layouts/header/default", locals: { project: @project, group: @group }
= render 'layouts/page', sidebar: sidebar, nav: nav
+ = footer_message
= yield :scripts_body
diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml
index 6003d973c88..2f3c13aaf6e 100644
--- a/app/views/layouts/devise.html.haml
+++ b/app/views/layouts/devise.html.haml
@@ -2,6 +2,7 @@
%html.devise-layout-html{ class: system_message_class }
= render "layouts/head"
%body.ui-indigo.login-page.application.navless.qa-login-page{ data: { page: body_data_page } }
+ = header_message
.page-wrap
= render "layouts/header/empty"
.login-page-broadcast
@@ -34,3 +35,4 @@
= link_to _("Explore"), explore_root_path
= link_to _("Help"), help_path
= link_to _("About GitLab"), "https://about.gitlab.com/"
+ = footer_message
diff --git a/app/views/layouts/devise_empty.html.haml b/app/views/layouts/devise_empty.html.haml
index 663e5b24368..6c9c8aa4431 100644
--- a/app/views/layouts/devise_empty.html.haml
+++ b/app/views/layouts/devise_empty.html.haml
@@ -2,6 +2,7 @@
%html{ lang: "en", class: system_message_class }
= render "layouts/head"
%body.ui-indigo.login-page.application.navless
+ = header_message
= render "layouts/header/empty"
= render "layouts/broadcast"
.container.navless-container
@@ -15,3 +16,4 @@
= link_to _("Explore"), explore_root_path
= link_to _("Help"), help_path
= link_to _("About GitLab"), "https://about.gitlab.com/"
+ = footer_message
diff --git a/app/views/layouts/empty_mailer.html.haml b/app/views/layouts/empty_mailer.html.haml
new file mode 100644
index 00000000000..a25dcefd445
--- /dev/null
+++ b/app/views/layouts/empty_mailer.html.haml
@@ -0,0 +1,5 @@
+= html_header_message
+
+= yield
+
+= html_footer_message
diff --git a/app/views/layouts/empty_mailer.text.erb b/app/views/layouts/empty_mailer.text.erb
new file mode 100644
index 00000000000..6ab0dbead07
--- /dev/null
+++ b/app/views/layouts/empty_mailer.text.erb
@@ -0,0 +1,5 @@
+<%= text_header_message %>
+
+<%= yield -%>
+
+<%= text_footer_message %>
diff --git a/app/views/layouts/mailer.text.erb b/app/views/layouts/mailer.text.erb
index 8e11174f8d7..f8032f3262b 100644
--- a/app/views/layouts/mailer.text.erb
+++ b/app/views/layouts/mailer.text.erb
@@ -1,4 +1,8 @@
+<%= text_header_message %>
+
<%= yield -%>
-- <%# signature marker %>
<%= _("You're receiving this email because of your account on %{host}.") % { host: Gitlab.config.gitlab.host } %>
+
+<%= text_footer_message %>
diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml
index 3fbaaafe89e..21ea9f3b2f3 100644
--- a/app/views/layouts/nav/sidebar/_group.html.haml
+++ b/app/views/layouts/nav/sidebar/_group.html.haml
@@ -6,7 +6,7 @@
.nav-sidebar-inner-scroll
.context-header
= link_to group_path(@group), title: @group.name do
- .avatar-container.s40.group-avatar
+ .avatar-container.rect-avatar.s40.group-avatar
= group_icon(@group, class: "avatar s40 avatar-tile")
.sidebar-context-title
= @group.name
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index dd7833647b7..7b492efeb09 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -3,7 +3,7 @@
- can_edit = can?(current_user, :admin_project, @project)
.context-header
= link_to project_path(@project), title: @project.name do
- .avatar-container.s40.project-avatar
+ .avatar-container.rect-avatar.s40.project-avatar
= project_icon(@project, alt: @project.name, class: 'avatar s40 avatar-tile', width: 40, height: 40)
.sidebar-context-title
= @project.name
diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml
index 1c3e05e07f4..8dff12c1b7f 100644
--- a/app/views/layouts/notify.html.haml
+++ b/app/views/layouts/notify.html.haml
@@ -7,6 +7,7 @@
= yield :head
%body
.content
+ = html_header_message
= yield
.footer{ style: "margin-top: 10px;" }
%p
@@ -30,3 +31,4 @@
adjust your notification settings.
= email_action @target_url
+ = html_footer_message
diff --git a/app/views/layouts/notify.text.erb b/app/views/layouts/notify.text.erb
index 9dc490efa9a..248916fba63 100644
--- a/app/views/layouts/notify.text.erb
+++ b/app/views/layouts/notify.text.erb
@@ -1,3 +1,5 @@
+<%= text_header_message %>
+
<%= yield -%>
-- <%# signature marker %>
@@ -10,3 +12,5 @@
<% end -%>
<%= "You're receiving this email because #{notification_reason_text(@reason)}." %>
+
+<%= text_footer_message -%>
diff --git a/app/views/profiles/passwords/new.html.haml b/app/views/profiles/passwords/new.html.haml
index d265f3c44ba..4b84835429c 100644
--- a/app/views/profiles/passwords/new.html.haml
+++ b/app/views/profiles/passwords/new.html.haml
@@ -1,12 +1,13 @@
-- page_title "New Password"
-- header_title "New Password"
-%h3.page-title Set up new password
+- page_title _('New Password')
+- breadcrumb_title _('New Password')
+
+%h3.page-title= _('Set up new password')
%hr
= form_for @user, url: profile_password_path, method: :post do |f|
%p.slead
- Please set a new password before proceeding.
+ = _('Please set a new password before proceeding.')
%br
- After a successful password update you will be redirected to login screen.
+ = _('After a successful password update you will be redirected to login screen.')
= form_errors(@user)
@@ -22,4 +23,4 @@
.col-sm-10
= f.password_field :password_confirmation, required: true, class: 'form-control'
.form-actions
- = f.submit 'Set new password', class: "btn btn-success"
+ = f.submit _('Set new password'), class: 'btn btn-success'
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index 1a9aca1f6bf..bfe1c3ddf33 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -73,6 +73,12 @@
= link_to _('Learn more'), help_page_path('user/profile/preferences', anchor: 'localization'), target: '_blank'
.col-lg-8
.form-group
+ = f.label :preferred_language, class: 'label-bold' do
+ = _('Language')
+ = f.select :preferred_language, language_choices, {}, class: 'select2'
+ .form-text.text-muted
+ = s_('Preferences|This feature is experimental and translations are not complete yet')
+ .form-group
= f.label :first_day_of_week, class: 'label-bold' do
= _('First day of the week')
= f.select :first_day_of_week, first_day_of_week_choices_with_default, {}, class: 'form-control'
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index 753316b27e2..4d3d92d09c0 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -95,9 +95,6 @@
= f.select :commit_email, options_for_select(commit_email_select_options(@user), selected: selected_commit_email(@user)),
{ help: s_("Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}").html_safe % { learn_more: commit_email_docs_link } },
control_class: 'select2 input-lg'
- = f.select :preferred_language, Gitlab::I18n::AVAILABLE_LANGUAGES.map { |value, label| [label, value] },
- { help: s_("Profiles|This feature is experimental and translations are not complete yet") },
- control_class: 'select2 input-lg'
= f.text_field :skype, class: 'input-md', placeholder: s_("Profiles|username")
= f.text_field :linkedin, class: 'input-md', help: s_("Profiles|Your LinkedIn profile name from linkedin.com/in/profilename")
= f.text_field :twitter, class: 'input-md', placeholder: s_("Profiles|@username")
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index 0be41b5888c..1d7287410ea 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -1,9 +1,10 @@
- empty_repo = @project.empty_repo?
- show_auto_devops_callout = show_auto_devops_callout?(@project)
+- max_project_topic_length = 15
.project-home-panel{ class: ("empty-project" if empty_repo) }
.row.append-bottom-8
.home-panel-title-row.col-md-12.col-lg-6.d-flex
- .avatar-container.home-panel-avatar.append-right-default.float-none
+ .avatar-container.rect-avatar.s64.home-panel-avatar.append-right-default.float-none
= project_icon(@project, alt: @project.name, class: 'avatar avatar-tile s64', width: 64, height: 64)
.d-flex.flex-column.flex-wrap.align-items-baseline
.d-inline-flex.align-items-baseline
@@ -19,15 +20,21 @@
%span.access-request-links.prepend-left-8
= render 'shared/members/access_request_links', source: @project
- if @project.tag_list.present?
- %span.home-panel-topic-list.d-inline-flex.prepend-left-8.has-tooltip{ data: { container: 'body' }, title: @project.has_extra_topics? ? @project.tag_list.join(', ') : nil }
+ %span.home-panel-topic-list.d-inline-flex.prepend-left-8
= sprite_icon('tag', size: 16, css_class: 'icon append-right-4')
- @project.topics_to_show.each do |topic|
- %a{ class: 'badge badge-pill badge-secondary append-right-5 str-truncated-30', href: explore_projects_path(tag: topic) }
- = topic.titleize
+ - project_topics_classes = "badge badge-pill badge-secondary append-right-5"
+ - explore_project_topic_path = explore_projects_path(tag: topic)
+ - if topic.length > max_project_topic_length
+ %a{ class: "#{ project_topics_classes } str-truncated-30 has-tooltip", data: { container: "body" }, title: topic, href: explore_project_topic_path }
+ = topic.titleize
+ - else
+ %a{ class: project_topics_classes, href: explore_project_topic_path }
+ = topic.titleize
- if @project.has_extra_topics?
- .text-nowrap
+ .text-nowrap.has-tooltip{ data: { container: 'body' }, title: @project.has_extra_topics? ? @project.topics_not_shown.join(', ') : nil }
= _("+ %{count} more") % { count: @project.count_of_extra_topics_not_shown }
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index 03ba1104507..830cfa80d58 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -12,10 +12,10 @@
%ul.nav.nav-tabs.nav-links.clearfix
%li.md-header-tab.active
%button.js-md-write-button{ tabindex: -1 }
- Write
+ = _("Write")
%li.md-header-tab
%button.js-md-preview-button{ tabindex: -1 }
- Preview
+ = _("Preview")
%li.md-header-toolbar.active
= render 'projects/blob/markdown_buttons', show_fullscreen_button: true
diff --git a/app/views/projects/_merge_request_merge_settings.html.haml b/app/views/projects/_merge_request_merge_settings.html.haml
index f178c94e008..6ac2e06afa5 100644
--- a/app/views/projects/_merge_request_merge_settings.html.haml
+++ b/app/views/projects/_merge_request_merge_settings.html.haml
@@ -9,6 +9,7 @@
%span.descr
Pipelines need to be configured to enable this feature.
= link_to icon('question-circle'), help_page_path('user/project/merge_requests/merge_when_pipeline_succeeds', anchor: 'only-allow-merge-requests-to-be-merged-if-the-pipeline-succeeds'), target: '_blank'
+ = render_if_exists 'projects/merge_pipelines_settings', form: form
.form-check
= form.check_box :only_allow_merge_if_all_discussions_are_resolved, class: 'form-check-input'
= form.label :only_allow_merge_if_all_discussions_are_resolved, class: 'form-check-label' do
diff --git a/app/views/projects/blob/_markdown_buttons.html.haml b/app/views/projects/blob/_markdown_buttons.html.haml
index 1d6acd86108..28d1ff97825 100644
--- a/app/views/projects/blob/_markdown_buttons.html.haml
+++ b/app/views/projects/blob/_markdown_buttons.html.haml
@@ -1,13 +1,13 @@
.md-header-toolbar.active
- = markdown_toolbar_button({ icon: "bold", data: { "md-tag" => "**" }, title: s_("MarkdownToolbar|Add bold text") })
- = markdown_toolbar_button({ icon: "italic", data: { "md-tag" => "*" }, title: s_("MarkdownToolbar|Add italic text") })
- = markdown_toolbar_button({ icon: "quote", data: { "md-tag" => "> ", "md-prepend" => true }, title: s_("MarkdownToolbar|Insert a quote") })
- = markdown_toolbar_button({ icon: "code", data: { "md-tag" => "`", "md-block" => "```" }, title: s_("MarkdownToolbar|Insert code") })
- = markdown_toolbar_button({ icon: "link", data: { "md-tag" => "[{text}](url)", "md-select" => "url" }, title: s_("MarkdownToolbar|Add a link") })
- = markdown_toolbar_button({ icon: "list-bulleted", data: { "md-tag" => "* ", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a bullet list") })
- = markdown_toolbar_button({ icon: "list-numbered", data: { "md-tag" => "1. ", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a numbered list") })
- = markdown_toolbar_button({ icon: "task-done", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a task list") })
- = markdown_toolbar_button({ icon: "table", data: { "md-tag" => "| header | header |\n| ------ | ------ |\n| cell | cell |\n| cell | cell |", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a table") })
+ = markdown_toolbar_button({ icon: "bold", data: { "md-tag" => "**" }, title: _("Add bold text") })
+ = markdown_toolbar_button({ icon: "italic", data: { "md-tag" => "*" }, title: _("Add italic text") })
+ = markdown_toolbar_button({ icon: "quote", data: { "md-tag" => "> ", "md-prepend" => true }, title: _("Insert a quote") })
+ = markdown_toolbar_button({ icon: "code", data: { "md-tag" => "`", "md-block" => "```" }, title: _("Insert code") })
+ = markdown_toolbar_button({ icon: "link", data: { "md-tag" => "[{text}](url)", "md-select" => "url" }, title: _("Add a link") })
+ = markdown_toolbar_button({ icon: "list-bulleted", data: { "md-tag" => "* ", "md-prepend" => true }, title: _("Add a bullet list") })
+ = markdown_toolbar_button({ icon: "list-numbered", data: { "md-tag" => "1. ", "md-prepend" => true }, title: _("Add a numbered list") })
+ = markdown_toolbar_button({ icon: "task-done", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: _("Add a task list") })
+ = markdown_toolbar_button({ icon: "table", data: { "md-tag" => "| header | header |\n| ------ | ------ |\n| cell | cell |\n| cell | cell |", "md-prepend" => true }, title: _("Add a table") })
- if show_fullscreen_button
- %button.toolbar-btn.toolbar-fullscreen-btn.js-zen-enter.has-tooltip{ type: "button", tabindex: -1, "aria-label": "Go full screen", title: s_("MarkdownToolbar|Go full screen"), data: { container: "body" } }
+ %button.toolbar-btn.toolbar-fullscreen-btn.js-zen-enter.has-tooltip{ type: "button", tabindex: -1, "aria-label": "Go full screen", title: _("Go full screen"), data: { container: "body" } }
= sprite_icon("screen-full")
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index c64ad1c8147..91c51d5e091 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -53,9 +53,8 @@
= _('Merge request')
- if branch.name != @repository.root_ref
- = link_to project_compare_index_path(@project, from: @repository.root_ref, to: branch.name),
+ = link_to project_compare_path(@project, @repository.root_ref, branch.name),
class: "btn btn-default #{'prepend-left-10' unless merge_project}",
- method: :post,
title: s_('Branches|Compare') do
= s_('Branches|Compare')
diff --git a/app/views/projects/commit/_ajax_signature.html.haml b/app/views/projects/commit/_ajax_signature.html.haml
index eb677cff5f0..ae9aef5a9b0 100644
--- a/app/views/projects/commit/_ajax_signature.html.haml
+++ b/app/views/projects/commit/_ajax_signature.html.haml
@@ -1,2 +1,2 @@
- if commit.has_signature?
- %a{ href: 'javascript:void(0)', tabindex: 0, class: commit_signature_badge_classes('js-loading-gpg-badge'), data: { toggle: 'tooltip', placement: 'top', title: 'GPG signature (loading...)', 'commit-sha' => commit.sha } }
+ %a{ href: 'javascript:void(0)', tabindex: 0, class: commit_signature_badge_classes('js-loading-gpg-badge'), data: { toggle: 'tooltip', placement: 'top', title: _('GPG signature (loading...)'), 'commit-sha' => commit.sha } }
diff --git a/app/views/projects/commit/_ci_menu.html.haml b/app/views/projects/commit/_ci_menu.html.haml
index 8b6e3e42ea1..41f5fb3dcbd 100644
--- a/app/views/projects/commit/_ci_menu.html.haml
+++ b/app/views/projects/commit/_ci_menu.html.haml
@@ -3,10 +3,10 @@
%ul.nav-links.no-top.no-bottom.commit-ci-menu.nav.nav-tabs
= nav_link(path: 'commit#show') do
= link_to project_commit_path(@project, @commit.id) do
- Changes
+ = _('Changes')
%span.badge.badge-pill= @diffs.size
- if any_pipelines
= nav_link(path: 'commit#pipelines') do
= link_to pipelines_project_commit_path(@project, @commit.id) do
- Pipelines
+ = _('Pipelines')
%span.badge.badge-pill.js-pipelines-mr-count= @commit.pipelines.size
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 90fee2d70be..a0db48bf8ff 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -6,8 +6,8 @@
%strong
#{ s_('CommitBoxTitle|Commit') }
%span.commit-sha= @commit.short_id
- = clipboard_button(text: @commit.id, title: _("Copy commit SHA to clipboard"))
- %span.d-none.d-sm-inline authored
+ = clipboard_button(text: @commit.id, title: _('Copy commit SHA to clipboard'))
+ %span.d-none.d-sm-inline= _('authored')
#{time_ago_with_tooltip(@commit.authored_date)}
%span= s_('ByAuthor|by')
= author_avatar(@commit, size: 24, has_tooltip: false)
@@ -43,13 +43,13 @@
= cherry_pick_commit_link(@commit, project_commit_path(@project, @commit.id), has_tooltip: false)
- if can?(current_user, :push_code, @project)
%li.clearfix
- = link_to s_("CreateTag|Tag"), new_project_tag_path(@project, ref: @commit)
+ = link_to s_('CreateTag|Tag'), new_project_tag_path(@project, ref: @commit)
%li.divider
%li.dropdown-header
#{ _('Download') }
- unless @commit.parents.length > 1
- %li= link_to s_("DownloadCommit|Email Patches"), project_commit_path(@project, @commit, format: :patch), class: "qa-email-patches"
- %li= link_to s_("DownloadCommit|Plain Diff"), project_commit_path(@project, @commit, format: :diff), class: "qa-plain-diff"
+ %li= link_to s_('DownloadCommit|Email Patches'), project_commit_path(@project, @commit, format: :patch), class: "qa-email-patches"
+ %li= link_to s_('DownloadCommit|Plain Diff'), project_commit_path(@project, @commit, format: :diff), class: "qa-plain-diff"
.commit-box{ data: { project_path: project_path(@project) } }
%h3.commit-title
@@ -95,8 +95,5 @@
.well-segment
= icon('info-circle fw')
- This commit is part of merge request
- = succeed '.' do
- = link_to @merge_request.to_reference, diffs_project_merge_request_path(@project, @merge_request, commit_id: @commit.id)
-
- Comments created here will be created in the context of that merge request.
+ - link_to_merge_request = link_to(@merge_request.to_reference, diffs_project_merge_request_path(@project, @merge_request, commit_id: @commit.id))
+ = _('This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request.').html_safe % { link_to_merge_request: link_to_merge_request }
diff --git a/app/views/projects/commit/_limit_exceeded_message.html.haml b/app/views/projects/commit/_limit_exceeded_message.html.haml
index a264f3517c4..7d3c0582d0b 100644
--- a/app/views/projects/commit/_limit_exceeded_message.html.haml
+++ b/app/views/projects/commit/_limit_exceeded_message.html.haml
@@ -1,8 +1,8 @@
-.has-tooltip{ class: "limit-box limit-box-#{objects} prepend-left-5", data: { title: "Project has too many #{label_for_message} to search"} }
+.has-tooltip{ class: "limit-box limit-box-#{objects} prepend-left-5", data: { title: _('Project has too many %{label_for_message} to search') % { label_for_message: label_for_message } } }
.limit-icon
- if objects == :branch
= sprite_icon('fork', size: 12)
- else
= icon('tag')
.limit-message
- %span #{label_for_message.capitalize} unavailable
+ %span= _('%{label_for_message} unavailable') % { label_for_message: label_for_message.capitalize }
diff --git a/app/views/projects/commit/_other_user_signature_badge.html.haml b/app/views/projects/commit/_other_user_signature_badge.html.haml
index d7bf2dc0cb6..bb843bee7c9 100644
--- a/app/views/projects/commit/_other_user_signature_badge.html.haml
+++ b/app/views/projects/commit/_other_user_signature_badge.html.haml
@@ -1,6 +1,6 @@
- title = capture do
- This commit was signed with a different user's verified signature.
+ = _("This commit was signed with a different user's verified signature.")
-- locals = { signature: signature, title: title, label: 'Unverified', css_class: 'invalid', icon: 'status_notfound_borderless', show_user: true }
+- locals = { signature: signature, title: title, label: _('Unverified'), css_class: 'invalid', icon: 'status_notfound_borderless', show_user: true }
= render partial: 'projects/commit/signature_badge', locals: locals
diff --git a/app/views/projects/commit/_same_user_different_email_signature_badge.html.haml b/app/views/projects/commit/_same_user_different_email_signature_badge.html.haml
index 22ffd66ff8e..d282ab4f520 100644
--- a/app/views/projects/commit/_same_user_different_email_signature_badge.html.haml
+++ b/app/views/projects/commit/_same_user_different_email_signature_badge.html.haml
@@ -1,7 +1,6 @@
- title = capture do
- This commit was signed with a verified signature, but the committer email
- is <strong>not verified</strong> to belong to the same user.
+ = _('This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user.').html_safe
-- locals = { signature: signature, title: title, label: 'Unverified', css_class: ['invalid'], icon: 'status_notfound_borderless', show_user: true }
+- locals = { signature: signature, title: title, label: _('Unverified'), css_class: ['invalid'], icon: 'status_notfound_borderless', show_user: true }
= render partial: 'projects/commit/signature_badge', locals: locals
diff --git a/app/views/projects/commit/_signature_badge.html.haml b/app/views/projects/commit/_signature_badge.html.haml
index c4d986ef742..1331fa179fc 100644
--- a/app/views/projects/commit/_signature_badge.html.haml
+++ b/app/views/projects/commit/_signature_badge.html.haml
@@ -19,10 +19,10 @@
.clearfix
= render partial: 'projects/commit/signature_badge_user', locals: { signature: signature }
- GPG Key ID:
+ = _('GPG Key ID:')
%span.monospace= signature.gpg_key_primary_keyid
- = link_to('Learn more about signing commits', help_page_path('user/project/repository/gpg_signed_commits/index.md'), class: 'gpg-popover-help-link')
+ = link_to(_('Learn more about signing commits'), help_page_path('user/project/repository/gpg_signed_commits/index.md'), class: 'gpg-popover-help-link')
%a{ href: 'javascript:void(0)', tabindex: 0, class: css_classes, data: { toggle: 'popover', html: 'true', placement: 'top', title: title, content: content } }
= label
diff --git a/app/views/projects/commit/_unverified_signature_badge.html.haml b/app/views/projects/commit/_unverified_signature_badge.html.haml
index 00e1efe0582..294f916d18f 100644
--- a/app/views/projects/commit/_unverified_signature_badge.html.haml
+++ b/app/views/projects/commit/_unverified_signature_badge.html.haml
@@ -1,6 +1,6 @@
- title = capture do
- This commit was signed with an <strong>unverified</strong> signature.
+ = _('This commit was signed with an <strong>unverified</strong> signature.').html_safe
-- locals = { signature: signature, title: title, label: 'Unverified', css_class: 'invalid', icon: 'status_notfound_borderless' }
+- locals = { signature: signature, title: title, label: _('Unverified'), css_class: 'invalid', icon: 'status_notfound_borderless' }
= render partial: 'projects/commit/signature_badge', locals: locals
diff --git a/app/views/projects/commit/_verified_signature_badge.html.haml b/app/views/projects/commit/_verified_signature_badge.html.haml
index 31408806be7..4964b1b8ee7 100644
--- a/app/views/projects/commit/_verified_signature_badge.html.haml
+++ b/app/views/projects/commit/_verified_signature_badge.html.haml
@@ -1,7 +1,6 @@
- title = capture do
- This commit was signed with a <strong>verified</strong> signature and the
- committer email is verified to belong to the same user.
+ = _('This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user.').html_safe
-- locals = { signature: signature, title: title, label: 'Verified', css_class: 'valid', icon: 'status_success_borderless', show_user: true }
+- locals = { signature: signature, title: title, label: _('Verified'), css_class: 'valid', icon: 'status_success_borderless', show_user: true }
= render partial: 'projects/commit/signature_badge', locals: locals
diff --git a/app/views/projects/commit/pipelines.html.haml b/app/views/projects/commit/pipelines.html.haml
index c66ea873dba..f8c27f4c026 100644
--- a/app/views/projects/commit/pipelines.html.haml
+++ b/app/views/projects/commit/pipelines.html.haml
@@ -1,4 +1,4 @@
-- page_title 'Pipelines', "#{@commit.title} (#{@commit.short_id})", 'Commits'
+- page_title _('Pipelines'), "#{@commit.title} (#{@commit.short_id})", _('Commits')
= render 'commit_box'
= render 'ci_menu'
diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml
index fe9a8ac4182..34226167288 100644
--- a/app/views/projects/commit/show.html.haml
+++ b/app/views/projects/commit/show.html.haml
@@ -1,10 +1,10 @@
- @no_container = true
-- add_to_breadcrumbs "Commits", project_commits_path(@project)
+- add_to_breadcrumbs _('Commits'), project_commits_path(@project)
- breadcrumb_title @commit.short_id
- container_class = !fluid_layout && diff_view == :inline ? 'container-limited' : ''
- limited_container_width = fluid_layout ? '' : 'limit-container-width'
- @content_class = limited_container_width
-- page_title "#{@commit.title} (#{@commit.short_id})", "Commits"
+- page_title "#{@commit.title} (#{@commit.short_id})", _('Commits')
- page_description @commit.description
.container-fluid{ class: [limited_container_width, container_class] }
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index a58f736b5b4..1a489bfa275 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -46,7 +46,7 @@
%h5.prepend-top-0= _("Project avatar")
.form-group
- if @project.avatar?
- .avatar-container.s160.append-bottom-15
+ .avatar-container.rect-avatar.s160.append-bottom-15
= project_icon(@project, alt: '', class: 'avatar project-avatar s160', width: 160, height: 160)
- if @project.avatar_in_git
%p.light
diff --git a/app/views/projects/forks/_fork_button.html.haml b/app/views/projects/forks/_fork_button.html.haml
index a69146513d8..3f0798a898d 100644
--- a/app/views/projects/forks/_fork_button.html.haml
+++ b/app/views/projects/forks/_fork_button.html.haml
@@ -5,7 +5,7 @@
.bordered-box.fork-thumbnail.text-center.prepend-left-default.append-right-default.prepend-top-default.append-bottom-default.forked
= link_to project_path(forked_project) do
- if /no_((\w*)_)*avatar/.match(avatar)
- = group_icon(namespace, class: "avatar s100 identicon")
+ = group_icon(namespace, class: "avatar rect-avatar s100 identicon")
- else
.avatar-container.s100
= image_tag(avatar, class: "avatar s100")
@@ -18,7 +18,7 @@
class: ("disabled has-tooltip" unless can_create_project),
title: (_('You have reached your project limit') unless can_create_project) do
- if /no_((\w*)_)*avatar/.match(avatar)
- = group_icon(namespace, class: "avatar s100 identicon")
+ = group_icon(namespace, class: "avatar rect-avatar s100 identicon")
- else
.avatar-container.s100
= image_tag(avatar, class: "avatar s100")
diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml
index 4917f4b8903..42b6aaa2634 100644
--- a/app/views/projects/issues/_discussion.html.haml
+++ b/app/views/projects/issues/_discussion.html.haml
@@ -5,7 +5,7 @@
= link_to 'Reopen issue', issue_path(@issue, issue: {state_event: :reopen}, format: 'json'), data: {original_text: "Reopen issue", alternative_text: "Comment & reopen issue"}, class: "btn btn-nr btn-reopen btn-comment js-note-target-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
= link_to 'Close issue', issue_path(@issue, issue: {state_event: :close}, format: 'json'), data: {original_text: "Close issue", alternative_text: "Comment & close issue"}, class: "btn btn-nr btn-close btn-comment js-note-target-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
-%section.js-vue-notes-event
+%section.issuable-discussion.js-vue-notes-event
#js-vue-notes{ data: { notes_data: notes_data(@issue).to_json,
noteable_data: serialize_issuable(@issue),
noteable_type: 'Issue',
diff --git a/app/views/projects/issues/_import_export.svg b/app/views/projects/issues/_import_export.svg
deleted file mode 100644
index 53c35d12f57..00000000000
--- a/app/views/projects/issues/_import_export.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 238 111" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><rect id="4" width="82" rx="3" height="28" fill="#fff"/><path id="5" d="m68.926 12.09v-2.41c0-.665-.437-.888-.975-.507l-6.552 4.631c-.542.383-.539.998 0 1.379l6.552 4.631c.542.383.975.154.975-.507v-2.41h4.874c.668 0 1.2-.538 1.2-1.201v-2.406c0-.668-.537-1.201-1.2-1.201h-4.874" fill="#fc8a51"/><path id="6" d="m4 24h74v-20h-74v20m-4-21c0-1.655 1.338-2.996 2.991-2.996h76.02c1.652 0 2.991 1.35 2.991 2.996v22.01c0 1.655-1.338 2.996-2.991 2.996h-76.02c-1.652 0-2.991-1.35-2.991-2.996v-22.01"/><circle id="2" cx="16" cy="14" r="7"/><circle id="0" cx="16" cy="14" r="7"/><mask id="3" width="14" height="14" x="0" y="0" fill="#fff"><use xlink:href="#2"/></mask><mask id="1" width="14" height="14" x="0" y="0" fill="#fff"><use xlink:href="#0"/></mask></defs><g fill="none" fill-rule="evenodd"><rect width="98" height="111" fill="#fff" rx="6"/><path fill="#e5e5e5" fill-rule="nonzero" d="m4 6.01v98.99c0 1.11.897 2.01 2 2.01h85.998c1.105 0 2-.897 2-2.01v-98.99c0-1.11-.897-2.01-2-2.01h-85.998c-1.105 0-2 .897-2 2.01m-4 0c0-3.318 2.685-6.01 6-6.01h85.998c3.314 0 6 2.689 6 6.01v98.99c0 3.318-2.685 6.01-6 6.01h-85.998c-3.314 0-6-2.689-6-6.01v-98.99"/><rect width="76" height="85" x="11" y="12" fill="#f9f9f9" rx="3"/><g transform="translate(37 59)"><use xlink:href="#4"/><path fill="#e5e5e5" fill-rule="nonzero" d="m4 24h74v-20h-74v20m-4-21c0-1.655 1.338-2.996 2.991-2.996h76.02c1.652 0 2.991 1.35 2.991 2.996v22.01c0 1.655-1.338 2.996-2.991 2.996h-76.02c-1.652 0-2.991-1.35-2.991-2.996v-22.01"/><use fill="#fff" stroke="#6b4fbb" stroke-width="8" mask="url(#1)" xlink:href="#0"/><use xlink:href="#5"/></g><g transform="translate(140)"><path fill="#fff" d="m0 4h94v103h-94z"/><path fill="#e5e5e5" fill-rule="nonzero" d="m0 74v30.993c0 3.318 2.687 6.01 6 6.01h85.998c3.316 0 6-2.69 6-6.01v-98.99c0-3.318-2.687-6.01-6-6.01h-85.998c-3.316 0-6 2.69-6 6.01v.993h4v-.993c0-1.11.896-2.01 2-2.01h85.998c1.105 0 2 .897 2 2.01v98.99c0 1.11-.896 2.01-2 2.01h-85.998c-1.105 0-2-.897-2-2.01v-30.993h-4"/><g fill="#f9f9f9"><rect width="82" height="28" x="8" y="12" rx="3"/><rect width="82" height="28" x="8" y="43" rx="3"/></g></g><g fill-rule="nonzero" transform="translate(148 73)"><use fill="#e5e5e5" xlink:href="#6"/><path fill="#6b4fbb" d="m17 17c1.657 0 3-1.343 3-3 0-1.657-1.343-3-3-3-1.657 0-3 1.343-3 3 0 1.657 1.343 3 3 3m0 4c-3.866 0-7-3.134-7-7 0-3.866 3.134-7 7-7 3.866 0 7 3.134 7 7 0 3.866-3.134 7-7 7"/></g><g transform="translate(25 24)"><use xlink:href="#4"/><use fill="#e5e5e5" fill-rule="nonzero" xlink:href="#6"/><use fill="#fff" stroke="#6b4fbb" stroke-width="8" mask="url(#3)" xlink:href="#2"/><use xlink:href="#5"/></g><g transform="translate(107 10)"><use xlink:href="#4"/><use fill="#fc8a51" fill-opacity=".3" fill-rule="nonzero" xlink:href="#6"/><path fill="#6b4fbb" fill-rule="nonzero" d="m16 17c1.657 0 3-1.343 3-3 0-1.657-1.343-3-3-3-1.657 0-3 1.343-3 3 0 1.657 1.343 3 3 3m0 4c-3.866 0-7-3.134-7-7 0-3.866 3.134-7 7-7 3.866 0 7 3.134 7 7 0 3.866-3.134 7-7 7" id="7"/><use xlink:href="#5"/></g><g transform="translate(128 41)"><use xlink:href="#4"/><use fill="#fc8a51" fill-opacity=".3" fill-rule="nonzero" xlink:href="#6"/><use xlink:href="#7"/><path fill="#fc8a51" d="m66.926 12.09v-2.41c0-.665-.437-.888-.975-.507l-6.552 4.631c-.542.383-.539.998 0 1.379l6.552 4.631c.542.383.975.154.975-.507v-2.41h4.874c.668 0 1.2-.538 1.2-1.201v-2.406c0-.668-.537-1.201-1.2-1.201h-4.874"/></g></g></svg> \ No newline at end of file
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index 31c72f2f759..ce7c7091c93 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -36,7 +36,7 @@
= issue.due_date.to_s(:medium)
- if issue.labels.any?
&nbsp;
- - issue.labels.each do |label|
+ - labels_sorted_by_title(issue.labels).each do |label|
= link_to_label(label, subject: issue.project, css_class: 'label-link')
.issuable-meta
diff --git a/app/views/projects/issues/import_csv/_modal.html.haml b/app/views/projects/issues/import_csv/_modal.html.haml
index 5339c4325b9..86bc54786ad 100644
--- a/app/views/projects/issues/import_csv/_modal.html.haml
+++ b/app/views/projects/issues/import_csv/_modal.html.haml
@@ -5,8 +5,8 @@
.modal-header
%h3
= _('Import issues')
- .import-export-svg-container
- = render 'projects/issues/import_export.svg'
+ .svg-content.import-export-svg-container
+ = image_tag 'illustrations/export-import.svg', alt: _('Import/Export illustration'), class: 'illustration'
%a.close{ href: '#', 'data-dismiss' => 'modal' } ×
.modal-body
.modal-text
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 653b7d4c6f3..3a674da6e87 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -14,9 +14,12 @@
.detail-page-header-body
.issuable-status-box.status-box.status-box-issue-closed{ class: issue_button_visibility(@issue, false) }
= sprite_icon('mobile-issue-close', size: 16, css_class: 'd-block d-sm-none')
- %span.d-none.d-sm-block
+ .d-none.d-sm-block
- if @issue.moved?
- = _("Closed (moved)")
+ - moved_link_start = "<a href=\"#{issue_path(@issue.moved_to)}\" class=\"text-white text-underline\">".html_safe
+ - moved_link_end = '</a>'.html_safe
+ = s_('IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})').html_safe % {moved_link_start: moved_link_start,
+ moved_link_end: moved_link_end}
- else
= _("Closed")
.issuable-status-box.status-box.status-box-open{ class: issue_button_visibility(@issue, true) }
@@ -88,7 +91,6 @@
#js-vue-discussion-filter{ data: { default_filter: current_user&.notes_filter_for(@issue), notes_filters: UserPreference.notes_filters.to_json } }
= render 'new_branch' unless @issue.confidential?
- %section.issuable-discussion
- = render 'projects/issues/discussion'
+ = render_if_exists 'projects/issues/discussion'
= render 'shared/issuable/sidebar', issuable_sidebar: @issuable_sidebar, assignees: @issue.assignees
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index ac29cd8f679..90916191d97 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -34,7 +34,7 @@
= merge_request.target_branch
- if merge_request.labels.any?
&nbsp;
- - merge_request.labels.each do |label|
+ - labels_sorted_by_title(merge_request.labels).each do |label|
= link_to_label(label, subject: merge_request.project, type: :merge_request, css_class: 'label-link')
.issuable-meta
diff --git a/app/views/projects/pages/_destroy.haml b/app/views/projects/pages/_destroy.haml
index ae8c801b705..138e2864bad 100644
--- a/app/views/projects/pages/_destroy.haml
+++ b/app/views/projects/pages/_destroy.haml
@@ -9,4 +9,4 @@
.form-actions
= link_to 'Remove pages', project_pages_path(@project), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove"
- else
- .nothing-here-block Only the project owner can remove pages
+ .nothing-here-block Only project maintainers can remove pages
diff --git a/app/views/projects/pages/_https_only.html.haml b/app/views/projects/pages/_https_only.html.haml
index ce3ef29c32e..74478ee011c 100644
--- a/app/views/projects/pages/_https_only.html.haml
+++ b/app/views/projects/pages/_https_only.html.haml
@@ -3,7 +3,7 @@
.form-check
= f.check_box :pages_https_only, class: 'form-check-input', disabled: pages_https_only_disabled?
= f.label :pages_https_only, class: pages_https_only_label_class do
- %strong Force domains with SSL certificates to use HTTPS
+ %strong Force HTTPS (requires valid certificates)
- unless pages_https_only_disabled?
.prepend-top-10
diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml
index 69a47faabed..55adeb345ab 100644
--- a/app/views/projects/pipelines/_info.html.haml
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -23,7 +23,7 @@
- if @pipeline.queued_duration
= "(queued for #{time_interval_in_words(@pipeline.queued_duration)})"
- .well-segment
+ .well-segment.qa-pipeline-badges
.icon-container
= sprite_icon('flag')
- if @pipeline.latest?
@@ -48,7 +48,7 @@
content: "<a class='autodevops-link' href='#{popover_content_url}' target='_blank' rel='noopener noreferrer nofollow'>#{popover_content_text}</a>",
} }
Auto DevOps
- - if @pipeline.merge_request?
+ - if @pipeline.merge_request_event?
%span.js-pipeline-url-mergerequest.badge.badge-info.has-tooltip{ title: "This pipeline is run in a merge request context" }
merge request
- if @pipeline.stuck?
diff --git a/app/views/projects/settings/operations/_error_tracking.html.haml b/app/views/projects/settings/operations/_error_tracking.html.haml
index 4911e8d3770..6b15331db01 100644
--- a/app/views/projects/settings/operations/_error_tracking.html.haml
+++ b/app/views/projects/settings/operations/_error_tracking.html.haml
@@ -8,23 +8,11 @@
= _('Error Tracking')
%p
= _('To link Sentry to GitLab, enter your Sentry URL and Auth Token.')
+ = link_to _('More information'), help_page_path('user/project/operations/error_tracking'), target: '_blank', rel: 'noopener noreferrer'
.settings-content
- = form_for @project, url: project_settings_operations_path(@project), method: :patch do |f|
- = form_errors(@project)
- .form-group
- = f.fields_for :error_tracking_setting_attributes, setting do |form|
- .form-check.form-group
- = form.check_box :enabled, class: 'form-check-input'
- = form.label :enabled, _('Active'), class: 'form-check-label'
- .form-group
- = form.label :api_url, _('Sentry API URL'), class: 'label-bold'
- = form.url_field :api_url, class: 'form-control', placeholder: _('http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/')
- %p.form-text.text-muted
- = _('Enter your Sentry API URL')
- .form-group
- = form.label :token, _('Auth Token'), class: 'label-bold'
- = form.text_field :token, class: 'form-control'
- %p.form-text.text-muted
- = _('Find and manage Auth Tokens in your Sentry account settings page.')
-
- = f.submit _('Save changes'), class: 'btn btn-success'
+ .js-error-tracking-form{ data: { list_projects_endpoint: list_projects_project_error_tracking_index_path(@project, format: :json),
+ operations_settings_endpoint: project_settings_operations_path(@project),
+ project: error_tracking_setting_project_json,
+ api_host: setting.api_host,
+ enabled: setting.enabled.to_json,
+ token: setting.token } }
diff --git a/app/views/projects/settings/operations/show.html.haml b/app/views/projects/settings/operations/show.html.haml
index b36fa9a5f51..2822debe426 100644
--- a/app/views/projects/settings/operations/show.html.haml
+++ b/app/views/projects/settings/operations/show.html.haml
@@ -1,5 +1,6 @@
- @content_class = 'limit-container-width' unless fluid_layout
-- page_title _('Operations')
+- page_title _('Operations Settings')
+- breadcrumb_title _('Operations Settings')
= render 'projects/settings/operations/error_tracking', expanded: true
= render_if_exists 'projects/settings/operations/tracing'
diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml
index a4974d89c1a..7682d01a5a1 100644
--- a/app/views/projects/snippets/index.html.haml
+++ b/app/views/projects/snippets/index.html.haml
@@ -1,12 +1,16 @@
- page_title _("Snippets")
-- if current_user
- .top-area
- - include_private = @project.team.member?(current_user) || current_user.admin?
- = render partial: 'snippets/snippets_scope_menu', locals: { subject: @project, include_private: include_private }
+- if @snippets.exists?
+ - if current_user
+ .top-area
+ - include_private = @project.team.member?(current_user) || current_user.admin?
+ = render partial: 'snippets/snippets_scope_menu', locals: { subject: @project, include_private: include_private }
- .nav-controls
- if can?(current_user, :create_project_snippet, @project)
- = link_to _("New snippet"), new_project_snippet_path(@project), class: "btn btn-success", title: _("New snippet")
+ .nav-controls
+ - if can?(current_user, :create_project_snippet, @project)
+ = link_to _("New snippet"), new_project_snippet_path(@project), class: "btn btn-success", title: _("New snippet")
-= render 'snippets/snippets'
+ = render 'shared/snippets/list'
+- else
+ = render 'shared/empty_states/snippets', button_path: new_namespace_project_snippet_path(@project.namespace, @project)
diff --git a/app/views/projects/snippets/new.html.haml b/app/views/projects/snippets/new.html.haml
index 26b333d4ecf..d64e3a49a81 100644
--- a/app/views/projects/snippets/new.html.haml
+++ b/app/views/projects/snippets/new.html.haml
@@ -1,8 +1,8 @@
- add_to_breadcrumbs _("Snippets"), project_snippets_path(@project)
- breadcrumb_title _("New")
-- page_title _("New Snippets")
+- page_title _("New Snippet")
%h3.page-title
- = _('New Snippet')
+ = _("New Snippet")
%hr
= render "shared/snippets/form", url: project_snippets_path(@project, @snippet)
diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml
index feeaf799f51..8ee1407d9d9 100644
--- a/app/views/projects/tags/show.html.haml
+++ b/app/views/projects/tags/show.html.haml
@@ -23,7 +23,7 @@
= link_to edit_project_tag_release_path(@project, @tag.name), class: 'btn btn-edit controls-item has-tooltip', title: s_('TagsPage|Edit release notes') do
= icon("pencil")
= link_to project_tree_path(@project, @tag.name), class: 'btn controls-item has-tooltip', title: s_('TagsPage|Browse files') do
- = icon('files-o')
+ = sprite_icon('folder-open')
= link_to project_commits_path(@project, @tag.name), class: 'btn controls-item has-tooltip', title: s_('TagsPage|Browse commits') do
= icon('history')
.btn-container.controls-item
diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml
index 26671a7b7d2..1277ea6c743 100644
--- a/app/views/projects/wikis/edit.html.haml
+++ b/app/views/projects/wikis/edit.html.haml
@@ -23,9 +23,6 @@
= s_("Wiki|Create Page")
.nav-controls
- - if can?(current_user, :create_wiki, @project)
- = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-success", "data-toggle" => "modal" do
- = s_("Wiki|New page")
- if @page.persisted?
= link_to project_wiki_history_path(@project, @page), class: "btn" do
= s_("Wiki|Page history")
diff --git a/app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml b/app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml
index 422be28737c..755fd3a17d3 100644
--- a/app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml
+++ b/app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml
@@ -1,5 +1,5 @@
- if show_auto_devops_implicitly_enabled_banner?(project, current_user)
- .auto-devops-implicitly-enabled-banner.alert.alert-warning
+ .qa-auto-devops-banner.auto-devops-implicitly-enabled-banner.alert.alert-warning
- more_information_link = link_to _('More information'), help_page_path('topics/autodevops/index.md'), target: '_blank', 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
diff --git a/app/views/shared/_issuable_meta_data.html.haml b/app/views/shared/_issuable_meta_data.html.haml
index 6cc8c485666..31a5370a5f8 100644
--- a/app/views/shared/_issuable_meta_data.html.haml
+++ b/app/views/shared/_issuable_meta_data.html.haml
@@ -1,4 +1,4 @@
-- note_count = @issuable_meta_data[issuable.id].notes_count
+- note_count = @issuable_meta_data[issuable.id].user_notes_count
- issue_votes = @issuable_meta_data[issuable.id]
- upvotes, downvotes = issue_votes.upvotes, issue_votes.downvotes
- issuable_url = @collection_type == "Issue" ? issue_path(issuable, anchor: 'notes') : merge_request_path(issuable, anchor: 'notes')
diff --git a/app/views/shared/deploy_keys/_form.html.haml b/app/views/shared/deploy_keys/_form.html.haml
index 913c065e188..eb7808573b9 100644
--- a/app/views/shared/deploy_keys/_form.html.haml
+++ b/app/views/shared/deploy_keys/_form.html.haml
@@ -15,7 +15,7 @@
%p.light
Paste a machine public key here. Read more about how to generate it
= link_to 'here', help_page_path('ssh/README')
- = form.text_area :key, class: 'form-control thin_area', rows: 5
+ = form.text_area :key, class: 'form-control thin-area', rows: 5
- else
= form.label :fingerprint, class: 'col-form-label col-sm-2'
.col-sm-10
diff --git a/app/views/shared/empty_states/_priority_labels.html.haml b/app/views/shared/empty_states/_priority_labels.html.haml
index 555cb4f4af9..bba3475d244 100644
--- a/app/views/shared/empty_states/_priority_labels.html.haml
+++ b/app/views/shared/empty_states/_priority_labels.html.haml
@@ -1,4 +1,4 @@
.text-center
- .svg-content
+ .svg-content.qa-label-svg
= image_tag 'illustrations/priority_labels.svg'
%p Star labels to start sorting by priority
diff --git a/app/views/shared/empty_states/_snippets.html.haml b/app/views/shared/empty_states/_snippets.html.haml
new file mode 100644
index 00000000000..a1a16b9d067
--- /dev/null
+++ b/app/views/shared/empty_states/_snippets.html.haml
@@ -0,0 +1,20 @@
+- button_path = local_assigns.fetch(:button_path, false)
+
+.row.empty-state
+ .col-12
+ .svg-content
+ = image_tag 'illustrations/snippets_empty.svg'
+ .text-content
+ - if current_user
+ %h4
+ = s_('SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep.')
+ %p
+ = s_('SnippetsEmptyState|They can be either public or private.')
+ .text-center
+ = link_to s_('SnippetsEmptyState|New snippet'), button_path, class: 'btn btn-success', title: s_('SnippetsEmptyState|New snippet'), id: 'new_snippet_link'
+ - unless current_page?(dashboard_snippets_path)
+ = link_to s_('SnippetsEmptyState|Explore public snippets'), explore_snippets_path, class: 'btn btn-default', title: s_('SnippetsEmptyState|Explore public snippets')
+ - else
+ %h4.text-center= s_('SnippetsEmptyState|There are no snippets to show.')
+
+
diff --git a/app/views/shared/groups/_group.html.haml b/app/views/shared/groups/_group.html.haml
index a1b901aaffa..609b8dce21a 100644
--- a/app/views/shared/groups/_group.html.haml
+++ b/app/views/shared/groups/_group.html.haml
@@ -14,7 +14,7 @@
%span.visibility-icon.has-tooltip{ data: { container: 'body', placement: 'left' }, title: visibility_icon_description(group) }
= visibility_level_icon(group.visibility_level, fw: false)
- .avatar-container.s40
+ .avatar-container.rect-avatar.s40
= link_to group do
= group_icon(group, class: "avatar s40 d-none d-sm-block")
.title
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index 588659c7e9c..bdba47ed14d 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -128,6 +128,14 @@
%li.filter-dropdown-item{ data: { value: 'no', capitalize: true } }
%button.btn.btn-link{ type: 'button' }
= _('No')
+ #js-dropdown-confidential.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul.filter-dropdown{ data: { dropdown: true } }
+ %li.filter-dropdown-item{ data: { value: 'yes', capitalize: true } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('Yes')
+ %li.filter-dropdown-item{ data: { value: 'no', capitalize: true } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('No')
= render_if_exists 'shared/issuable/filter_weight', type: type
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 0520eda37a4..9596c1df20e 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -133,7 +133,7 @@
#js-confidential-entry-point
-# haml-lint:disable InlineJavaScript
- %script#js-lock-issue-data{ type: "application/json" }= { is_locked: issuable_sidebar[:discussion_locked], is_editable: can_edit_issuable }.to_json.html_safe
+ %script#js-lock-issue-data{ type: "application/json" }= { is_locked: !!issuable_sidebar[:discussion_locked], is_editable: can_edit_issuable }.to_json.html_safe
#js-lock-entry-point
.js-sidebar-participants-entry-point
diff --git a/app/views/shared/issuable/_sort_dropdown.html.haml b/app/views/shared/issuable/_sort_dropdown.html.haml
index b6ea9185b10..967f31c8325 100644
--- a/app/views/shared/issuable/_sort_dropdown.html.haml
+++ b/app/views/shared/issuable/_sort_dropdown.html.haml
@@ -5,7 +5,7 @@
.dropdown.inline.prepend-left-10.issue-sort-dropdown
.btn-group{ role: 'group' }
.btn-group{ role: 'group' }
- %button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' }, class: 'btn btn-default' }
+ %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' }, class: 'btn btn-default' }
= sort_title
= icon('chevron-down')
%ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index df17ae95e2a..f1a87faa7ac 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -13,14 +13,15 @@
- cache_key = project_list_cache_key(project)
- updated_tooltip = time_ago_with_tooltip(project.last_activity_date)
- css_controls_class = compact_mode ? "" : "flex-lg-row justify-content-lg-between"
+- avatar_container_class = project.creator && use_creator_avatar ? '' : 'rect-avatar'
%li.project-row.d-flex{ class: css_class }
= cache(cache_key) do
- if avatar
- .avatar-container.s48.flex-grow-0.flex-shrink-0
+ .avatar-container.s48.flex-grow-0.flex-shrink-0{ class: avatar_container_class }
= link_to project_path(project), class: dom_class(project) do
- if project.creator && use_creator_avatar
- = image_tag avatar_icon_for_user(project.creator, 48), class: "avatar s65", alt:''
+ = image_tag avatar_icon_for_user(project.creator, 48), class: "avatar s48", alt:''
- else
= project_icon(project, alt: '', class: 'avatar project-avatar s48', width: 48, height: 48)
.project-details.d-sm-flex.flex-sm-fill.align-items-center
diff --git a/app/views/shared/snippets/_list.html.haml b/app/views/shared/snippets/_list.html.haml
new file mode 100644
index 00000000000..5d2152eb411
--- /dev/null
+++ b/app/views/shared/snippets/_list.html.haml
@@ -0,0 +1,12 @@
+- remote = local_assigns.fetch(:remote, false)
+- link_project = local_assigns.fetch(:link_project, false)
+
+- if @snippets.exists?
+ .snippets-list-holder
+ %ul.content-list
+ = render partial: 'shared/snippets/snippet', collection: @snippets, locals: { link_project: link_project }
+
+ = paginate @snippets, theme: 'gitlab', remote: remote
+
+- else
+ .nothing-here-block= s_("SnippetsEmptyState|No snippets found")
diff --git a/app/views/snippets/index.html.haml b/app/views/snippets/index.html.haml
index 4f418e2381f..f5fe75bed5a 100644
--- a/app/views/snippets/index.html.haml
+++ b/app/views/snippets/index.html.haml
@@ -10,4 +10,4 @@
= link_to user_path(@user) do
= _("%{user_name} profile page") % { user_name: @user.name }
-= render 'snippets'
+= render 'shared/snippets/list'
diff --git a/app/views/users/_groups.html.haml b/app/views/users/_groups.html.haml
index 55799e10a46..6d7a52c7688 100644
--- a/app/views/users/_groups.html.haml
+++ b/app/views/users/_groups.html.haml
@@ -1,5 +1,5 @@
.clearfix
- groups.each do |group|
= link_to group, class: 'profile-groups-avatars inline', title: group.name do
- .avatar-container.s40
+ .avatar-container.rect-avatar.s40
= group_icon(group, class: 'avatar group-avatar s40')
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index 410411b1294..d86f654dd44 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -47,6 +47,9 @@
- github_importer:github_import_stage_import_repository
- hashed_storage:hashed_storage_migrator
+- hashed_storage:hashed_storage_rollbacker
+- hashed_storage:hashed_storage_project_migrate
+- hashed_storage:hashed_storage_project_rollback
- mail_scheduler:mail_scheduler_issue_due
- mail_scheduler:mail_scheduler_notification_service
@@ -101,6 +104,7 @@
- authorized_projects
- background_migration
+- chat_notification
- create_gpg_signature
- delete_merged_branches
- delete_user
@@ -125,7 +129,6 @@
- project_cache
- project_destroy
- project_export
-- project_migrate_hashed_storage
- project_service
- propagate_service_template
- reactive_caching
@@ -145,3 +148,4 @@
- repository_cleanup
- delete_stored_files
- import_issues_csv
+- project_daily_statistics
diff --git a/app/workers/build_finished_worker.rb b/app/workers/build_finished_worker.rb
index ae853ec9316..adc38226405 100644
--- a/app/workers/build_finished_worker.rb
+++ b/app/workers/build_finished_worker.rb
@@ -30,5 +30,6 @@ class BuildFinishedWorker
# We execute these async as these are independent operations.
BuildHooksWorker.perform_async(build.id)
ArchiveTraceWorker.perform_async(build.id)
+ ChatNotificationWorker.perform_async(build.id) if build.pipeline.chat?
end
end
diff --git a/app/workers/chat_notification_worker.rb b/app/workers/chat_notification_worker.rb
new file mode 100644
index 00000000000..25a306e94d8
--- /dev/null
+++ b/app/workers/chat_notification_worker.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+class ChatNotificationWorker
+ include ApplicationWorker
+
+ RESCHEDULE_INTERVAL = 2.seconds
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def perform(build_id)
+ Ci::Build.find_by(id: build_id).try do |build|
+ send_response(build)
+ end
+ rescue Gitlab::Chat::Output::MissingBuildSectionError
+ # The creation of traces and sections appears to be eventually consistent.
+ # As a result it's possible for us to run the above code before the trace
+ # sections are present. To better handle such cases we'll just reschedule
+ # the job instead of producing an error.
+ self.class.perform_in(RESCHEDULE_INTERVAL, build_id)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ def send_response(build)
+ Gitlab::Chat::Responder.responder_for(build).try do |responder|
+ if build.success?
+ output = Gitlab::Chat::Output.new(build)
+
+ responder.success(output.to_s)
+ else
+ responder.failure
+ end
+ end
+ end
+end
diff --git a/app/workers/expire_pipeline_cache_worker.rb b/app/workers/expire_pipeline_cache_worker.rb
index 148384600b6..2c070d482a1 100644
--- a/app/workers/expire_pipeline_cache_worker.rb
+++ b/app/workers/expire_pipeline_cache_worker.rb
@@ -37,9 +37,9 @@ class ExpirePipelineCacheWorker
Gitlab::Routing.url_helpers.project_new_merge_request_path(project, format: :json)
end
- def each_pipelines_merge_request_path(project, pipeline)
+ def each_pipelines_merge_request_path(pipeline)
pipeline.all_merge_requests.each do |merge_request|
- path = Gitlab::Routing.url_helpers.pipelines_project_merge_request_path(project, merge_request, format: :json)
+ path = Gitlab::Routing.url_helpers.pipelines_project_merge_request_path(merge_request.target_project, merge_request, format: :json)
yield(path)
end
@@ -59,7 +59,7 @@ class ExpirePipelineCacheWorker
store.touch(project_pipeline_path(project, pipeline))
store.touch(commit_pipelines_path(project, pipeline.commit)) unless pipeline.commit.nil?
store.touch(new_merge_request_pipelines_path(project))
- each_pipelines_merge_request_path(project, pipeline) do |path|
+ each_pipelines_merge_request_path(pipeline) do |path|
store.touch(path)
end
end
diff --git a/app/workers/hashed_storage/base_worker.rb b/app/workers/hashed_storage/base_worker.rb
new file mode 100644
index 00000000000..816e0504db6
--- /dev/null
+++ b/app/workers/hashed_storage/base_worker.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module HashedStorage
+ class BaseWorker
+ include ExclusiveLeaseGuard
+
+ LEASE_TIMEOUT = 30.seconds.to_i
+ LEASE_KEY_SEGMENT = 'project_migrate_hashed_storage_worker'.freeze
+
+ protected
+
+ def lease_key
+ # we share the same lease key for both migration and rollback so they don't run simultaneously
+ "#{LEASE_KEY_SEGMENT}:#{project_id}"
+ end
+
+ def lease_timeout
+ LEASE_TIMEOUT
+ end
+ end
+end
diff --git a/app/workers/hashed_storage/project_migrate_worker.rb b/app/workers/hashed_storage/project_migrate_worker.rb
new file mode 100644
index 00000000000..f00a459a097
--- /dev/null
+++ b/app/workers/hashed_storage/project_migrate_worker.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module HashedStorage
+ class ProjectMigrateWorker < BaseWorker
+ include ApplicationWorker
+
+ queue_namespace :hashed_storage
+
+ attr_reader :project_id
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def perform(project_id, old_disk_path = nil)
+ @project_id = project_id # we need to set this in order to create the lease_key
+
+ try_obtain_lease do
+ project = Project.without_deleted.find_by(id: project_id)
+ break unless project
+
+ old_disk_path ||= project.disk_path
+
+ ::Projects::HashedStorage::MigrationService.new(project, old_disk_path, logger: logger).execute
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+ end
+end
diff --git a/app/workers/hashed_storage/project_rollback_worker.rb b/app/workers/hashed_storage/project_rollback_worker.rb
new file mode 100644
index 00000000000..55e1d7ab23e
--- /dev/null
+++ b/app/workers/hashed_storage/project_rollback_worker.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module HashedStorage
+ class ProjectRollbackWorker < BaseWorker
+ include ApplicationWorker
+
+ queue_namespace :hashed_storage
+
+ attr_reader :project_id
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def perform(project_id, old_disk_path = nil)
+ @project_id = project_id # we need to set this in order to create the lease_key
+
+ try_obtain_lease do
+ project = Project.without_deleted.find_by(id: project_id)
+ break unless project
+
+ old_disk_path ||= project.disk_path
+
+ ::Projects::HashedStorage::RollbackService.new(project, old_disk_path, logger: logger).execute
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+ end
+end
diff --git a/app/workers/hashed_storage/rollbacker_worker.rb b/app/workers/hashed_storage/rollbacker_worker.rb
new file mode 100644
index 00000000000..a4da8443787
--- /dev/null
+++ b/app/workers/hashed_storage/rollbacker_worker.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module HashedStorage
+ class RollbackerWorker
+ include ApplicationWorker
+
+ queue_namespace :hashed_storage
+
+ # @param [Integer] start initial ID of the batch
+ # @param [Integer] finish last ID of the batch
+ def perform(start, finish)
+ migrator = Gitlab::HashedStorage::Migrator.new
+ migrator.bulk_rollback(start: start, finish: finish)
+ end
+ end
+end
diff --git a/app/workers/project_daily_statistics_worker.rb b/app/workers/project_daily_statistics_worker.rb
new file mode 100644
index 00000000000..101f5c28459
--- /dev/null
+++ b/app/workers/project_daily_statistics_worker.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class ProjectDailyStatisticsWorker
+ include ApplicationWorker
+
+ def perform(project_id)
+ project = Project.find_by_id(project_id)
+
+ return unless project&.repository&.exists?
+
+ Projects::FetchStatisticsIncrementService.new(project).execute
+ end
+end
diff --git a/app/workers/project_migrate_hashed_storage_worker.rb b/app/workers/project_migrate_hashed_storage_worker.rb
deleted file mode 100644
index 1c8f313e6e9..00000000000
--- a/app/workers/project_migrate_hashed_storage_worker.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-# frozen_string_literal: true
-
-class ProjectMigrateHashedStorageWorker
- include ApplicationWorker
-
- LEASE_TIMEOUT = 30.seconds.to_i
- LEASE_KEY_SEGMENT = 'project_migrate_hashed_storage_worker'.freeze
-
- # rubocop: disable CodeReuse/ActiveRecord
- def perform(project_id, old_disk_path = nil)
- uuid = lease_for(project_id).try_obtain
-
- if uuid
- project = Project.find_by(id: project_id)
- return if project.nil? || project.pending_delete?
-
- old_disk_path ||= project.disk_path
-
- ::Projects::HashedStorage::MigrationService.new(project, old_disk_path, logger: logger).execute
- else
- return false
- end
-
- ensure
- cancel_lease_for(project_id, uuid) if uuid
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- def lease_for(project_id)
- Gitlab::ExclusiveLease.new(lease_key(project_id), timeout: LEASE_TIMEOUT)
- end
-
- private
-
- def lease_key(project_id)
- # we share the same lease key for both migration and rollback so they don't run simultaneously
- "#{LEASE_KEY_SEGMENT}:#{project_id}"
- end
-
- def cancel_lease_for(project_id, uuid)
- Gitlab::ExclusiveLease.cancel(lease_key(project_id), uuid)
- end
-end
diff --git a/app/workers/reactive_caching_worker.rb b/app/workers/reactive_caching_worker.rb
index 7c66ac046ea..9ec8bcca4f3 100644
--- a/app/workers/reactive_caching_worker.rb
+++ b/app/workers/reactive_caching_worker.rb
@@ -6,7 +6,7 @@ class ReactiveCachingWorker
# rubocop: disable CodeReuse/ActiveRecord
def perform(class_name, id, *args)
klass = begin
- Kernel.const_get(class_name)
+ class_name.constantize
rescue NameError
nil
end
diff --git a/.babelrc.js b/babel.config.js
index 1b05a67354e..e3db4dcbc9a 100644
--- a/.babelrc.js
+++ b/babel.config.js
@@ -1,3 +1,5 @@
+/* eslint-disable import/no-commonjs, filenames/match-regex */
+
const BABEL_ENV = process.env.BABEL_ENV || process.env.NODE_ENV || null;
const presets = [
@@ -38,8 +40,7 @@ if (BABEL_ENV === 'karma' || BABEL_ENV === 'coverage') {
// Jest is running in node environment
if (BABEL_ENV === 'jest') {
- plugins.push('transform-es2015-modules-commonjs');
- plugins.push('dynamic-import-node');
+ plugins.push('@babel/plugin-transform-modules-commonjs');
}
module.exports = { presets, plugins };
diff --git a/bin/background_jobs b/bin/background_jobs
index f28e2f722dc..9d12422b81a 100755
--- a/bin/background_jobs
+++ b/bin/background_jobs
@@ -38,7 +38,14 @@ start_no_deamonize()
start_sidekiq()
{
- exec bundle exec sidekiq -C "${sidekiq_config}" -e $RAILS_ENV -P $sidekiq_pidfile "$@"
+ cmd="exec"
+ chpst=$(which chpst)
+
+ if [ -n "$chpst" ]; then
+ cmd="${cmd} ${chpst} -P"
+ fi
+
+ ${cmd} bundle exec sidekiq -C "${sidekiq_config}" -e $RAILS_ENV -P $sidekiq_pidfile "$@"
}
load_ok()
diff --git a/bin/secpick b/bin/secpick
index 8f956d300a7..d01304285b6 100755
--- a/bin/secpick
+++ b/bin/secpick
@@ -45,9 +45,7 @@ module Secpick
def git_commands
["git fetch #{@options[:remote]} #{stable_branch}",
- "git checkout #{stable_branch}",
- "git pull #{@options[:remote]} #{stable_branch}",
- "git checkout -B #{source_branch}",
+ "git checkout -B #{source_branch} #{@options[:remote]}/#{stable_branch}",
"git cherry-pick #{@options[:sha]}",
"git push #{@options[:remote]} #{source_branch}",
"git checkout #{original_branch}"]
@@ -55,10 +53,10 @@ module Secpick
def gitlab_params
{
+ issuable_template: 'Security Release',
merge_request: {
source_branch: source_branch,
- target_branch: stable_branch,
- description: '/label ~security'
+ target_branch: stable_branch
}
}
end
diff --git a/changelogs/README.md b/changelogs/README.md
new file mode 100644
index 00000000000..c4113ccb863
--- /dev/null
+++ b/changelogs/README.md
@@ -0,0 +1,10 @@
+# Generating changelog entries
+
+To generate and validate your changelog entries:
+
+1. Run `bin/changelog` to generate.
+1. Run `scripts/lint-changelog-yaml` to validate.
+
+See [development/changelog] documentation for detailed usage.
+
+[development/changelog]: https://docs.gitlab.com/ee/development/changelog.html
diff --git a/changelogs/unreleased/10097-number-utils.yml b/changelogs/unreleased/10097-number-utils.yml
new file mode 100644
index 00000000000..417008f6539
--- /dev/null
+++ b/changelogs/unreleased/10097-number-utils.yml
@@ -0,0 +1,5 @@
+---
+title: Moves EE util into the CE file
+merge_request: 25680
+author:
+type: other
diff --git a/changelogs/unreleased/13784-simple-masking-of-protected-variables-in-logs.yml b/changelogs/unreleased/13784-simple-masking-of-protected-variables-in-logs.yml
new file mode 100644
index 00000000000..5c3b6833235
--- /dev/null
+++ b/changelogs/unreleased/13784-simple-masking-of-protected-variables-in-logs.yml
@@ -0,0 +1,5 @@
+---
+title: Add support for masking CI variables.
+merge_request: 25293
+author:
+type: added
diff --git a/changelogs/unreleased/19745-forms-with-task-lists-can-be-overwritten-when-editing-simultaneously.yml b/changelogs/unreleased/19745-forms-with-task-lists-can-be-overwritten-when-editing-simultaneously.yml
deleted file mode 100644
index b1177e1717e..00000000000
--- a/changelogs/unreleased/19745-forms-with-task-lists-can-be-overwritten-when-editing-simultaneously.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Increase reliability and performance of toggling task items
-merge_request: 23938
-author:
-type: fixed
diff --git a/changelogs/unreleased/20084-update-the-spinner-component.yml b/changelogs/unreleased/20084-update-the-spinner-component.yml
new file mode 100644
index 00000000000..c93648e4f54
--- /dev/null
+++ b/changelogs/unreleased/20084-update-the-spinner-component.yml
@@ -0,0 +1,5 @@
+---
+title: Add a spinner icon which is rendered using pure css
+merge_request: 25186
+author:
+type: changed
diff --git a/changelogs/unreleased/2105-add-setting-for-first-day-of-the-week.yml b/changelogs/unreleased/2105-add-setting-for-first-day-of-the-week.yml
deleted file mode 100644
index f4a52b1aacd..00000000000
--- a/changelogs/unreleased/2105-add-setting-for-first-day-of-the-week.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add setting for first day of the week
-merge_request: 22755
-author: Fabian Schneider @fabsrc
-type: added
diff --git a/changelogs/unreleased/24642-activity_service_optimization.yml b/changelogs/unreleased/24642-activity_service_optimization.yml
new file mode 100644
index 00000000000..bdfa769959e
--- /dev/null
+++ b/changelogs/unreleased/24642-activity_service_optimization.yml
@@ -0,0 +1,5 @@
+---
+title: Optimize Redis usage in User::ActivityService
+merge_request: 25005
+author:
+type: performance
diff --git a/changelogs/unreleased/24680-support-bamboo-api-polymorphism.yml b/changelogs/unreleased/24680-support-bamboo-api-polymorphism.yml
deleted file mode 100644
index 5117195cd0c..00000000000
--- a/changelogs/unreleased/24680-support-bamboo-api-polymorphism.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "Support bamboo api polymorphism"
-merge_request: 24680
-author: Alex Lossent
-type: fixed \ No newline at end of file
diff --git a/changelogs/unreleased/24875-label.yml b/changelogs/unreleased/24875-label.yml
deleted file mode 100644
index 1f9d2222edf..00000000000
--- a/changelogs/unreleased/24875-label.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Append prioritized label before pagination
-merge_request: 24815
-author:
-type: fixed
diff --git a/changelogs/unreleased/25043-empty-states.yml b/changelogs/unreleased/25043-empty-states.yml
deleted file mode 100644
index 529a8b3206f..00000000000
--- a/changelogs/unreleased/25043-empty-states.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Make issuable empty states actionable
-merge_request: 24077
-author:
-type: changed
diff --git a/changelogs/unreleased/25569-changing-wording-to-delete-when-referring-to-removing-a-branch.yml b/changelogs/unreleased/25569-changing-wording-to-delete-when-referring-to-removing-a-branch.yml
deleted file mode 100644
index 02a667073ca..00000000000
--- a/changelogs/unreleased/25569-changing-wording-to-delete-when-referring-to-removing-a-branch.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use delete instead of remove when referring to `git branch -D`
-merge_request: !23966
-author:
-type: changed
diff --git a/changelogs/unreleased/26375-markdown-footnotes-not-working.yml b/changelogs/unreleased/26375-markdown-footnotes-not-working.yml
deleted file mode 100644
index 86adef84a2a..00000000000
--- a/changelogs/unreleased/26375-markdown-footnotes-not-working.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Footnotes now render properly in markdown
-merge_request: 24168
-author:
-type: fixed
diff --git a/changelogs/unreleased/28500-empty-states-for-profile-page.yml b/changelogs/unreleased/28500-empty-states-for-profile-page.yml
deleted file mode 100644
index 53f840521ae..00000000000
--- a/changelogs/unreleased/28500-empty-states-for-profile-page.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Refresh empty states for profile page tabs
-merge_request: 24549
-author:
-type: changed
diff --git a/changelogs/unreleased/30120-add-flat-square-badge-style.yml b/changelogs/unreleased/30120-add-flat-square-badge-style.yml
deleted file mode 100644
index a542a58d3fc..00000000000
--- a/changelogs/unreleased/30120-add-flat-square-badge-style.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add flat-square badge style
-merge_request: 24172
-author: Fabian Schneider @fabsrc
-type: added
diff --git a/changelogs/unreleased/34555-empty-state-for-starred-projects.yml b/changelogs/unreleased/34555-empty-state-for-starred-projects.yml
new file mode 100644
index 00000000000..926d3a2eecf
--- /dev/null
+++ b/changelogs/unreleased/34555-empty-state-for-starred-projects.yml
@@ -0,0 +1,5 @@
+---
+title: Improve empty state for starred projects
+merge_request: 25138
+author:
+type: changed
diff --git a/changelogs/unreleased/35638-move-language-setting-to-preferences.yml b/changelogs/unreleased/35638-move-language-setting-to-preferences.yml
new file mode 100644
index 00000000000..d8658218676
--- /dev/null
+++ b/changelogs/unreleased/35638-move-language-setting-to-preferences.yml
@@ -0,0 +1,5 @@
+---
+title: Move language setting to preferences
+merge_request: 25427
+author: Fabian Schneider @fabsrc
+type: changed
diff --git a/changelogs/unreleased/36445-better-indication-that-an-issue-has-been-moved-or-marked-as-duplicated.yml b/changelogs/unreleased/36445-better-indication-that-an-issue-has-been-moved-or-marked-as-duplicated.yml
deleted file mode 100644
index 70b561ccbf6..00000000000
--- a/changelogs/unreleased/36445-better-indication-that-an-issue-has-been-moved-or-marked-as-duplicated.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Indicate on Issue Status if an Issue was Moved
-merge_request: 24470
-author:
-type: added
diff --git a/changelogs/unreleased/37990-task-list-bracket.yml b/changelogs/unreleased/37990-task-list-bracket.yml
deleted file mode 100644
index ffa77cf0af7..00000000000
--- a/changelogs/unreleased/37990-task-list-bracket.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix ambiguous brackets in task lists
-merge_request: 18514
-author: Jared Deckard <jared.deckard@gmail.com>
-type: fixed
diff --git a/changelogs/unreleased/39010-add-left-margin-to-1st-time-contributor-badge.yml b/changelogs/unreleased/39010-add-left-margin-to-1st-time-contributor-badge.yml
new file mode 100644
index 00000000000..758b97deb3b
--- /dev/null
+++ b/changelogs/unreleased/39010-add-left-margin-to-1st-time-contributor-badge.yml
@@ -0,0 +1,5 @@
+---
+title: Add left margin to 1st time contributor badge
+merge_request: 25216
+author: Gokhan Apaydin
+type: fixed
diff --git a/changelogs/unreleased/39676-wiki-api-problems-on-update-parameters-and-500-error.yml b/changelogs/unreleased/39676-wiki-api-problems-on-update-parameters-and-500-error.yml
new file mode 100644
index 00000000000..1af49fb6a2c
--- /dev/null
+++ b/changelogs/unreleased/39676-wiki-api-problems-on-update-parameters-and-500-error.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: Require only one parameter when updating a wiki'
+merge_request: 25191
+author: Robert Schilling
+type: fixed
diff --git a/changelogs/unreleased/40396-use-pgroups-for-background-jobs.yml b/changelogs/unreleased/40396-use-pgroups-for-background-jobs.yml
new file mode 100644
index 00000000000..578c780e1a1
--- /dev/null
+++ b/changelogs/unreleased/40396-use-pgroups-for-background-jobs.yml
@@ -0,0 +1,5 @@
+---
+title: 'If chpst is available, make fron-source installations run sidekiq as a process group leader'
+merge_request: 25654
+author:
+type: other
diff --git a/changelogs/unreleased/40795-set-project-name-on-fork-api.yml b/changelogs/unreleased/40795-set-project-name-on-fork-api.yml
new file mode 100644
index 00000000000..742184bbe1e
--- /dev/null
+++ b/changelogs/unreleased/40795-set-project-name-on-fork-api.yml
@@ -0,0 +1,5 @@
+---
+title: Add ability to set path and name for project on fork using API
+merge_request: 25363
+author:
+type: added
diff --git a/changelogs/unreleased/40997-gitlab-pages-deploy-jobs-have-a-null-status.yml b/changelogs/unreleased/40997-gitlab-pages-deploy-jobs-have-a-null-status.yml
deleted file mode 100644
index 01036253151..00000000000
--- a/changelogs/unreleased/40997-gitlab-pages-deploy-jobs-have-a-null-status.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix empty labels of CI builds for gitlab-pages on pipeline page
-merge_request: 24451
-author:
-type: fixed
diff --git a/changelogs/unreleased/42086-project-fetch-statistics-api-http-only.yml b/changelogs/unreleased/42086-project-fetch-statistics-api-http-only.yml
new file mode 100644
index 00000000000..f2c4f88b746
--- /dev/null
+++ b/changelogs/unreleased/42086-project-fetch-statistics-api-http-only.yml
@@ -0,0 +1,5 @@
+---
+title: Add project fetch statistics
+merge_request: 23596
+author: Jacopo Beschi @jacopo-beschi
+type: added
diff --git a/changelogs/unreleased/42769-remove-expansion-hover-animation-from-status-icon-buttons.yml b/changelogs/unreleased/42769-remove-expansion-hover-animation-from-status-icon-buttons.yml
deleted file mode 100644
index 5a4ff8b3358..00000000000
--- a/changelogs/unreleased/42769-remove-expansion-hover-animation-from-status-icon-buttons.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove expansion hover animation from pipeline status icon buttons
-merge_request: 24268
-author: Nathan Friend
-type: changed
diff --git a/changelogs/unreleased/43681-display-last-activity-and-created-at-datetimes-for-users-in-admin-users.yml b/changelogs/unreleased/43681-display-last-activity-and-created-at-datetimes-for-users-in-admin-users.yml
deleted file mode 100644
index 0fbf6314a27..00000000000
--- a/changelogs/unreleased/43681-display-last-activity-and-created-at-datetimes-for-users-in-admin-users.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Display last activity and created at datetimes for users
-merge_request: 24181
-author:
-type: added
diff --git a/changelogs/unreleased/44332-add-openid-profile-scopes.yml b/changelogs/unreleased/44332-add-openid-profile-scopes.yml
deleted file mode 100644
index b554fab5139..00000000000
--- a/changelogs/unreleased/44332-add-openid-profile-scopes.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: GitLab now supports the profile and email scopes from OpenID Connect
-merge_request: 24335
-author: Goten Xiao
-type: added
diff --git a/changelogs/unreleased/44698-recaptcha.yml b/changelogs/unreleased/44698-recaptcha.yml
deleted file mode 100644
index e1760a6c635..00000000000
--- a/changelogs/unreleased/44698-recaptcha.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Prevent unload when Recaptcha is open
-merge_request: 24625
-author:
-type: fixed
diff --git a/changelogs/unreleased/44740-api-to-verify-a-given-user-has-right-to-merge-a-given-mergerequest.yml b/changelogs/unreleased/44740-api-to-verify-a-given-user-has-right-to-merge-a-given-mergerequest.yml
new file mode 100644
index 00000000000..1c739130fcc
--- /dev/null
+++ b/changelogs/unreleased/44740-api-to-verify-a-given-user-has-right-to-merge-a-given-mergerequest.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: Expose if the current user can merge a MR'
+merge_request: 25207
+author: Robert Schilling
+type: added
diff --git a/changelogs/unreleased/45779-fix-default-visibility-level-for-projects.yml b/changelogs/unreleased/45779-fix-default-visibility-level-for-projects.yml
deleted file mode 100644
index b4cba5041d1..00000000000
--- a/changelogs/unreleased/45779-fix-default-visibility-level-for-projects.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix default visibility_level for new projects
-merge_request: 24120
-author: Fabian Schneider @fabsrc
-type: fixed
diff --git a/changelogs/unreleased/45791-number-of-repositories-usage-ping.yml b/changelogs/unreleased/45791-number-of-repositories-usage-ping.yml
deleted file mode 100644
index 8d1f5df56ea..00000000000
--- a/changelogs/unreleased/45791-number-of-repositories-usage-ping.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add repositories count to usage ping data
-merge_request: 24823
-author:
-type: added
diff --git a/changelogs/unreleased/46448-add-timestamps-for-each-stage-of-gitlab-rake-gitlab-backup-restore.yml b/changelogs/unreleased/46448-add-timestamps-for-each-stage-of-gitlab-rake-gitlab-backup-restore.yml
deleted file mode 100644
index 4ce6787570a..00000000000
--- a/changelogs/unreleased/46448-add-timestamps-for-each-stage-of-gitlab-rake-gitlab-backup-restore.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Display timestamps to messages printed by gitlab:backup:restore rake tasks
-merge_request:
-author: Will Chandler
-type: changed
diff --git a/changelogs/unreleased/46750-ci-empty-environment-is-created-even-when-a-job-isn-t-run-when-manual.yml b/changelogs/unreleased/46750-ci-empty-environment-is-created-even-when-a-job-isn-t-run-when-manual.yml
new file mode 100644
index 00000000000..d052a28ab51
--- /dev/null
+++ b/changelogs/unreleased/46750-ci-empty-environment-is-created-even-when-a-job-isn-t-run-when-manual.yml
@@ -0,0 +1,5 @@
+---
+title: Sort Environments by Last Updated
+merge_request: 25260
+author:
+type: added
diff --git a/changelogs/unreleased/47007-related-merge-requests-in-issue-design-restyle.yml b/changelogs/unreleased/47007-related-merge-requests-in-issue-design-restyle.yml
deleted file mode 100644
index 28e2a4cc377..00000000000
--- a/changelogs/unreleased/47007-related-merge-requests-in-issue-design-restyle.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Redesigned related merge requests in issue page.
-merge_request: 24270
-author:
-type: changed
diff --git a/changelogs/unreleased/47988-improve-milestone-queries-with-subqueries.yml b/changelogs/unreleased/47988-improve-milestone-queries-with-subqueries.yml
deleted file mode 100644
index d1a80ab43cf..00000000000
--- a/changelogs/unreleased/47988-improve-milestone-queries-with-subqueries.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Improve milestone queries using subqueries instead of separate queries for ids
-merge_request: 24325
-author:
-type: performance
diff --git a/changelogs/unreleased/48798-keybinding-mr-diff.yml b/changelogs/unreleased/48798-keybinding-mr-diff.yml
new file mode 100644
index 00000000000..3ef3f07f27c
--- /dev/null
+++ b/changelogs/unreleased/48798-keybinding-mr-diff.yml
@@ -0,0 +1,5 @@
+---
+title: Next/previous navigation between files in MR review
+merge_request: 25355
+author:
+type: added \ No newline at end of file
diff --git a/changelogs/unreleased/49502-gpg-signature-api-endpoint.yml b/changelogs/unreleased/49502-gpg-signature-api-endpoint.yml
new file mode 100644
index 00000000000..8393cb9d282
--- /dev/null
+++ b/changelogs/unreleased/49502-gpg-signature-api-endpoint.yml
@@ -0,0 +1,5 @@
+---
+title: Add API endpoint to get a commit's GPG signature
+merge_request: 25032
+author:
+type: added
diff --git a/changelogs/unreleased/50006-expose-textcolor-from-public-labels-api.yml b/changelogs/unreleased/50006-expose-textcolor-from-public-labels-api.yml
new file mode 100644
index 00000000000..3c8b58f3001
--- /dev/null
+++ b/changelogs/unreleased/50006-expose-textcolor-from-public-labels-api.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: Expose text_color for project and group labels'
+merge_request: 25172
+author: Robert Schilling
+type: added
diff --git a/changelogs/unreleased/50013-add-browser-platform-flags.yml b/changelogs/unreleased/50013-add-browser-platform-flags.yml
deleted file mode 100644
index 6176b8b64a7..00000000000
--- a/changelogs/unreleased/50013-add-browser-platform-flags.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add CSS & JS global flags to represent browser and platform
-merge_request: 24017
-author:
-type: other
diff --git a/changelogs/unreleased/50313-use-kaniko-to-build-containers-in-autodevops.yml b/changelogs/unreleased/50313-use-kaniko-to-build-containers-in-autodevops.yml
new file mode 100644
index 00000000000..0188df7fce7
--- /dev/null
+++ b/changelogs/unreleased/50313-use-kaniko-to-build-containers-in-autodevops.yml
@@ -0,0 +1,5 @@
+---
+title: Use auto-build-image for build job in Auto-DevOps.gitlab-ci.yml
+merge_request: 24279
+author:
+type: changed
diff --git a/changelogs/unreleased/50352-sort-save.yml b/changelogs/unreleased/50352-sort-save.yml
deleted file mode 100644
index cd046c8b785..00000000000
--- a/changelogs/unreleased/50352-sort-save.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Save issues/merge request sorting options to backend
-merge_request: 24198
-author:
-type: added
diff --git a/changelogs/unreleased/50433-make-emoji-picker-bigger.yml b/changelogs/unreleased/50433-make-emoji-picker-bigger.yml
new file mode 100644
index 00000000000..8fcf41df09d
--- /dev/null
+++ b/changelogs/unreleased/50433-make-emoji-picker-bigger.yml
@@ -0,0 +1,5 @@
+---
+title: Make emoji picker bigger
+merge_request: 25187
+author: Jacopo Beschi @jacopo-beschi
+type: changed
diff --git a/changelogs/unreleased/50521-block-emojis-and-symbol-characters-from-user-s-full-names-2.yml b/changelogs/unreleased/50521-block-emojis-and-symbol-characters-from-user-s-full-names-2.yml
deleted file mode 100644
index 04caf8262c6..00000000000
--- a/changelogs/unreleased/50521-block-emojis-and-symbol-characters-from-user-s-full-names-2.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Block emojis and symbol characters from users full names
-merge_request: 24523
-author:
-type: other
diff --git a/changelogs/unreleased/51754-admin-view-private-personal-snippets.yml b/changelogs/unreleased/51754-admin-view-private-personal-snippets.yml
deleted file mode 100644
index cf3d73fce0c..00000000000
--- a/changelogs/unreleased/51754-admin-view-private-personal-snippets.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow users with full private access to read private personal snippets.
-merge_request: 24560
-author:
-type: fixed
diff --git a/changelogs/unreleased/51759-filter-by-language.yml b/changelogs/unreleased/51759-filter-by-language.yml
deleted file mode 100644
index 6b5bedd6b2d..00000000000
--- a/changelogs/unreleased/51759-filter-by-language.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add `with_programming_language` filter for projects to API
-merge_request: 24377
-author: Dylan MacKenzie
-type: added
diff --git a/changelogs/unreleased/51819-show-feed-toggle-under-system-notes.yml b/changelogs/unreleased/51819-show-feed-toggle-under-system-notes.yml
new file mode 100644
index 00000000000..76ea4149c56
--- /dev/null
+++ b/changelogs/unreleased/51819-show-feed-toggle-under-system-notes.yml
@@ -0,0 +1,5 @@
+---
+title: Add support for toggling discussion filter from notes section
+merge_request: 25426
+author:
+type: added
diff --git a/changelogs/unreleased/51913-api-getting-projects-for-users-with-dot-gets-404.yml b/changelogs/unreleased/51913-api-getting-projects-for-users-with-dot-gets-404.yml
deleted file mode 100644
index 9d72efdd52a..00000000000
--- a/changelogs/unreleased/51913-api-getting-projects-for-users-with-dot-gets-404.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'API: Support username with dots'
-merge_request: 24395
-author: Robert Schilling
-type: fixed
diff --git a/changelogs/unreleased/52198-timer-is-vertically-misaligned-for-delayed-jobs-in-pipeline-actions.yml b/changelogs/unreleased/52198-timer-is-vertically-misaligned-for-delayed-jobs-in-pipeline-actions.yml
new file mode 100644
index 00000000000..84062c6db91
--- /dev/null
+++ b/changelogs/unreleased/52198-timer-is-vertically-misaligned-for-delayed-jobs-in-pipeline-actions.yml
@@ -0,0 +1,5 @@
+---
+title: 'Timer and action name aligned vertically for delayed jobs in pipeline actions'
+merge_request: 25117
+author: Gokhan Apaydin
+type: fixed
diff --git a/changelogs/unreleased/52275-fix-master-to-be-hyperlink.yml b/changelogs/unreleased/52275-fix-master-to-be-hyperlink.yml
deleted file mode 100644
index c1cde0ceff6..00000000000
--- a/changelogs/unreleased/52275-fix-master-to-be-hyperlink.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Resolve In Merge Request diff screen, master is not a hyperlink
-merge_request: 23874
-author:
-type: fixed
diff --git a/changelogs/unreleased/52278-squash-checkbox-fix.yml b/changelogs/unreleased/52278-squash-checkbox-fix.yml
deleted file mode 100644
index c81748ae419..00000000000
--- a/changelogs/unreleased/52278-squash-checkbox-fix.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Resolve When merging an MR, the squash checkbox isnt always supported
-merge_request: 24296
-author:
-type: fixed
diff --git a/changelogs/unreleased/52347-lines-changed-statistics-is-not-easily-visible-in-mr-changes-view.yml b/changelogs/unreleased/52347-lines-changed-statistics-is-not-easily-visible-in-mr-changes-view.yml
deleted file mode 100644
index cf1c4378f18..00000000000
--- a/changelogs/unreleased/52347-lines-changed-statistics-is-not-easily-visible-in-mr-changes-view.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Show MR statistics in diff comparisons
-merge_request: !24569
-author:
-type: changed
diff --git a/changelogs/unreleased/52363-modifies-environment-scope-field-on-cluster-page.yml b/changelogs/unreleased/52363-modifies-environment-scope-field-on-cluster-page.yml
deleted file mode 100644
index 07cb35e6529..00000000000
--- a/changelogs/unreleased/52363-modifies-environment-scope-field-on-cluster-page.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Modifies environment scope UI on cluster page
-merge_request: 24376
-author:
-type: other
diff --git a/changelogs/unreleased/52363-ui-changes-to-cluster-and-ado-pages.yml b/changelogs/unreleased/52363-ui-changes-to-cluster-and-ado-pages.yml
deleted file mode 100644
index eb4851971fb..00000000000
--- a/changelogs/unreleased/52363-ui-changes-to-cluster-and-ado-pages.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Moves domain setting from Auto DevOps to Cluster's page
-merge_request: 24580
-author:
-type: added
diff --git a/changelogs/unreleased/52568-external-mr-diffs.yml b/changelogs/unreleased/52568-external-mr-diffs.yml
deleted file mode 100644
index b1c9d5cb809..00000000000
--- a/changelogs/unreleased/52568-external-mr-diffs.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow merge request diffs to be placed into an object store
-merge_request: 24276
-author:
-type: added
diff --git a/changelogs/unreleased/52674-api-v4-projects-project_id-jobs-endpoint-hits-statement-timeout.yml b/changelogs/unreleased/52674-api-v4-projects-project_id-jobs-endpoint-hits-statement-timeout.yml
deleted file mode 100644
index f79078c1fd9..00000000000
--- a/changelogs/unreleased/52674-api-v4-projects-project_id-jobs-endpoint-hits-statement-timeout.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "[API] Omit `X-Total` and `X-Total-Pages` headers when items count is more than 10,000"
-merge_request: 23931
-author:
-type: performance
diff --git a/changelogs/unreleased/52734-styling-of-user-project-and-group-avatars.yml b/changelogs/unreleased/52734-styling-of-user-project-and-group-avatars.yml
new file mode 100644
index 00000000000..9329e81eb83
--- /dev/null
+++ b/changelogs/unreleased/52734-styling-of-user-project-and-group-avatars.yml
@@ -0,0 +1,5 @@
+---
+title: Add rectangular project and group avatars
+merge_request: 25098
+author:
+type: other
diff --git a/changelogs/unreleased/52778-don-t-display-pipeline-status-if-pipelines-are-disabled.yml b/changelogs/unreleased/52778-don-t-display-pipeline-status-if-pipelines-are-disabled.yml
new file mode 100644
index 00000000000..7fa01e2835a
--- /dev/null
+++ b/changelogs/unreleased/52778-don-t-display-pipeline-status-if-pipelines-are-disabled.yml
@@ -0,0 +1,5 @@
+---
+title: Hide pipeline status when pipelines are disabled on project.
+merge_request: 25204
+author:
+type: fixed
diff --git a/changelogs/unreleased/52877-ios-publishing-blog-post-and-gitlab-ci-yml-template.yml b/changelogs/unreleased/52877-ios-publishing-blog-post-and-gitlab-ci-yml-template.yml
new file mode 100644
index 00000000000..13529348c60
--- /dev/null
+++ b/changelogs/unreleased/52877-ios-publishing-blog-post-and-gitlab-ci-yml-template.yml
@@ -0,0 +1,5 @@
+---
+title: Add iOS-fastlane template for .gitlab-ci.yml
+merge_request: 25395
+author:
+type: changed
diff --git a/changelogs/unreleased/52971-merge-request-file-browser-should-always-be-possible-show-hide.yml b/changelogs/unreleased/52971-merge-request-file-browser-should-always-be-possible-show-hide.yml
deleted file mode 100644
index b661c55957d..00000000000
--- a/changelogs/unreleased/52971-merge-request-file-browser-should-always-be-possible-show-hide.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Make possible to toggle file tree while scrolling through diffs
-merge_request: !24103
-author:
-type: changed
diff --git a/changelogs/unreleased/53104-redesign-group-overview-ui-mvc.yml b/changelogs/unreleased/53104-redesign-group-overview-ui-mvc.yml
deleted file mode 100644
index cb810b7ac7f..00000000000
--- a/changelogs/unreleased/53104-redesign-group-overview-ui-mvc.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Refresh group overview to match project overview
-merge_request: 23866
-author:
-type: changed
diff --git a/changelogs/unreleased/53325-admin-runners-page-fails-with-an-sql-statement-timeout.yml b/changelogs/unreleased/53325-admin-runners-page-fails-with-an-sql-statement-timeout.yml
new file mode 100644
index 00000000000..e0ed38fc2fa
--- /dev/null
+++ b/changelogs/unreleased/53325-admin-runners-page-fails-with-an-sql-statement-timeout.yml
@@ -0,0 +1,5 @@
+---
+title: Use limited counter for runner build count in admin page.
+merge_request: 25220
+author:
+type: fixed
diff --git a/changelogs/unreleased/53411-remove_personal_access_tokens_token.yml b/changelogs/unreleased/53411-remove_personal_access_tokens_token.yml
new file mode 100644
index 00000000000..32cca07f58d
--- /dev/null
+++ b/changelogs/unreleased/53411-remove_personal_access_tokens_token.yml
@@ -0,0 +1,5 @@
+---
+title: Remove undigested token column from personal_access_tokens table from the database
+merge_request: 22743
+author:
+type: changed
diff --git a/changelogs/unreleased/53413-externalize-markdown-toolbar-tooltips.yml b/changelogs/unreleased/53413-externalize-markdown-toolbar-tooltips.yml
new file mode 100644
index 00000000000..c460760c10b
--- /dev/null
+++ b/changelogs/unreleased/53413-externalize-markdown-toolbar-tooltips.yml
@@ -0,0 +1,5 @@
+---
+title: Externalize markdown toolbar buttons tooltips
+merge_request: 25529
+author:
+type: fixed
diff --git a/changelogs/unreleased/53431-fix-upcoming-milestone-filter-for-groups.yml b/changelogs/unreleased/53431-fix-upcoming-milestone-filter-for-groups.yml
deleted file mode 100644
index 1e9c7f3913c..00000000000
--- a/changelogs/unreleased/53431-fix-upcoming-milestone-filter-for-groups.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix upcoming milestones filter not including group milestones
-merge_request: 23098
-author: Heinrich Lee Yu
-type: fixed
diff --git a/changelogs/unreleased/53671-redirect-projects-id-to-project-page.yml b/changelogs/unreleased/53671-redirect-projects-id-to-project-page.yml
deleted file mode 100644
index 08c5ded05d5..00000000000
--- a/changelogs/unreleased/53671-redirect-projects-id-to-project-page.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Redirect GET projects/:id to project page
-merge_request: 24467
-author:
-type: added
diff --git a/changelogs/unreleased/53676-ip-address-of-gitlab-runner-is-wrong-in-the-runners-description.yml b/changelogs/unreleased/53676-ip-address-of-gitlab-runner-is-wrong-in-the-runners-description.yml
deleted file mode 100644
index 12a6509e6f7..00000000000
--- a/changelogs/unreleased/53676-ip-address-of-gitlab-runner-is-wrong-in-the-runners-description.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Get remote IP address of runner
-merge_request: 24624
-author:
-type: changed
diff --git a/changelogs/unreleased/53714-inconsistent-text-color-for-labels.yml b/changelogs/unreleased/53714-inconsistent-text-color-for-labels.yml
deleted file mode 100644
index d804e2df2cd..00000000000
--- a/changelogs/unreleased/53714-inconsistent-text-color-for-labels.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix foreground color for labels to ensure consistency of label appearance
-merge_request: 23873
-author: Nathan Friend
-type: fixed
diff --git a/changelogs/unreleased/53856-changing-group-visibility-does-not-re-enable-save-button.yml b/changelogs/unreleased/53856-changing-group-visibility-does-not-re-enable-save-button.yml
deleted file mode 100644
index 1daa72fb9c4..00000000000
--- a/changelogs/unreleased/53856-changing-group-visibility-does-not-re-enable-save-button.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Fix suboptimal handling of checkbox and radio input events causing
- group general settings submit button to stay disabled after changing its visibility
-merge_request: 23022
-author:
-type: fixed
diff --git a/changelogs/unreleased/53861-api-promote-project-milestone-to-a-group-milestone.yml b/changelogs/unreleased/53861-api-promote-project-milestone-to-a-group-milestone.yml
new file mode 100644
index 00000000000..6c621763e2e
--- /dev/null
+++ b/changelogs/unreleased/53861-api-promote-project-milestone-to-a-group-milestone.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: Promote project milestone to a group milestone'
+merge_request: 25203
+author: Nermin Vehabovic
+type: added
diff --git a/changelogs/unreleased/53950-commit-comments-displayed-on-a-merge-request.yml b/changelogs/unreleased/53950-commit-comments-displayed-on-a-merge-request.yml
deleted file mode 100644
index adaaed7f1aa..00000000000
--- a/changelogs/unreleased/53950-commit-comments-displayed-on-a-merge-request.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Display "commented" only for commit discussions on merge requests
-merge_request: 24427
-author:
-type: changed
diff --git a/changelogs/unreleased/53966-make-hashed-storage-migration-safer-and-more-inviting.yml b/changelogs/unreleased/53966-make-hashed-storage-migration-safer-and-more-inviting.yml
new file mode 100644
index 00000000000..556a238ff7d
--- /dev/null
+++ b/changelogs/unreleased/53966-make-hashed-storage-migration-safer-and-more-inviting.yml
@@ -0,0 +1,5 @@
+---
+title: Hashed Storage rollback mechanism
+merge_request: 23955
+author:
+type: added
diff --git a/changelogs/unreleased/54167-rename-project-tags-to-project-topics.yml b/changelogs/unreleased/54167-rename-project-tags-to-project-topics.yml
deleted file mode 100644
index 6fc8aa1a195..00000000000
--- a/changelogs/unreleased/54167-rename-project-tags-to-project-topics.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Rename project tags to project topics
-merge_request: 24219
-author:
-type: other
diff --git a/changelogs/unreleased/54213-standardize-token-value-capitalization-in-filter-bar.yml b/changelogs/unreleased/54213-standardize-token-value-capitalization-in-filter-bar.yml
deleted file mode 100644
index 37dea77b8d2..00000000000
--- a/changelogs/unreleased/54213-standardize-token-value-capitalization-in-filter-bar.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Standardize filter value capitlization in filter bar in both issues and boards pages
-merge_request: 23846
-author: obahareth
-type: changed
diff --git a/changelogs/unreleased/54250-upstream-kubeclient-redirect-patch.yml b/changelogs/unreleased/54250-upstream-kubeclient-redirect-patch.yml
deleted file mode 100644
index d1bdbccb20a..00000000000
--- a/changelogs/unreleased/54250-upstream-kubeclient-redirect-patch.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Upgrade kubeclient to 4.2.2 and swap out monkey-patch to disallow redirects
-merge_request: 24284
-author:
-type: other
diff --git a/changelogs/unreleased/54484-anchor-links-to-comments-or-system-notes-can-break-with-discussion-filters.yml b/changelogs/unreleased/54484-anchor-links-to-comments-or-system-notes-can-break-with-discussion-filters.yml
deleted file mode 100644
index 4d543db567d..00000000000
--- a/changelogs/unreleased/54484-anchor-links-to-comments-or-system-notes-can-break-with-discussion-filters.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Ensured links to a comment or system note anchor resolves to the right note if a user has a discussion filter.
-merge_request: 24228
-author:
-type: changed
diff --git a/changelogs/unreleased/54544-update-project-topics-styling-to-use-badges-design.yml b/changelogs/unreleased/54544-update-project-topics-styling-to-use-badges-design.yml
deleted file mode 100644
index de12c66e9ef..00000000000
--- a/changelogs/unreleased/54544-update-project-topics-styling-to-use-badges-design.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update project topics styling to use badges design
-merge_request: 24415
-author:
-type: changed
diff --git a/changelogs/unreleased/54643-lower_issuable_finder_complexity.yml b/changelogs/unreleased/54643-lower_issuable_finder_complexity.yml
new file mode 100644
index 00000000000..f7f8e4d0e1f
--- /dev/null
+++ b/changelogs/unreleased/54643-lower_issuable_finder_complexity.yml
@@ -0,0 +1,5 @@
+---
+title: Speed up group issue search counts
+merge_request: 25411
+author:
+type: performance
diff --git a/changelogs/unreleased/54725-fix-emoji-button-active-state.yml b/changelogs/unreleased/54725-fix-emoji-button-active-state.yml
new file mode 100644
index 00000000000..4f0a436cc87
--- /dev/null
+++ b/changelogs/unreleased/54725-fix-emoji-button-active-state.yml
@@ -0,0 +1,5 @@
+---
+title: Fix hover and active state colors of award emoji button
+merge_request: 25295
+author:
+type: fixed
diff --git a/changelogs/unreleased/54796-api-sort-tie-breaker-for-pagination.yml b/changelogs/unreleased/54796-api-sort-tie-breaker-for-pagination.yml
new file mode 100644
index 00000000000..92b27f63f82
--- /dev/null
+++ b/changelogs/unreleased/54796-api-sort-tie-breaker-for-pagination.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: Sort tie breaker with id DESC'
+merge_request: 25311
+author: Nermin Vehabovic
+type: changed
diff --git a/changelogs/unreleased/54905-milestone-search.yml b/changelogs/unreleased/54905-milestone-search.yml
deleted file mode 100644
index 88717242e7c..00000000000
--- a/changelogs/unreleased/54905-milestone-search.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Adds milestone search
-merge_request: 24265
-author: Jacopo Beschi @jacopo-beschi
-type: added
diff --git a/changelogs/unreleased/54924-refactor-notes-actions-params.yml b/changelogs/unreleased/54924-refactor-notes-actions-params.yml
new file mode 100644
index 00000000000..b6083820401
--- /dev/null
+++ b/changelogs/unreleased/54924-refactor-notes-actions-params.yml
@@ -0,0 +1,5 @@
+---
+title: Fix commenting on commits having SHA1 starting with a large number
+merge_request: 25278
+author:
+type: fixed
diff --git a/changelogs/unreleased/55057-system-message-to-core.yml b/changelogs/unreleased/55057-system-message-to-core.yml
new file mode 100644
index 00000000000..3381879eb4a
--- /dev/null
+++ b/changelogs/unreleased/55057-system-message-to-core.yml
@@ -0,0 +1,5 @@
+---
+title: Port System Header and Footer feature to Core
+merge_request: 25241
+author:
+type: added
diff --git a/changelogs/unreleased/55098-ui-bug-adding-group-members-with-lower-permissions.yml b/changelogs/unreleased/55098-ui-bug-adding-group-members-with-lower-permissions.yml
deleted file mode 100644
index f22524ef4b2..00000000000
--- a/changelogs/unreleased/55098-ui-bug-adding-group-members-with-lower-permissions.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Resolve UI bug adding group members with lower permissions
-merge_request: 24820
-author:
-type: fixed
diff --git a/changelogs/unreleased/55109-jira-integration-api-doesn-t-respect-available-format.yml b/changelogs/unreleased/55109-jira-integration-api-doesn-t-respect-available-format.yml
new file mode 100644
index 00000000000..c58cdc19555
--- /dev/null
+++ b/changelogs/unreleased/55109-jira-integration-api-doesn-t-respect-available-format.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: Support Jira transition ID as string'
+merge_request: 24400
+author: Robert Schilling
+type: fixed
diff --git a/changelogs/unreleased/55111-gitlab-api-does-not-manage-default_branch_protection-3-value.yml b/changelogs/unreleased/55111-gitlab-api-does-not-manage-default_branch_protection-3-value.yml
deleted file mode 100644
index b609fc2d60b..00000000000
--- a/changelogs/unreleased/55111-gitlab-api-does-not-manage-default_branch_protection-3-value.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'API: Fix default_branch_protection admin setting'
-merge_request: 24398
-author: Robert Schilling
-type: fixed
diff --git a/changelogs/unreleased/55242-skeleton-loading-releases.yml b/changelogs/unreleased/55242-skeleton-loading-releases.yml
deleted file mode 100644
index 43cda64ce04..00000000000
--- a/changelogs/unreleased/55242-skeleton-loading-releases.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Adds skeleton loading to releases page
-merge_request:
-author:
-type: changed
diff --git a/changelogs/unreleased/55312-svg.yml b/changelogs/unreleased/55312-svg.yml
new file mode 100644
index 00000000000..a6260aeaf2a
--- /dev/null
+++ b/changelogs/unreleased/55312-svg.yml
@@ -0,0 +1,5 @@
+---
+title: Use export-import svgs from gitlab-svgs
+merge_request: 24954
+author:
+type: other
diff --git a/changelogs/unreleased/55376-related_merge_requests-api-call-returns-merge-requests-that-are-not-related-to-the-issue.yml b/changelogs/unreleased/55376-related_merge_requests-api-call-returns-merge-requests-that-are-not-related-to-the-issue.yml
new file mode 100644
index 00000000000..d2f24d6f499
--- /dev/null
+++ b/changelogs/unreleased/55376-related_merge_requests-api-call-returns-merge-requests-that-are-not-related-to-the-issue.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: Ensure that related merge requests are referenced cross-project'
+merge_request: 25222
+author: Robert Schilling
+type: fixed
diff --git a/changelogs/unreleased/55495-teamcity-use-revision-in-query.yml b/changelogs/unreleased/55495-teamcity-use-revision-in-query.yml
deleted file mode 100644
index 724de733b7c..00000000000
--- a/changelogs/unreleased/55495-teamcity-use-revision-in-query.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Build number does not need to be tweaked anymore for the TeamCity integration to work properly.
-merge_request: 23898
-author:
-type: changed
diff --git a/changelogs/unreleased/55628-artifacts-from-a-job-defined-after-a-parallel-job-are-not-downloaded.yml b/changelogs/unreleased/55628-artifacts-from-a-job-defined-after-a-parallel-job-are-not-downloaded.yml
deleted file mode 100644
index 071036cd568..00000000000
--- a/changelogs/unreleased/55628-artifacts-from-a-job-defined-after-a-parallel-job-are-not-downloaded.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Handle regular job dependencies next to parallelized job dependencies.
-merge_request: 24273
-author:
-type: fixed
diff --git a/changelogs/unreleased/55703-md-image-borders.yml b/changelogs/unreleased/55703-md-image-borders.yml
new file mode 100644
index 00000000000..94297a42f6d
--- /dev/null
+++ b/changelogs/unreleased/55703-md-image-borders.yml
@@ -0,0 +1,5 @@
+---
+title: Only show borders for markdown images in notes
+merge_request: 25448
+author:
+type: fixed
diff --git a/changelogs/unreleased/55820-adds-common-name-chart-value.yml b/changelogs/unreleased/55820-adds-common-name-chart-value.yml
deleted file mode 100644
index 1871abbfc6b..00000000000
--- a/changelogs/unreleased/55820-adds-common-name-chart-value.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Ensure Cert Manager works with Auto DevOps URLs greater than 64 bytes
-merge_request: 24683
-author:
-type: fixed
diff --git a/changelogs/unreleased/55884-adjust-emoji-and-cancel-buttons-height-in-user-status-modal-when-emoji-is-changed.yml b/changelogs/unreleased/55884-adjust-emoji-and-cancel-buttons-height-in-user-status-modal-when-emoji-is-changed.yml
deleted file mode 100644
index 2fbf334f5e9..00000000000
--- a/changelogs/unreleased/55884-adjust-emoji-and-cancel-buttons-height-in-user-status-modal-when-emoji-is-changed.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Emoji and cancel button are taller than input in set user status modal
-merge_request: 24173
-author: Dhiraj Bodicherla
-type: fixed
diff --git a/changelogs/unreleased/55893-artifacts-download.yml b/changelogs/unreleased/55893-artifacts-download.yml
new file mode 100644
index 00000000000..30c118b7094
--- /dev/null
+++ b/changelogs/unreleased/55893-artifacts-download.yml
@@ -0,0 +1,5 @@
+---
+title: Fixes not working dropdowns in pipelines page
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/55925-if-there-is-only-one-changed-page-in-review-app-go-directly-there.yml b/changelogs/unreleased/55925-if-there-is-only-one-changed-page-in-review-app-go-directly-there.yml
new file mode 100644
index 00000000000..ef3d9844acb
--- /dev/null
+++ b/changelogs/unreleased/55925-if-there-is-only-one-changed-page-in-review-app-go-directly-there.yml
@@ -0,0 +1,5 @@
+---
+title: Review App Link to Changed Page if Only One Change Present
+merge_request: 25048
+author:
+type: changed
diff --git a/changelogs/unreleased/55945-suggested-change-preview-highlight.yml b/changelogs/unreleased/55945-suggested-change-preview-highlight.yml
deleted file mode 100644
index 997290a5d50..00000000000
--- a/changelogs/unreleased/55945-suggested-change-preview-highlight.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix syntax highlighting for suggested changes preview
-merge_request: 24358
-author:
-type: fixed
diff --git a/changelogs/unreleased/55966-when-ref-is-ambiguous-createpipelineservice-raises-an-error.yml b/changelogs/unreleased/55966-when-ref-is-ambiguous-createpipelineservice-raises-an-error.yml
deleted file mode 100644
index 01a162944d3..00000000000
--- a/changelogs/unreleased/55966-when-ref-is-ambiguous-createpipelineservice-raises-an-error.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Prevent checking protected_ref? for ambiguous refs.
-merge_request: 24437
-author:
-type: fixed
diff --git a/changelogs/unreleased/56010-user-profile-page-horizonal-whitespace-between-overview-columns-breaks-two-column-layout.yml b/changelogs/unreleased/56010-user-profile-page-horizonal-whitespace-between-overview-columns-breaks-two-column-layout.yml
deleted file mode 100644
index 407346bbf22..00000000000
--- a/changelogs/unreleased/56010-user-profile-page-horizonal-whitespace-between-overview-columns-breaks-two-column-layout.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove horizontal whitespace on user profile overview on small breakpoints
-merge_request: 24189
-author:
-type: other
diff --git a/changelogs/unreleased/56014-api-merge-request-squash-commit-messages.yml b/changelogs/unreleased/56014-api-merge-request-squash-commit-messages.yml
deleted file mode 100644
index e324baa94a3..00000000000
--- a/changelogs/unreleased/56014-api-merge-request-squash-commit-messages.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: API allows setting the squash commit message when squashing a merge request
-merge_request: 24784
-author:
-type: added
diff --git a/changelogs/unreleased/56019-archived-stuck.yml b/changelogs/unreleased/56019-archived-stuck.yml
deleted file mode 100644
index de3698a327b..00000000000
--- a/changelogs/unreleased/56019-archived-stuck.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixes z-index and margins of archived alert in job page
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/56036-fix-translation-of-in-in-job-details-sidebar.yml b/changelogs/unreleased/56036-fix-translation-of-in-in-job-details-sidebar.yml
deleted file mode 100644
index ff9d4f2c175..00000000000
--- a/changelogs/unreleased/56036-fix-translation-of-in-in-job-details-sidebar.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-title: Remove multilingual translation from the word "in" in the job details sidebar.
-merge_request: 24192
-author: Nathan Friend
-type: changed
diff --git a/changelogs/unreleased/56110-cluster-kubernetes-api-500-error-on-post-request.yml b/changelogs/unreleased/56110-cluster-kubernetes-api-500-error-on-post-request.yml
deleted file mode 100644
index 4da14114225..00000000000
--- a/changelogs/unreleased/56110-cluster-kubernetes-api-500-error-on-post-request.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Improves restriction of multiple Kubernetes clusters through API
-merge_request: 24251
-author:
-type: fixed
diff --git a/changelogs/unreleased/56172-docs-fix-add-include-to-ci-param-list.yml b/changelogs/unreleased/56172-docs-fix-add-include-to-ci-param-list.yml
deleted file mode 100644
index 92592290ac4..00000000000
--- a/changelogs/unreleased/56172-docs-fix-add-include-to-ci-param-list.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update CI YAML param table with include
-merge_request: !24309
-author:
-type: fixed
diff --git a/changelogs/unreleased/56237-api-truncated-commit-title.yml b/changelogs/unreleased/56237-api-truncated-commit-title.yml
new file mode 100644
index 00000000000..1a48d0fda1b
--- /dev/null
+++ b/changelogs/unreleased/56237-api-truncated-commit-title.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: Expose full commit title'
+merge_request: 25189
+author: Robert Schilling
+type: fixed
diff --git a/changelogs/unreleased/56334-runners-ipv6-address-overlaps-other-values.yml b/changelogs/unreleased/56334-runners-ipv6-address-overlaps-other-values.yml
deleted file mode 100644
index 8a6adef5dae..00000000000
--- a/changelogs/unreleased/56334-runners-ipv6-address-overlaps-other-values.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Resolve Runners IPv6 address overlaps other values
-merge_request: 24531
-author:
-type: fixed
diff --git a/changelogs/unreleased/56363-inconsitent-file-size-indication-across-different-ci-pages.yml b/changelogs/unreleased/56363-inconsitent-file-size-indication-across-different-ci-pages.yml
deleted file mode 100644
index 7c923422534..00000000000
--- a/changelogs/unreleased/56363-inconsitent-file-size-indication-across-different-ci-pages.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Show CI artifact file size with 3 significant digits on 'browse job artifacts'
- page
-merge_request: 24387
-author:
-type: fixed
diff --git a/changelogs/unreleased/56371-don-t-check-confidential-issues-for-spam.yml b/changelogs/unreleased/56371-don-t-check-confidential-issues-for-spam.yml
deleted file mode 100644
index fcfa29977d1..00000000000
--- a/changelogs/unreleased/56371-don-t-check-confidential-issues-for-spam.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Do not run spam checks on confidential issues
-merge_request: 24453
-author:
-type: fixed
diff --git a/changelogs/unreleased/56379-pipeline-stages-job-action-button-icon-is-not-aligned.yml b/changelogs/unreleased/56379-pipeline-stages-job-action-button-icon-is-not-aligned.yml
deleted file mode 100644
index ec8a1d9d6ea..00000000000
--- a/changelogs/unreleased/56379-pipeline-stages-job-action-button-icon-is-not-aligned.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Resolve Pipeline stages job action button icon is not aligned
-merge_request: 24577
-author:
-type: fixed
diff --git a/changelogs/unreleased/56389-remove-unwanted-suggestion-flash-margin.yml b/changelogs/unreleased/56389-remove-unwanted-suggestion-flash-margin.yml
deleted file mode 100644
index 3494feb9be1..00000000000
--- a/changelogs/unreleased/56389-remove-unwanted-suggestion-flash-margin.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove unwanted margin above suggested changes.
-merge_request: 24419
-author:
-type: fixed
diff --git a/changelogs/unreleased/56398-fix-cluster-installation-loading-state.yml b/changelogs/unreleased/56398-fix-cluster-installation-loading-state.yml
deleted file mode 100644
index 19ff408ddf4..00000000000
--- a/changelogs/unreleased/56398-fix-cluster-installation-loading-state.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix cluster installation processing spinner
-merge_request: 24814
-author:
-type: fixed
diff --git a/changelogs/unreleased/56417-update-helm-to-2-12-2.yml b/changelogs/unreleased/56417-update-helm-to-2-12-2.yml
deleted file mode 100644
index f01915c532f..00000000000
--- a/changelogs/unreleased/56417-update-helm-to-2-12-2.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update Helm to 2.12.2 to address Helm client vulnerability
-merge_request: 24418
-author: Takuya Noguchi
-type: security
diff --git a/changelogs/unreleased/56477-units-are-appended-to-y-axis-label-on-metrics-dashboard.yml b/changelogs/unreleased/56477-units-are-appended-to-y-axis-label-on-metrics-dashboard.yml
new file mode 100644
index 00000000000..7febe175faf
--- /dev/null
+++ b/changelogs/unreleased/56477-units-are-appended-to-y-axis-label-on-metrics-dashboard.yml
@@ -0,0 +1,5 @@
+---
+title: Remove duplicate units from metrics graph
+merge_request: 25485
+author:
+type: fixed
diff --git a/changelogs/unreleased/56485-implement-graphql-mergerequestsresolver.yml b/changelogs/unreleased/56485-implement-graphql-mergerequestsresolver.yml
new file mode 100644
index 00000000000..5362ac65038
--- /dev/null
+++ b/changelogs/unreleased/56485-implement-graphql-mergerequestsresolver.yml
@@ -0,0 +1,5 @@
+---
+title: Add field mergeRequests for project in GraphQL
+merge_request: 24805
+author:
+type: added
diff --git a/changelogs/unreleased/56492-implement-new-arguments-state-closed_before-and-closed_after-for-issuesresolver-in-graphql.yml b/changelogs/unreleased/56492-implement-new-arguments-state-closed_before-and-closed_after-for-issuesresolver-in-graphql.yml
new file mode 100644
index 00000000000..9b7aed82d49
--- /dev/null
+++ b/changelogs/unreleased/56492-implement-new-arguments-state-closed_before-and-closed_after-for-issuesresolver-in-graphql.yml
@@ -0,0 +1,5 @@
+---
+title: "Implement new arguments `state`, `closed_before` and `closed_after` for `IssuesResolver` in GraphQL"
+merge_request: 24910
+author:
+type: changed
diff --git a/changelogs/unreleased/56507-sh-bump-katex-0.10.0.yml b/changelogs/unreleased/56507-sh-bump-katex-0.10.0.yml
deleted file mode 100644
index 671e204da21..00000000000
--- a/changelogs/unreleased/56507-sh-bump-katex-0.10.0.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Upgrade KaTeX to version 0.10.0
-merge_request: 24478
-author: Andrew Harmon
-type: fixed \ No newline at end of file
diff --git a/changelogs/unreleased/56543-project-lists-further-iteration-improvements.yml b/changelogs/unreleased/56543-project-lists-further-iteration-improvements.yml
deleted file mode 100644
index 388ff1d062a..00000000000
--- a/changelogs/unreleased/56543-project-lists-further-iteration-improvements.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Project list UI improvements
-merge_request: 24855
-author:
-type: other
diff --git a/changelogs/unreleased/56547-limit-sidekiq-logging-based-on-argument-size.yml b/changelogs/unreleased/56547-limit-sidekiq-logging-based-on-argument-size.yml
deleted file mode 100644
index 9ef274f3b49..00000000000
--- a/changelogs/unreleased/56547-limit-sidekiq-logging-based-on-argument-size.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Prevent Sidekiq arguments over 10 KB in size from being logged to JSON
-merge_request: 24493
-author:
-type: changed
diff --git a/changelogs/unreleased/56556-fix-markdown-table-border.yml b/changelogs/unreleased/56556-fix-markdown-table-border.yml
deleted file mode 100644
index 7724f49d4e9..00000000000
--- a/changelogs/unreleased/56556-fix-markdown-table-border.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix markdown table border.
-merge_request: 24601
-author:
-type: fixed
diff --git a/changelogs/unreleased/56622-admin-settings-cannot-read-property-addeventlistener-of-null.yml b/changelogs/unreleased/56622-admin-settings-cannot-read-property-addeventlistener-of-null.yml
deleted file mode 100644
index 52b2db0e999..00000000000
--- a/changelogs/unreleased/56622-admin-settings-cannot-read-property-addeventlistener-of-null.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Load initUserInternalRegexPlaceholder only when required
-merge_request: 24522
-author:
-type: fixed
diff --git a/changelogs/unreleased/56636-hashed-storage-afterrenameservice.yml b/changelogs/unreleased/56636-hashed-storage-afterrenameservice.yml
deleted file mode 100644
index 1f808850554..00000000000
--- a/changelogs/unreleased/56636-hashed-storage-afterrenameservice.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'Hashed Storage: `AfterRenameService` was receiving the wrong `old_path` under some circunstances'
-merge_request: 24526
-author:
-type: fixed
diff --git a/changelogs/unreleased/56694-mark-group-level-labels-in-label-api-as-such.yml b/changelogs/unreleased/56694-mark-group-level-labels-in-label-api-as-such.yml
new file mode 100644
index 00000000000..ae2d9e18e0b
--- /dev/null
+++ b/changelogs/unreleased/56694-mark-group-level-labels-in-label-api-as-such.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: Indicate if label is a project label'
+merge_request: 25219
+author: Robert Schilling
+type: added
diff --git a/changelogs/unreleased/56726-fix-n-1-in-issues-and-merge-requests-api.yml b/changelogs/unreleased/56726-fix-n-1-in-issues-and-merge-requests-api.yml
new file mode 100644
index 00000000000..3eb9e484647
--- /dev/null
+++ b/changelogs/unreleased/56726-fix-n-1-in-issues-and-merge-requests-api.yml
@@ -0,0 +1,5 @@
+---
+title: Fix N+1 query in Issues and MergeRequest API when issuable_metadata is present
+merge_request: 25042
+author: Alex Koval
+type: other
diff --git a/changelogs/unreleased/56764-poor-ui-on-milestone-validation-error-page.yml b/changelogs/unreleased/56764-poor-ui-on-milestone-validation-error-page.yml
deleted file mode 100644
index 089ffd47321..00000000000
--- a/changelogs/unreleased/56764-poor-ui-on-milestone-validation-error-page.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix CSS grid on a new Project/Group Milestone
-merge_request: 24614
-author: Takuya Noguchi
-type: fixed
diff --git a/changelogs/unreleased/56787-realtime-validation-for-user-fullname-and-username.yml b/changelogs/unreleased/56787-realtime-validation-for-user-fullname-and-username.yml
new file mode 100644
index 00000000000..cc3a60479d3
--- /dev/null
+++ b/changelogs/unreleased/56787-realtime-validation-for-user-fullname-and-username.yml
@@ -0,0 +1,5 @@
+---
+title: Add realtime validation for user fullname and username on validation
+merge_request: 25017
+author: Ehsan Abdulqader @EhsanZ
+type: added
diff --git a/changelogs/unreleased/56788-unicorn-metric-labels.yml b/changelogs/unreleased/56788-unicorn-metric-labels.yml
deleted file mode 100644
index 824c981780c..00000000000
--- a/changelogs/unreleased/56788-unicorn-metric-labels.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Clean up unicorn sampler metric labels
-merge_request: 24626
-author: bjk-gitlab
-type: fixed
diff --git a/changelogs/unreleased/56851-blank-values-in-reactive-cache.yml b/changelogs/unreleased/56851-blank-values-in-reactive-cache.yml
new file mode 100644
index 00000000000..5b9253793be
--- /dev/null
+++ b/changelogs/unreleased/56851-blank-values-in-reactive-cache.yml
@@ -0,0 +1,5 @@
+---
+title: Allow empty values such as [] to be stored in reactive cache
+merge_request: 25283
+author:
+type: fixed
diff --git a/changelogs/unreleased/56863-system-messages-in-email.yml b/changelogs/unreleased/56863-system-messages-in-email.yml
new file mode 100644
index 00000000000..21a90aa95ee
--- /dev/null
+++ b/changelogs/unreleased/56863-system-messages-in-email.yml
@@ -0,0 +1,5 @@
+---
+title: Show header and footer system messages in email
+merge_request: 25474
+author:
+type: added
diff --git a/changelogs/unreleased/56871-list-issues-error.yml b/changelogs/unreleased/56871-list-issues-error.yml
new file mode 100644
index 00000000000..af5585c6b5d
--- /dev/null
+++ b/changelogs/unreleased/56871-list-issues-error.yml
@@ -0,0 +1,5 @@
+---
+title: Display error message when API call to list Sentry issues fails
+merge_request: 24936
+author:
+type: fixed
diff --git a/changelogs/unreleased/56873-only-load-syntax-highlighting-css-when-selected.yml b/changelogs/unreleased/56873-only-load-syntax-highlighting-css-when-selected.yml
new file mode 100644
index 00000000000..a7af8994852
--- /dev/null
+++ b/changelogs/unreleased/56873-only-load-syntax-highlighting-css-when-selected.yml
@@ -0,0 +1,5 @@
+---
+title: Only load syntax highlight CSS of selected theme
+merge_request: 25232
+author:
+type: performance
diff --git a/changelogs/unreleased/56937-edit-knative-domain-after-it-has-been-deployed.yml b/changelogs/unreleased/56937-edit-knative-domain-after-it-has-been-deployed.yml
new file mode 100644
index 00000000000..11d93b34700
--- /dev/null
+++ b/changelogs/unreleased/56937-edit-knative-domain-after-it-has-been-deployed.yml
@@ -0,0 +1,5 @@
+---
+title: Fixes functions finder for upgraded Knative app
+merge_request: 25067
+author:
+type: fixed
diff --git a/changelogs/unreleased/56938-diff-file-headers-on-compare-not-quite-right.yml b/changelogs/unreleased/56938-diff-file-headers-on-compare-not-quite-right.yml
deleted file mode 100644
index f619a009a63..00000000000
--- a/changelogs/unreleased/56938-diff-file-headers-on-compare-not-quite-right.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Correct spacing for comparison page
-merge_request: !24783
-author:
-type: fixed
diff --git a/changelogs/unreleased/57063-implement-new-arguments-iid-for-issuesresolver-in-graphql.yml b/changelogs/unreleased/57063-implement-new-arguments-iid-for-issuesresolver-in-graphql.yml
deleted file mode 100644
index b05ab07e14c..00000000000
--- a/changelogs/unreleased/57063-implement-new-arguments-iid-for-issuesresolver-in-graphql.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add argument iids for issues in GraphQL
-merge_request: 24802
-author:
-type: added
diff --git a/changelogs/unreleased/57101-api-docs-for-hangouts-chat-service-incorrect.yml b/changelogs/unreleased/57101-api-docs-for-hangouts-chat-service-incorrect.yml
new file mode 100644
index 00000000000..2e0ae9c3732
--- /dev/null
+++ b/changelogs/unreleased/57101-api-docs-for-hangouts-chat-service-incorrect.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: Fix docs and parameters for hangouts-chat service'
+merge_request: 25180
+author: Robert Schilling
+type: fixed
diff --git a/changelogs/unreleased/57227-absolute-uri-missing-hierarchical-segment.yml b/changelogs/unreleased/57227-absolute-uri-missing-hierarchical-segment.yml
deleted file mode 100644
index 3a663ce2132..00000000000
--- a/changelogs/unreleased/57227-absolute-uri-missing-hierarchical-segment.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix potential Addressable::URI::InvalidURIError
-merge_request: 24908
-author:
-type: fixed
diff --git a/changelogs/unreleased/57353-git-push-fails-on-large-lfs-files-where-the-push-take-a-long-time.yml b/changelogs/unreleased/57353-git-push-fails-on-large-lfs-files-where-the-push-take-a-long-time.yml
new file mode 100644
index 00000000000..46f82afda62
--- /dev/null
+++ b/changelogs/unreleased/57353-git-push-fails-on-large-lfs-files-where-the-push-take-a-long-time.yml
@@ -0,0 +1,5 @@
+---
+title: Provide expires_in in LFS authentication payload
+merge_request: 25082
+author:
+type: fixed
diff --git a/changelogs/unreleased/57410-api-create-release-link-with-ftp-address-return-400-bad-request.yml b/changelogs/unreleased/57410-api-create-release-link-with-ftp-address-return-400-bad-request.yml
new file mode 100644
index 00000000000..6be6a2115b9
--- /dev/null
+++ b/changelogs/unreleased/57410-api-create-release-link-with-ftp-address-return-400-bad-request.yml
@@ -0,0 +1,5 @@
+---
+title: Add support for FTP assets for releases
+merge_request: 25071
+author: Robert Schilling
+type: added
diff --git a/changelogs/unreleased/57544-web-ide-new-directory-dialog-shows-file-templates.yml b/changelogs/unreleased/57544-web-ide-new-directory-dialog-shows-file-templates.yml
new file mode 100644
index 00000000000..9d9158ca4af
--- /dev/null
+++ b/changelogs/unreleased/57544-web-ide-new-directory-dialog-shows-file-templates.yml
@@ -0,0 +1,5 @@
+---
+title: Do not show file templates when creating a new directory in WebIDE
+merge_request: !25119
+author:
+type: fixed
diff --git a/changelogs/unreleased/57579-gitlab-project-import-fails-sidekiq-undefined-method-import_jid.yml b/changelogs/unreleased/57579-gitlab-project-import-fails-sidekiq-undefined-method-import_jid.yml
new file mode 100644
index 00000000000..f7d6a6c4863
--- /dev/null
+++ b/changelogs/unreleased/57579-gitlab-project-import-fails-sidekiq-undefined-method-import_jid.yml
@@ -0,0 +1,5 @@
+---
+title: Fix import_jid error on project import
+merge_request: 25239
+author:
+type: fixed
diff --git a/changelogs/unreleased/57582-dropdown-icon-misalignment-on-issues-list-on-mobile-screen.yml b/changelogs/unreleased/57582-dropdown-icon-misalignment-on-issues-list-on-mobile-screen.yml
new file mode 100644
index 00000000000..5681309cb9e
--- /dev/null
+++ b/changelogs/unreleased/57582-dropdown-icon-misalignment-on-issues-list-on-mobile-screen.yml
@@ -0,0 +1,5 @@
+---
+title: Fix alignment of dropdown icon on issuable on mobile
+merge_request: 25205
+author: Takuya Noguchi
+type: fixed
diff --git a/changelogs/unreleased/57671-fix_merge_request_base_pipeline.yml b/changelogs/unreleased/57671-fix_merge_request_base_pipeline.yml
new file mode 100644
index 00000000000..d89819eee60
--- /dev/null
+++ b/changelogs/unreleased/57671-fix_merge_request_base_pipeline.yml
@@ -0,0 +1,5 @@
+---
+title: Ensure the base pipeline of a Merge Request belongs to its target branch
+merge_request: 25226
+author:
+type: fixed
diff --git a/changelogs/unreleased/57712-project-import-error-user-expected-got-hash.yml b/changelogs/unreleased/57712-project-import-error-user-expected-got-hash.yml
new file mode 100644
index 00000000000..6fb198e1552
--- /dev/null
+++ b/changelogs/unreleased/57712-project-import-error-user-expected-got-hash.yml
@@ -0,0 +1,5 @@
+---
+title: Fix project import error importing releases
+merge_request: 25495
+author:
+type: fixed
diff --git a/changelogs/unreleased/57734-improve-label-dropdown-selection-performance.yml b/changelogs/unreleased/57734-improve-label-dropdown-selection-performance.yml
new file mode 100644
index 00000000000..781446b86d7
--- /dev/null
+++ b/changelogs/unreleased/57734-improve-label-dropdown-selection-performance.yml
@@ -0,0 +1,5 @@
+---
+title: Improve label select rendering
+merge_request: 25281
+author:
+type: performance
diff --git a/changelogs/unreleased/57768-remove-vertical-line.yml b/changelogs/unreleased/57768-remove-vertical-line.yml
new file mode 100644
index 00000000000..b73b0fa229e
--- /dev/null
+++ b/changelogs/unreleased/57768-remove-vertical-line.yml
@@ -0,0 +1,5 @@
+---
+title: Remove vertical connecting line placeholder from diff discussion notes
+merge_request: 25292
+author:
+type: fixed
diff --git a/changelogs/unreleased/57784-make-closed-duplicate-and-closed-moved-button-a-link-to-target.yml b/changelogs/unreleased/57784-make-closed-duplicate-and-closed-moved-button-a-link-to-target.yml
new file mode 100644
index 00000000000..2775d9f4e36
--- /dev/null
+++ b/changelogs/unreleased/57784-make-closed-duplicate-and-closed-moved-button-a-link-to-target.yml
@@ -0,0 +1,5 @@
+---
+title: Add Link from Closed (moved) Issues to Moved Issue
+merge_request: 25300
+author:
+type: added
diff --git a/changelogs/unreleased/57785-create-project-template-for-netlify.yml b/changelogs/unreleased/57785-create-project-template-for-netlify.yml
new file mode 100644
index 00000000000..78e9e3dece5
--- /dev/null
+++ b/changelogs/unreleased/57785-create-project-template-for-netlify.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve Create Project Template for Netlify
+merge_request: 25453
+author:
+type: changed
diff --git a/changelogs/unreleased/57788-project-labels-tooltip-missing.yml b/changelogs/unreleased/57788-project-labels-tooltip-missing.yml
new file mode 100644
index 00000000000..9146af0e0f3
--- /dev/null
+++ b/changelogs/unreleased/57788-project-labels-tooltip-missing.yml
@@ -0,0 +1,5 @@
+---
+title: Fix bug where project topics truncate
+merge_request: 25398
+author:
+type: fixed
diff --git a/changelogs/unreleased/57794-project-template-for-net.yml b/changelogs/unreleased/57794-project-template-for-net.yml
new file mode 100644
index 00000000000..bc05ac10aff
--- /dev/null
+++ b/changelogs/unreleased/57794-project-template-for-net.yml
@@ -0,0 +1,5 @@
+---
+title: Add Project template for .NET Core
+merge_request: 25486
+author:
+type: changed
diff --git a/changelogs/unreleased/57905-etag-caching-probably-broken-since-11-5-0.yml b/changelogs/unreleased/57905-etag-caching-probably-broken-since-11-5-0.yml
new file mode 100644
index 00000000000..046ef8ee99e
--- /dev/null
+++ b/changelogs/unreleased/57905-etag-caching-probably-broken-since-11-5-0.yml
@@ -0,0 +1,5 @@
+---
+title: Fix ETag caching not being used for AJAX requests
+merge_request: 25400
+author:
+type: fixed
diff --git a/changelogs/unreleased/57991-frontend-pagination-needs-to-handle-cases-where-the-x-total-pages-header-isn-t-present.yml b/changelogs/unreleased/57991-frontend-pagination-needs-to-handle-cases-where-the-x-total-pages-header-isn-t-present.yml
new file mode 100644
index 00000000000..2e18377a4cf
--- /dev/null
+++ b/changelogs/unreleased/57991-frontend-pagination-needs-to-handle-cases-where-the-x-total-pages-header-isn-t-present.yml
@@ -0,0 +1,5 @@
+---
+title: "Improve the JS pagination to handle the case when the `X-Total` and `X-Total-Pages` headers aren't present"
+merge_request: 25601
+author:
+type: fixed
diff --git a/changelogs/unreleased/58020-fix-merge-api-endpoint-param.yml b/changelogs/unreleased/58020-fix-merge-api-endpoint-param.yml
new file mode 100644
index 00000000000..7cfeb4a0cd7
--- /dev/null
+++ b/changelogs/unreleased/58020-fix-merge-api-endpoint-param.yml
@@ -0,0 +1,5 @@
+---
+title: Respect the should_remove_source_branch parameter to the merge API
+merge_request: 25525
+author:
+type: fixed
diff --git a/changelogs/unreleased/58082-project-template-for-go-micro.yml b/changelogs/unreleased/58082-project-template-for-go-micro.yml
new file mode 100644
index 00000000000..63a70cda0b8
--- /dev/null
+++ b/changelogs/unreleased/58082-project-template-for-go-micro.yml
@@ -0,0 +1,5 @@
+---
+title: Add Project template for go-micro
+merge_request: 25553
+author:
+type: changed
diff --git a/changelogs/unreleased/58098-auto-devops-postgres-version-variable.yml b/changelogs/unreleased/58098-auto-devops-postgres-version-variable.yml
new file mode 100644
index 00000000000..a7a87f60c28
--- /dev/null
+++ b/changelogs/unreleased/58098-auto-devops-postgres-version-variable.yml
@@ -0,0 +1,5 @@
+---
+title: Allow configuring POSTGRES_VERSION in Auto DevOps
+merge_request: 25500
+author:
+type: added
diff --git a/changelogs/unreleased/58149-fix-read-list-board-policy.yml b/changelogs/unreleased/58149-fix-read-list-board-policy.yml
new file mode 100644
index 00000000000..964813f4c9a
--- /dev/null
+++ b/changelogs/unreleased/58149-fix-read-list-board-policy.yml
@@ -0,0 +1,6 @@
+---
+title: Fix error when viewing group issue boards when user doesn't have explicit group
+ permissions
+merge_request: 25524
+author:
+type: fixed
diff --git a/changelogs/unreleased/58274-folder-icon-in-tags-page.yml b/changelogs/unreleased/58274-folder-icon-in-tags-page.yml
new file mode 100644
index 00000000000..db8128b8dfd
--- /dev/null
+++ b/changelogs/unreleased/58274-folder-icon-in-tags-page.yml
@@ -0,0 +1,5 @@
+---
+title: Use 'folder-open' from sprite icons for Browse Files button in Tag page
+merge_request: 25635
+author:
+type: fixed
diff --git a/changelogs/unreleased/8688-recursive-pipelines-ce-backport.yml b/changelogs/unreleased/8688-recursive-pipelines-ce-backport.yml
deleted file mode 100644
index cd7b56a1e05..00000000000
--- a/changelogs/unreleased/8688-recursive-pipelines-ce-backport.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Creates mixin to reduce code duplication between CE and EE in graph component
-merge_request:
-author:
-type: other
diff --git a/changelogs/unreleased/9841-geo-unable-to-compare-branches-on-secondary.yml b/changelogs/unreleased/9841-geo-unable-to-compare-branches-on-secondary.yml
new file mode 100644
index 00000000000..c014edf9c09
--- /dev/null
+++ b/changelogs/unreleased/9841-geo-unable-to-compare-branches-on-secondary.yml
@@ -0,0 +1,5 @@
+---
+title: Allow users to compare branches on a read-only instance
+merge_request: 25414
+author:
+type: fixed
diff --git a/changelogs/unreleased/Projects--dropdown-is-misaligned-on-issue-boards-page.yml b/changelogs/unreleased/Projects--dropdown-is-misaligned-on-issue-boards-page.yml
deleted file mode 100644
index 49511294c48..00000000000
--- a/changelogs/unreleased/Projects--dropdown-is-misaligned-on-issue-boards-page.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Proper align Projects dropdown on issue boards page
-merge_request: 24277
-author: Johann Hubert Sonntagbauer
-type: fixed
diff --git a/changelogs/unreleased/ab-54270-github-iid.yml b/changelogs/unreleased/ab-54270-github-iid.yml
deleted file mode 100644
index 1776b0aeb86..00000000000
--- a/changelogs/unreleased/ab-54270-github-iid.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Improve efficiency of GitHub importer by reducing amount of locks needed.
-merge_request: 24102
-author:
-type: performance
diff --git a/changelogs/unreleased/ac-pages-subgroups.yml b/changelogs/unreleased/ac-pages-subgroups.yml
deleted file mode 100644
index ef5a0c1872e..00000000000
--- a/changelogs/unreleased/ac-pages-subgroups.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Pages for subgroups
-merge_request: 23505
-author:
-type: added
diff --git a/changelogs/unreleased/actioncontroller-parameters-deprecations.yml b/changelogs/unreleased/actioncontroller-parameters-deprecations.yml
deleted file mode 100644
index ddd15c37542..00000000000
--- a/changelogs/unreleased/actioncontroller-parameters-deprecations.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix several ActionController::Parameters deprecations
-merge_request: 24332
-author: Jasper Maes
-type: other
diff --git a/changelogs/unreleased/add-badge-count-to-projects-and-groups.yml b/changelogs/unreleased/add-badge-count-to-projects-and-groups.yml
deleted file mode 100644
index e200bbaa806..00000000000
--- a/changelogs/unreleased/add-badge-count-to-projects-and-groups.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add badge count to projects
-merge_request: 18425
-author: George Tsiolis
-type: added
diff --git a/changelogs/unreleased/add-project-level-config-for-prospective-merge-pipelines-ce.yml b/changelogs/unreleased/add-project-level-config-for-prospective-merge-pipelines-ce.yml
new file mode 100644
index 00000000000..39d7ead9af4
--- /dev/null
+++ b/changelogs/unreleased/add-project-level-config-for-prospective-merge-pipelines-ce.yml
@@ -0,0 +1,5 @@
+---
+title: Add project level config for merge pipelines
+merge_request: 25385
+author:
+type: added
diff --git a/changelogs/unreleased/add-related-merge-request-count-to-api-response.yml b/changelogs/unreleased/add-related-merge-request-count-to-api-response.yml
new file mode 100644
index 00000000000..7438053a84f
--- /dev/null
+++ b/changelogs/unreleased/add-related-merge-request-count-to-api-response.yml
@@ -0,0 +1,5 @@
+---
+title: Add related merge request count to api response
+merge_request: 24974
+author:
+type: added
diff --git a/changelogs/unreleased/add-title-attribute-to-file-row.yml b/changelogs/unreleased/add-title-attribute-to-file-row.yml
new file mode 100644
index 00000000000..c68d3d544e7
--- /dev/null
+++ b/changelogs/unreleased/add-title-attribute-to-file-row.yml
@@ -0,0 +1,5 @@
+---
+title: add title attribute to display file name
+merge_request: 25154
+author: Satoshi Nakamatsu @satoshicano
+type: added
diff --git a/changelogs/unreleased/add-uniqueness-validation-to-url-column-in-releases-link-model.yml b/changelogs/unreleased/add-uniqueness-validation-to-url-column-in-releases-link-model.yml
deleted file mode 100644
index 7d767e220f7..00000000000
--- a/changelogs/unreleased/add-uniqueness-validation-to-url-column-in-releases-link-model.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add uniqueness validation to url column in Releases::Link model
-merge_request: 24223
-author:
-type: other
diff --git a/changelogs/unreleased/add-youtrack-integration.yml b/changelogs/unreleased/add-youtrack-integration.yml
new file mode 100644
index 00000000000..f500e625145
--- /dev/null
+++ b/changelogs/unreleased/add-youtrack-integration.yml
@@ -0,0 +1,5 @@
+---
+title: Add YouTrack integration service
+merge_request: 25361
+author: Yauhen Kotau @bessorion
+type: added
diff --git a/changelogs/unreleased/adrianmoisey-GITLAB_PAGES_PREDEFINED_VARIABLES.yml b/changelogs/unreleased/adrianmoisey-GITLAB_PAGES_PREDEFINED_VARIABLES.yml
deleted file mode 100644
index a664c44e1d7..00000000000
--- a/changelogs/unreleased/adrianmoisey-GITLAB_PAGES_PREDEFINED_VARIABLES.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add GitLab Pages predefined CI variables 'CI_PAGES_DOMAIN' and 'CI_PAGES_URL'
-merge_request: 24504
-author: Adrian Moisey
-type: added
diff --git a/changelogs/unreleased/adriel-remove-feature-flag.yml b/changelogs/unreleased/adriel-remove-feature-flag.yml
deleted file mode 100644
index d442e120d60..00000000000
--- a/changelogs/unreleased/adriel-remove-feature-flag.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update metrics dashboard graph design
-merge_request: 24653
-author:
-type: changed
diff --git a/changelogs/unreleased/allow-maintainers-to-remove-pages.yml b/changelogs/unreleased/allow-maintainers-to-remove-pages.yml
new file mode 100644
index 00000000000..6e344dbe0e9
--- /dev/null
+++ b/changelogs/unreleased/allow-maintainers-to-remove-pages.yml
@@ -0,0 +1,5 @@
+---
+title: Allow maintainers to remove pages
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/an-dtracing-test-for-invalid-tracers.yml b/changelogs/unreleased/an-dtracing-test-for-invalid-tracers.yml
deleted file mode 100644
index 5365260cbae..00000000000
--- a/changelogs/unreleased/an-dtracing-test-for-invalid-tracers.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Avoid overwriting default jaeger values with nil
-merge_request: 24482
-author:
-type: fixed
diff --git a/changelogs/unreleased/an-gilab-process-name.yml b/changelogs/unreleased/an-gilab-process-name.yml
deleted file mode 100644
index 72d811ee21f..00000000000
--- a/changelogs/unreleased/an-gilab-process-name.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Extract process_name from GitLab::Sentry
-merge_request: 24422
-author:
-type: other
diff --git a/changelogs/unreleased/an-opentracing-active-record-tracing.yml b/changelogs/unreleased/an-opentracing-active-record-tracing.yml
deleted file mode 100644
index 59b480675ec..00000000000
--- a/changelogs/unreleased/an-opentracing-active-record-tracing.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Adds tracing support for ActiveRecord notifications
-merge_request: 24604
-author:
-type: other
diff --git a/changelogs/unreleased/an-opentracing-factory.yml b/changelogs/unreleased/an-opentracing-factory.yml
deleted file mode 100644
index c04736f3e63..00000000000
--- a/changelogs/unreleased/an-opentracing-factory.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Conditionally initialize the global opentracing tracer
-merge_request: 24186
-author:
-type: other
diff --git a/changelogs/unreleased/an-opentracing-propagation.yml b/changelogs/unreleased/an-opentracing-propagation.yml
deleted file mode 100644
index d9aa7cd0048..00000000000
--- a/changelogs/unreleased/an-opentracing-propagation.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Adds inter-service OpenTracing propagation
-merge_request: 24239
-author:
-type: other
diff --git a/changelogs/unreleased/an-opentracing-render-tracing.yml b/changelogs/unreleased/an-opentracing-render-tracing.yml
deleted file mode 100644
index 6ff7f1f3cf2..00000000000
--- a/changelogs/unreleased/an-opentracing-render-tracing.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add OpenTracing instrumentation for Action View Render events
-merge_request: 24728
-author:
-type: other
diff --git a/changelogs/unreleased/api-group-labels.yml b/changelogs/unreleased/api-group-labels.yml
deleted file mode 100644
index 0df6f15a9b6..00000000000
--- a/changelogs/unreleased/api-group-labels.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'API: Add support for group labels'
-merge_request: 21368
-author: Robert Schilling
-type: added
diff --git a/changelogs/unreleased/api-nested-group-permission.yml b/changelogs/unreleased/api-nested-group-permission.yml
deleted file mode 100644
index 3ec0df6893f..00000000000
--- a/changelogs/unreleased/api-nested-group-permission.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Return the maximum group access level in the projects API
-merge_request: 24403
-author:
-type: changed
diff --git a/changelogs/unreleased/api-tags-search.yml b/changelogs/unreleased/api-tags-search.yml
deleted file mode 100644
index 1501acd5a9e..00000000000
--- a/changelogs/unreleased/api-tags-search.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'API: Support searching for tags'
-merge_request: 24385
-author: Robert Schilling
-type: added
diff --git a/changelogs/unreleased/api-wiki-dot-slug.yml b/changelogs/unreleased/api-wiki-dot-slug.yml
deleted file mode 100644
index 82c76fa7450..00000000000
--- a/changelogs/unreleased/api-wiki-dot-slug.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'API: Support dots in wiki slugs'
-merge_request: 24383
-author: Robert Schilling
-type: fixed
diff --git a/changelogs/unreleased/auto-devops-custom-domains.yml b/changelogs/unreleased/auto-devops-custom-domains.yml
deleted file mode 100644
index 37e8ee26a4d..00000000000
--- a/changelogs/unreleased/auto-devops-custom-domains.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Added support for custom hosts/domains to Auto DevOps
-merge_request: 24248
-author: walkafwalka
-type: added
diff --git a/changelogs/unreleased/auto-devops-kubectl-1-11-6.yml b/changelogs/unreleased/auto-devops-kubectl-1-11-6.yml
deleted file mode 100644
index 1a8cdead4ac..00000000000
--- a/changelogs/unreleased/auto-devops-kubectl-1-11-6.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Bump kubectl in Auto DevOps to 1.11.6
-merge_request: 24176
-author:
-type: other
diff --git a/changelogs/unreleased/backup_aws_sse-c.yml b/changelogs/unreleased/backup_aws_sse-c.yml
deleted file mode 100644
index 78b57d7efc3..00000000000
--- a/changelogs/unreleased/backup_aws_sse-c.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-title: Add support for customer provided encryption keys for Amazon S3 remote backups
-merge_request: 23797
-author: Pepijn Van Eeckhoudt
-type: added
-
diff --git a/changelogs/unreleased/backup_restore_fix_issue_46891.yml b/changelogs/unreleased/backup_restore_fix_issue_46891.yml
deleted file mode 100644
index b8fe3b1b861..00000000000
--- a/changelogs/unreleased/backup_restore_fix_issue_46891.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Modify file restore to rectify tar issue
-merge_request: 24000
-author:
-type: fixed
diff --git a/changelogs/unreleased/bump-ingress-chart-112.yml b/changelogs/unreleased/bump-ingress-chart-112.yml
deleted file mode 100644
index 8a46fedb4b0..00000000000
--- a/changelogs/unreleased/bump-ingress-chart-112.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Bump nginx-ingress chart to 1.1.2
-merge_request: 24203
-author:
-type: other
diff --git a/changelogs/unreleased/bvl-fix-race-condition-creating-signature.yml b/changelogs/unreleased/bvl-fix-race-condition-creating-signature.yml
deleted file mode 100644
index 307b4f526bb..00000000000
--- a/changelogs/unreleased/bvl-fix-race-condition-creating-signature.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Avoid race conditions when creating GpgSignature
-merge_request: 24939
-author:
-type: fixed
diff --git a/changelogs/unreleased/change-badges-example-to-pipeline.yml b/changelogs/unreleased/change-badges-example-to-pipeline.yml
new file mode 100644
index 00000000000..8ed4d77fd6c
--- /dev/null
+++ b/changelogs/unreleased/change-badges-example-to-pipeline.yml
@@ -0,0 +1,5 @@
+---
+title: Change badges.svg example to pipeline.svg
+merge_request: 25157
+author: Aviad Levy
+type: fixed
diff --git a/changelogs/unreleased/changelogs-readme.yml b/changelogs/unreleased/changelogs-readme.yml
new file mode 100644
index 00000000000..9f391699575
--- /dev/null
+++ b/changelogs/unreleased/changelogs-readme.yml
@@ -0,0 +1,5 @@
+---
+title: add readme to changelogs directory
+merge_request: 25209
+author: "@glensc"
+type: added
diff --git a/changelogs/unreleased/chore-update-js-regex.yml b/changelogs/unreleased/chore-update-js-regex.yml
deleted file mode 100644
index d45d0b47457..00000000000
--- a/changelogs/unreleased/chore-update-js-regex.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Upgrade js-regex gem to version 3.1
-merge_request: 24433
-author: rroger
-type: changed
diff --git a/changelogs/unreleased/cleanup-leagcy-artifact-migration.yml b/changelogs/unreleased/cleanup-leagcy-artifact-migration.yml
deleted file mode 100644
index 6e8dac97249..00000000000
--- a/changelogs/unreleased/cleanup-leagcy-artifact-migration.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Cleanup legacy artifact background migration
-merge_request: 24144
-author:
-type: other
diff --git a/changelogs/unreleased/cluster_application_version_updated.yml b/changelogs/unreleased/cluster_application_version_updated.yml
deleted file mode 100644
index 34fe55dcc5e..00000000000
--- a/changelogs/unreleased/cluster_application_version_updated.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update cluster application version on updated and installed status
-merge_request: 24810
-author:
-type: other
diff --git a/changelogs/unreleased/cluster_status_for_ugprading.yml b/changelogs/unreleased/cluster_status_for_ugprading.yml
deleted file mode 100644
index ca1f8b3a786..00000000000
--- a/changelogs/unreleased/cluster_status_for_ugprading.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Expose version for each application in cluster_status JSON endpoint
-merge_request: 24791
-author:
-type: other
diff --git a/changelogs/unreleased/consistent-pagination.yml b/changelogs/unreleased/consistent-pagination.yml
new file mode 100644
index 00000000000..95eefaeb31d
--- /dev/null
+++ b/changelogs/unreleased/consistent-pagination.yml
@@ -0,0 +1,5 @@
+---
+title: Fix inconsistent pagination styles
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/container-repository-cleanup-api.yml b/changelogs/unreleased/container-repository-cleanup-api.yml
deleted file mode 100644
index c2b23a9add0..00000000000
--- a/changelogs/unreleased/container-repository-cleanup-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add Container Registry API with cleanup function
-merge_request: 24303
-author:
-type: added
diff --git a/changelogs/unreleased/custom-helm-chart-repo.yml b/changelogs/unreleased/custom-helm-chart-repo.yml
deleted file mode 100644
index 592d2f60ca2..00000000000
--- a/changelogs/unreleased/custom-helm-chart-repo.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Added feature to specify a custom Auto DevOps chart repository
-merge_request: 24162
-author: walkafwalka
-type: added
diff --git a/changelogs/unreleased/deprecated-force-reload.yml b/changelogs/unreleased/deprecated-force-reload.yml
deleted file mode 100644
index 2a0e97089e0..00000000000
--- a/changelogs/unreleased/deprecated-force-reload.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: 'Fix deprecation: Passing an argument to force an association to reload is
- now deprecated'
-merge_request: 24136
-author: Jasper Maes
-type: other
diff --git a/changelogs/unreleased/diff-file-finder.yml b/changelogs/unreleased/diff-file-finder.yml
deleted file mode 100644
index 3160e9fc91b..00000000000
--- a/changelogs/unreleased/diff-file-finder.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Added fuzzy file finder to merge requests
-merge_request:
-author:
-type: changed
diff --git a/changelogs/unreleased/diff-tree-collapse-directories.yml b/changelogs/unreleased/diff-tree-collapse-directories.yml
deleted file mode 100644
index 6eae48f2352..00000000000
--- a/changelogs/unreleased/diff-tree-collapse-directories.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Collapse directory structure in merge request file tree
-merge_request:
-author:
-type: changed
diff --git a/changelogs/unreleased/diff-tree-resizable.yml b/changelogs/unreleased/diff-tree-resizable.yml
new file mode 100644
index 00000000000..7411640aea5
--- /dev/null
+++ b/changelogs/unreleased/diff-tree-resizable.yml
@@ -0,0 +1,5 @@
+---
+title: Make file tree in merge requests resizable
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/dm-copy-suggestion-as-gfm.yml b/changelogs/unreleased/dm-copy-suggestion-as-gfm.yml
deleted file mode 100644
index 96115e6ade1..00000000000
--- a/changelogs/unreleased/dm-copy-suggestion-as-gfm.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow suggestions to be copied and pasted as GFM
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/dm-trim-discussion-truncated-line-first-chars.yml b/changelogs/unreleased/dm-trim-discussion-truncated-line-first-chars.yml
deleted file mode 100644
index 1e1fa8295c3..00000000000
--- a/changelogs/unreleased/dm-trim-discussion-truncated-line-first-chars.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix bug that caused Suggestion Markdown toolbar button to insert snippet with leading +/-/<space>
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/docs-push-mirror-GitLab-GitHub.yml b/changelogs/unreleased/docs-push-mirror-GitLab-GitHub.yml
deleted file mode 100644
index 4539a9b7985..00000000000
--- a/changelogs/unreleased/docs-push-mirror-GitLab-GitHub.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Updated docs for fields in pushing mirror from GitLab to GitHub
-merge_request: 24566
-author: Joseph Yu
-type: other
diff --git a/changelogs/unreleased/dz-sort-labels-alphabetically.yml b/changelogs/unreleased/dz-sort-labels-alphabetically.yml
new file mode 100644
index 00000000000..acfde3de999
--- /dev/null
+++ b/changelogs/unreleased/dz-sort-labels-alphabetically.yml
@@ -0,0 +1,5 @@
+---
+title: Sort labels alphabetically on issues and merge requests list
+merge_request: 25470
+author:
+type: changed
diff --git a/changelogs/unreleased/expire-job-artifacts-worker.yml b/changelogs/unreleased/expire-job-artifacts-worker.yml
deleted file mode 100644
index cda6e9ff497..00000000000
--- a/changelogs/unreleased/expire-job-artifacts-worker.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Efficiently remove expired artifacts in `ExpireBuildArtifactsWorker`
-merge_request: 24450
-author:
-type: performance
diff --git a/changelogs/unreleased/expose-merge-ref-to-runner.yml b/changelogs/unreleased/expose-merge-ref-to-runner.yml
new file mode 100644
index 00000000000..945f4f6e05a
--- /dev/null
+++ b/changelogs/unreleased/expose-merge-ref-to-runner.yml
@@ -0,0 +1,5 @@
+---
+title: Expose refspecs and depth to runner
+merge_request: 25233
+author:
+type: added
diff --git a/changelogs/unreleased/feature-gb-enable-ci-persisted-stages-by-default.yml b/changelogs/unreleased/feature-gb-enable-ci-persisted-stages-by-default.yml
new file mode 100644
index 00000000000..ad92135d401
--- /dev/null
+++ b/changelogs/unreleased/feature-gb-enable-ci-persisted-stages-by-default.yml
@@ -0,0 +1,5 @@
+---
+title: Enable persisted pipeline stages by default
+merge_request: 25347
+author:
+type: performance
diff --git a/changelogs/unreleased/feature-runner-tag-filter-for-admin-view.yml b/changelogs/unreleased/feature-runner-tag-filter-for-admin-view.yml
new file mode 100644
index 00000000000..86df4595e7b
--- /dev/null
+++ b/changelogs/unreleased/feature-runner-tag-filter-for-admin-view.yml
@@ -0,0 +1,5 @@
+---
+title: Add a tag filter to the admin runners view
+merge_request: 19740
+author: Alexis Reigel
+type: added
diff --git a/changelogs/unreleased/features-document-graphicsmagick-source-installation.yml b/changelogs/unreleased/features-document-graphicsmagick-source-installation.yml
deleted file mode 100644
index b224cace4bf..00000000000
--- a/changelogs/unreleased/features-document-graphicsmagick-source-installation.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Document graphicsmagick installation for source installation
-merge_request: 24404
-author: Alexis Reigel
-type: added
diff --git a/changelogs/unreleased/filter-confidential-issues.yml b/changelogs/unreleased/filter-confidential-issues.yml
new file mode 100644
index 00000000000..83f19a57aab
--- /dev/null
+++ b/changelogs/unreleased/filter-confidential-issues.yml
@@ -0,0 +1,5 @@
+---
+title: Ability to filter confidential issues
+merge_request: 24960
+author: Robert Schilling
+type: added
diff --git a/changelogs/unreleased/filter-note-parameters.yml b/changelogs/unreleased/filter-note-parameters.yml
new file mode 100644
index 00000000000..fca2a394820
--- /dev/null
+++ b/changelogs/unreleased/filter-note-parameters.yml
@@ -0,0 +1,5 @@
+---
+title: Include note in the Rails filter_parameters configuration
+merge_request: 25238
+author:
+type: other
diff --git a/changelogs/unreleased/fix-39759-new-project-icon-vertical-align.yml b/changelogs/unreleased/fix-39759-new-project-icon-vertical-align.yml
deleted file mode 100644
index 3d87807dbc1..00000000000
--- a/changelogs/unreleased/fix-39759-new-project-icon-vertical-align.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Adjust vertical alignment for project visibility icons
-merge_request: 24511
-author: Martin Hobert
-type: fixed
diff --git a/changelogs/unreleased/fix-403-page-is-rendered-but-404-is-the-response.yml b/changelogs/unreleased/fix-403-page-is-rendered-but-404-is-the-response.yml
deleted file mode 100644
index eda69b32094..00000000000
--- a/changelogs/unreleased/fix-403-page-is-rendered-but-404-is-the-response.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Show the correct error page when access is denied
-merge_request: 23932
-author:
-type: fixed
diff --git a/changelogs/unreleased/fix-49388.yml b/changelogs/unreleased/fix-49388.yml
deleted file mode 100644
index f8b5e3e1943..00000000000
--- a/changelogs/unreleased/fix-49388.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update metrics environment dropdown to show complete option set
-merge_request: 24441
-author:
-type: fixed
diff --git a/changelogs/unreleased/fix-55956-oversized-dropdown-button-custom-notifications.yml b/changelogs/unreleased/fix-55956-oversized-dropdown-button-custom-notifications.yml
deleted file mode 100644
index e33699a2112..00000000000
--- a/changelogs/unreleased/fix-55956-oversized-dropdown-button-custom-notifications.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixed oversized custom project notification selector dropdown
-merge_request: 24557
-author:
-type: fixed
diff --git a/changelogs/unreleased/fix-56558-move-primary-button.yml b/changelogs/unreleased/fix-56558-move-primary-button.yml
deleted file mode 100644
index 4dcc896b327..00000000000
--- a/changelogs/unreleased/fix-56558-move-primary-button.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Moved primary button for labels to follow the design patterns used on rest of the site
-merge_request:
-author: Martin Hobert
-type: fixed
diff --git a/changelogs/unreleased/fix-auto-devops-domain-title-on-admin-settings.yml b/changelogs/unreleased/fix-auto-devops-domain-title-on-admin-settings.yml
deleted file mode 100644
index bb0b193a846..00000000000
--- a/changelogs/unreleased/fix-auto-devops-domain-title-on-admin-settings.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixes Auto DevOps title on CI/CD admin settings
-merge_request: 24249
-author:
-type: other
diff --git a/changelogs/unreleased/fix-badges-logs.yml b/changelogs/unreleased/fix-badges-logs.yml
new file mode 100644
index 00000000000..6236e7b046d
--- /dev/null
+++ b/changelogs/unreleased/fix-badges-logs.yml
@@ -0,0 +1,5 @@
+---
+title: Doc - fix the url of pipeline status badge
+merge_request: 25404
+author: Aviad Levy
+type: fixed
diff --git a/changelogs/unreleased/fix-repo-settings-file-upload-error.yml b/changelogs/unreleased/fix-repo-settings-file-upload-error.yml
deleted file mode 100644
index b219fdfaa1e..00000000000
--- a/changelogs/unreleased/fix-repo-settings-file-upload-error.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix bug causing repository mirror settings UI to break
-merge_request: 23712
-author:
-type: fixed
diff --git a/changelogs/unreleased/fix_jira_integration_VCS1019.yml b/changelogs/unreleased/fix_jira_integration_VCS1019.yml
deleted file mode 100644
index 3582ec1fe0f..00000000000
--- a/changelogs/unreleased/fix_jira_integration_VCS1019.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix Jira Service password validation on project integration services.
-merge_request: 24896
-author: Daniel Juarez
-type: fixed
diff --git a/changelogs/unreleased/fj-55882-fix-files-api-content-disposition.yml b/changelogs/unreleased/fj-55882-fix-files-api-content-disposition.yml
deleted file mode 100644
index f64b29644b0..00000000000
--- a/changelogs/unreleased/fj-55882-fix-files-api-content-disposition.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix files/blob api endpoints content disposition
-merge_request: 24267
-author:
-type: fixed
diff --git a/changelogs/unreleased/force-redeploy-on-updated-secrets.yml b/changelogs/unreleased/force-redeploy-on-updated-secrets.yml
deleted file mode 100644
index 3b727c99dd5..00000000000
--- a/changelogs/unreleased/force-redeploy-on-updated-secrets.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Redeploy Auto DevOps deployment on variable updates
-merge_request: 24498
-author: walkafwalka
-type: added
diff --git a/changelogs/unreleased/gitaly-update-1-13-0.yml b/changelogs/unreleased/gitaly-update-1-13-0.yml
deleted file mode 100644
index 73de25a532d..00000000000
--- a/changelogs/unreleased/gitaly-update-1-13-0.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Upgrade Gitaly to 1.13.0
-merge_request: 24429
-author:
-type: other
diff --git a/changelogs/unreleased/gitaly-update-1.18.0.yml b/changelogs/unreleased/gitaly-update-1.18.0.yml
deleted file mode 100644
index 392527f5e5d..00000000000
--- a/changelogs/unreleased/gitaly-update-1.18.0.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Upgrade gitaly to 1.18.0
-merge_request: 24981
-author:
-type: other
diff --git a/changelogs/unreleased/gitlab-workhorse-update-8.1.0.yml b/changelogs/unreleased/gitlab-workhorse-update-8.1.0.yml
deleted file mode 100644
index 1e0160c4d40..00000000000
--- a/changelogs/unreleased/gitlab-workhorse-update-8.1.0.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Upgrade gitlab-workhorse to 8.1.0
-merge_request: 24571
-author:
-type: other
diff --git a/changelogs/unreleased/gitlab_kubernetes_helm_bump.yml b/changelogs/unreleased/gitlab_kubernetes_helm_bump.yml
new file mode 100644
index 00000000000..b8668d338de
--- /dev/null
+++ b/changelogs/unreleased/gitlab_kubernetes_helm_bump.yml
@@ -0,0 +1,5 @@
+---
+title: Bump Helm and kubectl used in Kubernetes integration to 2.12.3 and 1.11.7 respectively
+merge_request: 25268
+author:
+type: other
diff --git a/changelogs/unreleased/gt-externalize-app-views-clusters.yml b/changelogs/unreleased/gt-externalize-app-views-clusters.yml
deleted file mode 100644
index 6d2284ead37..00000000000
--- a/changelogs/unreleased/gt-externalize-app-views-clusters.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Externalize strings from `/app/views/clusters`
-merge_request: 24666
-author: George Tsiolis
-type: other
diff --git a/changelogs/unreleased/gt-externalize-app-views-email_rejection_mailer.yml b/changelogs/unreleased/gt-externalize-app-views-email_rejection_mailer.yml
deleted file mode 100644
index 8f6fbdceb54..00000000000
--- a/changelogs/unreleased/gt-externalize-app-views-email_rejection_mailer.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Externalize strings from `/app/views/email_rejection_mailer`
-merge_request: 24869
-author: George Tsiolis
-type: other
diff --git a/changelogs/unreleased/gt-externalize-app-views-instance_statistics.yml b/changelogs/unreleased/gt-externalize-app-views-instance_statistics.yml
deleted file mode 100644
index a3bf54a1339..00000000000
--- a/changelogs/unreleased/gt-externalize-app-views-instance_statistics.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Externalize strings from `/app/views/instance_statistics`
-merge_request: 24809
-author: George Tsiolis
-type: other
diff --git a/changelogs/unreleased/gt-externalize-app-views-projects-ci.yml b/changelogs/unreleased/gt-externalize-app-views-projects-ci.yml
deleted file mode 100644
index ecc878ab892..00000000000
--- a/changelogs/unreleased/gt-externalize-app-views-projects-ci.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Externalize strings from `/app/views/projects/ci`
-merge_request: 24617
-author: George Tsiolis
-type: other
diff --git a/changelogs/unreleased/gt-externalize-app-views-projects-commit.yml b/changelogs/unreleased/gt-externalize-app-views-projects-commit.yml
new file mode 100644
index 00000000000..29dbf2367b7
--- /dev/null
+++ b/changelogs/unreleased/gt-externalize-app-views-projects-commit.yml
@@ -0,0 +1,5 @@
+---
+title: Externalize strings from `/app/views/projects/commit`
+merge_request: 24668
+author: George Tsiolis
+type: other
diff --git a/changelogs/unreleased/gt-externalize-app-views-projects-milestones.yml b/changelogs/unreleased/gt-externalize-app-views-projects-milestones.yml
deleted file mode 100644
index 56aaac812bb..00000000000
--- a/changelogs/unreleased/gt-externalize-app-views-projects-milestones.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Externalize strings from `/app/views/projects/milestones`
-merge_request: 24726
-author: George Tsiolis
-type: other
diff --git a/changelogs/unreleased/gt-externalize-app-views-projects-pages_domains.yml b/changelogs/unreleased/gt-externalize-app-views-projects-pages_domains.yml
deleted file mode 100644
index f60776a2ed8..00000000000
--- a/changelogs/unreleased/gt-externalize-app-views-projects-pages_domains.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Externalize strings from `/app/views/projects/pages_domains`
-merge_request: 24723
-author: George Tsiolis
-type: other
diff --git a/changelogs/unreleased/gt-externalize-app-views-projects-project_members.yml b/changelogs/unreleased/gt-externalize-app-views-projects-project_members.yml
deleted file mode 100644
index 1acea10fcaa..00000000000
--- a/changelogs/unreleased/gt-externalize-app-views-projects-project_members.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Externalize strings from `/app/views/projects/project_members`
-merge_request: 23227
-author: Tao Wang
-type: other
diff --git a/changelogs/unreleased/gt-externalize-app-views-sent_notifications.yml b/changelogs/unreleased/gt-externalize-app-views-sent_notifications.yml
deleted file mode 100644
index e77b5376fa8..00000000000
--- a/changelogs/unreleased/gt-externalize-app-views-sent_notifications.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Externalize strings from `/app/views/sent_notifications`
-merge_request: 24576
-author: George Tsiolis
-type: other
diff --git a/changelogs/unreleased/gt-remove-unused-button-class.yml b/changelogs/unreleased/gt-remove-unused-button-class.yml
deleted file mode 100644
index f7889e1d6f6..00000000000
--- a/changelogs/unreleased/gt-remove-unused-button-class.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove unused button classes `btn-create` and `comment-btn`
-merge_request: 23232
-author: George Tsiolis
-type: performance
diff --git a/changelogs/unreleased/gt-rename-gray-theme-color-variables.yml b/changelogs/unreleased/gt-rename-gray-theme-color-variables.yml
deleted file mode 100644
index b612bb3ee39..00000000000
--- a/changelogs/unreleased/gt-rename-gray-theme-color-variables.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove all `$theme-gray-{weight}` variables in favor of `$gray-{weight}`
-merge_request: 24333
-author: George Tsiolis
-type: other
diff --git a/changelogs/unreleased/gt-update-new-password-breadcrumb.yml b/changelogs/unreleased/gt-update-new-password-breadcrumb.yml
new file mode 100644
index 00000000000..43ea2f0d44b
--- /dev/null
+++ b/changelogs/unreleased/gt-update-new-password-breadcrumb.yml
@@ -0,0 +1,5 @@
+---
+title: Update new password breadcrumb
+merge_request: 25037
+author: George Tsiolis
+type: fixed
diff --git a/changelogs/unreleased/gt-update-operations-settings-breadcrumb-trail.yml b/changelogs/unreleased/gt-update-operations-settings-breadcrumb-trail.yml
new file mode 100644
index 00000000000..f7b10ea5c17
--- /dev/null
+++ b/changelogs/unreleased/gt-update-operations-settings-breadcrumb-trail.yml
@@ -0,0 +1,5 @@
+---
+title: Update operations settings breadcrumb trail
+merge_request: 25539
+author: George Tsiolis
+type: changed
diff --git a/changelogs/unreleased/gt-update-string-struture-for-group-runners.yml b/changelogs/unreleased/gt-update-string-struture-for-group-runners.yml
deleted file mode 100644
index fa06a78adae..00000000000
--- a/changelogs/unreleased/gt-update-string-struture-for-group-runners.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update string structure for available group runners
-merge_request: 24187
-author: George Tsiolis
-type: changed
diff --git a/changelogs/unreleased/helm-2-12-3.yml b/changelogs/unreleased/helm-2-12-3.yml
new file mode 100644
index 00000000000..0d0d904a9cb
--- /dev/null
+++ b/changelogs/unreleased/helm-2-12-3.yml
@@ -0,0 +1,5 @@
+---
+title: Bump Helm and kubectl in Auto DevOps to 2.12.3 and 1.11.7 respectively
+merge_request: 25072
+author:
+type: other
diff --git a/changelogs/unreleased/hnk-master-patch-61932.yml b/changelogs/unreleased/hnk-master-patch-61932.yml
deleted file mode 100644
index 8cc9d0057a9..00000000000
--- a/changelogs/unreleased/hnk-master-patch-61932.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update runner admin page to make description field larger
-merge_request: 23593
-author: Sascha Reynolds
-type: fixed
diff --git a/changelogs/unreleased/homepage-proj-descr-cutoff.yml b/changelogs/unreleased/homepage-proj-descr-cutoff.yml
deleted file mode 100644
index 837c01f6722..00000000000
--- a/changelogs/unreleased/homepage-proj-descr-cutoff.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Increase line height of project summaries
-merge_request:
-author: gfyoung
-type: fixed
diff --git a/changelogs/unreleased/import-go-to-project-cta.yml b/changelogs/unreleased/import-go-to-project-cta.yml
new file mode 100644
index 00000000000..ae719f08790
--- /dev/null
+++ b/changelogs/unreleased/import-go-to-project-cta.yml
@@ -0,0 +1,5 @@
+---
+title: Improve GitHub and Gitea project import table UI
+merge_request: 24606
+author:
+type: other
diff --git a/changelogs/unreleased/improve-snippets-empty-state.yml b/changelogs/unreleased/improve-snippets-empty-state.yml
new file mode 100644
index 00000000000..9859243a81f
--- /dev/null
+++ b/changelogs/unreleased/improve-snippets-empty-state.yml
@@ -0,0 +1,5 @@
+---
+title: Improve snippets empty state
+merge_request: 18348
+author: George Tsiolis
+type: changed
diff --git a/changelogs/unreleased/include-ci-yaml.yml b/changelogs/unreleased/include-ci-yaml.yml
new file mode 100644
index 00000000000..5909950ef0b
--- /dev/null
+++ b/changelogs/unreleased/include-ci-yaml.yml
@@ -0,0 +1,5 @@
+---
+title: Validate 'include' keywords in gitlab-ci.yml configuration files.
+merge_request: 24098
+author: Paul Bonaud
+type: fixed
diff --git a/changelogs/unreleased/introduce-environment-search-endpoint.yml b/changelogs/unreleased/introduce-environment-search-endpoint.yml
deleted file mode 100644
index 01851ba7d27..00000000000
--- a/changelogs/unreleased/introduce-environment-search-endpoint.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Introduce Internal API for searching environment names
-merge_request: 24923
-author:
-type: added
diff --git a/changelogs/unreleased/iss-32584-preserve-line-number-fragment-after-redirect.yml b/changelogs/unreleased/iss-32584-preserve-line-number-fragment-after-redirect.yml
deleted file mode 100644
index 8025cd472bd..00000000000
--- a/changelogs/unreleased/iss-32584-preserve-line-number-fragment-after-redirect.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Fix lost line number when navigating to a specific line in a protected file
- before authenticating.
-merge_request: 19165
-author: Scott Escue
-type: fixed
diff --git a/changelogs/unreleased/issue_55744.yml b/changelogs/unreleased/issue_55744.yml
deleted file mode 100644
index 6a643732b18..00000000000
--- a/changelogs/unreleased/issue_55744.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix template labels not being created on new projects
-merge_request: 24803
-author:
-type: fixed
diff --git a/changelogs/unreleased/jc-fix-set-project-writable.yml b/changelogs/unreleased/jc-fix-set-project-writable.yml
new file mode 100644
index 00000000000..0bfd90c3967
--- /dev/null
+++ b/changelogs/unreleased/jc-fix-set-project-writable.yml
@@ -0,0 +1,5 @@
+---
+title: Fix method to mark a project repository as writable
+merge_request: 25546
+author:
+type: fixed
diff --git a/changelogs/unreleased/jej-avoid-csrf-check-on-saml-failure.yml b/changelogs/unreleased/jej-avoid-csrf-check-on-saml-failure.yml
deleted file mode 100644
index 18cced2906a..00000000000
--- a/changelogs/unreleased/jej-avoid-csrf-check-on-saml-failure.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Display SAML failure messages instead of expecting CSRF token
-merge_request: 24509
-author:
-type: fixed
diff --git a/changelogs/unreleased/jej-feature-gates-can-be-set-by-group-path.yml b/changelogs/unreleased/jej-feature-gates-can-be-set-by-group-path.yml
new file mode 100644
index 00000000000..ba882112f70
--- /dev/null
+++ b/changelogs/unreleased/jej-feature-gates-can-be-set-by-group-path.yml
@@ -0,0 +1,5 @@
+---
+title: Allow setting feature flags per GitLab group through the API
+merge_request: 25022
+author:
+type: added
diff --git a/changelogs/unreleased/jira-link-mention-compact.yml b/changelogs/unreleased/jira-link-mention-compact.yml
new file mode 100644
index 00000000000..f75f3ce183c
--- /dev/null
+++ b/changelogs/unreleased/jira-link-mention-compact.yml
@@ -0,0 +1,5 @@
+---
+title: "Jira: make issue links title compact"
+merge_request: 25609
+author: Elan Ruusamäe @glensc
+type: changed
diff --git a/changelogs/unreleased/jlenny-AddPagesTemplates.yml b/changelogs/unreleased/jlenny-AddPagesTemplates.yml
deleted file mode 100644
index 0985e4e18ed..00000000000
--- a/changelogs/unreleased/jlenny-AddPagesTemplates.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add templates for most popular Pages templates
-merge_request: 24906
-author:
-type: added
diff --git a/changelogs/unreleased/jlenny-NewAndroidTemplate.yml b/changelogs/unreleased/jlenny-NewAndroidTemplate.yml
deleted file mode 100644
index ae8c58da859..00000000000
--- a/changelogs/unreleased/jlenny-NewAndroidTemplate.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add template for Android with Fastlane
-merge_request: 24722
-author:
-type: changed
diff --git a/changelogs/unreleased/jprovazn-remove-redcarpet.yml b/changelogs/unreleased/jprovazn-remove-redcarpet.yml
deleted file mode 100644
index 4e12de2d19b..00000000000
--- a/changelogs/unreleased/jprovazn-remove-redcarpet.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Removed deprecated Redcarpet markdown engine.
-merge_request:
-author:
-type: removed
diff --git a/changelogs/unreleased/kinolaev-master-patch-87865.yml b/changelogs/unreleased/kinolaev-master-patch-87865.yml
new file mode 100644
index 00000000000..b4dbc2c0e1f
--- /dev/null
+++ b/changelogs/unreleased/kinolaev-master-patch-87865.yml
@@ -0,0 +1,5 @@
+---
+title: Fix rollout status for statefulsets and daemonsets
+merge_request: 24972
+author: Sergej Nikolaev <kinolaev@gmail.com>
+type: fixed
diff --git a/changelogs/unreleased/knative-list.yml b/changelogs/unreleased/knative-list.yml
deleted file mode 100644
index 754d8e172cf..00000000000
--- a/changelogs/unreleased/knative-list.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Modified Knative list view to provide more details
-merge_request: 24072
-author: Chris Baumbauer
-type: changed
diff --git a/changelogs/unreleased/knative-show-page.yml b/changelogs/unreleased/knative-show-page.yml
deleted file mode 100644
index a48b754940f..00000000000
--- a/changelogs/unreleased/knative-show-page.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add Knative detailed view
-merge_request: 23863
-author: Chris Baumbauer
-type: added
diff --git a/changelogs/unreleased/local-markdown-version-bkp3.yml b/changelogs/unreleased/local-markdown-version-bkp3.yml
deleted file mode 100644
index ce5bff6ae6b..00000000000
--- a/changelogs/unreleased/local-markdown-version-bkp3.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow admins to invalidate markdown texts by setting local markdown version.
-merge_request:
-author:
-type: added
diff --git a/changelogs/unreleased/mg-fix-bad-cluster-update-entrypoint.yml b/changelogs/unreleased/mg-fix-bad-cluster-update-entrypoint.yml
deleted file mode 100644
index 932850cc825..00000000000
--- a/changelogs/unreleased/mg-fix-bad-cluster-update-entrypoint.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix cluster page non-interactive on form validation error
-merge_request: 24583
-author:
-type: fixed
diff --git a/changelogs/unreleased/monospace-registry-tags.yml b/changelogs/unreleased/monospace-registry-tags.yml
deleted file mode 100644
index b5992707d8c..00000000000
--- a/changelogs/unreleased/monospace-registry-tags.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use monospace font for registry table tag id and tag name
-merge_request: 24205
-author:
-type: other
diff --git a/changelogs/unreleased/move-job-cancel-btn.yml b/changelogs/unreleased/move-job-cancel-btn.yml
deleted file mode 100644
index 41f8e1be5f8..00000000000
--- a/changelogs/unreleased/move-job-cancel-btn.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Move cancel & new issue button on job page
-merge_request: 24074
-author:
-type: changed
diff --git a/changelogs/unreleased/move-permission-check-manual-actions-on-deployments.yml b/changelogs/unreleased/move-permission-check-manual-actions-on-deployments.yml
deleted file mode 100644
index 9e979b48ad1..00000000000
--- a/changelogs/unreleased/move-permission-check-manual-actions-on-deployments.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Move permission check of manual actions of deployments
-merge_request: 24660
-author:
-type: other
diff --git a/changelogs/unreleased/move_chatops_to_core.yml b/changelogs/unreleased/move_chatops_to_core.yml
new file mode 100644
index 00000000000..7a75efedfa8
--- /dev/null
+++ b/changelogs/unreleased/move_chatops_to_core.yml
@@ -0,0 +1,5 @@
+---
+title: Move ChatOps to Core
+merge_request: 24780
+author:
+type: changed
diff --git a/changelogs/unreleased/mr-file-tree-blob-truncate-improvements.yml b/changelogs/unreleased/mr-file-tree-blob-truncate-improvements.yml
deleted file mode 100644
index b01962591c6..00000000000
--- a/changelogs/unreleased/mr-file-tree-blob-truncate-improvements.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add folder header to files in merge request tree list
-merge_request:
-author:
-type: changed
diff --git a/changelogs/unreleased/mr-rebase-failing-tests.yml b/changelogs/unreleased/mr-rebase-failing-tests.yml
deleted file mode 100644
index 07ae05766b1..00000000000
--- a/changelogs/unreleased/mr-rebase-failing-tests.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixed rebase button not showing in merge request widget
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/not-run-pipeline-on-empty-merge-request.yml b/changelogs/unreleased/not-run-pipeline-on-empty-merge-request.yml
deleted file mode 100644
index 732e4baf4e9..00000000000
--- a/changelogs/unreleased/not-run-pipeline-on-empty-merge-request.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Don't create new merge request pipeline without commits
-merge_request: 24503
-author: Hiroyuki Sato
-type: added
diff --git a/changelogs/unreleased/notebook-multiple-outputs.yml b/changelogs/unreleased/notebook-multiple-outputs.yml
deleted file mode 100644
index 38cc52c0634..00000000000
--- a/changelogs/unreleased/notebook-multiple-outputs.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Support multiple outputs in jupyter notebooks
-merge_request:
-author:
-type: changed
diff --git a/changelogs/unreleased/notes-awards-double-tooltip-fix.yml b/changelogs/unreleased/notes-awards-double-tooltip-fix.yml
deleted file mode 100644
index 23338a60c2a..00000000000
--- a/changelogs/unreleased/notes-awards-double-tooltip-fix.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixed double tooltips on note awards buttons
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/osw-create-and-store-merge-ref-for-mrs.yml b/changelogs/unreleased/osw-create-and-store-merge-ref-for-mrs.yml
new file mode 100644
index 00000000000..012b547a630
--- /dev/null
+++ b/changelogs/unreleased/osw-create-and-store-merge-ref-for-mrs.yml
@@ -0,0 +1,5 @@
+---
+title: Support merge ref writing (without merging to target branch)
+merge_request: 24692
+author:
+type: added
diff --git a/changelogs/unreleased/osw-enforces-project-removal-with-past-failed-attempts.yml b/changelogs/unreleased/osw-enforces-project-removal-with-past-failed-attempts.yml
deleted file mode 100644
index 6a2a67e7aa8..00000000000
--- a/changelogs/unreleased/osw-enforces-project-removal-with-past-failed-attempts.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Cleanup stale +deleted repo paths on project removal (adjusts project removal bug)
-merge_request: 24269
-author:
-type: fixed
diff --git a/changelogs/unreleased/osw-fetch-latest-version-when-creating-suggestions.yml b/changelogs/unreleased/osw-fetch-latest-version-when-creating-suggestions.yml
new file mode 100644
index 00000000000..4e01a13d781
--- /dev/null
+++ b/changelogs/unreleased/osw-fetch-latest-version-when-creating-suggestions.yml
@@ -0,0 +1,5 @@
+---
+title: Always fetch MR latest version when creating suggestions
+merge_request: 25441
+author:
+type: fixed
diff --git a/changelogs/unreleased/osw-fix-bottom-expansion-diff-comment.yml b/changelogs/unreleased/osw-fix-bottom-expansion-diff-comment.yml
deleted file mode 100644
index b2ac53312ae..00000000000
--- a/changelogs/unreleased/osw-fix-bottom-expansion-diff-comment.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Adjusts duplicated line when commenting on unfolded diff lines (in the bottom)
-merge_request: 24201
-author:
-type: fixed
diff --git a/changelogs/unreleased/osw-merge-refs-refreshing-api.yml b/changelogs/unreleased/osw-merge-refs-refreshing-api.yml
new file mode 100644
index 00000000000..095600cd088
--- /dev/null
+++ b/changelogs/unreleased/osw-merge-refs-refreshing-api.yml
@@ -0,0 +1,5 @@
+---
+title: API support for MR merge to temporary merge ref path
+merge_request: 24918
+author:
+type: added
diff --git a/changelogs/unreleased/patch-38.yml b/changelogs/unreleased/patch-38.yml
deleted file mode 100644
index 9179fc6846e..00000000000
--- a/changelogs/unreleased/patch-38.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: fix display comment avatars issue in IE 11
-merge_request: 24777
-author: Gokhan Apaydin
-type: fixed
diff --git a/changelogs/unreleased/patch-45.yml b/changelogs/unreleased/patch-45.yml
new file mode 100644
index 00000000000..94fa1d29b32
--- /dev/null
+++ b/changelogs/unreleased/patch-45.yml
@@ -0,0 +1,5 @@
+---
+title: Fix incorrect Pages Domains checkbox description.
+merge_request: 25392
+author: Anton Melser
+type: other
diff --git a/changelogs/unreleased/persist-source-sha-and-target-sha-for-pipelines.yml b/changelogs/unreleased/persist-source-sha-and-target-sha-for-pipelines.yml
new file mode 100644
index 00000000000..6957d156161
--- /dev/null
+++ b/changelogs/unreleased/persist-source-sha-and-target-sha-for-pipelines.yml
@@ -0,0 +1,5 @@
+---
+title: Persist source sha and target sha for merge pipelines
+merge_request: 25417
+author:
+type: added
diff --git a/changelogs/unreleased/pl-serialize-ac-parameters.yml b/changelogs/unreleased/pl-serialize-ac-parameters.yml
deleted file mode 100644
index aad222b5506..00000000000
--- a/changelogs/unreleased/pl-serialize-ac-parameters.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Make `ActionController::Parameters` serializable for sidekiq jobs
-merge_request: 24864
-author:
-type: fixed
diff --git a/changelogs/unreleased/profile-project-empty-state.yml b/changelogs/unreleased/profile-project-empty-state.yml
deleted file mode 100644
index 484306d5b98..00000000000
--- a/changelogs/unreleased/profile-project-empty-state.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Added empty project illustration and updated text to user profile overview
-merge_request: 23973
-author: Fernando Arias
-type: changed
diff --git a/changelogs/unreleased/raise-on-unfiltered-params.yml b/changelogs/unreleased/raise-on-unfiltered-params.yml
deleted file mode 100644
index 531e9ba807e..00000000000
--- a/changelogs/unreleased/raise-on-unfiltered-params.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Actually set raise_on_unfiltered_parameters to true
-merge_request: 24443
-author: Jasper Maes
-type: other
diff --git a/changelogs/unreleased/rd-update-last_activity_on-on-logins-and-browsing-activity-54947.yml b/changelogs/unreleased/rd-update-last_activity_on-on-logins-and-browsing-activity-54947.yml
deleted file mode 100644
index abce9dcc0c6..00000000000
--- a/changelogs/unreleased/rd-update-last_activity_on-on-logins-and-browsing-activity-54947.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update last_activity_on for Users on some main GET endpoints
-merge_request: 24642
-author:
-type: changed
diff --git a/changelogs/unreleased/refactor-56366-extract-resolve-discussion-button.yml b/changelogs/unreleased/refactor-56366-extract-resolve-discussion-button.yml
deleted file mode 100644
index 98859e8aa07..00000000000
--- a/changelogs/unreleased/refactor-56366-extract-resolve-discussion-button.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Refactored NoteableDiscussion by extracting ResolveDiscussionButton
-merge_request: 24505
-author: Martin Hobert
-type: other
diff --git a/changelogs/unreleased/refactor-56367-extract-resolve-with-issue-button-component.yml b/changelogs/unreleased/refactor-56367-extract-resolve-with-issue-button-component.yml
new file mode 100644
index 00000000000..082075506c0
--- /dev/null
+++ b/changelogs/unreleased/refactor-56367-extract-resolve-with-issue-button-component.yml
@@ -0,0 +1,5 @@
+---
+title: Extracted ResolveWithIssueButton to its own component
+merge_request: 25093
+author: Martin Hobert
+type: other
diff --git a/changelogs/unreleased/refactor-56369-extract-jump-to-next-discussion-button.yml b/changelogs/unreleased/refactor-56369-extract-jump-to-next-discussion-button.yml
deleted file mode 100644
index 9a0d16c2d70..00000000000
--- a/changelogs/unreleased/refactor-56369-extract-jump-to-next-discussion-button.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Extracted JumpToNextDiscussionButton to its own component
-author: Martin Hobert
-merge_request: 24506
-type: other
diff --git a/changelogs/unreleased/refactor-56370-extract-reply-placeholder-component.yml b/changelogs/unreleased/refactor-56370-extract-reply-placeholder-component.yml
deleted file mode 100644
index a216d294b30..00000000000
--- a/changelogs/unreleased/refactor-56370-extract-reply-placeholder-component.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Extracted ReplyPlaceholder to its own component
-merge_request: 24507
-author: Martin Hobert
-type: other
diff --git a/changelogs/unreleased/refactor-merge-request-between-pipeline-and-build.yml b/changelogs/unreleased/refactor-merge-request-between-pipeline-and-build.yml
new file mode 100644
index 00000000000..bf78f8d84a6
--- /dev/null
+++ b/changelogs/unreleased/refactor-merge-request-between-pipeline-and-build.yml
@@ -0,0 +1,5 @@
+---
+title: Add suffix (`_event`) to merge request source
+merge_request: 25508
+author:
+type: other
diff --git a/changelogs/unreleased/remove-cancel-all-button-in-job-list-view.yml b/changelogs/unreleased/remove-cancel-all-button-in-job-list-view.yml
deleted file mode 100644
index 06546bc5a8e..00000000000
--- a/changelogs/unreleased/remove-cancel-all-button-in-job-list-view.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove Cancel all jobs button in general jobs list view
-merge_request:
-author: Jordi Llull
-type: removed
diff --git a/changelogs/unreleased/remove-diff-coloring.yml b/changelogs/unreleased/remove-diff-coloring.yml
deleted file mode 100644
index 1ee1b525c35..00000000000
--- a/changelogs/unreleased/remove-diff-coloring.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'remove red/green colors from diff view of no-color syntax theme'
-merge_request: 24582
-author: khm
-type: changed
diff --git a/changelogs/unreleased/remove-gap-between-mr-tabs-and-file-header.yml b/changelogs/unreleased/remove-gap-between-mr-tabs-and-file-header.yml
deleted file mode 100644
index ce8e1829b48..00000000000
--- a/changelogs/unreleased/remove-gap-between-mr-tabs-and-file-header.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove extra space between MR tab bar and sticky file headers
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/remove-second-primary-button-on-wiki-edit.yml b/changelogs/unreleased/remove-second-primary-button-on-wiki-edit.yml
new file mode 100644
index 00000000000..045fbbb48b7
--- /dev/null
+++ b/changelogs/unreleased/remove-second-primary-button-on-wiki-edit.yml
@@ -0,0 +1,5 @@
+---
+title: Remove second primary button on wiki edit
+merge_request: 19959
+author: George Tsiolis
+type: changed
diff --git a/changelogs/unreleased/rs-admin-user-case-insensitive.yml b/changelogs/unreleased/rs-admin-user-case-insensitive.yml
new file mode 100644
index 00000000000..40398c46a1e
--- /dev/null
+++ b/changelogs/unreleased/rs-admin-user-case-insensitive.yml
@@ -0,0 +1,5 @@
+---
+title: Admin section finds users case-insensitively
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/search-title.yml b/changelogs/unreleased/search-title.yml
deleted file mode 100644
index ff0933ed0b2..00000000000
--- a/changelogs/unreleased/search-title.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add 'in' filter that modifies scope of 'search' filter to issues and merge requests API
-merge_request: 24350
-author: Hiroyuki Sato
-type: added
diff --git a/changelogs/unreleased/security-22076-sanitize-url-in-names.yml b/changelogs/unreleased/security-22076-sanitize-url-in-names.yml
deleted file mode 100644
index 4e0ad4dd4c4..00000000000
--- a/changelogs/unreleased/security-22076-sanitize-url-in-names.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Sanitize user full name to clean up any URL to prevent mail clients from auto-linking
- URLs
-merge_request: 2793
-author:
-type: security
diff --git a/changelogs/unreleased/security-2770-verify-bundle-import-files.yml b/changelogs/unreleased/security-2770-verify-bundle-import-files.yml
deleted file mode 100644
index dea40dd1ef1..00000000000
--- a/changelogs/unreleased/security-2770-verify-bundle-import-files.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Validate bundle files before unpacking them
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-55320-stored-xss-in-user-status.yml b/changelogs/unreleased/security-55320-stored-xss-in-user-status.yml
deleted file mode 100644
index 8ea9ae0ccdf..00000000000
--- a/changelogs/unreleased/security-55320-stored-xss-in-user-status.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use sanitized user status message for user popover
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-stored-xss-via-katex.yml b/changelogs/unreleased/security-stored-xss-via-katex.yml
deleted file mode 100644
index a71ae1123f2..00000000000
--- a/changelogs/unreleased/security-stored-xss-via-katex.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixed XSS content in KaTex links
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/sh-bump-fog-gem.yml b/changelogs/unreleased/sh-bump-fog-gem.yml
new file mode 100644
index 00000000000..6a26d5c6488
--- /dev/null
+++ b/changelogs/unreleased/sh-bump-fog-gem.yml
@@ -0,0 +1,5 @@
+---
+title: Fixes issue with AWS V4 signatures not working with some S3 providers
+merge_request: 21788
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-disable-nil-user-id-identity-validation.yml b/changelogs/unreleased/sh-disable-nil-user-id-identity-validation.yml
deleted file mode 100644
index 5af3bdce51b..00000000000
--- a/changelogs/unreleased/sh-disable-nil-user-id-identity-validation.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix failed LDAP logins when nil user_id present
-merge_request: 24749
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-encode-content-disposition.yml b/changelogs/unreleased/sh-encode-content-disposition.yml
deleted file mode 100644
index b40ee6a85a8..00000000000
--- a/changelogs/unreleased/sh-encode-content-disposition.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Encode Content-Disposition filenames
-merge_request: 24919
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-fix-backfill-project-repo-migration.yml b/changelogs/unreleased/sh-fix-backfill-project-repo-migration.yml
deleted file mode 100644
index d1d4412eb50..00000000000
--- a/changelogs/unreleased/sh-fix-backfill-project-repo-migration.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix duplicate project disk path in BackfillLegacyProjectRepositories
-merge_request: 24213
-author:
-type: changed
diff --git a/changelogs/unreleased/sh-fix-bitbucket-server-error-handling.yml b/changelogs/unreleased/sh-fix-bitbucket-server-error-handling.yml
deleted file mode 100644
index 87405fa0a78..00000000000
--- a/changelogs/unreleased/sh-fix-bitbucket-server-error-handling.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix Bitbucket Server importer error handling
-merge_request: 24343
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-fix-cpp-templates-404.yml b/changelogs/unreleased/sh-fix-cpp-templates-404.yml
new file mode 100644
index 00000000000..ac958d84099
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-cpp-templates-404.yml
@@ -0,0 +1,5 @@
+---
+title: Fix 404s when C++ .gitignore template selected
+merge_request: 25416
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-double-xhr-pipelines.yml b/changelogs/unreleased/sh-fix-double-xhr-pipelines.yml
new file mode 100644
index 00000000000..e6c762f1d47
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-double-xhr-pipelines.yml
@@ -0,0 +1,5 @@
+---
+title: Remove duplicate XHR request when requesting new pipeline page
+merge_request: 25506
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-import-redirect-vulnerability.yml b/changelogs/unreleased/sh-fix-import-redirect-vulnerability.yml
deleted file mode 100644
index addf327b69d..00000000000
--- a/changelogs/unreleased/sh-fix-import-redirect-vulnerability.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Alias GitHub and BitBucket OAuth2 callback URLs
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/sh-fix-issue-58103.yml b/changelogs/unreleased/sh-fix-issue-58103.yml
new file mode 100644
index 00000000000..1599af23fed
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-issue-58103.yml
@@ -0,0 +1,5 @@
+---
+title: Properly handle multiple X-Forwarded-For addresses in runner IP
+merge_request: 25511
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-pages-zip-constant.yml b/changelogs/unreleased/sh-fix-pages-zip-constant.yml
deleted file mode 100644
index fcd8aa45825..00000000000
--- a/changelogs/unreleased/sh-fix-pages-zip-constant.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix uninitialized constant with GitLab Pages
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-fix-snippet-uploads-path-lookup.yml b/changelogs/unreleased/sh-fix-snippet-uploads-path-lookup.yml
deleted file mode 100644
index 414c8663049..00000000000
--- a/changelogs/unreleased/sh-fix-snippet-uploads-path-lookup.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix 404s with snippet uploads in object storage
-merge_request: 24550
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-fix-upload-snippets-with-relative-url-root.yml b/changelogs/unreleased/sh-fix-upload-snippets-with-relative-url-root.yml
deleted file mode 100644
index 8bc1e4b4f8a..00000000000
--- a/changelogs/unreleased/sh-fix-upload-snippets-with-relative-url-root.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix 404s for snippet uploads when relative URL root used
-merge_request: 24588
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-import-source-branch-github-forks.yml b/changelogs/unreleased/sh-import-source-branch-github-forks.yml
deleted file mode 100644
index b5ea60202c0..00000000000
--- a/changelogs/unreleased/sh-import-source-branch-github-forks.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Create the source branch for a GitHub import
-merge_request: 25064
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-include-project-path-for-internal-api.yml b/changelogs/unreleased/sh-include-project-path-for-internal-api.yml
new file mode 100644
index 00000000000..1973049e9e3
--- /dev/null
+++ b/changelogs/unreleased/sh-include-project-path-for-internal-api.yml
@@ -0,0 +1,5 @@
+---
+title: Include gl_project_path in API /internal/allowed response
+merge_request: 25314
+author:
+type: other
diff --git a/changelogs/unreleased/sh-issue-53419-fix.yml b/changelogs/unreleased/sh-issue-53419-fix.yml
deleted file mode 100644
index ab8b65857e2..00000000000
--- a/changelogs/unreleased/sh-issue-53419-fix.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix Bitbucket Server import not allowing personal projects
-merge_request: 23601
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-preload-associations-for-group-api.yml b/changelogs/unreleased/sh-preload-associations-for-group-api.yml
deleted file mode 100644
index 24e424b7efb..00000000000
--- a/changelogs/unreleased/sh-preload-associations-for-group-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Eliminate N+1 queries in /api/groups/:id
-merge_request: 24513
-author:
-type: performance
diff --git a/changelogs/unreleased/sh-remove-nplusone-admin-runners-tags.yml b/changelogs/unreleased/sh-remove-nplusone-admin-runners-tags.yml
new file mode 100644
index 00000000000..f8ac345bc95
--- /dev/null
+++ b/changelogs/unreleased/sh-remove-nplusone-admin-runners-tags.yml
@@ -0,0 +1,5 @@
+---
+title: Remove N+1 query for tags in /admin/runners page
+merge_request: 25572
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-wip-fix-duplicate-env-xhr.yml b/changelogs/unreleased/sh-wip-fix-duplicate-env-xhr.yml
new file mode 100644
index 00000000000..e7900e2230d
--- /dev/null
+++ b/changelogs/unreleased/sh-wip-fix-duplicate-env-xhr.yml
@@ -0,0 +1,5 @@
+---
+title: Fix pagination and duplicate requests in environments page
+merge_request: 25582
+author:
+type: fixed
diff --git a/changelogs/unreleased/shared_with_group_path.yml b/changelogs/unreleased/shared_with_group_path.yml
deleted file mode 100644
index 73ba9a9f30a..00000000000
--- a/changelogs/unreleased/shared_with_group_path.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add group full path to project's shared_with_groups
-merge_request: 24052
-author: Mathieu Parent
-type: added
diff --git a/changelogs/unreleased/support-chunking-in-client.yml b/changelogs/unreleased/support-chunking-in-client.yml
deleted file mode 100644
index e50648ea4b2..00000000000
--- a/changelogs/unreleased/support-chunking-in-client.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix code search when text is larger than max gRPC message size
-merge_request: 24111
-author:
-type: changed
diff --git a/changelogs/unreleased/support-only-changes-on-mr-pipelines.yml b/changelogs/unreleased/support-only-changes-on-mr-pipelines.yml
new file mode 100644
index 00000000000..fbab898b799
--- /dev/null
+++ b/changelogs/unreleased/support-only-changes-on-mr-pipelines.yml
@@ -0,0 +1,5 @@
+---
+title: 'Support `only: changes:` on MR pipelines'
+merge_request: 24490
+author: Hiroyuki Sato
+type: added
diff --git a/changelogs/unreleased/test-permissions.yml b/changelogs/unreleased/test-permissions.yml
deleted file mode 100644
index cfb69fdcb1e..00000000000
--- a/changelogs/unreleased/test-permissions.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Disallows unauthorized users from accessing the pipelines section.
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/tooltips-to-top.yml b/changelogs/unreleased/tooltips-to-top.yml
deleted file mode 100644
index 51bf127089e..00000000000
--- a/changelogs/unreleased/tooltips-to-top.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Change spawning of tooltips to be top by default
-merge_request: 21223
-author:
-type: changed
diff --git a/changelogs/unreleased/tr-error-tracking-project-selection.yml b/changelogs/unreleased/tr-error-tracking-project-selection.yml
new file mode 100644
index 00000000000..36cfe4556bb
--- /dev/null
+++ b/changelogs/unreleased/tr-error-tracking-project-selection.yml
@@ -0,0 +1,5 @@
+---
+title: Error tracking configuration - add a Sentry project selection dropdown
+merge_request: 24701
+author:
+type: changed
diff --git a/changelogs/unreleased/twang2218-gitlab-ce-i18n-extract-app-views-search.yml b/changelogs/unreleased/twang2218-gitlab-ce-i18n-extract-app-views-search.yml
deleted file mode 100644
index 1af1fe09f33..00000000000
--- a/changelogs/unreleased/twang2218-gitlab-ce-i18n-extract-app-views-search.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'i18n: externalize strings from ''app/views/search'''
-merge_request: 24297
-author: Tao Wang
-type: other
diff --git a/changelogs/unreleased/update-gitaly.yml b/changelogs/unreleased/update-gitaly.yml
deleted file mode 100644
index 4ba42a689a7..00000000000
--- a/changelogs/unreleased/update-gitaly.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update Gitaly to v1.17.0
-merge_request: 24873
-author:
-type: other
diff --git a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-1-45.yml b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-1-45.yml
deleted file mode 100644
index 7d92929221f..00000000000
--- a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-1-45.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update GitLab Runner Helm Chart to 0.1.45
-merge_request: 24564
-author:
-type: other
diff --git a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-2-0.yml b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-2-0.yml
new file mode 100644
index 00000000000..3bf55630c4d
--- /dev/null
+++ b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-2-0.yml
@@ -0,0 +1,5 @@
+---
+title: Update GitLab Runner Helm Chart to 0.2.0
+merge_request: 25493
+author:
+type: other
diff --git a/changelogs/unreleased/update-gitlab-styles.yml b/changelogs/unreleased/update-gitlab-styles.yml
deleted file mode 100644
index 379f0ad4486..00000000000
--- a/changelogs/unreleased/update-gitlab-styles.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update gitlab-styles to 2.5.1
-merge_request: 24336
-author: Jasper Maes
-type: other
diff --git a/changelogs/unreleased/update-pages-config-only-when-changed.yml b/changelogs/unreleased/update-pages-config-only-when-changed.yml
deleted file mode 100644
index 8d9e02df678..00000000000
--- a/changelogs/unreleased/update-pages-config-only-when-changed.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Do not reload daemon if configuration file of pages does not change
-merge_request:
-author:
-type: performance
diff --git a/changelogs/unreleased/update-pages-extensionless-urls.yml b/changelogs/unreleased/update-pages-extensionless-urls.yml
deleted file mode 100644
index 13b3e1df500..00000000000
--- a/changelogs/unreleased/update-pages-extensionless-urls.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add support for extensionless pages URLs
-merge_request: 24876
-author:
-type: added
diff --git a/changelogs/unreleased/update-sidekiq-cron.yml b/changelogs/unreleased/update-sidekiq-cron.yml
deleted file mode 100644
index edce32e3753..00000000000
--- a/changelogs/unreleased/update-sidekiq-cron.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Update sidekiq-cron to 1.0.4 and use fugit to replace rufus-scheduler to parse
- cron syntax
-merge_request: 24235
-author:
-type: other
diff --git a/changelogs/unreleased/update-smooshpack.yml b/changelogs/unreleased/update-smooshpack.yml
deleted file mode 100644
index a9222088d53..00000000000
--- a/changelogs/unreleased/update-smooshpack.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Upgraded Codesandbox smooshpack package
-merge_request:
-author:
-type: other
diff --git a/changelogs/unreleased/update-spriteicon-from-icon-on-profile.yml b/changelogs/unreleased/update-spriteicon-from-icon-on-profile.yml
deleted file mode 100644
index 32259bfacd4..00000000000
--- a/changelogs/unreleased/update-spriteicon-from-icon-on-profile.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update to GitLab SVG icon from Font Awesome in profile for location and work
-merge_request: 24671
-author: Yoginth
-type: changed
diff --git a/changelogs/unreleased/update-ui-admin-appearance.yml b/changelogs/unreleased/update-ui-admin-appearance.yml
deleted file mode 100644
index 7bc35029d77..00000000000
--- a/changelogs/unreleased/update-ui-admin-appearance.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update UI for admin appearance settings
-merge_request: 24685
-author:
-type: other
diff --git a/changelogs/unreleased/update-workhorse-8-2-0.yml b/changelogs/unreleased/update-workhorse-8-2-0.yml
deleted file mode 100644
index 7d593917a25..00000000000
--- a/changelogs/unreleased/update-workhorse-8-2-0.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update Workhorse to v8.2.0
-merge_request: 24909
-author:
-type: fixed
diff --git a/changelogs/unreleased/use-deployment-relation-to-fetch-environment-ce.yml b/changelogs/unreleased/use-deployment-relation-to-fetch-environment-ce.yml
deleted file mode 100644
index 1ec276b4abc..00000000000
--- a/changelogs/unreleased/use-deployment-relation-to-fetch-environment-ce.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use deployment relation to get an environment name
-merge_request: 24890
-author:
-type: performance
diff --git a/changelogs/unreleased/use_upgrade_install_for_helm_apps.yml b/changelogs/unreleased/use_upgrade_install_for_helm_apps.yml
deleted file mode 100644
index b41c3cfa1ab..00000000000
--- a/changelogs/unreleased/use_upgrade_install_for_helm_apps.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Added ability to upgrade cluster applications
-merge_request: 24789
-author:
-type: added
diff --git a/changelogs/unreleased/web-ide-commit-header-icon-alignment-fix.yml b/changelogs/unreleased/web-ide-commit-header-icon-alignment-fix.yml
new file mode 100644
index 00000000000..7a6bda1580d
--- /dev/null
+++ b/changelogs/unreleased/web-ide-commit-header-icon-alignment-fix.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed alignment of changed icon in Web IDE
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/web-ide-default-editor.yml b/changelogs/unreleased/web-ide-default-editor.yml
new file mode 100644
index 00000000000..b98be5c16c2
--- /dev/null
+++ b/changelogs/unreleased/web-ide-default-editor.yml
@@ -0,0 +1,5 @@
+---
+title: Make the Web IDE the default editor
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/winh-add-list-dropdown-height.yml b/changelogs/unreleased/winh-add-list-dropdown-height.yml
deleted file mode 100644
index 6bcedc15cc9..00000000000
--- a/changelogs/unreleased/winh-add-list-dropdown-height.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Adjust height of "Add list" dropdown in issue boards
-merge_request: 24227
-author:
-type: fixed
diff --git a/changelogs/unreleased/workhorse-8-3-0.yml b/changelogs/unreleased/workhorse-8-3-0.yml
deleted file mode 100644
index 6ae01d64ae5..00000000000
--- a/changelogs/unreleased/workhorse-8-3-0.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update Workhorse to v8.3.0
-merge_request: 24959
-author:
-type: other
diff --git a/changelogs/unreleased/yoginth-avatar-on-settings-sidebar.yml b/changelogs/unreleased/yoginth-avatar-on-settings-sidebar.yml
deleted file mode 100644
index 0ec76f9ce02..00000000000
--- a/changelogs/unreleased/yoginth-avatar-on-settings-sidebar.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Added Avatar in the settings sidebar
-merge_request: 24515
-author: Yoginth
-type: changed
diff --git a/changelogs/unreleased/zj-feature-gate-set-project-path.yml b/changelogs/unreleased/zj-feature-gate-set-project-path.yml
deleted file mode 100644
index b426a2f3fe7..00000000000
--- a/changelogs/unreleased/zj-feature-gate-set-project-path.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow setting of feature gates per project
-merge_request: 24184
-author:
-type: added
diff --git a/changelogs/unreleased/zj-load-languages-from-database.yml b/changelogs/unreleased/zj-load-languages-from-database.yml
new file mode 100644
index 00000000000..1688829b42c
--- /dev/null
+++ b/changelogs/unreleased/zj-load-languages-from-database.yml
@@ -0,0 +1,5 @@
+---
+title: Load repository language from the database if detected before
+merge_request: 25518
+author:
+type: performance
diff --git a/config/application.rb b/config/application.rb
index 92a3d031c63..1c11e347281 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -97,7 +97,7 @@ module Gitlab
#
# NOTE: It is **IMPORTANT** to also update gitlab-workhorse's filter when adding parameters here to not
# introduce another security vulnerability: https://gitlab.com/gitlab-org/gitlab-workhorse/issues/182
- config.filter_parameters += [/token$/, /password/, /secret/, /key$/]
+ config.filter_parameters += [/token$/, /password/, /secret/, /key$/, /^note$/, /^text$/]
config.filter_parameters += %i(
certificate
encrypted_key
@@ -147,6 +147,8 @@ module Gitlab
config.assets.precompile << "errors.css"
config.assets.precompile << "csslab.css"
+ config.assets.precompile << "highlight/themes/*.css"
+
# Import gitlab-svgs directly from vendored directory
config.assets.paths << "#{config.root}/node_modules/@gitlab/svgs/dist"
config.assets.precompile << "icons.svg"
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index be23166cb7b..1a5b9ec3f02 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -662,7 +662,6 @@ production: &base
## GitLab Shell settings
gitlab_shell:
path: /home/git/gitlab-shell/
- hooks_path: /home/git/gitlab-shell/hooks/
# File that contains the secret key for verifying access for gitlab-shell.
# Default is '.gitlab_shell_secret' relative to Rails.root (i.e. root of the GitLab app).
@@ -820,7 +819,6 @@ test:
path: tmp/tests/backups
gitlab_shell:
path: tmp/tests/gitlab-shell/
- hooks_path: tmp/tests/gitlab-shell/hooks/
issues_tracker:
redmine:
title: "Redmine"
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index dfcf1e648b4..1344b3cb1f6 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -337,7 +337,7 @@ Settings['sidekiq']['log_format'] ||= 'default'
#
Settings['gitlab_shell'] ||= Settingslogic.new({})
Settings.gitlab_shell['path'] = Settings.absolute(Settings.gitlab_shell['path'] || Settings.gitlab['user_home'] + '/gitlab-shell/')
-Settings.gitlab_shell['hooks_path'] = Settings.absolute(Settings.gitlab_shell['hooks_path'] || Settings.gitlab['user_home'] + '/gitlab-shell/hooks/')
+Settings.gitlab_shell['hooks_path'] = :deprecated_use_gitlab_shell_path_instead
Settings.gitlab_shell['secret_file'] ||= Rails.root.join('.gitlab_shell_secret')
Settings.gitlab_shell['receive_pack'] = true if Settings.gitlab_shell['receive_pack'].nil?
Settings.gitlab_shell['upload_pack'] = true if Settings.gitlab_shell['upload_pack'].nil?
diff --git a/config/initializers/fog_core_patch.rb b/config/initializers/fog_core_patch.rb
new file mode 100644
index 00000000000..d3d02216d45
--- /dev/null
+++ b/config/initializers/fog_core_patch.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+#
+# fog-core v2 changed the namespace format:
+#
+# Old: Fog::<service>::<provider> (e.g. Fog::Storage::AWS).
+# New: Fog::<provider>::<service> (e.g. Fog::AWS::Storage)
+#
+# To preserve backwards compatibility, fog-core v2.1.0 tries to load the
+# old schema first, but falls back to the older version if that
+# fails. This creates misleading warnings with fog-aws. See
+# https://github.com/fog/fog-aws/issues/504#issuecomment-468067991 for
+# more details.
+#
+# fog-core v2.1.2 reverses the load order
+# (https://github.com/fog/fog-core/pull/229), which works for fog-aws
+# but causes a stream of deprecation warnings for fog-google.
+# fog-google locked the dependency on fog-core v2.1.0 as a result
+# (https://github.com/fog/fog-google/issues/421) until the new namespace
+# is supported.
+#
+# Since we currently have some Fog gems that have not been updated, this
+# monkey patch makes a smarter decision about which namespace to try
+# first. This squelches a significant number of warning messages.
+#
+# Since this patch is mostly cosmetic, it can be removed safely at any
+# time, but it's probably best to wait until the following issues are
+# closed:
+#
+# fog-google: https://github.com/fog/fog-google/issues/421
+# fog-rackspace: https://github.com/fog/fog-rackspace/issues/29
+# fog-aliyun: https://github.com/fog/fog-aliyun/issues/23
+module Fog
+ module ServicesMixin
+ # Gems that have not yet updated with the new fog-core namespace
+ LEGACY_FOG_PROVIDERS = %w(google rackspace aliyun).freeze
+
+ def service_provider_constant(service_name, provider_name)
+ args = service_provider_search_args(service_name, provider_name)
+ Fog.const_get(args.first).const_get(*const_get_args(args.second))
+ rescue NameError # Try to find the constant from in an alternate location
+ Fog.const_get(args.second).const_get(*const_get_args(args.first))
+ end
+
+ def service_provider_search_args(service_name, provider_name)
+ if LEGACY_FOG_PROVIDERS.include?(provider_name.downcase)
+ [service_name, provider_name]
+ else
+ [provider_name, service_name]
+ end
+ end
+ end
+end
diff --git a/config/initializers/graphql.rb b/config/initializers/graphql.rb
new file mode 100644
index 00000000000..1ed93019329
--- /dev/null
+++ b/config/initializers/graphql.rb
@@ -0,0 +1,4 @@
+# frozen_string_literal: true
+
+GraphQL::Field.accepts_definitions(authorize: GraphQL::Define.assign_metadata_key(:authorize))
+Types::BaseField.accepts_definition(:authorize)
diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb
index be4183f39be..4a8d72e37a5 100644
--- a/config/initializers/sidekiq.rb
+++ b/config/initializers/sidekiq.rb
@@ -11,6 +11,10 @@ queues_config_hash[:namespace] = Gitlab::Redis::Queues::SIDEKIQ_NAMESPACE
# Default is to retry 25 times with exponential backoff. That's too much.
Sidekiq.default_worker_options = { retry: 3 }
+if Rails.env.development?
+ Sidekiq.default_worker_options[:backtrace] = true
+end
+
enable_json_logs = Gitlab.config.sidekiq.log_format == 'json'
Sidekiq.configure_server do |config|
@@ -18,7 +22,7 @@ Sidekiq.configure_server do |config|
config.server_middleware do |chain|
chain.add Gitlab::SidekiqMiddleware::ArgumentsLogger if ENV['SIDEKIQ_LOG_ARGUMENTS'] && !enable_json_logs
- chain.add Gitlab::SidekiqMiddleware::Shutdown
+ chain.add Gitlab::SidekiqMiddleware::MemoryKiller if ENV['SIDEKIQ_MEMORY_KILLER_MAX_RSS']
chain.add Gitlab::SidekiqMiddleware::RequestStoreMiddleware unless ENV['SIDEKIQ_REQUEST_STORE'] == '0'
chain.add Gitlab::SidekiqMiddleware::BatchLoader
chain.add Gitlab::SidekiqMiddleware::CorrelationLogger
diff --git a/config/locales/en.yml b/config/locales/en.yml
index e8dbc033a7c..eb3b7771968 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -10,6 +10,10 @@ en:
target: Target issue
group:
path: Group URL
+ project/error_tracking_setting:
+ token: "Auth Token"
+ project: "Project"
+ api_url: "Sentry API URL"
errors:
messages:
label_already_exists_at_group_level: "already exists at group level for %{group}. Please choose another one."
diff --git a/config/routes/admin.rb b/config/routes/admin.rb
index af333bdc748..a01003b6039 100644
--- a/config/routes/admin.rb
+++ b/config/routes/admin.rb
@@ -120,6 +120,10 @@ namespace :admin do
get :resume
get :pause
end
+
+ collection do
+ get :tag_list, format: :json
+ end
end
resources :jobs, only: :index do
diff --git a/config/routes/group.rb b/config/routes/group.rb
index a0aeebe4b91..b3015529c6e 100644
--- a/config/routes/group.rb
+++ b/config/routes/group.rb
@@ -67,7 +67,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
end
end
- resources :boards, only: [:index, :show]
+ resources :boards, only: [:index, :show], constraints: { id: /\d+/ }
resources :runners, only: [:index, :edit, :update, :destroy, :show] do
member do
diff --git a/config/routes/import.rb b/config/routes/import.rb
index da5c31d0062..24013eb2c88 100644
--- a/config/routes/import.rb
+++ b/config/routes/import.rb
@@ -12,13 +12,13 @@ namespace :import do
post :personal_access_token
get :status
get :callback
- get :jobs
+ get :realtime_changes
end
resource :gitea, only: [:create, :new], controller: :gitea do
post :personal_access_token
get :status
- get :jobs
+ get :realtime_changes
end
resource :gitlab, only: [:create], controller: :gitlab do
diff --git a/config/routes/project.rb b/config/routes/project.rb
index b4ebc7df4fe..d60a5cc9ae8 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -394,8 +394,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get 'noteable/:target_type/:target_id/notes' => 'notes#index', as: 'noteable_notes'
- # On CE only index and show are needed
- resources :boards, only: [:index, :show]
+ resources :boards, only: [:index, :show], constraints: { id: /\d+/ }
resources :todos, only: [:create]
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index 1e094c03171..cef123b86ae 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -68,6 +68,7 @@
- [background_migration, 1]
- [gcp_cluster, 1]
- [project_migrate_hashed_storage, 1]
+ - [project_rollback_hashed_storage, 1]
- [hashed_storage, 1]
- [pages_domain_verification, 1]
- [object_storage_upload, 1]
@@ -85,4 +86,6 @@
- [repository_cleanup, 1]
- [delete_stored_files, 1]
- [remote_mirror_notification, 2]
+ - [project_daily_statistics, 1]
- [import_issues_csv, 2]
+ - [chat_notification, 2]
diff --git a/config/webpack.config.js b/config/webpack.config.js
index fdf179b007a..64e6ec49219 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -150,6 +150,7 @@ module.exports = {
loader: 'worker-loader',
options: {
name: '[name].[hash:8].worker.js',
+ inline: IS_DEV_SERVER,
},
},
'babel-loader',
@@ -244,6 +245,17 @@ module.exports = {
jQuery: 'jquery',
}),
+ new webpack.NormalModuleReplacementPlugin(/^ee_component\/(.*)\.vue/, function(resource) {
+ if (Object.keys(module.exports.resolve.alias).indexOf('ee') >= 0) {
+ resource.request = resource.request.replace(/^ee_component/, 'ee');
+ } else {
+ resource.request = path.join(
+ ROOT_PATH,
+ 'app/assets/javascripts/vue_shared/components/empty_component.js'
+ );
+ }
+ }),
+
// compression can require a lot of compute time and is disabled in CI
IS_PRODUCTION && !NO_COMPRESSION && new CompressionPlugin(),
diff --git a/danger/changelog/Dangerfile b/danger/changelog/Dangerfile
index 530c6638653..63b2f6f5c5c 100644
--- a/danger/changelog/Dangerfile
+++ b/danger/changelog/Dangerfile
@@ -16,16 +16,12 @@ consider adding any of the %<labels>s labels.
#{SEE_DOC}
MSG
-def ee?
- ENV['CI_PROJECT_NAME'] == 'gitlab-ee' || File.exist?('../../CHANGELOG-EE.md')
-end
-
def ee_changelog?(changelog_path)
changelog_path =~ /unreleased-ee/
end
def ce_port_changelog?(changelog_path)
- ee? && !ee_changelog?(changelog_path)
+ helper.ee? && !ee_changelog?(changelog_path)
end
def check_changelog(path)
diff --git a/danger/commit_messages/Dangerfile b/danger/commit_messages/Dangerfile
index c20c8b77e6a..9be1ce2ff86 100644
--- a/danger/commit_messages/Dangerfile
+++ b/danger/commit_messages/Dangerfile
@@ -64,11 +64,12 @@ def too_many_changed_lines?(commit)
lines_changed_in_commit(commit) >= 30
end
-def lint_commits(commits)
- failures = false
- emoji_checker = EmojiChecker.new
+def emoji_checker
+ @emoji_checker ||= EmojiChecker.new
+end
- unicode_emoji_regex = %r((
+def unicode_emoji_regex
+ @unicode_emoji_regex ||= %r((
[\u{1F300}-\u{1F5FF}] |
[\u{1F1E6}-\u{1F1FF}] |
[\u{2700}-\u{27BF}] |
@@ -77,120 +78,132 @@ def lint_commits(commits)
[\u{1F680}-\u{1F6FF}] |
[\u{2600}-\u{26FF}]
))x
+end
+
+def lint_commit(commit)
+ # For now we'll ignore merge commits, as getting rid of those is a problem
+ # separate from enforcing good commit messages.
+ return false if commit.message.start_with?('Merge branch')
+
+ # We ignore revert commits as they are well structured by Git already
+ return false if commit.message.start_with?('Revert "')
+
+ failures = false
+ 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 > 72
+ fail_commit(
+ commit,
+ 'The commit subject may not be longer than 72 characters'
+ )
+
+ failures = true
+ elsif subject.length > 50
+ warn_commit(
+ commit,
+ "This commit's subject line is acceptable, but please try to [reduce it to 50 characters](#{URL_LIMIT_SUBJECT})."
+ )
+ 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
- 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 > 72
- fail_commit(
- commit,
- 'The commit subject may not be longer than 72 characters'
- )
-
- failures = true
- elsif subject.length > 50
- warn_commit(
- commit,
- "This commit's subject line is acceptable, but please try to [reduce it to 50 characters](#{URL_LIMIT_SUBJECT})."
- )
- 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 && too_many_changed_lines?(commit)
- fail_commit(
- commit,
- 'Commits that change 30 or more lines across at least three files ' \
- 'must describe these changes in the commit body'
- )
-
- failures = true
- end
-
- if emoji_checker.includes_emoji?(commit.message)
- 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+\b))
- 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
+ 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 && too_many_changed_lines?(commit)
+ fail_commit(
+ commit,
+ 'Commits that change 30 or more lines across at least three files ' \
+ 'must describe these changes in the commit body'
+ )
+
+ failures = true
+ end
+
+ if emoji_checker.includes_emoji?(commit.message)
+ 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+\b))
+ 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
+
+ failures
+end
+
+def lint_commits(commits)
+ failed = commits.select do |commit|
+ lint_commit(commit)
end
- if failures
+ if failed.any?
markdown(<<~MARKDOWN)
## Commit message standards
diff --git a/danger/documentation/Dangerfile b/danger/documentation/Dangerfile
index 188331cc87c..0cf478b4f89 100644
--- a/danger/documentation/Dangerfile
+++ b/danger/documentation/Dangerfile
@@ -1,47 +1,36 @@
# 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
-
-docs_paths_to_review = docs_paths_requiring_review(helper.all_changed_files)
+docs_paths_to_review = helper.changes_by_category[:docs]
unless docs_paths_to_review.empty?
- message 'This merge request adds or changes files that require a ' \
- 'review from the Docs team.'
+ message 'This merge request adds or changes files that require a review ' \
+ 'from the Technical Writing team whether before or after merging.'
markdown(<<~MARKDOWN)
-## Docs review
+## Documentation review
-The following files require a review from the Documentation team:
+The following files will require a review from the [Technical Writing](https://about.gitlab.com/handbook/product/technical-writing/) team:
* #{docs_paths_to_review.map { |path| "`#{path}`" }.join("\n* ")}
-When your content is ready for review, assign the MR to a technical writer
-according to the [DevOps stages](https://about.gitlab.com/handbook/product/categories/#devops-stages)
-in the table below. If necessary, mention them in a comment explaining what needs
-to be reviewed.
+Per the [documentation workflows](https://docs.gitlab.com/ee/development/documentation/workflow.html), the review may occur prior to merging this MR **or** after a maintainer has agreed to review and merge this MR without a tech writer review. (Meanwhile, you are welcome to involve a technical writer at any time if you have questions about writing or updating the documentation. GitLabbers are also welcome to use the [#docs](https://gitlab.slack.com/archives/C16HYA2P5) channel on Slack.)
+
+The technical writer who, by default, will review this content or collaborate as needed is dependent on the [DevOps stage](https://about.gitlab.com/handbook/product/categories/#devops-stages) to which the content applies:
| Tech writer | Stage(s) |
| ------------ | ------------------------------------------------------------ |
| `@marcia` | ~Create ~Release + ~"development guidelines" |
-| `@axil` | ~Distribution ~Gitaly ~Gitter ~Monitor ~Package ~Secure |
+| `@axil` | ~Distribution ~Gitaly ~Gitter ~Monitor ~Package ~Secure |
| `@eread` | ~Manage ~Configure ~Geo ~Verify |
| `@mikelewis` | ~Plan |
-You are welcome to mention them sooner if you have questions about writing or
-updating the documentation. GitLabbers are also welcome to use the
-[#docs](https://gitlab.slack.com/archives/C16HYA2P5) channel on Slack.
+**To request a review prior to merging**
+
+- Assign the MR to the applicable technical writer, above. If you are not sure which category the change falls within, or the change is not part of one of these categories, mention one of the usernames above.
+
+**To request a review to commence after merging**
-If you are not sure which category the change falls within, or the change is not
-part of one of these categories, mention one of the usernames above.
+- Create an issue for a doc review [using the Doc Review template](https://gitlab.com/gitlab-org/gitlab-ce/issues/new?issuable_template=Doc%20Review) and assign it to the applicable technicial writer from the table.
MARKDOWN
unless gitlab.mr_labels.include?('Documentation')
diff --git a/danger/plugins/helper.rb b/danger/plugins/helper.rb
index f4eb9119266..581c0720083 100644
--- a/danger/plugins/helper.rb
+++ b/danger/plugins/helper.rb
@@ -1,34 +1,15 @@
# frozen_string_literal: true
+require 'net/http'
+require 'yaml'
+
+require_relative '../../lib/gitlab/danger/helper'
+
module Danger
- # Common helper functions for our danger scripts
- # If we find ourselves repeating code in our danger files, we might as well put them in here.
+ # Common helper functions for our danger scripts. See Gitlab::Danger::Helper
+ # for more details
class Helper < Plugin
- # Returns a list of all files that have been added, modified or renamed.
- # `git.modified_files` might contain paths that already have been renamed,
- # so we need to remove them from the list.
- #
- # Considering these changes:
- #
- # - A new_file.rb
- # - D deleted_file.rb
- # - M modified_file.rb
- # - R renamed_file_before.rb -> renamed_file_after.rb
- #
- # it will return
- # ```
- # [ 'new_file.rb', 'modified_file.rb', 'renamed_file_after.rb' ]
- # ```
- #
- # @return [Array<String>]
- def all_changed_files
- Set.new
- .merge(git.added_files.to_a)
- .merge(git.modified_files.to_a)
- .merge(git.renamed_files.map { |x| x[:after] })
- .subtract(git.renamed_files.map { |x| x[:before] })
- .to_a
- .sort
- end
+ # Put the helper code somewhere it can be tested
+ include Gitlab::Danger::Helper
end
end
diff --git a/danger/roulette/Dangerfile b/danger/roulette/Dangerfile
new file mode 100644
index 00000000000..6cf54d0f854
--- /dev/null
+++ b/danger/roulette/Dangerfile
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+MESSAGE = <<MARKDOWN
+## Reviewer roulette
+
+Changes that require review have been detected! A merge request is normally
+reviewed by both a reviewer and a maintainer in its primary category (e.g.
+~frontend or ~backend), and by a maintainer in all other categories.
+MARKDOWN
+
+CATEGORY_TABLE_HEADER = <<MARKDOWN
+
+To spread load more evenly across eligible reviewers, Danger has randomly picked
+a candidate for each review slot. Feel free to override this selection if you
+think someone else would be better-suited, or the chosen person is unavailable.
+
+Once you've decided who will review this merge request, mention them as you
+normally would! Danger does not (yet?) automatically notify them for you.
+
+| Category | Reviewer | Maintainer |
+| -------- | -------- | ---------- |
+MARKDOWN
+
+UNKNOWN_FILES_MESSAGE = <<MARKDOWN
+
+These files couldn't be categorised, so Danger was unable to suggest a reviewer.
+Please consider creating a merge request to
+[add support](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/danger/helper.rb)
+for them.
+MARKDOWN
+
+def spin(team, project, category)
+ reviewers = team.select { |member| member.reviewer?(project, category) }
+ maintainers = team.select { |member| member.maintainer?(project, category) }
+
+ # TODO: filter out people who are currently not in the office
+ # TODO: take CODEOWNERS into account?
+
+ reviewer = reviewers[rand(reviewers.size)]
+ maintainer = maintainers[rand(maintainers.size)]
+
+ "| #{helper.label_for_category(category)} | #{reviewer&.markdown_name} | #{maintainer&.markdown_name} |"
+end
+
+def build_list(items)
+ list = items.map { |filename| "* `#{filename}`" }.join("\n")
+
+ if items.size > 10
+ "\n<details>\n\n#{list}\n\n</details>"
+ else
+ list
+ end
+end
+
+changes = helper.changes_by_category
+
+# Ignore any files that are known but uncategoried. Prompt for any unknown files
+changes.delete(:none)
+categories = changes.keys - [:unknown]
+
+unless changes.empty?
+ team =
+ begin
+ helper.project_team
+ rescue => err
+ warn("Reviewer roulette failed to load team data: #{err.message}")
+ []
+ end
+
+ # Exclude the MR author from the team for selection purposes
+ team.delete_if { |teammate| teammate.username == gitlab.mr_author }
+
+ project = helper.project_name
+ unknown = changes.fetch(:unknown, [])
+
+ rows = categories.map { |category| spin(team, project, category) }
+
+ markdown(MESSAGE)
+ markdown(CATEGORY_TABLE_HEADER + rows.join("\n")) unless rows.empty?
+ markdown(UNKNOWN_FILES_MESSAGE + build_list(unknown)) unless unknown.empty?
+end
diff --git a/db/migrate/20180209115333_create_chatops_tables.rb b/db/migrate/20180209115333_create_chatops_tables.rb
new file mode 100644
index 00000000000..2cfb71e1007
--- /dev/null
+++ b/db/migrate/20180209115333_create_chatops_tables.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+class CreateChatopsTables < ActiveRecord::Migration[4.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ create_table :ci_pipeline_chat_data, id: :bigserial do |t|
+ t.integer :pipeline_id, null: false
+ t.references :chat_name, foreign_key: { on_delete: :cascade }, null: false
+ t.text :response_url, null: false
+
+ # A pipeline can only contain one row in this table, hence this index is
+ # unique.
+ t.index :pipeline_id, unique: true
+
+ t.index :chat_name_id
+ end
+
+ # rubocop:disable Migration/AddConcurrentForeignKey
+ add_foreign_key :ci_pipeline_chat_data, :ci_pipelines,
+ column: :pipeline_id,
+ on_delete: :cascade
+ end
+end
diff --git a/db/migrate/20180314145917_add_header_and_footer_banners_to_appearances_table.rb b/db/migrate/20180314145917_add_header_and_footer_banners_to_appearances_table.rb
new file mode 100644
index 00000000000..8aba3448035
--- /dev/null
+++ b/db/migrate/20180314145917_add_header_and_footer_banners_to_appearances_table.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class AddHeaderAndFooterBannersToAppearancesTable < ActiveRecord::Migration[4.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :appearances, :header_message, :text
+ add_column :appearances, :header_message_html, :text
+
+ add_column :appearances, :footer_message, :text
+ add_column :appearances, :footer_message_html, :text
+
+ add_column :appearances, :message_background_color, :text
+ add_column :appearances, :message_font_color, :text
+ end
+end
diff --git a/db/migrate/20181205171941_create_project_daily_statistics.rb b/db/migrate/20181205171941_create_project_daily_statistics.rb
new file mode 100644
index 00000000000..c9e2a1e1aa7
--- /dev/null
+++ b/db/migrate/20181205171941_create_project_daily_statistics.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class CreateProjectDailyStatistics < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ create_table :project_daily_statistics, id: :bigserial do |t|
+ t.integer :project_id, null: false
+ t.integer :fetch_count, null: false
+ t.date :date
+
+ t.index [:project_id, :date], unique: true, order: { date: :desc }
+ t.foreign_key :projects, on_delete: :cascade
+ end
+ end
+end
diff --git a/db/migrate/20190206193120_add_index_to_tags.rb b/db/migrate/20190206193120_add_index_to_tags.rb
new file mode 100644
index 00000000000..5257ebba003
--- /dev/null
+++ b/db/migrate/20190206193120_add_index_to_tags.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class AddIndexToTags < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ INDEX_NAME = 'index_tags_on_name_trigram'
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :tags, :name, name: INDEX_NAME, using: :gin, opclasses: { name: :gin_trgm_ops }
+ end
+
+ def down
+ remove_concurrent_index_by_name(:tags, INDEX_NAME)
+ end
+end
diff --git a/db/migrate/20190215154930_add_merge_pipelines_enabled_to_ci_cd_settings.rb b/db/migrate/20190215154930_add_merge_pipelines_enabled_to_ci_cd_settings.rb
new file mode 100644
index 00000000000..2a2a216da7d
--- /dev/null
+++ b/db/migrate/20190215154930_add_merge_pipelines_enabled_to_ci_cd_settings.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AddMergePipelinesEnabledToCiCdSettings < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :project_ci_cd_settings, :merge_pipelines_enabled, :boolean
+ end
+end
diff --git a/db/migrate/20190218134158_add_masked_to_ci_variables.rb b/db/migrate/20190218134158_add_masked_to_ci_variables.rb
new file mode 100644
index 00000000000..b4999d5b4a9
--- /dev/null
+++ b/db/migrate/20190218134158_add_masked_to_ci_variables.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddMaskedToCiVariables < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default :ci_variables, :masked, :boolean, default: false, allow_null: false
+ end
+
+ def down
+ remove_column :ci_variables, :masked
+ end
+end
diff --git a/db/migrate/20190218134209_add_masked_to_ci_group_variables.rb b/db/migrate/20190218134209_add_masked_to_ci_group_variables.rb
new file mode 100644
index 00000000000..8633875b341
--- /dev/null
+++ b/db/migrate/20190218134209_add_masked_to_ci_group_variables.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddMaskedToCiGroupVariables < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default :ci_group_variables, :masked, :boolean, default: false, allow_null: false
+ end
+
+ def down
+ remove_column :ci_group_variables, :masked
+ end
+end
diff --git a/db/migrate/20190220142344_add_email_header_and_footer_enabled_flag_to_appearances_table.rb b/db/migrate/20190220142344_add_email_header_and_footer_enabled_flag_to_appearances_table.rb
new file mode 100644
index 00000000000..85b9e0580f4
--- /dev/null
+++ b/db/migrate/20190220142344_add_email_header_and_footer_enabled_flag_to_appearances_table.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddEmailHeaderAndFooterEnabledFlagToAppearancesTable < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ disable_ddl_transaction!
+
+ DOWNTIME = false
+
+ def up
+ add_column_with_default(:appearances, :email_header_and_footer_enabled, :boolean, default: false)
+ end
+
+ def down
+ remove_column(:appearances, :email_header_and_footer_enabled)
+ end
+end
diff --git a/db/migrate/20190220150130_add_extra_shas_to_ci_pipelines.rb b/db/migrate/20190220150130_add_extra_shas_to_ci_pipelines.rb
new file mode 100644
index 00000000000..45c7c0949c6
--- /dev/null
+++ b/db/migrate/20190220150130_add_extra_shas_to_ci_pipelines.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+class AddExtraShasToCiPipelines < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :ci_pipelines, :source_sha, :binary
+ add_column :ci_pipelines, :target_sha, :binary
+ end
+end
diff --git a/db/migrate/20190228092516_clean_up_noteable_id_for_notes_on_commits.rb b/db/migrate/20190228092516_clean_up_noteable_id_for_notes_on_commits.rb
new file mode 100644
index 00000000000..fe129e8f4dc
--- /dev/null
+++ b/db/migrate/20190228092516_clean_up_noteable_id_for_notes_on_commits.rb
@@ -0,0 +1,33 @@
+# 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 CleanUpNoteableIdForNotesOnCommits < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ TEMP_INDEX_NAME = 'index_notes_on_commit_with_null_noteable_id'
+
+ disable_ddl_transaction!
+
+ def up
+ remove_concurrent_index_by_name(:notes, TEMP_INDEX_NAME)
+
+ add_concurrent_index(:notes, :id, where: "noteable_type = 'Commit' AND noteable_id IS NOT NULL", name: TEMP_INDEX_NAME)
+
+ # rubocop:disable Migration/UpdateLargeTable
+ update_column_in_batches(:notes, :noteable_id, nil) do |table, query|
+ query.where(
+ table[:noteable_type].eq('Commit').and(table[:noteable_id].not_eq(nil))
+ )
+ end
+
+ remove_concurrent_index_by_name(:notes, TEMP_INDEX_NAME)
+ end
+
+ def down
+ end
+end
diff --git a/db/migrate/limits_to_mysql.rb b/db/migrate/limits_to_mysql.rb
index 87992b541b1..33cb19aff9e 100644
--- a/db/migrate/limits_to_mysql.rb
+++ b/db/migrate/limits_to_mysql.rb
@@ -2,19 +2,6 @@ class LimitsToMysql < ActiveRecord::Migration[4.2]
def up
return unless ActiveRecord::Base.configurations[Rails.env]['adapter'] =~ /^mysql/
- # These columns were removed in 10.3, but this is called from two places:
- # 1. A migration run after they were added, but before they were removed.
- # 2. A rake task which can be run at any time.
- #
- # Because of item 2, we need these checks.
- if column_exists?(:merge_request_diffs, :st_commits)
- change_column :merge_request_diffs, :st_commits, :text, limit: 2147483647
- end
-
- if column_exists?(:merge_request_diffs, :st_diffs)
- change_column :merge_request_diffs, :st_diffs, :text, limit: 2147483647
- end
-
change_column :snippets, :content, :text, limit: 2147483647
change_column :notes, :st_diff, :text, limit: 2147483647
end
diff --git a/db/post_migrate/20181101091005_steal_digest_column.rb b/db/post_migrate/20181101091005_steal_digest_column.rb
new file mode 100644
index 00000000000..58ea710c18a
--- /dev/null
+++ b/db/post_migrate/20181101091005_steal_digest_column.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class StealDigestColumn < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ Gitlab::BackgroundMigration.steal('DigestColumn')
+ end
+
+ def down
+ # raise ActiveRecord::IrreversibleMigration
+ end
+end
diff --git a/db/post_migrate/20181101091124_remove_token_from_personal_access_tokens.rb b/db/post_migrate/20181101091124_remove_token_from_personal_access_tokens.rb
new file mode 100644
index 00000000000..415373068d5
--- /dev/null
+++ b/db/post_migrate/20181101091124_remove_token_from_personal_access_tokens.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class RemoveTokenFromPersonalAccessTokens < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ remove_column :personal_access_tokens, :token, :string
+ end
+end
diff --git a/db/post_migrate/20190301081611_migrate_project_migrate_sidekiq_queue.rb b/db/post_migrate/20190301081611_migrate_project_migrate_sidekiq_queue.rb
new file mode 100644
index 00000000000..6af7902e0c4
--- /dev/null
+++ b/db/post_migrate/20190301081611_migrate_project_migrate_sidekiq_queue.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class MigrateProjectMigrateSidekiqQueue < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ DOWNTIME = false
+
+ def up
+ sidekiq_queue_migrate 'project_migrate_hashed_storage', to: 'hashed_storage:hashed_storage_project_migrate'
+ end
+
+ def down
+ sidekiq_queue_migrate 'hashed_storage:hashed_storage_project_migrate', to: 'project_migrate_hashed_storage'
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 0f7e9ad4996..2ddc8358433 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20190204115450) do
+ActiveRecord::Schema.define(version: 20190301081611) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -37,7 +37,14 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.integer "cached_markdown_version"
t.text "new_project_guidelines"
t.text "new_project_guidelines_html"
+ t.text "header_message"
+ t.text "header_message_html"
+ t.text "footer_message"
+ t.text "footer_message_html"
+ t.text "message_background_color"
+ t.text "message_font_color"
t.string "favicon"
+ t.boolean "email_header_and_footer_enabled", default: false, null: false
end
create_table "application_setting_terms", force: :cascade do |t|
@@ -399,6 +406,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.boolean "protected", default: false, null: false
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
+ t.boolean "masked", default: false, null: false
t.index ["group_id", "key"], name: "index_ci_group_variables_on_group_id_and_key", unique: true, using: :btree
end
@@ -421,6 +429,14 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["project_id"], name: "index_ci_job_artifacts_on_project_id", using: :btree
end
+ create_table "ci_pipeline_chat_data", id: :bigserial, force: :cascade do |t|
+ t.integer "pipeline_id", null: false
+ t.integer "chat_name_id", null: false
+ t.text "response_url", null: false
+ t.index ["chat_name_id"], name: "index_ci_pipeline_chat_data_on_chat_name_id", using: :btree
+ t.index ["pipeline_id"], name: "index_ci_pipeline_chat_data_on_pipeline_id", unique: true, using: :btree
+ end
+
create_table "ci_pipeline_schedule_variables", force: :cascade do |t|
t.string "key", null: false
t.text "value"
@@ -483,6 +499,8 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.integer "failure_reason"
t.integer "iid"
t.integer "merge_request_id"
+ t.binary "source_sha"
+ t.binary "target_sha"
t.index ["auto_canceled_by_id"], name: "index_ci_pipelines_on_auto_canceled_by_id", using: :btree
t.index ["merge_request_id"], name: "index_ci_pipelines_on_merge_request_id", where: "(merge_request_id IS NOT NULL)", using: :btree
t.index ["pipeline_schedule_id"], name: "index_ci_pipelines_on_pipeline_schedule_id", using: :btree
@@ -586,6 +604,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.integer "project_id", null: false
t.boolean "protected", default: false, null: false
t.string "environment_scope", default: "*", null: false
+ t.boolean "masked", default: false, null: false
t.index ["project_id", "key", "environment_scope"], name: "index_ci_variables_on_project_id_and_key_and_environment_scope", unique: true, using: :btree
end
@@ -1507,7 +1526,6 @@ ActiveRecord::Schema.define(version: 20190204115450) do
create_table "personal_access_tokens", force: :cascade do |t|
t.integer "user_id", null: false
- t.string "token"
t.string "name", null: false
t.boolean "revoked", default: false
t.date "expires_at"
@@ -1516,7 +1534,6 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.string "scopes", default: "--- []\n", null: false
t.boolean "impersonation", default: false, null: false
t.string "token_digest"
- t.index ["token"], name: "index_personal_access_tokens_on_token", unique: true, using: :btree
t.index ["token_digest"], name: "index_personal_access_tokens_on_token_digest", unique: true, using: :btree
t.index ["user_id"], name: "index_personal_access_tokens_on_user_id", using: :btree
end
@@ -1559,6 +1576,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
create_table "project_ci_cd_settings", force: :cascade do |t|
t.integer "project_id", null: false
t.boolean "group_runners_enabled", default: true, null: false
+ t.boolean "merge_pipelines_enabled"
t.index ["project_id"], name: "index_project_ci_cd_settings_on_project_id", unique: true, using: :btree
end
@@ -1572,6 +1590,13 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["project_id", "key"], name: "index_project_custom_attributes_on_project_id_and_key", unique: true, using: :btree
end
+ create_table "project_daily_statistics", id: :bigserial, force: :cascade do |t|
+ t.integer "project_id", null: false
+ t.integer "fetch_count", null: false
+ t.date "date"
+ t.index ["project_id", "date"], name: "index_project_daily_statistics_on_project_id_and_date", unique: true, order: { date: :desc }, using: :btree
+ end
+
create_table "project_deploy_tokens", force: :cascade do |t|
t.integer "project_id", null: false
t.integer "deploy_token_id", null: false
@@ -2030,6 +2055,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.string "name"
t.integer "taggings_count", default: 0
t.index ["name"], name: "index_tags_on_name", unique: true, using: :btree
+ t.index ["name"], name: "index_tags_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"}
end
create_table "term_agreements", force: :cascade do |t|
@@ -2345,6 +2371,8 @@ ActiveRecord::Schema.define(version: 20190204115450) do
add_foreign_key "ci_group_variables", "namespaces", column: "group_id", name: "fk_33ae4d58d8", on_delete: :cascade
add_foreign_key "ci_job_artifacts", "ci_builds", column: "job_id", on_delete: :cascade
add_foreign_key "ci_job_artifacts", "projects", on_delete: :cascade
+ add_foreign_key "ci_pipeline_chat_data", "chat_names", on_delete: :cascade
+ add_foreign_key "ci_pipeline_chat_data", "ci_pipelines", column: "pipeline_id", on_delete: :cascade
add_foreign_key "ci_pipeline_schedule_variables", "ci_pipeline_schedules", column: "pipeline_schedule_id", name: "fk_41c35fda51", on_delete: :cascade
add_foreign_key "ci_pipeline_schedules", "projects", name: "fk_8ead60fcc4", on_delete: :cascade
add_foreign_key "ci_pipeline_schedules", "users", column: "owner_id", name: "fk_9ea99f58d2", on_delete: :nullify
@@ -2453,6 +2481,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
add_foreign_key "project_auto_devops", "projects", on_delete: :cascade
add_foreign_key "project_ci_cd_settings", "projects", name: "fk_24c15d2f2e", on_delete: :cascade
add_foreign_key "project_custom_attributes", "projects", on_delete: :cascade
+ add_foreign_key "project_daily_statistics", "projects", on_delete: :cascade
add_foreign_key "project_deploy_tokens", "deploy_tokens", on_delete: :cascade
add_foreign_key "project_deploy_tokens", "projects", on_delete: :cascade
add_foreign_key "project_error_tracking_settings", "projects", on_delete: :cascade
diff --git a/doc/README.md b/doc/README.md
index f87ff925e94..ecc214d97c8 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -38,6 +38,7 @@ Have a look at some of our most popular documentation resources:
| [GitLab CI/CD examples](ci/examples/README.md) | Get up to speed quickly with common CI/CD scenarios. |
| [GitLab Container Registry](user/project/container_registry.md) | Host containers within GitLab. |
| [GitLab Pages](user/project/pages/index.md) | Host static websites for your projects with GitLab. |
+| [GitLab.com settings](user/gitlab_com/index.md) | Settings for [GitLab.com](#gitlabcom). |
| [Kubernetes integration](user/project/clusters/index.md) | Use GitLab with Kubernetes. |
| [SSH authentication](ssh/README.md) | Secure your network communications. |
| [Using Docker images](ci/docker/using_docker_images.md) | Build and test your applications with Docker. |
@@ -284,9 +285,11 @@ The following documentation relates to the DevOps **Configure** stage:
| [Auto DevOps](topics/autodevops/index.md) | Automatically employ a complete DevOps lifecycle. |
| [Easy creation of Kubernetes<br/>clusters on GKE](user/project/clusters/index.md#adding-and-creating-a-new-gke-cluster-via-gitlab) | Use Google Kubernetes Engine and GitLab. |
| [Executable Runbooks](user/project/clusters/runbooks/index.md) | Documented procedures that explain how to carry out particular processes. |
+| [GitLab ChatOps](ci/chatops/README.md) | Interact with CI/CD jobs through chat services. |
| [Installing Applications](user/project/clusters/index.md#installing-applications) | Deploy Helm, Ingress, and Prometheus on Kubernetes. |
| [Mattermost slash commands](user/project/integrations/mattermost_slash_commands.md) | Enable and use slash commands from within Mattermost. |
| [Protected variables](ci/variables/README.md#protected-variables) | Restrict variables to protected branches and tags. |
+| [Serverless](user/project/clusters/serverless/index.md) | Run serverless workloads on Kubernetes. |
| [Slack slash commands](user/project/integrations/slack_slash_commands.md) | Enable and use slash commands from within Slack. |
<div align="right">
diff --git a/doc/administration/auth/authentiq.md b/doc/administration/auth/authentiq.md
index 772e55cef07..726622d8599 100644
--- a/doc/administration/auth/authentiq.md
+++ b/doc/administration/auth/authentiq.md
@@ -50,9 +50,8 @@ Authentiq will generate a Client ID and the accompanying Client Secret for you t
}
```
-
1. The `scope` is set to request the user's name, email (required and signed), and permission to send push notifications to sign in on subsequent visits.
-See [OmniAuth Authentiq strategy](https://github.com/AuthentiqID/omniauth-authentiq/wiki/Scopes,-callback-url-configuration-and-responses) for more information on scopes and modifiers.
+ See [OmniAuth Authentiq strategy](https://github.com/AuthentiqID/omniauth-authentiq/wiki/Scopes,-callback-url-configuration-and-responses) for more information on scopes and modifiers.
1. Change `YOUR_CLIENT_ID` and `YOUR_CLIENT_SECRET` to the Client credentials you received in step 1.
diff --git a/doc/administration/auth/ldap.md b/doc/administration/auth/ldap.md
index 0ac73c55580..d5d0d99ac24 100644
--- a/doc/administration/auth/ldap.md
+++ b/doc/administration/auth/ldap.md
@@ -448,11 +448,10 @@ ldapsearch -H ldaps://$host:$port -D "$bind_dn" -y bind_dn_password.txt -b "$ba
port.
- We are assuming the password for the bind_dn user is in bind_dn_password.txt.
-
### Invalid credentials when logging in
- Make sure the user you are binding with has enough permissions to read the user's
-tree and traverse it.
+ tree and traverse it.
- Check that the `user_filter` is not blocking otherwise valid users.
- Run the following check command to make sure that the LDAP settings are
correct and GitLab can see your users:
diff --git a/doc/administration/auth/okta.md b/doc/administration/auth/okta.md
index ae38094391b..3136923fa96 100644
--- a/doc/administration/auth/okta.md
+++ b/doc/administration/auth/okta.md
@@ -140,7 +140,6 @@ Now that the Okta app is configured, it's time to enable it in GitLab.
}
```
-
1. [Reconfigure][reconf] or [restart] GitLab for Omnibus and installations
from source respectively for the changes to take effect.
diff --git a/doc/administration/container_registry.md b/doc/administration/container_registry.md
index 60f1911fa2a..a1ac4a2a57c 100644
--- a/doc/administration/container_registry.md
+++ b/doc/administration/container_registry.md
@@ -542,7 +542,6 @@ Read more about the Container Registry notifications config options in the
>**Note:**
Multiple endpoints can be configured for the Container Registry.
-
**Omnibus GitLab installations**
To configure a notification endpoint in Omnibus:
diff --git a/doc/administration/high_availability/gitlab.md b/doc/administration/high_availability/gitlab.md
index e554c06532e..d95c3acec54 100644
--- a/doc/administration/high_availability/gitlab.md
+++ b/doc/administration/high_availability/gitlab.md
@@ -146,6 +146,14 @@ the share is exported and exists on the NFS server and try to remount.
---
+## Upgrading GitLab HA
+
+GitLab HA installations can be upgraded with no downtime, but the
+upgrade process must be carefully coordinated to avoid failures. See the
+[Omnibus GitLab multi-node upgrade
+document](https://docs.gitlab.com/omnibus/update/#multi-node--ha-deployment)
+for more details.
+
Read more on high-availability configuration:
1. [Configure the database](database.md)
diff --git a/doc/administration/high_availability/redis.md b/doc/administration/high_availability/redis.md
index 987a0b9f350..a52bc5c3b02 100644
--- a/doc/administration/high_availability/redis.md
+++ b/doc/administration/high_availability/redis.md
@@ -359,11 +359,17 @@ following section assumes you are using Omnibus GitLab Enterprise Edition.
For the Omnibus Community Edition and installations from source, follow the
[Redis HA source install](redis_source.md) guide.
+NOTE: **Note:** If you are using an external Redis Sentinel instance, be sure
+to exclude the `requirepass` parameter from the Sentinel
+configuration. This parameter will cause clients to report `NOAUTH
+Authentication required.`. [Redis Sentinel 3.2.x does not support
+password authentication](https://github.com/antirez/redis/issues/3279).
+
Now that the Redis servers are all set up, let's configure the Sentinel
servers.
If you are not sure if your Redis servers are working and replicating
-correctly, please read the [Troubleshooting Replication](#troubleshooting-replication)
+correctly, please read the [Troubleshooting Replication](#troubleshooting-redis-replication)
and fix it before proceeding with Sentinel setup.
You must have at least `3` Redis Sentinel servers, and they need to
@@ -855,7 +861,6 @@ To make sure your configuration is correct:
You should see a different port after a few seconds delay
(the failover/reconnect time).
-
## Changelog
Changes to Redis HA over time.
diff --git a/doc/administration/high_availability/redis_source.md b/doc/administration/high_availability/redis_source.md
index 14e2784c419..be6b547372a 100644
--- a/doc/administration/high_availability/redis_source.md
+++ b/doc/administration/high_availability/redis_source.md
@@ -46,7 +46,7 @@ valuable information for the general setup.
Assuming that the Redis master instance IP is `10.0.0.1`:
-1. [Install Redis](../../install/installation.md#6-redis)
+1. [Install Redis](../../install/installation.md#7-redis).
1. Edit `/etc/redis/redis.conf`:
```conf
@@ -72,7 +72,7 @@ Assuming that the Redis master instance IP is `10.0.0.1`:
Assuming that the Redis slave instance IP is `10.0.0.2`:
-1. [Install Redis](../../install/installation.md#6-redis)
+1. [Install Redis](../../install/installation.md#7-redis).
1. Edit `/etc/redis/redis.conf`:
```conf
diff --git a/doc/administration/incoming_email.md b/doc/administration/incoming_email.md
index 05873e01a08..658b2f55d30 100644
--- a/doc/administration/incoming_email.md
+++ b/doc/administration/incoming_email.md
@@ -95,244 +95,249 @@ for a real-world example of this exploit.
### Omnibus package installations
-1. Find the `incoming_email` section in `/etc/gitlab/gitlab.rb`, enable the
- feature and fill in the details for your specific IMAP server and email account:
+1. Find the `incoming_email` section in `/etc/gitlab/gitlab.rb`, enable the feature
+ and fill in the details for your specific IMAP server and email account (see [examples](#config-examples) below).
- Configuration for Postfix mail server, assumes mailbox
- incoming@gitlab.example.com
+1. Reconfigure GitLab for the changes to take effect:
- ```ruby
- gitlab_rails['incoming_email_enabled'] = true
+ ```sh
+ sudo gitlab-ctl reconfigure
+ sudo gitlab-ctl restart
+ ```
- # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to.
- # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`).
- gitlab_rails['incoming_email_address'] = "incoming+%{key}@gitlab.example.com"
+1. Verify that everything is configured correctly:
- # Email account username
- # With third party providers, this is usually the full email address.
- # With self-hosted email servers, this is usually the user part of the email address.
- gitlab_rails['incoming_email_email'] = "incoming"
- # Email account password
- gitlab_rails['incoming_email_password'] = "[REDACTED]"
+ ```sh
+ sudo gitlab-rake gitlab:incoming_email:check
+ ```
- # IMAP server host
- gitlab_rails['incoming_email_host'] = "gitlab.example.com"
- # IMAP server port
- gitlab_rails['incoming_email_port'] = 143
- # Whether the IMAP server uses SSL
- gitlab_rails['incoming_email_ssl'] = false
- # Whether the IMAP server uses StartTLS
- gitlab_rails['incoming_email_start_tls'] = false
+Reply by email should now be working.
- # The mailbox where incoming mail will end up. Usually "inbox".
- gitlab_rails['incoming_email_mailbox_name'] = "inbox"
- # The IDLE command timeout.
- gitlab_rails['incoming_email_idle_timeout'] = 60
+### Installations from source
+
+1. Go to the GitLab installation directory:
+
+ ```sh
+ cd /home/git/gitlab
```
- Configuration for Gmail / Google Apps, assumes mailbox
- gitlab-incoming@gmail.com
+1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature
+ and fill in the details for your specific IMAP server and email account (see [examples](#config-examples) below).
- ```ruby
- gitlab_rails['incoming_email_enabled'] = true
+1. Enable `mail_room` in the init script at `/etc/default/gitlab`:
+
+ ```sh
+ sudo mkdir -p /etc/default
+ echo 'mail_room_enabled=true' | sudo tee -a /etc/default/gitlab
+ ```
+
+1. Restart GitLab:
+
+ ```sh
+ sudo service gitlab restart
+ ```
+
+1. Verify that everything is configured correctly:
+
+ ```sh
+ sudo -u git -H bundle exec rake gitlab:incoming_email:check RAILS_ENV=production
+ ```
+
+Reply by email should now be working.
+
+### Config examples
+
+#### Postfix
+
+Example configuration for Postfix mail server. Assumes mailbox incoming@gitlab.example.com.
+
+Example for Omnibus installs:
+
+```ruby
+gitlab_rails['incoming_email_enabled'] = true
+
+# The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to.
+# The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`).
+gitlab_rails['incoming_email_address'] = "incoming+%{key}@gitlab.example.com"
+
+# Email account username
+# With third party providers, this is usually the full email address.
+# With self-hosted email servers, this is usually the user part of the email address.
+gitlab_rails['incoming_email_email'] = "incoming"
+# Email account password
+gitlab_rails['incoming_email_password'] = "[REDACTED]"
+
+# IMAP server host
+gitlab_rails['incoming_email_host'] = "gitlab.example.com"
+# IMAP server port
+gitlab_rails['incoming_email_port'] = 143
+# Whether the IMAP server uses SSL
+gitlab_rails['incoming_email_ssl'] = false
+# Whether the IMAP server uses StartTLS
+gitlab_rails['incoming_email_start_tls'] = false
+
+# The mailbox where incoming mail will end up. Usually "inbox".
+gitlab_rails['incoming_email_mailbox_name'] = "inbox"
+# The IDLE command timeout.
+gitlab_rails['incoming_email_idle_timeout'] = 60
+```
+
+Example for source installs:
+
+```yaml
+incoming_email:
+ enabled: true
# The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to.
# The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`).
- gitlab_rails['incoming_email_address'] = "gitlab-incoming+%{key}@gmail.com"
+ address: "incoming+%{key}@gitlab.example.com"
# Email account username
# With third party providers, this is usually the full email address.
# With self-hosted email servers, this is usually the user part of the email address.
- gitlab_rails['incoming_email_email'] = "gitlab-incoming@gmail.com"
+ user: "incoming"
# Email account password
- gitlab_rails['incoming_email_password'] = "[REDACTED]"
+ password: "[REDACTED]"
# IMAP server host
- gitlab_rails['incoming_email_host'] = "imap.gmail.com"
+ host: "gitlab.example.com"
# IMAP server port
- gitlab_rails['incoming_email_port'] = 993
+ port: 143
# Whether the IMAP server uses SSL
- gitlab_rails['incoming_email_ssl'] = true
+ ssl: false
# Whether the IMAP server uses StartTLS
- gitlab_rails['incoming_email_start_tls'] = false
+ start_tls: false
# The mailbox where incoming mail will end up. Usually "inbox".
- gitlab_rails['incoming_email_mailbox_name'] = "inbox"
+ mailbox: "inbox"
# The IDLE command timeout.
- gitlab_rails['incoming_email_idle_timeout'] = 60
- ```
+ idle_timeout: 60
+```
+
+#### Gmail
+
+Example configuration for Gmail/G Suite. Assumes mailbox gitlab-incoming@gmail.com.
+
+Example for Omnibus installs:
+
+```ruby
+gitlab_rails['incoming_email_enabled'] = true
- Configuration for Microsoft Exchange mail server w/ IMAP enabled, assumes the
- catch-all mailbox incoming@exchange.example.com
+# The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to.
+# The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`).
+gitlab_rails['incoming_email_address'] = "gitlab-incoming+%{key}@gmail.com"
- ```ruby
- gitlab_rails['incoming_email_enabled'] = true
+# Email account username
+# With third party providers, this is usually the full email address.
+# With self-hosted email servers, this is usually the user part of the email address.
+gitlab_rails['incoming_email_email'] = "gitlab-incoming@gmail.com"
+# Email account password
+gitlab_rails['incoming_email_password'] = "[REDACTED]"
+
+# IMAP server host
+gitlab_rails['incoming_email_host'] = "imap.gmail.com"
+# IMAP server port
+gitlab_rails['incoming_email_port'] = 993
+# Whether the IMAP server uses SSL
+gitlab_rails['incoming_email_ssl'] = true
+# Whether the IMAP server uses StartTLS
+gitlab_rails['incoming_email_start_tls'] = false
+
+# The mailbox where incoming mail will end up. Usually "inbox".
+gitlab_rails['incoming_email_mailbox_name'] = "inbox"
+# The IDLE command timeout.
+gitlab_rails['incoming_email_idle_timeout'] = 60
+```
+
+Example for source installs:
+
+```yaml
+incoming_email:
+ enabled: true
# The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to.
# The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`).
- # Exchange does not support sub-addressing, so a catch-all mailbox must be used.
- gitlab_rails['incoming_email_address'] = "incoming-%{key}@exchange.example.com"
+ address: "gitlab-incoming+%{key}@gmail.com"
# Email account username
- # Typically this is the userPrincipalName (UPN)
- gitlab_rails['incoming_email_email'] = "incoming@ad-domain.example.com"
+ # With third party providers, this is usually the full email address.
+ # With self-hosted email servers, this is usually the user part of the email address.
+ user: "gitlab-incoming@gmail.com"
# Email account password
- gitlab_rails['incoming_email_password'] = "[REDACTED]"
+ password: "[REDACTED]"
# IMAP server host
- gitlab_rails['incoming_email_host'] = "exchange.example.com"
+ host: "imap.gmail.com"
# IMAP server port
- gitlab_rails['incoming_email_port'] = 993
+ port: 993
# Whether the IMAP server uses SSL
- gitlab_rails['incoming_email_ssl'] = true
- ```
-
-1. Reconfigure GitLab for the changes to take effect:
+ ssl: true
+ # Whether the IMAP server uses StartTLS
+ start_tls: false
- ```sh
- sudo gitlab-ctl reconfigure
- sudo gitlab-ctl restart
- ```
+ # The mailbox where incoming mail will end up. Usually "inbox".
+ mailbox: "inbox"
+ # The IDLE command timeout.
+ idle_timeout: 60
+```
-1. Verify that everything is configured correctly:
+#### MS Exchange
- ```sh
- sudo gitlab-rake gitlab:incoming_email:check
- ```
+Example configuration for Microsoft Exchange mail server with IMAP enabled. Assumes the
+catch-all mailbox incoming@exchange.example.com.
-1. Reply by email should now be working.
+Example for Omnibus installs:
-### Installations from source
+```ruby
+gitlab_rails['incoming_email_enabled'] = true
-1. Go to the GitLab installation directory:
+# The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to.
+# The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`).
+# Exchange does not support sub-addressing, so a catch-all mailbox must be used.
+gitlab_rails['incoming_email_address'] = "incoming-%{key}@exchange.example.com"
- ```sh
- cd /home/git/gitlab
- ```
+# Email account username
+# Typically this is the userPrincipalName (UPN)
+gitlab_rails['incoming_email_email'] = "incoming@ad-domain.example.com"
+# Email account password
+gitlab_rails['incoming_email_password'] = "[REDACTED]"
-1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature
- and fill in the details for your specific IMAP server and email account:
+# IMAP server host
+gitlab_rails['incoming_email_host'] = "exchange.example.com"
+# IMAP server port
+gitlab_rails['incoming_email_port'] = 993
+# Whether the IMAP server uses SSL
+gitlab_rails['incoming_email_ssl'] = true
+```
- ```sh
- sudo editor config/gitlab.yml
- ```
+Example for source installs:
- Configuration for Postfix mail server, assumes mailbox
- incoming@gitlab.example.com
-
- ```yaml
- incoming_email:
- enabled: true
-
- # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to.
- # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`).
- address: "incoming+%{key}@gitlab.example.com"
-
- # Email account username
- # With third party providers, this is usually the full email address.
- # With self-hosted email servers, this is usually the user part of the email address.
- user: "incoming"
- # Email account password
- password: "[REDACTED]"
-
- # IMAP server host
- host: "gitlab.example.com"
- # IMAP server port
- port: 143
- # Whether the IMAP server uses SSL
- ssl: false
- # Whether the IMAP server uses StartTLS
- start_tls: false
-
- # The mailbox where incoming mail will end up. Usually "inbox".
- mailbox: "inbox"
- # The IDLE command timeout.
- idle_timeout: 60
- ```
+```yaml
+incoming_email:
+ enabled: true
- Configuration for Gmail / Google Apps, assumes mailbox
- gitlab-incoming@gmail.com
-
- ```yaml
- incoming_email:
- enabled: true
-
- # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to.
- # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`).
- address: "gitlab-incoming+%{key}@gmail.com"
-
- # Email account username
- # With third party providers, this is usually the full email address.
- # With self-hosted email servers, this is usually the user part of the email address.
- user: "gitlab-incoming@gmail.com"
- # Email account password
- password: "[REDACTED]"
-
- # IMAP server host
- host: "imap.gmail.com"
- # IMAP server port
- port: 993
- # Whether the IMAP server uses SSL
- ssl: true
- # Whether the IMAP server uses StartTLS
- start_tls: false
-
- # The mailbox where incoming mail will end up. Usually "inbox".
- mailbox: "inbox"
- # The IDLE command timeout.
- idle_timeout: 60
- ```
-
- Configuration for Microsoft Exchange mail server w/ IMAP enabled, assumes the
- catch-all mailbox incoming@exchange.example.com
-
- ```yaml
- incoming_email:
- enabled: true
-
- # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to.
- # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`).
- # Exchange does not support sub-addressing, so a catch-all mailbox must be used.
- address: "incoming-%{key}@exchange.example.com"
-
- # Email account username
- # Typically this is the userPrincipalName (UPN)
- user: "incoming@ad-domain.example.com"
- # Email account password
- password: "[REDACTED]"
-
- # IMAP server host
- host: "exchange.example.com"
- # IMAP server port
- port: 993
- # Whether the IMAP server uses SSL
- ssl: true
- # Whether the IMAP server uses StartTLS
- start_tls: false
-
- # The mailbox where incoming mail will end up. Usually "inbox".
- mailbox: "inbox"
- # The IDLE command timeout.
- idle_timeout: 60
- ```
-
-1. Enable `mail_room` in the init script at `/etc/default/gitlab`:
-
- ```sh
- sudo mkdir -p /etc/default
- echo 'mail_room_enabled=true' | sudo tee -a /etc/default/gitlab
- ```
-
-1. Restart GitLab:
-
- ```sh
- sudo service gitlab restart
- ```
+ # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to.
+ # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`).
+ # Exchange does not support sub-addressing, so a catch-all mailbox must be used.
+ address: "incoming-%{key}@exchange.example.com"
-1. Verify that everything is configured correctly:
+ # Email account username
+ # Typically this is the userPrincipalName (UPN)
+ user: "incoming@ad-domain.example.com"
+ # Email account password
+ password: "[REDACTED]"
- ```sh
- sudo -u git -H bundle exec rake gitlab:incoming_email:check RAILS_ENV=production
- ```
+ # IMAP server host
+ host: "exchange.example.com"
+ # IMAP server port
+ port: 993
+ # Whether the IMAP server uses SSL
+ ssl: true
+ # Whether the IMAP server uses StartTLS
+ start_tls: false
-1. Reply by email should now be working.
+ # The mailbox where incoming mail will end up. Usually "inbox".
+ mailbox: "inbox"
+ # The IDLE command timeout.
+ idle_timeout: 60
+```
diff --git a/doc/administration/index.md b/doc/administration/index.md
index 12fec2753bf..6e08d4633cd 100644
--- a/doc/administration/index.md
+++ b/doc/administration/index.md
@@ -4,24 +4,27 @@ description: 'Learn how to install, configure, update, and maintain your GitLab
# Administrator documentation **[CORE ONLY]**
-Learn how to administer your GitLab instance (Community Edition and
-Enterprise Edition).
-Regular users don't have access to GitLab administration tools and settings.
+Learn how to administer your self-managed GitLab instance.
-GitLab has two product distributions: the open source
-[GitLab Community Edition (CE)](https://gitlab.com/gitlab-org/gitlab-ce),
-and the open core [GitLab Enterprise Edition (EE)](https://gitlab.com/gitlab-org/gitlab-ee),
-available through [different subscriptions](https://about.gitlab.com/pricing/).
+GitLab has two product distributions available through [different subscriptions](https://about.gitlab.com/pricing/):
-You can [install GitLab CE or GitLab EE](https://about.gitlab.com/installation/ce-or-ee/),
-but the features you'll have access to depend on the subscription you choose
-(Core, Starter, Premium, or Ultimate). GitLab Community Edition installations
-only have access to Core features.
+- The open source [GitLab Community Edition (CE)](https://gitlab.com/gitlab-org/gitlab-ce).
+- The open core [GitLab Enterprise Edition (EE)](https://gitlab.com/gitlab-org/gitlab-ee).
+
+You can [install either GitLab CE or GitLab EE](https://about.gitlab.com/installation/ce-or-ee/).
+However, the features you'll have access to depend on the subscription you choose
+(Core, Starter, Premium, or Ultimate).
+
+NOTE: **Note:**
+GitLab Community Edition installations only have access to Core features.
GitLab.com is administered by GitLab, Inc., therefore, only GitLab team members have
access to its admin configurations. If you're a GitLab.com user, please check the
[user documentation](../user/index.html).
+NOTE: **Note:**
+Non-administrator users don’t have access to GitLab administration tools and settings.
+
## Installing and maintaining GitLab
Learn how to install, configure, update, and maintain your GitLab instance.
@@ -48,8 +51,9 @@ Learn how to install, configure, update, and maintain your GitLab instance.
- [Third party offers](../user/admin_area/settings/third_party_offers.md)
- [Compliance](compliance.md): A collection of features from across the application that you may configure to help ensure that your GitLab instance and DevOps workflow meet compliance standards.
- [Diff limits](../user/admin_area/diff_limits.md): Configure the diff rendering size limits of branch comparison pages.
-- [Merge request diffs](merge_request_diffs.md): Configure the diffs shown on merge requests
+- [Merge request diffs storage](merge_request_diffs.md): Configure merge requests diffs external storage.
- [Broadcast Messages](../user/admin_area/broadcast_messages.md): Send messages to GitLab users through the UI.
+- [Admin Area](../user/admin_area/index.md): for self-managed instance-wide configuration and maintenance.
#### Customizing GitLab's appearance
@@ -127,7 +131,7 @@ Learn how to install, configure, update, and maintain your GitLab instance.
- [Job traces](job_traces.md): Information about the job traces (logs).
- [Register Shared and specific Runners](../ci/runners/README.md#registering-a-shared-runner): Learn how to register and configure Shared and specific Runners to your own instance.
- [Shared Runners pipelines quota](../user/admin_area/settings/continuous_integration.md#shared-runners-pipeline-minutes-quota): Limit the usage of pipeline minutes for Shared Runners.
-- [Enable/disable Auto DevOps](../topics/autodevops/index.md#enabling-auto-devops): Enable or disable Auto DevOps for your instance.
+- [Enable/disable Auto DevOps](../topics/autodevops/index.md#enablingdisabling-auto-devops): Enable or disable Auto DevOps for your instance.
## Git configuration options
diff --git a/doc/administration/integration/plantuml.md b/doc/administration/integration/plantuml.md
index b61c5409a56..d383d1efe70 100644
--- a/doc/administration/integration/plantuml.md
+++ b/doc/administration/integration/plantuml.md
@@ -52,7 +52,6 @@ http://localhost:8080/plantuml
you can change these defaults by editing the `/etc/tomcat7/server.xml` file.
-
## GitLab
You need to enable PlantUML integration from Settings under Admin Area. To do
diff --git a/doc/administration/merge_request_diffs.md b/doc/administration/merge_request_diffs.md
index 94620c3d3a0..c34a9519ace 100644
--- a/doc/administration/merge_request_diffs.md
+++ b/doc/administration/merge_request_diffs.md
@@ -1,7 +1,6 @@
-# Merge request diffs administration
+# Merge request diffs storage **[CORE ONLY]**
-> **Notes:**
-> - External merge request diffs introduced in GitLab 11.8
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/52568) in GitLab 11.8.
Merge request diffs are size-limited copies of diffs associated with merge
requests. When viewing a merge request, diffs are sourced from these copies
@@ -16,9 +15,7 @@ large, in which case, switching to external storage is recommended.
Merge request diffs can be stored on disk, or in object storage. In general, it
is better to store the diffs in the database than on disk.
-To enable external storage of merge request diffs:
-
----
+To enable external storage of merge request diffs, follow the instructions below.
**In Omnibus installations:**
@@ -29,17 +26,15 @@ To enable external storage of merge request diffs:
```
1. _The external diffs will be stored in in
- `/var/opt/gitlab/gitlab-rails/shared/external-diffs`._ To change the path,
- for example to `/mnt/storage/external-diffs`, edit `/etc/gitlab/gitlab.rb`
+ `/var/opt/gitlab/gitlab-rails/shared/external-diffs`._ To change the path,
+ for example, to `/mnt/storage/external-diffs`, edit `/etc/gitlab/gitlab.rb`
and add the following line:
```ruby
gitlab_rails['external_diffs_storage_path'] = "/mnt/storage/external-diffs"
```
-1. Save the file and [reconfigure GitLab][] for the changes to take effect.
-
----
+1. Save the file and [reconfigure GitLab](restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
**In installations from source:**
@@ -52,7 +47,7 @@ To enable external storage of merge request diffs:
```
1. _The external diffs will be stored in
- `/home/git/gitlab/shared/external-diffs`._ To change the path, for example
+ `/home/git/gitlab/shared/external-diffs`._ To change the path, for example,
to `/mnt/storage/external-diffs`, edit `/home/git/gitlab/config/gitlab.yml`
and add or amend the following lines:
@@ -62,18 +57,18 @@ To enable external storage of merge request diffs:
storage_path: /mnt/storage/external-diffs
```
-1. Save the file and [restart GitLab][] for the changes to take effect.
+1. Save the file and [restart GitLab](restart_gitlab.md#installations-from-source) for the changes to take effect.
### Using object storage
-Instead of storing the external diffs on disk, we recommended you use an object
+Instead of storing the external diffs on disk, we recommended the use of an object
store like AWS S3 instead. This configuration relies on valid AWS credentials to
be configured already.
### Object Storage Settings
For source installations, these settings are nested under `external_diffs:` and
-then `object_store:`. On omnibus installs, they are prefixed by
+then `object_store:`. On Omnibus installations, they are prefixed by
`external_diffs_object_store_`.
| Setting | Description | Default |
@@ -118,7 +113,7 @@ The connection settings match those provided by [Fog](https://github.com/fog), a
}
```
- NOTE: if you are using AWS IAM profiles, be sure to omit the
+ Note that, if you are using AWS IAM profiles, be sure to omit the
AWS access key and secret access key/value pairs. For example:
```ruby
@@ -129,9 +124,7 @@ The connection settings match those provided by [Fog](https://github.com/fog), a
}
```
-1. Save the file and [reconfigure GitLab][] for the changes to take effect.
-
----
+1. Save the file and [reconfigure GitLab](restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
**In installations from source:**
@@ -151,4 +144,4 @@ The connection settings match those provided by [Fog](https://github.com/fog), a
region: eu-central-1
```
-1. Save the file and [restart GitLab][] for the changes to take effect.
+1. Save the file and [restart GitLab](restart_gitlab.md#installations-from-source) for the changes to take effect.
diff --git a/doc/administration/monitoring/performance/grafana_configuration.md b/doc/administration/monitoring/performance/grafana_configuration.md
index 1f431f8bd62..ab43ec2cc4f 100644
--- a/doc/administration/monitoring/performance/grafana_configuration.md
+++ b/doc/administration/monitoring/performance/grafana_configuration.md
@@ -34,7 +34,7 @@ Test Connection to ensure the configuration is correct.
- **Default**: Checked
- **Type**: InfluxDB 0.9.x (Even if you're using InfluxDB 0.10.x)
- **Url**: `https://localhost:8086` (Or the remote URL if you've installed InfluxDB
-on a separate server)
+ on a separate server)
- **Access**: proxy
- **Database**: gitlab
- **User**: admin (Or the username configured when setting up InfluxDB)
diff --git a/doc/administration/monitoring/performance/performance_bar.md b/doc/administration/monitoring/performance/performance_bar.md
index 6a55dbe1eb4..95f497a1470 100644
--- a/doc/administration/monitoring/performance/performance_bar.md
+++ b/doc/administration/monitoring/performance/performance_bar.md
@@ -10,11 +10,11 @@ It allows you to see (from left to right):
- the current host serving the page
- the timing of the page (backend, frontend)
- time taken and number of DB queries, click through for details of these queries
-![SQL profiling using the Performance Bar](img/performance_bar_sql_queries.png)
+ ![SQL profiling using the Performance Bar](img/performance_bar_sql_queries.png)
- time taken and number of [Gitaly] calls, click through for details of these calls
-![Gitaly profiling using the Performance Bar](img/performance_bar_gitaly_calls.png)
+ ![Gitaly profiling using the Performance Bar](img/performance_bar_gitaly_calls.png)
- profile of the code used to generate the page, line by line. In the profile view, the numbers in the left panel represent wall time, cpu time, and number of calls (based on [rblineprof](https://github.com/tmm1/rblineprof)).
-![Line profiling using the Performance Bar](img/performance_bar_line_profiling.png)
+ ![Line profiling using the Performance Bar](img/performance_bar_line_profiling.png)
- time taken and number of calls to Redis
- time taken and number of background jobs created by Sidekiq
- time taken and number of Ruby GC calls
diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md
index 6ea0ac0d495..3bfcc9a289e 100644
--- a/doc/administration/monitoring/prometheus/gitlab_metrics.md
+++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md
@@ -17,10 +17,7 @@ GitLab monitors its own internal service metrics, and makes them available at th
`/-/metrics` endpoint. Unlike other [Prometheus] exporters, in order to access
it, the client IP needs to be [included in a whitelist][whitelist].
-Currently the embedded Prometheus server is not automatically configured to
-collect metrics from this endpoint. We recommend setting up another Prometheus
-server, because the embedded server configuration is overwritten once every
-[reconfigure of GitLab][reconfigure]. In the future this will not be required.
+For Omnibus and Chart installations, these metrics are automatically enabled and collected as of [GitLab 9.4](https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/1702). For source installations or earlier versions, these metrics will need to be enabled manually and collected by a Prometheus server.
## Unicorn Metrics available
diff --git a/doc/administration/operations/cleaning_up_redis_sessions.md b/doc/administration/operations/cleaning_up_redis_sessions.md
index 3a35aff8366..b45ca99fd80 100644
--- a/doc/administration/operations/cleaning_up_redis_sessions.md
+++ b/doc/administration/operations/cleaning_up_redis_sessions.md
@@ -20,7 +20,6 @@ configuration settings if you have used the advanced Redis
settings outlined in
[Configuration Files Documentation](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/README.md).
-
First we define a shell function with the proper Redis connection details.
```
diff --git a/doc/administration/operations/index.md b/doc/administration/operations/index.md
index a16fc7ae74f..32f36d68c50 100644
--- a/doc/administration/operations/index.md
+++ b/doc/administration/operations/index.md
@@ -3,20 +3,20 @@
Keep your GitLab instance up and running smoothly.
- [Clean up Redis sessions](cleaning_up_redis_sessions.md): Prior to GitLab 7.3,
-user sessions did not automatically expire from Redis. If
-you have been running a large GitLab server (thousands of users) since before
-GitLab 7.3 we recommend cleaning up stale sessions to compact the Redis
-database after you upgrade to GitLab 7.3.
+ user sessions did not automatically expire from Redis. If
+ you have been running a large GitLab server (thousands of users) since before
+ GitLab 7.3 we recommend cleaning up stale sessions to compact the Redis
+ database after you upgrade to GitLab 7.3.
- [Moving repositories](moving_repositories.md): Moving all repositories managed
-by GitLab to another file system or another server.
+ by GitLab to another file system or another server.
- [Sidekiq MemoryKiller](sidekiq_memory_killer.md): Configure Sidekiq MemoryKiller
-to restart Sidekiq.
+ to restart Sidekiq.
- [Unicorn](unicorn.md): Understand Unicorn and unicorn-worker-killer.
- Speed up SSH operations by [Authorizing SSH users via a fast,
-indexed lookup to the GitLab database](fast_ssh_key_lookup.md), and/or
-by [doing away with user SSH keys stored on GitLab entirely in favor
-of SSH certificates](ssh_certificates.md).
+ indexed lookup to the GitLab database](fast_ssh_key_lookup.md), and/or
+ by [doing away with user SSH keys stored on GitLab entirely in favor
+ of SSH certificates](ssh_certificates.md).
- [Filesystem Performance Benchmarking](filesystem_benchmarking.md): Filesystem
-performance can have a big impact on GitLab performance, especially for actions
-that read or write Git repositories. This information will help benchmark
-filesystem performance against known good and bad real-world systems.
+ performance can have a big impact on GitLab performance, especially for actions
+ that read or write Git repositories. This information will help benchmark
+ filesystem performance against known good and bad real-world systems.
diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md
index 10ae8c7dedf..279ad018aed 100644
--- a/doc/administration/pages/index.md
+++ b/doc/administration/pages/index.md
@@ -11,7 +11,7 @@ description: 'Learn how to administer GitLab Pages.'
> - This guide is for Omnibus GitLab installations. If you have installed
> GitLab from source, follow the [Pages source installation document](source.md).
> - To learn how to use GitLab Pages, read the [user documentation][pages-userguide].
-> - Does NOT support subgroups. See [this issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/30548) for more information and status.
+> - Support for subgroup project's websites was [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/30548) in GitLab 11.8.
This document describes how to set up the _latest_ GitLab Pages feature. Make
sure to read the [changelog](#changelog) if you are upgrading to a new GitLab
@@ -337,10 +337,10 @@ The default is 100MB.
You may want to run GitLab Pages daemon on a separate server in order to decrease the load on your main application server.
Follow the steps below to configure GitLab Pages in a separate server.
-1. Suppose you have the main GitLab application server named `app1`. Prepare
-new Linux server (let's call it `app2`), create NFS share there and configure access to
-this share from `app1`. Let's use the default GitLab Pages folder `/var/opt/gitlab/gitlab-rails/shared/pages`
-as the shared folder on `app2` and mount it to `/mnt/pages` on `app1`.
+1. Suppose you have the main GitLab application server named `app1`. Prepare
+ new Linux server (let's call it `app2`), create NFS share there and configure access to
+ this share from `app1`. Let's use the default GitLab Pages folder `/var/opt/gitlab/gitlab-rails/shared/pages`
+ as the shared folder on `app2` and mount it to `/mnt/pages` on `app1`.
1. On `app2` install GitLab omnibus and modify `/etc/gitlab/gitlab.rb` this way:
@@ -365,7 +365,7 @@ as the shared folder on `app2` and mount it to `/mnt/pages` on `app1`.
pages_external_url "http://<your-pages-domain>"
gitlab_rails['pages_path'] = "/mnt/pages"
```
-
+
1. Run `sudo gitlab-ctl reconfigure`.
## Backup
diff --git a/doc/administration/pages/source.md b/doc/administration/pages/source.md
index 9f2b4d9075a..60800d445b8 100644
--- a/doc/administration/pages/source.md
+++ b/doc/administration/pages/source.md
@@ -88,12 +88,13 @@ since that is needed in all configurations.
### Wildcard domains
->**Requirements:**
-> - [Wildcard DNS setup](#dns-configuration)
->
-> ---
->
-> URL scheme: `http://page.example.io`
+**Requirements:**
+
+- [Wildcard DNS setup](#dns-configuration)
+
+---
+
+URL scheme: `http://page.example.io`
This is the minimum setup that you can use Pages with. It is the base for all
other setups as described below. Nginx will proxy all requests to the daemon.
diff --git a/doc/administration/polling.md b/doc/administration/polling.md
index 35aaa20df2c..a1077614677 100644
--- a/doc/administration/polling.md
+++ b/doc/administration/polling.md
@@ -10,15 +10,15 @@ say that issue notes poll every 2 seconds, and issue titles poll every 5
seconds; these are _not_ the actual values.
- 1 is the default, and recommended for most installations. (Issue notes poll
-every 2 seconds, and issue titles poll every 5 seconds.)
+ every 2 seconds, and issue titles poll every 5 seconds.)
- 0 will disable UI polling completely. (On the next poll, clients will stop
-polling for updates.)
+ polling for updates.)
- A value greater than 1 will slow polling down. If you see issues with
-database load from lots of clients polling for updates, increasing the
-multiplier from 1 can be a good compromise, rather than disabling polling
-completely. (For example: If this is set to 2, then issue notes poll every 4
-seconds, and issue titles poll every 10 seconds.)
+ database load from lots of clients polling for updates, increasing the
+ multiplier from 1 can be a good compromise, rather than disabling polling
+ completely. (For example: If this is set to 2, then issue notes poll every 4
+ seconds, and issue titles poll every 10 seconds.)
- A value between 0 and 1 will make the UI poll more frequently (so updates
-will show in other sessions faster), but is **not recommended**. 1 should be
-fast enough. (For example, if this is set to 0.5, then issue notes poll every
-1 second, and issue titles poll every 2.5 seconds.)
+ will show in other sessions faster), but is **not recommended**. 1 should be
+ fast enough. (For example, if this is set to 0.5, then issue notes poll every
+ 1 second, and issue titles poll every 2.5 seconds.)
diff --git a/doc/administration/raketasks/maintenance.md b/doc/administration/raketasks/maintenance.md
index 0d863594fc7..b295b7d5dc4 100644
--- a/doc/administration/raketasks/maintenance.md
+++ b/doc/administration/raketasks/maintenance.md
@@ -60,6 +60,7 @@ Runs the following rake tasks:
It will check that each component was set up according to the installation guide and suggest fixes for issues found.
You may also have a look at our Troubleshooting Guides:
+
- [Troubleshooting Guide (GitLab)](http://docs.gitlab.com/ee/README.html#troubleshooting)
- [Troubleshooting Guide (Omnibus Gitlab)](http://docs.gitlab.com/omnibus/README.html#troubleshooting)
diff --git a/doc/administration/repository_storage_types.md b/doc/administration/repository_storage_types.md
index 51e1518d73f..4934aaf39f7 100644
--- a/doc/administration/repository_storage_types.md
+++ b/doc/administration/repository_storage_types.md
@@ -41,7 +41,6 @@ Registry, etc.
## Hashed Storage
-
Hashed Storage is the new storage behavior we rolled out with 10.0. Instead
of coupling project URL and the folder structure where the repository will be
stored on disk, we are coupling a hash, based on the project's ID. This makes
diff --git a/doc/administration/restart_gitlab.md b/doc/administration/restart_gitlab.md
index 7d245b3effd..cbc3fbd9473 100644
--- a/doc/administration/restart_gitlab.md
+++ b/doc/administration/restart_gitlab.md
@@ -137,7 +137,6 @@ If you are using other init systems, like systemd, you can check the
[GitLab Recipes][gl-recipes] repository for some unofficial services. These are
**not** officially supported so use them at your own risk.
-
[omnibus-dl]: https://about.gitlab.com/downloads/ "Download the Omnibus packages"
[install]: ../install/installation.md "Documentation to install GitLab from source"
[mailroom]: reply_by_email.md "Used for replying by email in GitLab issues and merge requests"
diff --git a/doc/api/README.md b/doc/api/README.md
index 3b43d195390..48d9ad8f30d 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -1,101 +1,140 @@
# GitLab API
-Automate GitLab via a simple and powerful API. All definitions can be found
-under [`/lib/api`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/lib/api).
+Automate GitLab via a simple and powerful API.
The main GitLab API is a [REST](https://en.wikipedia.org/wiki/Representational_state_transfer) API. Therefore, documentation in this section assumes knowledge of REST concepts.
-## API Resources
-
-The following API resources are available:
-
-- [Applications](applications.md)
-- [Avatar](avatar.md)
-- [Award emoji](award_emoji.md)
-- [Branches](branches.md)
-- [Broadcast messages](broadcast_messages.md)
-- [Code snippets](snippets.md)
-- [Commits](commits.md)
-- [Container Registry](container_registry.md)
-- [Custom attributes](custom_attributes.md)
-- [Deploy keys](deploy_keys.md), and [deploy keys for multiple projects](deploy_key_multiple_projects.md)
-- [Deployments](deployments.md)
-- [Discussions](discussions.md) (threaded comments)
-- [Environments](environments.md)
-- [Events](events.md)
-- [Feature flags](features.md)
-- Group-related resources, including:
- - [Groups](groups.md)
- - [Group access requests](access_requests.md)
- - [Group badges](group_badges.md)
- - [Group issue boards](group_boards.md)
- - [Group labels](group_labels.md)
- - [Group-level variables](group_level_variables.md)
- - [Group members](members.md)
- - [Group milestones](group_milestones.md)
-- [Issues](issues.md)
-- [Issue boards](boards.md)
-- [Jobs](jobs.md)
-- [Keys](keys.md)
-- [Labels](labels.md)
-- [Markdown](markdown.md)
-- [Merge requests](merge_requests.md)
-- [Namespaces](namespaces.md)
-- [Notes](notes.md) (comments)
-- [Notification settings](notification_settings.md)
-- [Pages domains](pages_domains.md)
-- [Pipelines](pipelines.md)
-- [Pipeline schedules](pipeline_schedules.md)
-- [Pipeline triggers](pipeline_triggers.md) and [triggering pipelines](../ci/triggers/README.md)
-- Project-related resources, including:
- - [Projects](projects.md) including setting Webhooks
- - [Project access requests](access_requests.md)
- - [Project badges](project_badges.md)
- - [Project clusters](project_clusters.md)
- - [Project-level variables](project_level_variables.md)
- - [Project import/export](project_import_export.md)
- - [Project import from GitHub](import.md)
- - [Project members](members.md)
- - [Project milestones](milestones.md)
- - [Project snippets](project_snippets.md)
- - [Project templates](project_templates.md) (see also [Templates API Resources](#templates-api-resources))
-- [Protected branches](protected_branches.md)
-- [Protected tags](protected_tags.md)
-- [Repositories](repositories.md)
-- [Repository files](repository_files.md)
-- [Repository submodules](repository_submodules.md)
-- [Resource label events](resource_label_events.md)
-- [Runners](runners.md)
-- [Search](search.md)
-- [Services](services.md)
-- [Settings](settings.md)
-- [Sidekiq metrics](sidekiq_metrics.md)
-- [System hooks](system_hooks.md)
-- [Tags](tags.md)
-- [Releases](releases/index.md)
-- Release Assets
- - [Links](releases/links.md)
-- [Todos](todos.md)
-- [Users](users.md)
-- [Validate CI configuration](lint.md) (linting)
-- [Version](version.md)
-- [Wikis](wikis.md)
-
-See also [V3 to V4](v3_to_v4.md).
-
-### Templates API Resources
+## API resources
+
+Available API resources can be grouped in the following contexts:
+
+- [Projects](#project-resources).
+- [Groups](#group-resources).
+- [Standalone](#standalone-resources).
+
+See also:
+
+- [V3 to V4](v3_to_v4.md).
+- Adding [deploy keys for multiple projects](deploy_key_multiple_projects.md).
+
+### Project resources
+
+The following API resources are available in the project context:
+
+| Resource | Available endpoints |
+|:------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| [Access requests](access_requests.md) | `/projects/:id/access_requests` (also available for groups) |
+| [Award emoji](award_emoji.md) | `/projects/:id/issues/.../award_emoji`, `/projects/:id/merge_requests/.../award_emoji`, `/projects/:id/snippets/.../award_emoji` |
+| [Branches](branches.md) | `/projects/:id/repository/branches/`, `/projects/:id/repository/merged_branches` |
+| [Commits](commits.md) | `/projects/:id/repository/commits`, `/projects/:id/statuses` |
+| [Container Registry](container_registry.md) | `/projects/:id/registry/repositories` |
+| [Custom attributes](custom_attributes.md) | `/projects/:id/custom_attributes` (also available for groups and users) |
+| [Deploy keys](deploy_keys.md) | `/projects/:id/deploy_keys` (also available standalone) |
+| [Deployments](deployments.md) | `/projects/:id/deployments` |
+| [Discussions](discussions.md) (threaded comments) | `/projects/:id/issues/.../discussions`, `/projects/:id/snippets/.../discussions`, `/projects/:id/merge_requests/.../discussions`, `/projects/:id/commits/.../discussions` |
+| [Environments](environments.md) | `/projects/:id/environments` |
+| [Events](events.md) | `/projects/:id/events` (also available for users and standalone) |
+| [Issues](issues.md) | `/projects/:id/issues` (also available for groups and standalone) |
+| [Issue boards](boards.md) | `/projects/:id/boards` |
+| [Jobs](jobs.md) | `/projects/:id/jobs`, `/projects/:id/pipelines/.../jobs` |
+| [Labels](labels.md) | `/projects/:id/labels` |
+| [Members](members.md) | `/projects/:id/members` (also available for groups) |
+| [Merge requests](merge_requests.md) | `/projects/:id/merge_requests` (also available for groups and standalone) |
+| [Notes](notes.md) (comments) | `/projects/:id/issues/.../notes`, `/projects/:id/snippets/.../notes`, `/projects/:id/merge_requests/.../notes` |
+| [Notification settings](notification_settings.md) | `/projects/:id/notification_settings` (also available for groups and standalone) |
+| [Pages domains](pages_domains.md) | `/projects/:id/pages` (also available standalone) |
+| [Pipelines](pipelines.md) | `/projects/:id/pipelines` |
+| [Pipeline schedules](pipeline_schedules.md) | `/projects/:id/pipeline_schedules` |
+| [Pipeline triggers](pipeline_triggers.md) | `/projects/:id/triggers` |
+| [Projects](projects.md) including setting Webhooks | `/projects`, `/projects/:id/hooks` (also available for users) |
+| [Project badges](project_badges.md) | `/projects/:id/badges` |
+| [Project clusters](project_clusters.md) | `/projects/:id/clusters` |
+| [Project-level variables](project_level_variables.md) | `/projects/:id/variables` |
+| [Project import/export](project_import_export.md) | `/projects/:id/export`, `/projects/import`, `/projects/:id/import` |
+| [Project milestones](milestones.md) | `/projects/:id/milestones` |
+| [Project snippets](project_snippets.md) | `/projects/:id/snippets` |
+| [Project templates](project_templates.md) | `/projects/:id/templates` |
+| [Protected branches](protected_branches.md) | `/projects/:id/protected_branches` |
+| [Protected tags](protected_tags.md) | `/projects/:id/protected_tags` |
+| [Releases](releases/index.md) | `/projects/:id/releases` |
+| [Release links](releases/links.md) | `/projects/:id/releases/.../assets/links` |
+| [Repositories](repositories.md) | `/projects/:id/repository` |
+| [Repository files](repository_files.md) | `/projects/:id/repository/files` |
+| [Repository submodules](repository_submodules.md) | `/projects/:id/repository/submodules` |
+| [Resource label events](resource_label_events.md) | `/projects/:id/issues/.../resource_label_events`, `/projects/:id/merge_requests/.../resource_label_events` |
+| [Runners](runners.md) | `/projects/:id/runners` (also available standalone) |
+| [Search](search.md) | `/projects/:id/search` (also available for groups and standalone) |
+| [Services](services.md) | `/projects/:id/services` |
+| [Tags](tags.md) | `/projects/:id/repository/tags` |
+| [Wikis](wikis.md) | `/projects/:id/wikis` |
+
+### Group resources
+
+The following API resources are available in the group context:
+
+| Resource | Available endpoints |
+|:--------------------------------------------------|:---------------------------------------------------------------------------------|
+| [Access requests](access_requests.md) | `/groups/:id/access_requests/` (also available for projects) |
+| [Custom attributes](custom_attributes.md) | `/groups/:id/custom_attributes` (also available for projects and users) |
+| [Groups](groups.md) | `/groups`, `/groups/.../subgroups` |
+| [Group badges](group_badges.md) | `/groups/:id/badges` |
+| [Group issue boards](group_boards.md) | `/groups/:id/boards` |
+| [Group labels](group_labels.md) | `/groups/:id/labels` |
+| [Group-level variables](group_level_variables.md) | `/groups/:id/variables` |
+| [Group milestones](group_milestones.md) | `/groups/:id/milestones` |
+| [Issues](issues.md) | `/groups/:id/issues` (also available for projects and standalone) |
+| [Members](members.md) | `/groups/:id/members` (also available for projects) |
+| [Merge requests](merge_requests.md) | `/groups/:id/merge_requests` (also available for projects and standalone) |
+| [Notification settings](notification_settings.md) | `/groups/:id/notification_settings` (also available for projects and standalone) |
+| [Search](search.md) | `/groups/:id/search` (also available for projects and standalone) |
+
+### Standalone resources
+
+The following API resources are available outside of project and group contexts (including `/users`):
+
+| Resource | Available endpoints |
+|:--------------------------------------------------|:------------------------------------------------------------------------|
+| [Applications](applications.md) | `/applications` |
+| [Avatar](avatar.md) | `/avatar` |
+| [Broadcast messages](broadcast_messages.md) | `/broadcast_messages` |
+| [Code snippets](snippets.md) | `/snippets` |
+| [Custom attributes](custom_attributes.md) | `/users/:id/custom_attributes` (also available for groups and projects) |
+| [Deploy keys](deploy_keys.md) | `/deploy_keys` (also available for projects) |
+| [Events](events.md) | `/events`, `/users/:id/events` (also available for projects) |
+| [Feature flags](features.md) | `/features` |
+| [Import repository from GitHub](import.md) | `/import/github` |
+| [Issues](issues.md) | `/issues` (also available for groups and projects) |
+| [Keys](keys.md) | `/keys` |
+| [Markdown](markdown.md) | `/markdown` |
+| [Merge requests](merge_requests.md) | `/merge_requests` (also available for groups and projects) |
+| [Namespaces](namespaces.md) | `/namespaces` |
+| [Notification settings](notification_settings.md) | `/notification_settings` (also available for groups and projects) |
+| [Pages domains](pages_domains.md) | `/pages/domains` (also available for projects) |
+| [Projects](projects.md) | `/users/:id/projects` (also available for projects) |
+| [Runners](runners.md) | `/runners` (also available for projects) |
+| [Search](search.md) | `/search` (also available for groups and projects) |
+| [Settings](settings.md) | `/application/settings` |
+| [Sidekiq metrics](sidekiq_metrics.md) | `/sidekiq` |
+| [Suggestions](suggestions.md) | `/suggestions` |
+| [System hooks](system_hooks.md) | `/hooks` |
+| [Todos](todos.md) | `/todos` |
+| [Users](users.md) | `/users` |
+| [Validate `.gitlab-ci.yml` file](lint.md) | `/lint` |
+| [Version](version.md) | `/version` |
+
+### Templates API resources
Endpoints are available for:
- [Dockerfile templates](templates/dockerfiles.md).
-- [gitignore templates](templates/gitignores.md).
+- [`.gitignore` templates](templates/gitignores.md).
- [GitLab CI YAML templates](templates/gitlab_ci_ymls.md).
- [Open source license templates](templates/licenses.md).
## Road to GraphQL
Going forward, we will start on moving to
-[GraphQL](http://graphql.org/learn/best-practices/) and deprecate the use of
+[GraphQL](graphql/index.md) and deprecate the use of
controller-specific endpoints. GraphQL has a number of benefits:
1. We avoid having to maintain two different APIs.
@@ -110,7 +149,7 @@ have been resolved to our satisfaction by the relicensing of the reference
implementations under MIT, and the use of the OWF license for the GraphQL
specification.
-## Compatibility Guidelines
+## Compatibility guidelines
The HTTP API is versioned using a single number, the current one being 4. This
number symbolizes the same as the major version number as described by
diff --git a/doc/api/award_emoji.md b/doc/api/award_emoji.md
index 92936a277ac..1d0e39e6bbf 100644
--- a/doc/api/award_emoji.md
+++ b/doc/api/award_emoji.md
@@ -31,7 +31,7 @@ Parameters:
| Attribute | Type | Required | Description |
|:---------------|:---------------|:---------|:-----------------------------------------------------------------------------|
| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
-| `awardable_id` | integer | yes | ID (`iid` for merge requests/issues, `id` for snippets) of an awardable. |
+| `issue_iid`/`merge_request_iid`/`snippet_id` | integer | yes | ID (`iid` for merge requests/issues, `id` for snippets) of an awardable. |
Example request:
@@ -93,7 +93,7 @@ Parameters:
| Attribute | Type | Required | Description |
|:---------------|:---------------|:---------|:-----------------------------------------------------------------------------|
| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
-| `awardable_id` | integer | yes | ID (`iid` for merge requests/issues, `id` for snippets) of an awardable. |
+| `issue_iid`/`merge_request_iid`/`snippet_id` | integer | yes | ID (`iid` for merge requests/issues, `id` for snippets) of an awardable. |
| `award_id` | integer | yes | ID of the award emoji. |
Example request:
@@ -138,7 +138,7 @@ Parameters:
| Attribute | Type | Required | Description |
|:---------------|:---------------|:---------|:-----------------------------------------------------------------------------|
| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
-| `awardable_id` | integer | yes | ID (`iid` for merge requests/issues, `id` for snippets) of an awardable. |
+| `issue_iid`/`merge_request_iid`/`snippet_id` | integer | yes | ID (`iid` for merge requests/issues, `id` for snippets) of an awardable. |
| `name` | string | yes | Name of the emoji without colons. |
```sh
@@ -184,7 +184,7 @@ Parameters:
| Attribute | Type | Required | Description |
|:---------------|:---------------|:---------|:-----------------------------------------------------------------------------|
| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
-| `awardable_id` | integer | yes | ID (`iid` for merge requests/issues, `id` for snippets) of an awardable. |
+| `issue_iid`/`merge_request_iid`/`snippet_id` | integer | yes | ID (`iid` for merge requests/issues, `id` for snippets) of an awardable. |
| `award_id` | integer | yes | ID of an award emoji. |
```sh
@@ -197,7 +197,8 @@ Comments (also known as notes) are a sub-resource of issues, merge requests, and
NOTE: **Note:**
The examples below describe working with award emoji on comments for an issue, but can be
-easily adapted for comments on a merge request.
+easily adapted for comments on a merge request or on a snippet. Therefore, you have to replace
+`issue_iid` either with `merge_request_iid` or with the `snippet_id`.
### List a comment's award emoji
diff --git a/doc/api/boards.md b/doc/api/boards.md
index 2a2622736c3..28c73db6b98 100644
--- a/doc/api/boards.md
+++ b/doc/api/boards.md
@@ -37,7 +37,7 @@ Example response:
"web_url": "http://example.com/diaspora/diaspora-project-site"
},
"milestone": {
- "id": 12
+ "id": 12,
"title": "10.0"
},
"lists" : [
@@ -95,7 +95,7 @@ Example response:
```json
{
"id": 1,
- "name:": "project issue board",
+ "name": "project issue board",
"project": {
"id": 5,
"name": "Diaspora Project Site",
@@ -106,7 +106,7 @@ Example response:
"web_url": "http://example.com/diaspora/diaspora-project-site"
},
"milestone": {
- "id": 12
+ "id": 12,
"title": "10.0"
},
"lists" : [
diff --git a/doc/api/commits.md b/doc/api/commits.md
index 14742f034e0..8d36ae7d559 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -18,7 +18,6 @@ GET /projects/:id/repository/commits
| `all` | boolean | no | Retrieve every commit from the repository |
| `with_stats` | boolean | no | Stats about each commit will be added to the response |
-
```bash
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/repository/commits"
```
@@ -81,7 +80,6 @@ POST /projects/:id/repository/commits
| `author_name` | string | no | Specify the commit author's name |
| `stats` | boolean | no | Include commit stats. Default is true |
-
| `actions[]` Attribute | Type | Required | Description |
| --------------------- | ---- | -------- | ----------- |
| `action` | string | yes | The action to perform, `create`, `delete`, `move`, `update`, `chmod`|
@@ -601,7 +599,6 @@ GET /projects/:id/repository/commits/:sha/merge_requests
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
| `sha` | string | yes | The commit SHA
-
```bash
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/repository/commits/af5b13261899fb2c0db30abdd0af8b07cb44fdc5/merge_requests"
```
@@ -656,6 +653,46 @@ Example response:
]
```
+## Get GPG signature of a commit
+
+Get the [GPG signature from a commit](../user/project/repository/gpg_signed_commits/index.md),
+if it is signed. For unsigned commits, it results in a 404 response.
+
+```
+GET /projects/:id/repository/commits/:sha/signature
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
+| `sha` | string | yes | The commit hash or name of a repository branch or tag |
+
+```bash
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/repository/commits/da738facbc19eb2fc2cef57c49be0e6038570352/signature"
+```
+
+Example response if commit is signed:
+
+```json
+{
+ "gpg_key_id": 1,
+ "gpg_key_primary_keyid": "8254AAB3FBD54AC9",
+ "gpg_key_user_name": "John Doe",
+ "gpg_key_user_email": "johndoe@example.com",
+ "verification_status": "verified",
+ "gpg_key_subkey_id": null
+}
+```
+
+Example response if commit is unsigned:
+```json
+{
+ "message": "404 GPG Signature Not Found"
+}
+```
+
[ce-6096]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6096 "Multi-file commit"
[ce-8047]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8047
[ce-15026]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15026
diff --git a/doc/api/container_registry.md b/doc/api/container_registry.md
index c77ed39e4dc..1f17af1f1e9 100644
--- a/doc/api/container_registry.md
+++ b/doc/api/container_registry.md
@@ -16,7 +16,6 @@ GET /projects/:id/registry/repositories
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
-
```bash
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/registry/repositories"
```
diff --git a/doc/api/discussions.md b/doc/api/discussions.md
index 79090ea5254..7d68d0ae744 100644
--- a/doc/api/discussions.md
+++ b/doc/api/discussions.md
@@ -641,7 +641,6 @@ Parameters:
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7?resolved=true
```
-
### Add note to existing merge request discussion
Adds a new note to the discussion.
diff --git a/doc/api/features.md b/doc/api/features.md
index 47f104e1f20..6ecd4ec14b9 100644
--- a/doc/api/features.md
+++ b/doc/api/features.md
@@ -60,10 +60,11 @@ POST /features/:name
| `value` | integer/string | yes | `true` or `false` to enable/disable, or an integer for percentage of time |
| `feature_group` | string | no | A Feature group name |
| `user` | string | no | A GitLab username |
+| `group` | string | no | A GitLab group's path, for example 'gitlab-org' |
| `project` | string | no | A projects path, for example 'gitlab-org/gitlab-ce' |
Note that you can enable or disable a feature for a `feature_group`, a `user`,
-and a `project` in a single API call.
+a `group`, and a `project` in a single API call.
```bash
curl --data "value=30" --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/features/new_library
diff --git a/doc/api/group_labels.md b/doc/api/group_labels.md
index d4715dec192..3d4b099b49e 100644
--- a/doc/api/group_labels.md
+++ b/doc/api/group_labels.md
@@ -28,6 +28,7 @@ Example response:
"id": 7,
"name": "bug",
"color": "#FF0000",
+ "text_color" : "#FFFFFF",
"description": null,
"open_issues_count": 0,
"closed_issues_count": 0,
@@ -38,6 +39,7 @@ Example response:
"id": 4,
"name": "feature",
"color": "#228B22",
+ "text_color" : "#FFFFFF",
"description": null,
"open_issues_count": 0,
"closed_issues_count": 0,
@@ -73,6 +75,7 @@ Example response:
"id": 9,
"name": "Feature Proposal",
"color": "#FFA500",
+ "text_color" : "#FFFFFF",
"description": "Describes new ideas",
"open_issues_count": 0,
"closed_issues_count": 0,
@@ -108,6 +111,7 @@ Example response:
"id": 9,
"name": "Feature Idea",
"color": "#FFA500",
+ "text_color" : "#FFFFFF",
"description": "Describes new ideas",
"open_issues_count": 0,
"closed_issues_count": 0,
@@ -158,6 +162,7 @@ Example response:
"id": 9,
"name": "Feature Idea",
"color": "#FFA500",
+ "text_color" : "#FFFFFF",
"description": "Describes new ideas",
"open_issues_count": 0,
"closed_issues_count": 0,
@@ -192,6 +197,7 @@ Example response:
"id": 9,
"name": "Feature Idea",
"color": "#FFA500",
+ "text_color" : "#FFFFFF",
"description": "Describes new ideas",
"open_issues_count": 0,
"closed_issues_count": 0,
diff --git a/doc/api/group_milestones.md b/doc/api/group_milestones.md
index 7be01ce9c6d..eb974267084 100644
--- a/doc/api/group_milestones.md
+++ b/doc/api/group_milestones.md
@@ -48,7 +48,6 @@ Example Response:
]
```
-
## Get single milestone
Gets a single group milestone.
diff --git a/doc/api/import.md b/doc/api/import.md
index 9f8e0d232c6..1deb26e8388 100644
--- a/doc/api/import.md
+++ b/doc/api/import.md
@@ -15,7 +15,6 @@ POST /import/github
| `new_name` | string | no | New repo name |
| `target_namespace` | string | yes | Namespace to import repo into |
-
```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "personal_access_token=abc123&repo_id=12345&target_namespace=root" https://gitlab.example.com/api/v4/import/github
```
@@ -30,4 +29,3 @@ Example response:
"full_name": "Administrator / my-repo"
}
```
-
diff --git a/doc/api/issues.md b/doc/api/issues.md
index ed3165d95df..cb5789e76b7 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -32,6 +32,7 @@ GET /issues?author_id=5
GET /issues?assignee_id=5
GET /issues?my_reaction_emoji=star
GET /issues?search=foo&in=title
+GET /issues?confidential=true
```
| Attribute | Type | Required | Description |
@@ -52,6 +53,7 @@ GET /issues?search=foo&in=title
| `created_before` | datetime | no | Return issues created on or before the given time |
| `updated_after` | datetime | no | Return issues updated on or after the given time |
| `updated_before` | datetime | no | Return issues updated on or before the given time |
+| `confidential ` | Boolean | no | Filter confidential or public issues. |
```bash
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/issues
@@ -110,6 +112,7 @@ Example response:
"labels" : [],
"upvotes": 4,
"downvotes": 0,
+ "merge_requests_count": 0,
"user_notes_count": 1,
"due_date": "2016-07-22",
"web_url": "http://example.com/example/example/issues/6",
@@ -147,6 +150,7 @@ GET /groups/:id/issues?search=issue+title+or+description
GET /groups/:id/issues?author_id=5
GET /groups/:id/issues?assignee_id=5
GET /groups/:id/issues?my_reaction_emoji=star
+GET /groups/:id/issues?confidential=true
```
| Attribute | Type | Required | Description |
@@ -167,7 +171,7 @@ GET /groups/:id/issues?my_reaction_emoji=star
| `created_before` | datetime | no | Return issues created on or before the given time |
| `updated_after` | datetime | no | Return issues updated on or after the given time |
| `updated_before` | datetime | no | Return issues updated on or before the given time |
-
+| `confidential ` | Boolean | no | Filter confidential or public issues. |
```bash
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/4/issues
@@ -220,6 +224,7 @@ Example response:
"labels" : [],
"upvotes": 4,
"downvotes": 0,
+ "merge_requests_count": 0,
"id" : 41,
"title" : "Ut commodi ullam eos dolores perferendis nihil sunt.",
"updated_at" : "2016-01-04T15:31:46.176Z",
@@ -263,6 +268,7 @@ GET /projects/:id/issues?search=issue+title+or+description
GET /projects/:id/issues?author_id=5
GET /projects/:id/issues?assignee_id=5
GET /projects/:id/issues?my_reaction_emoji=star
+GET /projects/:id/issues?confidential=true
```
| Attribute | Type | Required | Description |
@@ -283,6 +289,8 @@ GET /projects/:id/issues?my_reaction_emoji=star
| `created_before` | datetime | no | Return issues created on or before the given time |
| `updated_after` | datetime | no | Return issues updated on or after the given time |
| `updated_before` | datetime | no | Return issues updated on or before the given time |
+| `confidential ` | Boolean | no | Filter confidential or public issues. |
+
```bash
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/4/issues
@@ -335,6 +343,7 @@ Example response:
"labels" : [],
"upvotes": 4,
"downvotes": 0,
+ "merge_requests_count": 0,
"id" : 41,
"title" : "Ut commodi ullam eos dolores perferendis nihil sunt.",
"updated_at" : "2016-01-04T15:31:46.176Z",
@@ -431,6 +440,7 @@ Example response:
"labels" : [],
"upvotes": 4,
"downvotes": 0,
+ "merge_requests_count": 0,
"id" : 41,
"title" : "Ut commodi ullam eos dolores perferendis nihil sunt.",
"updated_at" : "2016-01-04T15:31:46.176Z",
@@ -506,6 +516,7 @@ Example response:
],
"upvotes": 4,
"downvotes": 0,
+ "merge_requests_count": 0,
"author" : {
"name" : "Alexandra Bashirian",
"avatar_url" : null,
@@ -568,7 +579,6 @@ PUT /projects/:id/issues/:issue_iid
| `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` |
| `discussion_locked` | boolean | no | Flag indicating if the issue's discussion is locked. If the discussion is locked only project members can add or edit comments. |
-
```bash
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/4/issues/85?state_event=close
```
@@ -606,6 +616,7 @@ Example response:
],
"upvotes": 4,
"downvotes": 0,
+ "merge_requests_count": 0,
"id" : 85,
"assignees" : [],
"assignee" : null,
@@ -692,6 +703,7 @@ Example response:
"labels": [],
"upvotes": 4,
"downvotes": 0,
+ "merge_requests_count": 0,
"milestone": null,
"assignees": [{
"name": "Miss Monserrate Beier",
@@ -776,6 +788,7 @@ Example response:
"labels": [],
"upvotes": 4,
"downvotes": 0,
+ "merge_requests_count": 0,
"milestone": null,
"assignees": [{
"name": "Miss Monserrate Beier",
@@ -822,7 +835,6 @@ Example response:
**Note**: `assignee` column is deprecated, now we show it as a single-sized array `assignees` to conform to the GitLab EE API.
-
**Note**: The `closed_by` attribute was [introduced in GitLab 10.6][ce-17042]. This value will only be present for issues which were closed after GitLab 10.6 and when the user account that closed the issue still exists.
## Unsubscribe from an issue
@@ -859,6 +871,7 @@ Example response:
"labels": [],
"upvotes": 4,
"downvotes": 0,
+ "merge_requests_count": 0,
"milestone": null,
"assignee": {
"name": "Edwardo Grady",
@@ -976,6 +989,7 @@ Example response:
"user_notes_count": 7,
"upvotes": 0,
"downvotes": 0,
+ "merge_requests_count": 0,
"due_date": null,
"web_url": "http://example.com/example/example/issues/110",
"confidential": false,
@@ -990,7 +1004,6 @@ Example response:
**Note**: `assignee` column is deprecated, now we show it as a single-sized array `assignees` to conform to the GitLab EE API.
-
**Note**: The `closed_by` attribute was [introduced in GitLab 10.6][ce-17042]. This value will only be present for issues which were closed after GitLab 10.6 and when the user account that closed the issue still exists.
## Set a time estimate for an issue
@@ -1288,7 +1301,6 @@ Example response:
]
```
-
## Participants on issues
```
@@ -1327,7 +1339,6 @@ Example response:
]
```
-
## Comments on issues
Comments are done via the [notes](notes.md) resource.
diff --git a/doc/api/labels.md b/doc/api/labels.md
index aec1a2c7592..9d10d383bf9 100644
--- a/doc/api/labels.md
+++ b/doc/api/labels.md
@@ -24,56 +24,66 @@ Example response:
"id" : 1,
"name" : "bug",
"color" : "#d9534f",
+ "text_color" : "#FFFFFF",
"description": "Bug reported by user",
"open_issues_count": 1,
"closed_issues_count": 0,
"open_merge_requests_count": 1,
"subscribed": false,
- "priority": 10
+ "priority": 10,
+ "is_project_label": true
},
{
"id" : 4,
"color" : "#d9534f",
+ "text_color" : "#FFFFFF",
"name" : "confirmed",
"description": "Confirmed issue",
"open_issues_count": 2,
"closed_issues_count": 5,
"open_merge_requests_count": 0,
"subscribed": false,
- "priority": null
+ "priority": null,
+ "is_project_label": true
},
{
"id" : 7,
"name" : "critical",
"color" : "#d9534f",
+ "text_color" : "#FFFFFF",
"description": "Critical issue. Need fix ASAP",
"open_issues_count": 1,
"closed_issues_count": 3,
"open_merge_requests_count": 1,
"subscribed": false,
- "priority": null
+ "priority": null,
+ "is_project_label": true
},
{
"id" : 8,
"name" : "documentation",
"color" : "#f0ad4e",
+ "text_color" : "#FFFFFF",
"description": "Issue about documentation",
"open_issues_count": 1,
"closed_issues_count": 0,
"open_merge_requests_count": 2,
"subscribed": false,
- "priority": null
+ "priority": null,
+ "is_project_label": false
},
{
"id" : 9,
"color" : "#5cb85c",
+ "text_color" : "#FFFFFF",
"name" : "enhancement",
"description": "Enhancement proposal",
"open_issues_count": 1,
"closed_issues_count": 0,
"open_merge_requests_count": 1,
"subscribed": true,
- "priority": null
+ "priority": null,
+ "is_project_label": true
}
]
```
@@ -105,12 +115,14 @@ Example response:
"id" : 10,
"name" : "feature",
"color" : "#5843AD",
+ "text_color" : "#FFFFFF",
"description":null,
"open_issues_count": 0,
"closed_issues_count": 0,
"open_merge_requests_count": 0,
"subscribed": false,
- "priority": null
+ "priority": null,
+ "is_project_label": true
}
```
@@ -149,7 +161,6 @@ PUT /projects/:id/labels
| `description` | string | no | The new description of the label |
| `priority` | integer | no | The new priority of the label. Must be greater or equal than zero or `null` to remove the priority. |
-
```bash
curl --request PUT --data "name=documentation&new_name=docs&color=#8E44AD&description=Documentation" --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/labels"
```
@@ -161,12 +172,14 @@ Example response:
"id" : 8,
"name" : "docs",
"color" : "#8E44AD",
+ "text_color" : "#FFFFFF",
"description": "Documentation",
"open_issues_count": 1,
"closed_issues_count": 0,
"open_merge_requests_count": 2,
"subscribed": false,
- "priority": null
+ "priority": null,
+ "is_project_label": true
}
```
@@ -196,12 +209,14 @@ Example response:
"id" : 1,
"name" : "bug",
"color" : "#d9534f",
+ "text_color" : "#FFFFFF",
"description": "Bug reported by user",
"open_issues_count": 1,
"closed_issues_count": 0,
"open_merge_requests_count": 1,
"subscribed": true,
- "priority": null
+ "priority": null,
+ "is_project_label": true
}
```
diff --git a/doc/api/lint.md b/doc/api/lint.md
index a0307f7081d..71c09d35b8c 100644
--- a/doc/api/lint.md
+++ b/doc/api/lint.md
@@ -2,7 +2,7 @@
> [Introduced][ce-5953] in GitLab 8.12.
-Checks if your .gitlab-ci.yml file is valid.
+Checks if your `.gitlab-ci.yml` file is valid.
```
POST /lint
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index d58cd45538d..ed4b6281acc 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -160,7 +160,6 @@ will be the same. In the case of a merge request from a fork,
`target_project_id` and `project_id` will be the same and
`source_project_id` will be the fork project's ID.
-
Parameters:
| Attribute | Type | Required | Description |
@@ -435,6 +434,9 @@ Parameters:
"avatar_url": null,
"web_url" : "https://gitlab.example.com/admin"
},
+ "user" : {
+ "can_merge" : false
+ }
"assignee": {
"id": 1,
"name": "Administrator",
@@ -528,7 +530,6 @@ Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `merge_request_iid` (required) - The internal ID of the merge request
-
```json
[
{
@@ -563,7 +564,6 @@ Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `merge_request_iid` (required) - The internal ID of the merge request
-
```json
[
{
@@ -1101,6 +1101,40 @@ Parameters:
}
```
+## Merge to default merge ref path
+
+Merge the changes between the merge request source and target branches into `refs/merge-requests/:iid/merge`
+ref, of the target project repository. This ref will have the state the target branch would have if
+a regular merge action was taken.
+
+This is not a regular merge action given it doesn't change the merge request state in any manner.
+
+This ref (`refs/merge-requests/:iid/merge`) is **always** overwritten when submitting
+requests to this API, so none of its state is kept or used in the process.
+
+If the merge request has conflicts, is empty or already merged,
+you'll get a `400` and a descriptive error message. If you don't have permissions to do so,
+you'll get a `403`.
+
+It returns the HEAD commit of `refs/merge-requests/:iid/merge` in the response body in
+case of `200`.
+
+```
+PUT /projects/:id/merge_requests/:merge_request_iid/merge_to_ref
+```
+
+Parameters:
+
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
+- `merge_request_iid` (required) - Internal ID of MR
+- `merge_commit_message` (optional) - Custom merge commit message
+
+```json
+{
+ "commit_id": "854a3a7a17acbcc0bbbea170986df1eb60435f34"
+}
+```
+
## Cancel Merge When Pipeline Succeeds
If you don't have permissions to accept this merge request - you'll get a `401`
diff --git a/doc/api/milestones.md b/doc/api/milestones.md
index fa8f8a0bcf0..897184d51af 100644
--- a/doc/api/milestones.md
+++ b/doc/api/milestones.md
@@ -130,3 +130,18 @@ Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `milestone_id` (required) - The ID of a project milestone
+
+## Promote project milestone to a group milestone
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/53861) in GitLab 11.9
+
+Only for users with developer access to the group.
+
+```
+POST /projects/:id/milestones/:milestone_id/promote
+```
+
+Parameters:
+
+- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
+- `milestone_id` (required) - The ID of a project milestone
diff --git a/doc/api/oauth2.md b/doc/api/oauth2.md
index 6e156a14b25..dfe62554852 100644
--- a/doc/api/oauth2.md
+++ b/doc/api/oauth2.md
@@ -206,4 +206,3 @@ or you can put the token to the Authorization header:
```
curl --header "Authorization: Bearer OAUTH-TOKEN" https://gitlab.example.com/api/v4/user
```
-
diff --git a/doc/api/pages_domains.md b/doc/api/pages_domains.md
index 4c41350dcdb..70fbe24099f 100644
--- a/doc/api/pages_domains.md
+++ b/doc/api/pages_domains.md
@@ -8,7 +8,7 @@ The GitLab Pages feature must be enabled to use these endpoints. Find out more a
Get a list of all pages domains. The user must have admin permissions.
-```http
+```text
GET /pages/domains
```
@@ -34,7 +34,7 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/ap
Get a list of project pages domains. The user must have permissions to view pages domains.
-```http
+```text
GET /projects/:id/pages/domains
```
@@ -69,7 +69,7 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/ap
Get a single project pages domain. The user must have permissions to view pages domains.
-```http
+```text
GET /projects/:id/pages/domains/:domain
```
@@ -110,7 +110,7 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/ap
Creates a new pages domain. The user must have permissions to create new pages domains.
-```http
+```text
POST /projects/:id/pages/domains
```
@@ -146,7 +146,7 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --form "domain
Updates an existing project pages domain. The user must have permissions to change an existing pages domains.
-```http
+```text
PUT /projects/:id/pages/domains/:domain
```
@@ -182,7 +182,7 @@ curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" --form "certifi
Deletes an existing project pages domain.
-```http
+```text
DELETE /projects/:id/pages/domains/:domain
```
diff --git a/doc/api/project_import_export.md b/doc/api/project_import_export.md
index fc91c5741da..5155e996158 100644
--- a/doc/api/project_import_export.md
+++ b/doc/api/project_import_export.md
@@ -1,8 +1,8 @@
# Project import/export API
-[Introduced][ce-41899] in GitLab 10.6
+> [Introduced][ce-41899] in GitLab 10.6.
-[See also the project import/export documentation](../user/project/settings/import_export.md)
+See also the [project import/export documentation](../user/project/settings/import_export.md).
## Schedule an export
@@ -16,7 +16,7 @@ data file uploads to the final server.
If the `upload` params is present, `upload[url]` param is required.
(**Note:** This feature was introduced in GitLab 10.7)
-```http
+```text
POST /projects/:id/export
```
@@ -28,8 +28,7 @@ POST /projects/:id/export
| `upload[url]` | string | yes | The URL to upload the project |
| `upload[http_method]` | string | no | The HTTP method to upload the exported project. Only `PUT` and `POST` methods allowed. Default is `PUT` |
-
-```console
+```sh
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/export \
--data "upload[http_method]=PUT" \
--data-urlencode "upload[url]=https://example-bucket.s3.eu-west-3.amazonaws.com/backup?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIMBJHN2O62W8IELQ%2F20180312%2Feu-west-3%2Fs3%2Faws4_request&X-Amz-Date=20180312T110328Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host&X-Amz-Signature=8413facb20ff33a49a147a0b4abcff4c8487cc33ee1f7e450c46e8f695569dbd"
@@ -45,7 +44,7 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab
Get the status of export.
-```http
+```text
GET /projects/:id/export
```
@@ -53,7 +52,7 @@ GET /projects/:id/export
| --------- | -------------- | -------- | ---------------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-```console
+```sh
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/export
```
@@ -86,7 +85,7 @@ to a web server, etc.
Download the finished export.
-```http
+```text
GET /projects/:id/export/download
```
@@ -94,18 +93,18 @@ GET /projects/:id/export/download
| --------- | -------------- | -------- | ---------------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-```console
+```sh
curl --header "PRIVATE-TOKEN: <your_access_token>" --remote-header-name --remote-name https://gitlab.example.com/api/v4/projects/5/export/download
```
-```console
+```sh
ls *export.tar.gz
2017-12-05_22-11-148_namespace_project_export.tar.gz
```
## Import a file
-```http
+```text
POST /projects/import
```
@@ -124,7 +123,7 @@ cURL to post data using the header `Content-Type: multipart/form-data`.
The `file=` parameter must point to a file on your file system and be preceded
by `@`. For example:
-```console
+```sh
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --form "path=api-project" --form "file=@/path/to/file" https://gitlab.example.com/api/v4/projects/import
```
@@ -168,7 +167,7 @@ requests.post(url, headers=headers, data=data, files=files)
Get the status of an import.
-```http
+```text
GET /projects/:id/import
```
@@ -176,7 +175,7 @@ GET /projects/:id/import
| --------- | -------------- | -------- | ---------------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-```console
+```sh
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/import
```
diff --git a/doc/api/project_snippets.md b/doc/api/project_snippets.md
index 8f4640fcbd6..f02674adfe2 100644
--- a/doc/api/project_snippets.md
+++ b/doc/api/project_snippets.md
@@ -124,7 +124,6 @@ Parameters:
> **Notes:**
> [Introduced][ce-29508] in GitLab 9.4.
-
Available only for admins.
```
diff --git a/doc/api/project_statistics.md b/doc/api/project_statistics.md
new file mode 100644
index 00000000000..34d73abfcbf
--- /dev/null
+++ b/doc/api/project_statistics.md
@@ -0,0 +1,49 @@
+# Project statistics API
+
+Every API call to [project](../user/project/index.md) statistics must be authenticated.
+
+## Get the statistics of the last 30 days
+
+Retrieving the statistics requires write access to the repository.
+Currently only HTTP fetches statistics are returned.
+Fetches statistics includes both clones and pulls count and are HTTP only, SSH fetches are not included.
+
+```
+GET /projects/:id/statistics
+```
+
+| Attribute | Type | Required | Description |
+| ---------- | ------ | -------- | ----------- |
+| `id ` | integer / string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
+
+Example response:
+
+```json
+{
+ "fetches": {
+ "total": 50,
+ "days": [
+ {
+ "count": 10,
+ "date": "2018-01-10"
+ },
+ {
+ "count": 10,
+ "date": "2018-01-09"
+ },
+ {
+ "count": 10,
+ "date": "2018-01-08"
+ },
+ {
+ "count": 10,
+ "date": "2018-01-07"
+ },
+ {
+ "count": 10,
+ "date": "2018-01-06"
+ }
+ ]
+ }
+}
+```
diff --git a/doc/api/project_templates.md b/doc/api/project_templates.md
index ef98205cd68..3b5b12c8da3 100644
--- a/doc/api/project_templates.md
+++ b/doc/api/project_templates.md
@@ -101,7 +101,6 @@ GET /projects/:id/templates/:type/:key
Example response (Dockerfile):
-
```json
{
"name": "Binary",
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 3c0c956ddc2..0a950352ecf 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -771,6 +771,8 @@ POST /projects/:id/fork
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `namespace` | integer/string | yes | The ID or path of the namespace that the project will be forked to |
+| `path` | string | no | The path that will be assigned to the resultant project after forking |
+| `name` | string | no | The name that will be assigned to the resultant project after forking |
## List Forks of a project
diff --git a/doc/api/protected_branches.md b/doc/api/protected_branches.md
index fa04680d406..a261bb75be5 100644
--- a/doc/api/protected_branches.md
+++ b/doc/api/protected_branches.md
@@ -5,6 +5,7 @@
**Valid access levels**
The access levels are defined in the `ProtectedRefAccess.allowed_access_levels` method. Currently, these levels are recognized:
+
```
0 => No access
30 => Developer access
diff --git a/doc/api/releases/index.md b/doc/api/releases/index.md
index 943109a3ea9..e7f79a0d359 100644
--- a/doc/api/releases/index.md
+++ b/doc/api/releases/index.md
@@ -2,7 +2,7 @@
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/41766) in GitLab 11.7.
> - Using this API you can manipulate GitLab's [Release](../../user/project/releases/index.md) entries.
-> - For manipulating links as a release asset, see [Release Links API](links.md)
+> - For manipulating links as a release asset, see [Release Links API](links.md).
## List Releases
@@ -14,7 +14,7 @@ GET /projects/:id/releases
| Attribute | Type | Required | Description |
| ------------- | -------------- | -------- | --------------------------------------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). |
Example request:
@@ -160,7 +160,7 @@ GET /projects/:id/releases/:tag_name
| Attribute | Type | Required | Description |
| ------------- | -------------- | -------- | --------------------------------------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). |
| `tag_name` | string | yes | The tag where the release will be created from. |
Example request:
@@ -239,10 +239,10 @@ POST /projects/:id/releases
| Attribute | Type | Required | Description |
| ------------- | -------------- | -------- | --------------------------------------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). |
| `name` | string | yes | The release name. |
| `tag_name` | string | yes | The tag where the release will be created from. |
-| `description` | string | yes | The description of the release. You can use [markdown](../user/markdown.md). |
+| `description` | string | yes | The description of the release. You can use [markdown](../../user/markdown.md). |
| `ref` | string | no | If `tag_name` doesn't exist, the release will be created from `ref`. It can be a commit SHA, another tag name, or a branch name. |
| `assets:links`| array of hash | no | An array of assets links. |
| `assets:links:name`| string | no (if `assets:links` specified, it's required) | The name of the link. |
@@ -331,10 +331,10 @@ PUT /projects/:id/releases/:tag_name
| Attribute | Type | Required | Description |
| ------------- | -------------- | -------- | --------------------------------------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). |
| `tag_name` | string | yes | The tag where the release will be created from. |
| `name` | string | no | The release name. |
-| `description` | string | no | The description of the release. You can use [markdown](../user/markdown.md). |
+| `description` | string | no | The description of the release. You can use [markdown](../../user/markdown.md). |
Example request:
@@ -412,7 +412,7 @@ DELETE /projects/:id/releases/:tag_name
| Attribute | Type | Required | Description |
| ------------- | -------------- | -------- | --------------------------------------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). |
| `tag_name` | string | yes | The tag where the release will be created from. |
Example request:
diff --git a/doc/api/releases/links.md b/doc/api/releases/links.md
index ae99f3bd8b6..fd7b9d6e6e2 100644
--- a/doc/api/releases/links.md
+++ b/doc/api/releases/links.md
@@ -3,6 +3,7 @@
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/41766) in GitLab 11.7.
Using this API you can manipulate GitLab's [Release](../../user/project/releases/index.md) links. For manipulating other Release assets, see [Release API](index.md).
+GitLab supports links links to `http`, `https`, and `ftp` assets.
## Get links
diff --git a/doc/api/repository_files.md b/doc/api/repository_files.md
index 8c1d982f394..6fcc06ea8cd 100644
--- a/doc/api/repository_files.md
+++ b/doc/api/repository_files.md
@@ -171,6 +171,7 @@ Parameters:
If the commit fails for any reason we return a 400 error with a non-specific
error message. Possible causes for a failed commit include:
+
- the `file_path` contained `/../` (attempted directory traversal);
- the new file contents were identical to the current file contents, i.e. the
user tried to make an empty commit;
diff --git a/doc/api/runners.md b/doc/api/runners.md
index 4aa0e4543e5..7d7215e6b80 100644
--- a/doc/api/runners.md
+++ b/doc/api/runners.md
@@ -13,13 +13,15 @@ GET /runners
GET /runners?scope=active
GET /runners?type=project_type
GET /runners?status=active
+GET /runners?tag_list=tag1,tag2
```
-| Attribute | Type | Required | Description |
-|-----------|---------|----------|---------------------|
-| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of specific runners to show, one of: `active`, `paused`, `online`, `offline`; showing all runners if none provided |
-| `type` | string | no | The type of runners to show, one of: `instance_type`, `group_type`, `project_type` |
-| `status` | string | no | The status of runners to show, one of: `active`, `paused`, `online`, `offline` |
+| Attribute | Type | Required | Description |
+|-------------|----------------|----------|---------------------|
+| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of specific runners to show, one of: `active`, `paused`, `online`, `offline`; showing all runners if none provided |
+| `type` | string | no | The type of runners to show, one of: `instance_type`, `group_type`, `project_type` |
+| `status` | string | no | The status of runners to show, one of: `active`, `paused`, `online`, `offline` |
+| `tag_list` | Array[String] | no | List of of the runner's tags |
```
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/runners"
@@ -62,13 +64,15 @@ GET /runners/all
GET /runners/all?scope=online
GET /runners/all?type=project_type
GET /runners/all?status=active
+GET /runners/all?tag_list=tag1,tag2
```
-| Attribute | Type | Required | Description |
-|-----------|---------|----------|---------------------|
-| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of runners to show, one of: `specific`, `shared`, `active`, `paused`, `online`, `offline`; showing all runners if none provided |
-| `type` | string | no | The type of runners to show, one of: `instance_type`, `group_type`, `project_type` |
-| `status` | string | no | The status of runners to show, one of: `active`, `paused`, `online`, `offline` |
+| Attribute | Type | Required | Description |
+|-------------|----------------|----------|---------------------|
+| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of runners to show, one of: `specific`, `shared`, `active`, `paused`, `online`, `offline`; showing all runners if none provided |
+| `type` | string | no | The type of runners to show, one of: `instance_type`, `group_type`, `project_type` |
+| `status` | string | no | The status of runners to show, one of: `active`, `paused`, `online`, `offline` |
+| `tag_list` | Array[String] | no | List of of the runner's tags |
```
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/runners/all"
@@ -347,14 +351,16 @@ GET /projects/:id/runners
GET /projects/:id/runners?scope=active
GET /projects/:id/runners?type=project_type
GET /projects/:id/runners?status=active
+GET /projects/:id/runners?tag_list=tag1,tag2
```
-| Attribute | Type | Required | Description |
-|-----------|----------------|----------|---------------------|
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of specific runners to show, one of: `active`, `paused`, `online`, `offline`; showing all runners if none provided |
-| `type` | string | no | The type of runners to show, one of: `instance_type`, `group_type`, `project_type` |
-| `status` | string | no | The status of runners to show, one of: `active`, `paused`, `online`, `offline` |
+| Attribute | Type | Required | Description |
+|------------|----------------|----------|---------------------|
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of specific runners to show, one of: `active`, `paused`, `online`, `offline`; showing all runners if none provided |
+| `type` | string | no | The type of runners to show, one of: `instance_type`, `group_type`, `project_type` |
+| `status` | string | no | The status of runners to show, one of: `active`, `paused`, `online`, `offline` |
+| `tag_list` | Array[String] | no | List of of the runner's tags |
```
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/9/runners"
@@ -478,7 +484,7 @@ Example response:
## Delete a registered Runner
-Deletes a registed Runner.
+Deletes a registered Runner.
```
DELETE /runners
diff --git a/doc/api/search.md b/doc/api/search.md
index aaaed7d9956..aa601648b2c 100644
--- a/doc/api/search.md
+++ b/doc/api/search.md
@@ -281,7 +281,6 @@ Example response:
]
```
-
## Group Search API
Search within the specified group.
@@ -520,7 +519,6 @@ Search the expression within the specified scope. Currently these scopes are sup
The response depends on the requested scope.
-
### Scope: issues
```bash
diff --git a/doc/api/services.md b/doc/api/services.md
index 2a8ce39e570..c44f5cc5781 100644
--- a/doc/api/services.md
+++ b/doc/api/services.md
@@ -412,7 +412,7 @@ Google GSuite team collaboration tool.
Set Hangouts Chat service for a project.
```
-PUT /projects/:id/services/hangouts_chat
+PUT /projects/:id/services/hangouts-chat
```
>**Note:** Specific event parameters (e.g. `push_events` flag) were [introduced in v10.4][11435]
@@ -438,7 +438,7 @@ Parameters:
Delete Hangouts Chat service for a project.
```
-DELETE /projects/:id/services/hangouts_chat
+DELETE /projects/:id/services/hangouts-chat
```
### Get Hangouts Chat service settings
@@ -446,7 +446,7 @@ DELETE /projects/:id/services/hangouts_chat
Get Hangouts Chat service settings for a project.
```
-GET /projects/:id/services/hangouts_chat
+GET /projects/:id/services/hangouts-chat
```
## Irker (IRC gateway)
@@ -522,6 +522,7 @@ Parameters:
| `project_key` | string | yes | The short identifier for your JIRA project, all uppercase, e.g., `PROJ`. |
| `username` | string | yes | The username of the user created to be used with GitLab/JIRA. |
| `password` | string | yes | The password of the user created to be used with GitLab/JIRA. |
+| `active` | boolean | no | Activates or deactivates the service. Defaults to false (deactivated). |
| `jira_issue_transition_id` | integer | no | The ID of a transition that moves issues to a closed state. You can find this number under the JIRA workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot][trans]). By default, this ID is set to `2`. |
### Delete JIRA service
@@ -1101,3 +1102,39 @@ GET /projects/:id/services/mock-ci
```
[11435]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11435
+
+## YouTrack
+
+YouTrack issue tracker
+
+### Create/Edit YouTrack service
+
+Set YouTrack service for a project.
+
+```
+PUT /projects/:id/services/youtrack
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `issues_url` | string | true | Issue url |
+| `project_url` | string | true | Project url |
+| `description` | string | false | Description |
+
+### Delete YouTrack Service
+
+Delete YouTrack service for a project.
+
+```
+DELETE /projects/:id/services/youtrack
+```
+
+### Get YouTrack Service Settings
+
+Get YouTrack service settings for a project.
+
+```
+GET /projects/:id/services/youtrack
+```
diff --git a/doc/api/sidekiq_metrics.md b/doc/api/sidekiq_metrics.md
index 95dcf2d5277..5f2202fa51d 100644
--- a/doc/api/sidekiq_metrics.md
+++ b/doc/api/sidekiq_metrics.md
@@ -149,4 +149,3 @@ Example response:
}
}
```
-
diff --git a/doc/api/tags.md b/doc/api/tags.md
index 23dbf2d9ff7..3177fec618f 100644
--- a/doc/api/tags.md
+++ b/doc/api/tags.md
@@ -163,7 +163,6 @@ Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `tag_name` (required) - The name of a tag
-
## Create a new release
Add release notes to the existing git tag. If there
diff --git a/doc/api/templates/gitignores.md b/doc/api/templates/gitignores.md
index 3804855129c..bf6a914e120 100644
--- a/doc/api/templates/gitignores.md
+++ b/doc/api/templates/gitignores.md
@@ -1,8 +1,8 @@
-# Gitignores API
+# `.gitignore` API
-## List gitignore templates
+## List `.gitignore` templates
-Get all gitignore templates.
+Get all `.gitignore` templates.
```
GET /templates/gitignores
@@ -99,9 +99,9 @@ Example response:
]
```
-## Single gitignore template
+## Single `.gitignore` template
-Get a single gitignore template.
+Get a single `.gitignore` template.
```
GET /templates/gitignores/:key
@@ -109,7 +109,7 @@ GET /templates/gitignores/:key
| Attribute | Type | Required | Description |
| ---------- | ------ | -------- | ----------- |
-| `key` | string | yes | The key of the gitignore template |
+| `key` | string | yes | The key of the `.gitignore` template |
```bash
curl https://gitlab.example.com/api/v4/templates/gitignores/Ruby
diff --git a/doc/api/users.md b/doc/api/users.md
index fd8778abb17..b0977810120 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -1043,7 +1043,6 @@ Will return `201 OK` on success, `404 User Not Found` is user cannot be found or
Please refer to the [Events API documentation](events.md#get-user-contribution-events)
-
## Get all impersonation tokens of a user
> Requires admin permissions.
diff --git a/doc/api/wikis.md b/doc/api/wikis.md
index 436d06cfd3a..12f048ac09b 100644
--- a/doc/api/wikis.md
+++ b/doc/api/wikis.md
@@ -119,7 +119,6 @@ PUT /projects/:id/wikis/:slug
| `format` | string | no | The format of the wiki page. Available formats are: `markdown` (default), `rdoc`, and `asciidoc` |
| `slug` | string | yes | The slug (a unique string) of the wiki page |
-
```bash
curl --request PUT --data "format=rdoc&content=documentation&title=Docs" --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/wikis/foo"
```
diff --git a/doc/articles/openshift_and_gitlab/index.md b/doc/articles/openshift_and_gitlab/index.md
index 76fdb2eb00a..822d012aa3d 100644
--- a/doc/articles/openshift_and_gitlab/index.md
+++ b/doc/articles/openshift_and_gitlab/index.md
@@ -1,4 +1,3 @@
---
redirect_to: '../../install/openshift_and_gitlab/index.html'
---
-
diff --git a/doc/ci/README.md b/doc/ci/README.md
index 4e066a0df97..4c3c9920b93 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -5,131 +5,194 @@ description: "Learn how to use GitLab CI/CD, the GitLab built-in Continuous Inte
# GitLab Continuous Integration (GitLab CI/CD)
-![Pipeline graph](img/cicd_pipeline_infograph.png)
+GitLab provides tools for continuously integrating and delivering code.
+
+Within the [entire DevOps lifecycle](../README.md#the-entire-devops-lifecycle), GitLab CI/CD spans
+the [Verify (CI)](../README.md#verify) and [Release (CD)](../README.md#release) stages.
-The benefits of Continuous Integration are huge when automation plays an
-integral part of your workflow. GitLab comes with built-in Continuous
-Integration, Continuous Deployment, and Continuous Delivery support
-to build, test, and deploy your application.
+## Overview
-Here's some info we've gathered to get you started.
+CI/CD is a vast area, so GitLab provides documentation for all levels of expertise. Consult the following table to find the right documentation for you:
-## Getting started
+| Level of expertise | Resource |
+|:------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------|
+| New to the concepts of CI and CD | For a high-level overview, see the [GitLab Continuous Integration & Delivery](https://about.gitlab.com/product/continuous-integration/) product page. |
+| Familiar with the purpose of CI/CD | Delve into GitLab CI/CD by continuing down the page, starting with our [introduction](#introduction). |
+| Familiar with GitLab CI/CD concepts | After getting familiar with GitLab CI/CD, let us walk you through a simple example in our [quick start guide](quick_start/README.md). |
+| A GitLab CI/CD expert | Jump straight to our [`.gitlab.yml`](yaml/README.md) reference. |
-The first steps towards your GitLab CI/CD journey.
+## Introduction
-- [Getting started with GitLab CI/CD](quick_start/README.md): understand how GitLab CI/CD works.
-- [GitLab CI/CD configuration file: `.gitlab-ci.yml`](yaml/README.md) - Learn all about the ins and outs of `.gitlab-ci.yml`.
-- [Pipelines and jobs](pipelines.md): configure your GitLab CI/CD pipelines to build, test, and deploy your application.
-- Runners: The [GitLab Runner](https://docs.gitlab.com/runner/) is responsible by running the jobs in your CI/CD pipeline. On GitLab.com, Shared Runners are enabled by default, so
-you don't need to set up anything to start to use them with GitLab CI/CD.
+The following introduces the process of continuous integration (CI) and continuous delivery (CD):
-### Introduction to GitLab CI/CD
+![Pipeline graph](img/cicd_pipeline_infograph.png)
-- Article (2016-08-05): [Continuous Integration, Delivery, and Deployment with GitLab - Intro to CI/CD](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/)
-- Article (2015-12-14): [Getting started with GitLab and GitLab CI - Intro to CI](https://about.gitlab.com/2015/12/14/getting-started-with-gitlab-and-gitlab-ci/)
-- Article (2017-07-13): [Making CI Easier with GitLab](https://about.gitlab.com/2017/07/13/making-ci-easier-with-gitlab/)
-- Article (2017-05-22): [Fast and Natural Continuous Integration with GitLab CI](https://about.gitlab.com/2017/05/22/fast-and-natural-continuous-integration-with-gitlab-ci/)
-- **Videos:**
- - Demo (Streamed live on Jul 17, 2017): [GitLab CI/CD Deep Dive](https://youtu.be/pBe4t1CD8Fc?t=195)
- - Demo (March, 2017): [How to get started using CI/CD with GitLab](https://about.gitlab.com/2017/03/13/ci-cd-demo/)
- - Webcast (April, 2016): [Getting started with CI in GitLab](https://about.gitlab.com/2016/04/20/webcast-recording-and-slides-introduction-to-ci-in-gitlab/)
-- **Third-party videos:**
- - [Intégration continue avec GitLab (September, 2016)](https://www.youtube.com/watch?v=URcMBXjIr24&t=13s)
- - [GitLab CI for Minecraft Plugins (July, 2016)](https://www.youtube.com/watch?v=Z4pcI9F8yf8)
+In this illustration:
-### Why GitLab CI/CD?
+- New code is combined with existing code through a commit to a project's [repository](../user/project/repository/index.md).
+- The newly combined code is sent to a CI [pipeline](pipelines.md) where:
+ - The code is [built](../user/project/pipelines/job_artifacts.md).
+ - Unit and integration tests are run over the built code.
+- Assuming the build and tests are successful, a CD pipeline is triggered to allow for:
+ - Review using [Review Apps](review_apps/index.md).
+ - Deploying to configured [environments](environments.md).
- - Article (2016-10-17): [Why We Chose GitLab CI for our CI/CD Solution](https://about.gitlab.com/2016/10/17/gitlab-ci-oohlala/)
- - Article (2016-07-22): [Building our web-app on GitLab CI: 5 reasons why Captain Train migrated from Jenkins to GitLab CI](https://about.gitlab.com/2016/07/22/building-our-web-app-on-gitlab-ci/)
+The benefits of CI/CD are vast, allowing automation to be an integral part of your workflow for testing, building, deploying, and monitoring your code.
-## Exploring GitLab CI/CD
+Because CI and CD with GitLab is broad topic with many possibilities, the rest of this section provides
+links to topics and resources needed to make use of GitLab CI/CD.
-- [CI/CD Variables](variables/README.md) - Learn how to use variables defined in
- your `.gitlab-ci.yml` or the ones defined in your project's settings
- - [Where variables can be used](variables/where_variables_can_be_used.md) - A
- deeper look on where and how the CI/CD variables can be used
-- **The permissions model** - Learn about the access levels a user can have for
- performing certain CI actions
- - [User permissions](../user/permissions.md#gitlab-ci)
- - [Job permissions](../user/permissions.md#job-permissions)
-- [Configure a Runner, the application that runs your jobs](runners/README.md)
-- Article (2016-03-01): [Setting up GitLab Runner For Continuous Integration](https://about.gitlab.com/2016/03/01/gitlab-runner-with-docker/)
-- Article (2016-07-29): [GitLab CI: Run jobs sequentially, in parallel, or build a custom pipeline](https://about.gitlab.com/2016/07/29/the-basics-of-gitlab-ci/)
-- Article (2016-08-26): [GitLab CI: Deployment & environments](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/)
-- Article (2016-05-23): [Introduction to GitLab Container Registry](https://about.gitlab.com/2016/05/23/gitlab-container-registry/)
+## Essentials
-## Advanced use
+The following documentation provides the minimum required knowledge for making use of GitLab CI/CD:
-Once you get familiar with the basics of GitLab CI/CD, it's time to dive in and
-learn how to leverage its potential even more.
+| Topic | Description |
+|:------------------------------------------------------------------------|:---------------------------------------------------------|
+| [Getting started with GitLab CI/CD](quick_start/README.md) | Outlines the first steps for configuring GitLab CI/CD. |
+| [Introduction to pipelines and jobs](pipelines.md) | Provides an overview of GitLab CI/CD and jobs. |
+| [Configuration of your pipelines with `.gitlab-ci.yml`](yaml/README.md) | A comprehensive reference for the `.gitlab-ci.yml` file. |
-- [Environments and deployments](environments.md): Separate your jobs into
- environments and use them for different purposes like testing, building and
- deploying
-- [Job artifacts](../user/project/pipelines/job_artifacts.md)
-- [Caching dependencies](caching/index.md)
-- [Git submodules](git_submodules.md) - How to run your CI jobs when Git
- submodules are involved
-- [Pipelines for merge requests](merge_request_pipelines/index.md)
-- [Use SSH keys in your build environment](ssh_keys/README.md)
-- [Trigger pipelines through the GitLab API](triggers/README.md)
-- [Trigger pipelines on a schedule](../user/project/pipelines/schedules.md)
-- [Kubernetes clusters](../user/project/clusters/index.md) - Integrate one or
- more Kubernetes clusters to your project
-- [Interactive web terminal](interactive_web_terminal/index.md) - Open an interactive
- web terminal to debug the running jobs
+NOTE: **Note:**
+Familiarity with [GitLab Runner](https://docs.gitlab.com/runner/) is useful because it is
+responsible for running the jobs in your CI/CD pipeline. On GitLab.com, shared Runners are enabled
+by default so you don't need to set up anything to get started.
-## GitLab CI/CD for Docker
+### Auto DevOps
-Leverage the power of Docker to run your CI pipelines.
+An alternative to manually configuring CI/CD, GitLab supports [Auto DevOps](../topics/autodevops/index.md),
+which:
-- [Use Docker images with GitLab Runner](docker/using_docker_images.md)
-- [Use CI to build Docker images](docker/using_docker_build.md)
-- [CI services (linked Docker containers)](services/README.md)
-- Article (2016-03-01): [Setting up GitLab Runner For Continuous Integration](https://about.gitlab.com/2016/03/01/gitlab-runner-with-docker/)
+- Provides simplified setup and execution of CI/CD.
+- Allows GitLab to automatically detect, build, test, deploy, and monitor your applications.
-## Review Apps
+## Basic usage
-- [Review Apps documentation](review_apps/index.md)
-- Article (2016-11-22): [Introducing Review Apps](https://about.gitlab.com/2016/11/22/introducing-review-apps/)
-- [Example project that shows how to use Review Apps](https://gitlab.com/gitlab-examples/review-apps-nginx/)
+With basic knowledge of how GitLab CI/CD works, the following documentation extends your knowledge
+into more features:
-## Auto DevOps
+| Topic | Description |
+|:-------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------|
+| [CI/CD Variables](variables/README.md) | How environment variables can be configured and made available in pipelines. |
+| [Where variables can be used](variables/where_variables_can_be_used.md) | A deeper look into where and how CI/CD variables can be used. |
+| [User](../user/permissions.md#gitlab-ci) and [job](../user/permissions.md#job-permissions) permissions | Learn about the access levels a user can have for performing certain CI actions. |
+| [Configuring GitLab Runners](runners/README.md) | Documentation for configuring [GitLab Runner](https://docs.gitlab.com/runner/). |
-- [Auto DevOps](../topics/autodevops/index.md): Auto DevOps automatically detects, builds, tests, deploys, and monitors your applications.
+## Advanced usage
-## GitLab CI for GitLab Pages
+Once you get familiar with the basics of GitLab CI/CD, consult the following documentation to make
+use of advanced features:
-See the documentation on [GitLab Pages](../user/project/pages/index.md).
+| Topic | Description |
+|:---------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------|
+| [Introduction to environments and deployments](environments.md) | Learn how to separate your jobs into environments and use them for different purposes like testing, building and, deploying. |
+| [Job artifacts](../user/project/pipelines/job_artifacts.md) | Learn about the output of jobs. |
+| [Cache dependencies in GitLab CI/CD](caching/index.md) | Discover how to speed up pipelines using caching. |
+| [Using Git submodules with GitLab CI](git_submodules.md) | How to run your CI jobs when using Git submodules. |
+| [Pipelines for merge requests](merge_request_pipelines/index.md) | Create pipelines specifically for merge requests. |
+| [Using SSH keys with GitLab CI/CD](ssh_keys/README.md) | Use SSH keys in your build environment. |
+| [Triggering pipelines through the API](triggers/README.md) | Use the GitLab API to trigger a pipeline. |
+| [Pipeline schedules](../user/project/pipelines/schedules.md) | Trigger pipelines on a schedule. |
+| [Connecting GitLab with a Kubernetes cluster](../user/project/clusters/index.md) | Integrate one or more Kubernetes clusters to your project. |
+| [ChatOps](chatops/README.md) | Trigger CI jobs from chat, with results sent back to the channel. |
+| [Interactive web terminals](interactive_web_terminal/index.md) | Open an interactive web terminal to debug the running jobs. |
+
+### GitLab Pages
+
+GitLab CI/CD can be used to build and host static websites. For more information, see the
+documentation on [GitLab Pages](../user/project/pages/index.md).
## Examples
-Check the [GitLab CI/CD examples](examples/README.md) for a collection of tutorials and guides on setting up your CI/CD pipeline for various programming languages, frameworks,
-and operating systems.
+Check out the [GitLab CI/CD examples](examples/README.md) for a collection of tutorials and guides on
+setting up your CI/CD pipeline for various programming languages, frameworks, and operating systems.
+
+## Administration
+
+As a GitLab administrator, you can change the default behavior of GitLab CI/CD for:
+
+- An [entire GitLab instance](../user/admin_area/settings/continuous_integration.md).
+- Specific projects, using [pipelines settings](../user/project/pipelines/settings.md).
+
+See also:
+
+- [How to enable or disable GitLab CI/CD](enable_or_disable_ci.md).
+- Other [CI administration settings](../administration/index.md#continuous-integration-settings).
+
+## Using Docker
+
+Docker is commonly used with GitLab CI/CD. Learn more about how to to accomplish this with the following
+documentation:
+
+| Topic | Description |
+|:-------------------------------------------------------------------------|:-------------------------------------------------------------------------|
+| [Using Docker images](docker/using_docker_images.md) | Use GitLab and GitLab Runner with Docker to build and test applications. |
+| [Building Docker images with GitLab CI/CD](docker/using_docker_build.md) | Maintain Docker-based projects using GitLab CI/CD. |
+
+Related topics include:
-## Integrations
+- [Docker integration](docker/README.md).
+- [CI services (linked Docker containers)](services/README.md).
+- [Setting up GitLab Runner For Continuous Integration](https://about.gitlab.com/2016/03/01/gitlab-runner-with-docker/) (article).
-- Article (2016-06-09): [Continuous Delivery with GitLab and Convox](https://about.gitlab.com/2016/06/09/continuous-delivery-with-gitlab-and-convox/)
-- Article (2016-05-05): [Getting Started with GitLab and Shippable Continuous Integration](https://about.gitlab.com/2016/05/05/getting-started-gitlab-and-shippable/)
-- Article (2016-04-19): [GitLab Partners with DigitalOcean to make Continuous Integration faster, safer, and more affordable](https://about.gitlab.com/2016/04/19/gitlab-partners-with-digitalocean-to-make-continuous-integration-faster-safer-and-more-affordable/)
+## Further resources
-## Special configuration (GitLab admin)
+This section provides further resources to help you get familiar with GitLab CI/CD.
-As a GitLab administrator, you can change the default behavior of GitLab CI/CD in
-your whole GitLab instance as well as in each project.
+### Articles
-- [Continuous Integration admin settings](../administration/index.md#continuous-integration-settings)
-- **Project specific:**
- - [Pipelines settings](../user/project/pipelines/settings.md)
- - [Learn how to enable or disable GitLab CI](enable_or_disable_ci.md)
-- **Affecting the whole GitLab instance:**
- - [Continuous Integration admin settings](../user/admin_area/settings/continuous_integration.md)
+The following table provides a list of articles about CI/CD, sorted in reverse chronological order of publish date:
+
+| Publish Date | Article |
+|:-------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| 2017-07-13 | [Making CI easier with GitLab](https://about.gitlab.com/2017/07/13/making-ci-easier-with-gitlab/). |
+| 2017-05-22 | [Fast and natural continuous integration with GitLab CI](https://about.gitlab.com/2017/05/22/fast-and-natural-continuous-integration-with-gitlab-ci/). |
+| 2016-11-22 | [Introducing Review Apps](https://about.gitlab.com/2016/11/22/introducing-review-apps/). |
+| 2016-08-26 | [GitLab CI: Deployment & Environments](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/). |
+| 2016-08-05 | [Continuous Integration, Delivery, and Deployment with GitLab](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/). |
+| 2016-07-29 | [GitLab CI: Run jobs sequentially, in parallel or build a custom pipeline](https://about.gitlab.com/2016/07/29/the-basics-of-gitlab-ci/). |
+| 2016-06-09 | [Continuous Delivery with GitLab and Convox](https://about.gitlab.com/2016/06/09/continuous-delivery-with-gitlab-and-convox/) |
+| 2016-05-23 | [GitLab Container Registry](https://about.gitlab.com/2016/05/23/gitlab-container-registry/). |
+| 2016-05-05 | [Getting Started with GitLab and Shippable Continuous Integration](https://about.gitlab.com/2016/05/05/getting-started-gitlab-and-shippable/) |
+| 2016-04-19 | [GitLab Partners with DigitalOcean to make Continuous Integration faster, safer, and more affordable](https://about.gitlab.com/2016/04/19/gitlab-partners-with-digitalocean-to-make-continuous-integration-faster-safer-and-more-affordable/) |
+| 2015-03-01 | [Setting up GitLab Runner For Continuous Integration](https://about.gitlab.com/2016/03/01/gitlab-runner-with-docker/). |
+| 2015-12-14 | [Getting started with GitLab and GitLab CI](https://about.gitlab.com/2015/12/14/getting-started-with-gitlab-and-gitlab-ci/). |
+
+### Videos
+
+The following table provides a list of videos about CI/CD, sorted in reverse chronological order of publish date:
+
+| Publish Date | Video |
+|:-------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| 2017-07-17 | [GitLab CI/CD Deep Dive](https://youtu.be/pBe4t1CD8Fc?t=195). |
+| 2017-03-13 | [Demo: CI/CD with GitLab in action](https://about.gitlab.com/2017/03/13/ci-cd-demo/). |
+| 2016-04-20 | [Webcast Recording and Slides: Getting started with CI in GitLab](https://about.gitlab.com/2016/04/20/webcast-recording-and-slides-introduction-to-ci-in-gitlab/). |
+
+In addition, the following third-party videos are available:
+
+- [Intégration continue avec GitLab (September 2016)](https://www.youtube.com/watch?v=URcMBXjIr24&t=13s).
+- [GitLab CI for Minecraft Plugins (July 2016)](https://www.youtube.com/watch?v=Z4pcI9F8yf8).
+
+### Example Projects
+
+[`review-apps-nginx`](https://gitlab.com/gitlab-examples/review-apps-nginx/) provides an example of using Review Apps.
+
+Other example projects are available at the [`gitlab-examples`](https://gitlab.com/gitlab-examples) group.
+
+### Why GitLab CI/CD?
+
+The following articles explain reasons why you might use GitLab CI/CD for your CI/CD infrastructure:
+
+- [Why we chose GitLab CI for our CI/CD solution](https://about.gitlab.com/2016/10/17/gitlab-ci-oohlala/).
+- [Building our web-app on GitLab CI](https://about.gitlab.com/2016/07/22/building-our-web-app-on-gitlab-ci/).
+
+See also the [Why CI/CD?](https://docs.google.com/presentation/d/1OGgk2Tcxbpl7DJaIOzCX4Vqg3dlwfELC3u2jEeCBbDk) presentation.
## Breaking changes
-- [CI variables renaming for GitLab 9.0](variables/README.md#9-0-renaming) Read about the
+As GitLab CI/CD has evolved, certain breaking changes have been necessary. These are:
+
+- [CI variables renaming for GitLab 9.0](variables/README.md#gitlab-90-renaming). Read about the
deprecated CI variables and what you should use for GitLab 9.0+.
-- [New CI job permissions model](../user/project/new_ci_build_permissions_model.md)
- Read about what changed in GitLab 8.12 and how that affects your jobs.
+- [New CI job permissions model](../user/project/new_ci_build_permissions_model.md).
+ See what changed in GitLab 8.12 and how that affects your jobs.
There's a new way to access your Git submodules and LFS objects in jobs.
diff --git a/doc/ci/caching/index.md b/doc/ci/caching/index.md
index 8b2ce425cf5..e079483e2b5 100644
--- a/doc/ci/caching/index.md
+++ b/doc/ci/caching/index.md
@@ -46,14 +46,13 @@ needed to compile the project:
with stages and shared artifacts before investing time in changes to the
setup.
-
It's sometimes confusing because the name artifact sounds like something that
is only useful outside of the job, like for downloading a final image. But
artifacts are also available in between stages within a pipeline. So if you
build your application by downloading all the required modules, you might want
to declare them as artifacts so that each subsequent stage can depend on them
being there. There are some optimizations like declaring an
-[expiry time](../yaml/README.md#artifacts-expire_in) so you don't keep artifacts
+[expiry time](../yaml/README.md#artifactsexpire_in) so you don't keep artifacts
around too long, and using [dependencies](../yaml/README.md#dependencies) to
control exactly where artifacts are passed around.
@@ -88,7 +87,7 @@ cache, when declaring `cache` in your jobs, use one or a mix of the following:
that share their cache.
- [Use sticky Runners](../runners/README.md#locking-a-specific-runner-from-being-enabled-for-other-projects)
that will be only available to a particular project.
-- [Use a `key`](../yaml/README.md#cache-key) that fits your workflow (e.g.,
+- [Use a `key`](../yaml/README.md#cachekey) that fits your workflow (e.g.,
different caches on each branch). For that, you can take advantage of the
[CI/CD predefined variables](../variables/README.md#predefined-environment-variables).
@@ -170,7 +169,7 @@ job:
```
For more fine tuning, read also about the
-[`cache: policy`](../yaml/README.md#cache-policy).
+[`cache: policy`](../yaml/README.md#cachepolicy).
## Common use cases
diff --git a/doc/ci/chatops/README.md b/doc/ci/chatops/README.md
new file mode 100644
index 00000000000..6ad1df7bb2a
--- /dev/null
+++ b/doc/ci/chatops/README.md
@@ -0,0 +1,61 @@
+# GitLab ChatOps
+
+> **Notes:**
+>
+> * [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4466) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 10.6. [Moved](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24780) to [GitLab Core](https://about.gitlab.com/pricing/) in 11.9.
+>
+> * ChatOps is currently in alpha, with some important features missing like access control.
+
+GitLab ChatOps provides a method to interact with CI/CD jobs through chat services like Slack. Many organizations' discussion, collaboration, and troubleshooting is taking place in chat services these days, and having a method to run CI/CD jobs with output posted back to the channel can significantly augment a team's workflow.
+
+## How it works
+
+GitLab ChatOps is built upon two existing features, [GitLab CI/CD](../README.md) and [Slack Slash Commmands](../../user/project/integrations/slack_slash_commands.md).
+
+A new `run` action has been added to the [slash commands](../../integration/slash_commands.md), which takes two arguments: a `<job name>` to execute and the `<job arguments>`. When executed, ChatOps will look up the specified job name and attempt to match it to a corresponding job in [.gitlab-ci.yml](../yaml/README.md). If a matching job is found on `master`, a pipeline containing just that job is scheduled. Two additional [CI/CD variables](../variables/README.html#predefined-variables-environment-variables) are passed to the job: `CHAT_INPUT` contains any additional arguments, and `CHAT_CHANNEL` is set to the name of channel the action was triggered in.
+
+After the job has finished, its output is sent back to Slack provided it has completed within 30 minutes. If a job takes more than 30 minutes to run it must use the Slack API to manually send data back to a channel.
+
+[Developer access and above](../../user/permissions.html#project-members-permissions) is required to use the `run` command. If a job should not be able to be triggered from chat, it can be set to `except: [chat]`.
+
+## Creating a ChatOps CI job
+
+Since ChatOps is built upon GitLab CI/CD, the job has all the same features and functions available. There a few best practices to consider however when creating ChatOps jobs:
+
+* It is strongly recommended to set `only: [chat]` so the job does not run as part of the standard CI pipeline.
+* If the job is set to `when: manual`, the pipeline will be created however the job will wait to be started.
+* It is important to keep in mind that there is very limited support for access control. If the user who triggered the slash command is a developer in the project, the job will run. The job itself can utilize existing [CI/CD variables](../variables/README.html#predefined-environment-variables) like `GITLAB_USER_ID` to perform additional rights validation, however these variables can be [overridden](https://docs.gitlab.com/ce/ci/variables/README.html#priority-of-variables).
+
+### Controlling the ChatOps reply
+
+For jobs with a single command, its output is automatically sent back to the channel as a reply. For example the chat reply of the following job is simply `Hello World.`
+
+```yaml
+hello-world:
+ stage: chatops
+ only: [chat]
+ script:
+ - echo "Hello World."
+```
+
+Jobs that contain multiple commands, or have a `before_script`, include additional content in the chat reply. In these cases both the commands and their output are included, with the commands wrapped in ANSI colors codes.
+
+To selectively reply with the output of one command, its output must be bounded by the `chat_reply` section. For example, the following job will list the files in the current directory.
+
+```yaml
+ls:
+ stage: chatops
+ only: [chat]
+ script:
+ - echo "This command will not be shown."
+ - echo -e "section_start:$( date +%s ):chat_reply\r\033[0K\n$( ls -la )\nsection_end:$( date +%s ):chat_reply\r\033[0K"
+```
+
+## GitLab ChatOps icon
+
+Say Hi to our ChatOps bot.
+You can find and download the official GitLab ChatOps icon here.
+
+![GitLab ChatOps bot icon](img/gitlab-chatops-icon-small.png)
+
+[Download bigger image](img/gitlab-chatops-icon.png)
diff --git a/doc/ci/chatops/img/gitlab-chatops-icon-small.png b/doc/ci/chatops/img/gitlab-chatops-icon-small.png
new file mode 100644
index 00000000000..71cc5dba5cf
--- /dev/null
+++ b/doc/ci/chatops/img/gitlab-chatops-icon-small.png
Binary files differ
diff --git a/doc/ci/chatops/img/gitlab-chatops-icon.png b/doc/ci/chatops/img/gitlab-chatops-icon.png
new file mode 100644
index 00000000000..3ba8bd308e3
--- /dev/null
+++ b/doc/ci/chatops/img/gitlab-chatops-icon.png
Binary files differ
diff --git a/doc/ci/docker/README.md b/doc/ci/docker/README.md
index 8ae80b2bc02..446f5b54f0c 100644
--- a/doc/ci/docker/README.md
+++ b/doc/ci/docker/README.md
@@ -4,6 +4,8 @@ comments: false
# Docker integration
-- [Using Docker Images](using_docker_images.md)
-- [Using Docker Build](using_docker_build.md)
-- [Using kaniko](using_kaniko.md)
+The following documentation is available for using GitLab CI/CD with Docker:
+
+- [Using Docker images](using_docker_images.md).
+- [Building Docker images with GitLab CI/CD](using_docker_build.md).
+- [Building images with kaniko and GitLab CI/CD](using_kaniko.md).
diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md
index 959271d8abc..3314c76d234 100644
--- a/doc/ci/docker/using_docker_images.md
+++ b/doc/ci/docker/using_docker_images.md
@@ -391,9 +391,9 @@ CI jobs:
from `Dockerfile` that may be overridden in `.gitlab-ci.yml`)
1. The Runner attaches itself to a running container.
1. The Runner prepares a script (the combination of
- [`before_script`](../yaml/README.md#before_script),
+ [`before_script`](../yaml/README.md#before_script-and-after_script),
[`script`](../yaml/README.md#script),
- and [`after_script`](../yaml/README.md#after_script)).
+ and [`after_script`](../yaml/README.md#before_script-and-after_script)).
1. The Runner sends the script to the container's shell STDIN and receives the
output.
diff --git a/doc/ci/environments.md b/doc/ci/environments.md
index 6a9917f6430..fe66f7e3c28 100644
--- a/doc/ci/environments.md
+++ b/doc/ci/environments.md
@@ -103,7 +103,7 @@ the Git SHA and environment name.
To sum up, with the above `.gitlab-ci.yml` we have achieved that:
- All branches will run the `test` and `build` jobs.
-- The `deploy_staging` job will run [only](yaml/README.md#only) on the `master`
+- The `deploy_staging` job will run [only](yaml/README.md#only-and-except-simplified) on the `master`
branch which means all merge requests that are created from branches don't
get to deploy to the staging server
- When a merge request is merged, all jobs will run and the `deploy_staging`
@@ -401,7 +401,7 @@ Let's briefly see where URL that's defined in the environments is exposed.
## Making use of the environment URL
-The [environment URL](yaml/README.md#environments-url) is exposed in a few
+The [environment URL](yaml/README.md#environmenturl) is exposed in a few
places within GitLab.
| In a merge request widget as a link | In the Environments view as a button | In the Deployments view as a button |
@@ -619,9 +619,9 @@ Below are some links you may find interesting:
[deployments]: #deployments
[permissions]: ../user/permissions.md
[variables]: variables/README.md
-[env-name]: yaml/README.md#environment-name
-[only]: yaml/README.md#only-and-except
-[onstop]: yaml/README.md#environment-on_stop
+[env-name]: yaml/README.md#environmentname
+[only]: yaml/README.md#only-and-except-simplified
+[onstop]: yaml/README.md#environmenton_stop
[ce-7015]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7015
[gitlab-flow]: ../workflow/gitlab_flow.md
[gitlab runner]: https://docs.gitlab.com/runner/
diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md
index a8c119edaa0..87e86bef44b 100644
--- a/doc/ci/examples/README.md
+++ b/doc/ci/examples/README.md
@@ -40,6 +40,7 @@ There's also a collection of repositories with [example projects](https://gitlab
### Miscellaneous
+- [End-to-end testing with GitLab CI/CD and WebdriverIO](end_to_end_testing_webdriverio/index.md)
- [Using `dpl` as deployment tool](deployment/README.md)
- [The `.gitlab-ci.yml` file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml)
diff --git a/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md b/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md
index 6499413baf0..474a481836a 100644
--- a/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md
+++ b/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md
@@ -112,7 +112,7 @@ on GitLab CI/CD. To set the environment variables, navigate to your project's
![Variable Settings in GitLab](img/cloud_foundry_variables.png)
Once set up, GitLab CI/CD will deploy your app to CF at every push to your
-repository's deafult branch. To see the build logs or watch your builds running
+repository's default branch. To see the build logs or watch your builds running
live, navigate to **CI/CD > Pipelines**.
CAUTION: **Caution:**
diff --git a/doc/ci/examples/deployment/composer-npm-deploy.md b/doc/ci/examples/deployment/composer-npm-deploy.md
index 36358515b84..4758ccad5aa 100644
--- a/doc/ci/examples/deployment/composer-npm-deploy.md
+++ b/doc/ci/examples/deployment/composer-npm-deploy.md
@@ -4,7 +4,6 @@ This guide covers the building dependencies of a PHP project while compiling ass
While is possible to create your own image with custom PHP and Node JS versions, for brevity, we will use an existing [Docker image](https://hub.docker.com/r/tetraweb/php/) that contains both PHP and NodeJS installed.
-
```yaml
image: tetraweb/php
```
diff --git a/doc/ci/examples/end_to_end_testing_webdriverio/img/deployed_dependency_update.png b/doc/ci/examples/end_to_end_testing_webdriverio/img/deployed_dependency_update.png
new file mode 100644
index 00000000000..c45d70d7f7a
--- /dev/null
+++ b/doc/ci/examples/end_to_end_testing_webdriverio/img/deployed_dependency_update.png
Binary files differ
diff --git a/doc/ci/examples/end_to_end_testing_webdriverio/index.md b/doc/ci/examples/end_to_end_testing_webdriverio/index.md
new file mode 100644
index 00000000000..d7afe11f41f
--- /dev/null
+++ b/doc/ci/examples/end_to_end_testing_webdriverio/index.md
@@ -0,0 +1,251 @@
+---
+author: Vincent Tunru
+author_gitlab: Vinnl
+level: advanced
+article_type: user guide
+date: 2019-02-18
+description: 'Confidence checking your entire app every time a new feature is added can quickly become repetitive. Learn how to automate it with GitLab CI/CD.'
+---
+
+# End-to-end testing with GitLab CI/CD and WebdriverIO
+
+[Review Apps](../../review_apps/index.md) are great: for every merge request
+(or branch, for that matter), the new code can be copied and deployed to a fresh production-like live
+environment, making it incredibly low-effort to assess the impact of the changes. Thus, when we use a dependency manager like
+[Dependencies.io](https://www.dependencies.io/), it can submit a merge request with an updated dependency,
+and it will immediately be clear that the application can still be properly built and deployed. After all, you can _see_ it
+running!
+
+<img src="img/deployed_dependency_update.png" alt="dependencies.io" class="image-noshadow">
+
+However, looking at the freshly deployed code to check whether it still looks and behaves as
+expected is repetitive manual work, which means it is a prime candidate for automation. This is
+where automated [end-to-end testing](https://martinfowler.com/bliki/BroadStackTest.html) comes in:
+having the computer run through a few simple scenarios that requires the proper functioning of all
+layers of your application, from the frontend to the database. In this article, we will discuss how
+to write such end-to-end tests, and how to set up GitLab CI/CD to automatically run these tests
+against your new code, on a branch-by-branch basis. For the scope of this article, we will walk you
+through the process of setting up GitLab CI/CD for end-to-end testing Javascript-based applications
+with WebdriverIO, but the general strategy should carry over to other languages.
+We assume you are familiar with GitLab, [GitLab CI/CD](../../README.md), [Review Apps](../../review_apps/index.md), and running your app locally, e.g., on `localhost:8000`.
+
+### What to test
+
+In the widely-used [testing pyramid strategy](https://martinfowler.com/bliki/TestPyramid.html), end-to-end tests act more like a
+safeguard: [most of your code should be covered by
+unit tests](https://vincenttunru.com/100-percent-coverage/) that allow you to easily identify the source of a problem, should one occur. Rather, you
+will likely want to
+[limit the number of end-to-end tests](https://testing.googleblog.com/2015/04/just-say-no-to-more-end-to-end-tests.html)
+to just enough to give you the confidence that the deployment went as intended, that your
+infrastructure is up and running, and that your units of code work well together.
+
+### Selenium and WebdriverIO
+
+[Selenium](http://www.seleniumhq.org/) is a piece of software that can control web browsers, e.g., to make them
+visit a specific URL or interact with elements on the page. It can be programmatically controlled
+from a variety of programming languages. In this article we're going to be using the
+[WebdriverIO](http://webdriver.io/) Javascript bindings, but the general concept should carry over
+pretty well to
+[other programming languages supported by Selenium](http://docs.seleniumhq.org/about/platforms.jsp#programming-languages).
+
+## Writing tests
+
+You can write tests using
+[several testing frameworks supported by WebdriverIO](http://webdriver.io/guide/testrunner/frameworks.html).
+We will be using [Jasmine](https://jasmine.github.io/) here:
+
+```javascript
+describe('A visitor without account', function(){
+ it('should be able to navigate to the homepage from the 404 page', function(){
+ browser.url('/page-that-does-not-exist');
+
+ expect(browser.getUrl()).toMatch('page-that-does-not-exist');
+
+ browser.element('.content a[href="/"]').click();
+
+ expect(browser.getUrl()).not.toMatch('page-that-does-not-exist');
+ });
+});
+```
+
+The functions `describe`, `it`, and `browser` are provided by WebdriverIO. Let's break them down one by one.
+
+The function `describe` allows you to group related tests. This can be useful if, for example, you want to
+run the same initialization commands (using [`beforeEach`](https://jasmine.github.io/api/2.9/global.html#beforeEach)) for
+multiple tests, such as making sure you are logged in.
+
+The function `it` defines an individual test.
+
+[The `browser` object](http://webdriver.io/guide/testrunner/browserobject.html) is WebdriverIO's
+special sauce. It provides most of [the WebdriverIO API methods](http://webdriver.io/api.html) that are the key to
+steering the browser. In this case, we can use
+[`browser.url`](http://webdriver.io/api/protocol/url.html) to visit `/page-that-does-not-exist` to
+hit our 404 page. We can then use [`browser.getUrl`](http://webdriver.io/api/property/getUrl.html)
+to verify that the current page is indeed at the location we specified. To interact with the page,
+we can simply pass CSS selectors to
+[`browser.element`](http://webdriver.io/api/protocol/element.html) to get access to elements on the
+page and to interact with them - for example, to click on the link back to the home page.
+
+The simple test shown above
+can already give us a lot of confidence if it passes: we know our deployment has succeeded, that the
+elements are visible on the page and that actual browsers can interact with it, and that routing
+works as expected. And all that in just 10 lines with gratuitous whitespace! Add to that succeeding
+unit tests and a successfully completed pipeline, and you can be fairly confident that the
+dependency upgrade did not break anything without even having to look at your website.
+
+## Running locally
+
+We'll get to running the above test in CI/CD in a moment. When writing tests, however, it helps if
+you do not have to wait for your pipelines to succeed in order to check whether they do what you
+expect them to do. In other words, let's get it to run locally.
+
+Make sure that your app is running locally. If you use Webpack,
+you can use [the Webpack Dev Server WebdriverIO plugin](https://www.npmjs.com/package/wdio-webpack-dev-server-service)
+that automatically starts a development server before executing the tests.
+
+The WebdriverIO documentation has
+[an overview of all configuration options](http://webdriver.io/guide/getstarted/configuration.html), but the
+easiest way to get started is to start with
+[WebdriverIO's default configuration](http://webdriver.io/guide/testrunner/configurationfile.html), which
+provides an overview of all available options. The two options that are going to be most relevant now are the
+`specs` option, which is an array of paths to your tests, and the `baseUrl` option, which points to where your app is
+running. And finally, we will need to tell WebdriverIO in which browsers we would like to run our
+tests. This can be configured through the `capabilities` option, which is an array of browser names (e.g.
+`firefox` or `chrome`). It is recommended to install
+[selenium-assistant](https://googlechromelabs.github.io/selenium-assistant/) to detect all installed
+browsers:
+
+```javascript
+ const seleniumAssistant = require('selenium-assistant');
+ const browsers = seleniumAssistant.getLocalBrowsers();
+ config.capabilities = browsers.map(browser => ({ browserName: browser.getId() }));
+```
+
+But of course, a simple configuration of `config.capabilities = ['firefox']` would work as well.
+
+If you've installed WebdriverIO as a dependency
+(`npm install --save-dev webdriverio`), you can add a line to the `scripts` property in your
+`package.json` that runs `wdio` with the path to your configuration file as value, e.g.:
+
+```javascript
+ "confidence-check": "wdio wdio.conf.js",
+```
+
+You can then execute the tests using `npm run confidence-check`, after which you will actually see a
+new browser window interacting with your app as you specified.
+
+## Configuring GitLab CI/CD
+
+Which brings us to the exciting part: how do we run this in GitLab CI/CD? There are two things we
+need to do for this:
+
+1. Set up [CI/CD jobs](../../yaml/README.md#jobs) that actually have a browser available.
+2. Update our WebdriverIO configuration to use those browsers to visit the review apps.
+
+For the scope of this article, we've defined an additional [CI/CD stage](../../yaml/README.md#stages)
+`confidence-check` that is executed _after_ the stage that deploys the review app. It uses the `node:latest` [Docker
+image](../../docker/using_docker_images.html). However, WebdriverIO fires up actual browsers
+to interact with your application, so we need to install and run them.
+Furthermore, WebdriverIO uses Selenium as a common interface to control different browsers,
+so we need to install and run Selenium as well. Luckily, the Selenium project provides the Docker images
+[standalone-firefox](https://hub.docker.com/r/selenium/standalone-firefox/) and
+[standalone-chrome](https://hub.docker.com/r/selenium/standalone-chrome/) that provide just that for
+Firefox and Chrome, respectively. (Since Safari and Internet Explorer/Edge are not open source and
+not available for Linux, we are unfortunately unable to use those in GitLab CI/CD).
+
+GitLab CI/CD makes it a breeze to link these images to our `confidence-check` jobs using the
+`service` property, which makes the Selenium server available under a hostname based on the image
+name. Our job configuration then looks something like this:
+
+```yaml
+e2e:firefox:
+ stage: confidence-check
+ services:
+ - selenium/standalone-firefox
+ script:
+ - npm run confidence-check --host=selenium__standalone-firefox
+```
+
+And likewise for Chrome:
+
+```yaml
+e2e:chrome:
+ stage: confidence-check
+ services:
+ - selenium/standalone-chrome
+ script:
+ - npm run confidence-check --host=selenium__standalone-chrome
+```
+
+Now that we have a job to run the end-to-end tests in, we need to tell WebdriverIO how to connect to
+the Selenium servers running alongside it. We've already cheated a bit above by
+passing the value of the [`host`](http://webdriver.io/guide/getstarted/configuration.html#host)
+option as an argument to `npm run confidence-check` on the command line.
+However, we still need to tell WebdriverIO which browser is available for it to use.
+
+[GitLab CI/CD makes
+a number of variables available](../../variables/README.html#predefined-variables-environment-variables)
+with information about the current CI job. We can use this information to dynamically set
+up our WebdriverIO configuration according to the job that is running. More specifically, we can
+tell WebdriverIO what browser to execute the test on depending on the name of the currently running
+job. We can do so in WebdriverIO's configuration file, which we named `wdio.conf.js` above:
+
+```javascript
+if(process.env.CI_JOB_NAME) {
+ dynamicConfig.capabilities = [
+ { browserName: process.env.CI_JOB_NAME === 'e2e:chrome' ? 'chrome' : 'firefox' },
+ ];
+}
+```
+
+Likewise, we can tell WebdriverIO where the review app is running - in this example's case, it's on
+`<branch name>.flockademic.com`:
+
+```javascript
+if(process.env.CI_COMMIT_REF_SLUG) {
+ dynamicConfig.baseUrl = `https://${process.env.CI_COMMIT_REF_SLUG}.flockademic.com`;
+}
+```
+
+And we can make sure our local-specific configuration is only used when _not_ running in CI using
+`if (!process.env.CI)`. That's basically all the ingredients you need to run your end-to-end tests
+on GitLab CI/CD!
+
+To recap, our `.gitlab-ci.yml` configuration file looks something like this:
+
+```yaml
+image: node:8.10
+stages:
+ - deploy
+ - confidence-check
+deploy_terraform:
+ stage: deploy
+ script:
+ # Your Review App deployment scripts - for a working example please check https://gitlab.com/Flockademic/Flockademic/blob/5a45f1c2412e93810fab50e2dab8949e2d0633c7/.gitlab-ci.yml#L315
+e2e:firefox:
+ stage: confidence-check
+ services:
+ - selenium/standalone-firefox
+ script:
+ - npm run confidence-check --host=selenium__standalone-firefox
+e2e:chrome:
+ stage: confidence-check
+ services:
+ - selenium/standalone-chrome
+ script:
+ - npm run confidence-check --host=selenium__standalone-chrome
+```
+
+## What's next
+
+If you are setting this up for yourself and want to peek at the working configuration of a
+production project, see:
+
+- [Flockademic's `wdio.conf.js`](https://gitlab.com/Flockademic/Flockademic/blob/dev/wdio.conf.js)
+- [Flockademic's `.gitlab-ci.yml`](https://gitlab.com/Flockademic/Flockademic/blob/dev/.gitlab-ci.yml)
+- [Flockademic's tests](https://gitlab.com/Flockademic/Flockademic/tree/dev/__e2e__)
+
+There's plenty more that WebdriverIO can do. For example, you can configure a [`screenshotPath`](http://webdriver.io/guide/getstarted/configuration.html#screenshotPath) to tell WebdriverIO to take
+a screenshot when tests are failing. Then tell GitLab CI/CD to store those
+[artifacts](../../yaml/README.md#artifacts), and you'll be able to see what went
+wrong within GitLab.
diff --git a/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md b/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md
index b1ccce744d8..d3b6650b0f4 100644
--- a/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md
+++ b/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md
@@ -68,7 +68,6 @@ Since we have our app up and running locally, it's time to push the codebase to
Let's create [a new project](../../../gitlab-basics/create-project.md) in GitLab named `laravel-sample`.
After that, follow the command line instructions displayed on the project's homepage to initiate the repository on our machine and push the first commit.
-
```bash
cd laravel-sample
git init
@@ -127,7 +126,6 @@ We'll use this variable in the `.gitlab-ci.yml` later, to easily connect to our
We also need to add the public key to **Project** > **Settings** > **Repository** as a [Deploy Key](../../../ssh/README.md#deploy-keys), which gives us the ability to access our repository from the server through [SSH protocol](../../../gitlab-basics/command-line-commands.md#start-working-on-your-project).
-
```bash
# As the deployer user on the server
#
@@ -186,7 +184,6 @@ To use Envoy, we should first install it on our local machine [using the given i
The pros of Envoy is that it doesn't require Blade engine, it just uses Blade syntax to define tasks.
To start, we create an `Envoy.blade.php` in the root of our app with a simple task to test Envoy.
-
```php
@servers(['web' => 'remote_username@remote_host'])
@@ -220,7 +217,6 @@ Our deployment plan is to clone the latest release from GitLab repository, insta
The first step of our deployment process is to define a set of variables within [@setup](https://laravel.com/docs/envoy/#setup) directive.
You may change the `app` to your application's name:
-
```php
...
@@ -246,7 +242,6 @@ You may change the `app` to your application's name:
The [@story](https://laravel.com/docs/envoy/#stories) directive allows us define a list of tasks that can be run as a single task.
Here we have three tasks called `clone_repository`, `run_composer`, `update_symlinks`. These variables are usable to making our task's codes more cleaner:
-
```php
...
@@ -618,7 +613,7 @@ Lastly, `when: manual` is used to turn the job from running automatically to a m
deploy_production:
script:
- # Add the private SSH key to the build environment
+ # Add the private SSH key to the build environment
- 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
- eval $(ssh-agent -s)
- ssh-add <(echo "$SSH_PRIVATE_KEY")
diff --git a/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md b/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md
index c0346d78141..f24e79355aa 100644
--- a/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md
+++ b/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md
@@ -75,7 +75,7 @@ When we call `mix` command, we'll pass two arguments:
- The task we want it to run: `phoenix.new`
- And the parameter `phoenix.new` requires, which is the name of the new project. In this case,
-we're calling it `hello_gitlab_ci`, but you're free to set your own name:
+ we're calling it `hello_gitlab_ci`, but you're free to set your own name:
```bash
mix phoenix.new hello_gitlab_ci
@@ -249,7 +249,7 @@ project.
![Set up CI](img/setup-ci.png)
- On next screen, we can select a template ready to go. Click on **Apply a GitLab CI/CD Yaml
-template** and select **Elixir**:
+ template** and select **Elixir**:
![Select template](img/select-template.png)
diff --git a/doc/ci/merge_request_pipelines/index.md b/doc/ci/merge_request_pipelines/index.md
index b7b5c660586..2af0a03cbe4 100644
--- a/doc/ci/merge_request_pipelines/index.md
+++ b/doc/ci/merge_request_pipelines/index.md
@@ -106,10 +106,10 @@ flow, external contributors follow the following steps:
1. Fork a parent project.
1. Create a merge request from the forked project that targets the `master` branch
-in the parent project.
+ in the parent project.
1. A pipeline runs on the merge request.
1. A maintainer from the parent project checks the pipeline result, and merge
-into a target branch if the latest pipeline has passed.
+ into a target branch if the latest pipeline has passed.
Currently, those pipelines are created in a **forked** project, not in the
parent project. This means you cannot completely trust the pipeline result,
diff --git a/doc/ci/pipelines.md b/doc/ci/pipelines.md
index c41f3b7e82d..4f3106c6dc6 100644
--- a/doc/ci/pipelines.md
+++ b/doc/ci/pipelines.md
@@ -197,7 +197,7 @@ stage has a job with a manual action.
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21767) in GitLab 11.4.
-When you do not want to run a job immediately, you can [delay the job to run after a certain period](yaml/README.md#when-delayed).
+When you do not want to run a job immediately, you can [delay the job to run after a certain period](yaml/README.md#whendelayed).
This is especially useful for timed incremental rollout that new code is rolled out gradually.
For example, if you start rolling out new code and users do not experience trouble, GitLab automatically completes the deployment from 0% to 100%.
Alternatively, if you start rolling out and you noticed that a few users experience trouble with the version,
diff --git a/doc/ci/runners/README.md b/doc/ci/runners/README.md
index c742dc61368..ce55b231666 100644
--- a/doc/ci/runners/README.md
+++ b/doc/ci/runners/README.md
@@ -240,21 +240,64 @@ shared Runners will [only run the jobs they are equipped to run](../yaml/README.
For instance, at GitLab we have Runners tagged with "rails" if they contain
the appropriate dependencies to run Rails test suites.
-### Preventing Runners with tags from picking jobs without tags
+### Allowing Runners with tags to pick jobs without tags
-You can configure a Runner to prevent it from picking
-[jobs with tags](../yaml/README.md#tags) when the Runner does not have tags
-assigned. This setting can be enabled the first
-time you [register a Runner][register] and can be changed afterwards under
-each Runner's settings.
+When you [register a Runner][register], its default behavior is to **only pick**
+[tagged jobs](../yaml/README.md#tags).
-To make a Runner pick tagged/untagged jobs:
+NOTE: **Note:**
+Maintainer [permissions](../../user/permissions.md) are required to change the
+Runner settings.
-1. Visit your project's **Settings âž” CI/CD**
-1. Find the Runner you wish and make sure it's enabled
-1. Click the pencil button
-1. Check the **Run untagged jobs** option
-1. Click **Save changes** for the changes to take effect
+To make a Runner pick untagged jobs:
+
+1. Visit your project's **Settings > CI/CD > Runners**.
+1. Find the Runner you want to pick untagged jobs and make sure it's enabled.
+1. Click the pencil button.
+1. Check the **Run untagged jobs** option.
+1. Click the **Save changes** button for the changes to take effect.
+
+NOTE: **Note:**
+The Runner tags list can not be empty when it's not allowed to pick untagged jobs.
+
+Below are some example scenarios of different variations.
+
+#### Runner runs only tagged jobs
+
+The following examples illustrate the potential impact of the Runner being set
+to run only tagged jobs.
+
+Example 1:
+
+1. The Runner is configured to run only tagged jobs and has the `docker` tag.
+1. A job that has a `hello` tag is executed and stuck.
+
+Example 2:
+
+1. The Runner is configured to run only tagged jobs and has the `docker` tag.
+1. A job that has a `docker` tag is executed and run.
+
+Example 3:
+
+1. The Runner is configured to run only tagged jobs and has the `docker` tag.
+1. A job that has no tags defined is executed and stuck.
+
+#### Runner is allowed to run untagged jobs
+
+The following examples illustrate the potential impact of the Runner being set
+to run tagged and untagged jobs.
+
+Example 1:
+
+1. The Runner is configured to run untagged jobs and has the `docker` tag.
+1. A job that has no tags defined is executed and run.
+1. A second job that has a `docker` tag defined is executed and run.
+
+Example 2:
+
+1. The Runner is configured to run untagged jobs and has no tags defined.
+1. A job that has no tags defined is executed and run.
+1. A second job that has a `docker` tag defined is stuck.
### Setting maximum job timeout for a Runner
diff --git a/doc/ci/ssh_keys/README.md b/doc/ci/ssh_keys/README.md
index 0c3b0bf6990..ff63d0bd69f 100644
--- a/doc/ci/ssh_keys/README.md
+++ b/doc/ci/ssh_keys/README.md
@@ -92,7 +92,7 @@ to access it. This is where an SSH key pair comes in handy.
```
NOTE: **Note:**
- The [`before_script`](../yaml/README.md#before-script) can be set globally
+ The [`before_script`](../yaml/README.md#before_script-and-after_script) can be set globally
or per-job.
1. Make sure the private server's [SSH host keys are verified](#verifying-the-ssh-host-keys).
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index 7498617ed2c..6fe352df48a 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -53,6 +53,8 @@ future GitLab releases.**
| Variable | GitLab | Runner | Description |
|-------------------------------------------|--------|--------|-------------|
| **ARTIFACT_DOWNLOAD_ATTEMPTS** | 8.15 | 1.9 | Number of attempts to download artifacts running a job |
+| **CHAT_INPUT** | 10.6 | all | Additional arguments passed in the [ChatOps](../chatops/README.md) command |
+| **CHAT_CHANNEL** | 10.6 | all | Source chat channel which triggered the [ChatOps](../chatops/README.md) command |
| **CI** | all | 0.4 | Mark that job is executed in CI environment |
| **CI_COMMIT_BEFORE_SHA** | 11.2 | all | The previous latest commit present on a branch before a push request. |
| **CI_COMMIT_DESCRIPTION** | 10.8 | all | The description of the commit: the message without first line, if the title is shorter than 100 characters; full message in other case. |
@@ -84,10 +86,12 @@ future GitLab releases.**
| **CI_MERGE_REQUEST_PROJECT_URL** | 11.6 | all | The URL of the project of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) (e.g. `http://192.168.10.15:3000/namespace/awesome-project`) |
| **CI_MERGE_REQUEST_REF_PATH** | 11.6 | all | The ref path of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md). (e.g. `refs/merge-requests/1/head`) |
| **CI_MERGE_REQUEST_SOURCE_BRANCH_NAME** | 11.6 | all | The source branch name of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) |
+| **CI_MERGE_REQUEST_SOURCE_BRANCH_SHA** | 11.9 | all | The HEAD sha of the source branch of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) |
| **CI_MERGE_REQUEST_SOURCE_PROJECT_ID** | 11.6 | all | The ID of the source project of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) |
| **CI_MERGE_REQUEST_SOURCE_PROJECT_PATH** | 11.6 | all | The path of the source project of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) |
| **CI_MERGE_REQUEST_SOURCE_PROJECT_URL** | 11.6 | all | The URL of the source project of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) |
| **CI_MERGE_REQUEST_TARGET_BRANCH_NAME** | 11.6 | all | The target branch name of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) |
+| **CI_MERGE_REQUEST_TARGET_BRANCH_SHA** | 11.9 | all | The HEAD sha of the target branch of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) |
| **CI_NODE_INDEX** | 11.5 | all | Index of the job in the job set. If the job is not parallelized, this variable is not set. |
| **CI_NODE_TOTAL** | 11.5 | all | Total number of instances of this job running in parallel. If the job is not parallelized, this variable is set to `1`. |
| **CI_API_V4_URL** | 11.7 | all | The GitLab API v4 root URL |
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 984878b6c9b..455ab900764 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -1,7 +1,7 @@
-# Configuration of your jobs with .gitlab-ci.yml
+# Configuration of your pipelines with .gitlab-ci.yml
This document describes the usage of `.gitlab-ci.yml`, the file that is used by
-GitLab Runner to manage your project's jobs.
+GitLab Runner to manage your project's pipelines.
From version 7.12, GitLab CI uses a [YAML](https://en.wikipedia.org/wiki/YAML)
file (`.gitlab-ci.yml`) for the project configuration. It is placed in the root
@@ -96,9 +96,9 @@ This can be an array or a multi-line string.
jobs, including failed ones. This has to be an array or a multi-line string.
The `before_script` and the main `script` are concatenated and run in a single context/container.
-The `after_script` is run separately, so depending on the executor, changes done
-outside of the working tree might not be visible, e.g. software installed in the
-`before_script`.
+The `after_script` is run separately. The current working directory is set back to
+default. Depending on the executor, changes done outside of the working tree might
+not be visible, e.g. software installed in the `before_script`.
It's possible to overwrite the globally defined `before_script` and `after_script`
if you set it per-job:
@@ -423,10 +423,28 @@ connected with merge requests yet, and because GitLab is creating pipelines
before an user can create a merge request we don't know a target branch at
this point.
-Without a target branch, it is not possible to know what the common ancestor is,
-thus we always create a job in that case. This feature works best for stable
-branches like `master` because in that case GitLab uses the previous commit
-that is present in a branch to compare against the latest SHA that was pushed.
+#### Using `changes` with `merge_requests`
+
+With [pipelines for merge requests](../merge_request_pipelines/index.md),
+make it possible to define if a job should be created base on files modified
+in a merge request.
+
+For example:
+
+```
+docker build service one:
+ script: docker build -t my-service-one-image:$CI_COMMIT_REF_SLUG .
+ only:
+ refs:
+ - merge_requests
+ changes:
+ - Dockerfile
+ - service-one/**/*
+```
+
+In the scenario above, if you create or update a merge request that changes
+either files in `service-one` folder or `Dockerfile`, GitLab creates and triggers
+the `docker build service one` job.
## `tags`
@@ -600,9 +618,9 @@ action fails, the pipeline will eventually succeed.
Manual actions are considered to be write actions, so permissions for
[protected branches](../../user/project/protected_branches.md) are used when
-user wants to trigger an action. In other words, in order to trigger a manual
-action assigned to a branch that the pipeline is running for, user needs to
-have ability to merge to this branch.
+a user wants to trigger an action. In other words, in order to trigger a manual
+action assigned to a branch that the pipeline is running for, the user needs to
+have the ability to merge to this branch.
### `when:delayed`
@@ -1188,7 +1206,7 @@ job:
`expire_in` allows you to specify how long artifacts should live before they
expire and therefore deleted, counting from the time they are uploaded and
stored on GitLab. If the expiry time is not defined, it defaults to the
-[instance wide setting](../../user/admin_area/settings/continuous_integration.md#default-artifacts-expiration)
+[instance wide setting](../../user/admin_area/settings/continuous_integration.md#default-artifacts-expiration-core-only)
(30 days by default, forever on GitLab.com).
You can use the **Keep** button on the job page to override expiration and
@@ -1227,7 +1245,7 @@ this with [JUnit reports](#artifactsreportsjunit).
NOTE: **Note:**
The test reports are collected regardless of the job results (success or failure).
-You can use [`artifacts:expire_in`](#artifacts-expire_in) to set up an expiration
+You can use [`artifacts:expire_in`](#artifactsexpire_in) to set up an expiration
date for their artifacts.
NOTE: **Note:**
@@ -1407,7 +1425,7 @@ deploy:
> Introduced in GitLab 10.3.
If the artifacts of the job that is set as a dependency have been
-[expired](#artifacts-expire_in) or
+[expired](#artifactsexpire_in) or
[erased](../../user/project/pipelines/job_artifacts.md#erasing-artifacts), then
the dependent job will fail.
diff --git a/doc/customization/help_message.md b/doc/customization/help_message.md
new file mode 100644
index 00000000000..c2e592d03bf
--- /dev/null
+++ b/doc/customization/help_message.md
@@ -0,0 +1,13 @@
+# GitLab Help custom text
+
+In larger organizations it is useful to have information about who has the responsibility of maintaining the company GitLab server.
+
+1. Navigate to the admin area, click on **Preferences** and expand **Help page**.
+
+1. Under **Help text** fill in the required information about the person(s) administering GitLab or any other information relevant to your needs.
+
+ ![help message](help_message/help_text.png)
+
+1. After saving the page this information will be shown on the GitLab login page and on the GitLab `/help` page (e.g., <https://gitlab.com/help>).
+
+ ![help text on help page](help_message/help_text_on_help_page.png)
diff --git a/doc/customization/help_message/help_text.png b/doc/customization/help_message/help_text.png
new file mode 100644
index 00000000000..99697a106bf
--- /dev/null
+++ b/doc/customization/help_message/help_text.png
Binary files differ
diff --git a/doc/customization/help_message/help_text_on_help_page.png b/doc/customization/help_message/help_text_on_help_page.png
new file mode 100644
index 00000000000..288b4b8c1eb
--- /dev/null
+++ b/doc/customization/help_message/help_text_on_help_page.png
Binary files differ
diff --git a/doc/customization/index.md b/doc/customization/index.md
new file mode 100644
index 00000000000..71e87b3f111
--- /dev/null
+++ b/doc/customization/index.md
@@ -0,0 +1,18 @@
+---
+description: Learn how to customize GitLab's appearance for self-managed installations.
+---
+
+# Customizing GitLab's appearance **[CORE ONLY]**
+
+For GitLab self-managed instances, it's possible to customize
+a few pages.
+
+Read through the following documents to adjust GitLab's
+look and feel to meet your needs:
+
+- [Custom login page](branded_login_page.md)
+- [Custom header and email logo](branded_page_and_email_header.md)
+- [Custom favicon](favicon.md)
+- [Libravatar](libravatar.md)
+- [New project page](new_project_page.md)
+- [Custom `/help` message](help_message.md) \ No newline at end of file
diff --git a/doc/customization/libravatar.md b/doc/customization/libravatar.md
index 9bd22d3966d..18aaeb5a712 100644
--- a/doc/customization/libravatar.md
+++ b/doc/customization/libravatar.md
@@ -38,7 +38,6 @@ For example, you host a service on `http://libravatar.example.com` the `plain_ur
`http://libravatar.example.com/avatar/%{hash}?s=%{size}&d=identicon`
-
## Omnibus-gitlab example
In `/etc/gitlab/gitlab.rb`:
@@ -57,10 +56,8 @@ gitlab_rails['gravatar_enabled'] = true
gitlab_rails['gravatar_ssl_url'] = "https://seccdn.libravatar.org/avatar/%{hash}?s=%{size}&d=identicon"
```
-
Run `sudo gitlab-ctl reconfigure` for changes to take effect.
-
## Default URL for missing images
[Libravatar supports different sets](https://wiki.libravatar.org/api/) of `missing images` for emails not found on the Libravatar service.
@@ -68,7 +65,6 @@ Run `sudo gitlab-ctl reconfigure` for changes to take effect.
In order to use a different set other than `identicon`, replace `&d=identicon` portion of the URL with another supported set.
For example, you can use `retro` set in which case the URL would look like: `plain_url: "http://cdn.libravatar.org/avatar/%{hash}?s=%{size}&d=retro"`
-
## Usage examples
#### For Microsoft Office 365
diff --git a/doc/customization/system_header_and_footer_messages.md b/doc/customization/system_header_and_footer_messages.md
new file mode 100644
index 00000000000..9d6931c730d
--- /dev/null
+++ b/doc/customization/system_header_and_footer_messages.md
@@ -0,0 +1,22 @@
+# Adding a system message to every page
+
+> [Introduced][ee-1283] in [GitLab Premium][eep] 10.7.
+
+Navigate to the **Admin** area and go to the **Appearance** page.
+
+Under **System header and footer** insert your header message and/or footer message.
+Both background and font color of the header and footer are customizable.
+
+You can also apply the header and footer messages to gitlab emails,
+by checking the **Enable header and footer in emails** checkbox.
+Note that color settings will only be applied within the app interface and not to emails
+
+![appearance](system_header_and_footer_messages/appearance.png)
+
+After saving, all GitLab pages will contain the custom system header and/or footer messages:
+
+![custom_header_footer](system_header_and_footer_messages/custom_header_footer.png)
+
+The GitLab sign in page will also show the header and the footer messages:
+
+![sign_up_custom_header_and_footer](system_header_and_footer_messages/sign_up_custom_header_and_footer.png)
diff --git a/doc/customization/system_header_and_footer_messages/appearance.png b/doc/customization/system_header_and_footer_messages/appearance.png
new file mode 100644
index 00000000000..fd315bb6c07
--- /dev/null
+++ b/doc/customization/system_header_and_footer_messages/appearance.png
Binary files differ
diff --git a/doc/customization/system_header_and_footer_messages/custom_header_footer.png b/doc/customization/system_header_and_footer_messages/custom_header_footer.png
new file mode 100644
index 00000000000..691ca8a3484
--- /dev/null
+++ b/doc/customization/system_header_and_footer_messages/custom_header_footer.png
Binary files differ
diff --git a/doc/customization/system_header_and_footer_messages/sign_up_custom_header_and_footer.png b/doc/customization/system_header_and_footer_messages/sign_up_custom_header_and_footer.png
new file mode 100644
index 00000000000..a9c59bc9f62
--- /dev/null
+++ b/doc/customization/system_header_and_footer_messages/sign_up_custom_header_and_footer.png
Binary files differ
diff --git a/doc/customization/welcome_message.md b/doc/customization/welcome_message.md
index 0aef0bf5abb..9194f847cdf 100644
--- a/doc/customization/welcome_message.md
+++ b/doc/customization/welcome_message.md
@@ -1,12 +1 @@
-# Customize the complete sign-in page
-
-Please see [Branded login page](branded_login_page.md)
-
-# Add a welcome message to the sign-in page (GitLab Community Edition)
-
-It is possible to add a markdown-formatted welcome message to your GitLab
-sign-in page. Users of GitLab Enterprise Edition should use the [branded login
-page feature](branded_login_page.md) instead.
-
-The welcome message (extra_sign_in_text) can now be set/changed in the Admin UI.
-Admin area > Settings
+This document was moved to [another location](branded_login_page.md).
diff --git a/doc/development/README.md b/doc/development/README.md
index d5829e31343..13646cbfe48 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -49,6 +49,7 @@ description: 'Learn how to contribute to GitLab.'
- [Working with the GitHub importer](github_importer.md)
- [Import/Export development documentation](import_export.md)
- [Working with Merge Request diffs](diffs.md)
+- [Kubernetes integration guidelines](kubernetes.md)
- [Permissions](permissions.md)
- [Prometheus metrics](prometheus_metrics.md)
- [Guidelines for reusing abstractions](reusing_abstractions.md)
diff --git a/doc/development/api_graphql_styleguide.md b/doc/development/api_graphql_styleguide.md
index 95722c027ba..501092ff2aa 100644
--- a/doc/development/api_graphql_styleguide.md
+++ b/doc/development/api_graphql_styleguide.md
@@ -12,24 +12,34 @@ add a `HTTP_PRIVATE_TOKEN` header.
### Authorization
Fields can be authorized using the same abilities used in the Rails
-app. This can be done using the `authorize` helper:
+app. This can be done by supplying the `authorize` option:
```ruby
module Types
class QueryType < BaseObject
graphql_name 'Query'
- field :project, Types::ProjectType, null: true, resolver: Resolvers::ProjectResolver do
- authorize :read_project
- end
+ field :project, Types::ProjectType, null: true, resolver: Resolvers::ProjectResolver, authorize: :read_project
end
+end
+```
+
+Fields can be authorized against multiple abilities, in which case all
+ability checks must pass. This requires explicitly passing a block to `field`:
+
+```ruby
+field :project, Types::ProjectType, null: true, resolver: Resolvers::ProjectResolver do
+ authorize [:read_project, :another_ability]
+end
```
The object found by the resolve call is used for authorization.
-This works for authorizing a single record, for authorizing
-collections, we should only load what the currently authenticated user
-is allowed to view. Preferably we use our existing finders for that.
+TIP: **Tip:**
+When authorizing collections, try to load only what the currently
+authenticated user is allowed to view with our existing finders first.
+This minimizes database queries and unnecessary authorization checks of
+the loaded records.
## Types
diff --git a/doc/development/architecture.md b/doc/development/architecture.md
index e65c5f05505..e22552fd3a3 100644
--- a/doc/development/architecture.md
+++ b/doc/development/architecture.md
@@ -35,7 +35,7 @@ run: redis: (pid 30560) 14274s; run: log: (pid 13807) 2432047s
run: redis-exporter: (pid 30946) 14205s; run: log: (pid 13869) 2432045s
run: sidekiq: (pid 30953) 14205s; run: log: (pid 13810) 2432047s
run: unicorn: (pid 30960) 14204s; run: log: (pid 13809) 2432047s
-```
+```
### Layers
@@ -51,19 +51,19 @@ GitLab can be considered to have two layers from a process perspective:
- Omnibus configuration options
- Layer: Monitoring
-[Alert manager](https://prometheus.io/docs/alerting/alertmanager/) is a tool provided by prometheus that _"handles alerts sent by client applications such as the Prometheus server. It takes care of deduplicating, grouping, and routing them to the correct receiver integration such as email, PagerDuty, or OpsGenie. It also takes care of silencing and inhibition of alerts."_ You can read more in [issue gitlab-ce#45740](https://gitlab.com/gitlab-org/gitlab-ce/issues/45740) about what we will be alerting on.
+[Alert manager](https://prometheus.io/docs/alerting/alertmanager/) is a tool provided by prometheus that _"handles alerts sent by client applications such as the Prometheus server. It takes care of deduplicating, grouping, and routing them to the correct receiver integration such as email, PagerDuty, or OpsGenie. It also takes care of silencing and inhibition of alerts."_ You can read more in [issue gitlab-ce#45740](https://gitlab.com/gitlab-org/gitlab-ce/issues/45740) about what we will be alerting on.
### gitaly
-- [Omnibus configuration options](https://gitlab.com/gitlab-org/gitaly/tree/master/doc/configuration)
+- [Omnibus configuration options](https://gitlab.com/gitlab-org/gitaly/tree/master/doc/configuration)
- Layer: Core Service (Data)
-Gitaly is a service designed by GitLab to remove our need for NFS for Git storage in distributed deployments of GitLab. (Think GitLab.com or High Availablity Deployments) As of 11.3.0, This service handles all Git level access in GitLab. You can read more about the project [in the project's readme](https://gitlab.com/gitlab-org/gitaly).
+Gitaly is a service designed by GitLab to remove our need for NFS for Git storage in distributed deployments of GitLab (Think GitLab.com or High Availability Deployments). As of 11.3.0, this service handles all Git level access in GitLab. You can read more about the project [in the project's readme](https://gitlab.com/gitlab-org/gitaly).
### gitlab-monitor
- Omnibus configuration options
-- Layer: Monitoring
+- Layer: Monitoring
GitLab Monitor is a process disigned in house that allows us to export metrics about GitLab application internals to prometheus. You can read more [in the project's readme](https://gitlab.com/gitlab-org/gitlab-monitor)
@@ -100,14 +100,14 @@ Nginx as an ingress port for all HTTP requests and routes them to the approriate
- [Omnibus configuration options](https://docs.gitlab.com/ee/administration/monitoring/prometheus/postgres_exporter.html)
- Layer: Monitoring
-[Postgres-exporter](https://github.com/wrouesnel/postgres_exporter) is the community provided Prometheus exporter that will deliver data about Postgres to prometheus for use in Grafana Dashboards.
+[Postgres-exporter](https://github.com/wrouesnel/postgres_exporter) is the community provided Prometheus exporter that will deliver data about Postgres to prometheus for use in Grafana Dashboards.
### postgresql
- [Omnibus configuration options](https://docs.gitlab.com/omnibus/settings/database.html)
- Layer: Core Service (Data)
-GitLab packages the popular Database to provide storage for Application meta data and user information.
+GitLab packages the popular Database to provide storage for Application meta data and user information.
### prometheus
@@ -121,11 +121,11 @@ Prometheus is a time-series tool that helps GitLab administrators expose metrics
- [Omnibus configuration options](https://docs.gitlab.com/omnibus/settings/redis.html)
- Layer: Core Service (Data)
-Redis is packaged to provide a place to store:
+Redis is packaged to provide a place to store:
- session data
- temporary cache information
-- background job queues.
+- background job queues.
### redis-exporter
@@ -146,7 +146,7 @@ Sidekiq is a Ruby background job processor that pulls jobs from the redis queue
- [Omnibus configuration options](https://docs.gitlab.com/omnibus/settings/unicorn.html)
- Layer: Core Service (Processor)
-[Unicorn](https://bogomips.org/unicorn/) is a Ruby application server that is used to run the core Rails Application that provides the user facing features in GitLab. Often process output you will see this as `bundle` or `config.ru` depending on the GitLab version.
+[Unicorn](https://bogomips.org/unicorn/) is a Ruby application server that is used to run the core Rails Application that provides the user facing features in GitLab. Often process output you will see this as `bundle` or `config.ru` depending on the GitLab version.
### Additional Processes
@@ -174,8 +174,7 @@ When making a request to an HTTP Endpoint (Think `/users/sign_in`) the request w
- nginx - Acts as our first line reverse proxy
- gitlab-workhorse - This determines if it needs to go to the Rails application or somewhere else to reduce load on unicorn.
- unicorn - Since this is a web request, and it needs to access the application it will go to Unicorn.
-- Postgres/Gitaly/Redis - Depending on the type of request, it may hit these services to store or retreive data.
-
+- Postgres/Gitaly/Redis - Depending on the type of request, it may hit these services to store or retrieve data.
### GitLab Git Request Cycle
diff --git a/doc/development/automatic_ce_ee_merge.md b/doc/development/automatic_ce_ee_merge.md
index fed772b9240..3ca841353e6 100644
--- a/doc/development/automatic_ce_ee_merge.md
+++ b/doc/development/automatic_ce_ee_merge.md
@@ -115,16 +115,16 @@ Now, every time you create an MR for CE and EE:
1. Continue cherry-picking: `git cherry-pick --continue`
1. Push to EE: `git push origin branch-example-ee`
1. Create the EE-equivalent MR and link to the CE MR from the
-description "Ports [CE-MR-LINK] to EE"
+ description "Ports [CE-MR-LINK] to EE"
1. Once all the jobs are passing in both CE and EE, you've addressed the
-feedback from your own team, and got them approved, the merge requests can be merged.
+ feedback from your own team, and got them approved, the merge requests can be merged.
1. When both MRs are ready, the EE merge request will be merged first, and the
-CE-equivalent will be merged next.
+ CE-equivalent will be merged next.
**Important notes:**
- The commit SHA can be easily found from the GitLab UI. From a merge request,
-open the tab **Commits** and click the copy icon to copy the commit SHA.
+ open the tab **Commits** and click the copy icon to copy the commit SHA.
- To cherry-pick a **commit range**, such as [A > B > C > D] use:
```shell
@@ -140,7 +140,7 @@ open the tab **Commits** and click the copy icon to copy the commit SHA.
```
- To cherry-pick a **merge commit**, use the flag `-m 1`. For example, suppose that the
-merge commit SHA is `138f5e2f20289bb376caffa0303adb0cac859ce1`:
+ merge commit SHA is `138f5e2f20289bb376caffa0303adb0cac859ce1`:
```shell
git cherry-pick -m 1 138f5e2f20289bb376caffa0303adb0cac859ce1
@@ -163,12 +163,12 @@ merge commit SHA is `138f5e2f20289bb376caffa0303adb0cac859ce1`:
commits and you want to cherry-pick all but the merge commit.
- If you push more commits to the CE branch, you can safely repeat the procedure
-to cherry-pick them to the EE-equivalent branch. You can do that as many times as
-necessary, using the same CE and EE branches.
+ to cherry-pick them to the EE-equivalent branch. You can do that as many times as
+ necessary, using the same CE and EE branches.
- If you submitted the merge request to the CE repo and the `ee-compat-check` job passed,
-you are not required to submit the EE-equivalent MR, but it's still recommended. If the
-job failed, you are required to submit the EE MR so that you can fix the conflicts in EE
-before merging your changes into CE.
+ you are not required to submit the EE-equivalent MR, but it's still recommended. If the
+ job failed, you are required to submit the EE MR so that you can fix the conflicts in EE
+ before merging your changes into CE.
## FAQ
diff --git a/doc/development/code_review.md b/doc/development/code_review.md
index 25ea2211b64..f115045dbb7 100644
--- a/doc/development/code_review.md
+++ b/doc/development/code_review.md
@@ -23,6 +23,11 @@ one of the [Merge request coaches][team].
If you need assistance with security scans or comments, feel free to include the
Security Team (`@gitlab-com/gl-security`) in the review.
+The `danger-review` CI job will randomly pick a reviewer and a maintainer for
+each area of the codebase that your merge request seems to touch. It only makes
+recommendations - feel free to override it if you think someone else is a better
+fit!
+
Depending on the areas your merge request touches, it must be **approved** by one
or more [maintainers](https://about.gitlab.com/handbook/engineering/workflow/code-review/#maintainer):
@@ -132,6 +137,14 @@ If a developer who happens to also be a maintainer was involved in a merge reque
as a domain expert and/or reviewer, it is recommended that they are not also picked
as the maintainer to ultimately approve and merge it.
+Try to review in a timely manner; doing so allows everyone involved in the merge
+request to iterate faster as the context is fresh in memory. Further, this
+improves contributors' experiences significantly. Reviewers should aim to review
+within two working days from the date they were assigned the merge request. If
+you don't think you'll be able to review a merge request within that time, let
+the author know as soon as possible. When the author of the merge request has not
+heard anything after two days, a new reviewer should be assigned.
+
Maintainers should check before merging if the merge request is approved by the
required approvers.
@@ -190,7 +203,10 @@ first time.
- Extract unrelated changes and refactorings into future merge requests/issues.
- Seek to understand the reviewer's perspective.
- Try to respond to every comment.
-- Let the reviewer select the "Resolve discussion" buttons.
+- The merge request author resolves only the discussions they have fully
+ addressed. If there's an open reply, an open discussion, a suggestion,
+ a question, or anything else, the discussion should be left to be resolved
+ by the reviewer.
- Push commits based on earlier rounds of feedback as isolated commits to the
branch. Do not squash until the branch is ready to merge. Reviewers should be
able to read individual updates based on their earlier feedback.
diff --git a/doc/development/contributing/index.md b/doc/development/contributing/index.md
index 7ac846e4c4d..f7a0fdbeb40 100644
--- a/doc/development/contributing/index.md
+++ b/doc/development/contributing/index.md
@@ -4,7 +4,7 @@ Thank you for your interest in contributing to GitLab. This guide details how
to contribute to GitLab in a way that is easy for everyone.
We want to create a welcoming environment for everyone who is interested in contributing.
-Please visit our [Code of Conduct page](https://about.gitlab.com/contributing/code-of-conduct) to learn more about our committment to an open and welcoming environment.
+Please visit our [Code of Conduct page](https://about.gitlab.com/contributing/code-of-conduct) to learn more about our commitment to an open and welcoming environment.
For a first-time step-by-step guide to the contribution process, please see
["Contributing to GitLab"](https://about.gitlab.com/contributing/).
diff --git a/doc/development/contributing/issue_workflow.md b/doc/development/contributing/issue_workflow.md
index c5344139ab4..4c53643ed9c 100644
--- a/doc/development/contributing/issue_workflow.md
+++ b/doc/development/contributing/issue_workflow.md
@@ -199,7 +199,7 @@ different way.
We add the ~"Accepting merge requests" label to:
- Low priority ~bug issues (i.e. we do not add it to the bugs that we want to
-solve in the ~"Next Patch Release")
+ solve in the ~"Next Patch Release")
- Small ~feature
- Small ~"technical debt" issues
@@ -300,17 +300,17 @@ below will make it easy to manage this, without unnecessary overhead.
1. Set weight for any issue at the earliest possible convenience
1. If you don't agree with a set weight, discuss with other developers until
-consensus is reached about the weight
+ consensus is reached about the weight
1. Issue weights are an abstract measurement of complexity of the issue. Do not
-relate issue weight directly to time. This is called [anchoring](https://en.wikipedia.org/wiki/Anchoring)
-and something you want to avoid.
+ relate issue weight directly to time. This is called [anchoring](https://en.wikipedia.org/wiki/Anchoring)
+ and something you want to avoid.
1. Something that has a weight of 1 (or no weight) is really small and simple.
-Something that is 9 is rewriting a large fundamental part of GitLab,
-which might lead to many hard problems to solve. Changing some text in GitLab
-is probably 1, adding a new Git Hook maybe 4 or 5, big features 7-9.
+ Something that is 9 is rewriting a large fundamental part of GitLab,
+ which might lead to many hard problems to solve. Changing some text in GitLab
+ is probably 1, adding a new Git Hook maybe 4 or 5, big features 7-9.
1. If something is very large, it should probably be split up in multiple
-issues or chunks. You can simply not set the weight of a parent issue and set
-weights to children issues.
+ issues or chunks. You can simply not set the weight of a parent issue and set
+ weights to children issues.
## Regression issues
diff --git a/doc/development/database_debugging.md b/doc/development/database_debugging.md
index b2c804b2ff0..f00c5ccb9e9 100644
--- a/doc/development/database_debugging.md
+++ b/doc/development/database_debugging.md
@@ -13,7 +13,6 @@ Available `RAILS_ENV`
- `development` (this is your main GDK db)
- `test` (used for tests like rspec)
-
## Nuke everything and start over
If you just want to delete everything and start over with an empty DB (~1 minute):
@@ -36,7 +35,6 @@ If your test DB is giving you problems, it is safe to nuke it because it doesn't
- `bundle exec rake db:migrate:up VERSION=20170926203418 RAILS_ENV=development`: Set up a migration
- `bundle exec rake db:migrate:redo VERSION=20170926203418 RAILS_ENV=development`: Re-run a specific migration
-
## Manually access the database
Access the database via one of these commands (they all get you to the same place)
@@ -54,7 +52,6 @@ bundle exec rails db RAILS_ENV=development
- `SELECT * FROM schema_migrations WHERE version = '20170926203418';`: Check if a migration was run
- `DELETE FROM schema_migrations WHERE version = '20170926203418';`: Manually remove a migration
-
## FAQ
### `ActiveRecord::PendingMigrationError` with Spring
diff --git a/doc/development/diffs.md b/doc/development/diffs.md
index 43fc125c21d..56e869c21f8 100644
--- a/doc/development/diffs.md
+++ b/doc/development/diffs.md
@@ -59,28 +59,24 @@ Gitlab::Git::DiffCollection.collection_limits[:safe_max_files] = Gitlab::Git::Di
File diffs will be collapsed (but be expandable) if 100 files have already been rendered.
-
```ruby
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
```
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
```
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
```
@@ -129,4 +125,3 @@ Diff Viewers, which can be found on `models/diff_viewer/*` are classes used to m
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/distributed_tracing.md b/doc/development/distributed_tracing.md
new file mode 100644
index 00000000000..038e3de10d7
--- /dev/null
+++ b/doc/development/distributed_tracing.md
@@ -0,0 +1,182 @@
+# Distributed Tracing - development guidelines
+
+GitLab is instrumented for distributed tracing.
+
+According to [Open Tracing](https://opentracing.io/docs/overview/what-is-tracing/):
+
+> Distributed tracing, also called distributed request tracing, is a method used to profile and
+> monitor applications, especially those built using a microservices architecture. Distributed
+> tracing helps to pinpoint where failures occur and what causes poor performance.
+
+Distributed tracing is especially helpful in understanding the lifecycle of a request as it passes
+through the different components of the GitLab application. At present, Workhorse, Rails, Sidekiq,
+and Gitaly support tracing instrumentation.
+
+Distributed tracing adds minimal overhead when disabled, but imposes only small overhead when
+enabled and is therefore capable in any environment, including production. For this reason, it can
+be useful in diagnosing production issues, particularly performance problems.
+
+## Enabling distributed tracing
+
+GitLab uses the `GITLAB_TRACING` environment variable to configure distributed tracing. The same
+configuration is used for all components (e.g., Workhorse, Rails, etc).
+
+When `GITLAB_TRACING` is not set, the application will not be instrumented, meaning that there is
+no overhead at all.
+
+To enable `GITLAB_TRACING`, a valid _"configuration-string"_ value should be set, with a URL-like
+form:
+
+```console
+GITLAB_TRACING=opentracing://<driver>?<param_name>=<param_value>&<param_name_2>=<param_value_2>
+```
+
+In this example, we have the following hypothetical values:
+
+- `driver`: the driver. [GitLab supports
+ `jaeger`](https://docs.gitlab.com/ee/user/project/operations/tracing.html). In future, other
+ tracing implementations may also be supported.
+- `param_name`, `param_value`: these are driver specific configuration values. Configuration
+ parameters for Jaeger are documented [further on in this
+ document](#2-configure-the-gitlab_tracing-environment-variable) they should be URL encoded.
+ Multiple values should be separated by `&` characters like a URL.
+
+## Using Jaeger in the GitLab Development Kit
+
+The first tracing implementation that GitLab supports is Jaeger, and the [GitLab Development
+Kit](https://gitlab.com/gitlab-org/gitlab-development-kit/) supports distributed tracing with
+Jaeger out-of-the-box.
+
+The easiest way to access tracing from a GDK environment is through the
+[performance-bar](../administration/monitoring/performance/performance_bar.md). This can be shown
+by typing `p` `b` in the browser window.
+
+![Jaeger Search UI](img/distributed_tracing_performance_bar.png)
+
+Once the performance bar is enabled, click on the **Trace** link in the performance bar to go to
+the Jaeger UI.
+
+The Jaeger search UI will return a query for the `Correlation-ID` of the current request. Normally,
+this search should return a single trace result. Clicking this result will show the detail of the
+trace in a hierarchical time-line.
+
+![Jaeger Search UI](img/distributed_tracing_jaeger_ui.png)
+
+## Using Jaeger without the GitLab Developer Kit
+
+Distributed Tracing can be enabled in non-GDK development environments as well as production or
+staging environments, for troubleshooting. Please note that at this time, this functionality is
+experimental, and not supported in production environments at present. In this first release, it is intended to be
+used for debugging in development environments only.
+
+Jaeger tracing can be enabled through a three-step process:
+
+1. [Start Jaeger](#1-start-jaeger).
+1. [Configure the `GITLAB_TRACING` environment variable](#2-configure-the-gitlab_tracing-environment-variable).
+1. [Start the GitLab application](#3-start-the-gitlab-application).
+1. [Go to the Jaeger Search UI in your browser](#4-open-the-jaeger-search-ui).
+
+### 1. Start Jaeger
+
+Jaeger has many configuration options, but is very easy to start in an "all-in-one" mode which uses
+memory for trace storage (and is therefore non-persistent). The main advantage of "all-in-one" mode
+being ease of use.
+
+For more detailed configuration options, refer to the [Jaeger
+documentation](https://www.jaegertracing.io/docs/1.9/getting-started/).
+
+#### Using Docker
+
+If you have Docker available, the easier approach to running the Jaeger all-in-one is through
+Docker, using the following command:
+
+```console
+$ docker run \
+ --rm \
+ -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
+ -p 5775:5775/udp \
+ -p 6831:6831/udp \
+ -p 6832:6832/udp \
+ -p 5778:5778 \
+ -p 16686:16686 \
+ -p 14268:14268 \
+ -p 9411:9411 \
+ jaegertracing/all-in-one:latest
+```
+
+#### Using the Jaeger process
+
+Without Docker, the all-in-one process is still easy to setup.
+
+1. Download the [latest Jaeger release](https://github.com/jaegertracing/jaeger/releases) for your
+ platform.
+1. Extract the archive and run the `bin/all-in-one` process.
+
+This should start the process with the default listening ports.
+
+### 2. Configure the `GITLAB_TRACING` environment variable
+
+Once you have Jaeger running, you'll need to configure the `GITLAB_TRACING` variable with the
+appropriate configuration string.
+
+**TL;DR:** If you are running everything on the same host, use the following value:
+
+```console
+$ export GITLAB_TRACING="opentracing://jaeger?http_endpoint=http%3A%2F%2Flocalhost%3A14268%2Fapi%2Ftraces&sampler=const&sampler_param=1"
+```
+
+This configuration string uses the Jaeger driver `opentracing://jaeger` with the following options:
+
+| Name | Value | Description |
+|------|-------|-------------|
+| `http_endpoint` | `http://localhost:14268/api/traces` | Configures Jaeger to send trace information to the HTTP endpoint running on `http://localhost:14268/`. Alternatively, the `upd_endpoint` can be used. |
+| `sampler` | `const` | Configures Jaeger to use the constant sampler (either on or off). |
+| `sampler_param` | `1` | Configures the `const` sampler to sample _all_ traces. Using `0` would sample _no_ traces. |
+
+**Other parameter values are also possible:**
+
+| Name | Example | Description |
+|------|-------|-------------|
+| `udp_endpoint` | `localhost:6831` | This is the default. Configures Jaeger to send trace information to the UDP listener on port `6831` using compact thrift protocol. Note that we've experienced some issues with the [Jaeger Client for Ruby](https://github.com/salemove/jaeger-client-ruby) when using this protocol. |
+| `sampler` | `probabalistic` | Configures Jaeger to use a probabilistic random sampler. The rate of samples is configured by the `sampler_param` value. |
+| `sampler_param` | `0.01` | Use a ratio of `0.01` to configure the `probabalistic` sampler to randomly sample _1%_ of traces. |
+
+NOTE: **Note:**
+The same `GITLAB_TRACING` value should to be configured in the environment
+variables for all GitLab processes, including Workhorse, Gitaly, Rails, and Sidekiq.
+
+### 3. Start the GitLab application
+
+Once the `GITLAB_TRACING` environment variable is exported to all GitLab services, start the
+application.
+
+When `GITLAB_TRACING` is configured properly, the application will log this on startup:
+
+```console
+13:41:53 gitlab-workhorse.1 | 2019/02/12 13:41:53 Tracing enabled
+...
+13:41:54 gitaly.1 | 2019/02/12 13:41:54 Tracing enabled
+...
+```
+
+If `GITLAB_TRACING` is not configured correctly, this will also be logged:
+
+```console
+13:43:45 gitaly.1 | 2019/02/12 13:43:45 skipping tracing configuration step: tracer: unable to load driver mytracer
+```
+
+By default, GitLab ships with the Jaeger tracer, but other tracers can be included at compile time.
+Details of how this can be done are included in the [LabKit tracing
+documentation](https://godoc.org/gitlab.com/gitlab-org/labkit/tracing).
+
+If no log messages about tracing are emitted, the `GITLAB_TRACING` environment variable is likely
+not set.
+
+### 4. Open the Jaeger Search UI
+
+By default, the Jaeger search UI is available at <http://localhost:16686/search>.
+
+TIP: **Tip:**
+Don't forget that you will need to generate traces by using the application before
+they appear in the Jaeger UI.
+
diff --git a/doc/development/documentation/feature-change-workflow.md b/doc/development/documentation/feature-change-workflow.md
index b5b325683a3..3f31fe5ca19 100644
--- a/doc/development/documentation/feature-change-workflow.md
+++ b/doc/development/documentation/feature-change-workflow.md
@@ -2,111 +2,178 @@
description: How to add docs for new or enhanced GitLab features.
---
-# Documentation process at GitLab
+# Documentation process for feature changes
At GitLab, developers contribute new or updated documentation along with their code, but product managers and technical writers also have essential roles in the process.
- **Developers**: Author/update documentation in the same MR as their code, and
-merge it by the feature freeze for the assigned milestone. Request technical writer
-assistance if needed.
+ merge it by the feature freeze for the assigned milestone. Request technical writer
+ assistance if needed. Other developers typically act as reviewers.
- **Product Managers** (PMs): In the issue for all new and enhanced features,
-confirm the documentation requirements, plus the mentioned feature description
-and use cases, which can be reused in docs. They can bring in a technical
-writer for discussion or help, and can be called upon themselves as a doc reviewer.
+ confirm the documentation requirements, plus the mentioned feature description
+ and use cases, which can be reused in docs. They can bring in a technical
+ writer for discussion or collaboration, and can be called upon themselves as a doc reviewer.
- **Technical Writers**: Review doc requirements in issues, track issues and MRs
-that contain docs changes, help with any questions throughout the authoring/editing process,
-and review all new and updated docs content after it's merged (unless a pre-merge
-review request is made).
+ that contain docs changes, help with any questions throughout the authoring/editing process,
+ work on special projects related to the documentation, and review all new and updated
+ docs content, whether before or after it is merged.
-Beyond this process, any member of the GitLab community can also author documentation
+Beyond this process, any member of the GitLab community can also author or request documentation
improvements that are not associated with a new or changed feature. See the [Documentation improvement workflow](improvement-workflow.md).
## When documentation is required
Documentation must be delivered whenever:
-- A new or enhanced feature is shipped that impacts the user/admin experience
-- There are changes to the UI or API
-- A process, workflow, or previously documented feature is changed
-- A feature is deprecated or removed
+- A new or enhanced feature is shipped that impacts the user/admin experience.
+- There are changes to the UI or API.
+- A process, workflow, or previously documented feature is changed.
+- A feature is deprecated or removed.
+
+For example, a UI restyling that offers no difference in functionality may require
+documentation updates if screenshots are now needed, or need to be updated.
Documentation is not required when a feature is changed on the backend
-only and does not directly affect the way that any user or
-administrator would interact with GitLab. For example, a UI restyling that offers
-no difference in functionality may require documentation updates if screenshots
-are now needed, or need to be updated.
+only and does not directly affect the way that any user or administrator would
+interact with GitLab.
NOTE: **Note:**
When revamping documentation, if unrelated to the feature change, this should be submitted
in its own MR (using the [documentation improvement workflow](improvement-workflow.md))
so that we can ensure the more time-sensitive doc updates are merged with code by the freeze.
+## Documentation requirements in feature issues
+
+Requirements for the documentation of a feature should be included as part of the
+issue for planning that feature, in a Documentation section within the issue description.
+
+This section is provided as part of the Feature Proposal template and should be added
+to the issue if it is not already present.
+
+Anyone can add these details, but the product manager who assigns the issue to a specific release
+milestone will ensure these details are present and finalized by the time of that milestone's kickoff.
+Developers, technical writers, and others may help further refine this plan at any time.
+
+### Details to include
+
+- What concepts and procedures should the docs guide and enable the user to understand or accomplish?
+- To this end, what new page(s) are needed, if any? What pages/subsections need updates? Consider user, admin, and API doc changes and additions.
+- For any guide or instruction set, should it help address a single use case, or be flexible to address a certain range of use cases?
+- Do we need to update a previously recommended workflow? Should we link the new feature from various relevant locations? Consider all ways documentation should be affected.
+- Are there any key terms or task descriptions that should be included so that the docs are found in relevant searches?
+- Include suggested titles of any pages or subsections, if applicable.
+
## Documenting a new or changed feature
To follow a consistent workflow every month, documentation changes
involve the Product Managers, the developer who shipped the feature,
-and the Technical Writing team. Each role is described below.
+and the technical writer for the DevOps stage. Each role is described below.
+
+The Documentation items in the GitLab CE/EE [Feature Proposal issue template](https://gitlab.com/gitlab-org/gitlab-ce/raw/template-improvements-for-documentation/.gitlab/issue_templates/Feature%20proposal.md)
+and default merge request template will assist you with following this process.
+
+### Product Manager role
-### 1. Product Manager's role
+For issues requiring any new or updated documentation, the Product Manager (PM)
+must:
-The Product Manager (PM) should confirm or add the following items in the issue:
+- Add the `Documentation` label.
+- Confirm or add the [documentation requirements](#documentation-requirements).
+- Ensure the issue contains any new or updated feature name, overview/description,
+ and use cases, as required per the [documentation structure and template](structure.md), when applicable.
-- New or updated feature name, overview/description, and use cases, all required per the [Documentation structure and template](structure.md).
-- The documentation requirements for the developer working on the docs.
- - What new page, new subsection of an existing page, or other update to an existing page/subsection is needed.
- - Just one page/section/update or multiple (perhaps there's an end user and admin change needing docs, or we need to update a previously recommended workflow, or we want to link the new feature from various places; consider and mention all ways documentation should be affected.
- - Suggested title of any page or subsection, if applicable.
-- Label the issue with `Documentation` and `docs:P1` in addition to the `Deliverable` label and correct milestone.
+Everyone is encouraged to draft the requirements in the issue, but a product manager will
+do the following:
-Anyone is welcome to draft the items above in the issue, but a product manager must review and update them whenever the issue is assigned a specific milestone.
+- When the issue is assigned a release milestone, review and update the Documentation details.
+- By the kickoff, finalizie the Documentation details.
-### 2. Developer's role
+### Developer and maintainer roles
+
+#### Authoring
As a developer, you must ship the documentation with the code of the feature that
you are creating or updating. The documentation is an essential part of the product.
+Technical writers are happy to help, as requested and planned on an issue-by-issue basis.
+
+Follow the process below unless otherwise agreed with the product manager and technical writer for a given issue:
-- New and edited docs should be included in the MR introducing the code, and planned
-in the issue that proposed the feature. However, if the new or changed doc requires
-extensive collaboration or conversation, a separate, linked issue can be used for the planning process.
+- Include any new and edited docs in the MR introducing the code.
+- Use the Documentation requirements confirmed by the Product Manager in the
+ issue and discuss any further doc plans or ideas as needed.
+ - If the new or changed doc requires extensive collaboration or conversation, a separate,
+ linked issue can be used for the planning process.
+ - We are trying to avoid using a separate MR, so that the docs stay with the code, but the
+ Technical Writing team is interested in discussing any potential exceptions that may be suggested.
- Use the [Documentation guidelines](index.md), as well as other resources linked from there,
-including the [Structure and template](structure.md) page, [Style Guide](styleguide.md), and [Markdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/).
+ including the Documentation [Structure and template](structure.md) page, [Style Guide](styleguide.md), and [Markdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/).
- If you need any help to choose the correct place for a doc, discuss a documentation
-idea or outline, or request any other help, ping the Technical Writer for the relevant
-[DevOps stage](https://about.gitlab.com/handbook/product/categories/#devops-stages)
-in your issue or MR, or write within `#docs` on the GitLab Slack.
+ idea or outline, or request any other help, ping the Technical Writer for the relevant
+ [DevOps stage](https://about.gitlab.com/handbook/product/categories/#devops-stages)
+ in your issue or MR, or write within `#docs` on the GitLab Slack.
- The docs must be merged with the code **by the feature freeze date**, otherwise
-- the feature cannot be included with the release.<!-- TODO: Policy/process for feature-flagged issues -->
+ the feature cannot be included with the release. A policy for documenting feature-flagged
+ issues is forthcoming and you are welcome to join the [discussion](https://gitlab.com/gitlab-org/gitlab-ce/issues/56813).
+
+#### Reviews and merging
+
+All reviewers can help ensure accuracy, clarity, completeness, and adherence to the plans in the issue, as well as the [Documentation Guidelines](https://docs.gitlab.com/ee/development/documentation/) and [Style Guide](https://docs.gitlab.com/ee/development/documentation/styleguide.html).
+
+- **Prior to merging**, documentation changes committed by the developer must be reviewed by:
+
+ 1. **The code reviewer** for the MR, to confirm accuracy, clarity, and completeness.
+ 1. Optionally: Others involved in the work, such as other devs or the PM.
+ 1. Optionally: The technical writer for the DevOps stage. If not prior to merging, the technical writer will review after the merge.
+ This helps us ensure that the developer has time to merge good content by the freeze, and that it can be further refined by the release, if needed.
+ - To decide whether to request this review before the merge, consider the amount of time left before the code freeze, the size of the change,
+ and your degree of confidence in having users of an RC use your docs as written.
+ - Pre-merge tech writer reviews should be most common when the code is complete well in advance of the freeze and/or for larger documentation changes.
+ - You can request a review and if there is not sufficient time to complete it prior to the freeze,
+ the maintainer can merge the current doc changes (if complete) and create a follow-up doc review issue.
+ - The technical writer can also help decide what docs to merge before the freeze and whether to work on further changes in a follow up MR.
+ - **To request a pre-merge technical writer review**, assign the writer listed for the applicable [DevOps stage](https://about.gitlab.com/handbook/product/categories/#devops-stages).
+ - **To request a post-merge technical writer review**, [create an issue for one using the Doc Review template](https://gitlab.com/gitlab-org/gitlab-ce/issues/new?issuable_template=Doc%20Review) and link it from the MR that makes the doc change.
+ 1. **The maintainer** who is assigned to merge the MR, to verify clarity, completeness, and quality, to the best of their ability.
+
+- Upon merging, if a technical writer review has not been performed and there is not yet a linked issue for a follow-up review, the maintainer should [create an issue using the Doc Review template](https://gitlab.com/gitlab-org/gitlab-ce/issues/new?issuable_template=Doc%20Review), link it from the MR, and
+ mention the original MR author in the new issue. Alternatively, the mainitainer can ask the MR author to create and link this issue before the MR is merged.
+
+- After merging, documentation changes are reviewed by:
+
+ 1. The technical writer--**if** their review was not performed prior to the merge.
+ 2. Optionally: by the PM (for accuracy and to ensure it's consistent with the vision for how the product will be used).
+ Any party can raise the item to the PM for review at any point: the dev, the technical writer, or the PM, who can request/plan a review at the outset.
+
+### Technical Writer role
+
+#### Planning
+
+- The technical writer monitors the documentation needs of issues assigned to the current and next milestone
+ for their DevOps stage(s), and participates in any needed discussion on docs planning and requirements refinement
+ with the dev, PM, and others.
+- The technical writer will review these requirements again upon the kickoff and provide feedback, as needed.
+ This is not a blocking review and developers should not wait to work on docs.
-Prior to merge, documentation changes commited by the developer must be reviewed by:
-* the person reviewing the code and merging the MR.
-* optionally: others involved in the work (such as other devs, the PM, or a technical writer), if requested.
+#### Collaboration
-After merging, documentation changing are reviewed by:
-* a technical writer (for clarity, structure, grammar, etc).
-* optionally: by the PM (for accuracy and to ensure it's consistent with the vision for how the product will be used).
-Any party can raise the item to the PM for review at any point: the dev, the technical writer, or the PM, who can request/plan a review at the outset.
+By default, the developer will work on documentation changes independently, but
+the developer, PM, or technicial writer can propose a broader collaboration for
+any given issue.
-### 3. Technical Writer's role
+Additionally, technical writers are available for questions at any time.
-**Planning**
-- Once an issue contains a Documentation label and an upcoming milestone, a
-technical writer reviews the listed documentation requirements, which should have
-already been reviewed by the PM. (These are non-blocking reviews; developers should
-not wait to work on docs.)
-- Monitor the documentation needs of issues assigned to the current and next milestone,
-and participate in any needed discussion on docs planning with the dev, PM, and others.
+#### Review
-**Review**
- Techncial writers provide non-blocking reviews of all documentation changes,
-typically after the change is merged. However, if the docs are ready in the MR while
-we are awaiting other work in order to merge, the technical writer's review can commence early.
+ before or after the change is merged. However, if the docs are ready in the MR while
+ there's time before the freeze, the technical writer's review can commence early, on request.
- The technical writer will confirm that the doc is clear, grammatically correct,
-and discoverable, while avoiding redundancy, bad file locations, typos, broken links,
-etc. The technical writer will review the documentation for the following, which
-the developer and code reviewer should have already made a good-faith effort to ensure:
+ and discoverable, while avoiding redundancy, bad file locations, typos, broken links,
+ etc. The technical writer will review the documentation for the following, which
+ the developer and code reviewer should have already made a good-faith effort to ensure:
- Clarity.
- - Relevance (make sure the content is appropriate given the impact of the feature).
- - Location (make sure the doc is in the correct dir and has the correct name).
+ - Adherence to the plans and goals in the issue.
+ - Location (make sure the docs are in the correct directorkes and has the correct name).
- Syntax, typos, and broken links.
- Improvements to the content.
- - Accordance to the [Documentation Style Guide](styleguide.md) and [structure/template](structure.md).
+ - Accordance with the [Documentation Style Guide](styleguide.md), and [Structure and Template](structure.md) doc.
diff --git a/doc/development/documentation/improvement-workflow.md b/doc/development/documentation/improvement-workflow.md
index ef6392c6f7f..a12c3d5ea7b 100644
--- a/doc/development/documentation/improvement-workflow.md
+++ b/doc/development/documentation/improvement-workflow.md
@@ -9,26 +9,30 @@ Anyone can contribute a merge request or create an issue for GitLab's documentat
This page covers the process for any contributions to GitLab's docs that are
not part of feature development. If you are looking for information on updating
GitLab's docs as is required with the development and release of a new feature
-or feature enhancement, see the [feature-change documentation workflow](feature-change-workflow.md).
+or feature enhancement, see the [documentation process for feature changes](feature-change-workflow.md).
## Who updates the docs
Anyone can contribute! You can create a merge request with documentation
when you find errors or other room for improvement in an existing doc, or when you
-have an idea for all-new documentation that would help a GitLab user or admin
-to achieve or improve their DevOps workflows.
+have an idea for all-new documentation that would help a GitLab user or administrator
+to accomplish their work with GitLab.
## How to update the docs
-- Follow the described standards and processes listed on the [GitLab Documentation guidelines](index.md) page,
-including linked resources: the [Structure and template](structure.md) page, [Style Guide](styleguide.md), and [Markdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/).
-- Follow GitLab's [Merge Request Guidelines](../contributing/merge_request_workflow.md#merge-request-guidelines).
-- If you need any help to choose the correct place for a doc, discuss a documentation
+1. Click "Edit this Page" at the bottom of any page on docs.gitlab.com, or navigate to
+ one of the repositories and doc paths listed on the [GitLab Documentation guidelines](index.md) page.
+ Work in a fork if you do not have developer access to the GitLab project.
+1. Follow the described standards and processes listed on that Guidelines page,
+ including the linked resources: the [Structure and template](structure.md) page, [Style Guide](styleguide.md), and [Markdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/).
+1. Follow GitLab's [Merge Request Guidelines](../contributing/merge_request_workflow.md#merge-request-guidelines).
+
+If you need any help to choose the correct place for a doc, discuss a documentation
idea or outline, or request any other help, ping the Technical Writer for the relevant
[DevOps stage](https://about.gitlab.com/handbook/product/categories/#devops-stages)
in your issue or MR, or write within `#docs` if you are a member of GitLab's Slack workspace.
-## Merging
+## Review and merging
Anyone with master access to the affected GitLab project can merge documentation changes.
This person must make a good-faith effort to ensure that the content is clear
@@ -38,12 +42,22 @@ that it meets the [Documentation Guidelines](index.md) and [Style Guide](stylegu
If the author or reviewer has any questions, or would like a techncial writer's review
before merging, mention the writer who is assigned to the relevant [DevOps stage](https://about.gitlab.com/handbook/product/categories/#devops-stages).
-## Technical Writer review
+The process can involve the following parties/phases, and is replicated in the `Documentation` MR template for GitLab CE and EE, to help with following the process.
+
+**1. Primary Reviewer** - Review by a [code reviewer](https://about.gitlab.com/handbook/engineering/projects/) or other appropriate colleague to confirm accuracy, clarity, and completeness. This can be skipped for minor fixes without substantive content changes.
+
+**2. Technical Writer** - Optional - If not requested for this MR, must be scheduled post-merge. To request a pre-merge review, assign the writer listed for the applicable [DevOps stage](https://about.gitlab.com/handbook/product/categories/#devops-stages).
+To request a post-merge review, [create an issue for one using the Doc Review template](https://gitlab.com/gitlab-org/gitlab-ce/issues/new?issuable_template=Doc%20Review) and link it from the MR that makes the doc change.
+
+**3. Maintainer**
-The technical writing team reviews changes after they are merged, unless a prior
-review is requested.
+1. Review by assigned maintainer, who can always request/require the above reviews. Maintainer review can occur before or after a technical writer review.
+2. Ensure a release milestone of the format XX.Y is set. If the freeze for that release has begun, add the label `pick into <XX.Y>` unless this change is not required for the release. In that case, simply change the milestone.
+3. If EE and CE MRs exist, merge the EE MR first, then the CE MR.
+4. After merging, if there has not been a technical writer review and an issue for a follow-up review was not already created and linked from the MR, [create the issue using the Doc Review template](https://gitlab.com/gitlab-org/gitlab-ce/issues/new?issuable_template=Doc%20Review) and link it from the MR.
## Other ways to help
If you have ideas for further documentation resources that would be best
-considered/handled by technical writers, devs, and other SMEs, please create an issue.
+considered/handled by technical writers, devs, and other SMEs, please [create an issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/new?issuable_template=Documentation)
+using the Documentation template.
diff --git a/doc/development/documentation/index.md b/doc/development/documentation/index.md
index 287a472d0d8..652fe3ea711 100644
--- a/doc/development/documentation/index.md
+++ b/doc/development/documentation/index.md
@@ -10,8 +10,8 @@ In addition to this page, the following resources to help craft and contribute d
- [Style Guide](styleguide.md) - What belongs in the docs, language guidelines, and more.
- [Structure and template](structure.md) - Learn the typical parts of a doc page and how to write each one.
-- [Workflow](workflow.md) - A landing page for our key workflows:
- - [Feature-change documentation workflow](feature-change-workflow.md) - Adding required documentation when developing a GitLab feature.
+- [Workflows](workflow.md) - A landing page for our key workflows:
+ - [Documentation process for feature changes](feature-change-workflow.md) - Adding required documentation when developing a GitLab feature.
- [Documentation improvement workflow](improvement-workflow.md) - New content not associated with a new feature.
- [Markdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/) - A reference for the markdown implementation used by GitLab's documentation site and about.gitlab.com.
- [Site architecture](site_architecture/index.md) - How docs.gitlab.com is built.
diff --git a/doc/development/documentation/site_architecture/global_nav.md b/doc/development/documentation/site_architecture/global_nav.md
index 62ca7d6c805..0aa3c41a225 100644
--- a/doc/development/documentation/site_architecture/global_nav.md
+++ b/doc/development/documentation/site_architecture/global_nav.md
@@ -234,7 +234,7 @@ Examples:
```yaml
- category_title: Issues
category_url: 'user/project/issues/'
- # note that the above URL does not start with a slash and
+ # note that the above URL does not start with a slash and
# does not include index.html at the end
docs:
@@ -295,7 +295,6 @@ On the other hand, if the user is looking at `/ce/` docs,
all the links in the CE nav should link internally to `/ce/`
files, except for [`ee-only` docs](#ee-only-docs).
-
```html
<% if dir != 'ce' %>
<a href="/ee/<%= sec[:section_url] %>">...</a>
diff --git a/doc/development/documentation/site_architecture/index.md b/doc/development/documentation/site_architecture/index.md
index 0ce5825fd61..ee3a9caf9a0 100644
--- a/doc/development/documentation/site_architecture/index.md
+++ b/doc/development/documentation/site_architecture/index.md
@@ -44,7 +44,7 @@ read through the [global navigation](global_nav.md) doc.
The docs site is deployed to production with GitLab Pages, and previewed in
merge requests with Review Apps.
-The deployment aspects will be soon transfered from the [original document](https://gitlab.com/gitlab-com/gitlab-docs/blob/master/README.md)
+The deployment aspects will be soon transferred from the [original document](https://gitlab.com/gitlab-com/gitlab-docs/blob/master/README.md)
to this page.
<!--
diff --git a/doc/development/documentation/structure.md b/doc/development/documentation/structure.md
index ee3bd5606a5..95b5fcd99a1 100644
--- a/doc/development/documentation/structure.md
+++ b/doc/development/documentation/structure.md
@@ -20,7 +20,7 @@ Every feature or use case document should include the following content in the f
with exceptions and details noted below and in the template included on this page.
- **Title**: Top-level heading with the feature name, or a use case name, which would start with
-a verb, like Configuring, Enabling, etc.
+ a verb, like Configuring, Enabling, etc.
- **Introduction**: A couple sentences about the subject matter and what's to be found on this page.
- **Overview** Describe what it is, what it does, and in what context it should be used.
- **Use cases**: describes real use case scenarios for that feature/configuration.
@@ -95,7 +95,7 @@ Link each one to an appropriate place for more information.
"Instructions" is usually not the name of the heading.
This is the part of the document where you can include one or more sets of instructions, each to accomplish a specific task.
Headers should describe the task the reader will achieve by following the instructions within, typically starting with a verb.
-Larger instruction sets may have subsections covering specific phases of the process.
+Larger instruction sets may have subsections covering specific phases of the process.
- Write a step-by-step guide, with no gaps between the steps.
- Start with an h2 (`##`), break complex steps into small steps using
diff --git a/doc/development/documentation/styleguide.md b/doc/development/documentation/styleguide.md
index cda66447c2c..cd38721e3ab 100644
--- a/doc/development/documentation/styleguide.md
+++ b/doc/development/documentation/styleguide.md
@@ -112,7 +112,7 @@ table_display_block: true
## Emphasis
- Use double asterisks (`**`) to mark a word or text in bold (`**bold**`).
-- Use undescore (`_`) for text in italics (`_italic_`).
+- Use underscore (`_`) for text in italics (`_italic_`).
- Use greater than (`>`) for blockquotes.
## Punctuation
@@ -236,6 +236,24 @@ For other punctuation rules, please refer to the
E.g., instead of writing something like `Read more about GitLab Issue Boards [here](LINK)`,
write `Read more about [GitLab Issue Boards](LINK)`.
+### Links to confidential issues
+
+Don't link directly to [confidential issues](../../user/project/issues/confidential_issues.md). These will fail for:
+
+- Those without sufficient permissions.
+- Automated link checkers.
+
+Instead:
+
+- Mention in the text that the information is contained in a confidential issue. This will reduce confusion.
+- Provide a link in back ticks (`` ` ``) so that those with access to the issue can easily navigate to it.
+
+Example:
+
+```md
+For more information, see the [confidential issue](https://docs.gitlab.com/ee/user/project/issues/confidential_issues.html) `https://gitlab.com/gitlab-org/gitlab-ce/issues/<issue_number>`.
+```
+
### Unlinking emails
By default, all email addresses will render in an email tag on docs.gitlab.com.
diff --git a/doc/development/documentation/workflow.md b/doc/development/documentation/workflow.md
index 7c32c92b147..0abfe4b82a4 100644
--- a/doc/development/documentation/workflow.md
+++ b/doc/development/documentation/workflow.md
@@ -2,9 +2,9 @@
description: Learn the processes for contributing to GitLab's documentation.
---
-# Documentation workflows at GitLab
+# Documentation workflows
-Documentation workflows at GitLab differ depending on the reason for the change. The two types of documentation changes are:
+Documentation workflows at GitLab differ depending on the reason for the change:
-- [Feature-change documentation workflow](feature-change-workflow.md) - The documentation is being created or updated as part of the development and release of a new or enhanced feature. This process involves the developer of the feature (who includes new/updated documentation files as part of the same merge request containing the feature's code) and also involves the product manager and technical writer who are listed for the feature's [DevOps stage](https://about.gitlab.com/handbook/product/categories/#devops-stages).
+- [Documentation process for feature changes](feature-change-workflow.md) - The documentation is being created or updated as part of the development and release of a new or enhanced feature. This process involves the developer of the feature (who includes new/updated documentation files as part of the same merge request containing the feature's code) and also involves the product manager and technical writer who are listed for the feature's [DevOps stage](https://about.gitlab.com/handbook/product/categories/#devops-stages).
- [Documentation improvement workflow](improvement-workflow.md) - All documentation additions not associated with a feature release. Documentation is being created or updated to improve accuracy, completeness, ease of use, or any reason other than a feature change. Anyone (and everyone) can contribute a merge request for this type of change at any time.
diff --git a/doc/development/ee_features.md b/doc/development/ee_features.md
index 3e85c0e1995..17da4c51033 100644
--- a/doc/development/ee_features.md
+++ b/doc/development/ee_features.md
@@ -880,6 +880,98 @@ import bundle from 'ee_else_ce/protected_branches/protected_branches_bundle.js';
See the frontend guide [performance section](./fe_guide/performance.md) for
information on managing page-specific javascript within EE.
+
+## Vue code in `assets/javascript`
+### script tag
+
+#### Child Component only used in EE
+To seperate Vue template differences we should [async import the components](https://vuejs.org/v2/guide/components-dynamic-async.html#Async-Components).
+
+Doing this allows for us to load the correct component in EE whilst in CE
+we can load a empty component that renders nothing. This code **should**
+exist in the CE repository as well as the EE repository.
+
+```html
+<script>
+export default {
+ components: {
+ EEComponent: () => import('ee_component/components/test.vue'),
+ },
+};
+</script>
+
+<template>
+ <div>
+ <ee-component />
+ </div>
+</template>
+```
+
+#### For JS code that is EE only, like props, computed properties, methods, etc, we will keep the current approach
+ - Since we [can't async load a mixin](https://github.com/vuejs/vue-loader/issues/418#issuecomment-254032223) we will use the [`ee_else_ce`](https://docs.gitlab.com/ee/development/ee_features.html#javascript-code-in-assetsjavascripts) alias we already have for webpack.
+ - This means all the EE specific props, computed properties, methods, etc that are EE only should be in a mixin in the `ee/` folder and we need to create a CE counterpart of the mixin
+
+ ##### Example:
+ ```javascript
+ import mixin from 'ee_else_ce/path/mixin';
+
+ {
+ mixins: [mixin]
+ }
+ ```
+- Computed Properties/methods and getters only used in the child import still need a counterpart in CE
+
+- For store modules, we will need a CE counterpart too.
+- You can see an MR with an example [here](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/9762)
+
+#### `template` tag
+* **EE Child components**
+ - Since we are using the async loading to check which component to load, we'd still use the component's name, check [this example](#child-component-only-used-in-ee).
+
+* **EE extra HTML**
+ - For the templates that have extra HTML in EE we will use the `ifEE` mixin with the `v-if` directive.
+ - You can either use the `template` tag as a wrapper or directly in the element, if there is only one element to be rendered in EE:
+
+```html
+ <template v-if="ifEE">
+ <p>Several</p>
+ <p>non wrapper</p>
+ <p>elements</p>
+ <p>that are rendered</p>
+ <p>in EE only</p>
+ </template>
+```
+
+
+```html
+ <ul v-if="renderIfEE">
+ <li>One wrapped</li>
+ <li>element</li>
+ <li>that is rendered</li>
+ <li>in EE only</li>
+ </template>
+```
+
+### Non Vue Files
+For regular JS files, the approach is similar.
+
+1. We will keep using the [`ee_else_ce`](https://docs.gitlab.com/ee/development/ee_features.html#javascript-code-in-assetsjavascripts) helper, this means that EE only code should be inside the `ee/` folder.
+ 1. An EE file should be created with the EE only code, and it should extend the CE counterpart.
+1. For code inside functions that can't be extended, we will use an `if` statement with the `ifEE` helper
+
+##### Example:
+
+```javascript
+import { ifEE } from '~/lib/utils/common_utils'
+if (renderIfEE) {
+ $('.js-import-git-toggle-button').on('click', () => {
+ const $projectMirror = $('#project_mirror');
+
+ $projectMirror.attr('disabled', !$projectMirror.attr('disabled'));
+ });
+}
+```
+
## SCSS code in `assets/stylesheets`
To separate EE-specific styles in SCSS files, if a component you're adding styles for
diff --git a/doc/development/fe_guide/accessibility.md b/doc/development/fe_guide/accessibility.md
index 366b220cbb2..df32242a522 100644
--- a/doc/development/fe_guide/accessibility.md
+++ b/doc/development/fe_guide/accessibility.md
@@ -8,6 +8,5 @@ are useful for testing for potential accessibility problems in GitLab.
Accessibility best-practices and more in-depth information is available on
[the Audit Rules page][audit-rules] for the Chrome Accessibility Developer Tools.
-
[chrome-accessibility-developer-tools]: https://github.com/GoogleChrome/accessibility-developer-tools
[audit-rules]: https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules
diff --git a/doc/development/fe_guide/design_patterns.md b/doc/development/fe_guide/design_patterns.md
index e05887a19af..0342d16a87c 100644
--- a/doc/development/fe_guide/design_patterns.md
+++ b/doc/development/fe_guide/design_patterns.md
@@ -74,5 +74,4 @@ new Foo({ container: '.my-element' });
```
You can find an example of the above in this [class][container-class-example];
-
[container-class-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/javascripts/mini_pipeline_graph_dropdown.js
diff --git a/doc/development/fe_guide/development_process.md b/doc/development/fe_guide/development_process.md
index 905668eef58..f3fdaa3b883 100644
--- a/doc/development/fe_guide/development_process.md
+++ b/doc/development/fe_guide/development_process.md
@@ -61,17 +61,19 @@ Please use your best judgement when to use it and please contribute new points t
---
### Share your work early
+
1. Before writing code, ensure your vision of the architecture is aligned with
-GitLab's architecture.
+ GitLab's architecture.
1. Add a diagram to the issue and ask a frontend architect in the slack channel `#fe_architectural` about it.
![Diagram of Issue Boards Architecture](img/boards_diagram.png)
1. Don't take more than one week between starting work on a feature and
-sharing a Merge Request with a reviewer or a maintainer.
+ sharing a Merge Request with a reviewer or a maintainer.
### Vue features
+
1. Follow the steps in [Vue.js Best Practices](vue.md)
1. Follow the style guide.
1. Only a handful of people are allowed to merge Vue related features.
-Reach out to one of Vue experts early in this process.
+ Reach out to one of Vue experts early in this process.
diff --git a/doc/development/fe_guide/droplab/droplab.md b/doc/development/fe_guide/droplab/droplab.md
index e6aa0be671f..2f8c79abde1 100644
--- a/doc/development/fe_guide/droplab/droplab.md
+++ b/doc/development/fe_guide/droplab/droplab.md
@@ -90,7 +90,6 @@ const list = document.getElementById('list');
droplab.addHook(trigger, list);
```
-
### Dynamic data
Adding `data-dynamic` to your dropdown element will enable dynamic list rendering.
diff --git a/doc/development/fe_guide/droplab/plugins/filter.md b/doc/development/fe_guide/droplab/plugins/filter.md
index ddc6a3386c7..1f188c64fe4 100644
--- a/doc/development/fe_guide/droplab/plugins/filter.md
+++ b/doc/development/fe_guide/droplab/plugins/filter.md
@@ -9,7 +9,7 @@ Add the `Filter` object to the plugins array of a `DropLab.prototype.init` or `D
- `Filter` requires a config value for `template`.
- `template` should be the key of the objects within your data array that you want to compare
-to the user input string, for filtering.
+ to the user input string, for filtering.
```html
<input href="#" id="trigger" data-dropdown-trigger="#list">
diff --git a/doc/development/fe_guide/droplab/plugins/input_setter.md b/doc/development/fe_guide/droplab/plugins/input_setter.md
index 8e28a41f32e..e4050213869 100644
--- a/doc/development/fe_guide/droplab/plugins/input_setter.md
+++ b/doc/development/fe_guide/droplab/plugins/input_setter.md
@@ -9,11 +9,10 @@ Add the `InputSetter` object to the plugins array of a `DropLab.prototype.init`
- `InputSetter` requires a config value for `input` and `valueAttribute`.
- `input` should be the DOM element that you want to manipulate.
- `valueAttribute` should be a string that is the name of an attribute on your list items that is used to get the value
-to update the `input` element with.
+ to update the `input` element with.
You can also set the `InputSetter` config to an array of objects, which will allow you to update multiple elements.
-
```html
<input id="input" value="">
<div id="div" data-selected-id=""></div>
diff --git a/doc/development/fe_guide/graphql.md b/doc/development/fe_guide/graphql.md
index f55f01720fd..3290f29530a 100644
--- a/doc/development/fe_guide/graphql.md
+++ b/doc/development/fe_guide/graphql.md
@@ -9,7 +9,7 @@ read more about [Feature Flags][feature-flags].
## Apollo Client
To save duplicated clients getting created in different apps, we have a
-[default client][defualt-client] that should be used. This setups the
+[default client][default-client] that should be used. This setups the
Apollo client with the correct URL and also sets the CSRF headers.
## GraphQL Queries
@@ -27,11 +27,11 @@ the Vue application is mounted.
```javascript
import Vue from 'vue';
import VueApollo from 'vue-apollo';
-import defaultClient from '~/lib/graphql';
+import createDefaultClient from '~/lib/graphql';
Vue.use(VueApollo);
const apolloProvider = new VueApollo({
- defaultClient,
+ defaultClient: createDefaultClient(),
});
new Vue({
@@ -43,6 +43,29 @@ new Vue({
Read more about [Vue Apollo][vue-apollo] in the [Vue Apollo documentation][vue-apollo-docs].
+### Local state with `apollo-link-state`
+
+It is possible to use our Apollo setup with [apollo-link-state][apollo-link-state] by passing
+in the client state object when creating the default client.
+
+```javascript
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+Vue.use(VueApollo);
+
+const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient({
+ defaults: {
+ testing: true,
+ },
+ resolvers: {
+ ...
+ },
+ }),
+});
+```
+
### Testing
With [Vue test utils][vue-test-utils] it is easy to quickly test components that
@@ -81,3 +104,4 @@ Read more about the [Apollo] client in the [Apollo documentation][apollo-client-
[default-client]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/javascripts/lib/graphql.js
[apollo-client-docs]: https://www.apollographql.com/docs/tutorial/client.html
[vue-test-utils]: https://vue-test-utils.vuejs.org/
+[apollo-link-state]: https://www.apollographql.com/docs/link/links/state.html
diff --git a/doc/development/fe_guide/index.md b/doc/development/fe_guide/index.md
index 3a3cb77f592..86b8972a69e 100644
--- a/doc/development/fe_guide/index.md
+++ b/doc/development/fe_guide/index.md
@@ -1,7 +1,7 @@
# Frontend Development Guidelines
> **Notice:**
-We are currently in the process of re-writing our development guide to make it easier to find information. The new guide is still WIP but viewable in [development/new_fe_guide](../new_fe_guide/index.md)
+> We are currently in the process of re-writing our development guide to make it easier to find information. The new guide is still WIP but viewable in [development/new_fe_guide](../new_fe_guide/index.md)
This document describes various guidelines to ensure consistency and quality
across GitLab's frontend team.
@@ -32,32 +32,41 @@ For our currently-supported browsers, see our [requirements][requirements].
---
## [Development Process](development_process.md)
+
How we plan and execute the work on the frontend.
## [Architecture](architecture.md)
+
How we go about making fundamental design decisions in GitLab's frontend team
or make changes to our frontend development guidelines.
## [Testing](../testing_guide/frontend_testing.md)
+
How we write frontend tests, run the GitLab test suite, and debug test related
issues.
## [Design Patterns](design_patterns.md)
+
Common JavaScript design patterns in GitLab's codebase.
## [Vue.js Best Practices](vue.md)
+
Vue specific design patterns and practices.
## [Vuex](vuex.md)
+
Vuex specific design patterns and practices.
## [Axios](axios.md)
+
Axios specific practices and gotchas.
## [GraphQL](graphql.md)
+
How to use GraphQL
## [Icons and Illustrations](icons.md)
+
How we use SVG for our Icons and Illustrations.
## [Components](components.md)
@@ -70,7 +79,7 @@ How we use UI components.
### [JavaScript Style Guide](style_guide_js.md)
-We use eslint to enforce our JavaScript style guides. Our guide is based on
+We use eslint to enforce our JavaScript style guides. Our guide is based on
the excellent [Airbnb][airbnb-js-style-guide] style guide with a few small
changes.
@@ -81,23 +90,26 @@ Our SCSS conventions which are enforced through [scss-lint][scss-lint].
---
## [Performance](performance.md)
+
Best practices for monitoring and maximizing frontend performance.
---
## [Security](security.md)
+
Frontend security practices.
---
## [Accessibility](accessibility.md)
+
Our accessibility standards and resources.
## [Internationalization (i18n) and Translations](../i18n/externalization.md)
+
Frontend internationalization support is described in [this document](../i18n/).
The [externalization part of the guide](../i18n/externalization.md) explains the helpers/methods available.
-
[rails]: http://rubyonrails.org/
[haml]: http://haml.info/
[hamlit]: https://github.com/k0kubun/hamlit
@@ -116,6 +128,7 @@ The [externalization part of the guide](../i18n/externalization.md) explains the
---
## [DropLab](droplab/droplab.md)
+
Our internal `DropLab` dropdown library.
- [DropLab](droplab/droplab.md)
diff --git a/doc/development/fe_guide/performance.md b/doc/development/fe_guide/performance.md
index e5a383c25f5..2628e95dbc1 100644
--- a/doc/development/fe_guide/performance.md
+++ b/doc/development/fe_guide/performance.md
@@ -5,6 +5,7 @@
### Realtime Components
When writing code for realtime features we have to keep a couple of things in mind:
+
1. Do not overload the server with requests.
1. It should feel realtime.
@@ -12,16 +13,16 @@ Thus, we must strike a balance between sending requests and the feeling of realt
Use the following rules when creating realtime solutions.
1. The server will tell you how much to poll by sending `Poll-Interval` in the header.
-Use that as your polling interval. This way it is [easy for system administrators to change the
-polling rate](../../administration/polling.md).
-A `Poll-Interval: -1` means you should disable polling, and this must be implemented.
+ Use that as your polling interval. This way it is [easy for system administrators to change the
+ polling rate](../../administration/polling.md).
+ A `Poll-Interval: -1` means you should disable polling, and this must be implemented.
1. A response with HTTP status different from 2XX should disable polling as well.
1. Use a common library for polling.
1. Poll on active tabs only. Please use [Visibility](https://github.com/ai/visibilityjs).
1. Use regular polling intervals, do not use backoff polling, or jitter, as the interval will be
-controlled by the server.
+ controlled by the server.
1. The backend code will most likely be using etags. You do not and should not check for status
-`304 Not Modified`. The browser will transform it for you.
+ `304 Not Modified`. The browser will transform it for you.
### Lazy Loading Images
@@ -169,7 +170,6 @@ General tips:
- [Profiling with Chrome DevTools][google-devtools-profiling]
- [Browser Diet][browser-diet] is a community-built guide that catalogues practical tips for improving web page performance.
-
[web-page-test]: http://www.webpagetest.org/
[pagespeed-insights]: https://developers.google.com/speed/pagespeed/insights/
[google-devtools-profiling]: https://developers.google.com/web/tools/chrome-devtools/profile/?hl=en
diff --git a/doc/development/fe_guide/security.md b/doc/development/fe_guide/security.md
index 19e72c1d368..83bb449e54d 100644
--- a/doc/development/fe_guide/security.md
+++ b/doc/development/fe_guide/security.md
@@ -77,7 +77,6 @@ Inline styles should be avoided in almost all cases, they should only be used
when no alternatives can be found. This allows reusability of styles as well as
readability.
-
[observatory-cli]: https://github.com/mozilla/http-observatory-cli
[qualys-ssl]: https://www.ssllabs.com/ssltest/analyze.html
[secure_headers]: https://github.com/twitter/secureheaders
diff --git a/doc/development/fe_guide/style_guide_js.md b/doc/development/fe_guide/style_guide_js.md
index 1e0529262ad..060cd8baf7f 100644
--- a/doc/development/fe_guide/style_guide_js.md
+++ b/doc/development/fe_guide/style_guide_js.md
@@ -14,9 +14,9 @@ See [our current .eslintrc](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/
#### ESlint
1. **Never** disable eslint rules unless you have a good reason.
-You may see a lot of legacy files with `/* eslint-disable some-rule, some-other-rule */`
-at the top, but legacy files are a special case. Any time you develop a new feature or
-refactor an existing one, you should abide by the eslint rules.
+ You may see a lot of legacy files with `/* eslint-disable some-rule, some-other-rule */`
+ at the top, but legacy files are a special case. Any time you develop a new feature or
+ refactor an existing one, you should abide by the eslint rules.
1. **Never Ever EVER** disable eslint globally for a file
@@ -53,7 +53,7 @@ refactor an existing one, you should abide by the eslint rules.
1. [class-methods-use-this][eslint-this]
1. When they are needed _always_ place ESlint directive comment blocks on the first line of a script,
-followed by any global declarations, then a blank newline prior to any imports or code.
+ followed by any global declarations, then a blank newline prior to any imports or code.
```javascript
// bad
@@ -125,8 +125,8 @@ followed by any global declarations, then a blank newline prior to any imports o
```
1. Relative paths: when importing a module in the same directory, a child
-directory, or an immediate parent directory prefer relative paths. When
-importing a module which is two or more levels up, prefer either `~/` or `ee/`.
+ directory, or an immediate parent directory prefer relative paths. When
+ importing a module which is two or more levels up, prefer either `~/` or `ee/`.
In **app/assets/javascripts/my-feature/subdir**:
@@ -163,9 +163,9 @@ importing a module which is two or more levels up, prefer either `~/` or `ee/`.
```
1. Avoid using IIFE. Although we have a lot of examples of files which wrap their
-contents in IIFEs (immediately-invoked function expressions),
-this is no longer necessary after the transition from Sprockets to webpack.
-Do not use them anymore and feel free to remove them when refactoring legacy code.
+ contents in IIFEs (immediately-invoked function expressions),
+ this is no longer necessary after the transition from Sprockets to webpack.
+ Do not use them anymore and feel free to remove them when refactoring legacy code.
1. Avoid adding to the global namespace.
```javascript
@@ -484,8 +484,8 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation.
```
1. Default key should be provided if the prop is not required.
-_Note:_ There are some scenarios where we need to check for the existence of the property.
-On those a default key should not be provided.
+ _Note:_ There are some scenarios where we need to check for the existence of the property.
+ On those a default key should not be provided.
```javascript
// good
diff --git a/doc/development/fe_guide/vue.md b/doc/development/fe_guide/vue.md
index 9c614e3468a..3cd70bd63fa 100644
--- a/doc/development/fe_guide/vue.md
+++ b/doc/development/fe_guide/vue.md
@@ -223,13 +223,13 @@ need to test the rendered output. [Vue][vue-test] guide's to unit test show us e
## Vue.js Expert Role
One should apply to be a Vue.js expert by opening an MR when the Merge Request's they create and review show:
+
- Deep understanding of Vue and Vuex reactivy
- Vue and Vuex code are structured according to both official and our guidelines
- Full understanding of testing a Vue and Vuex application
- Vuex code follows the [documented pattern](./vuex.md#actions-pattern-request-and-receive-namespaces)
- Knowledge about the existing Vue and Vuex applications and existing reusable components
-
[vue-docs]: http://vuejs.org/guide/index.html
[issue-boards]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/boards
[environments-table]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/environments
diff --git a/doc/development/feature_flags.md b/doc/development/feature_flags.md
index b6161cd6163..3271f9a7fb3 100644
--- a/doc/development/feature_flags.md
+++ b/doc/development/feature_flags.md
@@ -26,8 +26,9 @@ the time, you should execute `/chatops run feature set my_feature_flag 50`.
## Feature flags for user applications
-GitLab does not yet support the use of feature flags in deployed user applications.
-You can follow the progress on that [in the issue on our issue tracker](https://gitlab.com/gitlab-org/gitlab-ee/issues/779).
+This document only covers feature flags used in the development of GitLab
+itself. Feature flags in deployed user applications can be found at
+[Feature Flags](https://docs.gitlab.com/ee/user/project/operations/feature_flags.html)
## Developing with feature flags
@@ -124,4 +125,3 @@ Feature.enable(:feature_flag_name)
## Enabling a feature flag (in production)
Check how to [roll out changes using feature flags](rolling_out_changes_using_feature_flags.md).
-
diff --git a/doc/development/file_storage.md b/doc/development/file_storage.md
index 597812c8c49..18e4dc2ca0c 100644
--- a/doc/development/file_storage.md
+++ b/doc/development/file_storage.md
@@ -20,7 +20,6 @@ There are many places where file uploading is used, according to contexts:
- LFS Objects
- Merge request diffs
-
## Disk storage
GitLab started saving everything on local disk. While directory location changed from previous versions,
diff --git a/doc/development/i18n/merging_translations.md b/doc/development/i18n/merging_translations.md
index 2fa7558d30b..85284d8c714 100644
--- a/doc/development/i18n/merging_translations.md
+++ b/doc/development/i18n/merging_translations.md
@@ -32,8 +32,7 @@ clicking `Pause sync` on the [Crowdin integration settings
page](https://translate.gitlab.com/project/gitlab-ee/settings#integration).
When all failures are resolved, the translations need to be double
-checked once more [as discussed in this
-issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/37850).
+checked once more as discussed in [confidential issue](https://docs.gitlab.com/ee/user/project/issues/confidential_issues.html) `https://gitlab.com/gitlab-org/gitlab-ce/issues/37850`.
## Merging translations
diff --git a/doc/development/i18n/proofreader.md b/doc/development/i18n/proofreader.md
index ac910e80a89..dd338f6f67d 100644
--- a/doc/development/i18n/proofreader.md
+++ b/doc/development/i18n/proofreader.md
@@ -47,6 +47,7 @@ are very appreciative of the work done by translators and proofreaders!
- Hungarian
- Proofreaders needed.
- Indonesian
+ - Adi Ferdian - [GitLab](https://gitlab.com/adiferd), [Crowdin](https://crowdin.com/profile/adiferd)
- Ahmad Naufal Mukhtar - [GitLab](https://gitlab.com/anaufalm), [Crowdin](https://crowdin.com/profile/anaufalm)
- Italian
- Paolo Falomo - [GitLab](https://gitlab.com/paolofalomo), [Crowdin](https://crowdin.com/profile/paolo.falomo)
@@ -85,7 +86,7 @@ are very appreciative of the work done by translators and proofreaders!
- Spanish
- Pedro Garcia - [GitLab](https://gitlab.com/pedgarrod), [Crowdin](https://crowdin.com/profile/breaking_pitt)
- Turkish
- - Proofreaders needed.
+ - Ali DemirtaÅŸ - [GitLab](https://gitlab.com/alidemirtas), [Crowdin](https://crowdin.com/profile/alidemirtas)
- Ukrainian
- Volodymyr Sobotovych - [GitLab](https://gitlab.com/wheleph), [Crowdin](https://crowdin.com/profile/wheleph)
- Andrew Vityuk - [GitLab](https://gitlab.com/3_1_3_u), [Crowdin](https://crowdin.com/profile/andruwa13)
diff --git a/doc/development/img/distributed_tracing_jaeger_ui.png b/doc/development/img/distributed_tracing_jaeger_ui.png
new file mode 100644
index 00000000000..57517dacced
--- /dev/null
+++ b/doc/development/img/distributed_tracing_jaeger_ui.png
Binary files differ
diff --git a/doc/development/img/distributed_tracing_performance_bar.png b/doc/development/img/distributed_tracing_performance_bar.png
new file mode 100644
index 00000000000..c9998cedd2d
--- /dev/null
+++ b/doc/development/img/distributed_tracing_performance_bar.png
Binary files differ
diff --git a/doc/development/import_export.md b/doc/development/import_export.md
index 71db1abb201..f7f48b03651 100644
--- a/doc/development/import_export.md
+++ b/doc/development/import_export.md
@@ -60,12 +60,12 @@ class StuckImportJobsWorker
Marked stuck import jobs as failed. JIDs: xyz
```
-```
+```
+-----------+ +-----------------------------------+
|Export Job |--->| Calls ActiveRecord `as_json` and |
+-----------+ | `to_json` on all project models |
+-----------------------------------+
-
+
+-----------+ +-----------------------------------+
|Import Job |--->| Loads all JSON in memory, then |
+-----------+ | inserts into the DB in batches |
@@ -109,13 +109,13 @@ The `AttributeCleaner` removes any prohibited keys:
# Removes all `_ids` and other prohibited keys
class AttributeCleaner
ALLOWED_REFERENCES = RelationFactory::PROJECT_REFERENCES + RelationFactory::USER_REFERENCES + ['group_id']
-
+
def clean
@relation_hash.reject do |key, _value|
prohibited_key?(key) || !@relation_class.attribute_method?(key) || excluded_key?(key)
end.except('id')
end
-
+
...
```
@@ -133,7 +133,7 @@ The `AttributeConfigurationSpec` checks and confirms the addition of new columns
SAFE_MODEL_ATTRIBUTES: #{File.expand_path(safe_attributes_file)}
IMPORT_EXPORT_CONFIG: #{Gitlab::ImportExport.config_file}
-MSG
+MSG
```
The `ModelConfigurationSpec` checks and confirms the addition of new models:
@@ -157,7 +157,7 @@ The `ExportFileSpec` detects encrypted or sensitive columns:
```ruby
# ExportFileSpec
<<-MSG
- Found a new sensitive word <#{key_found}>, which is part of the hash #{parent.inspect}
+ Found a new sensitive word <#{key_found}>, which is part of the hash #{parent.inspect}
If you think this information shouldn't get exported, please exclude the model or attribute in
IMPORT_EXPORT_CONFIG.
@@ -214,7 +214,6 @@ We do not need to bump the version up in any of the following cases:
- Remove a column or model (unless there is a DB constraint)
- Export new things (such as a new type of upload)
-
Every time we bump the version, the integration specs will fail and can be fixed with:
```bash
@@ -223,7 +222,7 @@ bundle exec rake gitlab:import_export:bump_version
### Renaming columns or models
-This is a relatively common occurence that will require a version bump.
+This is a relatively common occurrence that will require a version bump.
There is also the _RC problem_ - GitLab.com runs an RC, prior to any customers,
meaning that we want to bump the version up in the next version (or patch release).
@@ -231,7 +230,7 @@ meaning that we want to bump the version up in the next version (or patch releas
For example:
1. Add rename to `RelationRenameService` in X.Y
-2. Remove it from `RelationRenameService` in X.Y + 1
+2. Remove it from `RelationRenameService` in X.Y + 1
3. Bump Import/Export version in X.Y + 1
```ruby
@@ -270,8 +269,8 @@ included_attributes:
user:
- :id
- :email
- ...
-
+ ...
+
```
Do not include the following attributes for the models specified:
@@ -319,7 +318,7 @@ module Gitlab
ensure
remove_import_file
end
-
+
def restorers
[repo_restorer, wiki_restorer, project_tree, avatar_restorer,
uploads_restorer, lfs_restorer, statistics_restorer]
@@ -346,7 +345,7 @@ module Projects
end
def save_services
- [version_saver, avatar_saver, project_tree_saver, uploads_saver, repo_saver,
+ [version_saver, avatar_saver, project_tree_saver, uploads_saver, repo_saver,
wiki_repo_saver, lfs_saver].all?(&:save)
end
```
diff --git a/doc/development/kubernetes.md b/doc/development/kubernetes.md
new file mode 100644
index 00000000000..4b2d48903ac
--- /dev/null
+++ b/doc/development/kubernetes.md
@@ -0,0 +1,126 @@
+# Kubernetes integration - development guidelines
+
+This document provides various guidelines when developing for GitLab's
+[Kubernetes integration](../user/project/clusters/index.md).
+
+## Development
+
+### Architecture
+
+Some Kubernetes operations, such as creating restricted project
+namespaces are performed on the GitLab Rails application. These
+operations are performed using a [client library](#client-library).
+These operations will carry an element of risk as the operations will be
+run as the same user running the GitLab Rails application, see the
+[security](#security) section below.
+
+Some Kubernetes operations, such as installing cluster applications are
+performed on one-off pods on the Kubernetes cluster itself. These
+installation pods are currently named `install-<application_name>` and
+are created within the `gitlab-managed-apps` namespace.
+
+In terms of code organization, we generally add objects that represent
+Kubernetes resources in
+[`lib/gitlab/kubernetes`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/lib/gitlab/kubernetes).
+
+### Client library
+
+We use the [`kubeclient`](https://rubygems.org/gems/kubeclient) gem to
+perform Kubernetes API calls. As the `kubeclient` gem does not support
+different API Groups (e.g. `apis/rbac.authorization.k8s.io`) from a
+single client, we have created a wrapper class,
+[`Gitlab::Kubernetes::KubeClient`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/kubernetes/kube_client.rb)
+that will enable you to achieve this.
+
+Selected Kubernetes API groups are currently supported. Do add support
+for new API groups or methods to
+[`Gitlab::Kubernetes::KubeClient`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/kubernetes/kube_client.rb)
+if you need to use them. New API groups or API group versions can be
+added to `SUPPORTED_API_GROUPS` - internally, this will create an
+internal client for that group. New methods can be added as a delegation
+to the relevant internal client.
+
+### Performance considerations
+
+All calls to the Kubernetes API must be in a background process. Do not
+perform Kubernetes API calls within a web request as this will block
+unicorn and can easily lead to a Denial Of Service (DoS) attack in GitLab as
+the Kubernetes cluster response times are outside of our control.
+
+The easiest way to ensure your calls happen a background process is to
+delegate any such work to happen in a [sidekiq
+worker](sidekiq_style_guide.md).
+
+There are instances where you would like to make calls to Kubernetes and
+return the response and as such a background worker does not seem to be
+a good fit. For such cases you should make use of [reactive
+caching](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/models/concerns/reactive_caching.rb).
+For example:
+
+```ruby
+ def calculate_reactive_cache!
+ { pods: cluster.platform_kubernetes.kubeclient.get_pods }
+ end
+
+ def pods
+ with_reactive_cache do |data|
+ data[:pods]
+ end
+ end
+```
+
+### Testing
+
+We have some Webmock stubs in
+[`KubernetesHelpers`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/spec/support/helpers/kubernetes_helpers.rb)
+which can help with mocking out calls to Kubernetes API in your tests.
+
+## Security
+
+### SSRF
+
+As URLs for Kubernetes clusters are user controlled it is easily
+susceptible to Server Side Request Forgery (SSRF) attacks. You should
+understand the mitigation strategies if you are adding more API calls to
+a cluster.
+
+Mitigation strategies include:
+
+1. Not allowing redirects to attacker controller resources:
+ [`Kubeclient::KubeClient`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/kubernetes/kube_client.rb#)
+ can be configured to disallow any redirects by passing in
+ `http_max_redirects: 0` as an option.
+1. Not exposing error messages: by doing so, we
+ prevent attackers from triggering errors to expose results from
+ attacker controlled requests. For example, we do not expose (or store)
+ raw error messages:
+
+ ```ruby
+ rescue Kubernetes::HttpError => e
+ # bad
+ # app.make_errored!("Kubernetes error: #{e.message}")
+
+ # good
+ app.make_errored!("Kubernetes error: #{e.error_code}")
+ ```
+
+## Debugging
+
+Logs related to the Kubernetes integration can be found in
+[kubernetes.log](../administration/logs.md#kuberneteslog). On a local
+GDK install, this will be present in `log/kubernetes.log`.
+
+Some services such as
+[`Clusters::Applications::InstallService`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/services/clusters/applications/install_service.rb#L18)
+rescues `StandardError` which can make it harder to debug issues in an
+development environment. The current workaround is to temporarily
+comment out the `rescue` in your local development source.
+
+You can also follow the installation pod logs to debug issues related to
+installation. Once the installation/upgrade is underway, wait for the
+pod to be created. Then run the following to obtain the pods logs as
+they are written:
+
+```bash
+kubectl logs <pod_name> --follow -n gitlab-managed-apps
+```
diff --git a/doc/development/logging.md b/doc/development/logging.md
index 5c1d96b9e0c..d61441813b2 100644
--- a/doc/development/logging.md
+++ b/doc/development/logging.md
@@ -32,8 +32,8 @@ These logs suffer from a number of problems:
1. They often lack timestamps or other contextual information (e.g. project ID, user)
2. They may span multiple lines, which make them hard to find via Elasticsearch.
3. They lack a common structure, which make them hard to parse by log
-forwarders, such as Logstash or Fluentd. This also makes them hard to
-search.
+ forwarders, such as Logstash or Fluentd. This also makes them hard to
+ search.
Note that currently on GitLab.com, any messages in `production.log` will
NOT get indexed by Elasticsearch due to the sheer volume and noise. They
@@ -61,12 +61,12 @@ importer. You want to log issues created, merge requests, etc. as the
importer progresses. Here's what to do:
1. Look at [the list of GitLab Logs](../administration/logs.md) to see
-if your log message might belong with one of the existing log files.
+ if your log message might belong with one of the existing log files.
1. If there isn't a good place, consider creating a new filename, but
-check with a maintainer if it makes sense to do so. A log file should
-make it easy for people to search pertinent logs in one place. For
-example, `geo.log` contains all logs pertaining to GitLab Geo.
-To create a new file:
+ check with a maintainer if it makes sense to do so. A log file should
+ make it easy for people to search pertinent logs in one place. For
+ example, `geo.log` contains all logs pertaining to GitLab Geo.
+ To create a new file:
1. Choose a filename (e.g. `importer_json.log`).
1. Create a new subclass of `Gitlab::JsonLogger`:
@@ -130,15 +130,15 @@ To create a new file:
## Additional steps with new log files
1. Consider log retention settings. By default, Omnibus will rotate any
-logs in `/var/log/gitlab/gitlab-rails/*.log` every hour and [keep at
-most 30 compressed files](https://docs.gitlab.com/omnibus/settings/logs.html#logrotate).
-On GitLab.com, that setting is only 6 compressed files. These settings should suffice
-for most users, but you may need to tweak them in [omnibus-gitlab](https://gitlab.com/gitlab-org/omnibus-gitlab).
+ logs in `/var/log/gitlab/gitlab-rails/*.log` every hour and [keep at
+ most 30 compressed files](https://docs.gitlab.com/omnibus/settings/logs.html#logrotate).
+ On GitLab.com, that setting is only 6 compressed files. These settings should suffice
+ for most users, but you may need to tweak them in [omnibus-gitlab](https://gitlab.com/gitlab-org/omnibus-gitlab).
1. If you add a new file, submit an issue to the [production
-tracker](https://gitlab.com/gitlab-com/gl-infra/production/issues) or
-a merge request to the [gitlab_fluentd](https://gitlab.com/gitlab-cookbooks/gitlab_fluentd)
-project. See [this example](https://gitlab.com/gitlab-cookbooks/gitlab_fluentd/merge_requests/51/diffs).
+ tracker](https://gitlab.com/gitlab-com/gl-infra/production/issues) or
+ a merge request to the [gitlab_fluentd](https://gitlab.com/gitlab-cookbooks/gitlab_fluentd)
+ project. See [this example](https://gitlab.com/gitlab-cookbooks/gitlab_fluentd/merge_requests/51/diffs).
1. Be sure to update the [GitLab CE/EE documentation](../administration/logs.md) and the [GitLab.com
-runbooks](https://gitlab.com/gitlab-com/runbooks/blob/master/howto/logging.md).
+ runbooks](https://gitlab.com/gitlab-com/runbooks/blob/master/howto/logging.md).
diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md
index 98b54684d39..bb40c0d32b4 100644
--- a/doc/development/migration_style_guide.md
+++ b/doc/development/migration_style_guide.md
@@ -48,7 +48,6 @@ various database operations, such as
and whether they require downtime and how to work around that whenever possible.
-
## Downtime Tagging
Every migration must specify if it requires downtime or not, and if it should
diff --git a/doc/development/new_fe_guide/development/accessibility.md b/doc/development/new_fe_guide/development/accessibility.md
index 2a3a126ca5c..8420a504ec4 100644
--- a/doc/development/new_fe_guide/development/accessibility.md
+++ b/doc/development/new_fe_guide/development/accessibility.md
@@ -2,7 +2,7 @@
Using semantic HTML plays a key role when it comes to accessibility.
## Accessible Rich Internet Applications - ARIA
-WAI-ARIA, the Accessible Rich Internet Applications specification, defines a way to make Web content and Web applications more accessible to people with disabilities.
+WAI-ARIA, the Accessible Rich Internet Applications specification, defines a way to make Web content and Web applications more accessible to people with disabilities.
> Note: It is [recommended][using-aria] to use semantic elements as the primary method to achieve accessibility rather than adding aria attributes. Adding aria attributes should be seen as a secondary method for creating accessible elements.
@@ -15,6 +15,7 @@ Check the list of WAI-ARIA roles [here][roles]
When using icons or images that aren't absolutely needed to understand the context, we should use `aria-hidden="true"`.
On the other hand, if an icon is crucial to understand the context we should do one of the following:
+
1. Use `aria-label` in the element with a meaningful description
1. Use `aria-labelledby` to point to an element that contains the explanation for that icon
diff --git a/doc/development/new_fe_guide/development/testing.md b/doc/development/new_fe_guide/development/testing.md
index d74f141f08f..8441089418e 100644
--- a/doc/development/new_fe_guide/development/testing.md
+++ b/doc/development/new_fe_guide/development/testing.md
@@ -339,7 +339,6 @@ afterEach(() => {
Some regressions only affect a specific browser version. We can install and test in particular browsers with either Firefox or Browserstack using the following steps:
-
### Browserstack
[Browserstack](https://www.browserstack.com/) allows you to test more than 1200 mobile devices and browsers.
diff --git a/doc/development/new_fe_guide/event_tracking.md b/doc/development/new_fe_guide/event_tracking.md
new file mode 100644
index 00000000000..1958f1ce528
--- /dev/null
+++ b/doc/development/new_fe_guide/event_tracking.md
@@ -0,0 +1,74 @@
+# Event Tracking
+
+We use [Snowplow](https://github.com/snowplow/snowplow) for tracking custom events.
+
+## Generic tracking function
+
+In addition to Snowplow's built-in method for tracking page views, we use a generic tracking function which enables us to selectively apply listeners to events.
+
+The generic tracking function can be imported in EE-specific JS files as follows:
+
+```javascript
+import { trackEvent } from `ee/stats`;
+```
+
+This gives the user access to the `trackEvent` method, which takes the following parameters:
+
+| parameter | type | description | required |
+| ---------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
+| `category` | string | Describes the page that you're capturing click events on. Unless infeasible, please use the Rails page attribute `document.body.dataset.page` by default. | true |
+| `eventName` | string | Describes the action the user is taking. The first word should always describe the action. For example, clicks should be `click` and activations should be `activate`. Use underscores to describe what was acted on. For example, activating a form field would be `activate_form_input`. Clicking on a dropdown is `click_dropdown`. | true |
+| `additionalData` | object | Additional data such as `label`, `property`, and `value` as described [in our Feature Instrumentation taxonomy](https://about.gitlab.com/handbook/product/feature-instrumentation/#taxonomy). | false |
+
+Read more about instrumentation and the taxonomy in the [Product Handbook](https://about.gitlab.com/handbook/product/feature-instrumentation).
+
+### Tracking in `.js` and `.vue` files
+
+The most simple use case is to add tracking programmatically to an event of interest in Javascript.
+
+The following example demonstrates how to track a click on a button in Javascript by calling the `trackEvent` method explicitly:
+
+```javascript
+import { trackEvent } from `ee/stats`;
+
+trackEvent('dashboard:projects:index', 'click_button', {
+ label: 'create_from_template',
+ property: 'template_preview',
+ value: 'rails',
+});
+```
+
+### Tracking in HAML templates
+
+Sometimes we want to track clicks for multiple elements on a page. Creating event handlers for all elements could soon turn into a tedious task.
+
+There's a more convenient solution to this problem. When working with HAML templates, we can add `data-track-*` attributes to elements of interest. This way, all elements that have both `data-track-label` and `data-track-event` attributes assigned get marked for event tracking. All we have to do is call the `bindTrackableContainer` method on a container which allows for better scoping.
+
+Below is an example of `data-track-*` attributes assigned to a button in HAML:
+
+```ruby
+%button.btn{ data: { track_label: "create_from_template", track_property: "template_preview", track_event: "click_button", track_value: "my-template" } }
+```
+
+By calling `bindTrackableContainer('.my-container')`, click handlers get bound to all elements located in `.my-container` provided that they have the necessary `data-track-*` attributes assigned to them.
+
+```javascript
+import Stats from 'ee/stats';
+
+document.addEventListener('DOMContentLoaded', () => {
+ Stats.bindTrackableContainer('.my-container', 'category');
+});
+```
+
+The second parameter in `bindTrackableContainer` is optional. If omitted, the value of `document.body.dataset.page` will be used as category instead.
+
+Below is a list of supported `data-track-*` attributes:
+
+| attribute | description | required |
+| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
+| `data-track-label` | The `label` in `trackEvent` | true |
+| `data-track-event` | The `eventName` in `trackEvent` | true |
+| `data-track-property` | The `property` in `trackEvent`. If omitted, an empty string will be used as a default value. | false |
+| `data-track-value` | The `value` in `trackEvent`. If omitted, this will be `target.value` or empty string. For checkboxes, the default value being tracked will be the element's checked attribute if `data-track-value` is omitted. | false |
+
+Since Snowplow is an Enterprise Edition feature, it's necessary to create a CE backport when adding `data-track-*` attributes to HAML templates in most cases.
diff --git a/doc/development/new_fe_guide/index.md b/doc/development/new_fe_guide/index.md
index bfcca9cec7b..5fd5af252ef 100644
--- a/doc/development/new_fe_guide/index.md
+++ b/doc/development/new_fe_guide/index.md
@@ -27,6 +27,10 @@ Learn about all the internal JavaScript modules that make up our frontend.
Style guides to keep our code consistent.
+## [Event Tracking with Snowplow](event_tracking.md)
+
+How we use Snowplow to track custom events.
+
## [Tips](tips.md)
Tips from our frontend team to develop more efficiently and effectively.
diff --git a/doc/development/new_fe_guide/style/html.md b/doc/development/new_fe_guide/style/html.md
index 2d5b7d048ab..035fcbb28df 100644
--- a/doc/development/new_fe_guide/style/html.md
+++ b/doc/development/new_fe_guide/style/html.md
@@ -3,6 +3,7 @@
## Buttons
<a name="button-type"></a><a name="1.1"></a>
+
- [1.1](#button-type) **Use button type** Button tags requires a `type` attribute according to the [W3C HTML specification][button-type-spec].
```
@@ -14,6 +15,7 @@
```
<a name="button-role"></a><a name="1.2"></a>
+
- [1.2](#button-role) **Use button role for non buttons** If an HTML element has an onClick handler but is not a button, it should have `role="button"`. This is more [accessible][button-role-accessible].
```
@@ -27,6 +29,7 @@
## Links
<a name="blank-links"></a><a name="2.1"></a>
+
- [2.1](#blank-links) **Use rel for target blank** Use `rel="noopener noreferrer"` whenever your links open in a new window i.e. `target="_blank"`. This prevents [the following][jitbit-target-blank] security vulnerability documented by JitBit
```
@@ -38,6 +41,7 @@
```
<a name="fake-links"></a><a name="2.2"></a>
+
- [2.2](#fake-links) **Do not use fake links** Use a button tag if a link only invokes JavaScript click event handlers. This is more semantic.
```
diff --git a/doc/development/new_fe_guide/style/javascript.md b/doc/development/new_fe_guide/style/javascript.md
index 922fd1e4ea4..7985b893c9e 100644
--- a/doc/development/new_fe_guide/style/javascript.md
+++ b/doc/development/new_fe_guide/style/javascript.md
@@ -10,6 +10,7 @@ You can run eslint locally by running `yarn eslint`
## Arrays
<a name="avoid-foreach"></a><a name="1.1"></a>
+
- [1.1](#avoid-foreach) **Avoid ForEach when mutating data** Use `map`, `reduce` or `filter` instead of `forEach` when mutating data. This will minimize mutations in functions ([which is aligned with Airbnb's style guide][airbnb-minimize-mutations])
```
@@ -17,7 +18,7 @@ You can run eslint locally by running `yarn eslint`
users.forEach((user, index) => {
user.id = index;
});
-
+
// good
const usersWithId = users.map((user, index) => {
return Object.assign({}, user, { id: index });
@@ -27,6 +28,7 @@ You can run eslint locally by running `yarn eslint`
## Functions
<a name="limit-params"></a><a name="2.1"></a>
+
- [2.1](#limit-params) **Limit number of parameters** If your function or method has more than 3 parameters, use an object as a parameter instead.
```
@@ -34,7 +36,7 @@ You can run eslint locally by running `yarn eslint`
function a(p1, p2, p3) {
// ...
};
-
+
// good
function a(p) {
// ...
@@ -44,6 +46,7 @@ You can run eslint locally by running `yarn eslint`
## Classes & constructors
<a name="avoid-constructor-side-effects"></a><a name="3.1"></a>
+
- [3.1](#avoid-constructor-side-effects) **Avoid side effects in constructors** Avoid making some operations in the `constructor`, such as asynchronous calls, API requests and DOM manipulations. Prefer moving them into separate functions. This will make tests easier to write and code easier to maintain.
```javascript
@@ -54,23 +57,24 @@ You can run eslint locally by running `yarn eslint`
axios.get(this.config.endpoint)
}
}
-
+
// good
class myClass {
constructor(config) {
this.config = config;
}
-
+
makeRequest() {
axios.get(this.config.endpoint)
}
}
const instance = new myClass();
instance.makeRequest();
-
+
```
<a name="avoid-classes-to-handle-dom-events"></a><a name="3.2"></a>
+
- [3.2](#avoid-classes-to-handle-dom-events) **Avoid classes to handle DOM events** If the only purpose of the class is to bind a DOM event and handle the callback, prefer using a function.
```
@@ -79,14 +83,14 @@ You can run eslint locally by running `yarn eslint`
constructor(config) {
this.config = config;
}
-
+
init() {
document.addEventListener('click', () => {});
}
}
-
+
// good
-
+
const myFunction = () => {
document.addEventListener('click', () => {
// handle callback here
@@ -95,8 +99,9 @@ You can run eslint locally by running `yarn eslint`
```
<a name="element-container"></a><a name="3.3"></a>
+
- [3.3](#element-container) **Pass element container to constructor** When your class manipulates the DOM, receive the element container as a parameter.
-This is more maintainable and performant.
+ This is more maintainable and performant.
```
// bad
@@ -105,7 +110,7 @@ This is more maintainable and performant.
document.querySelector('.b');
}
}
-
+
// good
class a {
constructor(options) {
@@ -117,12 +122,13 @@ This is more maintainable and performant.
## Type Casting & Coercion
<a name="use-parseint"></a><a name="4.1"></a>
+
- [4.1](#use-parseint) **Use ParseInt** Use `ParseInt` when converting a numeric string into a number.
```
// bad
Number('10')
-
+
// good
parseInt('10', 10);
```
@@ -130,12 +136,13 @@ This is more maintainable and performant.
## CSS Selectors
<a name="use-js-prefix"></a><a name="5.1"></a>
+
- [5.1](#use-js-prefix) **Use js prefix** If a CSS class is only being used in JavaScript as a reference to the element, prefix the class name with `js-`
```
// bad
<button class="add-user"></button>
-
+
// good
<button class="js-add-user"></button>
```
@@ -143,44 +150,51 @@ This is more maintainable and performant.
## Modules
<a name="use-absolute-paths"></a><a name="6.1"></a>
+
- [6.1](#use-absolute-paths) **Use absolute paths for nearby modules** Use absolute paths if the module you are importing is less than two levels up.
```
// bad
import GitLabStyleGuide from '~/guides/GitLabStyleGuide';
-
+
// good
import GitLabStyleGuide from '../GitLabStyleGuide';
```
<a name="use-relative-paths"></a><a name="6.2"></a>
+
- [6.2](#use-relative-paths) **Use relative paths for distant modules** If the module you are importing is two or more levels up, use a relative path instead of an absolute path.
```
// bad
import GitLabStyleGuide from '../../../guides/GitLabStyleGuide';
-
+
// good
import GitLabStyleGuide from '~/GitLabStyleGuide';
```
<a name="global-namespace"></a><a name="6.3"></a>
+
- [6.3](#global-namespace) **Do not add to global namespace**
<a name="domcontentloaded"></a><a name="6.4"></a>
+
- [6.4](#domcontentloaded) **Do not use DOMContentLoaded in non-page modules** Imported modules should act the same each time they are loaded. `DOMContentLoaded` events are only allowed on modules loaded in the `/pages/*` directory because those are loaded dynamically with webpack.
## Security
<a name="avoid-xss"></a><a name="7.1"></a>
+
- [7.1](#avoid-xss) **Avoid XSS** Do not use `innerHTML`, `append()` or `html()` to set content. It opens up too many vulnerabilities.
## ESLint
<a name="disable-eslint-file"></a><a name="8.1"></a>
+
- [8.1](#disable-eslint-file) **Disabling ESLint in new files** Do not disable ESLint when creating new files. Existing files may have existing rules disabled due to legacy compatibility reasons but they are in the process of being refactored.
<a name="disable-eslint-rule"></a><a name="8.2"></a>
+
- [8.2](#disable-eslint-rule) **Disabling ESLint rule** Do not disable specific ESLint rules. Due to technical debt, you may disable the following rules only if you are invoking/instantiating existing code modules
- [no-new][no-new]
diff --git a/doc/development/ordering_table_columns.md b/doc/development/ordering_table_columns.md
index 3e49a65f5ab..cbfd05e731d 100644
--- a/doc/development/ordering_table_columns.md
+++ b/doc/development/ordering_table_columns.md
@@ -19,7 +19,7 @@ The space between rows is also subject to alignment padding. The `user_id`
column takes only 4 bytes, and on 64-bit platform, 4 zeroes will be added for
alignment padding, to allow storing the next row beginning with the "clear" word.
-As a result, the actual size of each column would be (ommiting variable length
+As a result, the actual size of each column would be (omitting variable length
data and 24-byte tuple header): 8 bytes, variable, 8 bytes. This means that
each row will require at least 16 bytes for the two 4-byte integers. If a table
has a few rows this is not an issue. However, once you start storing millions of
diff --git a/doc/development/performance.md b/doc/development/performance.md
index 972c93be817..0e21d45f57c 100644
--- a/doc/development/performance.md
+++ b/doc/development/performance.md
@@ -38,6 +38,7 @@ GitLab provides built-in tools to help improve performance and availability:
- [Profiling](profiling.md).
- [Sherlock](profiling.md#sherlock).
+- [Distributed Tracing](distributed_tracing.md)
- [GitLab Performance Monitoring](../administration/monitoring/performance/index.md).
- [Request Profiling](../administration/monitoring/performance/request_profiling.md).
- [QueryRecoder](query_recorder.md) for preventing `N+1` regressions.
@@ -336,7 +337,6 @@ the same method won't end up retrieving data from Redis upon every call. When
memoizing cached data in an instance variable, make sure to also reset the
instance variable when flushing the cache. An example:
-
```ruby
def first_branch
@first_branch ||= cache.fetch(:first_branch) { branches.first }
@@ -411,6 +411,21 @@ there's nothing stopping somebody from doing this elsewhere in the code:
SOME_CONSTANT = 'bar'
```
+## How to seed a database with millions of rows
+
+You might want millions of project rows in your local database, for example,
+in order to compare relative query performance, or to reproduce a bug. You could
+do this by hand with SQL commands, but since you have ActiveRecord models, you
+might find using these gems more convenient:
+
+- [BulkInsert gem](https://github.com/jamis/bulk_insert)
+- [ActiveRecord::PgGenerateSeries gem](https://github.com/ryu39/active_record-pg_generate_series)
+
+### Examples
+
+You may find some useful examples in this snippet:
+https://gitlab.com/gitlab-org/gitlab-ce/snippets/33946
+
[#15607]: https://gitlab.com/gitlab-org/gitlab-ce/issues/15607
[yorickpeterse]: https://gitlab.com/yorickpeterse
[anti-pattern]: https://en.wikipedia.org/wiki/Anti-pattern
diff --git a/doc/development/policies.md b/doc/development/policies.md
index 97424d90fb5..c4ac42bb40a 100644
--- a/doc/development/policies.md
+++ b/doc/development/policies.md
@@ -8,7 +8,6 @@ The policy used is based on the subject's class name - so `Ability.allowed?(user
Permissions are broken into two parts: `conditions` and `rules`. Conditions are boolean expressions that can access the database and the environment, while rules are statically configured combinations of expressions and other rules that enable or prevent certain abilities. For an ability to be allowed, it must be enabled by at least one rule, and not prevented by any.
-
### Conditions
Conditions are defined by the `condition` method, and are given a name and a block. The block will be executed in the context of the policy object - so it can access `@user` and `@subject`, as well as call any methods defined on the policy. Note that `@user` may be nil (in the anonymous case), but `@subject` is guaranteed to be a real instance of the subject class.
@@ -66,7 +65,51 @@ Within the rule DSL, you can use:
To see how the rules get evaluated into a judgment, it is useful in a console to use `policy.debug(:some_ability)`. This will print the rules in the order they are evaluated.
-When a policy is asked whether a particular ability is allowed (`policy.allowed?(:some_ability)`), it does not necessarily have to compute all the conditions on the policy. First, only the rules relevant to that particular ability are selected. Then, the execution model takes advantage of short-circuiting, and attempts to sort rules based on a heuristic of how expensive they will be to calculate. The sorting is dynamic and cache-aware, so that previously calculated conditions will be considered first, before computing other conditions.
+For example, let's say you wanted to debug `IssuePolicy`. You might run
+the debugger in this way:
+
+```ruby
+user = User.find_by(username: 'john')
+issue = Issue.first
+policy = IssuePolicy.new(user, issue)
+policy.debug(:read_issue)
+```
+
+An example debug output would look as follows:
+
+```ruby
+- [0] prevent when all?(confidential, ~can_read_confidential) ((@john : Issue/1))
+- [0] prevent when archived ((@john : Project/4))
+- [0] prevent when issues_disabled ((@john : Project/4))
+- [0] prevent when all?(anonymous, ~public_project) ((@john : Project/4))
++ [32] enable when can?(:reporter_access) ((@john : Project/4))
+```
+
+Each line represents a rule that was evaluated. There are a few things to note:
+
+1. The `-` or `+` symbol indicates whether the rule block was evaluated to be
+ `false` or `true`, respectively.
+2. The number inside the brackets indicates the score.
+3. The last part of the line (e.g. `@john : Issue/1`) shows the username
+ and subject for that rule.
+
+Here you can see that the first four rules were evaluated `false` for
+which user and subject. For example, you can see in the last line that
+the rule was activated because the user `root` had at reporter access to
+the `Project/4`.
+
+When a policy is asked whether a particular ability is allowed
+(`policy.allowed?(:some_ability)`), it does not necessarily have to
+compute all the conditions on the policy. First, only the rules relevant
+to that particular ability are selected. Then, the execution model takes
+advantage of short-circuiting, and attempts to sort rules based on a
+heuristic of how expensive they will be to calculate. The sorting is
+dynamic and cache-aware, so that previously calculated conditions will
+be considered first, before computing other conditions.
+
+Note that the score is chosen by a developer via the `score:` parameter
+in a `condition` to denote how expensive evaluating this rule would be
+relative to other rules.
## Scope
diff --git a/doc/development/polling.md b/doc/development/polling.md
index 3b34f985cd4..76bb5ae7819 100644
--- a/doc/development/polling.md
+++ b/doc/development/polling.md
@@ -51,6 +51,7 @@ request path. By doing this we avoid query parameter ordering problems and make
route matching easier.
For more information see:
+
- [`Poll-Interval` header](fe_guide/performance.md#realtime-components)
- [RFC 7232](https://tools.ietf.org/html/rfc7232)
- [ETag proposal](https://gitlab.com/gitlab-org/gitlab-ce/issues/26926)
diff --git a/doc/development/profiling.md b/doc/development/profiling.md
index b7d9f640a3f..be13485ff78 100644
--- a/doc/development/profiling.md
+++ b/doc/development/profiling.md
@@ -74,7 +74,6 @@ Gitlab::Profiler.print_by_total_time(result, max_percent: 60, min_percent: 2)
To print the profile in HTML format, use the following example:
-
```ruby
result = Gitlab::Profiler.profile('/my-user')
@@ -87,11 +86,8 @@ that builds on this to add some additional niceties, such as allowing
configuration with a single Yaml file for multiple URLs, and uploading of the
profile and log output to S3.
-For GitLab.com, currently the latest profiling data has been [moved from
-Redash to Looker](https://gitlab.com/gitlab-com/Product/issues/5#note_121194467).
-We are [currently investigating how to make this data
-public](https://gitlab.com/meltano/looker/issues/294).
-
+For GitLab.com, you can find the latest results here:
+<http://redash.gitlab.com/dashboard/gitlab-profiler-statistics>
## Sherlock
diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md
index ae9bf863419..dcb32c89f65 100644
--- a/doc/development/rake_tasks.md
+++ b/doc/development/rake_tasks.md
@@ -73,6 +73,7 @@ To make sure that indices still fit. You could find great details in:
## Run tests
In order to run the test you can use the following commands:
+
- `rake spec` to run the rspec suite
- `rake karma` to run the karma test suite
- `rake gitlab:test` to run all the tests
@@ -151,7 +152,6 @@ following:
bundle exec rake gemojione:digests
```
-
This will update the file `fixtures/emojis/digests.json` based on the currently
available Emoji.
diff --git a/doc/development/shell_commands.md b/doc/development/shell_commands.md
index 73893f9dd46..7bdf676be58 100644
--- a/doc/development/shell_commands.md
+++ b/doc/development/shell_commands.md
@@ -190,7 +190,7 @@ A check like this could have avoided CVE-2013-4583.
## Properly anchor regular expressions to the start and end of strings
-When using regular expressions to validate user input that is passed as an argument to a shell command, make sure to use the `\A` and `\z` anchors that designate the start and end of the string, rather than `^` and `$`, or no anchors at all.
+When using regular expressions to validate user input that is passed as an argument to a shell command, make sure to use the `\A` and `\z` anchors that designate the start and end of the string, rather than `^` and `$`, or no anchors at all.
If you don't, an attacker could use this to execute commands with potentially harmful effect.
@@ -198,7 +198,7 @@ For example, when a project's `import_url` is validated like below, the user cou
```ruby
validates :import_url, format: { with: URI.regexp(%w(ssh git http https)) }
-# URI.regexp(%w(ssh git http https)) roughly evaluates to /(ssh|git|http|https):(something_that_looks_like_a_url)/
+# URI.regexp(%w(ssh git http https)) roughly evaluates to /(ssh|git|http|https):(something_that_looks_like_a_url)/
```
Suppose the user submits the following as their import URL:
@@ -211,7 +211,6 @@ Since there are no anchors in the used regular expression, the `git:/tmp/lol` in
When importing, GitLab would execute the following command, passing the `import_url` as an argument:
-
```sh
git clone file://git:/tmp/lol
```
diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md
index 4cc3812b0f0..2bd8332bf93 100644
--- a/doc/development/testing_guide/best_practices.md
+++ b/doc/development/testing_guide/best_practices.md
@@ -168,12 +168,13 @@ instead of 30+ seconds in case of a regular `spec_helper`.
### `let` variables
-GitLab's RSpec suite has made extensive use of `let` variables to reduce
-duplication. However, this sometimes [comes at the cost of clarity][lets-not],
+GitLab's RSpec suite has made extensive use of `let`(along with it strict, non-lazy
+version `let!`) variables to reduce duplication. However, this sometimes [comes at the cost of clarity][lets-not],
so we need to set some guidelines for their use going forward:
-- `let` variables are preferable to instance variables. Local variables are
- preferable to `let` variables.
+- `let!` variables are preferable to instance variables. `let` variables
+ are preferable to `let!` variables. Local variables are preferable to
+ `let` variables.
- Use `let` to reduce duplication throughout an entire spec file.
- Don't use `let` to define variables used by a single test; define them as
local variables inside the test's `it` block.
@@ -183,6 +184,9 @@ so we need to set some guidelines for their use going forward:
- Try to avoid overriding the definition of one `let` variable with another.
- Don't define a `let` variable that's only used by the definition of another.
Use a helper method instead.
+- `let!` variables should be used only in case if strict evaluation with defined
+ order is required, otherwise `let` will suffice. Remember that `let` is lazy and won't
+ be evaluated until it is referenced.
[lets-not]: https://robots.thoughtbot.com/lets-not
@@ -358,16 +362,11 @@ range of inputs, might look like this:
describe "#==" do
using RSpec::Parameterized::TableSyntax
- let(:project1) { create(:project) }
- let(:project2) { create(:project) }
where(:a, :b, :result) do
1 | 1 | true
1 | 2 | false
true | true | true
true | false | false
- project1 | project1 | true
- project2 | project2 | true
- project 1 | project2 | false
end
with_them do
@@ -380,6 +379,11 @@ describe "#==" do
end
```
+CAUTION: **Caution:**
+Only use simple values as input in the `where` block. Using procs, stateful
+objects, FactoryBot-created objects etc. can lead to
+[unexpected results](https://github.com/tomykaira/rspec-parameterized/issues/8).
+
### Prometheus tests
Prometheus metrics may be preserved from one test run to another. To ensure that metrics are
diff --git a/doc/development/testing_guide/ci.md b/doc/development/testing_guide/ci.md
index 5aa668290b4..7a7fca46534 100644
--- a/doc/development/testing_guide/ci.md
+++ b/doc/development/testing_guide/ci.md
@@ -31,7 +31,11 @@ After that, the next pipeline will use the up-to-date
The GitLab test suite is [monitored] for the `master` branch, and any branch
that includes `rspec-profile` in their name.
+A [public dashboard] is available for everyone to see. Feel free to look at the
+slowest test files and try to improve them.
+
[monitored]: ../performance.md#rspec-profiling
+[public dashboard]: https://redash.gitlab.com/public/dashboards/l1WhHXaxrCWM5Ai9D7YDqHKehq6OU3bx5gssaiWe?org_slug=default
## CI setup
diff --git a/doc/development/testing_guide/end_to_end_tests.md b/doc/development/testing_guide/end_to_end_tests.md
index e9f236c6b3a..a239dc84a1c 100644
--- a/doc/development/testing_guide/end_to_end_tests.md
+++ b/doc/development/testing_guide/end_to_end_tests.md
@@ -41,24 +41,24 @@ Currently, we are using _multi-project pipeline_-like approach to run QA
pipelines.
1. Developer triggers a manual action, that can be found in CE / EE merge
-requests. This starts a chain of pipelines in multiple projects.
+ requests. This starts a chain of pipelines in multiple projects.
1. The script being executed triggers a pipeline in [Omnibus GitLab][omnibus-gitlab]
-and waits for the resulting status. We call this a _status attribution_.
+ and waits for the resulting status. We call this a _status attribution_.
1. GitLab packages are being built in the [Omnibus GitLab][omnibus-gitlab]
-pipeline. Packages are then pushed to its Container Registry.
+ pipeline. Packages are then pushed to its Container Registry.
1. When packages are ready, and available in the registry, a final step in the
-[Omnibus GitLab][omnibus-gitlab] pipeline, triggers a new
-[GitLab QA pipeline][gitlab-qa-pipelines]. It also waits for a resulting status.
+ [Omnibus GitLab][omnibus-gitlab] pipeline, triggers a new
+ [GitLab QA pipeline][gitlab-qa-pipelines]. It also waits for a resulting status.
1. GitLab QA pulls images from the registry, spins-up containers and runs tests
-against a test environment that has been just orchestrated by the `gitlab-qa`
-tool.
+ against a test environment that has been just orchestrated by the `gitlab-qa`
+ tool.
1. The result of the [GitLab QA pipeline][gitlab-qa-pipelines] is being
-propagated upstream, through Omnibus, back to the CE / EE merge request.
+ propagated upstream, through Omnibus, back to the CE / EE merge request.
#### How do I write tests?
diff --git a/doc/development/testing_guide/frontend_testing.md b/doc/development/testing_guide/frontend_testing.md
index 6012f1080ab..0470a071d39 100644
--- a/doc/development/testing_guide/frontend_testing.md
+++ b/doc/development/testing_guide/frontend_testing.md
@@ -141,14 +141,15 @@ module. GitLab has a custom `spyOnDependency` method which utilizes
[babel-plugin-rewire](https://github.com/speedskater/babel-plugin-rewire) to
achieve this. It can be used like so:
-```javascript
+```js
// my_module.js
import { visitUrl } from '~/lib/utils/url_utility';
export default function doSomething() {
visitUrl('/foo/bar');
}
-
+```
+```js
// my_module_spec.js
import doSomething from '~/my_module';
diff --git a/doc/development/testing_guide/review_apps.md b/doc/development/testing_guide/review_apps.md
index 3af97717775..fda3ff57316 100644
--- a/doc/development/testing_guide/review_apps.md
+++ b/doc/development/testing_guide/review_apps.md
@@ -70,9 +70,8 @@ subgraph GCP `gitlab-review-apps` project
you get a dedicated environment for your branch that's very close to what
it would look in production.
1. Once the [`review-deploy`][review-deploy] job succeeds, you should be able to
- use your Review App thanks to the direct link to it from the MR widget. The
- default username is `root` and its password can be found in the 1Password
- secure note named **gitlab-{ce,ee} Review App's root password**.
+ use your Review App thanks to the direct link to it from the MR widget. To log
+ into the Review App, see "Log into my Review App?" below.
**Additional notes:**
@@ -96,8 +95,27 @@ You can also manually start the `review-qa-all`: it runs the full QA suite.
Note that both jobs first wait for the `review-deploy` job to be finished.
+## Performance Metrics
+
+On every [pipeline][gitlab-pipeline] during the `test` stage, the
+`review-performance` job is automatically started: this job does basic
+browser performance testing using [Sitespeed.io Container](https://docs.gitlab.com/ee/user/project/merge_requests/browser_performance_testing.html) .
+
+This job waits for the `review-deploy` job to be finished.
+
## How to?
+### Log into my Review App?
+
+The default username is `root` and its password can be found in the 1Password
+secure note named **gitlab-{ce,ee} Review App's root password**.
+
+### Enable a feature flag for my Review App?
+
+1. Open your Review App and log in as documented above.
+1. Create a personal access token.
+1. Enable the feature flag using the [Feature flag API](../../api/features.md).
+
### Find my Review App slug?
1. Open the `review-deploy` job.
@@ -143,17 +161,21 @@ thousands of unused Docker images.**
**How big are the Kubernetes clusters (`review-apps-ce` and `review-apps-ee`)?**
> The clusters are currently set up with a single pool of preemptible nodes,
- with a minimum of 1 node and a maximum of 100 nodes.
+ with a minimum of 1 node and a maximum of 50 nodes.
**What are the machine running on the cluster?**
- > We're currently using `n1-standard-4` (4 vCPUs, 15 GB memory) machines.
+ > We're currently using `n1-standard-16` (16 vCPUs, 60 GB memory) machines.
**How do we secure this from abuse? Apps are open to the world so we need to
find a way to limit it to only us.**
> This isn't enabled for forks.
+## Other resources
+
+* [Review Apps integration for CE/EE (presentation)](https://docs.google.com/presentation/d/1QPLr6FO4LduROU8pQIPkX1yfGvD13GEJIBOenqoKxR8/edit?usp=sharing)
+
[charts-1068]: https://gitlab.com/charts/gitlab/issues/1068
[gitlab-pipeline]: https://gitlab.com/gitlab-org/gitlab-ce/pipelines/44362587
[gitlab:assets:compile]: https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/149511610
diff --git a/doc/development/testing_guide/testing_levels.md b/doc/development/testing_guide/testing_levels.md
index 070b6477a7a..a7a3459719b 100644
--- a/doc/development/testing_guide/testing_levels.md
+++ b/doc/development/testing_guide/testing_levels.md
@@ -159,6 +159,10 @@ Every new feature should come with a [test plan].
> See [end-to-end tests](end_to_end_tests.md) for more information.
+Note that `qa/spec` contains unit tests of the QA framework itself, not to be
+confused with the application's [unit tests](#unit-tests) or
+[end-to-end tests](#black-box-tests-at-the-system-level-aka-end-to-end-tests).
+
[multiple pieces]: ../architecture.md#components
[GitLab Shell]: https://gitlab.com/gitlab-org/gitlab-shell
[GitLab Workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse
diff --git a/doc/gitlab-basics/create-your-ssh-keys.md b/doc/gitlab-basics/create-your-ssh-keys.md
index 881629c3bfd..da7853d7d34 100644
--- a/doc/gitlab-basics/create-your-ssh-keys.md
+++ b/doc/gitlab-basics/create-your-ssh-keys.md
@@ -1,10 +1,9 @@
# How to create your SSH Keys
-1. The first thing you need to do is go to your [command line](start-using-git.md)
- and follow the [instructions](../ssh/README.md) to generate your SSH key pair.
+1. Go to your [command line](start-using-git.md) and follow the [instructions](../ssh/README.md) to generate your SSH key pair.
-1. Once you do that, login to GitLab with your credentials.
-1. On the upper right corner, click on your avatar and go to your **Profile settings**.
+1. Log in to GitLab with your credentials.
+1. In the upper-right corner, click your avatar and then click **Settings**.
![Profile settings dropdown](img/profile_settings.png)
@@ -22,12 +21,11 @@
![SSH key title](img/profile_settings_ssh_keys_title.png)
-1. Finally, click on **Add key** to add it to GitLab. You will be able to see
- its fingerprint, its title and creation date.
+1. Finally, click **Add key** to add it to GitLab. You will be able to see
+ its fingerprint, title, and creation date.
![SSH key single page](img/profile_settings_ssh_keys_single_key.png)
-
>**Note:**
Once you add a key, you cannot edit it, only remove it. In case the paste
didn't work, you will have to remove the offending key and re-add it.
diff --git a/doc/install/aws/index.md b/doc/install/aws/index.md
index e209a00b38c..ce50b5fa317 100644
--- a/doc/install/aws/index.md
+++ b/doc/install/aws/index.md
@@ -379,6 +379,10 @@ size depends on your needs and you can always migrate to a bigger volume later.
You will be able to [set up that volume](#setting-up-the-ebs-volume)
after the instance is created.
+CAUTION: **Caution:**
+We **do not** recommend using the AWS Elastic File System (EFS), as it can result
+in [significantly degraded performance](../../administration/high_availability/nfs.html#avoid-using-awss-elastic-file-system-efs).
+
### Configure security group
As a last step, configure the security group:
@@ -471,7 +475,6 @@ gitlab_rails['redis_port'] = 6379
Finally, reconfigure GitLab for the change to take effect:
-
```sh
sudo gitlab-ctl reconfigure
```
diff --git a/doc/install/azure/index.md b/doc/install/azure/index.md
index 19a6e46f969..fa5be1d30f9 100644
--- a/doc/install/azure/index.md
+++ b/doc/install/azure/index.md
@@ -1,5 +1,5 @@
---
-description: 'Learn how to spin up a
+description: 'Learn how to spin up a
pre-configured GitLab VM on Microsoft Azure and have your very own private GitLab instance up and running in around 30 minutes.'
---
@@ -9,10 +9,10 @@ pre-configured GitLab VM on Microsoft Azure and have your very own private GitLa
>
> _Ported to the GitLab documentation and updated on 2017-08-24 by [Ian Scorer](https://gitlab.com/iscorer)._
-Azure is Microsoft's business cloud and GitLab is a pre-configured offering on the Azure Marketplace.
-Hopefully, you aren't surprised to hear that Microsoft and Azure have embraced open source software
-like Ubuntu, Red Hat Enterprise Linux, and of course - GitLab! This means that you can spin up a
-pre-configured GitLab VM and have your very own private GitLab up and running in around 30 minutes.
+Azure is Microsoft's business cloud and GitLab is a pre-configured offering on the Azure Marketplace.
+Hopefully, you aren't surprised to hear that Microsoft and Azure have embraced open source software
+like Ubuntu, Red Hat Enterprise Linux, and of course - GitLab! This means that you can spin up a
+pre-configured GitLab VM and have your very own private GitLab up and running in around 30 minutes.
Let's get started.
## Getting started
@@ -20,40 +20,40 @@ Let's get started.
First, you'll need an account on Azure. There are three ways to do this:
- If your company (or you) already has an account, then you are ready to go!
-- You can also open your own Azure account for free. _At time of writing_, you get $200
-of credit to spend on Azure services for 30 days. You can use this credit to try out paid Azure
-services, exploring Microsoft's cloud for free. Even after the first 30 days, you never have to pay
-anything unless you decide to transition to paid services with a Pay-As-You-Go Azure subscription.
-This is a great way to try out Azure and cloud computing, and you can
-[read more in their comprehensive FAQ][Azure-Free-Account-FAQ].
-- If you have an MSDN subscription, you can activate your Azure subscriber benefits. Your MSDN
-subscription gives you recurring Azure credits every month, so why not put those credits to use and
-try out GitLab right now?
+- You can also open your own Azure account for free. _At time of writing_, you get $200
+ of credit to spend on Azure services for 30 days. You can use this credit to try out paid Azure
+ services, exploring Microsoft's cloud for free. Even after the first 30 days, you never have to pay
+ anything unless you decide to transition to paid services with a Pay-As-You-Go Azure subscription.
+ This is a great way to try out Azure and cloud computing, and you can
+ [read more in their comprehensive FAQ][Azure-Free-Account-FAQ].
+- If you have an MSDN subscription, you can activate your Azure subscriber benefits. Your MSDN
+ subscription gives you recurring Azure credits every month, so why not put those credits to use and
+ try out GitLab right now?
## Working with Azure
-Once you have an Azure account, you can get started. Login to Azure using
+Once you have an Azure account, you can get started. Login to Azure using
[portal.azure.com](https://portal.azure.com) and the first thing you will see is the Dashboard:
![Azure Dashboard](img/azure-dashboard.png)
-The Dashboard gives you a quick overview of Azure resources, and from here you can build VMs,
+The Dashboard gives you a quick overview of Azure resources, and from here you can build VMs,
create SQL Databases, author websites, and perform lots of other cloud tasks.
## Create New VM
-The [Azure Marketplace][Azure-Marketplace] is an online store for pre-configured applications and
+The [Azure Marketplace][Azure-Marketplace] is an online store for pre-configured applications and
services which have been optimized for the cloud by software vendors like GitLab,
available on the Azure Marketplace as pre-configured solutions. In this tutorial
we will install GitLab Community Edition, but for GitLab Enterprise Edition you
can follow the same process.
-To begin creating a new GitLab VM, click on the **+ New** icon, type "GitLab" into the search
+To begin creating a new GitLab VM, click on the **+ New** icon, type "GitLab" into the search
box, and then click the **"GitLab Community Edition"** search result:
![Azure - New - Search for 'GitLab'](img/azure-new-search-gitlab.png)
-A new "blade" window will pop-out, where you can read more about the **"GitLab Community Edition"**
+A new "blade" window will pop-out, where you can read more about the **"GitLab Community Edition"**
offering which is freely available under the MIT Expat License:
![Azure - New - Select 'GitLab Community Edition'](img/azure-new-gitlab-ce.png)
@@ -70,18 +70,18 @@ The first items we need to configure are the basic settings of the underlying vi
1. Select a `VM disk type` - either **HDD** _(slower, lower cost)_ or **SSD** _(faster, higher cost)_
1. Enter a `User name` - e.g. **"gitlab-admin"**
1. Select an `Authentication type`, either **SSH public key** or **Password**:
-
+
> **Note:** if you're unsure which authentication type to use, select **Password**
- 1. If you chose **SSH public key** - enter your `SSH public key` into the field provided
- _(read the [SSH documentation][GitLab-Docs-SSH] to learn more about how to set up SSH
+ 1. If you chose **SSH public key** - enter your `SSH public key` into the field provided
+ _(read the [SSH documentation][GitLab-Docs-SSH] to learn more about how to set up SSH
public keys)_
- 1. If you chose **Password** - enter the password you wish to use _(this is the password that you
+ 1. If you chose **Password** - enter the password you wish to use _(this is the password that you
will use later in this tutorial to [SSH] into the VM, so make sure it's a strong password/passphrase)_
1. Choose the appropriate `Subscription` tier for your Azure account
1. Choose an existing `Resource Group` or create a new one - e.g. **"GitLab-CE-Azure"**
- > **Note:** a "Resource group" is a way to group related resources together for easier administration.
+ > **Note:** a "Resource group" is a way to group related resources together for easier administration.
> We chose "GitLab-CE-Azure", but your resource group can have the same name as your VM.
1. Choose a `Location` - if you're unsure, select the default location
@@ -94,23 +94,23 @@ Check the settings you have entered, and then click **"OK"** when you're ready t
## Size
-Next, you need to choose the size of your VM - selecting features such as the number of CPU cores,
+Next, you need to choose the size of your VM - selecting features such as the number of CPU cores,
the amount of RAM, the size of storage (and its speed), etc.
-> **Note:** in common with other cloud vendors, Azure operates a resource/usage pricing model, i.e.
-the more resources your VM consumes the more it will cost you to run, so make your selection
+> **Note:** in common with other cloud vendors, Azure operates a resource/usage pricing model, i.e.
+the more resources your VM consumes the more it will cost you to run, so make your selection
carefully. You'll see that Azure provides an _estimated_ monthly cost beneath each VM Size to help
guide your selection.
-The default size - the lowest cost **"DS1_V2 Standard"** VM - meets the minimum system requirements
-to run a small GitLab environment for testing and evaluation purposes, and so we're going to go
+The default size - the lowest cost **"DS1_V2 Standard"** VM - meets the minimum system requirements
+to run a small GitLab environment for testing and evaluation purposes, and so we're going to go
ahead and select this one, but please choose the size which best meets your own requirements:
![Azure - Create Virtual Machine - Size](img/azure-create-virtual-machine-size.png)
-> **Note:** be aware that whilst your VM is active (known as "allocated"), it will incur
-"compute charges" which, ultimately, you will be billed for. So, even if you're using the
-free trial credits, you'll likely want to learn
+> **Note:** be aware that whilst your VM is active (known as "allocated"), it will incur
+"compute charges" which, ultimately, you will be billed for. So, even if you're using the
+free trial credits, you'll likely want to learn
[how to properly shutdown an Azure VM to save money][Azure-Properly-Shutdown-VM].
Go ahead and click your chosen size, then click **"Select"** when you're ready to proceed to the
@@ -118,8 +118,8 @@ next step.
## Settings
-On the next blade, you're asked to configure the Storage, Network and Extension settings.
-We've gone with the default settings as they're sufficient for test-driving GitLab, but please
+On the next blade, you're asked to configure the Storage, Network and Extension settings.
+We've gone with the default settings as they're sufficient for test-driving GitLab, but please
choose the settings which best meet your own requirements:
![Azure - Create Virtual Machine - Settings](img/azure-create-virtual-machine-settings.png)
@@ -128,80 +128,80 @@ Review the settings and then click **"OK"** when you're ready to proceed to the
## Purchase
-The Purchase page is the last step and here you will be presented with the price per hour for your
-new VM. You'll be billed only for the VM itself (e.g. "Standard DS1 v2") because the
+The Purchase page is the last step and here you will be presented with the price per hour for your
+new VM. You'll be billed only for the VM itself (e.g. "Standard DS1 v2") because the
**"GitLab Community Edition"** marketplace solution is free to use at 0 USD/hr:
![Azure - Create Virtual Machine - Purchase](img/azure-create-virtual-machine-purchase.png)
-> **Note:** at this stage, you can review and modify the any of the settings you have made during all
+> **Note:** at this stage, you can review and modify the any of the settings you have made during all
previous steps, just click on any of the four steps to re-open them.
When you have read and agreed to the terms of use and are ready to proceed, click **"Purchase"**.
## Deployment
-At this point, Azure will begin deploying your new VM. The deployment process will take a few
+At this point, Azure will begin deploying your new VM. The deployment process will take a few
minutes to complete, with progress displayed on the **"Deployment"** blade:
![Azure - Create Virtual Machine - Deployment](img/azure-create-virtual-machine-deployment.png)
-Once the deployment process is complete, the new VM and its associated resources will be displayed
+Once the deployment process is complete, the new VM and its associated resources will be displayed
on the Azure Dashboard (you may need to refresh the page):
![Azure - Dashboard - All resources](img/azure-dashboard-running-resources.png)
-The new VM can also be accessed by clicking the `All resources` or `Virtual machines` icons in the
+The new VM can also be accessed by clicking the `All resources` or `Virtual machines` icons in the
Azure Portal sidebar navigation menu.
## Set up a domain name
-The VM will have a public IP address (static by default), but Azure allows us to assign a friendly
+The VM will have a public IP address (static by default), but Azure allows us to assign a friendly
DNS name to the VM, so let's go ahead and do that.
-From the Dashboard, click on the **"GitLab-CE"** tile to open the management blade for the new VM.
+From the Dashboard, click on the **"GitLab-CE"** tile to open the management blade for the new VM.
The public IP address that the VM uses is shown in the 'Essentials' section:
![Azure - VM - Management - Public IP Address](img/azure-vm-management-public-ip.png)
-Click on the public IP address - which should open the **"Public IP address - Configuration"** blade,
-then click on **"Configuration"** (under "Settings"). Now enter a friendly DNS name for your instance
+Click on the public IP address - which should open the **"Public IP address - Configuration"** blade,
+then click on **"Configuration"** (under "Settings"). Now enter a friendly DNS name for your instance
in the `DNS name label` field:
![Azure - VM - Domain Name](img/azure-vm-domain-name.png)
-In the screenshot above, you'll see that we've set the `DNS name label` to **"gitlab-ce-test"**.
-This will make our VM accessible at `gitlab-ce-test.centralus.cloudapp.azure.com`
-_(the full domain name of your own VM will be different, of course)_.
+In the screenshot above, you'll see that we've set the `DNS name label` to **"gitlab-ce-test"**.
+This will make our VM accessible at `gitlab-ce-test.centralus.cloudapp.azure.com`
+_(the full domain name of your own VM will be different, of course)_.
Click **"Save"** for the changes to take effect.
-> **Note:** if you want to use your own domain name, you will need to add a DNS `A` record at your
-domain registrar which points to the public IP address of your Azure VM. If you do this, you'll need
-to make sure your VM is configured to use a _static_ public IP address (i.e. not a _dynamic_ one)
-or you will have to reconfigure the DNS `A` record each time Azure reassigns your VM a new public IP
+> **Note:** if you want to use your own domain name, you will need to add a DNS `A` record at your
+domain registrar which points to the public IP address of your Azure VM. If you do this, you'll need
+to make sure your VM is configured to use a _static_ public IP address (i.e. not a _dynamic_ one)
+or you will have to reconfigure the DNS `A` record each time Azure reassigns your VM a new public IP
address. Read [IP address types and allocation methods in Azure][Azure-IP-Address-Types] to learn more.
## Let's open some ports!
-At this stage you should have a running and fully operational VM. However, none of the services on
-your VM (e.g. GitLab) will be publicly accessible via the internet until you have opened up the
+At this stage you should have a running and fully operational VM. However, none of the services on
+your VM (e.g. GitLab) will be publicly accessible via the internet until you have opened up the
necessary ports to enable access to those services.
-Ports are opened by adding _security rules_ to the **"Network security group"** (NSG) which our VM
-has been assigned to. If you followed the process above, then Azure will have automatically created
-an NSG named `GitLab-CE-nsg` and assigned the `GitLab-CE` VM to it.
+Ports are opened by adding _security rules_ to the **"Network security group"** (NSG) which our VM
+has been assigned to. If you followed the process above, then Azure will have automatically created
+an NSG named `GitLab-CE-nsg` and assigned the `GitLab-CE` VM to it.
-> **Note:** if you gave your VM a different name then the NSG automatically created by Azure will
+> **Note:** if you gave your VM a different name then the NSG automatically created by Azure will
also have a different name - the name you have your VM, with `-nsg` appended to it.
-You can navigate to the NSG settings via many different routes in the Azure Portal, but one of the
-simplest ways is to go to the Azure Dashboard, and then click on the Network Security Group listed
+You can navigate to the NSG settings via many different routes in the Azure Portal, but one of the
+simplest ways is to go to the Azure Dashboard, and then click on the Network Security Group listed
in the **"All resources"** tile:
![Azure - Dashboard - All resources - Network security group](img/azure-dashboard-highlight-nsg.png)
-With the **"Network security group"** blade open, click on **"Inbound security rules"** under
+With the **"Network security group"** blade open, click on **"Inbound security rules"** under
**"Settings"**:
![Azure - Network security group - Inbound security rules](img/azure-nsg-inbound-sec-rules-highlight.png)
@@ -212,18 +212,18 @@ Next, click **"Add"**:
### Which ports to open?
-Like all servers, our VM will be running many services. However, we want to open up the correct
+Like all servers, our VM will be running many services. However, we want to open up the correct
ports to enable public internet access to two services in particular:
-1. **HTTP** (port 80) - opening port 80 will enable our VM to respond to HTTP requests, allowing
-public access to the instance of GitLab running on our VM.
-1. **SSH** (port 22) - opening port 22 will enable our VM to respond to SSH connection requests,
-allowing public access (with authentication) to remote terminal sessions
-_(you'll see why we need [SSH] access to our VM [later on in this tutorial](#maintaining-your-gitlab-instance))_
+1. **HTTP** (port 80) - opening port 80 will enable our VM to respond to HTTP requests, allowing
+ public access to the instance of GitLab running on our VM.
+1. **SSH** (port 22) - opening port 22 will enable our VM to respond to SSH connection requests,
+ allowing public access (with authentication) to remote terminal sessions
+ _(you'll see why we need [SSH] access to our VM [later on in this tutorial](#maintaining-your-gitlab-instance))_
### Open HTTP on Port 80
-In the **"Add inbound security rule"** blade, let's open port 80 so that our VM will accept HTTP
+In the **"Add inbound security rule"** blade, let's open port 80 so that our VM will accept HTTP
connections:
![Azure - Add inbound security rules - HTTP](img/azure-add-inbound-sec-rule-http.png)
@@ -235,7 +235,7 @@ connections:
### Open SSH on Port 22
-Repeat the above process, adding a second Inbound security rule to open port 22, enabling our VM to
+Repeat the above process, adding a second Inbound security rule to open port 22, enabling our VM to
accept [SSH] connections:
![Azure - Add inbound security rules - SSH](img/azure-add-inbound-sec-rule-ssh.png)
@@ -245,16 +245,15 @@ accept [SSH] connections:
1. Make sure the `Action` is set to **Allow**
1. Click **"OK"**
-
-It will take a moment for Azure to add each new Inbound Security Rule (and you may need to click on
-**"Inbound security rules"** to refresh the list), but once completed, you should see the two new
+It will take a moment for Azure to add each new Inbound Security Rule (and you may need to click on
+**"Inbound security rules"** to refresh the list), but once completed, you should see the two new
rules in the list:
![Azure - Inbound security rules - List](img/azure-inbound-sec-rules-list.png)
## Connecting to GitLab
-Use the domain name you set up earlier (or the public IP address) to visit your new GitLab instance
-in your browser. If everything has gone according to plan you should be presented with the
+Use the domain name you set up earlier (or the public IP address) to visit your new GitLab instance
+in your browser. If everything has gone according to plan you should be presented with the
following page, asking you to set a _new_ password for the administrator account automatically
created by GitLab:
@@ -262,26 +261,26 @@ created by GitLab:
Enter your _new_ password into both form fields, and then click **"Change your password"**.
-Once you have changed the password you will be redirected to the GitLab login page. Use `root` as
+Once you have changed the password you will be redirected to the GitLab login page. Use `root` as
the username, enter the new password you set in the previous step, and then click **"Sign in"**:
![GitLab - Login](img/gitlab-login.png)
### Success?
-After signing in successfully, you should see the GitLab Projects page displaying a
+After signing in successfully, you should see the GitLab Projects page displaying a
**"Welcome to GitLab!"** message:
![GitLab - Projects Page](img/gitlab-home.png)
-If so, you now have a working GitLab instance on your own private Azure VM. **Congratulations!**
+If so, you now have a working GitLab instance on your own private Azure VM. **Congratulations!**
## Creating your first GitLab project
-You can skip this section if you are familiar with Git and GitLab. Otherwise, let's create our first
+You can skip this section if you are familiar with Git and GitLab. Otherwise, let's create our first
project. From the Welcome page, click **"New Project"**.
-Let's give our project a name and a description, and then accept the default values for everything
+Let's give our project a name and a description, and then accept the default values for everything
else:
1. Enter **"demo"** into the `Project path` project name field
@@ -290,12 +289,12 @@ else:
![GitLab - New Project](img/gitlab-new-project.png)
-Once the new project has been created (which should only take a moment), you'll be redirected to
+Once the new project has been created (which should only take a moment), you'll be redirected to
homepage for the project:
![GitLab - Empty Project](img/gitlab-project-home-empty.png)
-If you scroll further down the project's home page, you'll see some basic instructions on how to
+If you scroll further down the project's home page, you'll see some basic instructions on how to
set up a local clone of your new repository and push and pull from it:
![GitLab - Empty Project - Basic Instructions](img/gitlab-project-home-instructions.png)
@@ -304,50 +303,50 @@ set up a local clone of your new repository and push and pull from it:
## Maintaining your GitLab instance
-It's important to keep your GitLab environment up-to-date. The GitLab team is constantly making
-enhancements and occasionally you may need to update for security reasons. So let's review how to
-update GitLab.
+It's important to keep your GitLab environment up-to-date. The GitLab team is constantly making
+enhancements and occasionally you may need to update for security reasons. So let's review how to
+update GitLab.
### Checking our current version
To check which version of GitLab we're currently running, click on the "Admin Area" link - it's the
-the wrench icon displayed in the top-right, next to the search box.
+the wrench icon displayed in the top-right, next to the search box.
-In the following screenshot you can see an **"update asap"** notification message in the top-right.
-This particular message indicates that there is a newer version of GitLab available which contains
+In the following screenshot you can see an **"update asap"** notification message in the top-right.
+This particular message indicates that there is a newer version of GitLab available which contains
one or more security fixes:
![GitLab - update asap](img/gitlab-admin-area.png)
-Under the **"Components"** section, we can see that our VM is currently running version `8.6.5` of
-GitLab. This is the version of GitLab which was contained in the Azure Marketplace
-**"GitLab Community Edition"** offering we used to build the VM when we wrote this tutorial.
+Under the **"Components"** section, we can see that our VM is currently running version `8.6.5` of
+GitLab. This is the version of GitLab which was contained in the Azure Marketplace
+**"GitLab Community Edition"** offering we used to build the VM when we wrote this tutorial.
-> **Note:** The version of GitLab in your own VM instance may well be different, but the update
+> **Note:** The version of GitLab in your own VM instance may well be different, but the update
process will still be the same.
### Connect via SSH
-To perform an update, we need to connect directly to our Azure VM instance and run some commands
-from the terminal. Our Azure VM is actually a server running Linux (Ubuntu), so we'll need to
-connect to it using SSH ([Secure Shell][SSH]).
+To perform an update, we need to connect directly to our Azure VM instance and run some commands
+from the terminal. Our Azure VM is actually a server running Linux (Ubuntu), so we'll need to
+connect to it using SSH ([Secure Shell][SSH]).
-If you're running Windows, you'll need to connect using [PuTTY] or an equivalent Windows SSH client.
-If you're running Linux or macOS, then you already have an SSH client installed.
+If you're running Windows, you'll need to connect using [PuTTY] or an equivalent Windows SSH client.
+If you're running Linux or macOS, then you already have an SSH client installed.
-> **Note:**
-> - Remember that you will need to login with the username and password you specified
+> **Note:**
+> - Remember that you will need to login with the username and password you specified
> [when you created](#basics) your Azure VM
-> - If you need to reset your VM password, read
+> - If you need to reset your VM password, read
> [how to reset SSH credentials for a user on an Azure VM][Azure-Troubleshoot-SSH-Connection].
#### SSH from the command-line
-If you're running [SSH] from the command-line (terminal), then type in the following command to
-connect to your VM, substituting `username` and `your-azure-domain-name.com` for the correct values.
+If you're running [SSH] from the command-line (terminal), then type in the following command to
+connect to your VM, substituting `username` and `your-azure-domain-name.com` for the correct values.
-Again, remember that your Azure VM domain name will be the one you
-[set up previously in the tutorial](#set-up-a-domain-name). If you didn't set up a domain name for
+Again, remember that your Azure VM domain name will be the one you
+[set up previously in the tutorial](#set-up-a-domain-name). If you didn't set up a domain name for
your VM, you can use the IP address in its place in the following command:
```bash
@@ -357,7 +356,7 @@ Provide your password at the prompt to authenticate.
#### SSH from Windows (PuTTY)
-If you're using [PuTTY] in Windows as your [SSH] client, then you might want to take a quick
+If you're using [PuTTY] in Windows as your [SSH] client, then you might want to take a quick
read on [using PuTTY in Windows][Using-SSH-In-Putty].
### Updating GitLab
@@ -369,8 +368,8 @@ version:
sudo apt-get update && sudo apt-get install gitlab-ce
```
-This command will update GitLab and its associated components to the latest versions, so it will
-take a little time to complete. You'll see various update tasks being completed in your SSH
+This command will update GitLab and its associated components to the latest versions, so it will
+take a little time to complete. You'll see various update tasks being completed in your SSH
terminal window:
![GitLab updating](img/gitlab-ssh-update-in-progress.png)
@@ -387,23 +386,23 @@ before anything else.
#### Check out your updated GitLab
-Refresh your GitLab instance in the browser and navigate to the Admin Area. You should now have an
-up-to-date GitLab instance.
+Refresh your GitLab instance in the browser and navigate to the Admin Area. You should now have an
+up-to-date GitLab instance.
-When we wrote this tutorial our Azure VM GitLab instance was updated to the latest version at time
-of writing (`9.4.0`). You can see that the message which was previously displaying **"update asap"**
+When we wrote this tutorial our Azure VM GitLab instance was updated to the latest version at time
+of writing (`9.4.0`). You can see that the message which was previously displaying **"update asap"**
is now showing **"up-to-date"**:
![GitLab up to date](img/gitlab-admin-area-9.4.0.png)
## Conclusion
-Naturally, we believe that GitLab is a great git repository tool. However, GitLab is a whole lot
-more than that too. GitLab unifies issues, code review, CI and CD into a single UI, helping you to
-move faster from idea to production, and in this tutorial we showed you how quick and easy it is to
-set up and run your own instance of GitLab on Azure, Microsoft's cloud service.
+Naturally, we believe that GitLab is a great git repository tool. However, GitLab is a whole lot
+more than that too. GitLab unifies issues, code review, CI and CD into a single UI, helping you to
+move faster from idea to production, and in this tutorial we showed you how quick and easy it is to
+set up and run your own instance of GitLab on Azure, Microsoft's cloud service.
-Azure is a great way to experiment with GitLab, and if you decide (as we hope) that GitLab is for
+Azure is a great way to experiment with GitLab, and if you decide (as we hope) that GitLab is for
you, you can continue to use Azure as your secure, scalable cloud provider or of course run GitLab
on any cloud service you choose.
diff --git a/doc/install/installation.md b/doc/install/installation.md
index fb24d4fa0ef..61f544deabe 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -9,7 +9,8 @@ On heavily used GitLab instances the memory usage of the Sidekiq background work
Omnibus packages solve this by [letting the Sidekiq terminate gracefully](../administration/operations/sidekiq_memory_killer.md) if it uses too much memory.
After this termination Runit will detect Sidekiq is not running and will start it.
-Since installations from source don't have Runit, Sidekiq can't be terminated and its memory usage will grow over time.
+Since installations from source don't use Runit for process supervision, Sidekiq
+can't be terminated and its memory usage will grow over time.
## Select Version to Install
@@ -72,7 +73,8 @@ Install the required packages (needed to compile Ruby and native extensions to R
```sh
sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libre2-dev \
libreadline-dev libncurses5-dev libffi-dev curl openssh-server checkinstall libxml2-dev \
- libxslt-dev libcurl4-openssl-dev libicu-dev logrotate rsync python-docutils pkg-config cmake
+ libxslt-dev libcurl4-openssl-dev libicu-dev logrotate rsync python-docutils pkg-config cmake \
+ runit
```
Ubuntu 14.04 (Trusty Tahr) doesn't have the `libre2-dev` package available, but
diff --git a/doc/install/kubernetes/gitlab_chart.md b/doc/install/kubernetes/gitlab_chart.md
index 26ced45de7b..9db246b3eb3 100644
--- a/doc/install/kubernetes/gitlab_chart.md
+++ b/doc/install/kubernetes/gitlab_chart.md
@@ -5,7 +5,7 @@ This is the official way to install GitLab on a cloud native environment.
NOTE: **Kubernetes experience required:**
Our Helm charts are recommended for those who are familiar with Kubernetes.
If you're not sure if Kubernetes is for you, our
-[Omnibus GitLab packages](../README.md#install-gitlab-using-the-omnibus-gitlab-package-recommended)
+[Omnibus GitLab packages](../README.md#installing-gitlab-using-the-omnibus-gitlab-package-recommended)
are mature, scalable, support [high availability](../../administration/high_availability/README.md)
and are used today on GitLab.com.
It is not necessary to have GitLab installed on Kubernetes in order to use [GitLab Kubernetes integration](https://docs.gitlab.com/ee/user/project/clusters/index.html).
diff --git a/doc/install/kubernetes/gitlab_omnibus.md b/doc/install/kubernetes/gitlab_omnibus.md
index 2ae5485869e..c0cb7694e91 100644
--- a/doc/install/kubernetes/gitlab_omnibus.md
+++ b/doc/install/kubernetes/gitlab_omnibus.md
@@ -4,7 +4,7 @@ CAUTION: **Caution:**
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).
-For more information on available GitLab Helm Charts, see the [charts overview](index.md#chart-overview).
+For more information on available GitLab Helm Charts, see [Installing GitLab on Kubernetes](index.md).
- This GitLab-Omnibus chart has been tested on Google Kubernetes Engine and Azure Container Service.
- This work is based partially on: <https://github.com/lwolf/kubernetes-gitlab/>. GitLab would like to thank Sergey Nuzhdin for his work.
@@ -39,7 +39,7 @@ The deployment includes:
- _At least_ 4 GB of RAM available on your cluster. 41GB of storage and 2 CPU are also required.
- Kubernetes 1.4+ with Beta APIs enabled
- [Persistent Volume](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) provisioner support in the underlying infrastructure
-- A [wildcard DNS entry](#networking-prerequisites), which resolves to the external IP address
+- A [wildcard DNS entry](#networking-requirements), which resolves to the external IP address
- The `kubectl` CLI installed locally and authenticated for the cluster
- The [Helm client](https://github.com/kubernetes/helm/blob/master/docs/quickstart.md) installed locally on your machine
@@ -52,7 +52,7 @@ and [Mattermost](https://docs.gitlab.com/omnibus/gitlab-mattermost/).
To support the GitLab services and dynamic environments, a wildcard DNS entry
is required which resolves to the [load balancer](#load-balancer-ip) or
-[external IP](#external-ip). Configuration of the DNS entry will depend upon
+[external IP](#external-ip-recommended). Configuration of the DNS entry will depend upon
the DNS service being used.
#### External IP (recommended)
@@ -84,13 +84,13 @@ to configure the Wildcard DNS entry. For more information on creating a wildcard
DNS entry, consult the documentation for the DNS server you are using.
For production deployments of GitLab, we strongly recommend using a
-[external IP](#external-ip).
+[external IP](#external-ip-recommended).
## Configuring and Installing GitLab
For most installations, two parameters are required:
-- `baseDomain`: the [base domain](#networking-prerequisites) of the wildcard host entry. For example, `mycompany.io` if the wild card entry is `*.mycompany.io`.
+- `baseDomain`: the [base domain](#networking-requirements) of the wildcard host entry. For example, `mycompany.io` if the wild card entry is `*.mycompany.io`.
- `legoEmail`: Email address to use when requesting new SSL certificates from Let's Encrypt.
Other common configuration options:
@@ -105,7 +105,7 @@ For additional configuration options, consult the
### Choosing a different GitLab release version
-The version of GitLab installed is based on the `gitlab` setting (see [section](#choosing-gitlab-edition) above), and
+The version of GitLab installed is based on the `gitlab` setting (see [section](#configuring-and-installing-gitLab) above), and
the value of the corresponding helm setting: `gitlabCEImage` or `gitabEEImage`.
```yaml
diff --git a/doc/install/kubernetes/gitlab_runner_chart.md b/doc/install/kubernetes/gitlab_runner_chart.md
index 3c2f883f29d..68b2a146115 100644
--- a/doc/install/kubernetes/gitlab_runner_chart.md
+++ b/doc/install/kubernetes/gitlab_runner_chart.md
@@ -11,7 +11,7 @@ This chart configures the Runner to:
- For each new job it receives from [GitLab CI](https://about.gitlab.com/features/gitlab-ci-cd/), it will provision a
new pod within the specified namespace to run it.
-For more information on available GitLab Helm Charts, please see our [overview](index.md#chart-overview).
+For more information on available GitLab Helm Charts, please see our [overview](index.md).
## Prerequisites
@@ -33,7 +33,7 @@ In order for GitLab Runner to function, your config file **must** specify the fo
- `gitlabUrl` - the GitLab Server URL (with protocol) to register the runner against
- `runnerRegistrationToken` - The Registration Token for adding new Runners to the GitLab Server. This must be
- retrieved from your GitLab Instance. See the [GitLab Runner Documentation](../../ci/runners/README.md#creating-and-registering-a-runner) for more information.
+ retrieved from your GitLab Instance. See the [GitLab Runner Documentation](../../ci/runners/README.md) for more information.
Unless you need to specify additional configuration, you are [ready to install](#installing-gitlab-runner-using-the-helm-chart).
@@ -51,7 +51,7 @@ gitlabUrl: http://gitlab.your-domain.com/
## The Registration Token for adding new Runners to the GitLab Server. This must
## be retrieved from your GitLab Instance.
-## ref: https://docs.gitlab.com/ce/ci/runners/README.html#creating-and-registering-a-runner
+## ref: https://docs.gitlab.com/ee/ci/runners/README.html
##
runnerRegistrationToken: ""
@@ -227,7 +227,7 @@ helm repo add gitlab https://charts.gitlab.io
helm init
```
-Once you [have configured](#configuration) GitLab Runner in your `values.yml` file,
+Once you [have configured](#configuring-gitlab-runner-using-the-helm-chart) GitLab Runner in your `values.yml` file,
run the following:
```bash
@@ -236,7 +236,7 @@ helm install --namespace <NAMESPACE> --name gitlab-runner -f <CONFIG_VALUES_FILE
- `<NAMESPACE>` is the Kubernetes namespace where you want to install the GitLab Runner.
- `<CONFIG_VALUES_FILE>` is the path to values file containing your custom configuration. See the
- [Configuration](#configuration) section to create it.
+ [Configuring GitLab Runner using the Helm Chart](#configuring-gitlab-runner-using-the-helm-chart) section to create it.
## Updating GitLab Runner using the Helm Chart
@@ -247,11 +247,12 @@ helm upgrade --namespace <NAMESPACE> -f <CONFIG_VALUES_FILE> <RELEASE-NAME> gitl
```
Where:
+
- `<NAMESPACE>` is the Kubernetes namespace where GitLab Runner is installed
- `<CONFIG_VALUES_FILE>` is the path to values file containing your custom configuration. See the
- [Configuration](#configuration) section to create it.
+ [Configuring GitLab Runner using the Helm Chart](#configuring-gitlab-runner-using-the-helm-chart) section to create it.
- `<RELEASE-NAME>` is the name you gave the chart when installing it.
- In the [Install section](#installing) we called it `gitlab-runner`.
+ In the [Installing GitLab Runner using the Helm Chart](#installing-gitlab-runner-using-the-helm-chart) section, we called it `gitlab-runner`.
## Uninstalling GitLab Runner using the Helm Chart
@@ -265,4 +266,4 @@ where:
- `<NAMESPACE>` is the Kubernetes namespace where GitLab Runner is installed
- `<RELEASE-NAME>` is the name you gave the chart when installing it.
- In the [Install section](#installing) we called it `gitlab-runner`.
+ In the [Installing GitLab Runner using the Helm Chart](#installing-gitlab-runner-using-the-helm-chart) section, we called it `gitlab-runner`.
diff --git a/doc/install/kubernetes/index.md b/doc/install/kubernetes/index.md
index 281630174e7..ecc956d04e9 100644
--- a/doc/install/kubernetes/index.md
+++ b/doc/install/kubernetes/index.md
@@ -7,7 +7,7 @@ description: 'Read through the different methods to deploy GitLab on Kubernetes.
NOTE: **Kubernetes experience required:**
Our Helm charts are recommended for those who are familiar with Kubernetes.
If you're not sure if Kubernetes is for you, our
-[Omnibus GitLab packages](../README.md#install-gitlab-using-the-omnibus-gitlab-package-recommended)
+[Omnibus GitLab packages](../README.md#installing-gitlab-using-the-omnibus-gitlab-package-recommended)
are mature, scalable, support [high availability](../../administration/high_availability/README.md)
and are used today on GitLab.com.
It is not necessary to have GitLab installed on Kubernetes in order to use [GitLab Kubernetes integration](https://docs.gitlab.com/ee/user/project/clusters/index.html).
diff --git a/doc/install/kubernetes/preparation/eks.md b/doc/install/kubernetes/preparation/eks.md
index c40177c4302..ea3b075dd82 100644
--- a/doc/install/kubernetes/preparation/eks.md
+++ b/doc/install/kubernetes/preparation/eks.md
@@ -5,6 +5,7 @@ There are a few nuances to Amazon EKS which are important to be aware of, when d
## Persistent volume management
There are two methods to manage volume claims on Kubernetes:
+
1. Manually creating each persistent volume (recommended on EKS)
1. Utilizing dynamic provisioning to automatically create the persistent volumes
@@ -26,7 +27,7 @@ With EKS, there are a few important details to keep in mind:
The easiest way to solve this and still utilize dynamic provisioning is to utilize, or create, a Storage Class that is locked to a specific zone.
-> **Note**: Restricting volumes to specific zone will cause GitLab and any other application using this Storage Class to only reside in that zone. For multiple zone support, utilize [manually provisioned volumes](#manual-provisioning-of-volumes).
+> **Note**: Restricting volumes to specific zone will cause GitLab and any other application using this Storage Class to only reside in that zone. For multiple zone support, utilize [manually provisioned volumes](#manual-provisioning-of-volumes-recommended).
To create the storage class, download and edit Amazon EKS's [sample Storage Class](https://docs.aws.amazon.com/eks/latest/userguide/storage-classes.html) and add the following parameter:
diff --git a/doc/install/kubernetes/preparation/networking.md b/doc/install/kubernetes/preparation/networking.md
index 34a6130de27..b9fb9a7399f 100644
--- a/doc/install/kubernetes/preparation/networking.md
+++ b/doc/install/kubernetes/preparation/networking.md
@@ -12,7 +12,7 @@ To support the GitLab services and dynamic environments, a wildcard DNS entry is
To provision an external IP on GCP and Azure, simply request a new address from the Networking section. Ensure that the region matches the region your container cluster is created in. Note, it is important that the IP is not assigned at this point in time. It will be automatically assigned once the Helm chart is installed, to the Load Balancer.
-Set `global.hosts.externalIP` to this IP address when [deploying GitLab](../gitlab_chart.md#configuring-and-installing-gitlab).
+Set `global.hosts.externalIP` to this IP address when [deploying GitLab](../gitlab_chart.md#installing-gitlab-using-the-helm-chart).
Then, create a [wildcard DNS record](#wildcard-dns-entry) which resolves to this IP address.
@@ -35,4 +35,4 @@ Please consult the documentation for your DNS service for more information on cr
- [Google Domains](https://support.google.com/domains/answer/3290350?hl=en)
- [GoDaddy](https://www.godaddy.com/help/add-an-a-record-19238)
-Set `global.hosts.domain` to this DNS name when [deploying GitLab](../gitlab_chart.md#configuring-and-installing-gitlab).
+Set `global.hosts.domain` to this DNS name when [deploying GitLab](../gitlab_chart.md#installing-gitlab-using-the-helm-chart).
diff --git a/doc/install/kubernetes/preparation/tiller.md b/doc/install/kubernetes/preparation/tiller.md
index 107df074b3b..684df14ac2c 100644
--- a/doc/install/kubernetes/preparation/tiller.md
+++ b/doc/install/kubernetes/preparation/tiller.md
@@ -7,7 +7,7 @@ Helm consists of two parts, the `helm` client and a `tiller` server inside Kuber
NOTE: **Note:**
If you are not able to run Tiller in your cluster, for example on OpenShift, it
-is possible to use [Tiller locally](https://gitlab.com/charts/gitlab/tree/master/doc/helm#local-tiller)
+is possible to use [Tiller locally](https://docs.gitlab.com/charts/installation/tools.html#local-tiller)
and avoid deploying it into the cluster. This should only be used when Tiller
cannot be normally deployed.
@@ -15,7 +15,7 @@ cannot be normally deployed.
Tiller is deployed into the cluster and interacts with the Kubernetes API to deploy your applications. If role based access control (RBAC) is enabled, Tiller will need to be [granted permissions](#preparing-for-helm-with-rbac) to allow it to talk to the Kubernetes API.
-If RBAC is not enabled, skip to [initalizing Helm](#initialize-helm).
+If RBAC is not enabled, skip to [initializing Helm](#initialize-helm).
If you are not sure whether RBAC is enabled in your cluster, or to learn more, read through our [RBAC documentation](rbac.md).
@@ -54,19 +54,25 @@ Some clusters require authentication to use `kubectl` to create the Tiller roles
#### Upload the RBAC config as an admin user (GKE)
-For GKE, you need to grab the admin credentials:
+For GKE, you need to obtain the admin credentials. This command will output the admin password:
```
gcloud container clusters describe <cluster-name> --zone <zone> --project <project-id> --format='value(masterAuth.password)'
```
-This command will output the admin password. We need the password to authenticate with `kubectl` and create the role.
+Use the admin password to set the admin credentials. Replace the password value below with the output value from the above step:
```
-kubectl --username=admin --password=xxxxxxxxxxxxxx create -f rbac-config.yaml
+kubectl config set-credentials admin --username=admin --password=xxxxxxxxxxxxxx
```
-#### Upload the RBAC config (Other clusters)
+Once credentials have been set, create the role:
+
+```
+kubectl --user=admin create -f rbac-config.yaml
+```
+
+#### Upload the RBAC config (Non-GKE clusters)
For other clusters like Amazon EKS, you can directly upload the RBAC configuration.
diff --git a/doc/install/kubernetes/preparation/tools_installation.md b/doc/install/kubernetes/preparation/tools_installation.md
index 210bc2f9e58..d2f7a69a0af 100644
--- a/doc/install/kubernetes/preparation/tools_installation.md
+++ b/doc/install/kubernetes/preparation/tools_installation.md
@@ -16,4 +16,4 @@ You can get Helm from the project's [releases page](https://github.com/kubernete
# Next steps
-Once installed, proceed to the next [installation step](../gitlab_chart.md#prerequisites).
+Once installed, proceed to the next [installation step](../gitlab_chart.md#installing-gitlab-using-the-helm-chart).
diff --git a/doc/install/openshift_and_gitlab/index.md b/doc/install/openshift_and_gitlab/index.md
index 509020d1975..77bd9e9f7a9 100644
--- a/doc/install/openshift_and_gitlab/index.md
+++ b/doc/install/openshift_and_gitlab/index.md
@@ -11,7 +11,7 @@ date: 2016-06-28
CAUTION: **Deprecated:**
This article is deprecated. Use the official Kubernetes Helm charts for
installing GitLab to OpenShift. Check out the
-[official installation docs](https://gitlab.com/charts/gitlab/blob/master/doc/cloud/openshift.md)
+[official installation docs](https://docs.gitlab.com/charts/installation/cloud/openshift.html)
for details.
## Introduction
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index 1b7e0d1d0ab..c1f2297f3be 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -33,7 +33,7 @@ Please consider using a virtual machine to run GitLab.
## Ruby versions
-GitLab requires Ruby (MRI) 2.3. Support for Ruby versions below 2.3 (2.1, 2.2) will stop with GitLab 8.13.
+GitLab requires Ruby (MRI) 2.5. Support for Ruby versions below 2.5 (2.3, 2.4) will stop with GitLab 11.6.
You will have to use the standard MRI implementation of Ruby.
We love [JRuby](https://www.jruby.org/) and [Rubinius](https://rubinius.com) but GitLab
diff --git a/doc/integration/README.md b/doc/integration/README.md
index 7941cb42667..f5bc0693b84 100644
--- a/doc/integration/README.md
+++ b/doc/integration/README.md
@@ -11,8 +11,7 @@ See the documentation below for details on how to configure these services.
- [Akismet](akismet.md) Configure Akismet to stop spam
- [Auth0 OmniAuth](auth0.md) Enable the Auth0 OmniAuth provider
-- [Bitbucket](bitbucket.md) Import projects from Bitbucket.org and login to your GitLab instance with your
-Bitbucket.org account
+- [Bitbucket](bitbucket.md) Import projects from Bitbucket.org and login to your GitLab instance with your Bitbucket.org account
- [CAS](cas.md) Configure GitLab to sign in using CAS
- [External issue tracker](external-issue-tracker.md) Redmine, JIRA, etc.
- [Gmail actions buttons](gmail_action_buttons_for_gitlab.md) Adds GitLab actions to messages
diff --git a/doc/integration/akismet.md b/doc/integration/akismet.md
index 35024a78fca..4f7be70baf2 100644
--- a/doc/integration/akismet.md
+++ b/doc/integration/akismet.md
@@ -34,7 +34,6 @@ To use Akismet:
![Screenshot of Akismet settings](img/akismet_settings.png)
-
## Training
> *Note:* Training the Akismet filter is only available in 8.11 and above.
diff --git a/doc/integration/auth0.md b/doc/integration/auth0.md
index e2ed7a4b1ab..c67375ede50 100644
--- a/doc/integration/auth0.md
+++ b/doc/integration/auth0.md
@@ -4,18 +4,18 @@ To enable the Auth0 OmniAuth provider, you must create an Auth0 account, and an
application.
1. Sign in to the [Auth0 Console](https://auth0.com/auth/login). If you need to
-create an account, you can do so at the same link.
+ create an account, you can do so at the same link.
1. Select "New App/API".
1. Provide the Application Name ('GitLab' works fine).
1. Once created, you should see the Quick Start options. Disregard them and
-select 'Settings' above the Quick Start options.
+ select 'Settings' above the Quick Start options.
1. At the top of the Settings screen, you should see your Domain, Client ID and
-Client Secret. Take note of these as you'll need to put them in the
-configuration file. For example:
+ Client Secret. Take note of these as you'll need to put them in the
+ configuration file. For example:
- Domain: `test1234.auth0.com`
- Client ID: `t6X8L2465bNePWLOvt9yi41i`
- Client Secret: `KbveM3nqfjwCbrhaUy_gDu2dss8TIlHIdzlyf33pB7dEK5u_NyQdp65O_o02hXs2`
@@ -44,7 +44,7 @@ configuration file. For example:
```
1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration)
-for initial settings.
+ for initial settings.
1. Add the provider configuration:
@@ -76,10 +76,10 @@ for initial settings.
```
1. Change `YOUR_AUTH0_CLIENT_ID` to the client ID from the Auth0 Console page
-from step 5.
+ from step 5.
1. Change `YOUR_AUTH0_CLIENT_SECRET` to the client secret from the Auth0 Console
-page from step 5.
+ page from step 5.
1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you
installed GitLab via Omnibus or from source respectively.
diff --git a/doc/integration/cas.md b/doc/integration/cas.md
index f757edf0bc2..c6178fa44f0 100644
--- a/doc/integration/cas.md
+++ b/doc/integration/cas.md
@@ -38,7 +38,6 @@ To enable the CAS OmniAuth provider you must register your application with your
}
]
```
-
For installations from source:
@@ -65,4 +64,3 @@ On the sign in page there should now be a CAS tab in the sign in form.
[reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
[restart GitLab]: ../administration/restart_gitlab.md#installations-from-source
-
diff --git a/doc/integration/external-issue-tracker.md b/doc/integration/external-issue-tracker.md
index 075feaeead9..edd1af423ca 100644
--- a/doc/integration/external-issue-tracker.md
+++ b/doc/integration/external-issue-tracker.md
@@ -1,8 +1,8 @@
# External issue tracker
GitLab has a great issue tracker but you can also use an external one such as
-Jira, Redmine, or Bugzilla. Issue trackers are configurable per GitLab project and allow
-you to do the following:
+Jira, Redmine, YouTrack, or Bugzilla. Issue trackers are configurable per GitLab project
+and allow you to do the following:
- you can reference these external issues inside GitLab interface
(merge requests, commits, comments) and they will be automatically converted
@@ -20,6 +20,7 @@ To enable an external issue tracker you must configure the appropriate **Service
Visit the links below for details:
- [Redmine](../user/project/integrations/redmine.md)
+- [YouTrack](../user/project/integrations/youtrack.md)
- [Jira](../user/project/integrations/jira.md)
- [Bugzilla](../user/project/integrations/bugzilla.md)
- [Custom Issue Tracker](../user/project/integrations/custom_issue_tracker.md)
diff --git a/doc/integration/facebook.md b/doc/integration/facebook.md
index a67de23b17b..fe789a80eed 100644
--- a/doc/integration/facebook.md
+++ b/doc/integration/facebook.md
@@ -9,7 +9,7 @@ To enable the Facebook OmniAuth provider you must register your application with
1. Select the type "Website"
1. Enter a name for your app. This can be anything. Consider something like "&lt;Organization&gt;'s GitLab" or "&lt;Your Name&gt;'s GitLab" or
-something else descriptive.
+ something else descriptive.
1. Choose "Create New Facebook App ID"
diff --git a/doc/integration/github.md b/doc/integration/github.md
index eca9aa16499..bee68688ace 100644
--- a/doc/integration/github.md
+++ b/doc/integration/github.md
@@ -21,10 +21,10 @@ To get the credentials (a pair of Client ID and Client Secret), you must registe
- Application name: This can be anything. Consider something like `<Organization>'s GitLab` or `<Your Name>'s GitLab` or something else descriptive.
- Homepage URL: the URL to your GitLab installation. e.g., `https://gitlab.company.com`
- Application description: Fill this in if you wish.
- - Authorization callback URL: `http(s)://${YOUR_DOMAIN}/users/auth`. Please make sure the port is included if your GitLab instance is not configured on default port.
+ - Authorization callback URL: `http(s)://${YOUR_DOMAIN}/users/auth/github/callback`. Please make sure the port is included if your GitLab instance is not configured on default port.
![Register OAuth App](img/github_register_app.png)
- NOTE: Be sure to append `/users/auth` to the end of the callback URL
+ NOTE: Be sure to append `/users/auth/github/callback` to the end of the callback URL
to prevent a [OAuth2 convert
redirect](http://tetraph.com/covert_redirect/) vulnerability.
@@ -93,7 +93,6 @@ To get the credentials (a pair of Client ID and Client Secret), you must registe
args: { scope: 'user:email' } }
```
-
For GitHub Enterprise:
```
diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md
index f5f1f9486c2..4db986197f3 100644
--- a/doc/integration/omniauth.md
+++ b/doc/integration/omniauth.md
@@ -231,7 +231,6 @@ In order to enable/disable an OmniAuth provider, go to Admin Area -> Settings ->
![Enabled OAuth Sign-In sources](img/enabled-oauth-sign-in-sources.png)
-
## Disabling Omniauth
Starting from version 11.4 of GitLab, Omniauth is enabled by default. This only
diff --git a/doc/integration/saml.md b/doc/integration/saml.md
index bb3cd9a005f..8ee07a7fcdc 100644
--- a/doc/integration/saml.md
+++ b/doc/integration/saml.md
@@ -249,7 +249,6 @@ If you want some SAML authentication methods to count as 2FA on a per session ba
1. Save the file and [restart GitLab][] for the changes ot take effect
-
In addition to the changes in GitLab, make sure that your Idp is returning the
`AuthnContext`. For example:
diff --git a/doc/integration/slash_commands.md b/doc/integration/slash_commands.md
index 7d73026a6c6..cd755089be8 100644
--- a/doc/integration/slash_commands.md
+++ b/doc/integration/slash_commands.md
@@ -1,5 +1,7 @@
# Slash Commands
+> The `run` command was [introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4466) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 10.6. [Moved](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24780) to [GitLab Core](https://about.gitlab.com/pricing/) in 11.9.
+
Slash commands in Mattermost and Slack allow you to control GitLab and view GitLab content right inside your chat client, without having to leave it. For Slack, this requires a [project service configuration](../user/project/integrations/slack_slash_commands.md). Simply type the command as a message in your chat client to activate it.
Commands are scoped to a project, with a trigger term that is specified during configuration.
@@ -8,7 +10,6 @@ We suggest you use the project name as the trigger term for simplicity and clari
Taking the trigger term as `project-name`, the commands are:
-
| Command | Effect |
| ------- | ------ |
| `/project-name help` | Shows all available slash commands |
@@ -17,6 +18,7 @@ Taking the trigger term as `project-name`, the commands are:
| `/project-name issue search <query>` | Shows up to 5 issues matching `<query>` |
| `/project-name issue move <id> to <project>` | Moves issue ID `<id>` to `<project>` |
| `/project-name deploy <from> to <to>` | Deploy from the `<from>` environment to the `<to>` environment |
+| `/project-name run <job name> <arguments>` | Execute [ChatOps](../ci/chatops/README.md) job `<job name>` on `master` |
Note that if you are using the [GitLab Slack application](https://docs.gitlab.com/ee/user/project/integrations/gitlab_slack_application.html) for
your GitLab.com projects, you need to [add the `gitlab` keyword at the beginning of the command](https://docs.gitlab.com/ee/user/project/integrations/gitlab_slack_application.html#usage).
diff --git a/doc/public_access/public_access.md b/doc/public_access/public_access.md
index 81ee7338e4e..8601551e3bd 100644
--- a/doc/public_access/public_access.md
+++ b/doc/public_access/public_access.md
@@ -1,6 +1,6 @@
# Public access
-GitLab allows you to change your projects' visibility in order be accessed
+GitLab allows [Owners](../user/permissions.md) to change a projects' visibility in order to be accessed
**publicly** or **internally**.
Projects with either of these visibility levels will be listed in the
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 037b71a27b9..069c87f921a 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -442,6 +442,7 @@ backups will be copied to, and will be created if it does not exist. If the
directory that you want to copy the tarballs to is the root of your mounted
directory, just use `.` instead.
+NOTE: **Note:** Since file system performance may affect GitLab's overall performance, we do not recommend using EFS for storage. See the [relevant documentation](../administration/high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs) for more details.
For Omnibus GitLab packages:
@@ -564,7 +565,6 @@ For installations from source:
1. [Restart GitLab] for the changes to take effect.
-
```sh
sudo -u git crontab -e # Edit the crontab for the git user
```
@@ -806,9 +806,22 @@ If you have failed to [back up the secrets file](#storing-configuration-files),
then users with 2FA enabled will not be able to log into GitLab. In that case,
you need to [disable 2FA for everyone](../security/two_factor_authentication.md#disabling-2fa-for-everyone).
-In the case of CI/CD, if your project has secure variables set, you might experience
-some weird behavior, like stuck jobs or 500 errors. In that case, you can try
-deleting the `ci_variables` table from the database.
+The secrets file is also responsible for storing the encryption key for several
+columns containing sensitive information. If the key is lost, GitLab will be
+unable to decrypt those columns. This will break a wide range of functionality,
+including (but not restricted to):
+
+* [CI/CD variables](../ci/variables/README.md)
+* [Kubernetes / GCP integration](../user/project/clusters/index.md)
+* [Custom Pages domains](../user/project/pages/getting_started_part_three.md)
+* [Project error tracking](../user/project/operations/error_tracking.md)
+* [Runner authentication](../ci/runners/README.md)
+* [Project mirroring](../workflow/repository_mirroring.md)
+* [Web hooks](../user/project/integrations/webhooks.md)
+
+In the case of CI/CD, variables, you might experience some weird behavior, like
+stuck jobs or 500 errors. In that case, you can try removing contents of the
+`ci_group_variables` and `ci_project_variables` tables from the database.
CAUTION: **Warning:**
Use the following commands at your own risk, and make sure you've taken a
@@ -828,9 +841,10 @@ backup beforehand.
sudo -u git -H bundle exec rails dbconsole RAILS_ENV=production
```
-1. Check the `ci_variables` table:
+1. Check the `ci_group_variables` and `ci_variables` tables:
```sql
+ SELECT * FROM public."ci_group_variables";
SELECT * FROM public."ci_variables";
```
@@ -839,6 +853,7 @@ backup beforehand.
1. Drop the table:
```sql
+ DELETE FROM ci_group_variables;
DELETE FROM ci_variables;
```
@@ -848,5 +863,9 @@ backup beforehand.
You should now be able to visit your project, and the jobs will start
running again.
+A similar strategy can be employed for the remaining features - by removing the
+data that cannot be decrypted, GitLab can be brought back into working order,
+and the lost data can be manually replaced.
+
[reconfigure GitLab]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
[restart GitLab]: ../administration/restart_gitlab.md#installations-from-source
diff --git a/doc/raketasks/user_management.md b/doc/raketasks/user_management.md
index e1b1912ed47..571e784e530 100644
--- a/doc/raketasks/user_management.md
+++ b/doc/raketasks/user_management.md
@@ -52,7 +52,6 @@ bundle exec rake gitlab:import:all_users_to_all_groups RAILS_ENV=production
- Enable this setting to keep new users blocked until they have been cleared by the admin (default: false).
-
```
block_auto_created_users: false
```
@@ -77,7 +76,6 @@ GitLab stores the secret data enabling 2FA to work in an encrypted database
column. The encryption key for this data is known as `otp_key_base`, and is
stored in `config/secrets.yml`.
-
If that file is leaked, but the individual 2FA secrets have not, it's possible
to re-encrypt those secrets with a new encryption key. This allows you to change
the leaked key without forcing all users to change their 2FA details.
diff --git a/doc/security/img/ssh_keys_restricted_key_icon.png b/doc/security/img/ssh_keys_restricted_key_icon.png
new file mode 100644
index 00000000000..ad3749e8233
--- /dev/null
+++ b/doc/security/img/ssh_keys_restricted_key_icon.png
Binary files differ
diff --git a/doc/security/ssh_keys_restrictions.md b/doc/security/ssh_keys_restrictions.md
index 213fa5bfef5..6b6a8a06cc9 100644
--- a/doc/security/ssh_keys_restrictions.md
+++ b/doc/security/ssh_keys_restrictions.md
@@ -17,3 +17,11 @@ In the Admin area under **Settings** (`/admin/application_settings`), look for
the "Visibility and Access Controls" area:
![SSH keys restriction admin settings](img/ssh_keys_restrictions_settings.png)
+
+If a restriction is imposed on any key type, users will be unable to upload new SSH keys that don't meet the requirement. Any existing keys that don't meet it will be disabled but not removed and users will be unable to pull or push code using them.
+
+An icon will be visible to the user of a restricted key in the SSH keys section of their profile:
+
+![Restricted SSH key icon](img/ssh_keys_restricted_key_icon.png)
+
+Hovering over this icon will tell you why the key is restricted.
diff --git a/doc/system_hooks/system_hooks.md b/doc/system_hooks/system_hooks.md
index 55b678d6af5..a3698d60f6d 100644
--- a/doc/system_hooks/system_hooks.md
+++ b/doc/system_hooks/system_hooks.md
@@ -147,7 +147,7 @@ Please refer to `group_rename` and `user_rename` for that case.
"user_name": "John Smith",
"user_username": "johnsmith",
"user_id": 41,
- "project_visibility": "private"
+ "project_visibility": "visibilitylevel|private"
}
```
@@ -158,7 +158,7 @@ Please refer to `group_rename` and `user_rename` for that case.
"created_at": "2012-07-21T07:30:56Z",
"updated_at": "2012-07-21T07:38:22Z",
"event_name": "user_remove_from_team",
- "project_access": "Maintainer",
+ "access_level": "Maintainer",
"project_id": 74,
"project_name": "StoreCloud",
"project_path": "storecloud",
@@ -167,7 +167,7 @@ Please refer to `group_rename` and `user_rename` for that case.
"user_name": "John Smith",
"user_username": "johnsmith",
"user_id": 41,
- "project_visibility": "private"
+ "project_visibility": "visibilitylevel|private"
}
```
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index b72138ef24f..2e1df9d50d4 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -13,7 +13,7 @@ Starting with GitLab 11.3, the Auto DevOps pipeline is enabled by default for al
projects. If it has not been 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. A GitLab
-administrator can [change this setting](../../user/admin_area/settings/continuous_integration.html#auto-devops)
+administrator can [change this setting](../../user/admin_area/settings/continuous_integration.html#auto-devops-core-only)
in the admin area.
With Auto DevOps, the software development process becomes easier to set up
@@ -114,7 +114,7 @@ To make full use of Auto DevOps, you will need:
will need Prometheus installed somewhere (inside or outside your cluster) and
configured to scrape your Kubernetes cluster. To get response metrics
(in addition to system metrics), you need to
- [configure Prometheus to monitor NGINX](../../user/project/integrations/prometheus_library/nginx_ingress.md#configuring-prometheus-to-monitor-for-nginx-ingress-metrics).
+ [configure Prometheus to monitor NGINX](../../user/project/integrations/prometheus_library/nginx_ingress.md#configuring-nginx-ingress-monitoring).
The [Prometheus service](../../user/project/integrations/prometheus.md)
integration needs to be enabled for the project, or enabled as a
[default service template](../../user/project/integrations/services_templates.md)
@@ -166,7 +166,7 @@ them to the Kubernetes pods that run your application(s).
When using Auto DevOps, you may want to deploy different environments to
different Kubernetes clusters. This is possible due to the 1:1 connection that
-[exists between them](../../user/project/clusters/index.md#multiple-kubernetes-clusters).
+[exists between them](../../user/project/clusters/index.md#multiple-kubernetes-clusters-premium).
In the [Auto DevOps template](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml)
(used behind the scenes by Auto DevOps), there are currently 3 defined environment names that you need to know:
@@ -179,7 +179,7 @@ Those environments are tied to jobs that use [Auto Deploy](#auto-deploy), so
except for the environment scope, they would also need to have a different
domain they would be deployed to. This is why you need to define a separate
`KUBE_INGRESS_BASE_DOMAIN` variable for all the above
-[based on the environment](../../ci/variables/README.md#limiting-environment-scopes-of-variables).
+[based on the environment](https://docs.gitlab.com/ee/ci/variables/index.html#limiting-environment-scopes-of-variables-premium).
The following table is an example of how the three different clusters would
be configured.
@@ -188,7 +188,7 @@ be configured.
| ------------ | -------------- | ----------------------------- | ------------- | ------ |
| review | `review/*` | `review.example.com` | `review/*` | The review cluster which will run all [Review Apps](../../ci/review_apps/index.md). `*` is a wildcard, which means it will be used by every environment name starting with `review/`. |
| staging | `staging` | `staging.example.com` | `staging` | (Optional) The staging cluster which will run the deployments of the staging environments. You need to [enable it first](#deploy-policy-for-staging-and-production-environments). |
-| production | `production` | `example.com` | `production` | The production cluster which will run the deployments of the production environment. You can use [incremental rollouts](#incremental-rollout-to-production). |
+| production | `production` | `example.com` | `production` | The production cluster which will run the deployments of the production environment. You can use [incremental rollouts](#incremental-rollout-to-production-premium). |
To add a different cluster for each environment:
@@ -269,12 +269,12 @@ The available options are:
- **Continuous deployment to production**: Enables [Auto Deploy](#auto-deploy)
with `master` branch directly deployed to production.
- **Continuous deployment to production using timed incremental rollout**: Sets the
- [`INCREMENTAL_ROLLOUT_MODE`](#timed-incremental-rollout-to-production) variable
+ [`INCREMENTAL_ROLLOUT_MODE`](#timed-incremental-rollout-to-production-premium) variable
to `timed`, and production deployment will be executed with a 5 minute delay between
each increment in rollout.
- **Automatic deployment to staging, manual deployment to production**: Sets the
[`STAGING_ENABLED`](#deploy-policy-for-staging-and-production-environments) and
- [`INCREMENTAL_ROLLOUT_MODE`](#incremental-rollout-to-production) variables
+ [`INCREMENTAL_ROLLOUT_MODE`](#incremental-rollout-to-production-premium) variables
to `1` and `manual`. This means:
- `master` branch is directly deployed to staging.
@@ -503,7 +503,7 @@ Auto Deploy doesn't include deployments to staging or canary by default, but the
[Auto DevOps template] contains job definitions for these tasks if you want to
enable them.
-You can make use of [environment variables](#helm-chart-variables) to automatically
+You can make use of [environment variables](#environment-variables) to automatically
scale your pod replicas.
It's important to note that when a project is deployed to a Kubernetes cluster,
@@ -553,7 +553,7 @@ Herokuish](https://github.com/gliderlabs/herokuish#paths)
> [Introduced][ce-19507] in GitLab 11.0.
-For internal and private projects a [GitLab Deploy Token](../../user/project/deploy_tokens/index.md###gitlab-deploy-token)
+For internal and private projects a [GitLab Deploy Token](../../user/project/deploy_tokens/index.md#gitlab-deploy-token)
will be automatically created, when Auto DevOps is enabled and the Auto DevOps settings are saved. This Deploy Token
can be used for permanent access to the registry.
@@ -581,7 +581,7 @@ The metrics include:
In order to make use of monitoring you need to:
-1. [Deploy Prometheus](../../user/project/integrations/prometheus.md#configuring-your-own-prometheus-server-within-kubernetes) into your Kubernetes cluster
+1. [Deploy Prometheus](../../user/project/integrations/prometheus.md) into your Kubernetes cluster
1. If you would like response metrics, ensure you are running at least version
0.9.0 of NGINX Ingress and
[enable Prometheus metrics](https://github.com/kubernetes/ingress-nginx/blob/master/docs/examples/customization/custom-vts-metrics-prometheus/nginx-vts-metrics-conf.yaml).
@@ -684,7 +684,7 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac
| **Variable** | **Description** |
| ------------ | --------------- |
-| `AUTO_DEVOPS_DOMAIN` | The [Auto DevOps domain](#auto-devops-domain). By default, set automatically by the [Auto DevOps setting](#enabling-auto-devops). This variable is deprecated and [is scheduled to be removed](https://gitlab.com/gitlab-org/gitlab-ce/issues/56959). Use `KUBE_INGRESS_BASE_DOMAIN` instead. |
+| `AUTO_DEVOPS_DOMAIN` | The [Auto DevOps domain](#auto-devops-base-domain). By default, set automatically by the [Auto DevOps setting](#enablingdisabling-auto-devops). This variable is deprecated and [is scheduled to be removed](https://gitlab.com/gitlab-org/gitlab-ce/issues/56959). Use `KUBE_INGRESS_BASE_DOMAIN` instead. |
| `AUTO_DEVOPS_CHART` | The Helm Chart used to deploy your apps; defaults to the one [provided by GitLab](https://gitlab.com/charts/auto-deploy-app). |
| `AUTO_DEVOPS_CHART_REPOSITORY` | The Helm Chart repository used to search for charts; defaults to `https://charts.gitlab.io`. |
| `REPLICAS` | The number of replicas to deploy; defaults to 1. |
@@ -697,14 +697,15 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac
| `POSTGRES_USER` | The PostgreSQL user; defaults to `user`. Set it to use a custom username. |
| `POSTGRES_PASSWORD` | The PostgreSQL password; defaults to `testing-password`. Set it to use a custom password. |
| `POSTGRES_DB` | The PostgreSQL database name; defaults to the value of [`$CI_ENVIRONMENT_SLUG`](../../ci/variables/README.md#predefined-environment-variables). Set it to use a custom database name. |
+| `POSTGRES_VERSION` | Tag for the [`postgres` Docker image](https://hub.docker.com/_/postgres) to use. Defaults to `9.6.2`. |
| `BUILDPACK_URL` | The buildpack's full URL. It can point to either Git repositories or a tarball URL. For Git repositories, it is possible to point to a specific `ref`, for example `https://github.com/heroku/heroku-buildpack-ruby.git#v142` |
| `SAST_CONFIDENCE_LEVEL` | The minimum confidence level of security issues you want to be reported; `1` for Low, `2` for Medium, `3` for High; defaults to `3`.|
| `DEP_SCAN_DISABLE_REMOTE_CHECKS` | Whether remote Dependency Scanning checks are disabled; defaults to `"false"`. Set to `"true"` to disable checks that send data to GitLab central servers. [Read more about remote checks](https://gitlab.com/gitlab-org/security-products/dependency-scanning#remote-checks).|
| `DB_INITIALIZE` | From GitLab 11.4, this variable can be used to specify the command to run to initialize the application's PostgreSQL database. It runs inside the application pod. |
| `DB_MIGRATE` | From GitLab 11.4, this variable can be used to specify the command to run to migrate the application's PostgreSQL database. It runs inside the application pod. |
| `STAGING_ENABLED` | From GitLab 10.8, this variable can be used to define a [deploy policy for staging and production environments](#deploy-policy-for-staging-and-production-environments). |
-| `CANARY_ENABLED` | From GitLab 11.0, this variable can be used to define a [deploy policy for canary environments](#deploy-policy-for-canary-environments). |
-| `INCREMENTAL_ROLLOUT_MODE`| From GitLab 11.4, this variable, if present, can be used to enable an [incremental rollout](#incremental-rollout-to-production) of your application for the production environment.<br/>Set to: <ul><li>`manual`, for manual deployment jobs.</li><li>`timed`, for automatic rollout deployments with a 5 minute delay each one.</li></ul> |
+| `CANARY_ENABLED` | From GitLab 11.0, this variable can be used to define a [deploy policy for canary environments](#deploy-policy-for-canary-environments-premium). |
+| `INCREMENTAL_ROLLOUT_MODE`| From GitLab 11.4, this variable, if present, can be used to enable an [incremental rollout](#incremental-rollout-to-production-premium) of your application for the production environment.<br/>Set to: <ul><li>`manual`, for manual deployment jobs.</li><li>`timed`, for automatic rollout deployments with a 5 minute delay each one.</li></ul> |
| `TEST_DISABLED` | From GitLab 11.0, this variable can be used to disable the `test` job. If the variable is present, the job will not be created. |
| `CODE_QUALITY_DISABLED` | From GitLab 11.0, this variable can be used to disable the `codequality` job. If the variable is present, the job will not be created. |
| `SAST_DISABLED` | From GitLab 11.0, this variable can be used to disable the `sast` job. If the variable is present, the job will not be created. |
@@ -938,7 +939,7 @@ TIP: **Tip:**
You can also set this inside your [project's settings](#deployment-strategy).
This configuration based on
-[incremental rollout to production](#incremental-rollout-to-production).
+[incremental rollout to production](#incremental-rollout-to-production-premium).
Everything behaves the same way, except:
diff --git a/doc/topics/autodevops/quick_start_guide.md b/doc/topics/autodevops/quick_start_guide.md
index 9749bd63f2b..367e192b85a 100644
--- a/doc/topics/autodevops/quick_start_guide.md
+++ b/doc/topics/autodevops/quick_start_guide.md
@@ -159,15 +159,15 @@ In the **test** stage, GitLab runs various checks on the application:
- The `test` job runs unit and integration tests by detecting the language and
framework ([Auto Test](index.md#auto-test))
- The `code_quality` job checks the code quality and is allowed to fail
- ([Auto Code Quality](index.md#auto-code-quality)) **[STARTER]**
+ ([Auto Code Quality](index.md#auto-code-quality-starter)) **[STARTER]**
- The `container_scanning` job checks the Docker container if it has any
vulnerabilities and is allowed to fail ([Auto Container Scanning](index.md#auto-container-scanning))
- The `dependency_scanning` job checks if the application has any dependencies
- susceptible to vulnerabilities and is allowed to fail ([Auto Dependency Scanning](index.md#auto-dependency-scanning)) **[ULTIMATE]**
+ susceptible to vulnerabilities and is allowed to fail ([Auto Dependency Scanning](index.md#auto-dependency-scanning-ultimate)) **[ULTIMATE]**
- The `sast` job runs static analysis on the current code to check for potential
- security issues and is allowed to fail([Auto SAST](index.md#auto-sast)) **[ULTIMATE]**
+ security issues and is allowed to fail([Auto SAST](index.md#auto-sast-ultimate)) **[ULTIMATE]**
- The `license_management` job searches the application's dependencies to determine each of their
- licenses and is allowed to fail ([Auto License Management](index.md#auto-license-management)) **[ULTIMATE]**
+ licenses and is allowed to fail ([Auto License Management](index.md#auto-license-management-ultimate)) **[ULTIMATE]**
NOTE: **Note:**
As you might have noticed, all jobs except `test` are allowed to fail in the
@@ -178,7 +178,7 @@ deploys the application in Kubernetes ([Auto Deploy](index.md#auto-deploy)).
Lastly, in the **performance** stage, some performance tests will run
on the deployed application
-([Auto Browser Performance Testing](index.md#auto-browser-performance-testing)). **[PREMIUM]**
+([Auto Browser Performance Testing](index.md#auto-browser-performance-testing-premium)). **[PREMIUM]**
---
@@ -285,8 +285,8 @@ all within GitLab. Despite its automatic nature, Auto DevOps can also be configu
and customized to fit your workflow. Here are some helpful resources for further reading:
1. [Auto DevOps](index.md)
-1. [Multiple Kubernetes clusters](index.md#using-multiple-kubernetes-clusters) **[PREMIUM]**
-1. [Incremental rollout to production](index.md#incremental-rollout-to-production) **[PREMIUM]**
+1. [Multiple Kubernetes clusters](index.md#using-multiple-kubernetes-clusters-premium) **[PREMIUM]**
+1. [Incremental rollout to production](index.md#incremental-rollout-to-production-premium) **[PREMIUM]**
1. [Disable jobs you don't need with environment variables](index.md#environment-variables)
1. [Use a static IP for your cluster](../../user/project/clusters/index.md#using-a-static-ip)
1. [Use your own buildpacks to build your application](index.md#custom-buildpacks)
diff --git a/doc/topics/git/numerous_undo_possibilities_in_git/index.md b/doc/topics/git/numerous_undo_possibilities_in_git/index.md
index 7195b0f0f04..8a8021dc36d 100644
--- a/doc/topics/git/numerous_undo_possibilities_in_git/index.md
+++ b/doc/topics/git/numerous_undo_possibilities_in_git/index.md
@@ -45,7 +45,6 @@ Here's what we'll cover in this tutorial:
- [How to modify history](#how-modifying-history-is-done)
- [How to remove sensitive information from repository](#deleting-sensitive-information-from-commits)
-
### Branching strategy
[Git][git-official] is a de-centralized version control system, which means that beside regular
@@ -64,14 +63,12 @@ prevent that anything is lost or out of sync when feature is complete. You can a
read through this blog post on [Git Tips & Tricks][gitlab-git-tips-n-tricks]
to learn how to easily **do** things in Git.
-
## Undo local changes
Until you push your changes to any remote repository, they will only affect you.
That broadens your options on how to handle undoing them. Still, local changes
can be on various stages and each stage has a different approach on how to tackle them.
-
### Unstaged local changes (before you commit)
When a change is made, but it is not added to the staged tree, Git itself
@@ -315,7 +312,6 @@ In case you want to modify something introduced in commit `B`.
You can find some more examples in [below section where we explain how to modify
history](#how-modifying-history-is-done)
-
### Redoing the Undo
Sometimes you realize that the changes you undid were useful and you want them
@@ -396,8 +392,8 @@ a nicer history of your contribution.
Keep in mind that this also removes the comments attached to certain commits
in merge requests, so if you need to retain traceability in GitLab, then
modifying history is not acceptable.
-A feature-branch of a merge request is a public branch and might be used by
-other developers, but project process and rules might allow or require
+A feature-branch of a merge request is a public branch and might be used by
+other developers, but project process and rules might allow or require
you to use `git rebase` (command that changes history) to reduce number of
displayed commits on target branch after reviews are done (for example
GitLab). There is a `git merge --squash` command which does exactly that
diff --git a/doc/university/glossary/README.md b/doc/university/glossary/README.md
index cf148a424db..254e234a22c 100644
--- a/doc/university/glossary/README.md
+++ b/doc/university/glossary/README.md
@@ -706,4 +706,3 @@ Files that have been modified but are not committed. Check them by using the com
### YAML
A human-readable data serialization [language](http://www.yaml.org/about.html) that takes concepts from programming languages such as C, Perl, and Python, and ideas from XML and the data format of electronic mail.
-
diff --git a/doc/university/high-availability/aws/README.md b/doc/university/high-availability/aws/README.md
index 77a1892b656..1906655dfa5 100644
--- a/doc/university/high-availability/aws/README.md
+++ b/doc/university/high-availability/aws/README.md
@@ -3,7 +3,7 @@ comments: false
---
> **Note**: We **do not** recommend using the AWS Elastic File System (EFS), as it can result
-in [significantly degraded performance](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/doc/administration/high_availability/nfs.md#aws-elastic-file-system).
+in [significantly degraded performance](../../../administration/high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs).
# High Availability on AWS
@@ -80,7 +80,6 @@ our newly created VPC.
![Route Table](img/route_table.png)
-
### Internet Gateway
Now still on the same dashboard head over to Internet Gateways and
@@ -126,10 +125,10 @@ image below we have the settings for this article but note the
following two options which are of particular interest for HA:
1. Multi-AZ-Deployment is recommended as redundancy. Read more at
-[High Availability (Multi-AZ)](http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.MultiAZ.html)
+ [High Availability (Multi-AZ)](http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.MultiAZ.html)
1. While we chose a General Purpose (SSD) for this article a Provisioned
-IOPS (SSD) is best suited for HA. Read more about it at
-[Storage for Amazon RDS](http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Storage.html)
+ IOPS (SSD) is best suited for HA. Read more about it at
+ [Storage for Amazon RDS](http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Storage.html)
![RDS Instance Specs](img/instance_specs.png)
@@ -183,7 +182,7 @@ Another option is to build a simple NFS server using a vanilla Linux server back
by AWS Elastic Block Storage (EBS).
> **Note:** GitLab does not recommend using AWS Elastic File System (EFS). See
- details in [High Availability NFS documentation](../../../administration/high_availability/nfs.md#aws-elastic-file-system)
+ details in [High Availability NFS documentation](../../../administration/high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs)
***
@@ -238,7 +237,6 @@ running reconfigure we need to make sure all our services are tied down
so just leave the reconfigure command until after we edit our gitlab.rb
file.
-
### Extension for PostgreSQL
Connect to your new RDS instance to verify access and to install
@@ -277,8 +275,6 @@ username, and password.
Next, we only need to configure the Redis section by adding the host and
uncommenting the port.
-
-
The last configuration step is to [change the default file locations ](http://docs.gitlab.com/ee/administration/high_availability/nfs.html)
to make the EFS integration easier to manage.
@@ -344,7 +340,6 @@ text area that allows us to add a lot of custom configurations which
allows you to add a custom script for when launching an instance. Let's
add the following script to the User Data section:
-
#cloud-config
package_upgrade: true
packages:
diff --git a/doc/university/support/README.md b/doc/university/support/README.md
index 805af253367..feb90ae9bad 100644
--- a/doc/university/support/README.md
+++ b/doc/university/support/README.md
@@ -2,7 +2,6 @@
comments: false
---
-
# Support Boot Camp
**Goal:** Prepare new Service Engineers at GitLab
diff --git a/doc/university/training/end-user/README.md b/doc/university/training/end-user/README.md
index 53578a34d1c..99fb5e83387 100644
--- a/doc/university/training/end-user/README.md
+++ b/doc/university/training/end-user/README.md
@@ -2,7 +2,6 @@
comments: false
---
-
# Training
This training material is the markdown used to generate training slides
@@ -87,6 +86,7 @@ git config --global user.email you@example.com
```bash
git config --global --list
```
+
- You might want or be required to use an SSH key.
- Instructions: [SSH](http://doc.gitlab.com/ce/ssh/README.html)
@@ -205,7 +205,6 @@ git push origin squash_some_bugs
- Anyone can comment, not just the assignee
- Push corrections to the same branch
-
---
### Merge request example
@@ -395,7 +394,6 @@ git revert vs git reset
Reset removes the commit while revert removes the changes but leaves the commit
Revert is safer considering we can revert a revert
-
# Changed file
git commit -am "bug introduced"
git revert HEAD
@@ -416,6 +414,7 @@ Revert is safer considering we can revert a revert
---
### Version Control
+
- Local VCS was used with a filesystem or a simple db.
- Centralized VCS such as Subversion includes collaboration but
still is prone to data loss as the main server is the single point of
diff --git a/doc/university/training/topics/git_log.md b/doc/university/training/topics/git_log.md
index 127fdf4d44a..763ef802a04 100644
--- a/doc/university/training/topics/git_log.md
+++ b/doc/university/training/topics/git_log.md
@@ -40,7 +40,6 @@ Git log lists commit history. It allows searching and filtering.
git log --since=1.month.ago --until=3.weeks.ago
```
-
----------
## Git Log Workflow
diff --git a/doc/university/training/topics/rollback_commits.md b/doc/university/training/topics/rollback_commits.md
index ca3a64e4347..96b89e3319a 100644
--- a/doc/university/training/topics/rollback_commits.md
+++ b/doc/university/training/topics/rollback_commits.md
@@ -51,7 +51,6 @@ comments: false
1. Pull for updates
1. Push changes
-
----------
## Commands
diff --git a/doc/university/training/topics/stash.md b/doc/university/training/topics/stash.md
index f1c91fb1b37..dfd28fbcbc9 100644
--- a/doc/university/training/topics/stash.md
+++ b/doc/university/training/topics/stash.md
@@ -30,7 +30,7 @@ and we need to change to a different branch.
----------
- Every time we save a stash it gets stacked so by using list we can see all our
-stashes.
+ stashes.
```
git stash list
diff --git a/doc/update/mysql_to_postgresql.md b/doc/update/mysql_to_postgresql.md
index 7c6a14cb104..350072186ee 100644
--- a/doc/update/mysql_to_postgresql.md
+++ b/doc/update/mysql_to_postgresql.md
@@ -95,8 +95,7 @@ Now, you can use pgloader to migrate the data from MySQL to PostgreSQL:
```
1. Once the migration finishes, you should see a summary table that looks like
-the following:
-
+ the following:
```
table name read imported errors total time
@@ -243,8 +242,7 @@ Now, you can use pgloader to migrate the data from MySQL to PostgreSQL:
```
1. Once the migration finishes, you should see a summary table that looks like
-the following:
-
+ the following:
```
table name read imported errors total time
@@ -286,4 +284,3 @@ If you experience 500 errors after the migration, try to clear the cache:
``` bash
sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
```
-
diff --git a/doc/update/restore_after_failure.md b/doc/update/restore_after_failure.md
index 01c52aae7f5..faa3a6e65cb 100644
--- a/doc/update/restore_after_failure.md
+++ b/doc/update/restore_after_failure.md
@@ -80,4 +80,3 @@ exit
Once the migration is successfully marked, run the rake `db:migrate` task again.
You will likely have to repeat this process several times until all failed
migrations are marked complete.
-
diff --git a/doc/update/upgrading_postgresql_using_slony.md b/doc/update/upgrading_postgresql_using_slony.md
index d2e2cf439b5..835166837a8 100644
--- a/doc/update/upgrading_postgresql_using_slony.md
+++ b/doc/update/upgrading_postgresql_using_slony.md
@@ -270,7 +270,6 @@ sudo -u gitlab-psql /opt/gitlab/embedded/bin/slon_start 1 --conf /var/opt/gitlab
If all went well this will produce output such as:
-
```
Invoke slon for node 1 - /opt/gitlab/embedded/bin/slon -p /var/run/slony1/slony_replication_node1.pid -s 1000 -d2 slony_replication 'host=192.168.0.7 dbname=gitlabhq_production user=slony port=5432 password=hieng8ezohHuCeiqu0leeghai4aeyahp' > /var/log/gitlab/slony/node1/gitlabhq_production-2016-10-06.log 2>&1 &
Slon successfully started for cluster slony_replication, node node1
diff --git a/doc/user/abuse_reports.md b/doc/user/abuse_reports.md
index 1f4f598b121..41ee7e62b2c 100644
--- a/doc/user/abuse_reports.md
+++ b/doc/user/abuse_reports.md
@@ -25,7 +25,6 @@ To report abuse from a user's comment:
1. Complete an abuse report.
1. Click the **Send report** button.
-
NOTE: **Note:**
A URL to the reported user's comment will be
pre-filled in the abuse report's **Message** field.
diff --git a/doc/user/admin_area/index.md b/doc/user/admin_area/index.md
new file mode 100644
index 00000000000..00cea22e4e1
--- /dev/null
+++ b/doc/user/admin_area/index.md
@@ -0,0 +1,29 @@
+# GitLab Admin Area **[CORE ONLY]**
+
+The Admin Area provides a web UI for administering some features of GitLab self-managed instances.
+
+To access the Admin Area, either:
+
+- Click the Admin Area icon (the spanner or wrench icon).
+- Visit `/admin` on your self-managed instance.
+
+NOTE: **Note:**
+Only admin users can access the Admin Area.
+
+## Admin Area sections
+
+The Admin Area is made up of the following sections:
+
+| Section | Description |
+|:------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------|
+| Overview | View your GitLab Dashboard, and maintain projects, users, groups, jobs, runners, and Gitaly servers. |
+| Monitoring | View GitLab system information, and information on background jobs, logs, [health checks](monitoring/health_check.md), request profiles, and audit logs. |
+| Messages | Send and manage [broadcast messages](broadcast_messages.md) for your users. |
+| System Hooks | Configure [system hooks](../../system_hooks/system_hooks.md) for many events. |
+| Applications | Create system [OAuth applications](../../integration/oauth_provider.md) for integrations with other services. |
+| Abuse Reports | Manage [abuse reports](abuse_reports.md) submitted by your users. |
+| Deploy Keys | Create instance-wide [SSH deploy keys](../../ssh/README.md#deploy-keys). |
+| Service Templates | Create [service templates](../project/integrations/services_templates.md) for projects. |
+| Labels | Create and maintain [labels](labels.md) for your GitLab instance. |
+| Appearance | Customize [GitLab's appearance](../../customization/index.md). |
+| Settings | Modify the [settings](settings/index.md) for your GitLab instance. |
diff --git a/doc/user/admin_area/monitoring/health_check.md b/doc/user/admin_area/monitoring/health_check.md
index 43b1190fb48..c22982ac190 100644
--- a/doc/user/admin_area/monitoring/health_check.md
+++ b/doc/user/admin_area/monitoring/health_check.md
@@ -28,7 +28,6 @@ With default whitelist settings, the probes can be accessed from localhost:
- `http://localhost/-/readiness`
- `http://localhost/-/liveness`
-
The first endpoint, `/-/health/`, only checks whether the application server is running. It does
-not verify the database or other services are running. A successful response will return
a 200 status code with the following message:
diff --git a/doc/user/admin_area/settings/continuous_integration.md b/doc/user/admin_area/settings/continuous_integration.md
index d4853a5842e..01979f12a01 100644
--- a/doc/user/admin_area/settings/continuous_integration.md
+++ b/doc/user/admin_area/settings/continuous_integration.md
@@ -20,7 +20,7 @@ From now on, every existing project and newly created ones that don't have a
`.gitlab-ci.yml`, will use the Auto DevOps pipelines.
If you want to disable it for a specific project, you can do so in
-[its settings](../../../topics/autodevops/index.md#enabling-auto-devops).
+[its settings](../../../topics/autodevops/index.md##enablingdisabling-auto-devops).
## Maximum artifacts size **[CORE ONLY]**
@@ -38,7 +38,7 @@ To change it:
The default expiration time of the [job artifacts](../../../administration/job_artifacts.md)
can be set in the Admin area of your GitLab instance. The syntax of duration is
-described in [`artifacts:expire_in`](../../../ci/yaml/README.md#artifacts-expire_in)
+described in [`artifacts:expire_in`](../../../ci/yaml/README.md#artifactsexpire_in)
and the default value is `30 days`. On GitLab.com they
[never expire](../../gitlab_com/index.md#gitlab-ci-cd).
@@ -47,7 +47,7 @@ and the default value is `30 days`. On GitLab.com they
1. Hit **Save changes** for the changes to take effect.
This setting is set per job and can be overridden in
-[`.gitlab-ci.yml`](../../../ci/yaml/README.md#artifacts-expire_in).
+[`.gitlab-ci.yml`](../../../ci/yaml/README.md#artifactsexpire_in).
To disable the expiration, set it to `0`. The default unit is in seconds.
## Archive jobs **[CORE ONLY]**
diff --git a/doc/user/admin_area/settings/index.md b/doc/user/admin_area/settings/index.md
index 93767aefb51..8358fe64f18 100644
--- a/doc/user/admin_area/settings/index.md
+++ b/doc/user/admin_area/settings/index.md
@@ -14,6 +14,10 @@ include:
- [Usage statistics](usage_statistics.md)
- [Visibility and access controls](visibility_and_access_controls.md)
+NOTE: **Note:**
+You can change the [first day of the week](../../profile/preferences.md) for the entire GitLab instance
+in the **Localization** section of **Admin area > Settings > Preferences**.
+
## GitLab.com admin area settings
Most of the settings under the admin area change the behavior of the whole
diff --git a/doc/user/discussions/index.md b/doc/user/discussions/index.md
index 84f4b0b3922..1e2fad1efe9 100644
--- a/doc/user/discussions/index.md
+++ b/doc/user/discussions/index.md
@@ -248,13 +248,14 @@ For large projects with many contributors, it may be useful to stop discussions
in issues or merge requests in these scenarios:
- The project maintainer has already resolved the discussion and it is not helpful
-for continued feedback. The project maintainer has already directed new conversation
-to newer issues or merge requests.
+ for continued feedback. The project maintainer has already directed new conversation
+ to newer issues or merge requests.
- The people participating in the discussion are trolling, abusive, or otherwise
-being unproductive.
+ being unproductive.
-In these cases, a user with Maintainer permissions or higher in the project can lock (and unlock)
-an issue or a merge request, using the "Lock" section in the sidebar:
+In these cases, a user with Developer permissions or higher in the project can lock (and unlock)
+an issue or a merge request, using the "Lock" section in the sidebar. For issues,
+a user with Reporter permissions can lock (and unlock).
| Unlock | Lock |
| :-----------: | :----------: |
@@ -275,17 +276,17 @@ Additionally locked issues can not be reopened.
## Filtering notes
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/26723) in GitLab 11.5.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/26723) in GitLab 11.5.
-For issues with many comments like activity notes and user comments, sometimes
-finding useful information can be hard. There is a way to filter comments from single notes and discussions for merge requests and issues.
+For issues with many comments like activity notes and user comments, sometimes
+finding useful information can be hard. There is a way to filter comments from single notes and discussions for merge requests and issues.
From a merge request's **Discussion** tab, or from an epic/issue overview, find the filter's dropdown menu on the right side of the page, from which you can choose one of the following options:
- **Show all activity**: displays all user comments and system notes
-(issue updates, mentions from other issues, changes to the description, etc).
+ (issue updates, mentions from other issues, changes to the description, etc).
- **Show comments only**: only displays user comments in the list.
-- **Show history only**: only displays activity notes.
+- **Show history only**: only displays activity notes.
![Notes filters dropdown options](img/index_notes_filters.png)
@@ -297,21 +298,21 @@ from any device you're logged into.
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/18008) in GitLab 11.6.
-As a reviewer, you're able to suggest code changes with a simple
+As a reviewer, you're able to suggest code changes with a simple
markdown syntax in Merge Request Diff discussions. Then, the
-Merge Request author (or other users with appropriate
+Merge Request author (or other users with appropriate
[permission](../permissions.md)) is able to apply these
-suggestions with a click, which will generate a commit in
+suggestions with a click, which will generate a commit in
the Merge Request authored by the user that applied them.
1. Choose a line of code to be changed, add a new comment, then click
-on the **Insert suggestion** icon in the toolbar:
+ on the **Insert suggestion** icon in the toolbar:
![Add a new comment](img/insert_suggestion.png)
-
+
> **Note:**
The suggestion will only affect the commented line. Multi-line
- suggestions are currently not supported. Will be introduced by
+ suggestions are currently not supported. Will be introduced by
[#53310](https://gitlab.com/gitlab-org/gitlab-ce/issues/53310).
1. In the comment, add your suggestion to the pre-populated code block:
@@ -326,9 +327,9 @@ on the **Insert suggestion** icon in the toolbar:
![Apply suggestions](img/suggestion.png)
> **Note:**
- Discussions are _not_ automatically resolved. Will be introduced by
+ Discussions are _not_ automatically resolved. Will be introduced by
[#54405](https://gitlab.com/gitlab-org/gitlab-ce/issues/54405).
-
+
Once the author applies a suggestion, it will be marked with the **Applied** label,
and GitLab will create a new commit with the message `Apply suggestion to <file-name>`
and push the suggested change directly into the codebase in the merge request's branch.
diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md
index c1c9b8bf43c..762cf911fcf 100644
--- a/doc/user/gitlab_com/index.md
+++ b/doc/user/gitlab_com/index.md
@@ -60,7 +60,7 @@ Below are the current settings regarding [GitLab CI/CD](../../ci/README.md).
| Setting | GitLab.com | Default |
| ----------- | ----------------- | ------------- |
| Artifacts maximum size | 1G | 100M |
-| Artifacts [expiry time](../../ci/yaml/README.md#artifacts-expire_in) | kept forever | deleted after 30 days unless otherwise specified |
+| Artifacts [expiry time](../../ci/yaml/README.md#artifactsexpire_in) | kept forever | deleted after 30 days unless otherwise specified |
## Repository size limit
diff --git a/doc/user/group/clusters/index.md b/doc/user/group/clusters/index.md
index 9fc50741407..8d223d1c5f0 100644
--- a/doc/user/group/clusters/index.md
+++ b/doc/user/group/clusters/index.md
@@ -95,7 +95,6 @@ For example, let's say we have the following Kubernetes clusters:
| Test | `test` | Group |
| Development| `*` | Group |
-
And the following environments are set in [`.gitlab-ci.yml`](../../../ci/yaml/README.md):
```yaml
diff --git a/doc/user/group/index.md b/doc/user/group/index.md
index 5fea683a7fd..300e0115c60 100644
--- a/doc/user/group/index.md
+++ b/doc/user/group/index.md
@@ -18,9 +18,9 @@ a link to the group settings. By clicking the last button you can leave that gro
You can create groups for numerous reasons. To name a few:
- Organize related projects under the same [namespace](#namespaces), add members to that
-group and grant access to all their projects at once
+ group and grant access to all their projects at once
- Create a group, include members of your team, and make it easier to
-`@mention` all the team at once in issues and merge requests
+ `@mention` all the team at once in issues and merge requests
- Create a group for your company members, and create [subgroups](subgroups/index.md)
for each individual team. Let's say you create a group called `company-team`, and among others,
you created subgroups in this group for each individual team `backend-team`,
@@ -43,11 +43,11 @@ In GitLab, a namespace is a unique name to be used as a user name, a group name,
For example, consider a user named Alex:
1. Alex creates an account on GitLab.com with the username `alex`;
-their profile will be accessed under `https://gitlab.example.com/alex`
+ their profile will be accessed under `https://gitlab.example.com/alex`
1. Alex creates a group for their team with the groupname `alex-team`;
-the group and its projects will be accessed under `https://gitlab.example.com/alex-team`
+ the group and its projects will be accessed under `https://gitlab.example.com/alex-team`
1. Alex creates a subgroup of `alex-team` with the subgroup name `marketing`;
-this subgroup and its projects will be accessed under `https://gitlab.example.com/alex-team/marketing`
+ this subgroup and its projects will be accessed under `https://gitlab.example.com/alex-team/marketing`
By doing so:
@@ -107,7 +107,7 @@ Consider we have a group with two projects:
- On the **Group Members** page we can now add a new user to the group.
- Now because this user is a **Developer** member of the group, he automatically
-gets **Developer** access to **all projects** within that group.
+ gets **Developer** access to **all projects** within that group.
If necessary, you can increase the access level of an individual user for a specific project,
by adding them again as a new member to the project with the new permission levels.
@@ -267,9 +267,9 @@ Define project templates at a group-level by setting a group as a template sourc
### Advanced settings
- **Projects**: view all projects within that group, add members to each project,
-access each project's settings, and remove any project from the same screen.
+ access each project's settings, and remove any project from the same screen.
- **Webhooks**: configure [webhooks](../project/integrations/webhooks.md) to your group.
- **Kubernetes cluster integration**: connect your GitLab group with [Kubernetes clusters](clusters/index.md).
- **Audit Events**: view [Audit Events](https://docs.gitlab.com/ee/administration/audit_events.html#audit-events)
-for the group. **[STARTER ONLY]**
+ for the group. **[STARTER ONLY]**
- **Pipelines quota**: keep track of the [pipeline quota](../admin_area/settings/continuous_integration.md) for the group.
diff --git a/doc/user/index.md b/doc/user/index.md
index 22506b30498..71378920ed4 100644
--- a/doc/user/index.md
+++ b/doc/user/index.md
@@ -38,10 +38,10 @@ GitLab is a Git-based platform that integrates a great number of essential tools
- Hosting code in repositories with version control
- Tracking proposals for new implementations, bug reports, and feedback with a
-fully featured [Issue Tracker](project/issues/index.md#issue-tracker)
+ fully featured [Issue Tracker](project/issues/index.md#issue-tracker)
- Organizing and prioritizing with [Issue Boards](project/issues/index.md#issue-boards)
- Reviewing code in [Merge Requests](project/merge_requests/index.md) with live-preview changes per
-branch with [Review Apps](../ci/review_apps/index.md)
+ branch with [Review Apps](../ci/review_apps/index.md)
- Building, testing and deploying with built-in [Continuous Integration](../ci/README.md)
- Deploying personal and professional static websites with [GitLab Pages](project/pages/index.md)
- Integrating with Docker by using [GitLab Container Registry](project/container_registry.md)
@@ -51,9 +51,9 @@ With GitLab Enterprise Edition, you can also:
- Provide support with [Service Desk](https://docs.gitlab.com/ee/user/project/service_desk.html)
- Improve collaboration with
-[Merge Request Approvals](https://docs.gitlab.com/ee/user/project/merge_requests/index.html#merge-request-approvals),
-[Multiple Assignees for Issues](https://docs.gitlab.com/ee/user/project/issues/multiple_assignees_for_issues.html),
-and [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards)
+ [Merge Request Approvals](https://docs.gitlab.com/ee/user/project/merge_requests/index.html#merge-request-approvals),
+ [Multiple Assignees for Issues](https://docs.gitlab.com/ee/user/project/issues/multiple_assignees_for_issues.html),
+ and [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards)
- Create formal relationships between issues with [Related Issues](https://docs.gitlab.com/ee/user/project/issues/related_issues.html)
- Use [Burndown Charts](https://docs.gitlab.com/ee/user/project/milestones/burndown_charts.html) to track progress during a sprint or while working on a new version of their software.
- Leverage [Elasticsearch](https://docs.gitlab.com/ee/integration/elasticsearch.html) with [Advanced Global Search](https://docs.gitlab.com/ee/user/search/advanced_global_search.html) and [Advanced Syntax Search](https://docs.gitlab.com/ee/user/search/advanced_search_syntax.html) for faster, more advanced code search across your entire GitLab instance
@@ -77,12 +77,12 @@ build, test, and deploy your app with built-in GitLab CI/CD. Or, you can do
it all at once, from one single project.
- [Repositories](project/repository/index.md): Host your codebase in
-repositories with version control and as part of a fully integrated platform.
+ repositories with version control and as part of a fully integrated platform.
- [Issues](project/issues/index.md): Explore the best of GitLab Issues' features.
- [Merge Requests](project/merge_requests/index.md): Collaborate on code,
-reviews, live preview changes per branch, and request approvals with Merge Requests.
+ reviews, live preview changes per branch, and request approvals with Merge Requests.
- [Milestones](project/milestones/index.md): Work on multiple issues and merge
-requests towards the same target date with Milestones.
+ requests towards the same target date with Milestones.
## GitLab CI/CD
@@ -92,9 +92,9 @@ directly from GitLab. No third-party integrations needed.
- [GitLab Auto Deploy](../ci/autodeploy/index.md): Deploy your application out-of-the-box with GitLab Auto Deploy.
- [Review Apps](../ci/review_apps/index.md): Live-preview the changes introduced by a merge request with Review Apps.
- [GitLab Pages](project/pages/index.md): Publish your static site directly from
-GitLab with GitLab Pages. You can build, test, and deploy any Static Site Generator with Pages.
+ GitLab with GitLab Pages. You can build, test, and deploy any Static Site Generator with Pages.
- [GitLab Container Registry](project/container_registry.md): Build and deploy Docker
-images with Container Registry.
+ images with Container Registry.
## Account
@@ -102,13 +102,13 @@ There is a lot you can customize and configure
to enjoy the best of GitLab.
- [Settings](profile/index.md): Manage your user settings to change your personal info,
-personal access tokens, authorized applications, etc.
+ personal access tokens, authorized applications, etc.
- [Authentication](../topics/authentication/index.md): Read through the authentication
-methods available in GitLab.
+ methods available in GitLab.
- [Permissions](permissions.md): Learn the different set of permissions levels for each
-user type (guest, reporter, developer, maintainer, owner).
+ user type (guest, reporter, developer, maintainer, owner).
- [Feature highlight](feature_highlight.md): Learn more about the little blue dots
-around the app that explain certain features
+ around the app that explain certain features
- [Abuse reports](abuse_reports.md): Report abuse from users to GitLab administrators
## Groups
diff --git a/doc/user/instance_statistics/convdev.md b/doc/user/instance_statistics/convdev.md
index 52b99b69a02..ddf34b19a5a 100644
--- a/doc/user/instance_statistics/convdev.md
+++ b/doc/user/instance_statistics/convdev.md
@@ -2,25 +2,27 @@
> [Introduced][ce-30469] in GitLab 9.3.
+NOTE: **NOTE**
+Your GitLab instance's [usage ping][ping] must be activated in order to use this feature.
+
The Conversational Development Index (ConvDev Index) gives you an overview of your entire
instance's adoption of [Concurrent DevOps](https://about.gitlab.com/concurrent-devops/)
-from planning to monitoring. It displays the usage of these GitLab features over
+from planning to monitoring.
+
+This displays the usage of these GitLab features over
the last 30 days, averaged over the number of active users in that time period. It also
provides a Lead score per feature, which is calculated based on GitLab's analysis
of top-performing instances based on [usage ping data][ping] that GitLab has
-collected. Your score is compared to the lead score, expressed as a percentage.
-Your overall index score is an average of all your feature score percentages.
+collected. Your score is compared to the lead score of each feature and then expressed as a percentage at the bottom of said feature.
+Your overall index score is an average of all your feature score percentages - this percentage value is presented above all the of features on the page.
![ConvDev index](img/convdev_index.png)
The page also provides helpful links to articles and GitLab docs, to help you
improve your scores.
-Your GitLab instance's [usage ping][ping] must be activated in order to use this feature.
Usage ping data is aggregated on GitLab's servers for analysis. Your usage
-information is **not sent** to any other GitLab instances.
-
-If you have just started using GitLab, it may take a few weeks for data to be
+information is **not sent** to any other GitLab instances. If you have just started using GitLab, it may take a few weeks for data to be
collected before this feature is available.
[ce-30469]: https://gitlab.com/gitlab-org/gitlab-ce/issues/30469
diff --git a/doc/user/markdown.md b/doc/user/markdown.md
index 9a01625f3ff..a7a87773eec 100644
--- a/doc/user/markdown.md
+++ b/doc/user/markdown.md
@@ -288,7 +288,6 @@ On Linux, you can download [Noto Color Emoji](https://www.google.com/get/noto/he
Ubuntu 18.04 (like many modern Linux distros) has this font installed by default.
```
-
Sometimes you want to <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/monkey.png" width="20px" height="20px"> around a bit and add some <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/star2.png" width="20px" height="20px"> to your <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/speech_balloon.png" width="20px" height="20px">. Well we have a gift for you:
<img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/zap.png" width="20px" height="20px">You can use emoji anywhere GFM is supported. <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/v.png" width="20px" height="20px">
@@ -453,14 +452,14 @@ Color written inside backticks will be followed by a color "chip".
Examples:
- `#F00`
- `#F00A`
- `#FF0000`
- `#FF0000AA`
- `RGB(0,255,0)`
- `RGB(0%,100%,0%)`
- `RGBA(0,255,0,0.7)`
- `HSL(540,70%,50%)`
+ `#F00`
+ `#F00A`
+ `#FF0000`
+ `#FF0000AA`
+ `RGB(0,255,0)`
+ `RGB(0%,100%,0%)`
+ `RGBA(0,255,0,0.7)`
+ `HSL(540,70%,50%)`
`HSLA(540,70%,50%,0.7)`
Becomes:
@@ -987,7 +986,6 @@ while the equation for the theory of relativity is E = mc<sup>2</sup>.
The formula for water is H<sub>2</sub>O while the equation for the theory of relativity is E = mc<sup>2</sup>.
-
## Wiki-specific Markdown
The following examples show how links inside wikis behave.
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index 4976284aea4..ea3e9d0cce9 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -61,6 +61,7 @@ The following table depicts the various user permission levels in a project.
| Manage related issues **[STARTER]** | | ✓ | ✓ | ✓ | ✓ |
| Lock issue discussions | | ✓ | ✓ | ✓ | ✓ |
| Create issue from vulnerability **[ULTIMATE]** | | ✓ | ✓ | ✓ | ✓ |
+| View Error Tracking list | | ✓ | ✓ | ✓ | ✓ |
| Lock merge request discussions | | | ✓ | ✓ | ✓ |
| Create new environments | | | ✓ | ✓ | ✓ |
| Stop environments | | | ✓ | ✓ | ✓ |
@@ -96,19 +97,20 @@ The following table depicts the various user permission levels in a project.
| Manage variables | | | | ✓ | ✓ |
| Manage GitLab Pages | | | | ✓ | ✓ |
| Manage GitLab Pages domains and certificates | | | | ✓ | ✓ |
-| Remove GitLab Pages | | | | | ✓ |
+| Remove GitLab Pages | | | | ✓ | ✓ |
| View GitLab Pages protected by [access control](project/pages/introduction.md#gitlab-pages-access-control-core-only) | ✓ | ✓ | ✓ | ✓ | ✓ |
| Manage clusters | | | | ✓ | ✓ |
| Manage license policy **[ULTIMATE]** | | | | ✓ | ✓ |
| Edit comments (posted by any user) | | | | ✓ | ✓ |
+| Manage Error Tracking | | | | ✓ | ✓ |
| Switch visibility level | | | | | ✓ |
| Transfer project to another namespace | | | | | ✓ |
| Remove project | | | | | ✓ |
| Delete issues | | | | | ✓ |
-| Remove pages | | | | | ✓ |
| Force push to protected branches [^4] | | | | | |
| Remove protected branches [^4] | | | | | |
| View project Audit Events | | | | ✓ | ✓ |
+| View project statistics | | | | ✓ | ✓ |
## Project features permissions
diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md
index 83bc79925e1..8e5fe4b0fb9 100644
--- a/doc/user/profile/account/two_factor_authentication.md
+++ b/doc/user/profile/account/two_factor_authentication.md
@@ -18,7 +18,7 @@ the second factor of authentication. Once enabled, in addition to supplying your
password to login, you'll be prompted to activate your U2F device (usually by pressing
a button on it), and it will perform secure authentication on your behalf.
-The U2F workflow is only supported by Google Chrome at this point, so we _strongly_ recommend
+The U2F workflow is only [supported by](https://caniuse.com/#search=U2F) Google Chrome, Opera and Firefox at this point, so we _strongly_ recommend
that you set up both methods of two-factor authentication, so you can still access your account
from other browsers.
@@ -41,7 +41,7 @@ or a U2F device.
**On your phone:**
1. Install a compatible application. We recommend [Google Authenticator]
-\(proprietary\) or [FreeOTP] \(open source\).
+ \(proprietary\) or [FreeOTP] \(open source\).
1. In the application, add a new entry in one of two ways:
- Scan the code with your phone's camera to add the entry automatically.
- Enter the details provided to add the entry manually.
diff --git a/doc/user/profile/index.md b/doc/user/profile/index.md
index efb031b8239..a2b15d058d7 100644
--- a/doc/user/profile/index.md
+++ b/doc/user/profile/index.md
@@ -35,13 +35,13 @@ From there, you can:
- Manage [2FA](account/two_factor_authentication.md)
- Change your username and [delete your account](account/delete_account.md)
- Manage applications that can
-[use GitLab as an OAuth provider](../../integration/oauth_provider.md#introduction-to-oauth)
+ [use GitLab as an OAuth provider](../../integration/oauth_provider.md#introduction-to-oauth)
- Manage [personal access tokens](personal_access_tokens.md) to access your account via API and authorized applications
- Add and delete emails linked to your account
- Choose which email to use for notifications, web-based commits, and display on your public profile
- Manage [SSH keys](../../ssh/README.md#ssh) to access your account via SSH
- Manage your [preferences](preferences.md#syntax-highlighting-theme)
-to customize your own GitLab experience
+ to customize your own GitLab experience
- [View your active sessions](active_sessions.md) and revoke any of them if necessary
- Access your audit log, a security log of important events involving your account
@@ -89,7 +89,6 @@ To enable private profile:
1. Check the "Private profile" option.
1. Hit **Update profile settings**.
-
NOTE: **Note:**
You and GitLab admins can see your the abovementioned information on your profile even if it is private.
diff --git a/doc/user/profile/preferences.md b/doc/user/profile/preferences.md
index 7387d1810ca..db68510c46d 100644
--- a/doc/user/profile/preferences.md
+++ b/doc/user/profile/preferences.md
@@ -93,6 +93,12 @@ You can choose between 3 options:
## Localization
+### Language
+
+Select your preferred language from a list of supported languages.
+
+*This feature is experimental and translations are not complete yet.*
+
### First day of the week
The first day of the week can be customised for calendar views and date pickers.
diff --git a/doc/user/project/clusters/eks_and_gitlab/index.md b/doc/user/project/clusters/eks_and_gitlab/index.md
index 0e6d4bce153..ea09f9f1547 100644
--- a/doc/user/project/clusters/eks_and_gitlab/index.md
+++ b/doc/user/project/clusters/eks_and_gitlab/index.md
@@ -253,7 +253,7 @@ With RBAC disabled and services deployed,
[Auto DevOps](../../../../topics/autodevops/index.md) can now be leveraged
to build, test, and deploy the app.
-[Enable Auto DevOps](../../../../topics/autodevops/index.md##enablingdisabling-auto-devops-at-the-project-level)
+[Enable Auto DevOps](../../../../topics/autodevops/index.md#enablingdisabling-auto-devops-at-the-project-level)
if not already enabled. If a wildcard DNS entry was created resolving to the
Load Balancer, enter it in the `domain` field under the Auto DevOps settings.
Otherwise, the deployed app will not be externally available outside of the cluster.
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index 85a4af24dc5..0c514e005b2 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -41,7 +41,7 @@ integration, make sure the following requirements are met:
- A [billing account](https://cloud.google.com/billing/docs/how-to/manage-billing-account)
is set up and you have permissions to access it.
-- The Kubernetes Engine API is enabled. Follow the steps as outlined in the
+- The Kubernetes Engine API and related service are enabled. It should work immediately but may take up to 10 minutes after you create a project. For more information see the
["Before you begin" section of the Kubernetes Engine docs](https://cloud.google.com/kubernetes-engine/docs/quickstart#before-you-begin).
### Creating the cluster
@@ -69,6 +69,7 @@ new Kubernetes cluster to your project:
- **Number of nodes** - Enter the number of nodes you wish the cluster to have.
- **Machine type** - The [machine type](https://cloud.google.com/compute/docs/machine-types)
of the Virtual Machine instance that the cluster will be based on.
+ - **RBAC-enabled cluster** - Leave this checked if using default GKE creation options, see the [RBAC section](#role-based-access-control-rbac-core-only) for more information.
1. Finally, click the **Create Kubernetes cluster** button.
After a couple of minutes, your cluster will be ready to go. You can now proceed
@@ -86,15 +87,20 @@ To add an existing Kubernetes cluster to your project:
1. Click **Add Kubernetes cluster**.
1. Click **Add an existing Kubernetes cluster** and fill in the details:
- **Kubernetes cluster name** (required) - The name you wish to give the cluster.
- - **Environment scope** (required)- The
+ - **Environment scope** (required) - The
[associated environment](#setting-the-environment-scope) to this cluster.
- **API URL** (required) -
It's the URL that GitLab uses to access the Kubernetes API. Kubernetes
exposes several APIs, we want the "base" URL that is common to all of them,
e.g., `https://kubernetes.example.com` rather than `https://kubernetes.example.com/api/v1`.
- - **CA certificate** (optional) -
- If the API is using a self-signed TLS certificate, you'll also need to include
- the `ca.crt` contents here.
+ - **CA certificate** (required) - A valid Kubernetes certificate is needed to authenticate to the EKS cluster. We will use the certificate created by default.
+ - List the secrets with `kubectl get secrets`, and one should named similar to
+ `default-token-xxxxx`. Copy that token name for use below.
+ - Get the certificate by running this command:
+
+ ```sh
+ kubectl get secret <secret name> -o jsonpath="{['data']['ca\.crt']}" | base64 --decode
+ ```
- **Token** -
GitLab authenticates against Kubernetes using service tokens, which are
scoped to a particular `namespace`.
@@ -102,36 +108,81 @@ To add an existing Kubernetes cluster to your project:
[`cluster-admin`](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles)
privileges.** To create this service account:
- 1. Create a `gitlab` service account in the `default` namespace:
+ 1. Create a file called `gitlab-admin-service-account.yaml` with contents:
- ```bash
- kubectl create -f - <<EOF
- apiVersion: v1
- kind: ServiceAccount
- metadata:
- name: gitlab
- namespace: default
- EOF
+ ```yaml
+ apiVersion: v1
+ kind: ServiceAccount
+ metadata:
+ name: gitlab-admin
+ namespace: kube-system
```
- 1. Create a cluster role binding to give the `gitlab` service account
- `cluster-admin` privileges:
+
+ 2. Apply the service account to your cluster:
```bash
- kubectl create -f - <<EOF
+ kubectl apply -f gitlab-admin-service-account.yaml
+ ```
+
+ Output:
+
+ ```bash
+ serviceaccount "gitlab-admin" created
+ ```
+
+ 3. Create a file called `gitlab-admin-cluster-role-binding.yaml` with contents:
+
+ ```yaml
+ apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
- apiVersion: rbac.authorization.k8s.io/v1
metadata:
- name: gitlab-cluster-admin
- subjects:
- - kind: ServiceAccount
- name: gitlab
- namespace: default
+ name: gitlab-admin
roleRef:
+ apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
- apiGroup: rbac.authorization.k8s.io
- EOF
+ subjects:
+ - kind: ServiceAccount
+ name: gitlab-admin
+ namespace: kube-system
+ ```
+
+ 4. Apply the cluster role binding to your cluster:
+
+ ```bash
+ kubectl apply -f gitlab-admin-cluster-role-binding.yaml
+ ```
+
+ Output:
+
+ ```bash
+ clusterrolebinding "gitlab-admin" created
```
+
+ 5. Retrieve the token for the `gitlab-admin` service account:
+
+ ```bash
+ kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep gitlab-admin | awk '{print $1}')
+ ```
+
+ Copy the `<authentication_token>` value from the output:
+
+ ```yaml
+ Name: gitlab-admin-token-b5zv4
+ Namespace: kube-system
+ Labels: <none>
+ Annotations: kubernetes.io/service-account.name=gitlab-admin
+ kubernetes.io/service-account.uid=bcfe66ac-39be-11e8-97e8-026dce96b6e8
+
+ Type: kubernetes.io/service-account-token
+
+ Data
+ ====
+ ca.crt: 1025 bytes
+ namespace: 11 bytes
+ token: <authentication_token>
+ ```
+
NOTE: **Note:**
For GKE clusters, you will need the
`container.clusterRoleBindings.create` permission to create a cluster
@@ -176,12 +227,19 @@ applications running on the cluster.
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24580) in GitLab 11.8.
-Domains at the cluster level permit support for multiple domains
-per [multiple Kubernetes clusters](#multiple-kubernetes-clusters-premium). When specifying a domain,
-this will be automatically set as an environment variable (`KUBE_INGRESS_BASE_DOMAIN`) during
-the [Auto DevOps](../../../topics/autodevops/index.md) stages.
+NOTE: **Note:**
+You do not need to specify a base domain on cluster settings when using GitLab Serverless. The domain in that case
+will be specified as part of the Knative installation. See [Installing Applications](#installing-applications).
+
+Specifying a base domain will automatically set `KUBE_INGRESS_BASE_DOMAIN` as an environment variable.
+If you are using [Auto DevOps](../../../topics/autodevops/index.md), this domain will be used for the different
+stages. For example, Auto Review Apps and Auto Deploy.
+
+The domain should have a wildcard DNS configured to the Ingress IP address. After ingress has been installed (see [Installing Applications](#installing-applications)),
+you can either:
-The domain should have a wildcard DNS configured to the Ingress IP address.
+- Create an `A` record that points to the Ingress IP address with your domain provider.
+- Enter a wildcard DNS address using a service such as nip.io or xip.io. For example, `192.168.1.1.xip.io`.
## Access controls
@@ -307,6 +365,28 @@ you should be careful as GitLab cannot detect it. In this case, installing
Tiller via the applications will result in the cluster having it twice, which
can lead to confusion during deployments.
+### Upgrading applications
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24789)
+in GitLab 11.8.
+
+Users can perform a one-click upgrade for the GitLab Runner application,
+when there is an upgrade available.
+
+To upgrade the GitLab Runner application:
+
+1. Navigate to your project's **Operations > Kubernetes**.
+1. Select your cluster.
+1. Click the **Upgrade** button for the Runnner application.
+
+The **Upgrade** button will not be shown if there is no upgrade
+available.
+
+NOTE: **Note:**
+Upgrades will reset values back to the values built into the `runner`
+chart plus the values set by
+[`values.yaml`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/vendor/runner/values.yaml)
+
## Getting the external IP address
NOTE: **Note:**
diff --git a/doc/user/project/clusters/runbooks/index.md b/doc/user/project/clusters/runbooks/index.md
index e1b8dc07b50..54c475a1762 100644
--- a/doc/user/project/clusters/runbooks/index.md
+++ b/doc/user/project/clusters/runbooks/index.md
@@ -4,6 +4,10 @@ Runbooks are a collection of documented procedures that explain how to
carry out a particular process, be it starting, stopping, debugging,
or troubleshooting a particular system.
+Using [Jupyter Notebooks](https://jupyter.org/) and the [Rubix library](https://github.com/Nurtch/rubix),
+users can get started writing their own executable runbooks.
+
+
## Overview
Historically, runbooks took the form of a decision tree or a detailed
@@ -23,7 +27,7 @@ runbooks. A sample runbook is provided, showcasing common operations.
**<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
Watch this [video](https://www.youtube.com/watch?v=Q_OqHIIUPjE)
-for an overview of how this is acomplished in GitLab!**
+for an overview of how this is accomplished in GitLab!**
## Requirements
@@ -36,7 +40,7 @@ To create an executable runbook, you will need:
can run the helm CLI in a safe environment.
1. **Ingress** - Ingress can provide load balancing, SSL termination, and name-based
virtual hosting. It acts as a web proxy for your applications.
-1. **JupyterHub** - JupyterHub is a multi-user service for managing notebooks across
+1. **JupyterHub** - [JupyterHub](https://jupyterhub.readthedocs.io/) is a multi-user service for managing notebooks across
a team. Jupyter Notebooks provide a web-based interactive programming environment
used for data analysis, visualization, and machine learning.
diff --git a/doc/user/project/clusters/serverless/img/app-domain.png b/doc/user/project/clusters/serverless/img/app-domain.png
deleted file mode 100644
index d113dfadd2e..00000000000
--- a/doc/user/project/clusters/serverless/img/app-domain.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/clusters/serverless/img/dns-entry.png b/doc/user/project/clusters/serverless/img/dns-entry.png
index 678743f256e..351e40b77d5 100644
--- a/doc/user/project/clusters/serverless/img/dns-entry.png
+++ b/doc/user/project/clusters/serverless/img/dns-entry.png
Binary files differ
diff --git a/doc/user/project/clusters/serverless/img/install-knative.png b/doc/user/project/clusters/serverless/img/install-knative.png
index 93b1cbe602f..ecc2f8fb481 100644
--- a/doc/user/project/clusters/serverless/img/install-knative.png
+++ b/doc/user/project/clusters/serverless/img/install-knative.png
Binary files differ
diff --git a/doc/user/project/clusters/serverless/img/serverless-page.png b/doc/user/project/clusters/serverless/img/serverless-page.png
index 814b8532205..a872fda7740 100644
--- a/doc/user/project/clusters/serverless/img/serverless-page.png
+++ b/doc/user/project/clusters/serverless/img/serverless-page.png
Binary files differ
diff --git a/doc/user/project/clusters/serverless/index.md b/doc/user/project/clusters/serverless/index.md
index aa1e165e3a2..2871510d5ed 100644
--- a/doc/user/project/clusters/serverless/index.md
+++ b/doc/user/project/clusters/serverless/index.md
@@ -19,14 +19,23 @@ For more information on Knative, visit the [Knative docs repo](https://github.co
With GitLab serverless, you can deploy both functions-as-a-service (FaaS) and serverless applications.
-## Requirements
+## Prerequisites
To run Knative on Gitlab, you will need:
+1. **Existing GitLab project:** You will need a GitLab project to associate all resources. The simplest way to get started:
+
+ - If you are planning on deploying functions, clone the [functions example project](https://gitlab.com/knative-examples/functions) to get started.
+ - If you are planning on deploying a serverless application, clone the sample [Knative Ruby App](https://gitlab.com/knative-examples/knative-ruby-app) to get started.
+
1. **Kubernetes Cluster:** An RBAC-enabled Kubernetes cluster is required to deploy Knative.
The simplest way to get started is to add a cluster using [GitLab's GKE integration](../index.md#adding-and-creating-a-new-gke-cluster-via-gitlab).
+ The set of minimum recommended cluster specifications to run Knative is 3 nodes, 6 vCPUs, and 22.50 GB memory.
1. **Helm Tiller:** Helm is a package manager for Kubernetes and is required to install
Knative.
+1. **GitLab Runner:** A runner is required to run the CI jobs that will deploy serverless
+ applications or functions onto your cluster. You can install the GitLab Runner
+ onto the existing Kubernetes cluster. See [Installing Applications](../index.md#installing-applications) for more information.
1. **Domain Name:** Knative will provide its own load balancer using Istio. It will provide an
external IP address for all the applications served by Knative. You will be prompted to enter a
wildcard domain where your applications will be served. Configure your DNS server to use the
@@ -39,20 +48,22 @@ To run Knative on Gitlab, you will need:
runtime being used.
1. **`Dockerfile`** (for [applications only](#deploying-serverless-applications): Knative requires a `Dockerfile` in order to build your application. It should be included
at the root of your project's repo and expose port `8080`.
+1. **Prometheus** (optional): Installing Prometheus allows you to monitor the scale and traffic of your serverless function/application.
+ See [Installing Applications](../index.md#installing-applications) for more information.
## Installing Knative via GitLab's Kubernetes integration
NOTE: **Note:**
The minimum recommended cluster size to run Knative is 3-nodes, 6 vCPUs, and 22.50 GB memory. **RBAC must be enabled.**
-1. [Add a Kubernetes cluster](../index.md) and install Helm.
-1. Once Helm has been successfully installed, on the Knative app section, enter the domain to be used with
- your application and click "Install".
+1. [Add a Kubernetes cluster](../index.md) and [install Helm](../index.md#installing-applications).
+1. Once Helm has been successfully installed, scroll down to the Knative app section. Enter the domain to be used with
+ your application/functions (e.g. `example.com`) and click **Install**.
![install-knative](img/install-knative.png)
1. After the Knative installation has finished, you can wait for the IP address to be displayed in the
- **Knative IP Address** field or retrieve the Istio Ingress IP address by running the following command:
+ **Knative IP Address** field (takes up to 5 minutes) or retrieve the Istio Ingress IP address by running the following command:
```bash
kubectl get svc --namespace=istio-system knative-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip} '
@@ -64,14 +75,23 @@ The minimum recommended cluster size to run Knative is 3-nodes, 6 vCPUs, and 22.
35.161.143.124 my-machine-name:~ my-user$
```
+ NOTE: **Note:**
+ Running `kubectl` commands on your cluster requires setting up access to the cluster first.
+ For clusters created on GKE, see [GKE Cluster Access](https://cloud.google.com/kubernetes-engine/docs/how-to/cluster-access-for-kubectl),
+ for other platforms [Install kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/).
+
1. The ingress is now available at this address and will route incoming requests to the proper service based on the DNS
name in the request. To support this, a wildcard DNS A record should be created for the desired domain name. For example,
- if your Knative base domain is `knative.info` then you need to create an A record with domain `*.knative.info`
+ if your Knative base domain is `example.com` then you need to create an A record with domain `*.example.com`
pointing the ip address of the ingress.
![dns entry](img/dns-entry.png)
-## Deploying Functions
+NOTE: **Note:**
+You can deploy either [functions](#deploying-functions) or [serverless applications](#deploying-serverless-applications)
+on a given project but not both. The current implementation makes use of a `serverless.yml` file to signal a FaaS project.
+
+## Deploying functions
> Introduced in GitLab 11.6.
@@ -84,9 +104,9 @@ Currently the following [runtimes](https://gitlab.com/triggermesh/runtimes) are
- node.js
- kaniko
-You can find all the files referenced in this doc in the [functions example project](https://gitlab.com/knative-examples/functions).
+You can find and import all the files referenced in this doc in the **[functions example project](https://gitlab.com/knative-examples/functions)**.
-Follow these steps to deploy a function using the Node.js runtime to your Knative instance:
+Follow these steps to deploy a function using the Node.js runtime to your Knative instance (you can skip these steps if you've cloned the example project):
1. Create a directory that will house the function. In this example we will create a directory called `echo` at the root of the project.
@@ -138,7 +158,6 @@ Follow these steps to deploy a function using the Node.js runtime to your Knativ
FUNCTION: echo
```
-
The `serverless.yml` file references both an `echo` directory (under `buildargs`) and an `echo` file (under `handler`),
which is a reference to `echo.js` in the [repository](https://gitlab.com/knative-examples/functions). Additionally, it
contains three sections with distinct parameters:
@@ -150,7 +169,6 @@ contains three sections with distinct parameters:
| `service` | Name for the Knative service which will serve the function. |
| `description` | A short description of the `service`. |
-
### `provider`
| Parameter | Description |
@@ -182,7 +200,7 @@ appear under **Operations > Serverless**.
This page contains all functions available for the project, the description for
accessing the function, and, if available, the function's runtime information.
The details are derived from the Knative installation inside each of the project's
-Kubernetes cluster.
+Kubernetes cluster. Click on each function to obtain detailed scale and invocation data.
The function details can be retrieved directly from Knative on the cluster:
@@ -190,19 +208,30 @@ The function details can be retrieved directly from Knative on the cluster:
kubectl -n "$KUBE_NAMESPACE" get services.serving.knative.dev
```
-The sample function can now be triggered from any HTTP client using a simple `POST` call.
+The sample function can now be triggered from any HTTP client using a simple `POST` call:
+
+ 1. Using curl (replace the URL on the last line with the URL of your application):
+
+ ```bash
+ curl \
+ --header "Content-Type: application/json" \
+ --request POST \
+ --data '{"GitLab":"FaaS"}' \
+ <http://functions-echo.functions-1.functions.example.com/>
+ ```
+ 2. Using a web-based tool (ie. postman, restlet, etc)
-![function exection](img/function-execution.png)
+ ![function execution](img/function-execution.png)
## Deploying Serverless applications
> Introduced in GitLab 11.5.
NOTE: **Note:**
-You can reference the sample [Knative Ruby App](https://gitlab.com/knative-examples/knative-ruby-app) to get started.
+You can reference and import the sample [Knative Ruby App](https://gitlab.com/knative-examples/knative-ruby-app) to get started.
Add the following `.gitlab-ci.yml` to the root of your repository
-(you may skip this step if using the sample [Knative Ruby App](https://gitlab.com/knative-examples/knative-ruby-app) mentioned above):
+(you may skip this step if you've previously cloned the sample [Knative Ruby App](https://gitlab.com/knative-examples/knative-ruby-app) mentioned above):
```yaml
stages:
@@ -238,11 +267,7 @@ With all the pieces in place, the next time a CI pipeline runs, the Knative appl
### Obtain the URL for the Knative deployment
-Go to the **Operations > Serverless** page to find the URL for your deployment in the **Domain** column.
-
-![app domain](img/app-domain.png)
-
-Alternatively, use the CI/CD deployment job output to obtain the deployment URL. Once all the stages of the pipeline finish, click the **deploy** stage.
+Go to the **CI/CD > Pipelines** and click on the pipeline that deployed your app. Once all the stages of the pipeline finish, click the **deploy** stage.
![deploy stage](img/deploy-stage.png)
@@ -264,7 +289,7 @@ registry.staging.gitlab.com/danielgruesso/knative
$ tm -n "$KUBE_NAMESPACE" --config "$KUBECONFIG" deploy service "$CI_PROJECT_NAME" --from-image "$CI_REGISTRY_IMAGE" --wait
Deployment started. Run "tm -n knative-4342902 describe service knative" to see the details
Waiting for ready state.......
-Service domain: knative.knative-4342902.knative.info
+Service domain: knative.knative-4342902.example.com
Job succeeded
```
diff --git a/doc/user/project/container_registry.md b/doc/user/project/container_registry.md
index 638b73bfcb6..dec6eac2508 100644
--- a/doc/user/project/container_registry.md
+++ b/doc/user/project/container_registry.md
@@ -47,7 +47,6 @@ to enable it.
> - To move or rename a repository with a container registry you will have to
> delete all existing images.
-
If you visit the **Registry** link under your project's menu, you can see the
explicit instructions to login to the Container Registry using your GitLab
credentials.
diff --git a/doc/user/project/cycle_analytics.md b/doc/user/project/cycle_analytics.md
index b98221bea61..05127b274e0 100644
--- a/doc/user/project/cycle_analytics.md
+++ b/doc/user/project/cycle_analytics.md
@@ -46,7 +46,7 @@ You can see that there are seven stages in total:
## How the data is measured
Cycle Analytics records cycle time and data based on the project issues with the
-exception of the staging and production stages, where only data deployed to
+exception of the staging and production stages, where only data deployed to
production are measured.
Specifically, if your CI is not set up and you have not defined a `production`
@@ -80,13 +80,13 @@ Here's a little explanation of how this works behind the scenes:
To sum up, anything that doesn't follow the [GitLab flow] won't be tracked at all.
So, the Cycle Analytics dashboard won't present any data:
+
- For merge requests that do not close an issue.
- For issues not labeled with a label present in the Issue Board.
- For issues not assigned a milestone.
- For staging and production stages, if the project has no `production` or `production/*`
environment.
-
## Example workflow
Below is a simple fictional workflow of a single cycle that happens in a
@@ -159,7 +159,6 @@ Learn more about Cycle Analytics in the following resources:
- [Cycle Analytics feature preview](https://about.gitlab.com/2016/09/16/feature-preview-introducing-cycle-analytics/)
- [Cycle Analytics feature highlight](https://about.gitlab.com/2016/09/21/cycle-analytics-feature-highlight/)
-
[board]: issue_board.md#creating-a-new-list
[ce-5986]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5986
[ce-20975]: https://gitlab.com/gitlab-org/gitlab-ce/issues/20975
diff --git a/doc/user/project/description_templates.md b/doc/user/project/description_templates.md
index 7c94f4ef4d8..08647caaf07 100644
--- a/doc/user/project/description_templates.md
+++ b/doc/user/project/description_templates.md
@@ -93,4 +93,3 @@ Possible fixes
[ce-4981]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4981
[gitlab-ce-templates]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/.gitlab
-
diff --git a/doc/user/project/import/bitbucket_server.md b/doc/user/project/import/bitbucket_server.md
index ebf87890cfd..d51a0c0ccca 100644
--- a/doc/user/project/import/bitbucket_server.md
+++ b/doc/user/project/import/bitbucket_server.md
@@ -22,14 +22,14 @@ Import your projects from Bitbucket Server to GitLab with minimal effort.
## Limitations
1. Currently GitLab doesn't allow comments on arbitrary lines of code, so any
-Bitbucket comments out of bounds will be inserted as comments in the merge
-request.
+ Bitbucket comments out of bounds will be inserted as comments in the merge
+ request.
1. Bitbucket Server allows multiple levels of threading. GitLab
-import will collapse this into one discussion and quote part of the original
-comment.
+ import will collapse this into one discussion and quote part of the original
+ comment.
1. Declined pull requests have unreachable commits, which prevents the GitLab
-importer from generating a proper diff. These pull requests will show up as
-empty changes.
+ importer from generating a proper diff. These pull requests will show up as
+ empty changes.
1. Attachments in Markdown are currently not imported.
1. Task lists are not imported.
1. Emoji reactions are not imported
diff --git a/doc/user/project/import/fogbugz.md b/doc/user/project/import/fogbugz.md
index 17222c53675..13409c93929 100644
--- a/doc/user/project/import/fogbugz.md
+++ b/doc/user/project/import/fogbugz.md
@@ -23,6 +23,6 @@ users to GitLab users.
![Import Project](img/fogbugz_import_select_project.png)
1. Once the import has finished click the link to take you to the project
-dashboard. Follow the directions to push your existing repository.
+ dashboard. Follow the directions to push your existing repository.
![Finished](img/fogbugz_import_finished.png)
diff --git a/doc/user/project/import/github.md b/doc/user/project/import/github.md
index cf99dded5e2..63b90dd76fd 100644
--- a/doc/user/project/import/github.md
+++ b/doc/user/project/import/github.md
@@ -67,7 +67,7 @@ developer documentation.
Before you begin, ensure that any GitHub users who you want to map to GitLab users have either:
- A GitLab account that has logged in using the GitHub icon
-\- or -
+ \- or -
- A GitLab account with an email address that matches the [public email address](https://help.github.com/articles/setting-your-commit-email-address-on-github/) of the GitHub user
User-matching attempts occur in that order, and if a user is not identified either way, the activity is associated with
diff --git a/doc/user/project/index.md b/doc/user/project/index.md
index 6a1aadf058e..0dc50d28cb0 100644
--- a/doc/user/project/index.md
+++ b/doc/user/project/index.md
@@ -8,7 +8,7 @@ Your projects can be [available](../../public_access/public_access.md)
publicly, internally, or privately, at your choice. GitLab does not limit
the number of private projects you create.
-## Project's features
+## Project features
When you create a project in GitLab, you'll have access to a large number of
[features](https://about.gitlab.com/features/):
@@ -19,7 +19,7 @@ When you create a project in GitLab, you'll have access to a large number of
- [Issue Boards](issue_board.md): Organize and prioritize your workflow
- [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards): Allow your teams to create their own workflows (Issue Boards) for the same project **[STARTER]**
- [Repositories](repository/index.md): Host your code in a fully
-integrated platform
+ integrated platform
- [Branches](repository/branches/index.md): use Git branching strategies to
collaborate on code
- [Protected branches](protected_branches.md): Prevent collaborators
@@ -29,7 +29,7 @@ integrated platform
- [Signing commits](gpg_signed_commits/index.md): use GPG to sign your commits
- [Deploy tokens](deploy_tokens/index.md): Manage project-based deploy tokens that allow permanent access to the repository and Container Registry.
- [Merge Requests](merge_requests/index.md): Apply your branching
-strategy and get reviewed by your team
+ strategy and get reviewed by your team
- [Merge Request Approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html): Ask for approval before
implementing a change **[STARTER]**
- [Fix merge conflicts from the UI](merge_requests/resolve_conflicts.md):
@@ -38,13 +38,13 @@ strategy and get reviewed by your team
of the changes proposed in a merge request in a per-branch basis
- [Labels](labels.md): Organize issues and merge requests by labels
- [Time Tracking](../../workflow/time_tracking.md): Track estimate time
-and time spent on
+ and time spent on
the conclusion of an issue or merge request
- [Milestones](milestones/index.md): Work towards a target date
- [Description templates](description_templates.md): Define context-specific
-templates for issue and merge request description fields for your project
+ templates for issue and merge request description fields for your project
- [Slash commands (quick actions)](quick_actions.md): Textual shortcuts for
-common actions on issues or merge requests
+ common actions on issues or merge requests
- [Web IDE](web_ide/index.md)
**GitLab CI/CD:**
@@ -68,7 +68,7 @@ common actions on issues or merge requests
- [Kubernetes cluster integration](clusters/index.md): Connecting your GitLab project
with a Kubernetes cluster
- [GitLab Pages](pages/index.md): Build, test, and deploy your static
-website with GitLab Pages
+ website with GitLab Pages
**Other features:**
@@ -76,13 +76,13 @@ website with GitLab Pages
- [Snippets](../snippets.md): store, share and collaborate on code snippets.
- [Cycle Analytics](cycle_analytics.md): review your development lifecycle.
- [Syntax highlighting](highlighting.md): an alternative to customize
-your code blocks, overriding GitLab's default choice of language.
+ your code blocks, overriding GitLab's default choice of language.
- [Badges](badges.md): badges for the project overview.
- [Releases](releases/index.md): a way to track deliverables in your project as snapshot in time of
-the source, build output, and other metadata or artifacts
-associated with a released version of your code.
+ the source, build output, and other metadata or artifacts
+ associated with a released version of your code.
-### Project's integrations
+### Project integrations
[Integrate your project](integrations/index.md) with Jira, Mattermost,
Kubernetes, Slack, and a lot more.
@@ -96,7 +96,7 @@ Learn how to [create a new project](../../gitlab-basics/create-project.md) in Gi
You can [fork a project](../../gitlab-basics/fork-project.md) in order to:
- Collaborate on code by forking a project and creating a merge request
-from your fork to the upstream project
+ from your fork to the upstream project
- Fork a sample project to work on the top of that
## Project settings
@@ -116,7 +116,7 @@ Read through the documentation on [project settings](settings/index.md).
- [Export a project from GitLab](settings/import_export.md#exporting-a-project-and-its-data)
- [Importing and exporting projects between GitLab instances](settings/import_export.md)
-## Project's members
+## Project members
Learn how to [add members to your projects](members/index.md).
@@ -170,3 +170,23 @@ password <personal_access_token>
To quickly access a project from the GitLab UI using the project ID,
visit the `/projects/:id` URL in your browser or other tool accessing the project.
+
+## Project APIs
+
+There are numerous [APIs](../../api/README.md) to use with your projects:
+
+- [Badges](../../api/project_badges.md)
+- [Clusters](../../api/project_clusters.md)
+- [Discussions](../../api/discussions.md)
+- [General](../../api/projects.md)
+- [Import/export](../../api/project_import_export.md)
+- [Issue Board](../../api/boards.md)
+- [Labels](../../api/labels.md)
+- [Markdown](../../api/markdown.md)
+- [Merge Requests](../../api/merge_requests.md)
+- [Milestones](../../api/milestones.md)
+- [Services](../../api/services.md)
+- [Snippets](../../api/project_snippets.md)
+- [Templates](../../api/project_templates.md)
+- [Traffic](../../api/project_statistics.md)
+- [Variables](../../api/project_level_variables.md)
diff --git a/doc/user/project/integrations/bamboo.md b/doc/user/project/integrations/bamboo.md
index 48aabd02438..3206a39dc08 100644
--- a/doc/user/project/integrations/bamboo.md
+++ b/doc/user/project/integrations/bamboo.md
@@ -59,4 +59,3 @@ Bamboo under 'Trigger IP addresses'.
> **Note:**
> - Starting with GitLab 8.14.0, builds are triggered on push events.
-
diff --git a/doc/user/project/integrations/irker.md b/doc/user/project/integrations/irker.md
index f220fa8497a..4fb753d1707 100644
--- a/doc/user/project/integrations/irker.md
+++ b/doc/user/project/integrations/irker.md
@@ -30,12 +30,12 @@ need to follow the firsts steps of the next section.
1. Click "Irker".
1. Select the "Active" checkbox.
1. Enter the server host address where `irkerd` runs (defaults to `localhost`)
-in the `Server host` field on the Web page
+ in the `Server host` field on the Web page
1. Enter the server port of `irkerd` (e.g. defaults to 6659) in the
-`Server port` field on the Web page.
+ `Server port` field on the Web page.
1. Optional: if `Default IRC URI` is set, it has to be in the format
-`irc[s]://domain.name` and will be prepend to each and every channel provided
-by the user which is not a full URI.
+ `irc[s]://domain.name` and will be prepend to each and every channel provided
+ by the user which is not a full URI.
1. Specify the recipients (e.g. #channel1, user1, etc.)
1. Save or optionally click "Test Settings".
diff --git a/doc/user/project/integrations/project_services.md b/doc/user/project/integrations/project_services.md
index cec9018b67f..e2f23827360 100644
--- a/doc/user/project/integrations/project_services.md
+++ b/doc/user/project/integrations/project_services.md
@@ -50,6 +50,7 @@ Click on the service links to see further configuration instructions and details
| [Prometheus](prometheus.md) | Monitor the performance of your deployed apps |
| Pushover | Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop |
| [Redmine](redmine.md) | Redmine issue tracker |
+| [YouTrack](youtrack.md) | YouTrack issue tracker |
## Services templates
diff --git a/doc/user/project/integrations/redmine.md b/doc/user/project/integrations/redmine.md
index 76a2617125e..8112aa21859 100644
--- a/doc/user/project/integrations/redmine.md
+++ b/doc/user/project/integrations/redmine.md
@@ -1,9 +1,9 @@
# Redmine Service
1. To enable the Redmine integration in a project, navigate to the
-[Integrations page](project_services.md#accessing-the-project-services), click
-the **Redmine** service, and fill in the required details on the page as described
-in the table below.
+ [Integrations page](project_services.md#accessing-the-project-services), click
+ the **Redmine** service, and fill in the required details on the page as described
+ in the table below.
| Field | Description |
| ----- | ----------- |
diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md
index 60a4c6aaf64..c3fc6d4b859 100644
--- a/doc/user/project/integrations/webhooks.md
+++ b/doc/user/project/integrations/webhooks.md
@@ -48,9 +48,9 @@ Navigate to the webhooks page by going to your project's
## Use-cases
- You can set up a webhook in GitLab to send a notification to
-[Slack](https://api.slack.com/incoming-webhooks) every time a build fails, for example
+ [Slack](https://api.slack.com/incoming-webhooks) every time a build fails, for example
- You can [integrate with Twilio to be notified via SMS](https://www.datadoghq.com/blog/send-alerts-sms-customizable-webhooks-twilio/)
-every time an issue is created for a specific project or group within GitLab
+ every time an issue is created for a specific project or group within GitLab
- You can use them to [automatically assign labels to merge requests](https://about.gitlab.com/2016/08/19/applying-gitlab-labels-automatically/).
## Webhook endpoint tips
@@ -1228,6 +1228,15 @@ by uncommenting or adding the following setting to your `/etc/gitlab/gitlab.rb`:
gitlab_rails['webhook_timeout'] = 10
```
+### Troubleshooting: "Unable to get local issuer certificate"
+
+When SSL verification is enabled, this error indicates that GitLab isn't able to verify the SSL certificate of the webhook endpoint.
+Typically, this is because the root certificate isn't issued by a trusted certification authority as
+determined by [CAcert.org](http://www.cacert.org/).
+
+Should that not be the case, consider using [SSL Checker](https://www.sslshopper.com/ssl-checker.html) to identify faults.
+Missing intermediate certificates are a common point of verification failure.
+
## Example webhook receiver
If you want to see GitLab's webhooks in action for testing purposes you can use
diff --git a/doc/user/project/integrations/youtrack.md b/doc/user/project/integrations/youtrack.md
new file mode 100644
index 00000000000..2ab14a8db2c
--- /dev/null
+++ b/doc/user/project/integrations/youtrack.md
@@ -0,0 +1,31 @@
+# YouTrack Service
+
+JetBrains YouTrack is a web-based issue tracking and project management platform.
+Please refer official [documentation](https://www.jetbrains.com/help/youtrack/standalone/YouTrack-Documentation.html) for details about YouTrack itself.
+
+
+1. To enable the YouTrack integration in a project, navigate to the
+[Integrations page](project_services.md#accessing-the-project-services), click
+the **YouTrack** service, and fill in the required details on the page as described
+in the table below.
+
+ | Field | Description |
+ | ----- | ----------- |
+ | `description` | A name for the issue tracker (to differentiate between instances, for example) |
+ | `project_url` | The URL to the project in YouTrack which is being linked to this GitLab project |
+ | `issues_url` | The URL to the issue in YouTrack project that is linked to this GitLab project. Note that the `issues_url` requires `:id` in the URL. This ID is used by GitLab as a placeholder to replace the issue number. |
+
+ Once you have configured and enabled YouTrack you'll see the YouTrack link on the GitLab project pages that takes you to the appropriate YouTrack project.
+
+1. To disable the internal issue tracking system in a project, navigate to the General page, expand [Permissions](../settings/index.md#sharing-and-permissions), and slide the Issues switch invalid.
+
+ ![Issue configuration](img/issue_configuration.png)
+
+## Referencing issues in YouTrack
+
+Issues in YouTrack can be referenced as `<PROJECT>-<ID>` where `<PROJECT>`
+starts with a capital letter which is then followed by capital or lower case
+letters, numbers or underscores, and `<ID>` is a number (example `Api_32-143`).
+
+`<PROJECT>` part is included into issue_id and links can point any YouTrack
+project (`issues_url` + issue_id)
diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md
index 7962eeada5c..0bd565547c3 100644
--- a/doc/user/project/issue_board.md
+++ b/doc/user/project/issue_board.md
@@ -78,9 +78,9 @@ with labels, and from there organize and prioritize them with Issue Boards.
For example, let's consider this simplified development workflow:
1. You have a repository hosting your app's codebase
-and your team actively contributing to code
+ and your team actively contributing to code
1. Your **backend** team starts working a new
-implementation, gathers feedback and approval, and pass it over to **frontend**
+ implementation, gathers feedback and approval, and pass it over to **frontend**
1. When frontend is complete, the new feature is deployed to **staging** to be tested
1. When successful, it is deployed to **production**
@@ -88,7 +88,7 @@ If we have the labels "**backend**", "**frontend**", "**staging**", and
"**production**", and an Issue Board with a list for each, we can:
- Visualize the entire flow of implementations since the
-beginning of the development lifecycle until deployed to production
+ beginning of the development lifecycle until deployed to production
- Prioritize the issues in a list by moving them vertically
- Move issues between lists to organize them according to the labels you've set
- Add multiple issues to lists in the board by selecting one or more existing issues
@@ -177,7 +177,7 @@ menu from where you can create another Issue Board and rename or delete the
existing one.
Using the search box at the top of the menu, you can filter the listed boards.
-When you're revisiting an issue board in a project or group with multiple boards,
+When you're revisiting an issue board in a project or group with multiple boards,
GitLab will automatically load the last board you visited.
NOTE: **Note:**
@@ -234,7 +234,7 @@ group-level objects are available.
NOTE: **Note:**
Multiple group issue boards were originally introduced in [GitLab 10.0 Premium](https://about.gitlab.com/2017/09/22/gitlab-10-0-released/#group-issue-boards) and
-one group issue board per group was made available in GitLab 10.6 Core.
+one group issue board per group was made available in GitLab 10.6 Core.
![Group issue board](img/group_issue_board.png)
diff --git a/doc/user/project/issues/automatic_issue_closing.md b/doc/user/project/issues/automatic_issue_closing.md
index afb7d9ada5f..c3e06b219ff 100644
--- a/doc/user/project/issues/automatic_issue_closing.md
+++ b/doc/user/project/issues/automatic_issue_closing.md
@@ -1,6 +1,7 @@
# Automatic issue closing
>**Notes:**
+>
> - This is the user docs. In order to change the default issue closing pattern,
> follow the steps in the [administration docs].
> - For performance reasons, automatic issue closing is disabled for the very
diff --git a/doc/user/project/issues/create_new_issue.md b/doc/user/project/issues/create_new_issue.md
index 2bf4fa287e9..40040e44d64 100644
--- a/doc/user/project/issues/create_new_issue.md
+++ b/doc/user/project/issues/create_new_issue.md
@@ -79,6 +79,6 @@ in the same URL (since a description template just populates the description fie
Follow these examples to form your new issue URL with prefilled fields.
- For a new issue in the GitLab Community Edition project with a pre-entered title
-and a pre-entered description, the URL would be `https://gitlab.com/gitlab-org/gitlab-ce/issues/new?issue[title]=Validate%20new%20concept&issue[description]=Research%20idea`
+ and a pre-entered description, the URL would be `https://gitlab.com/gitlab-org/gitlab-ce/issues/new?issue[title]=Validate%20new%20concept&issue[description]=Research%20idea`
- For a new issue in the GitLab Community Edition project with a pre-entered title
-and a pre-entered description template, the URL would be `https://gitlab.com/gitlab-org/gitlab-ce/issues/new?issue[title]=Validate%20new%20concept&issuable_template=Research%20proposal`
+ and a pre-entered description template, the URL would be `https://gitlab.com/gitlab-org/gitlab-ce/issues/new?issue[title]=Validate%20new%20concept&issuable_template=Research%20proposal`
diff --git a/doc/user/project/issues/due_dates.md b/doc/user/project/issues/due_dates.md
index 93306437c6c..7972c14c1c4 100644
--- a/doc/user/project/issues/due_dates.md
+++ b/doc/user/project/issues/due_dates.md
@@ -42,6 +42,7 @@ server's timezone.
Issues with due dates can also be exported as an iCalendar feed. The URL of the
feed can be added to calendar applications. The feed is accessible by clicking
on the _Subscribe to calendar_ button on the following pages:
+
- on the **Assigned Issues** page that is linked on the right-hand side of the
GitLab header
- on the **Project Issues** page
diff --git a/doc/user/project/issues/index.md b/doc/user/project/issues/index.md
index 5a3ac9c175b..907a305fe23 100644
--- a/doc/user/project/issues/index.md
+++ b/doc/user/project/issues/index.md
@@ -155,7 +155,7 @@ For further details, see [Importing issues from CSV](csv_import.md)
Alternatively to GitLab's built-in Issue Tracker, you can also use an [external
tracker](../../../integration/external-issue-tracker.md) such as Jira, Redmine,
-or Bugzilla.
+YouTrack, or Bugzilla.
### Issue API
diff --git a/doc/user/project/issues/issues_functionalities.md b/doc/user/project/issues/issues_functionalities.md
index e4a3ff52e07..27b9dc51760 100644
--- a/doc/user/project/issues/issues_functionalities.md
+++ b/doc/user/project/issues/issues_functionalities.md
@@ -99,16 +99,16 @@ Learn more in the [Issue Weight documentation](https://docs.gitlab.com/ee/workfl
#### 10. Notifications
- Subscribe: if you are not a participant of the discussion on that issue, but
-want to receive notifications on each new input, subscribe to it.
+ want to receive notifications on each new input, subscribe to it.
- Unsubscribe: if you are receiving notifications on that issue but no
-longer want to receive them, unsubscribe from it.
+ longer want to receive them, unsubscribe from it.
Read more in the [notifications documentation](../../../workflow/notifications.md#issue--merge-request-events).
#### 11. Reference
- A quick "copy to clipboard" button for that issue's reference, `foo/bar#xxx`, where `foo` is the `username` or `groupname`, `bar`
-is the `project-name`, and `xxx` is the issue number.
+ is the `project-name`, and `xxx` is the issue number.
#### 12. Title and description
@@ -138,7 +138,7 @@ interpreted as spam.
#### 14. Related Merge Requests
- Any merge requests mentioned in that issue's description
-or in the issue discussion thread.
+ or in the issue discussion thread.
#### 15. Award emoji
@@ -152,8 +152,8 @@ know you like it without spamming them.
#### 16. Thread
- Comments: collaborate to that issue by posting comments in its thread.
-These text fields also fully support
-[GitLab Flavored Markdown](../../markdown.md#gitlab-flavored-markdown-gfm).
+ These text fields also fully support
+ [GitLab Flavored Markdown](../../markdown.md#gitlab-flavored-markdown-gfm).
#### 17. Comment, start a discussion, or comment and close
@@ -166,7 +166,7 @@ Once you write a comment, you can either:
#### 18. New Merge Request
- Create a new merge request (with a new source branch named after the issue) in one action.
-The merge request will automatically inherit the milestone and labels of the issue. The merge
-request will automatically close that issue when it is merged.
+ The merge request will automatically inherit the milestone and labels of the issue. The merge
+ request will automatically close that issue when it is merged.
- Optionally, you can just create a [new branch](../repository/web_editor.md#create-a-new-branch-from-an-issue)
-named after that issue.
+ named after that issue.
diff --git a/doc/user/project/operations/error_tracking.md b/doc/user/project/operations/error_tracking.md
index fe4b36062f7..41aa5b91aa7 100644
--- a/doc/user/project/operations/error_tracking.md
+++ b/doc/user/project/operations/error_tracking.md
@@ -14,10 +14,14 @@ You may sign up to the cloud hosted <https://sentry.io> or deploy your own [on-p
### Enabling Sentry
+NOTE: **Note:**
+You will need at least Maintainer [permissions](../../permissions.md) to enable the Sentry integration.
+
GitLab provides an easy way to connect Sentry to your project:
1. Sign up to Sentry.io or [deploy your own](#deploying-sentry) Sentry instance.
1. [Find or generate](https://docs.sentry.io/api/auth/) a Sentry auth token for your Sentry project.
+ Make sure to give the token at least the following scopes: `event:read` and `project:read`.
1. Navigate to your project’s **Settings > Operations** and provide the Sentry API URL and auth token.
1. Ensure that the 'Active' checkbox is set.
1. Click **Save changes** for the changes to take effect.
@@ -25,6 +29,9 @@ GitLab provides an easy way to connect Sentry to your project:
## Error Tracking List
+NOTE: **Note:**
+You will need at least Reporter [permissions](../../permissions.md) to view the Error Tracking list.
+
The Error Tracking list may be found at **Operations > Error Tracking** in your project's sidebar.
![Error Tracking list](img/error_tracking_list.png)
diff --git a/doc/user/project/pages/getting_started_part_four.md b/doc/user/project/pages/getting_started_part_four.md
index e4ee2f7cdfa..05d5a2fd99a 100644
--- a/doc/user/project/pages/getting_started_part_four.md
+++ b/doc/user/project/pages/getting_started_part_four.md
@@ -376,11 +376,11 @@ manually in the past. Read through the
to understand how to go even further on your scripts.
- On this blog post, understand the concept of
-[using GitLab CI `environments` to deploy your
-web app to staging and production](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/).
+ [using GitLab CI `environments` to deploy your
+ web app to staging and production](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/).
- On this post, learn [how to run jobs sequentially,
-in parallel, or build a custom pipeline](https://about.gitlab.com/2016/07/29/the-basics-of-gitlab-ci/)
+ in parallel, or build a custom pipeline](https://about.gitlab.com/2016/07/29/the-basics-of-gitlab-ci/)
- On this blog post, we go through the process of
-[pulling specific directories from different projects](https://about.gitlab.com/2016/12/07/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages/)
-to deploy this website you're looking at, docs.gitlab.com.
+ [pulling specific directories from different projects](https://about.gitlab.com/2016/12/07/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages/)
+ to deploy this website you're looking at, docs.gitlab.com.
- On this blog post, we teach you [how to use GitLab Pages to produce a code coverage report](https://about.gitlab.com/2016/11/03/publish-code-coverage-report-with-gitlab-pages/).
diff --git a/doc/user/project/pages/getting_started_part_one.md b/doc/user/project/pages/getting_started_part_one.md
index 595241b2cba..9a95fb70964 100644
--- a/doc/user/project/pages/getting_started_part_one.md
+++ b/doc/user/project/pages/getting_started_part_one.md
@@ -77,41 +77,41 @@ Learn more about [namespaces](../../group/index.md#namespaces).
#### Project Websites
- You created a project called `blog` under your username `john`,
-therefore your project URL is `https://gitlab.com/john/blog/`.
-Once you enable GitLab Pages for this project, and build your site,
-it will be available under `https://john.gitlab.io/blog/`.
+ therefore your project URL is `https://gitlab.com/john/blog/`.
+ Once you enable GitLab Pages for this project, and build your site,
+ it will be available under `https://john.gitlab.io/blog/`.
- You created a group for all your websites called `websites`,
-and a project within this group is called `blog`. Your project
-URL is `https://gitlab.com/websites/blog/`. Once you enable
-GitLab Pages for this project, the site will live under
-`https://websites.gitlab.io/blog/`.
+ and a project within this group is called `blog`. Your project
+ URL is `https://gitlab.com/websites/blog/`. Once you enable
+ GitLab Pages for this project, the site will live under
+ `https://websites.gitlab.io/blog/`.
- You created a group for your engineering department called `engineering`,
-a subgroup for all your documentation websites called `docs`,
-and a project within this subgroup is called `workflows`. Your project
-URL is `https://gitlab.com/engineering/docs/workflows/`. Once you enable
-GitLab Pages for this project, the site will live under
-`https://engineering.gitlab.io/docs/workflows`.
+ a subgroup for all your documentation websites called `docs`,
+ and a project within this subgroup is called `workflows`. Your project
+ URL is `https://gitlab.com/engineering/docs/workflows/`. Once you enable
+ GitLab Pages for this project, the site will live under
+ `https://engineering.gitlab.io/docs/workflows`.
#### User and Group Websites
- Under your username, `john`, you created a project called
-`john.gitlab.io`. Your project URL will be `https://gitlab.com/john/john.gitlab.io`.
-Once you enable GitLab Pages for your project, your website
-will be published under `https://john.gitlab.io`.
+ `john.gitlab.io`. Your project URL will be `https://gitlab.com/john/john.gitlab.io`.
+ Once you enable GitLab Pages for your project, your website
+ will be published under `https://john.gitlab.io`.
- Under your group `websites`, you created a project called
-`websites.gitlab.io`. your project's URL will be `https://gitlab.com/websites/websites.gitlab.io`.
-Once you enable GitLab Pages for your project,
-your website will be published under `https://websites.gitlab.io`.
+ `websites.gitlab.io`. your project's URL will be `https://gitlab.com/websites/websites.gitlab.io`.
+ Once you enable GitLab Pages for your project,
+ your website will be published under `https://websites.gitlab.io`.
> Support for subgroup project's websites was [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/30548) in GitLab 11.8.
**General example:**
- On GitLab.com, a project site will always be available under
-`https://namespace.gitlab.io/project-name`
+ `https://namespace.gitlab.io/project-name`
- On GitLab.com, a user or group website will be available under
-`https://namespace.gitlab.io/`
+ `https://namespace.gitlab.io/`
- On your GitLab instance, replace `gitlab.io` above with your
-Pages server domain. Ask your sysadmin for this information.
+ Pages server domain. Ask your sysadmin for this information.
_Read on about [Projects for GitLab Pages and URL structure](getting_started_part_two.md)._
diff --git a/doc/user/project/pages/getting_started_part_three.md b/doc/user/project/pages/getting_started_part_three.md
index b2da1c85c62..daae2f4b5a3 100644
--- a/doc/user/project/pages/getting_started_part_three.md
+++ b/doc/user/project/pages/getting_started_part_three.md
@@ -19,7 +19,7 @@ To use one or more custom domain with your Pages site, there are two things
you should consider first, which we'll cover in this guide:
1. Either if you're adding a **root domain** or a **subdomain**, for which
-you'll need to set up [DNS records](#dns-records)
+ you'll need to set up [DNS records](#dns-records)
1. Whether you want to add an [SSL/TLS certificate](#ssl-tls-certificates) or not
To finish the association, you need to [add your domain to your project's Pages settings](#add-your-custom-domain-to-gitlab-pages-settings).
@@ -136,13 +136,13 @@ verify your domain's ownership with a TXT record:
> **Notes**:
>
> - **Do not** use a CNAME record if you want to point your
-`domain.com` to your GitLab Pages site. Use an `A` record instead.
+ `domain.com` to your GitLab Pages site. Use an `A` record instead.
> - **Do not** add any special chars after the default Pages
-domain. E.g., **do not** point your `subdomain.domain.com` to
-`namespace.gitlab.io.` or `namespace.gitlab.io/`.
+ domain. E.g., **do not** point your `subdomain.domain.com` to
+ `namespace.gitlab.io.` or `namespace.gitlab.io/`.
> - GitLab Pages IP on GitLab.com [was changed](https://about.gitlab.com/2017/03/06/we-are-changing-the-ip-of-gitlab-pages-on-gitlab-com/) in 2017
> - GitLab Pages IP on GitLab.com [has been changed](https://about.gitlab.com/2018/07/19/gcp-move-update/#gitlab-pages-and-custom-domains)
-from `52.167.214.135` to `35.185.44.232` in 2018
+ from `52.167.214.135` to `35.185.44.232` in 2018
### Add your custom domain to GitLab Pages settings
@@ -278,15 +278,15 @@ These fields are found under your **Project**'s **Settings** > **Pages** > **New
### What's what?
- A PEM certificate is the certificate generated by the CA,
-which needs to be added to the field **Certificate (PEM)**.
+ which needs to be added to the field **Certificate (PEM)**.
- An [intermediate certificate](https://en.wikipedia.org/wiki/Intermediate_certificate_authority) (aka "root certificate") is
-the part of the encryption keychain that identifies the CA.
-Usually it's combined with the PEM certificate, but there are
-some cases in which you need to add them manually.
-[CloudFlare certs](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/)
-are one of these cases.
+ the part of the encryption keychain that identifies the CA.
+ Usually it's combined with the PEM certificate, but there are
+ some cases in which you need to add them manually.
+ [CloudFlare certs](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/)
+ are one of these cases.
- A private key is an encrypted key which validates
-your PEM against your domain.
+ your PEM against your domain.
### Now what?
@@ -295,9 +295,9 @@ of this, it's simple:
- Your PEM certificate needs to be added to the first field
- If your certificate is missing its intermediate, copy
-and paste the root certificate (usually available from your CA website)
-and paste it in the [same field as your PEM certificate](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/),
-just jumping a line between them.
+ and paste the root certificate (usually available from your CA website)
+ and paste it in the [same field as your PEM certificate](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/),
+ just jumping a line between them.
- Copy your private key and paste it in the last field
>**Note:**
diff --git a/doc/user/project/pages/getting_started_part_two.md b/doc/user/project/pages/getting_started_part_two.md
index c9081a6d72b..644a1c951d3 100644
--- a/doc/user/project/pages/getting_started_part_two.md
+++ b/doc/user/project/pages/getting_started_part_two.md
@@ -16,7 +16,7 @@ To get started with GitLab Pages, you need:
1. A project
1. A configuration file (`.gitlab-ci.yml`) to deploy your site
1. A specific `job` called `pages` in the configuration file
-that will make GitLab aware that you are deploying a GitLab Pages website
+ that will make GitLab aware that you are deploying a GitLab Pages website
1. A `public` directory with the content of the website
Optional Features:
@@ -85,16 +85,16 @@ is useful for submitting merge requests to the upstream.
### Create a project from scratch
1. From your **Project**'s **[Dashboard](https://gitlab.com/dashboard/projects)**,
-click **New project**, and name it considering the
-[practical examples](getting_started_part_one.md#practical-examples).
+ click **New project**, and name it considering the
+ [practical examples](getting_started_part_one.md#practical-examples).
1. Clone it to your local computer, add your website
-files to your project, add, commit and push to GitLab.
+ files to your project, add, commit and push to GitLab.
1. From the your **Project**'s page, click **Set up CI/CD**:
![setup GitLab CI/CD](img/setup_ci.png)
1. Choose one of the templates from the dropbox menu.
-Pick up the template corresponding to the SSG you're using (or plain HTML).
+ Pick up the template corresponding to the SSG you're using (or plain HTML).
![gitlab-ci templates](img/choose_ci_template.png)
@@ -107,20 +107,20 @@ where you'll find its default URL.
> **Notes:**
>
> - GitLab Pages [supports any SSG](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/), but,
-if you don't find yours among the templates, you'll need
-to configure your own `.gitlab-ci.yml`. To do that, please
-read through the article [Creating and Tweaking GitLab CI/CD for GitLab Pages](getting_started_part_four.md). New SSGs are very welcome among
-the [example projects](https://gitlab.com/pages). If you set
-up a new one, please
-[contribute](https://gitlab.com/pages/pages.gitlab.io/blob/master/CONTRIBUTING.md)
-to our examples.
+ if you don't find yours among the templates, you'll need
+ to configure your own `.gitlab-ci.yml`. To do that, please
+ read through the article [Creating and Tweaking GitLab CI/CD for GitLab Pages](getting_started_part_four.md). New SSGs are very welcome among
+ the [example projects](https://gitlab.com/pages). If you set
+ up a new one, please
+ [contribute](https://gitlab.com/pages/pages.gitlab.io/blob/master/CONTRIBUTING.md)
+ to our examples.
>
> - The second step _"Clone it to your local computer"_, can be done
-differently, achieving the same results: instead of cloning the bare
-repository to you local computer and moving your site files into it,
-you can run `git init` in your local website directory, add the
-remote URL: `git remote add origin git@gitlab.com:namespace/project-name.git`,
-then add, commit, and push.
+ differently, achieving the same results: instead of cloning the bare
+ repository to you local computer and moving your site files into it,
+ you can run `git init` in your local website directory, add the
+ remote URL: `git remote add origin git@gitlab.com:namespace/project-name.git`,
+ then add, commit, and push.
## URLs and Baseurls
diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md
index 11f6165fcb4..e0b78753e21 100644
--- a/doc/user/project/pages/index.md
+++ b/doc/user/project/pages/index.md
@@ -66,7 +66,7 @@ publish any website written directly in plain HTML, CSS, and JavaScript.</p>
If you're using GitLab.com, your website will be publicly available to the internet.
If you're using self-managed instances (Core, Starter, Premium, or Ultimate),
your websites will be published on your own server, according to the
-[Pages admin settings](../../../administration/pages/index.md) chosen by your sysdamin,
+[Pages admin settings](../../../administration/pages/index.md) chosen by your sysadmin,
who can opt for making them public or internal to your server.
### How it works
@@ -91,13 +91,13 @@ site under the HTTPS protocol.
## Getting started
-To get started with GitLab Pages, you can either [create a project from scratch](getting_started_part_two.md#create-a-project-from-scratch),
+To get started with GitLab Pages, you can either [create a project from scratch](getting_started_part_two.md#create-a-project-from-scratch),
use a [bundled template](getting_started_part_two.md#use-one-of-the-popular-pages-templates-bundled-with-gitlab), or copy any of our existing example projects:
1. Choose an [example project](https://gitlab.com/pages) to [fork](../../../gitlab-basics/fork-project.md#how-to-fork-a-project):
by forking a project, you create a copy of the codebase you're forking from to start from a template instead of starting from scratch.
1. From the left sidebar, navigate to your project's **CI/CD > Pipelines** and click
-**Run pipeline** so that GitLab CI/CD will build and deploy your site to the server.
+ **Run pipeline** so that GitLab CI/CD will build and deploy your site to the server.
1. Once the pipeline has finished successfully, find the link to visit your website from your
project's **Settings > Pages**.
@@ -164,7 +164,7 @@ with Pages, read through this series:
### GitLab Pages with SSL/TLS certificates
-If you're using GitLab Pages default domain (`.gitlab.io`), your website will be
+If you're using GitLab Pages default domain (`.gitlab.io`), your website will be
automatically secure and available under HTTPS. If you're using your own domain, you can
optionally secure it with SSL/TLS certificates. You can read the following
tutorials to learn how to use these third-party certificates with GitLab Pages:
diff --git a/doc/user/project/pages/introduction.md b/doc/user/project/pages/introduction.md
index fa65a206273..23eb88fd305 100644
--- a/doc/user/project/pages/introduction.md
+++ b/doc/user/project/pages/introduction.md
@@ -151,7 +151,7 @@ Depending on how you plan to publish your website, the steps defined in the
Be aware that Pages are by default branch/tag agnostic and their deployment
relies solely on what you specify in `.gitlab-ci.yml`. If you don't limit the
-`pages` job with the [`only` parameter](../../../ci/yaml/README.md#only-and-except),
+`pages` job with the [`only` parameter](../../../ci/yaml/README.md#only-and-except-simplified),
whenever a new commit is pushed to whatever branch or tag, the Pages will be
overwritten. In the example below, we limit the Pages to be deployed whenever
a commit is pushed only on the `master` branch:
@@ -252,7 +252,7 @@ get you started.
Remember that GitLab Pages are by default branch/tag agnostic and their
deployment relies solely on what you specify in `.gitlab-ci.yml`. You can limit
-the `pages` job with the [`only` parameter](../../../ci/yaml/README.md#only-and-except),
+the `pages` job with the [`only` parameter](../../../ci/yaml/README.md#only-and-except-simplified),
whenever a new commit is pushed to a branch that will be used specifically for
your pages.
diff --git a/doc/user/project/pipelines/job_artifacts.md b/doc/user/project/pipelines/job_artifacts.md
index 4ab66063dcf..bf939dbdaa3 100644
--- a/doc/user/project/pipelines/job_artifacts.md
+++ b/doc/user/project/pipelines/job_artifacts.md
@@ -195,5 +195,5 @@ artifacts and the job's trace.
In order to retrieve a job artifact of a different project, you might need to use a private token in order to [authenticate and download](../../../api/jobs.md#get-job-artifacts) the artifacts.
-[expiry date]: ../../../ci/yaml/README.md#artifacts-expire_in
+[expiry date]: ../../../ci/yaml/README.md#artifactsexpire_in
[ce-14399]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14399 \ No newline at end of file
diff --git a/doc/user/project/pipelines/schedules.md b/doc/user/project/pipelines/schedules.md
index ec8b8444d99..58a0fbc97cd 100644
--- a/doc/user/project/pipelines/schedules.md
+++ b/doc/user/project/pipelines/schedules.md
@@ -1,4 +1,4 @@
-# Pipeline Schedules
+# Pipeline schedules
> **Notes**:
> - This feature was introduced in 9.1 as [Trigger Schedule][ce-10533].
diff --git a/doc/user/project/pipelines/settings.md b/doc/user/project/pipelines/settings.md
index cce330aecc7..4a989afad4d 100644
--- a/doc/user/project/pipelines/settings.md
+++ b/doc/user/project/pipelines/settings.md
@@ -23,7 +23,7 @@ in `.gitlab-ci.yml`.
## Timeout
Timeout defines the maximum amount of time in minutes that a job is able run.
-This is configureable under your project's **Settings > CI/CD > General pipelines settings**.
+This is configurable under your project's **Settings > CI/CD > General pipelines settings**.
The default value is 60 minutes. Decrease the time limit if you want to impose
a hard limit on your jobs' running time or increase it otherwise. In any case,
if the job surpasses the threshold, it is marked as failed.
@@ -134,7 +134,7 @@ Depending on the status of your job, a badge can have the following values:
You can access a pipeline status badge image using the following link:
```text
-https://example.gitlab.com/<namespace>/<project>/badges/<branch>/build.svg
+https://example.gitlab.com/<namespace>/<project>/badges/<branch>/pipeline.svg
```
### Test coverage report badge
diff --git a/doc/user/project/protected_tags.md b/doc/user/project/protected_tags.md
index 3d8fff9f733..26bec323f02 100644
--- a/doc/user/project/protected_tags.md
+++ b/doc/user/project/protected_tags.md
@@ -10,7 +10,6 @@ This feature evolved out of [Protected Branches](protected_branches.md)
Protected tags will prevent anyone from updating or deleting the tag, as and will prevent creation of matching tags based on the permissions you have selected. By default, anyone without Maintainer permission will be prevented from creating tags.
-
## Configuring protected tags
To protect a tag, you need to have at least Maintainer permission level.
@@ -43,7 +42,6 @@ matching the wildcard. For example:
| `*gitlab*` | `gitlab`, `gitlab/v1` |
| `*` | `v1.0.1rc2`, `accidental-tag` |
-
Two different wildcards can potentially match the same tag. For example,
`*-stable` and `production-*` would both match a `production-stable` tag.
In that case, if _any_ of these protected tags have a setting like
@@ -54,7 +52,6 @@ all matching tags:
![Protected tag matches](img/protected_tag_matches.png)
-
---
[ce-10356]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10356 "Protected Tags"
diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md
index 85a03d125dd..392e72dcc5c 100644
--- a/doc/user/project/quick_actions.md
+++ b/doc/user/project/quick_actions.md
@@ -55,7 +55,6 @@ discussions, and descriptions:
| `/merge` | Merge (when pipeline succeeds) | | ✓ |
| `/create_merge_request <branch name>` | Create a new merge request starting from the current issue | ✓ | |
-
## Quick actions for commit messages
The following quick actions are applicable for commit messages:
diff --git a/doc/user/project/repository/gpg_signed_commits/index.md b/doc/user/project/repository/gpg_signed_commits/index.md
index c7e20f01a75..6495ede42e0 100644
--- a/doc/user/project/repository/gpg_signed_commits/index.md
+++ b/doc/user/project/repository/gpg_signed_commits/index.md
@@ -190,7 +190,6 @@ key to use.
Replace `30F2B65B9246B6CA` with your GPG key ID.
-
1. (Optional) If Git is using `gpg` and you get errors like `secret key not available`
or `gpg: signing failed: secret key not available`, run the following command to
change to `gpg2`:
@@ -266,3 +265,7 @@ To remove a GPG key from your account:
You can configure your project to reject commits that aren't GPG-signed
via [push rules](https://docs.gitlab.com/ee/push_rules/push_rules.html).
+
+## GPG signing API
+
+Learn how to [get the GPG signature from a commit via API](../../../../api/commits.md#get-gpg-signature-of-a-commit).
diff --git a/doc/user/project/repository/index.md b/doc/user/project/repository/index.md
index fac5975a0dc..22d912cd9d1 100644
--- a/doc/user/project/repository/index.md
+++ b/doc/user/project/repository/index.md
@@ -146,9 +146,9 @@ you are introducing those changes to your branch.
Via command line, you can commit multiple times before pushing.
- **Commit message:**
-A commit message is important to identity what is being changed and,
-more importantly, why. In GitLab, you can add keywords to the commit
-message that will perform one of the actions below:
+ A commit message is important to identity what is being changed and,
+ more importantly, why. In GitLab, you can add keywords to the commit
+ message that will perform one of the actions below:
- **Trigger a GitLab CI/CD pipeline:**
If you have your project configured with [GitLab CI/CD](../../../ci/README.md),
you will trigger a pipeline per push, not per commit.
@@ -162,14 +162,14 @@ message that will perform one of the actions below:
If you mention an issue or a merge request in a commit message, they will be shown
on their respective thread.
- **Cherry-pick a commit:**
-In GitLab, you can
-[cherry-pick a commit](../merge_requests/cherry_pick_changes.md#cherry-picking-a-commit)
-right from the UI.
+ In GitLab, you can
+ [cherry-pick a commit](../merge_requests/cherry_pick_changes.md#cherry-picking-a-commit)
+ right from the UI.
- **Revert a commit:**
-Easily [revert a commit](../merge_requests/revert_changes.md#reverting-a-commit)
-from the UI to a selected branch.
+ Easily [revert a commit](../merge_requests/revert_changes.md#reverting-a-commit)
+ from the UI to a selected branch.
- **Sign a commit:**
-Use GPG to [sign your commits](gpg_signed_commits/index.md).
+ Use GPG to [sign your commits](gpg_signed_commits/index.md).
## Repository size
diff --git a/doc/user/project/repository/web_editor.md b/doc/user/project/repository/web_editor.md
index 57e8437697b..ce9d23bf911 100644
--- a/doc/user/project/repository/web_editor.md
+++ b/doc/user/project/repository/web_editor.md
@@ -112,7 +112,6 @@ screenshot above will yield a branch named
Since GitLab 9.0, when you click the `New branch` in an empty repository project, GitLab automatically creates the master branch, commits a blank `README.md` file to it and creates and redirects you to a new branch based on the issue title.
If your [project is already configured with a deployment service][project-services-doc] (e.g. Kubernetes), GitLab takes one step further and prompts you to set up [auto deploy][auto-deploy-doc] by helping you create a `.gitlab-ci.yml` file.
-
After the branch is created, you can edit files in the repository to fix
the issue. When a merge request is created based on the newly created branch,
the description field will automatically display the [issue closing pattern]
diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md
index b09a3f927d1..d5f4a7fd4ac 100644
--- a/doc/user/project/settings/index.md
+++ b/doc/user/project/settings/index.md
@@ -121,7 +121,7 @@ NOTE: **Note:**
GitLab administrators can use the admin interface to move any project to any
namespace if needed.
-[permissions]: ../../permissions.md##project-members-permissions
+[permissions]: ../../permissions.md#project-members-permissions
## Operations settings
diff --git a/doc/user/project/web_ide/index.md b/doc/user/project/web_ide/index.md
index 9c095da5a4b..46a1b2bc3aa 100644
--- a/doc/user/project/web_ide/index.md
+++ b/doc/user/project/web_ide/index.md
@@ -24,21 +24,21 @@ file path fragments to start seeing results.
## Syntax highlighting
-As expected from an IDE, syntax highlighting for many languages within
+As expected from an IDE, syntax highlighting for many languages within
the Web IDE will make your direct editing even easier.
The Web IDE currently provides:
-- Basic syntax colorization for a variety of programming, scripting and markup
-languages such as XML, PHP, C#, C++, Markdown, Java, VB, Batch, Python, Ruby
-and Objective-C.
-- IntelliSense and validation support (displaying errors and warnings, providing
-smart completions, formatting, and outlining) for some languages. For example:
-TypeScript, JavaScript, CSS, LESS, SCSS, JSON and HTML.
+- Basic syntax colorization for a variety of programming, scripting and markup
+ languages such as XML, PHP, C#, C++, Markdown, Java, VB, Batch, Python, Ruby
+ and Objective-C.
+- IntelliSense and validation support (displaying errors and warnings, providing
+ smart completions, formatting, and outlining) for some languages. For example:
+TypeScript, JavaScript, CSS, LESS, SCSS, JSON and HTML.
-Because the Web IDE is based on the [Monaco Editor](https://microsoft.github.io/monaco-editor/),
-you can find a more complete list of supported languages in the
-[Monaco languages](https://github.com/Microsoft/monaco-languages) repository.
+Because the Web IDE is based on the [Monaco Editor](https://microsoft.github.io/monaco-editor/),
+you can find a more complete list of supported languages in the
+[Monaco languages](https://github.com/Microsoft/monaco-languages) repository.
NOTE: **Note:**
Single file editing is based on the [Ace Editor](https://ace.c9.io).
@@ -50,7 +50,7 @@ review the list of changed files. Click on each file to review the changes and
click the tick icon to stage the file.
Once you have staged some changes, you can add a commit message and commit the
-staged changes. Unstaged changes will not be commited.
+staged changes. Unstaged changes will not be committed.
![Commit changes](img/commit_changes.png)
diff --git a/doc/user/reserved_names.md b/doc/user/reserved_names.md
index a14df6c8402..9aa81e33fc0 100644
--- a/doc/user/reserved_names.md
+++ b/doc/user/reserved_names.md
@@ -5,6 +5,7 @@ existing routes used by GitLab.
For a list of words that are not allowed to be used as group or project names, see the
[`path_regex.rb` file][reserved] under the `TOP_LEVEL_ROUTES`, `PROJECT_WILDCARD_ROUTES` and `GROUP_ROUTES` lists:
+
- `TOP_LEVEL_ROUTES`: are names that are reserved as usernames or top level groups
- `PROJECT_WILDCARD_ROUTES`: are names that are reserved for child groups or projects.
- `GROUP_ROUTES`: are names that are reserved for all groups or projects.
diff --git a/doc/workflow/forking_workflow.md b/doc/workflow/forking_workflow.md
index 733d079bd4a..02be0ad191d 100644
--- a/doc/workflow/forking_workflow.md
+++ b/doc/workflow/forking_workflow.md
@@ -10,7 +10,6 @@ document more information about using branches to work together.
Forking a project is in most cases a two-step process.
-
1. Click on the fork button located in the middle of the page or a project's
home page right next to the stars button.
diff --git a/doc/workflow/gitlab_flow.md b/doc/workflow/gitlab_flow.md
index a7313082fac..1b9fb504b15 100644
--- a/doc/workflow/gitlab_flow.md
+++ b/doc/workflow/gitlab_flow.md
@@ -2,314 +2,324 @@
# Introduction to GitLab Flow
-Version management with git makes branching and merging much easier than older versioning systems such as SVN.
-This allows a wide variety of branching strategies and workflows.
-Almost all of these are an improvement over the methods used before git.
-But many organizations end up with a workflow that is not clearly defined, overly complex or not integrated with issue tracking systems.
-Therefore we propose the GitLab flow as clearly defined set of best practices.
-It combines [feature driven development](https://en.wikipedia.org/wiki/Feature-driven_development) and [feature branches](http://martinfowler.com/bliki/FeatureBranch.html) with issue tracking.
+Git allows a wide variety of branching strategies and workflows.
+Because of this, many organizations end up with workflows that are too complicated, not clearly defined, or not integrated with issue tracking systems.
+Therefore, we propose GitLab flow as a clearly defined set of best practices.
+It combines [feature-driven development](https://en.wikipedia.org/wiki/Feature-driven_development) and [feature branches](https://martinfowler.com/bliki/FeatureBranch.html) with issue tracking.
-Organizations coming to git from other version control systems frequently find it hard to develop an effective workflow.
-This article describes the GitLab flow that integrates the git workflow with an issue tracking system.
-It offers a simple, transparent and effective way to work with git.
+Organizations coming to Git from other version control systems frequently find it hard to develop a productive workflow.
+This article describes GitLab flow, which integrates the Git workflow with an issue tracking system.
+It offers a simple, transparent, and effective way to work with Git.
![Four stages (working copy, index, local repo, remote repo) and three steps between them](four_stages.png)
-When converting to git you have to get used to the fact that there are three steps before a commit is shared with colleagues.
-Most version control systems have only one step, committing from the working copy to a shared server.
-In git you add files from the working copy to the staging area. After that you commit them to the local repo.
+When converting to Git, you have to get used to the fact that it takes three steps to share a commit with colleagues.
+Most version control systems have only one step: committing from the working copy to a shared server.
+In Git, you add files from the working copy to the staging area. After that, you commit them to your local repo.
The third step is pushing to a shared remote repository.
-After getting used to these three steps the branching model becomes the challenge.
+After getting used to these three steps, the next challenge is the branching model.
-![Multiple long running branches and merging in all directions](messy_flow.png)
+![Multiple long-running branches and merging in all directions](messy_flow.png)
-Since many organizations new to git have no conventions how to work with it, it can quickly become a mess.
-The biggest problem they run into is that many long running branches that each contain part of the changes are around.
-People have a hard time figuring out which branch they should develop on or deploy to production.
-Frequently the reaction to this problem is to adopt a standardized pattern such as [git flow](http://nvie.com/posts/a-successful-git-branching-model/) and [GitHub flow](http://scottchacon.com/2011/08/31/github-flow.html).
-We think there is still room for improvement and will detail a set of practices we call GitLab flow.
+Since many organizations new to Git have no conventions for how to work with it, their repositories can quickly become messy.
+The biggest problem is that many long-running branches emerge that all contain part of the changes.
+People have a hard time figuring out which branch has the latest code, or which branch to deploy to production.
+Frequently, the reaction to this problem is to adopt a standardized pattern such as [Git flow](https://nvie.com/posts/a-successful-git-branching-model/) and [GitHub flow](http://scottchacon.com/2011/08/31/github-flow.html).
+We think there is still room for improvement. In this document, we describe a set of practices we call GitLab flow.
## Git flow and its problems
![Git Flow timeline by Vincent Driessen, used with permission](gitdashflow.png)
-Git flow was one of the first proposals to use git branches and it has gotten a lot of attention.
-It advocates a master branch and a separate develop branch as well as supporting branches for features, releases and hotfixes.
-The development happens on the develop branch, moves to a release branch and is finally merged into the master branch.
-Git flow is a well defined standard but its complexity introduces two problems.
-The first problem is that developers must use the develop branch and not master, master is reserved for code that is released to production.
-It is a convention to call your default branch master and to mostly branch from and merge to this.
-Since most tools automatically make the master branch the default one and display that one by default it is annoying to have to switch to another one.
-The second problem of git flow is the complexity introduced by the hotfix and release branches.
+Git flow was one of the first proposals to use Git branches, and it has received a lot of attention.
+It suggests a `master` branch and a separate `develop` branch, as well as supporting branches for features, releases, and hotfixes.
+The development happens on the `develop` branch, moves to a release branch, and is finally merged into the `master` branch.
+
+Git flow is a well-defined standard, but its complexity introduces two problems.
+The first problem is that developers must use the `develop` branch and not `master`. `master` is reserved for code that is released to production.
+It is a convention to call your default branch `master` and to mostly branch from and merge to this.
+Since most tools automatically use the `master` branch as the default, it is annoying to have to switch to another branch.
+
+The second problem of Git flow is the complexity introduced by the hotfix and release branches.
These branches can be a good idea for some organizations but are overkill for the vast majority of them.
-Nowadays most organizations practice continuous delivery which means that your default branch can be deployed.
-This means that hotfix and release branches can be prevented including all the ceremony they introduce.
+Nowadays, most organizations practice continuous delivery, which means that your default branch can be deployed.
+Continuous delivery removes the need for hotfix and release branches, including all the ceremony they introduce.
An example of this ceremony is the merging back of release branches.
Though specialized tools do exist to solve this, they require documentation and add complexity.
-Frequently developers make a mistake and for example changes are only merged into master and not into the develop branch.
-The root cause of these errors is that git flow is too complex for most of the use cases.
-And doing releases doesn't automatically mean also doing hotfixes.
+Frequently, developers make mistakes such as merging changes only into `master` and not into the `develop` branch.
+The reason for these errors is that Git flow is too complicated for most use cases.
+For example, many projects do releases but don't need to do hotfixes.
## GitHub flow as a simpler alternative
![Master branch with feature branches merged in](github_flow.png)
-In reaction to git flow a simpler alternative was detailed, [GitHub flow](https://guides.github.com/introduction/flow/index.html).
-This flow has only feature branches and a master branch.
-This is very simple and clean, many organizations have adopted it with great success.
-Atlassian recommends [a similar strategy](http://blogs.atlassian.com/2014/01/simple-git-workflow-simple/) although they rebase feature branches.
-Merging everything into the master branch and deploying often means you minimize the amount of code in 'inventory' which is in line with the lean and continuous delivery best practices.
-But this flow still leaves a lot of questions unanswered regarding deployments, environments, releases and integrations with issues.
-With GitLab flow we offer additional guidance for these questions.
+In reaction to Git flow, GitHub created a simpler alternative.
+[GitHub flow](https://guides.github.com/introduction/flow/index.html) has only feature branches and a `master` branch.
+This flow is clean and straightforward, and many organizations have adopted it with great success.
+Atlassian recommends [a similar strategy](https://www.atlassian.com/blog/archives/simple-git-workflow-simple), although they rebase feature branches.
+Merging everything into the `master` branch and frequently deploying means you minimize the amount of unreleased code, which is in line with lean and continuous delivery best practices.
+However, this flow still leaves a lot of questions unanswered regarding deployments, environments, releases, and integrations with issues.
+With GitLab flow, we offer additional guidance for these questions.
## Production branch with GitLab flow
-![Master branch and production branch with arrow that indicate deployments](production_branch.png)
+![Master branch and production branch with an arrow that indicates a deployment](production_branch.png)
-GitHub flow does assume you are able to deploy to production every time you merge a feature branch.
-This is possible for e.g. SaaS applications, but there are many cases where this is not possible.
-One would be a situation where you are not in control of the exact release moment, for example an iOS application that needs to pass App Store validation.
-Another example is when you have deployment windows (workdays from 10am to 4pm when the operations team is at full capacity) but you also merge code at other times.
-In these cases you can make a production branch that reflects the deployed code.
-You can deploy a new version by merging in master to the production branch.
-If you need to know what code is in production you can just checkout the production branch to see.
+GitHub flow assumes you can deploy to production every time you merge a feature branch.
+While this is possible in some cases, such as SaaS applications, there are many cases where this is not possible.
+One case is where you don't control the timing of a release, for example, an iOS application that is released when it passes App Store validation.
+Another case is when you have deployment windows &mdash; for example, workdays from 10&nbsp;AM to 4&nbsp;PM when the operations team is at full capacity &mdash; but you also merge code at other times.
+In these cases, you can make a production branch that reflects the deployed code.
+You can deploy a new version by merging `master` into the production branch.
+If you need to know what code is in production, you can just checkout the production branch to see.
The approximate time of deployment is easily visible as the merge commit in the version control system.
This time is pretty accurate if you automatically deploy your production branch.
-If you need a more exact time you can have your deployment script create a tag on each deployment.
-This flow prevents the overhead of releasing, tagging and merging that is common to git flow.
+If you need a more exact time, you can have your deployment script create a tag on each deployment.
+This flow prevents the overhead of releasing, tagging, and merging that happens with Git flow.
## Environment branches with GitLab flow
![Multiple branches with the code cascading from one to another](environment_branches.png)
-It might be a good idea to have an environment that is automatically updated to the master branch.
-Only in this case, the name of this environment might differ from the branch name.
-Suppose you have a staging environment, a pre-production environment and a production environment.
-In this case the master branch is deployed on staging. When someone wants to deploy to pre-production they create a merge request from the master branch to the pre-production branch.
-And going live with code happens by merging the pre-production branch into the production branch.
-This workflow where commits only flow downstream ensures that everything has been tested on all environments.
-If you need to cherry-pick a commit with a hotfix it is common to develop it on a feature branch and merge it into master with a merge request, do not delete the feature branch.
-If master is good to go (it should be if you are practicing [continuous delivery](http://martinfowler.com/bliki/ContinuousDelivery.html)) you then merge it to the other branches.
-If this is not possible because more manual testing is required you can send merge requests from the feature branch to the downstream branches.
+It might be a good idea to have an environment that is automatically updated to the `master` branch.
+Only, in this case, the name of this environment might differ from the branch name.
+Suppose you have a staging environment, a pre-production environment, and a production environment.
+In this case, deploy the `master` branch to staging.
+To deploy to pre-production, create a merge request from the `master` branch to the pre-production branch.
+Go live by merging the pre-production branch into the production branch.
+This workflow, where commits only flow downstream, ensures that everything is tested in all environments.
+If you need to cherry-pick a commit with a hotfix, it is common to develop it on a feature branch and merge it into `master` with a merge request.
+In this case, do not delete the feature branch yet.
+If `master` passes automatic testing, you then merge the feature branch into the other branches.
+If this is not possible because more manual testing is required, you can send merge requests from the feature branch to the downstream branches.
## Release branches with GitLab flow
![Master and multiple release branches that vary in length with cherry-picks from master](release_branches.png)
-Only in case you need to release software to the outside world you need to work with release branches.
-In this case, each branch contains a minor version (2-3-stable, 2-4-stable, etc.).
-The stable branch uses master as a starting point and is created as late as possible.
-By branching as late as possible you minimize the time you have to apply bug fixes to multiple branches.
-After a release branch is announced, only serious bug fixes are included in the release branch.
-If possible these bug fixes are first merged into master and then cherry-picked into the release branch.
-This way you can't forget to cherry-pick them into master and encounter the same bug on subsequent releases.
-This is called an 'upstream first' policy that is also practiced by [Google](https://www.chromium.org/chromium-os/chromiumos-design-docs/upstream-first) and [Red Hat](https://www.redhat.com/about/news/archive/2013/5/a-community-for-using-openstack-with-red-hat-rdo).
-Every time a bug-fix is included in a release branch the patch version is raised (to comply with [Semantic Versioning](http://semver.org/)) by setting a new tag.
+You only need to work with release branches if you need to release software to the outside world.
+In this case, each branch contains a minor version, for example, 2-3-stable, 2-4-stable, etc.
+Create stable branches using `master` as a starting point, and branch as late as possible.
+By doing this, you minimize the length of time during which you have to apply bug fixes to multiple branches.
+After announcing a release branch, only add serious bug fixes to the branch.
+If possible, first merge these bug fixes into `master`, and then cherry-pick them into the release branch.
+If you start by merging into the release branch, you might forget to cherry-pick them into `master`, and then you'd encounter the same bug in subsequent releases.
+Merging into `master` and then cherry-picking into release is called an "upstream first" policy, which is also practiced by [Google](https://www.chromium.org/chromium-os/chromiumos-design-docs/upstream-first) and [Red Hat](https://www.redhat.com/en/blog/a-community-for-using-openstack-with-red-hat-rdo).
+Every time you include a bug fix in a release branch, increase the patch version (to comply with [Semantic Versioning](https://semver.org/)) by setting a new tag.
Some projects also have a stable branch that points to the same commit as the latest released branch.
-In this flow it is not common to have a production branch (or git flow master branch).
+In this flow, it is not common to have a production branch (or Git flow `master` branch).
## Merge/pull requests with GitLab flow
-![Merge request with line comments](mr_inline_comments.png)
+![Merge request with inline comments](mr_inline_comments.png)
-Merge or pull requests are created in a git management application and ask an assigned person to merge two branches.
-Tools such as GitHub and Bitbucket choose the name pull request since the first manual action would be to pull the feature branch.
-Tools such as GitLab and others choose the name merge request since that is the final action that is requested of the assignee.
-In this article we'll refer to them as merge requests.
+Merge or pull requests are created in a Git management application. They ask an assigned person to merge two branches.
+Tools such as GitHub and Bitbucket choose the name "pull request" since the first manual action is to pull the feature branch.
+Tools such as GitLab and others choose the name "merge request" since the final action is to merge the feature branch.
+In this article, we'll refer to them as merge requests.
-If you work on a feature branch for more than a few hours it is good to share the intermediate result with the rest of the team.
-This can be done by creating a merge request without assigning it to anyone, instead you mention people in the description or a comment (/cc @mark @susan).
-This means it is not ready to be merged but feedback is welcome.
+If you work on a feature branch for more than a few hours, it is good to share the intermediate result with the rest of the team.
+To do this, create a merge request without assigning it to anyone.
+Instead, mention people in the description or a comment, for example, "/cc @mark @susan."
+This indicates that the merge request is not ready to be merged yet, but feedback is welcome.
Your team members can comment on the merge request in general or on specific lines with line comments.
-The merge requests serves as a code review tool and no separate tools such as Gerrit and reviewboard should be needed.
-If the review reveals shortcomings anyone can commit and push a fix.
-Commonly the person to do this is the creator of the merge/pull request.
-The diff in the merge/pull requests automatically updates when new commits are pushed on the branch.
+The merge request serves as a code review tool, and no separate code review tools should be needed.
+If the review reveals shortcomings, anyone can commit and push a fix.
+Usually, the person to do this is the creator of the merge request.
+The diff in the merge request automatically updates when new commits are pushed to the branch.
+
+When you are ready for your feature branch to be merged, assign the merge request to the person who knows most about the codebase you are changing.
+Also, mention any other people from whom you would like feedback.
+After the assigned person feels comfortable with the result, they can merge the branch.
+If the assigned person does not feel comfortable, they can request more changes or close the merge request without merging.
+
+In GitLab, it is common to protect the long-lived branches, e.g., the `master` branch, so that [most developers can't modify them](../permissions/permissions.md).
+So, if you want to merge into a protected branch, assign your merge request to someone with maintainer permissions.
-When you feel comfortable with it to be merged you assign it to the person that knows most about the codebase you are changing and mention any other people you would like feedback from.
-There is room for more feedback and after the assigned person feels comfortable with the result the branch is merged.
-If the assigned person does not feel comfortable they can close the merge request without merging.
+After you merge a feature branch, you should remove it from the source control software.
+In GitLab, you can do this when merging.
+Removing finished branches ensures that the list of branches shows only work in progress.
+It also ensures that if someone reopens the issue, they can use the same branch name without causing problems.
-In GitLab it is common to protect the long-lived branches (e.g. the master branch) so that normal developers [can't modify these protected branches](http://docs.gitlab.com/ce/permissions/permissions.html).
-So if you want to merge it into a protected branch you assign it to someone with maintainer authorizations.
+NOTE: **Note:**
+When you reopen an issue you need to create a new merge request.
+
+![Remove checkbox for branch in merge requests](remove_checkbox.png)
## Issue tracking with GitLab flow
-![Merge request with the branch name 15-require-a-password-to-change-it and assignee field shown](merge_request.png)
+![Merge request with the branch name "15-require-a-password-to-change-it" and assignee field shown](merge_request.png)
GitLab flow is a way to make the relation between the code and the issue tracker more transparent.
-Any significant change to the code should start with an issue where the goal is described.
-Having a reason for every code change is important to inform everyone on the team and to help people keep the scope of a feature branch small.
-In GitLab each change to the codebase starts with an issue in the issue tracking system.
-If there is no issue yet it should be created first provided there is significant work involved (more than 1 hour).
-For many organizations this will be natural since the issue will have to be estimated for the sprint.
-Issue titles should describe the desired state of the system, e.g. "As an administrator I want to remove users without receiving an error" instead of "Admin can't remove users.".
-
-When you are ready to code you start a branch for the issue from the master branch.
-The name of this branch should start with the issue number, for example '15-require-a-password-to-change-it'.
-
-When you are done or want to discuss the code you open a merge request.
-This is an online place to discuss the change and review the code.
-Opening a merge request is a manual action since you do not always want to merge a new branch you push, it could be a long-running environment or release branch.
-If you open the merge request but do not assign it to anyone it is a 'Work In Progress' merge request.
-These are used to discuss the proposed implementation but are not ready for inclusion in the master branch yet.
-_Pro tip:_ Start the title of the merge request with `[WIP]` or `WIP:` to prevent it from being merged before it's ready.
-
-When the author thinks the code is ready the merge request is assigned to reviewer.
-The reviewer presses the merge button when they think the code is ready for inclusion in the master branch.
-In this case the code is merged and a merge commit is generated that makes this event easily visible later on.
-Merge requests always create a merge commit even when the commit could be added without one.
-This merge strategy is called 'no fast-forward' in git.
-After the merge the feature branch is deleted since it is no longer needed, in GitLab this deletion is an option when merging.
+Any significant change to the code should start with an issue that describes the goal.
+Having a reason for every code change helps to inform the rest of the team and to keep the scope of a feature branch small.
+In GitLab, each change to the codebase starts with an issue in the issue tracking system.
+If there is no issue yet, create the issue, as long as the change will take a significant amount of work, i.e., more than 1 hour.
+In many organizations, raising an issue is part of the development process because they are used in sprint planning.
+The issue title should describe the desired state of the system.
+For example, the issue title "As an administrator, I want to remove users without receiving an error" is better than "Admin can't remove users."
+
+When you are ready to code, create a branch for the issue from the `master` branch.
+This branch is the place for any work related to this change.
+
+NOTE: **Note:**
+The name of a branch might be dictated by organizational standards.
+For example, in GitLab, any branches in GitLab EE that are equivalent to branches in GitLab CE [must end in `-ee`](https://docs.gitlab.com/ee/development/automatic_ce_ee_merge.html#cherry-picking-from-ce-to-ee).
+
+When you are done or want to discuss the code, open a merge request.
+A merge request is an online place to discuss the change and review the code.
+
+If you open the merge request but do not assign it to anyone, it is a "Work In Progress" merge request.
+These are used to discuss the proposed implementation but are not ready for inclusion in the `master` branch yet.
+Start the title of the merge request with "[WIP]" or "WIP:" to prevent it from being merged before it's ready.
+
+When you think the code is ready, assign the merge request to a reviewer.
+The reviewer can merge the changes when they think the code is ready for inclusion in the `master` branch.
+When they press the merge button, GitLab merges the code and creates a merge commit that makes this event easily visible later on.
+Merge requests always create a merge commit, even when the branch could be merged without one.
+This merge strategy is called "no fast-forward" in Git.
+After the merge, delete the feature branch since it is no longer needed.
+In GitLab, this deletion is an option when merging.
Suppose that a branch is merged but a problem occurs and the issue is reopened.
-In this case it is no problem to reuse the same branch name since it was deleted when the branch was merged.
-At any time there is at most one branch for every issue.
+In this case, it is no problem to reuse the same branch name since the first branch was deleted when it was merged.
+At any time, there is at most one branch for every issue.
It is possible that one feature branch solves more than one issue.
## Linking and closing issues from merge requests
![Merge request showing the linked issues that will be closed](close_issue_mr.png)
-Linking to issues can happen by mentioning them in commit messages (fixes #14, closes #67, etc.) or in the merge request description.
-GitLab then creates links to the mentioned issues and creates comments in the corresponding issues linking back to the merge request.
+Link to issues by mentioning them in commit messages or the description of a merge request, for example, "Fixes #16" or "Duck typing is preferred. See #12."
+GitLab then creates links to the mentioned issues and creates comments in the issues linking back to the merge request.
-These issues are closed once code is merged into the default branch.
+To automatically close linked issues, mention them with the words "fixes" or "closes," for example, "fixes #14" or "closes #67." GitLab closes these issues when the code is merged into the default branch.
-If you only want to make the reference without closing the issue you can also just mention it: "Duck typing is preferred. #12".
-
-If you have an issue that spans across multiple repositories, the best thing is to create an issue for each repository and link all issues to a parent issue.
+If you have an issue that spans across multiple repositories, create an issue for each repository and link all issues to a parent issue.
## Squashing commits with rebase
![Vim screen showing the rebase view](rebase.png)
-With git you can use an interactive rebase (`rebase -i`) to squash multiple commits into one and reorder them.
-In GitLab EE and .com you can also [rebase before merge](http://docs.gitlab.com/ee/workflow/rebase_before_merge.html) from the web interface.
-This functionality is useful if you made a couple of commits for small changes during development and want to replace them with a single commit or if you want to make the order more logical.
-However you should never rebase commits you have pushed to a remote server.
-Somebody can have referred to the commits or cherry-picked them.
-When you rebase you change the identifier (SHA-1) of the commit and this is confusing.
-If you do that the same change will be known under multiple identifiers and this can cause much confusion.
-If people already reviewed your code it will be hard for them to review only the improvements you made since then if you have rebased everything into one commit.
-Another reasons not to rebase is that you lose authorship information, maybe someone created a merge request, another person pushed a commit on there to improve it and a third one merged it.
-In this case rebasing all the commits into one prevent the other authors from being properly attributed and sharing part of the [git blame](https://git-scm.com/docs/git-blame).
-
-People are encouraged to commit often and to frequently push to the remote repository so other people are aware what everyone is working on.
-This will lead to many commits per change which makes the history harder to understand.
-But the advantages of having stable identifiers outweigh this drawback.
-And to understand a change in context one can always look at the merge commit that groups all the commits together when the code is merged into the master branch.
-
-After you merge multiple commits from a feature branch into the master branch this is harder to undo.
-If you had squashed all the commits into one you could have just reverted this commit but as we indicated you should not rebase commits after they are pushed.
-Fortunately [reverting a merge made some time ago](https://git-scm.com/blog/2010/03/02/undoing-merges.html) can be done with git.
-This however, requires having specific merge commits for the commits your want to revert.
-If you revert a merge and you change your mind, revert the revert instead of merging again since git will not allow you to merge the code again otherwise.
-
-Being able to revert a merge is a good reason always to create a merge commit when you merge manually with the `--no-ff` option.
-Git management software will always create a merge commit when you accept a merge request.
-
-## Do not order commits with rebase
+With Git, you can use an interactive rebase (`rebase -i`) to squash multiple commits into one or reorder them.
+This functionality is useful if you want to replace a couple of small commits with a single commit, or if you want to make the order more logical.
+
+However, you should never rebase commits you have pushed to a remote server.
+Rebasing creates new commits for all your changes, which can cause confusion because the same change would have multiple identifiers.
+It also causes merge errors for anyone working on the same branch because their history would not match with yours.
+Also, if someone has already reviewed your code, rebasing makes it hard to tell what changed since the last review.
+
+You should also never rebase commits authored by other people.
+Not only does this rewrite history, but it also loses authorship information.
+Rebasing prevents the other authors from being attributed and sharing part of the [`git blame`](https://git-scm.com/docs/git-blame).
+
+If a merge involves many commits, it may seem more difficult to undo.
+You might think to solve this by squashing all the changes into one commit before merging, but as discussed earlier, it is a bad idea to rebase commits that you have already pushed.
+Fortunately, there is an easy way to undo a merge with all its commits.
+The way to do this is by reverting the merge commit.
+Preserving this ability to revert a merge is a good reason to always use the "no fast-forward" (`--no-ff`) strategy when you merge manually.
+
+NOTE: **Note:**
+If you revert a merge commit and then change your mind, revert the revert commit to redo the merge.
+Git does not allow you to merge the code again otherwise.
+
+## Reducing merge commits in feature branches
![List of sequential merge commits](merge_commits.png)
-With git you can also rebase your feature branch commits to order them after the commits on the master branch.
-This prevents creating a merge commit when merging master into your feature branch and creates a nice linear history.
-However, just like with squashing you should never rebase commits you have pushed to a remote server.
-This makes it impossible to rebase work in progress that you already shared with your team which is something we recommend.
-When using rebase to keep your feature branch updated you [need to resolve similar conflicts again and again](https://blogs.atlassian.com/2013/10/git-team-workflows-merge-or-rebase/).
-You can reuse recorded resolutions (rerere) sometimes, but without rebasing you only have to solve the conflicts one time and you’re set.
-There has to be a better way to avoid many merge commits.
-
-The way to prevent creating many merge commits is to not frequently merge master into the feature branch.
-We'll discuss the three reasons to merge in master: leveraging code, merge conflicts, and long running branches.
-If you need to leverage some code that was introduced in master after you created the feature branch you can sometimes solve this by just cherry-picking a commit.
-If your feature branch has a merge conflict, creating a merge commit is a normal way of solving this.
-You can prevent some merge conflicts by using [gitattributes](http://git-scm.com/docs/gitattributes) for files that can be in a random order.
-For example in GitLab our changelog file is specified in .gitattributes as `CHANGELOG.md merge=union` so that there are fewer merge conflicts in it.
-The last reason for creating merge commits is having long lived branches that you want to keep up to date with the latest state of the project.
-Martin Fowler, in [his article about feature branches](http://martinfowler.com/bliki/FeatureBranch.html) talks about this Continuous Integration (CI).
-At GitLab we are guilty of confusing CI with branch testing. Quoting Martin Fowler: "I've heard people say they are doing CI because they are running builds, perhaps using a CI server, on every branch with every commit.
-That's continuous building, and a Good Thing, but there's no integration, so it's not CI.".
-The solution to prevent many merge commits is to keep your feature branches short-lived, the vast majority should take less than one day of work.
-If your feature branches commonly take more than a day of work, look into ways to create smaller units of work and/or use [feature toggles](http://martinfowler.com/bliki/FeatureToggle.html).
-As for the long running branches that take more than one day there are two strategies.
-In a CI strategy you can merge in master at the start of the day to prevent painful merges at a later time.
-In a synchronization point strategy you only merge in from well defined points in time, for example a tagged release.
-This strategy is [advocated by Linus Torvalds](https://www.mail-archive.com/dri-devel@lists.sourceforge.net/msg39091.html) because the state of the code at these points is better known.
-
-In conclusion, we can say that you should try to prevent merge commits, but not eliminate them.
-Your codebase should be clean but your history should represent what actually happened.
-Developing software happen in small messy steps and it is OK to have your history reflect this.
-You can use tools to view the network graphs of commits and understand the messy history that created your code.
-If you rebase code the history is incorrect, and there is no way for tools to remedy this because they can't deal with changing commit identifiers.
+Having lots of merge commits can make your repository history messy.
+Therefore, you should try to avoid merge commits in feature branches.
+Often, people avoid merge commits by just using rebase to reorder their commits after the commits on the `master` branch.
+Using rebase prevents a merge commit when merging `master` into your feature branch, and it creates a neat linear history.
+However, as discussed in [the section about rebasing](#squashing-commits-with-rebase), you should never rebase commits you have pushed to a remote server.
+This restriction makes it impossible to rebase work in progress that you already shared with your team, which is something we recommend.
-## Award emojis on issues and merge requests
+Rebasing also creates more work, since every time you rebase, you have to resolve similar conflicts.
+Sometimes you can reuse recorded resolutions (`rerere`), but merging is better since you only have to resolve conflicts once.
+Atlassian has a more thorough explanation of the tradeoffs between merging and rebasing [on their blog](https://www.atlassian.com/blog/git/git-team-workflows-merge-or-rebase).
-![Emoji bar in GitLab](award_emoji.png)
+A good way to prevent creating many merge commits is to not frequently merge `master` into the feature branch.
+There are three reasons to merge in `master`: utilizing new code, resolving merge conflicts, and updating long-running branches.
-It is common to voice approval or disapproval by using +1 or -1. In GitLab you
-can use emojis to give a virtual high five on issues and merge requests.
+If you need to utilize some code that was introduced in `master` after you created the feature branch, you can often solve this by just cherry-picking a commit.
-## Pushing and removing branches
+If your feature branch has a merge conflict, creating a merge commit is a standard way of solving this.
-![Remove checkbox for branch in merge requests](remove_checkbox.png)
+NOTE: **Note:**
+Sometimes you can use .gitattributes to reduce merge conflicts.
+For example, you can set your changelog file to use the [union merge driver](https://git-scm.com/docs/gitattributes#gitattributes-union) so that multiple new entries don't conflict with each other.
-We recommend that people push their feature branches frequently, even when they are not ready for review yet.
-By doing this you prevent team members from accidentally starting to work on the same issue.
-Of course this situation should already be prevented by assigning someone to the issue in the issue tracking software.
-However sometimes one of the two parties forgets to assign someone in the issue tracking software.
-After a branch is merged it should be removed from the source control software.
-In GitLab and similar systems this is an option when merging.
-This ensures that the branch overview in the repository management software shows only work in progress.
-This also ensures that when someone reopens the issue a new branch with the same name can be used without problem.
-When you reopen an issue you need to create a new merge request.
+The last reason for creating merge commits is to keep long-running feature branches up-to-date with the latest state of the project.
+The solution here is to keep your feature branches short-lived.
+Most feature branches should take less than one day of work.
+If your feature branches often take more than a day of work, try to split your features into smaller units of work.
+
+If you need to keep a feature branch open for more than a day, there are a few strategies to keep it up-to-date.
+One option is to use continuous integration (CI) to merge in `master` at the start of the day.
+Another option is to only merge in from well-defined points in time, for example, a tagged release.
+You could also use [feature toggles](https://martinfowler.com/bliki/FeatureToggle.html) to hide incomplete features so you can still merge back into `master` every day.
+
+> **Note:** Don't confuse automatic branch testing with continuous integration.
+> Martin Fowler makes this distinction in [his article about feature branches](https://martinfowler.com/bliki/FeatureBranch.html):
+>
+> "I've heard people say they are doing CI because they are running builds, perhaps using a CI server, on every branch with every commit.
+> That's continuous building, and a Good Thing, but there's no *integration*, so it's not CI."
+
+In conclusion, you should try to prevent merge commits, but not eliminate them.
+Your codebase should be clean, but your history should represent what actually happened.
+Developing software happens in small, messy steps, and it is OK to have your history reflect this.
+You can use tools to view the network graphs of commits and understand the messy history that created your code.
+If you rebase code, the history is incorrect, and there is no way for tools to remedy this because they can't deal with changing commit identifiers.
+
+## Commit often and push frequently
+
+Another way to make your development work easier is to commit often.
+Every time you have a working set of tests and code, you should make a commit.
+Splitting up work into individual commits provides context for developers looking at your code later.
+Smaller commits make it clear how a feature was developed, and they make it easy to roll back to a specific good point in time or to revert one code change without reverting several unrelated changes.
+
+Committing often also makes it easy to share your work, which is important so that everyone is aware of what you are working on.
+You should push your feature branch frequently, even when it is not yet ready for review.
+By sharing your work in a feature branch or [a merge request](#mergepull-requests-with-gitlab-flow), you prevent your team members from duplicating work.
+Sharing your work before it's complete also allows for discussion and feedback about the changes, which can help improve the code before it gets to review.
-## Committing often and with the right message
+## How to write a good commit message
![Good and bad commit message](good_commit.png)
-We recommend to commit early and often.
-Each time you have a functioning set of tests and code a commit can be made.
-The advantage is that when an extension or refactor goes wrong it is easy to revert to a working version.
-This is quite a change for programmers that used SVN before, they used to commit when their work was ready to share.
-The trick is to use the merge/pull request with multiple commits when your work is ready to share.
-The commit message should reflect your intention, not the contents of the commit.
-The contents of the commit can be easily seen anyway, the question is why you did it.
-An example of a good commit message is: "Combine templates to dry up the user views.".
-Some words that are bad commit messages because they don't contain much information are: change, improve and refactor.
-The word fix or fixes is also a red flag, unless it comes after the commit sentence and references an issue number.
-To see more information about the formatting of commit messages please see this great [blog post by Tim Pope](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
+A commit message should reflect your intention, not just the contents of the commit.
+It is easy to see the changes in a commit, so the commit message should explain why you made those changes.
+An example of a good commit message is: "Combine templates to reduce duplicate code in the user views."
+The words "change," "improve," "fix," and "refactor" don't add much information to a commit message.
+For example, "Improve XML generation" could be better written as "Properly escape special characters in XML generation."
+For more information about formatting commit messages, please see this excellent [blog post by Tim Pope](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
## Testing before merging
-![Merge requests showing the test states, red, yellow and green](ci_mr.png)
-
-In old workflows the Continuous Integration (CI) server commonly ran tests on the master branch only.
-Developers had to ensure their code did not break the master branch.
-When using GitLab flow developers create their branches from this master branch so it is essential it is green.
-Therefore each merge request must be tested before it is accepted.
-CI software like Travis and GitLab CI show the build results right in the merge request itself to make this easy.
-One drawback is that they are testing the feature branch itself and not the merged result.
-What one can do to improve this is to test the merged result itself.
-The problem is that the merge result changes every time something is merged into master.
-Retesting on every commit to master is computationally expensive and means you are more frequently waiting for test results.
-If there are no merge conflicts and the feature branches are short lived the risk is acceptable.
-If there are merge conflicts you merge the master branch into the feature branch and the CI server will rerun the tests.
-If you have long lived feature branches that last for more than a few days you should make your issues smaller.
+![Merge requests showing the test states: red, yellow, and green](ci_mr.png)
-## Working with feature branches
+In old workflows, the continuous integration (CI) server commonly ran tests on the `master` branch only.
+Developers had to ensure their code did not break the `master` branch.
+When using GitLab flow, developers create their branches from this `master` branch, so it is essential that it never breaks.
+Therefore, each merge request must be tested before it is accepted.
+CI software like Travis CI and GitLab CI show the build results right in the merge request itself to make this easy.
-![Shell output showing git pull output](git_pull.png)
+There is one drawback to testing merge requests: the CI server only tests the feature branch itself, not the merged result.
+Ideally, the server could also test the `master` branch after each change.
+However, retesting on every commit to `master` is computationally expensive and means you are more frequently waiting for test results.
+Since feature branches should be short-lived, testing just the branch is an acceptable risk.
+If new commits in `master` cause merge conflicts with the feature branch, merge `master` back into the branch to make the CI server re-run the tests.
+As said before, if you often have feature branches that last for more than a few days, you should make your issues smaller.
-When initiating a feature branch, always start with an up to date master to branch off from.
-If you know beforehand that your work absolutely depends on another branch you can also branch from there.
-If you need to merge in another branch after starting explain the reason in the merge commit.
-If you have not pushed your commits to a shared location yet you can also rebase on master or another feature branch.
-Do not merge in upstream if your code will work and merge cleanly without doing so, Linus even says that [you should never merge in upstream at random points, only at major releases](https://lwn.net/Articles/328438/).
-Merging only when needed prevents creating merge commits in your feature branch that later end up littering the master history.
+## Working with feature branches
-### References
+![Shell output showing git pull output](git_pull.png)
-- [Git Flow by Vincent Driessen](http://nvie.com/posts/a-successful-git-branching-model/)
+When creating a feature branch, always branch from an up-to-date `master`.
+If you know before you start that your work depends on another branch, you can also branch from there.
+If you need to merge in another branch after starting, explain the reason in the merge commit.
+If you have not pushed your commits to a shared location yet, you can also incorporate changes by rebasing on `master` or another feature branch.
+Do not merge from upstream again if your code can work and merge cleanly without doing so.
+Merging only when needed prevents creating merge commits in your feature branch that later end up littering the `master` history.
diff --git a/doc/workflow/shortcuts.md b/doc/workflow/shortcuts.md
index 7359e1c6119..6ed6b0bda66 100644
--- a/doc/workflow/shortcuts.md
+++ b/doc/workflow/shortcuts.md
@@ -82,6 +82,8 @@ You can see GitLab's keyboard shortcuts by using 'shift + ?'
| <kbd>r</kbd> | Reply (quoting selected text) |
| <kbd>e</kbd> | Edit issue/merge request |
| <kbd>l</kbd> | Change label |
+| <kbd>]</kbd> or <kbd>j</kbd> | Move to next file |
+| <kbd>[</kbd> or <kbd>k</kbd> | Move to previous file |
## Wiki pages
diff --git a/doc/workflow/time_tracking.md b/doc/workflow/time_tracking.md
index ad43189148c..e60b6819bf1 100644
--- a/doc/workflow/time_tracking.md
+++ b/doc/workflow/time_tracking.md
@@ -11,7 +11,7 @@ Time Tracking lets you:
- Record the time spent working on an issue or a merge request.
- Add an estimate of the amount of time needed to complete an issue or a merge
-request.
+ request.
You don't have to indicate an estimate to enter the time spent, and vice versa.
diff --git a/jest.config.js b/jest.config.js
index 4dab7c2891a..4e346005b8a 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -13,11 +13,15 @@ if (process.env.CI) {
// eslint-disable-next-line import/no-commonjs
module.exports = {
- testMatch: ['<rootDir>/spec/frontend/**/*_spec.js'],
+ testMatch: ['<rootDir>/spec/frontend/**/*_spec.js', '<rootDir>/ee/spec/frontend/**/*_spec.js'],
moduleFileExtensions: ['js', 'json', 'vue'],
moduleNameMapper: {
'^~(.*)$': '<rootDir>/app/assets/javascripts$1',
+ '^ee(.*)$': '<rootDir>/ee/app/assets/javascripts$1',
+ '^fixtures(.*)$': '<rootDir>/spec/javascripts/fixtures$1',
'^helpers(.*)$': '<rootDir>/spec/frontend/helpers$1',
+ '^vendor(.*)$': '<rootDir>/vendor/assets/javascripts$1',
+ '\\.(jpg|jpeg|png|svg)$': '<rootDir>/spec/frontend/__mocks__/file_mock.js',
},
collectCoverageFrom: ['<rootDir>/app/assets/javascripts/**/*.{js,vue}'],
coverageDirectory: '<rootDir>/coverage-frontend/',
@@ -25,10 +29,12 @@ module.exports = {
cacheDirectory: '<rootDir>/tmp/cache/jest',
modulePathIgnorePatterns: ['<rootDir>/.yarn-cache/'],
reporters,
- setupTestFrameworkScriptFile: '<rootDir>/spec/frontend/test_setup.js',
+ setupFilesAfterEnv: ['<rootDir>/spec/frontend/test_setup.js'],
restoreMocks: true,
transform: {
+ '^.+\\.(gql|graphql)$': 'jest-transform-graphql',
'^.+\\.js$': 'babel-jest',
'^.+\\.vue$': 'vue-jest',
},
+ transformIgnorePatterns: ['node_modules/(?!(@gitlab/ui)/)'],
};
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 4dd1b459554..bf8ddba6f0d 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -141,6 +141,7 @@ module API
mount ::API::Projects
mount ::API::ProjectSnapshots
mount ::API::ProjectSnippets
+ mount ::API::ProjectStatistics
mount ::API::ProjectTemplates
mount ::API::ProtectedBranches
mount ::API::ProtectedTags
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 9d23daafe95..8defc59224d 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -323,6 +323,22 @@ module API
present paginate(commit.merge_requests), with: Entities::MergeRequestBasic
end
+
+ desc "Get a commit's GPG signature" do
+ success Entities::CommitSignature
+ end
+ params do
+ requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag'
+ end
+ get ':id/repository/commits/:sha/signature', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do
+ commit = user_project.commit(params[:sha])
+ not_found! 'Commit' unless commit
+
+ signature = commit.signature
+ not_found! 'GPG Signature' unless signature
+
+ present signature, with: Entities::CommitSignature
+ end
end
end
end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index beb8ce349b4..18f15632f2b 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -300,6 +300,18 @@ module API
expose :build_artifacts_size, as: :job_artifacts_size
end
+ class ProjectDailyFetches < Grape::Entity
+ expose :fetch_count, as: :count
+ expose :date
+ end
+
+ class ProjectDailyStatistics < Grape::Entity
+ expose :fetches do
+ expose :total_fetch_count, as: :total
+ expose :fetches, as: :days, using: ProjectDailyFetches
+ end
+ end
+
class Member < Grape::Entity
expose :user, merge: true, using: UserBasic
expose :access_level
@@ -369,8 +381,9 @@ module API
end
class Commit < Grape::Entity
- expose :id, :short_id, :title, :created_at
+ expose :id, :short_id, :created_at
expose :parent_ids
+ expose :full_title, as: :title
expose :safe_message, as: :message
expose :author_name, :author_email, :authored_date
expose :committer_name, :committer_email, :committed_date
@@ -391,6 +404,13 @@ module API
expose :project_id
end
+ class CommitSignature < Grape::Entity
+ expose :gpg_key_id
+ expose :gpg_key_primary_keyid, :gpg_key_user_name, :gpg_key_user_email
+ expose :verification_status
+ expose :gpg_key_subkey_id
+ end
+
class BasicRef < Grape::Entity
expose :type, :name
end
@@ -465,6 +485,12 @@ module API
expose(:project_id) { |entity| entity&.project.try(:id) }
expose :title, :description
expose :state, :created_at, :updated_at
+
+ # Avoids an N+1 query when metadata is included
+ def issuable_metadata(subject, options, method)
+ cached_subject = options.dig(:issuable_metadata, subject.id)
+ (cached_subject || subject).public_send(method) # rubocop: disable GitlabSecurity/PublicSend
+ end
end
class Diff < Grape::Entity
@@ -510,39 +536,26 @@ module API
class IssueBasic < ProjectEntity
expose :closed_at
expose :closed_by, using: Entities::UserBasic
- expose :labels do |issue, options|
+ expose :labels do |issue|
# Avoids an N+1 query since labels are preloaded
issue.labels.map(&:title).sort
end
expose :milestone, using: Entities::Milestone
expose :assignees, :author, using: Entities::UserBasic
- expose :assignee, using: ::API::Entities::UserBasic do |issue, options|
+ expose :assignee, using: ::API::Entities::UserBasic do |issue|
issue.assignees.first
end
- expose :user_notes_count
- expose :upvotes do |issue, options|
- if options[:issuable_metadata]
- # Avoids an N+1 query when metadata is included
- options[:issuable_metadata][issue.id].upvotes
- else
- issue.upvotes
- end
- end
- expose :downvotes do |issue, options|
- if options[:issuable_metadata]
- # Avoids an N+1 query when metadata is included
- options[:issuable_metadata][issue.id].downvotes
- else
- issue.downvotes
- end
- end
+ expose(:user_notes_count) { |issue, options| issuable_metadata(issue, options, :user_notes_count) }
+ expose(:merge_requests_count) { |issue, options| issuable_metadata(issue, options, :merge_requests_count) }
+ expose(:upvotes) { |issue, options| issuable_metadata(issue, options, :upvotes) }
+ expose(:downvotes) { |issue, options| issuable_metadata(issue, options, :downvotes) }
expose :due_date
expose :confidential
expose :discussion_locked
- expose :web_url do |issue, options|
+ expose :web_url do |issue|
Gitlab::UrlBuilder.build(issue)
end
@@ -642,23 +655,12 @@ module API
MarkupHelper.markdown_field(entity, :description)
end
expose :target_branch, :source_branch
- expose :upvotes do |merge_request, options|
- if options[:issuable_metadata]
- options[:issuable_metadata][merge_request.id].upvotes
- else
- merge_request.upvotes
- end
- end
- expose :downvotes do |merge_request, options|
- if options[:issuable_metadata]
- options[:issuable_metadata][merge_request.id].downvotes
- else
- merge_request.downvotes
- end
- end
+ expose(:user_notes_count) { |merge_request, options| issuable_metadata(merge_request, options, :user_notes_count) }
+ expose(:upvotes) { |merge_request, options| issuable_metadata(merge_request, options, :upvotes) }
+ expose(:downvotes) { |merge_request, options| issuable_metadata(merge_request, options, :downvotes) }
expose :author, :assignee, using: Entities::UserBasic
expose :source_project_id, :target_project_id
- expose :labels do |merge_request, options|
+ expose :labels do |merge_request|
# Avoids an N+1 query since labels are preloaded
merge_request.labels.map(&:title).sort
end
@@ -676,7 +678,6 @@ module API
end
expose :diff_head_sha, as: :sha
expose :merge_commit_sha
- expose :user_notes_count
expose :discussion_locked
expose :should_remove_source_branch?, as: :should_remove_source_branch
expose :force_remove_source_branch?, as: :force_remove_source_branch
@@ -684,7 +685,7 @@ module API
# Deprecated
expose :allow_collaboration, as: :allow_maintainer_to_push, if: -> (merge_request, _) { merge_request.for_fork? }
- expose :web_url do |merge_request, options|
+ expose :web_url do |merge_request|
Gitlab::UrlBuilder.build(merge_request)
end
@@ -731,6 +732,12 @@ module API
def build_available?(options)
options[:project]&.feature_available?(:builds, options[:current_user])
end
+
+ expose :user do
+ expose :can_merge do |merge_request, options|
+ merge_request.can_be_merged_by?(options[:current_user])
+ end
+ end
end
class MergeRequestChanges < MergeRequest
@@ -1003,7 +1010,7 @@ module API
end
class LabelBasic < Grape::Entity
- expose :id, :name, :color, :description
+ expose :id, :name, :color, :description, :text_color
end
class Label < LabelBasic
@@ -1031,6 +1038,9 @@ module API
expose :priority do |label, options|
label.priority(options[:parent])
end
+ expose :is_project_label do |label, options|
+ label.is_a?(::ProjectLabel)
+ end
end
class List < Grape::Entity
@@ -1359,13 +1369,9 @@ module API
class GitInfo < Grape::Entity
expose :repo_url, :ref, :sha, :before_sha
- expose :ref_type do |model|
- if model.tag
- 'tag'
- else
- 'branch'
- end
- end
+ expose :ref_type
+ expose :refspecs
+ expose :git_depth, as: :depth
end
class RunnerInfo < Grape::Entity
diff --git a/lib/api/features.rb b/lib/api/features.rb
index 835aac05905..4dc1834c644 100644
--- a/lib/api/features.rb
+++ b/lib/api/features.rb
@@ -42,6 +42,7 @@ module API
requires :value, type: String, desc: '`true` or `false` to enable/disable, an integer for percentage of time'
optional :feature_group, type: String, desc: 'A Feature group name'
optional :user, type: String, desc: 'A GitLab username'
+ optional :group, type: String, desc: "A GitLab group's path, such as 'gitlab-org'"
optional :project, type: String, desc: 'A projects path, like gitlab-org/gitlab-ce'
end
post ':name' do
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 2eb7b04711a..54cd4cd9cdb 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -299,6 +299,12 @@ module API
items.search(text)
end
+ def order_options_with_tie_breaker
+ order_options = { params[:order_by] => params[:sort] }
+ order_options['id'] ||= 'desc'
+ order_options
+ end
+
# error helpers
def forbidden!(reason = nil)
@@ -393,7 +399,7 @@ module API
# rubocop: disable CodeReuse/ActiveRecord
def reorder_projects(projects)
- projects.reorder(params[:order_by] => params[:sort])
+ projects.reorder(order_options_with_tie_breaker)
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb
index 4eaaca96b49..fe78049af87 100644
--- a/lib/api/helpers/internal_helpers.rb
+++ b/lib/api/helpers/internal_helpers.rb
@@ -81,6 +81,14 @@ module API
Gitlab::GlRepository.gl_repository(project, wiki?)
end
+ def gl_project_path
+ if wiki?
+ project.wiki.full_path
+ else
+ project.full_path
+ end
+ end
+
# Return the repository depending on whether we want the wiki or the
# regular repository
def repository
diff --git a/lib/api/helpers/pagination.rb b/lib/api/helpers/pagination.rb
index de59c915d66..d00e61678b5 100644
--- a/lib/api/helpers/pagination.rb
+++ b/lib/api/helpers/pagination.rb
@@ -13,6 +13,33 @@ module API
strategy.new(self).paginate(relation)
end
+ class Base
+ private
+
+ def per_page
+ @per_page ||= params[:per_page]
+ end
+
+ def base_request_uri
+ @base_request_uri ||= URI.parse(request.url).tap do |uri|
+ uri.host = Gitlab.config.gitlab.host
+ uri.port = nil
+ end
+ end
+
+ def build_page_url(query_params:)
+ base_request_uri.tap do |uri|
+ uri.query = query_params
+ end.to_s
+ end
+
+ def page_href(next_page_params = {})
+ query_params = params.merge(**next_page_params, per_page: per_page).to_query
+
+ build_page_url(query_params: query_params)
+ end
+ end
+
class KeysetPaginationInfo
attr_reader :relation, :request_context
@@ -85,7 +112,7 @@ module API
end
end
- class KeysetPaginationStrategy
+ class KeysetPaginationStrategy < Base
attr_reader :request_context
delegate :params, :header, :request, to: :request_context
@@ -141,10 +168,6 @@ module API
]
end
- def per_page
- params[:per_page]
- end
-
def add_default_pagination_headers
header 'X-Per-Page', per_page.to_s
end
@@ -154,22 +177,12 @@ module API
header 'Link', link_for('next', next_page_params)
end
- def page_href(next_page_params)
- request_url = request.url.split('?').first
- request_params = params.dup
- request_params[:per_page] = per_page
-
- request_params.merge!(next_page_params) if next_page_params
-
- "#{request_url}?#{request_params.to_query}"
- end
-
def link_for(rel, next_page_params)
%(<#{page_href(next_page_params)}>; rel="#{rel}")
end
end
- class DefaultPaginationStrategy
+ class DefaultPaginationStrategy < Base
attr_reader :request_context
delegate :params, :header, :request, to: :request_context
@@ -198,15 +211,13 @@ module API
end
end
- # rubocop: disable CodeReuse/ActiveRecord
def add_default_order(relation)
if relation.is_a?(ActiveRecord::Relation) && relation.order_values.empty?
- relation = relation.order(:id)
+ relation = relation.order(:id) # rubocop: disable CodeReuse/ActiveRecord
end
relation
end
- # rubocop: enable CodeReuse/ActiveRecord
def add_pagination_headers(paginated_data)
header 'X-Per-Page', paginated_data.limit_value.to_s
@@ -222,27 +233,13 @@ module API
end
def pagination_links(paginated_data)
- request_url = request.url.split('?').first
- request_params = params.clone
- request_params[:per_page] = paginated_data.limit_value
-
- links = []
-
- request_params[:page] = paginated_data.prev_page
- links << %(<#{request_url}?#{request_params.to_query}>; rel="prev") if request_params[:page]
-
- request_params[:page] = paginated_data.next_page
- links << %(<#{request_url}?#{request_params.to_query}>; rel="next") if request_params[:page]
-
- request_params[:page] = 1
- links << %(<#{request_url}?#{request_params.to_query}>; rel="first")
-
- unless data_without_counts?(paginated_data)
- request_params[:page] = total_pages(paginated_data)
- links << %(<#{request_url}?#{request_params.to_query}>; rel="last")
- end
+ [].tap do |links|
+ links << %(<#{page_href(page: paginated_data.prev_page)}>; rel="prev") if paginated_data.prev_page
+ links << %(<#{page_href(page: paginated_data.next_page)}>; rel="next") if paginated_data.next_page
+ links << %(<#{page_href(page: 1)}>; rel="first")
- links.join(', ')
+ links << %(<#{page_href(page: total_pages(paginated_data))}>; rel="last") unless data_without_counts?(paginated_data)
+ end.join(', ')
end
def total_pages(paginated_data)
diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb
index 16df8e830e1..ff73a49d5e8 100644
--- a/lib/api/helpers/runner.rb
+++ b/lib/api/helpers/runner.rb
@@ -26,7 +26,7 @@ module API
end
def get_runner_ip
- { ip_address: request.env["HTTP_X_FORWARDED_FOR"] || request.ip }
+ { ip_address: env["action_dispatch.remote_ip"].to_s || request.ip }
end
def current_runner
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index 9488b3469d9..70b32f7d758 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -77,6 +77,7 @@ module API
when ::Gitlab::GitAccessResult::Success
payload = {
gl_repository: gl_repository,
+ gl_project_path: gl_project_path,
gl_id: Gitlab::GlId.gl_id(user),
gl_username: user&.username,
git_config_options: [],
@@ -117,13 +118,7 @@ module API
raise ActiveRecord::RecordNotFound.new("No key_id or user_id passed!")
end
- token_handler = Gitlab::LfsToken.new(actor)
-
- {
- username: token_handler.actor_name,
- lfs_token: token_handler.token,
- repository_http_path: project.http_url_to_repo
- }
+ Gitlab::LfsToken.new(actor).authentication_payload(project.http_url_to_repo)
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index b3636c98550..f43f4d961d6 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -29,8 +29,7 @@ module API
issues = IssuesFinder.new(current_user, args).execute
.preload(:assignees, :labels, :notes, :timelogs, :project, :author, :closed_by)
-
- issues.reorder(args[:order_by] => args[:sort])
+ issues.reorder(order_options_with_tie_breaker)
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -55,6 +54,7 @@ module API
optional :scope, type: String, values: %w[created-by-me assigned-to-me created_by_me assigned_to_me all],
desc: 'Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`'
optional :my_reaction_emoji, type: String, desc: 'Return issues reacted by the authenticated user by the given emoji'
+ optional :confidential, type: Boolean, desc: 'Filter confidential or public issues'
use :pagination
use :issues_params_ee
@@ -304,19 +304,14 @@ module API
get ':id/issues/:issue_iid/related_merge_requests' do
issue = find_project_issue(params[:issue_iid])
- merge_request_iids = ::Issues::ReferencedMergeRequestsService.new(user_project, current_user)
+ merge_requests = ::Issues::ReferencedMergeRequestsService.new(user_project, current_user)
.execute(issue)
.flatten
- .map(&:iid)
-
- merge_requests =
- if merge_request_iids.present?
- MergeRequestsFinder.new(current_user, project_id: user_project.id, iids: merge_request_iids).execute
- else
- MergeRequest.none
- end
- present paginate(merge_requests), with: Entities::MergeRequestBasic, current_user: current_user, project: user_project
+ present paginate(::Kaminari.paginate_array(merge_requests)),
+ with: Entities::MergeRequestBasic,
+ current_user: current_user,
+ project: user_project
end
desc 'List merge requests closing issue' do
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index df46b4446ff..44f1e81caf2 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -38,7 +38,7 @@ module API
args[:scope] = args[:scope].underscore if args[:scope]
merge_requests = MergeRequestsFinder.new(current_user, args).execute
- .reorder(args[:order_by] => args[:sort])
+ .reorder(order_options_with_tie_breaker)
merge_requests = paginate(merge_requests)
.preload(:source_project, :target_project)
@@ -369,11 +369,11 @@ module API
merge_request.update(squash: params[:squash]) if params[:squash]
- merge_params = {
+ merge_params = HashWithIndifferentAccess.new(
commit_message: params[:merge_commit_message],
squash_commit_message: params[:squash_commit_message],
should_remove_source_branch: params[:should_remove_source_branch]
- }
+ )
if merge_when_pipeline_succeeds && merge_request.head_pipeline && merge_request.head_pipeline.active?
::MergeRequests::MergeWhenPipelineSucceedsService
@@ -388,6 +388,31 @@ module API
present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project
end
+ desc 'Merge a merge request to its default temporary merge ref path'
+ params do
+ optional :merge_commit_message, type: String, desc: 'Custom merge commit message'
+ end
+ put ':id/merge_requests/:merge_request_iid/merge_to_ref' do
+ merge_request = find_project_merge_request(params[:merge_request_iid])
+
+ authorize! :admin_merge_request, user_project
+
+ merge_params = {
+ commit_message: params[:merge_commit_message]
+ }
+
+ result = ::MergeRequests::MergeToRefService
+ .new(merge_request.target_project, current_user, merge_params)
+ .execute(merge_request)
+
+ if result[:status] == :success
+ present result.slice(:commit_id), 200
+ else
+ http_status = result[:http_status] || 400
+ render_api_error!(result[:message], http_status)
+ end
+ end
+
desc 'Cancel merge if "Merge When Pipeline Succeeds" is enabled' do
success Entities::MergeRequest
end
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index 1bdf7aeb119..f7bd092ce50 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -39,7 +39,7 @@ module API
# at the DB query level (which we cannot in that case), the current
# page can have less elements than :per_page even if
# there's more than one page.
- raw_notes = noteable.notes.with_metadata.reorder(params[:order_by] => params[:sort])
+ raw_notes = noteable.notes.with_metadata.reorder(order_options_with_tie_breaker)
notes =
# paginate() only works with a relation. This could lead to a
# mismatch between the pagination headers info and the actual notes
diff --git a/lib/api/project_milestones.rb b/lib/api/project_milestones.rb
index da31bcb8dac..ca24742b7a3 100644
--- a/lib/api/project_milestones.rb
+++ b/lib/api/project_milestones.rb
@@ -98,6 +98,23 @@ module API
milestone_issuables_for(user_project, :merge_request)
end
+
+ desc 'Promote a milestone to group milestone' do
+ detail 'This feature was introduced in GitLab 11.9'
+ end
+ post ':id/milestones/:milestone_id/promote' do
+ begin
+ authorize! :admin_milestone, user_project
+ authorize! :admin_milestone, user_project.group
+
+ milestone = user_project.milestones.find(params[:milestone_id])
+ Milestones::PromoteService.new(user_project, current_user).execute(milestone)
+
+ status(200)
+ rescue Milestones::PromoteService::PromoteMilestoneError => error
+ render_api_error!(error.message, 400)
+ end
+ end
end
end
end
diff --git a/lib/api/project_statistics.rb b/lib/api/project_statistics.rb
new file mode 100644
index 00000000000..2f73785f72d
--- /dev/null
+++ b/lib/api/project_statistics.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module API
+ class ProjectStatistics < Grape::API
+ before do
+ authenticate!
+ not_found! unless user_project.daily_statistics_enabled?
+ authorize! :daily_statistics, user_project
+ end
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ desc 'Get the list of project fetch statistics for the last 30 days'
+ get ":id/statistics" do
+ statistic_finder = ::Projects::DailyStatisticsFinder.new(user_project)
+
+ present statistic_finder, with: Entities::ProjectDailyStatistics
+ end
+ end
+ end
+end
diff --git a/lib/api/project_templates.rb b/lib/api/project_templates.rb
index d05ddad7466..119902a189c 100644
--- a/lib/api/project_templates.rb
+++ b/lib/api/project_templates.rb
@@ -36,7 +36,10 @@ module API
optional :project, type: String, desc: 'The project name to use when expanding placeholders in the template. Only affects licenses'
optional :fullname, type: String, desc: 'The full name of the copyright holder to use when expanding placeholders in the template. Only affects licenses'
end
- get ':id/templates/:type/:name', requirements: { name: /[\w\.-]+/ } do
+ # The regex is needed to ensure a period (e.g. agpl-3.0)
+ # isn't confused with a format type. We also need to allow encoded
+ # values (e.g. C%2B%2B for C++), so allow % and + as well.
+ get ':id/templates/:type/:name', requirements: { name: /[\w%.+-]+/ } do
template = TemplateFinder
.build(params[:type], user_project, name: params[:name])
.execute
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 6a93ef9f3ad..b23fe6cd4e7 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -258,6 +258,8 @@ module API
end
params do
optional :namespace, type: String, desc: 'The ID or name of the namespace that the project will be forked into'
+ optional :path, type: String, desc: 'The path that will be assigned to the fork'
+ optional :name, type: String, desc: 'The name that will be assigned to the fork'
end
post ':id/fork' do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42284')
@@ -386,7 +388,11 @@ module API
desc 'Get languages in project repository'
get ':id/languages' do
- user_project.repository.languages.map { |language| language.values_at(:label, :value) }.to_h
+ if user_project.repository_languages.present?
+ user_project.repository_languages.map { |l| [l.name, l.share] }.to_h
+ else
+ user_project.repository.languages.map { |language| language.values_at(:label, :value) }.to_h
+ end
end
desc 'Remove a project'
diff --git a/lib/api/runners.rb b/lib/api/runners.rb
index f72b33605a7..f3fea463e7f 100644
--- a/lib/api/runners.rb
+++ b/lib/api/runners.rb
@@ -17,6 +17,7 @@ module API
desc: 'The type of the runners to show'
optional :status, type: String, values: Ci::Runner::AVAILABLE_STATUSES,
desc: 'The status of the runners to show'
+ optional :tag_list, type: Array[String], desc: 'The tags of the runners to show'
use :pagination
end
get do
@@ -24,6 +25,7 @@ module API
runners = filter_runners(runners, params[:scope], allowed_scopes: Ci::Runner::AVAILABLE_STATUSES)
runners = filter_runners(runners, params[:type], allowed_scopes: Ci::Runner::AVAILABLE_TYPES)
runners = filter_runners(runners, params[:status], allowed_scopes: Ci::Runner::AVAILABLE_STATUSES)
+ runners = runners.tagged_with(params[:tag_list]) if params[:tag_list]
present paginate(runners), with: Entities::Runner
end
@@ -38,6 +40,7 @@ module API
desc: 'The type of the runners to show'
optional :status, type: String, values: Ci::Runner::AVAILABLE_STATUSES,
desc: 'The status of the runners to show'
+ optional :tag_list, type: Array[String], desc: 'The tags of the runners to show'
use :pagination
end
get 'all' do
@@ -47,6 +50,7 @@ module API
runners = filter_runners(runners, params[:scope])
runners = filter_runners(runners, params[:type], allowed_scopes: Ci::Runner::AVAILABLE_TYPES)
runners = filter_runners(runners, params[:status], allowed_scopes: Ci::Runner::AVAILABLE_STATUSES)
+ runners = runners.tagged_with(params[:tag_list]) if params[:tag_list]
present paginate(runners), with: Entities::Runner
end
@@ -139,6 +143,7 @@ module API
desc: 'The type of the runners to show'
optional :status, type: String, values: Ci::Runner::AVAILABLE_STATUSES,
desc: 'The status of the runners to show'
+ optional :tag_list, type: Array[String], desc: 'The tags of the runners to show'
use :pagination
end
get ':id/runners' do
@@ -146,6 +151,7 @@ module API
runners = filter_runners(runners, params[:scope])
runners = filter_runners(runners, params[:type], allowed_scopes: Ci::Runner::AVAILABLE_TYPES)
runners = filter_runners(runners, params[:status], allowed_scopes: Ci::Runner::AVAILABLE_STATUSES)
+ runners = runners.tagged_with(params[:tag_list]) if params[:tag_list]
present paginate(runners), with: Entities::Runner
end
diff --git a/lib/api/services.rb b/lib/api/services.rb
index 36bdba2d765..bda6be51553 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -368,8 +368,9 @@ module API
name: :webhook,
type: String,
desc: 'The Hangouts Chat webhook. e.g. https://chat.googleapis.com/v1/spaces…'
- }
- ],
+ },
+ CHAT_NOTIFICATION_EVENTS
+ ].flatten,
'irker' => [
{
required: true,
@@ -430,7 +431,7 @@ module API
{
required: false,
name: :jira_issue_transition_id,
- type: Integer,
+ type: String,
desc: 'The ID of a transition that moves issues to a closed state. You can find this number under the JIRA workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot][trans]). By default, this ID is set to `2`'
}
],
@@ -591,6 +592,26 @@ module API
desc: 'The description of the tracker'
}
],
+ 'youtrack' => [
+ {
+ required: true,
+ name: :project_url,
+ type: String,
+ desc: 'The project URL'
+ },
+ {
+ required: true,
+ name: :issues_url,
+ type: String,
+ desc: 'The issues URL'
+ },
+ {
+ required: false,
+ name: :description,
+ type: String,
+ desc: 'The description of the tracker'
+ }
+ ],
'slack' => [
CHAT_NOTIFICATION_SETTINGS,
CHAT_NOTIFICATION_FLAGS,
@@ -664,6 +685,7 @@ module API
PrometheusService,
PushoverService,
RedmineService,
+ YoutrackService,
SlackService,
MattermostService,
MicrosoftTeamsService,
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 8ce09a8881b..7d88880d412 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -26,7 +26,7 @@ module API
# rubocop: disable CodeReuse/ActiveRecord
def reorder_users(users)
if params[:order_by] && params[:sort]
- users.reorder(params[:order_by] => params[:sort])
+ users.reorder(order_options_with_tie_breaker)
else
users
end
diff --git a/lib/api/wikis.rb b/lib/api/wikis.rb
index ef0e3decc2c..994074ddc67 100644
--- a/lib/api/wikis.rb
+++ b/lib/api/wikis.rb
@@ -11,9 +11,7 @@ module API
}
end
- params :wiki_page_params do
- requires :content, type: String, desc: 'Content of a wiki page'
- requires :title, type: String, desc: 'Title of a wiki page'
+ params :common_wiki_page_params do
optional :format,
type: String,
values: ProjectWiki::MARKUPS.values.map(&:to_s),
@@ -54,7 +52,9 @@ module API
success Entities::WikiPage
end
params do
- use :wiki_page_params
+ requires :title, type: String, desc: 'Title of a wiki page'
+ requires :content, type: String, desc: 'Content of a wiki page'
+ use :common_wiki_page_params
end
post ':id/wikis' do
authorize! :create_wiki, user_project
@@ -72,7 +72,10 @@ module API
success Entities::WikiPage
end
params do
- use :wiki_page_params
+ optional :title, type: String, desc: 'Title of a wiki page'
+ optional :content, type: String, desc: 'Content of a wiki page'
+ use :common_wiki_page_params
+ at_least_one_of :content, :title, :format
end
put ':id/wikis/:slug' do
authorize! :create_wiki, user_project
diff --git a/lib/banzai/filter/footnote_filter.rb b/lib/banzai/filter/footnote_filter.rb
index 97527976437..de133774dfa 100644
--- a/lib/banzai/filter/footnote_filter.rb
+++ b/lib/banzai/filter/footnote_filter.rb
@@ -29,21 +29,30 @@ module Banzai
# Sanitization stripped off the section wrapper - add it back in
first_footnote.parent.wrap('<section class="footnotes">')
rand_suffix = "-#{random_number}"
+ modified_footnotes = {}
doc.css('sup > a[id]').each do |link_node|
ref_num = link_node[:id].delete_prefix(FOOTNOTE_LINK_ID_PREFIX)
footnote_node = doc.at_css("li[id=#{fn_id(ref_num)}]")
- backref_node = footnote_node.at_css("a[href=\"##{fnref_id(ref_num)}\"]")
- if ref_num =~ INTEGER_PATTERN && footnote_node && backref_node
- link_node[:href] += rand_suffix
- link_node[:id] += rand_suffix
- footnote_node[:id] += rand_suffix
- backref_node[:href] += rand_suffix
+ if INTEGER_PATTERN.match?(ref_num) && (footnote_node || modified_footnotes[ref_num])
+ link_node[:href] += rand_suffix
+ link_node[:id] += rand_suffix
# Sanitization stripped off class - add it back in
link_node.parent.append_class('footnote-ref')
- backref_node.append_class('footnote-backref')
+
+ unless modified_footnotes[ref_num]
+ footnote_node[:id] += rand_suffix
+ backref_node = footnote_node.at_css("a[href=\"##{fnref_id(ref_num)}\"]")
+
+ if backref_node
+ backref_node[:href] += rand_suffix
+ backref_node.append_class('footnote-backref')
+ end
+
+ modified_footnotes[ref_num] = true
+ end
end
end
diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb
index 93e6d6470f1..2745905c5ff 100644
--- a/lib/banzai/filter/relative_link_filter.rb
+++ b/lib/banzai/filter/relative_link_filter.rb
@@ -150,7 +150,10 @@ module Banzai
end
def uri_type(path)
- @uri_types[path] ||= current_commit.uri_type(path)
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/58011
+ Gitlab::GitalyClient.allow_n_plus_1_calls do
+ @uri_types[path] ||= current_commit.uri_type(path)
+ end
end
def current_commit
diff --git a/lib/bitbucket_server/connection.rb b/lib/bitbucket_server/connection.rb
index 9c14b26c65a..fbd451efb23 100644
--- a/lib/bitbucket_server/connection.rb
+++ b/lib/bitbucket_server/connection.rb
@@ -77,6 +77,7 @@ module BitbucketServer
private
def check_errors!(response)
+ return if ActionDispatch::Response::NO_CONTENT_CODES.include?(response.code)
raise ConnectionError, "Response is not valid JSON" unless response.parsed_response.is_a?(Hash)
return if response.code >= 200 && response.code < 300
diff --git a/lib/feature.rb b/lib/feature.rb
index e59cd70f822..749c861d740 100644
--- a/lib/feature.rb
+++ b/lib/feature.rb
@@ -111,11 +111,11 @@ class Feature
end
def gate_specified?
- %i(user project feature_group).any? { |key| params.key?(key) }
+ %i(user project group feature_group).any? { |key| params.key?(key) }
end
def targets
- [feature_group, user, project].compact
+ [feature_group, user, project, group].compact
end
private
@@ -139,5 +139,11 @@ class Feature
Project.find_by_full_path(params[:project])
end
+
+ def group
+ return unless params.key?(:group)
+
+ Group.find_by_full_path(params[:group])
+ end
end
end
diff --git a/lib/gitlab/chat.rb b/lib/gitlab/chat.rb
new file mode 100644
index 00000000000..23d4fb36b66
--- /dev/null
+++ b/lib/gitlab/chat.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Chat
+ # Returns `true` if Chatops is available for the current instance.
+ def self.available?
+ ::Feature.enabled?(:chatops, default_enabled: true)
+ end
+ end
+end
diff --git a/lib/gitlab/chat/command.rb b/lib/gitlab/chat/command.rb
new file mode 100644
index 00000000000..49b7dcf4bbe
--- /dev/null
+++ b/lib/gitlab/chat/command.rb
@@ -0,0 +1,94 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Chat
+ # Class for scheduling chat pipelines.
+ #
+ # A Command takes care of creating a `Ci::Pipeline` with all the data
+ # necessary to execute a chat command. This includes data such as the chat
+ # data (e.g. the response URL) and any environment variables that should be
+ # exposed to the chat command.
+ class Command
+ include Utils::StrongMemoize
+
+ attr_reader :project, :chat_name, :name, :arguments, :response_url,
+ :channel
+
+ # project - The Project to schedule the command for.
+ # chat_name - The ChatName belonging to the user that scheduled the
+ # command.
+ # name - The name of the chat command to run.
+ # arguments - The arguments (as a String) to pass to the command.
+ # channel - The channel the message was sent from.
+ # response_url - The URL to send the response back to.
+ def initialize(project:, chat_name:, name:, arguments:, channel:, response_url:)
+ @project = project
+ @chat_name = chat_name
+ @name = name
+ @arguments = arguments
+ @channel = channel
+ @response_url = response_url
+ end
+
+ # Tries to create a new pipeline.
+ #
+ # This method will return a pipeline that _may_ be persisted, or `nil` if
+ # the pipeline could not be created.
+ def try_create_pipeline
+ return unless valid?
+
+ create_pipeline
+ end
+
+ def create_pipeline
+ service = ::Ci::CreatePipelineService.new(
+ project,
+ chat_name.user,
+ ref: branch,
+ sha: commit,
+ chat_data: {
+ chat_name_id: chat_name.id,
+ command: name,
+ arguments: arguments,
+ response_url: response_url
+ }
+ )
+
+ service.execute(:chat) do |pipeline|
+ build_environment_variables(pipeline)
+ build_chat_data(pipeline)
+ end
+ end
+
+ # pipeline - The `Ci::Pipeline` to create the environment variables for.
+ def build_environment_variables(pipeline)
+ pipeline.variables.build(
+ [{ key: 'CHAT_INPUT', value: arguments },
+ { key: 'CHAT_CHANNEL', value: channel }]
+ )
+ end
+
+ # pipeline - The `Ci::Pipeline` to create the chat data for.
+ def build_chat_data(pipeline)
+ pipeline.build_chat_data(
+ chat_name_id: chat_name.id,
+ response_url: response_url
+ )
+ end
+
+ def valid?
+ branch && commit
+ end
+
+ def branch
+ strong_memoize(:branch) { project.default_branch }
+ end
+
+ def commit
+ strong_memoize(:commit) do
+ project.commit(branch)&.id if branch
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/chat/output.rb b/lib/gitlab/chat/output.rb
new file mode 100644
index 00000000000..411b1555a7d
--- /dev/null
+++ b/lib/gitlab/chat/output.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Chat
+ # Class for gathering and formatting the output of a `Ci::Build`.
+ class Output
+ attr_reader :build
+
+ MissingBuildSectionError = Class.new(StandardError)
+
+ # The primary trace section to look for.
+ PRIMARY_SECTION = 'chat_reply'
+
+ # The backup trace section in case the primary one could not be found.
+ FALLBACK_SECTION = 'build_script'
+
+ # build - The `Ci::Build` to obtain the output from.
+ def initialize(build)
+ @build = build
+ end
+
+ # Returns a `String` containing the output of the build.
+ #
+ # The output _does not_ include the command that was executed.
+ def to_s
+ offset, length = read_offset_and_length
+
+ trace.read do |stream|
+ stream.seek(offset)
+
+ output = stream
+ .stream
+ .read(length)
+ .force_encoding(Encoding.default_external)
+
+ without_executed_command_line(output)
+ end
+ end
+
+ # Returns the offset to seek to and the number of bytes to read relative
+ # to the offset.
+ def read_offset_and_length
+ section = find_build_trace_section(PRIMARY_SECTION) ||
+ find_build_trace_section(FALLBACK_SECTION)
+
+ unless section
+ raise(
+ MissingBuildSectionError,
+ "The build_script trace section could not be found for build #{build.id}"
+ )
+ end
+
+ length = section[:byte_end] - section[:byte_start]
+
+ [section[:byte_start], length]
+ end
+
+ # Removes the line containing the executed command from the build output.
+ #
+ # output - A `String` containing the output of a trace section.
+ def without_executed_command_line(output)
+ # If `output.split("\n")` produces an empty Array then the slicing that
+ # follows it will produce a nil. For example:
+ #
+ # "\n".split("\n") # => []
+ # "\n".split("\n")[1..-1] # => nil
+ #
+ # To work around this we only "join" if we're given an Array.
+ if (converted = output.split("\n")[1..-1])
+ converted.join("\n")
+ else
+ ''
+ end
+ end
+
+ # Returns the trace section for the given name, or `nil` if the section
+ # could not be found.
+ #
+ # name - The name of the trace section to find.
+ def find_build_trace_section(name)
+ trace_sections.find { |s| s[:name] == name }
+ end
+
+ def trace_sections
+ @trace_sections ||= trace.extract_sections
+ end
+
+ def trace
+ @trace ||= build.trace
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/chat/responder.rb b/lib/gitlab/chat/responder.rb
new file mode 100644
index 00000000000..6267fbc20e2
--- /dev/null
+++ b/lib/gitlab/chat/responder.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Chat
+ module Responder
+ # Returns an instance of the responder to use for generating chat
+ # responses.
+ #
+ # This method will return `nil` if no formatter is available for the given
+ # build.
+ #
+ # build - A `Ci::Build` that executed a chat command.
+ def self.responder_for(build)
+ service = build.pipeline.chat_data&.chat_name&.service
+
+ if (responder = service.try(:chat_responder))
+ responder.new(build)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/chat/responder/base.rb b/lib/gitlab/chat/responder/base.rb
new file mode 100644
index 00000000000..f1ad0e36793
--- /dev/null
+++ b/lib/gitlab/chat/responder/base.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Chat
+ module Responder
+ class Base
+ attr_reader :build
+
+ # build - The `Ci::Build` that was executed.
+ def initialize(build)
+ @build = build
+ end
+
+ def pipeline
+ build.pipeline
+ end
+
+ def project
+ pipeline.project
+ end
+
+ def success(*)
+ raise NotImplementedError, 'You must implement #success(output)'
+ end
+
+ def failure
+ raise NotImplementedError, 'You must implement #failure'
+ end
+
+ def send_response(output)
+ raise NotImplementedError, 'You must implement #send_response(output)'
+ end
+
+ def scheduled_output
+ raise NotImplementedError, 'You must implement #scheduled_output'
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/chat/responder/slack.rb b/lib/gitlab/chat/responder/slack.rb
new file mode 100644
index 00000000000..0cf02c92a67
--- /dev/null
+++ b/lib/gitlab/chat/responder/slack.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Chat
+ module Responder
+ class Slack < Responder::Base
+ SUCCESS_COLOR = '#B3ED8E'
+ FAILURE_COLOR = '#FF5640'
+ RESPONSE_TYPE = :in_channel
+
+ # Slack breaks messages apart if they're around 4 KB in size. We use a
+ # slightly smaller limit here to account for user mentions.
+ MESSAGE_SIZE_LIMIT = 3.5.kilobytes
+
+ # Sends a response back to Slack
+ #
+ # output - The output to send back to Slack, as a Hash.
+ def send_response(output)
+ Gitlab::HTTP.post(
+ pipeline.chat_data.response_url,
+ {
+ headers: { Accept: 'application/json' },
+ body: output.to_json
+ }
+ )
+ end
+
+ # Sends the output for a build that completed successfully.
+ #
+ # output - The output produced by the chat command.
+ def success(output)
+ return if output.empty?
+
+ send_response(
+ text: message_text(limit_output(output)),
+ response_type: RESPONSE_TYPE
+ )
+ end
+
+ # Sends the output for a build that failed.
+ def failure
+ send_response(
+ text: message_text("<#{build_url}|Sorry, the build failed!>"),
+ response_type: RESPONSE_TYPE
+ )
+ end
+
+ # Returns the output to send back after a command has been scheduled.
+ def scheduled_output
+ # We return an empty message so that Slack still shows the input
+ # command, without polluting the channel with standard "The job has
+ # been scheduled" (or similar) responses.
+ { text: '' }
+ end
+
+ private
+
+ def limit_output(output)
+ if output.bytesize <= MESSAGE_SIZE_LIMIT
+ output
+ else
+ "<#{build_url}|The output is too large to be sent back directly!>"
+ end
+ end
+
+ def mention_user
+ "<@#{pipeline.chat_data.chat_name.chat_id}>"
+ end
+
+ def message_text(output)
+ "#{mention_user}: #{output}"
+ end
+
+ def build_url
+ ::Gitlab::Routing.url_helpers.project_build_url(project, build)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/build/policy/changes.rb b/lib/gitlab/ci/build/policy/changes.rb
index 1663c875426..9c705a1cd3e 100644
--- a/lib/gitlab/ci/build/policy/changes.rb
+++ b/lib/gitlab/ci/build/policy/changes.rb
@@ -10,7 +10,7 @@ module Gitlab
end
def satisfied_by?(pipeline, seed)
- return true unless pipeline.branch_updated?
+ return true if pipeline.modified_paths.nil?
pipeline.modified_paths.any? do |path|
@globs.any? do |glob|
diff --git a/lib/gitlab/ci/build/policy/refs.rb b/lib/gitlab/ci/build/policy/refs.rb
index 0e9bb5c94bb..df5f5ffc253 100644
--- a/lib/gitlab/ci/build/policy/refs.rb
+++ b/lib/gitlab/ci/build/policy/refs.rb
@@ -29,8 +29,8 @@ module Gitlab
def matches_pattern?(pattern, pipeline)
return true if pipeline.tag? && pattern == 'tags'
return true if pipeline.branch? && pattern == 'branches'
- return true if pipeline.source == pattern
- return true if pipeline.source&.pluralize == pattern
+ return true if sanitized_source_name(pipeline) == pattern
+ return true if sanitized_source_name(pipeline)&.pluralize == pattern
# patterns can be matched only when branch or tag is used
# the pattern matching does not work for merge requests pipelines
@@ -42,6 +42,10 @@ module Gitlab
end
end
end
+
+ def sanitized_source_name(pipeline)
+ @sanitized_source_name ||= pipeline&.source&.delete_suffix('_event')
+ end
end
end
end
diff --git a/lib/gitlab/ci/config/entry/global.rb b/lib/gitlab/ci/config/entry/global.rb
index 09ecb5fdb99..2b5a59c078e 100644
--- a/lib/gitlab/ci/config/entry/global.rb
+++ b/lib/gitlab/ci/config/entry/global.rb
@@ -17,6 +17,9 @@ module Gitlab
entry :image, Entry::Image,
description: 'Docker image that will be used to execute jobs.'
+ entry :include, Entry::Includes,
+ description: 'List of external YAML files to include.'
+
entry :services, Entry::Services,
description: 'Docker images that will be linked to the container.'
diff --git a/lib/gitlab/ci/config/entry/include.rb b/lib/gitlab/ci/config/entry/include.rb
new file mode 100644
index 00000000000..f2f3dd84eda
--- /dev/null
+++ b/lib/gitlab/ci/config/entry/include.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module Entry
+ ##
+ # Entry that represents a single include.
+ #
+ class Include < ::Gitlab::Config::Entry::Node
+ include ::Gitlab::Config::Entry::Validatable
+
+ ALLOWED_KEYS = %i[local file remote template].freeze
+
+ validations do
+ validates :config, hash_or_string: true
+ validates :config, allowed_keys: ALLOWED_KEYS
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/entry/includes.rb b/lib/gitlab/ci/config/entry/includes.rb
new file mode 100644
index 00000000000..82b2b1ccf4b
--- /dev/null
+++ b/lib/gitlab/ci/config/entry/includes.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module Entry
+ ##
+ # Entry that represents a list of include.
+ #
+ class Includes < ::Gitlab::Config::Entry::Node
+ include ::Gitlab::Config::Entry::Validatable
+
+ validations do
+ validates :config, type: Array
+ end
+
+ def self.aspects
+ super.append -> do
+ @config = Array.wrap(@config)
+
+ @config.each_with_index do |config, i|
+ @entries[i] = ::Gitlab::Config::Entry::Factory.new(Entry::Include)
+ .value(config || {})
+ .create!
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/pipeline/chain/build.rb b/lib/gitlab/ci/pipeline/chain/build.rb
index 41632211374..164a4634d84 100644
--- a/lib/gitlab/ci/pipeline/chain/build.rb
+++ b/lib/gitlab/ci/pipeline/chain/build.rb
@@ -12,6 +12,8 @@ module Gitlab
ref: @command.ref,
sha: @command.sha,
before_sha: @command.before_sha,
+ source_sha: @command.source_sha,
+ target_sha: @command.target_sha,
tag: @command.tag_exists?,
trigger_requests: Array(@command.trigger_request),
user: @command.current_user,
diff --git a/lib/gitlab/ci/pipeline/chain/command.rb b/lib/gitlab/ci/pipeline/chain/command.rb
index e62d547d862..7b77e86feae 100644
--- a/lib/gitlab/ci/pipeline/chain/command.rb
+++ b/lib/gitlab/ci/pipeline/chain/command.rb
@@ -7,10 +7,11 @@ module Gitlab
module Chain
Command = Struct.new(
:source, :project, :current_user,
- :origin_ref, :checkout_sha, :after_sha, :before_sha,
+ :origin_ref, :checkout_sha, :after_sha, :before_sha, :source_sha, :target_sha,
:trigger_request, :schedule, :merge_request,
:ignore_skip_ci, :save_incompleted,
- :seeds_block, :variables_attributes, :push_options
+ :seeds_block, :variables_attributes, :push_options,
+ :chat_data
) do
include Gitlab::Utils::StrongMemoize
diff --git a/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs.rb b/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs.rb
index 0f687a4ce9b..1e09b417311 100644
--- a/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs.rb
+++ b/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs.rb
@@ -6,7 +6,13 @@ module Gitlab
module Chain
class RemoveUnwantedChatJobs < Chain::Base
def perform!
- # to be overriden in EE
+ return unless pipeline.config_processor && pipeline.chat?
+
+ # When scheduling a chat pipeline we only want to run the build
+ # that matches the chat command.
+ pipeline.config_processor.jobs.select! do |name, _|
+ name.to_s == command.chat_data[:command].to_s
+ end
end
def break?
diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
index e369d26f22f..c0d4d4400b3 100644
--- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
@@ -48,12 +48,15 @@ variables:
POSTGRES_PASSWORD: testing-password
POSTGRES_ENABLED: "true"
POSTGRES_DB: $CI_ENVIRONMENT_SLUG
+ POSTGRES_VERSION: 9.6.2
- KUBERNETES_VERSION: 1.11.6
- HELM_VERSION: 2.12.2
+ KUBERNETES_VERSION: 1.11.7
+ HELM_VERSION: 2.12.3
DOCKER_DRIVER: overlay2
+ ROLLOUT_RESOURCE_TYPE: deployment
+
stages:
- build
- test
@@ -71,12 +74,11 @@ stages:
build:
stage: build
- image: docker:stable-git
+ image: "registry.gitlab.com/gitlab-org/cluster-integration/auto-build-image/master:stable"
services:
- - docker:stable-dind
+ - docker:stable-dind
script:
- - setup_docker
- - build
+ - /build/build.sh
only:
- branches
@@ -488,7 +490,6 @@ rollout 100%:
export DATABASE_URL=${DATABASE_URL-$auto_database_url}
export CI_APPLICATION_REPOSITORY=$CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG
export CI_APPLICATION_TAG=$CI_COMMIT_SHA
- export CI_CONTAINER_NAME=ci_job_build_${CI_JOB_ID}
export TILLER_NAMESPACE=$KUBE_NAMESPACE
# Extract "MAJOR.MINOR" from CI_SERVER_VERSION and generate "MAJOR-MINOR-stable" for Security Products
export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
@@ -698,6 +699,7 @@ rollout 100%:
--set postgresql.postgresUser="$POSTGRES_USER" \
--set postgresql.postgresPassword="$POSTGRES_PASSWORD" \
--set postgresql.postgresDatabase="$POSTGRES_DB" \
+ --set postgresql.imageTag="$POSTGRES_VERSION" \
--set application.initializeCommand="$DB_INITIALIZE" \
--namespace="$KUBE_NAMESPACE" \
"$name" \
@@ -740,7 +742,7 @@ rollout 100%:
chart/
fi
- kubectl rollout status -n "$KUBE_NAMESPACE" -w "deployment/$name"
+ kubectl rollout status -n "$KUBE_NAMESPACE" -w "$ROLLOUT_RESOURCE_TYPE/$name"
}
function scale() {
@@ -826,7 +828,7 @@ rollout 100%:
# Function to ensure backwards compatibility with AUTO_DEVOPS_DOMAIN
function ensure_kube_ingress_base_domain() {
- if [ -z ${KUBE_INGRESS_BASE_DOMAIN+x} ]; then
+ if [ -z ${KUBE_INGRESS_BASE_DOMAIN+x} ] && [ -n "$AUTO_DEVOPS_DOMAIN" ] ; then
export KUBE_INGRESS_BASE_DOMAIN=$AUTO_DEVOPS_DOMAIN
fi
}
@@ -847,50 +849,6 @@ rollout 100%:
fi
}
- function build() {
- registry_login
-
- if [[ -f Dockerfile ]]; then
- echo "Building Dockerfile-based application..."
- docker build \
- --build-arg HTTP_PROXY="$HTTP_PROXY" \
- --build-arg http_proxy="$http_proxy" \
- --build-arg HTTPS_PROXY="$HTTPS_PROXY" \
- --build-arg https_proxy="$https_proxy" \
- --build-arg FTP_PROXY="$FTP_PROXY" \
- --build-arg ftp_proxy="$ftp_proxy" \
- --build-arg NO_PROXY="$NO_PROXY" \
- --build-arg no_proxy="$no_proxy" \
- -t "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG" .
- else
- echo "Building Heroku-based application using gliderlabs/herokuish docker image..."
- docker run -i \
- -e BUILDPACK_URL \
- -e HTTP_PROXY \
- -e http_proxy \
- -e HTTPS_PROXY \
- -e https_proxy \
- -e FTP_PROXY \
- -e ftp_proxy \
- -e NO_PROXY \
- -e no_proxy \
- --name="$CI_CONTAINER_NAME" -v "$(pwd):/tmp/app:ro" gliderlabs/herokuish /bin/herokuish buildpack build
- docker commit "$CI_CONTAINER_NAME" "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG"
- docker rm "$CI_CONTAINER_NAME" >/dev/null
- echo ""
-
- echo "Configuring $CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG docker image..."
- docker create --expose 5000 --env PORT=5000 --name="$CI_CONTAINER_NAME" "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG" /bin/herokuish procfile start web
- docker commit "$CI_CONTAINER_NAME" "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG"
- docker rm "$CI_CONTAINER_NAME" >/dev/null
- echo ""
- fi
-
- echo "Pushing to GitLab Container Registry..."
- docker push "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG"
- echo ""
- }
-
function initialize_tiller() {
echo "Checking Tiller..."
diff --git a/lib/gitlab/ci/templates/iOS-Fastlane.gitlab-ci.yml b/lib/gitlab/ci/templates/iOS-Fastlane.gitlab-ci.yml
new file mode 100644
index 00000000000..245e6bec60a
--- /dev/null
+++ b/lib/gitlab/ci/templates/iOS-Fastlane.gitlab-ci.yml
@@ -0,0 +1,28 @@
+# This is a very simple template that mainly relies on FastLane to build and distribute your app.
+# Read more about how to use this template on the blog post https://about.gitlab.com/2019/03/06/ios-publishing-with-gitlab-and-fastlane/
+# You will also need fastlane and signing configuration for this to work, along with a MacOS runner.
+# These details are provided in the blog post.
+
+# Note that when you're using the shell executor for MacOS builds, the
+# build and tests run as the identity of the runner logged in user, directly on
+# the build host. This is less secure than using container executors, so please
+# take a look at our security implications documentation at
+# https://docs.gitlab.com/runner/security/#usage-of-shell-executor for additional
+# detail on what to keep in mind in this scenario.
+
+stages:
+ - build
+
+variables:
+ LC_ALL: "en_US.UTF-8"
+ LANG: "en_US.UTF-8"
+ GIT_STRATEGY: clone
+
+build:
+ stage: build
+ script:
+ - bundle install
+ - bundle exec fastlane build
+ artifacts:
+ paths:
+ - ./*.ipa
diff --git a/lib/gitlab/ci/variables/collection/item.rb b/lib/gitlab/ci/variables/collection/item.rb
index e3e4e62cc02..833aa75adb5 100644
--- a/lib/gitlab/ci/variables/collection/item.rb
+++ b/lib/gitlab/ci/variables/collection/item.rb
@@ -5,12 +5,12 @@ module Gitlab
module Variables
class Collection
class Item
- def initialize(key:, value:, public: true, file: false)
+ def initialize(key:, value:, public: true, file: false, masked: false)
raise ArgumentError, "`#{key}` must be of type String or nil value, while it was: #{value.class}" unless
value.is_a?(String) || value.nil?
@variable = {
- key: key, value: value, public: public, file: file
+ key: key, value: value, public: public, file: file, masked: masked
}
end
@@ -27,9 +27,13 @@ module Gitlab
# don't expose `file` attribute at all (stems from what the runner
# expects).
#
+ # If the `variable_masking` feature is enabled we expose the `masked`
+ # attribute, otherwise it's not exposed.
+ #
def to_runner_variable
@variable.reject do |hash_key, hash_value|
- hash_key == :file && hash_value == false
+ (hash_key == :file && hash_value == false) ||
+ (hash_key == :masked && !Feature.enabled?(:variable_masking))
end
end
diff --git a/lib/gitlab/danger/helper.rb b/lib/gitlab/danger/helper.rb
new file mode 100644
index 00000000000..d2b7ca015d4
--- /dev/null
+++ b/lib/gitlab/danger/helper.rb
@@ -0,0 +1,136 @@
+# frozen_string_literal: true
+require 'net/http'
+require 'json'
+
+require_relative 'teammate'
+
+module Gitlab
+ module Danger
+ module Helper
+ ROULETTE_DATA_URL = URI.parse('https://about.gitlab.com/roulette.json').freeze
+
+ # Returns a list of all files that have been added, modified or renamed.
+ # `git.modified_files` might contain paths that already have been renamed,
+ # so we need to remove them from the list.
+ #
+ # Considering these changes:
+ #
+ # - A new_file.rb
+ # - D deleted_file.rb
+ # - M modified_file.rb
+ # - R renamed_file_before.rb -> renamed_file_after.rb
+ #
+ # it will return
+ # ```
+ # [ 'new_file.rb', 'modified_file.rb', 'renamed_file_after.rb' ]
+ # ```
+ #
+ # @return [Array<String>]
+ def all_changed_files
+ Set.new
+ .merge(git.added_files.to_a)
+ .merge(git.modified_files.to_a)
+ .merge(git.renamed_files.map { |x| x[:after] })
+ .subtract(git.renamed_files.map { |x| x[:before] })
+ .to_a
+ .sort
+ end
+
+ def ee?
+ ENV['CI_PROJECT_NAME'] == 'gitlab-ee' || File.exist?('../../CHANGELOG-EE.md')
+ end
+
+ def project_name
+ ee? ? 'gitlab-ee' : 'gitlab-ce'
+ end
+
+ # Looks up the current list of GitLab team members and parses it into a
+ # useful form
+ #
+ # @return [Array<Teammate>]
+ def team
+ @team ||=
+ begin
+ rsp = Net::HTTP.get_response(ROULETTE_DATA_URL)
+ raise "Failed to read #{ROULETTE_DATA_URL}: #{rsp.code} #{rsp.message}" unless
+ rsp.is_a?(Net::HTTPSuccess)
+
+ data = JSON.parse(rsp.body)
+ data.map { |hash| ::Gitlab::Danger::Teammate.new(hash) }
+ rescue JSON::ParserError
+ raise "Failed to parse JSON response from #{ROULETTE_DATA_URL}"
+ end
+ end
+
+ # Like +team+, but only returns teammates in the current project, based on
+ # project_name.
+ #
+ # @return [Array<Teammate>]
+ def project_team
+ team.select { |member| member.in_project?(project_name) }
+ end
+
+ # @return [Hash<String,Array<String>>]
+ def changes_by_category
+ all_changed_files.each_with_object(Hash.new { |h, k| h[k] = [] }) do |file, hash|
+ hash[category_for_file(file)] << file
+ end
+ end
+
+ # Determines the category a file is in, e.g., `:frontend` or `:backend`
+ # @return[Symbol]
+ def category_for_file(file)
+ _, category = CATEGORIES.find { |regexp, _| regexp.match?(file) }
+
+ category || :unknown
+ end
+
+ # Returns the GFM for a category label, making its best guess if it's not
+ # a category we know about.
+ #
+ # @return[String]
+ def label_for_category(category)
+ CATEGORY_LABELS.fetch(category, "~#{category}")
+ end
+
+ CATEGORY_LABELS = {
+ docs: "~Documentation",
+ none: "",
+ qa: "~QA"
+ }.freeze
+
+ # rubocop:disable Style/RegexpLiteral
+ CATEGORIES = {
+ %r{\Adoc/} => :docs,
+ %r{\A(CONTRIBUTING|LICENSE|MAINTENANCE|PHILOSOPHY|PROCESS|README)(\.md)?\z} => :docs,
+
+ %r{\A(ee/)?app/(assets|views)/} => :frontend,
+ %r{\A(ee/)?public/} => :frontend,
+ %r{\A(ee/)?spec/(javascripts|frontend)/} => :frontend,
+ %r{\A(ee/)?vendor/assets/} => :frontend,
+ %r{\A(jest\.config\.js|package\.json|yarn\.lock)\z} => :frontend,
+
+ %r{\A(ee/)?app/(?!assets|views)[^/]+} => :backend,
+ %r{\A(ee/)?(bin|config|danger|generator_templates|lib|rubocop|scripts)/} => :backend,
+ %r{\A(ee/)?spec/(?!javascripts|frontend)[^/]+} => :backend,
+ %r{\A(ee/)?vendor/(?!assets)[^/]+} => :backend,
+ %r{\A(ee/)?vendor/(languages\.yml|licenses\.csv)\z} => :backend,
+ %r{\A(Dangerfile|Gemfile|Gemfile.lock|Procfile|Rakefile|\.gitlab-ci\.yml)\z} => :backend,
+ %r{\A[A-Z_]+_VERSION\z} => :backend,
+
+ %r{\A(ee/)?db/} => :database,
+ %r{\A(ee/)?qa/} => :qa,
+
+ # Files that don't fit into any category are marked with :none
+ %r{\A(ee/)?changelogs/} => :none,
+ %r{\Alocale/gitlab\.pot\z} => :none,
+
+ # Fallbacks in case the above patterns miss anything
+ %r{\.rb\z} => :backend,
+ %r{\.(md|txt)\z} => :docs,
+ %r{\.js\z} => :frontend
+ }.freeze
+ # rubocop:enable Style/RegexpLiteral
+ end
+ end
+end
diff --git a/lib/gitlab/danger/teammate.rb b/lib/gitlab/danger/teammate.rb
new file mode 100644
index 00000000000..4b822aa86c5
--- /dev/null
+++ b/lib/gitlab/danger/teammate.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Danger
+ class Teammate
+ attr_reader :name, :username, :projects
+
+ def initialize(options = {})
+ @name = options['name']
+ @username = options['username']
+ @projects = options['projects']
+ end
+
+ def markdown_name
+ "[#{name}](https://gitlab.com/#{username}) (`@#{username}`)"
+ end
+
+ def in_project?(name)
+ projects&.has_key?(name)
+ end
+
+ # Traintainers also count as reviewers
+ def reviewer?(project, category)
+ capabilities(project) == "reviewer #{category}" || traintainer?(project, category)
+ end
+
+ def traintainer?(project, category)
+ capabilities(project) == "trainee_maintainer #{category}"
+ end
+
+ def maintainer?(project, category)
+ capabilities(project) == "maintainer #{category}"
+ end
+
+ private
+
+ def capabilities(project)
+ projects.fetch(project, '')
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb
index e410d5a8333..c9d89d56884 100644
--- a/lib/gitlab/diff/file.rb
+++ b/lib/gitlab/diff/file.rb
@@ -293,6 +293,10 @@ module Gitlab
end
end
+ def viewer
+ rich_viewer || simple_viewer
+ end
+
def simple_viewer
@simple_viewer ||= simple_viewer_class.new(self)
end
diff --git a/lib/gitlab/etag_caching/router.rb b/lib/gitlab/etag_caching/router.rb
index 08e30214b46..0891f79198d 100644
--- a/lib/gitlab/etag_caching/router.rb
+++ b/lib/gitlab/etag_caching/router.rb
@@ -52,6 +52,14 @@ module Gitlab
Gitlab::EtagCaching::Router::Route.new(
%r(^(?!.*(#{RESERVED_WORDS_REGEX})).*/environments\.json\z),
'environments'
+ ),
+ Gitlab::EtagCaching::Router::Route.new(
+ %r(^(?!.*(#{RESERVED_WORDS_REGEX})).*/import/github/realtime_changes\.json\z),
+ 'realtime_changes_import_github'
+ ),
+ Gitlab::EtagCaching::Router::Route.new(
+ %r(^(?!.*(#{RESERVED_WORDS_REGEX})).*/import/gitea/realtime_changes\.json\z),
+ 'realtime_changes_import_gitea'
)
].freeze
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 593a3676519..aea132a3dd9 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -556,6 +556,12 @@ module Gitlab
tags.find { |tag| tag.name == name }
end
+ def merge_to_ref(user, source_sha, branch, target_ref, message)
+ wrapped_gitaly_errors do
+ gitaly_operation_client.user_merge_to_ref(user, source_sha, branch, target_ref, message)
+ end
+ end
+
def merge(user, source_sha, target_branch, message, &block)
wrapped_gitaly_errors do
gitaly_operation_client.user_merge_branch(user, source_sha, target_branch, message, &block)
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index 0ab53f8f706..869b835b61e 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -28,7 +28,7 @@ module Gitlab
PEM_REGEX = /\-+BEGIN CERTIFICATE\-+.+?\-+END CERTIFICATE\-+/m
SERVER_VERSION_FILE = 'GITALY_SERVER_VERSION'
- MAXIMUM_GITALY_CALLS = 35
+ MAXIMUM_GITALY_CALLS = 30
CLIENT_NAME = (Sidekiq.server? ? 'gitlab-sidekiq' : 'gitlab-web').freeze
MUTEX = Mutex.new
@@ -164,8 +164,6 @@ module Gitlab
kwargs = yield(kwargs) if block_given?
stub(service, storage).__send__(rpc, request, kwargs) # rubocop:disable GitlabSecurity/PublicSend
- rescue GRPC::Unavailable => ex
- handle_grpc_unavailable!(ex)
ensure
duration = Gitlab::Metrics::System.monotonic_time - start
@@ -178,27 +176,6 @@ module Gitlab
add_call_details(feature: "#{service}##{rpc}", duration: duration, request: request_hash, rpc: rpc)
end
- def self.handle_grpc_unavailable!(ex)
- status = ex.to_status
- raise ex unless status.details == 'Endpoint read failed'
-
- # There is a bug in grpc 1.8.x that causes a client process to get stuck
- # always raising '14:Endpoint read failed'. The only thing that we can
- # do to recover is to restart the process.
- #
- # See https://gitlab.com/gitlab-org/gitaly/issues/1029
-
- if Sidekiq.server?
- raise Gitlab::SidekiqMiddleware::Shutdown::WantShutdown.new(ex.to_s)
- else
- # SIGQUIT requests a Unicorn worker to shut down gracefully after the current request.
- Process.kill('QUIT', Process.pid)
- end
-
- raise ex
- end
- private_class_method :handle_grpc_unavailable!
-
def self.current_transaction_labels
Gitlab::Metrics::Transaction.current&.labels || {}
end
diff --git a/lib/gitlab/gitaly_client/operation_service.rb b/lib/gitlab/gitaly_client/operation_service.rb
index 22d2d149e65..d172c798da2 100644
--- a/lib/gitlab/gitaly_client/operation_service.rb
+++ b/lib/gitlab/gitaly_client/operation_service.rb
@@ -100,6 +100,25 @@ module Gitlab
end
end
+ def user_merge_to_ref(user, source_sha, branch, target_ref, message)
+ request = Gitaly::UserMergeToRefRequest.new(
+ repository: @gitaly_repo,
+ source_sha: source_sha,
+ branch: encode_binary(branch),
+ target_ref: encode_binary(target_ref),
+ user: Gitlab::Git::User.from_gitlab(user).to_gitaly,
+ message: message
+ )
+
+ response = GitalyClient.call(@repository.storage, :operation_service, :user_merge_to_ref, request)
+
+ if pre_receive_error = response.pre_receive_error.presence
+ raise Gitlab::Git::PreReceiveError, pre_receive_error
+ end
+
+ response.commit_id
+ end
+
def user_merge_branch(user, source_sha, target_branch, message)
request_enum = QueueEnumerator.new
response_enum = GitalyClient.call(
diff --git a/lib/gitlab/graphql/authorize.rb b/lib/gitlab/graphql/authorize.rb
index 5e48bf9043d..f62813db82c 100644
--- a/lib/gitlab/graphql/authorize.rb
+++ b/lib/gitlab/graphql/authorize.rb
@@ -10,21 +10,6 @@ module Gitlab
def self.use(schema_definition)
schema_definition.instrument(:field, Instrumentation.new)
end
-
- def required_permissions
- # If the `#authorize` call is used on multiple classes, we add the
- # permissions specified on a subclass, to the ones that were specified
- # on it's superclass.
- @required_permissions ||= if self.respond_to?(:superclass) && superclass.respond_to?(:required_permissions)
- superclass.required_permissions.dup
- else
- []
- end
- end
-
- def authorize(*permissions)
- required_permissions.concat(permissions)
- end
end
end
end
diff --git a/lib/gitlab/graphql/authorize/authorize_resource.rb b/lib/gitlab/graphql/authorize/authorize_resource.rb
index a56c4f6368d..b367a97105c 100644
--- a/lib/gitlab/graphql/authorize/authorize_resource.rb
+++ b/lib/gitlab/graphql/authorize/authorize_resource.rb
@@ -6,8 +6,21 @@ module Gitlab
module AuthorizeResource
extend ActiveSupport::Concern
- included do
- extend Gitlab::Graphql::Authorize
+ class_methods do
+ def required_permissions
+ # If the `#authorize` call is used on multiple classes, we add the
+ # permissions specified on a subclass, to the ones that were specified
+ # on it's superclass.
+ @required_permissions ||= if self.respond_to?(:superclass) && superclass.respond_to?(:required_permissions)
+ superclass.required_permissions.dup
+ else
+ []
+ end
+ end
+
+ def authorize(*permissions)
+ required_permissions.concat(permissions)
+ end
end
def find_object(*args)
diff --git a/lib/gitlab/graphql/authorize/instrumentation.rb b/lib/gitlab/graphql/authorize/instrumentation.rb
index d638d2b43ee..593da8471dd 100644
--- a/lib/gitlab/graphql/authorize/instrumentation.rb
+++ b/lib/gitlab/graphql/authorize/instrumentation.rb
@@ -6,19 +6,15 @@ module Gitlab
class Instrumentation
# Replace the resolver for the field with one that will only return the
# resolved object if the permissions check is successful.
- #
- # Collections are not supported. Apply permissions checks for those at the
- # database level instead, to avoid loading superfluous data from the DB
def instrument(_type, field)
- field_definition = field.metadata[:type_class]
- return field unless field_definition.respond_to?(:required_permissions)
- return field if field_definition.required_permissions.empty?
+ required_permissions = Array.wrap(field.metadata[:authorize])
+ return field if required_permissions.empty?
old_resolver = field.resolve_proc
new_resolver = -> (obj, args, ctx) do
resolved_obj = old_resolver.call(obj, args, ctx)
- checker = build_checker(ctx[:current_user], field_definition.required_permissions)
+ checker = build_checker(ctx[:current_user], required_permissions)
if resolved_obj.respond_to?(:then)
resolved_obj.then(&checker)
@@ -35,10 +31,22 @@ module Gitlab
private
def build_checker(current_user, abilities)
- proc do |obj|
+ lambda do |value|
# Load the elements if they weren't loaded by BatchLoader yet
- obj = obj.sync if obj.respond_to?(:sync)
- obj if abilities.all? { |ability| Ability.allowed?(current_user, ability, obj) }
+ value = value.sync if value.respond_to?(:sync)
+
+ check = lambda do |object|
+ abilities.all? do |ability|
+ Ability.allowed?(current_user, ability, object)
+ end
+ end
+
+ case value
+ when Array
+ value.select(&check)
+ else
+ value if check.call(value)
+ end
end
end
end
diff --git a/lib/gitlab/hashed_storage/migrator.rb b/lib/gitlab/hashed_storage/migrator.rb
index bf463077dcc..7046b4e2a43 100644
--- a/lib/gitlab/hashed_storage/migrator.rb
+++ b/lib/gitlab/hashed_storage/migrator.rb
@@ -13,10 +13,18 @@ module Gitlab
#
# @param [Integer] start first project id for the range
# @param [Integer] finish last project id for the range
- def bulk_schedule(start:, finish:)
+ def bulk_schedule_migration(start:, finish:)
::HashedStorage::MigratorWorker.perform_async(start, finish)
end
+ # Schedule a range of projects to be bulk rolledback with #bulk_rollback asynchronously
+ #
+ # @param [Integer] start first project id for the range
+ # @param [Integer] finish last project id for the range
+ def bulk_schedule_rollback(start:, finish:)
+ ::HashedStorage::RollbackerWorker.perform_async(start, finish)
+ end
+
# Start migration of projects from specified range
#
# Flagging a project to be migrated is a synchronous action
@@ -34,6 +42,23 @@ module Gitlab
end
# rubocop: enable CodeReuse/ActiveRecord
+ # Start rollback of projects from specified range
+ #
+ # Flagging a project to be rolled back is a synchronous action
+ # but the rollback runs through async jobs
+ #
+ # @param [Integer] start first project id for the range
+ # @param [Integer] finish last project id for the range
+ # rubocop: disable CodeReuse/ActiveRecord
+ def bulk_rollback(start:, finish:)
+ projects = build_relation(start, finish)
+
+ projects.with_route.find_each(batch_size: BATCH_SIZE) do |project|
+ rollback(project)
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
# Flag a project to be migrated to Hashed Storage
#
# @param [Project] project that will be migrated
@@ -45,8 +70,15 @@ module Gitlab
Rails.logger.error("#{err.message} migrating storage of #{project.full_path} (ID=#{project.id}), trace - #{err.backtrace}")
end
+ # Flag a project to be rolled-back to Legacy Storage
+ #
+ # @param [Project] project that will be rolled-back
def rollback(project)
- # TODO: implement rollback strategy
+ Rails.logger.info "Starting storage rollback of #{project.full_path} (ID=#{project.id})..."
+
+ project.rollback_to_legacy_storage!
+ rescue => err
+ Rails.logger.error("#{err.message} rolling-back storage of #{project.full_path} (ID=#{project.id}), trace - #{err.backtrace}")
end
private
diff --git a/lib/gitlab/hashed_storage/rake_helper.rb b/lib/gitlab/hashed_storage/rake_helper.rb
index 38f552fab03..87a31a37e3f 100644
--- a/lib/gitlab/hashed_storage/rake_helper.rb
+++ b/lib/gitlab/hashed_storage/rake_helper.rb
@@ -24,7 +24,7 @@ module Gitlab
end
# rubocop: disable CodeReuse/ActiveRecord
- def self.project_id_batches(&block)
+ def self.project_id_batches_migration(&block)
Project.with_unmigrated_storage.in_batches(of: batch_size, start: range_from, finish: range_to) do |relation| # rubocop: disable Cop/InBatches
ids = relation.pluck(:id)
@@ -34,6 +34,16 @@ module Gitlab
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
+ def self.project_id_batches_rollback(&block)
+ Project.with_storage_feature(:repository).in_batches(of: batch_size, start: range_from, finish: range_to) do |relation| # rubocop: disable Cop/InBatches
+ ids = relation.pluck(:id)
+
+ yield ids.min, ids.max
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ # rubocop: disable CodeReuse/ActiveRecord
def self.legacy_attachments_relation
Upload.joins(<<~SQL).where('projects.storage_version < :version OR projects.storage_version IS NULL', version: Project::HASHED_STORAGE_FEATURES[:attachments])
JOIN projects
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index 099677a791c..fa54fc17d95 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -28,7 +28,6 @@ project_tree:
- notes:
:author
- releases:
- - :author
- :links
- project_members:
- :user
@@ -133,7 +132,6 @@ excluded_attributes:
- :external_diff
- :stored_externally
- :external_diff_store
- - :st_diffs
merge_request_diff_files:
- :diff
- :external_diff_offset
diff --git a/lib/gitlab/import_export/shared.rb b/lib/gitlab/import_export/shared.rb
index 947caaaefee..725c1101d70 100644
--- a/lib/gitlab/import_export/shared.rb
+++ b/lib/gitlab/import_export/shared.rb
@@ -61,7 +61,7 @@ module Gitlab
def log_base_data
{
importer: 'Import/Export',
- import_jid: @project&.import_state&.import_jid,
+ import_jid: @project&.import_state&.jid,
project_id: @project&.id,
project_path: @project&.full_path
}
diff --git a/lib/gitlab/json_cache.rb b/lib/gitlab/json_cache.rb
index 1adf83739ad..24daad638f4 100644
--- a/lib/gitlab/json_cache.rb
+++ b/lib/gitlab/json_cache.rb
@@ -71,7 +71,21 @@ module Gitlab
end
def parse_entry(raw, klass)
- klass.new(raw) if valid_entry?(raw, klass)
+ return unless valid_entry?(raw, klass)
+ return klass.new(raw) unless klass.ancestors.include?(ActiveRecord::Base)
+
+ # When the cached value is a persisted instance of ActiveRecord::Base in
+ # some cases a relation can return an empty collection becauses scope.none!
+ # is being applied on ActiveRecord::Associations::CollectionAssociation#scope
+ # when the new_record? method incorrectly returns false.
+ #
+ # See https://gitlab.com/gitlab-org/gitlab-ee/issues/9903#note_145329964
+ attributes = klass.attributes_builder.build_from_database(raw, {})
+ klass.allocate.init_with("attributes" => attributes, "new_record" => new_record?(raw, klass))
+ end
+
+ def new_record?(raw, klass)
+ raw.fetch(klass.primary_key, nil).blank?
end
def valid_entry?(raw, klass)
diff --git a/lib/gitlab/kubernetes/helm.rb b/lib/gitlab/kubernetes/helm.rb
index bbac15c7710..42c4745ff98 100644
--- a/lib/gitlab/kubernetes/helm.rb
+++ b/lib/gitlab/kubernetes/helm.rb
@@ -3,8 +3,8 @@
module Gitlab
module Kubernetes
module Helm
- HELM_VERSION = '2.12.2'.freeze
- KUBECTL_VERSION = '1.11.0'.freeze
+ HELM_VERSION = '2.12.3'.freeze
+ KUBECTL_VERSION = '1.11.7'.freeze
NAMESPACE = 'gitlab-managed-apps'.freeze
SERVICE_ACCOUNT = 'tiller'.freeze
CLUSTER_ROLE_BINDING = 'tiller-admin'.freeze
diff --git a/lib/gitlab/lfs_token.rb b/lib/gitlab/lfs_token.rb
index 26b81847d37..31e6fc9d8c7 100644
--- a/lib/gitlab/lfs_token.rb
+++ b/lib/gitlab/lfs_token.rb
@@ -30,8 +30,8 @@ module Gitlab
end
end
- def token(expire_time: DEFAULT_EXPIRE_TIME)
- HMACToken.new(actor).token(expire_time)
+ def token
+ HMACToken.new(actor).token(DEFAULT_EXPIRE_TIME)
end
def token_valid?(token_to_check)
@@ -47,6 +47,15 @@ module Gitlab
user? ? :lfs_token : :lfs_deploy_token
end
+ def authentication_payload(repository_http_path)
+ {
+ username: actor_name,
+ lfs_token: token,
+ repository_http_path: repository_http_path,
+ expires_in: DEFAULT_EXPIRE_TIME
+ }
+ end
+
private # rubocop:disable Lint/UselessAccessModifier
class HMACToken
diff --git a/lib/gitlab/metrics/instrumentation.rb b/lib/gitlab/metrics/instrumentation.rb
index 651e241362c..ff3fffe7b95 100644
--- a/lib/gitlab/metrics/instrumentation.rb
+++ b/lib/gitlab/metrics/instrumentation.rb
@@ -19,7 +19,7 @@ module Gitlab
# Returns the name of the series to use for storing method calls.
def self.series
- @series ||= "#{Metrics.series_prefix}method_calls"
+ @series ||= "#{::Gitlab::Metrics.series_prefix}method_calls"
end
# Instruments a class method.
@@ -118,7 +118,7 @@ module Gitlab
# mod - The module containing the method.
# name - The name of the method to instrument.
def self.instrument(type, mod, name)
- return unless Metrics.enabled?
+ return unless ::Gitlab::Metrics.enabled?
name = name.to_sym
target = type == :instance ? mod : mod.singleton_class
diff --git a/lib/gitlab/metrics/method_call.rb b/lib/gitlab/metrics/method_call.rb
index 85438011cb9..d0c63a862c2 100644
--- a/lib/gitlab/metrics/method_call.rb
+++ b/lib/gitlab/metrics/method_call.rb
@@ -65,7 +65,7 @@ module Gitlab
# Returns true if the total runtime of this method exceeds the method call
# threshold.
def above_threshold?
- real_time.in_milliseconds >= Metrics.method_call_threshold
+ real_time.in_milliseconds >= ::Gitlab::Metrics.method_call_threshold
end
end
end
diff --git a/lib/gitlab/metrics/methods.rb b/lib/gitlab/metrics/methods.rb
index 447d03bfca4..cee601ff14c 100644
--- a/lib/gitlab/metrics/methods.rb
+++ b/lib/gitlab/metrics/methods.rb
@@ -58,11 +58,11 @@ module Gitlab
def build_metric!(type, name, options)
case type
when :gauge
- Gitlab::Metrics.gauge(name, options.docstring, options.base_labels, options.multiprocess_mode)
+ ::Gitlab::Metrics.gauge(name, options.docstring, options.base_labels, options.multiprocess_mode)
when :counter
- Gitlab::Metrics.counter(name, options.docstring, options.base_labels)
+ ::Gitlab::Metrics.counter(name, options.docstring, options.base_labels)
when :histogram
- Gitlab::Metrics.histogram(name, options.docstring, options.base_labels, options.buckets)
+ ::Gitlab::Metrics.histogram(name, options.docstring, options.base_labels, options.buckets)
when :summary
raise NotImplementedError, "summary metrics are not currently supported"
else
diff --git a/lib/gitlab/metrics/requests_rack_middleware.rb b/lib/gitlab/metrics/requests_rack_middleware.rb
index 74c956ab5af..26aa0910047 100644
--- a/lib/gitlab/metrics/requests_rack_middleware.rb
+++ b/lib/gitlab/metrics/requests_rack_middleware.rb
@@ -8,15 +8,15 @@ module Gitlab
end
def self.http_request_total
- @http_request_total ||= Gitlab::Metrics.counter(:http_requests_total, 'Request count')
+ @http_request_total ||= ::Gitlab::Metrics.counter(:http_requests_total, 'Request count')
end
def self.rack_uncaught_errors_count
- @rack_uncaught_errors_count ||= Gitlab::Metrics.counter(:rack_uncaught_errors_total, 'Request handling uncaught errors count')
+ @rack_uncaught_errors_count ||= ::Gitlab::Metrics.counter(:rack_uncaught_errors_total, 'Request handling uncaught errors count')
end
def self.http_request_duration_seconds
- @http_request_duration_seconds ||= Gitlab::Metrics.histogram(:http_request_duration_seconds, 'Request handling execution time',
+ @http_request_duration_seconds ||= ::Gitlab::Metrics.histogram(:http_request_duration_seconds, 'Request handling execution time',
{}, [0.05, 0.1, 0.25, 0.5, 0.7, 1, 2.5, 5, 10, 25])
end
diff --git a/lib/gitlab/metrics/samplers/influx_sampler.rb b/lib/gitlab/metrics/samplers/influx_sampler.rb
index c4c38b23a55..5138b37f83e 100644
--- a/lib/gitlab/metrics/samplers/influx_sampler.rb
+++ b/lib/gitlab/metrics/samplers/influx_sampler.rb
@@ -10,7 +10,7 @@ module Gitlab
# statistics, etc.
class InfluxSampler < BaseSampler
# interval - The sampling interval in seconds.
- def initialize(interval = Metrics.settings[:sample_interval])
+ def initialize(interval = ::Gitlab::Metrics.settings[:sample_interval])
super(interval)
@last_step = nil
@@ -32,7 +32,7 @@ module Gitlab
end
def flush
- Metrics.submit_metrics(@metrics.map(&:to_hash))
+ ::Gitlab::Metrics.submit_metrics(@metrics.map(&:to_hash))
end
def sample_memory_usage
diff --git a/lib/gitlab/metrics/samplers/ruby_sampler.rb b/lib/gitlab/metrics/samplers/ruby_sampler.rb
index 232a58a7d69..18a69321905 100644
--- a/lib/gitlab/metrics/samplers/ruby_sampler.rb
+++ b/lib/gitlab/metrics/samplers/ruby_sampler.rb
@@ -24,14 +24,14 @@ module Gitlab
def init_metrics
metrics = {}
- metrics[:sampler_duration] = Metrics.counter(with_prefix(:sampler, :duration_seconds_total), 'Sampler time', labels)
- metrics[:total_time] = Metrics.counter(with_prefix(:gc, :duration_seconds_total), 'Total GC time', labels)
+ metrics[:sampler_duration] = ::Gitlab::Metrics.counter(with_prefix(:sampler, :duration_seconds_total), 'Sampler time', labels)
+ metrics[:total_time] = ::Gitlab::Metrics.counter(with_prefix(:gc, :duration_seconds_total), 'Total GC time', labels)
GC.stat.keys.each do |key|
- metrics[key] = Metrics.gauge(with_prefix(:gc_stat, key), to_doc_string(key), labels, :livesum)
+ metrics[key] = ::Gitlab::Metrics.gauge(with_prefix(:gc_stat, key), to_doc_string(key), labels, :livesum)
end
- metrics[:memory_usage] = Metrics.gauge(with_prefix(:memory, :bytes), 'Memory used', labels, :livesum)
- metrics[:file_descriptors] = Metrics.gauge(with_prefix(:file, :descriptors), 'File descriptors used', labels, :livesum)
+ metrics[:memory_usage] = ::Gitlab::Metrics.gauge(with_prefix(:memory, :bytes), 'Memory used', labels, :livesum)
+ metrics[:file_descriptors] = ::Gitlab::Metrics.gauge(with_prefix(:file, :descriptors), 'File descriptors used', labels, :livesum)
metrics
end
diff --git a/lib/gitlab/metrics/samplers/unicorn_sampler.rb b/lib/gitlab/metrics/samplers/unicorn_sampler.rb
index 4c5b849cc51..bec64e864b3 100644
--- a/lib/gitlab/metrics/samplers/unicorn_sampler.rb
+++ b/lib/gitlab/metrics/samplers/unicorn_sampler.rb
@@ -9,11 +9,11 @@ module Gitlab
end
def unicorn_active_connections
- @unicorn_active_connections ||= Gitlab::Metrics.gauge(:unicorn_active_connections, 'Unicorn active connections', {}, :max)
+ @unicorn_active_connections ||= ::Gitlab::Metrics.gauge(:unicorn_active_connections, 'Unicorn active connections', {}, :max)
end
def unicorn_queued_connections
- @unicorn_queued_connections ||= Gitlab::Metrics.gauge(:unicorn_queued_connections, 'Unicorn queued connections', {}, :max)
+ @unicorn_queued_connections ||= ::Gitlab::Metrics.gauge(:unicorn_queued_connections, 'Unicorn queued connections', {}, :max)
end
def enabled?
diff --git a/lib/gitlab/metrics/sidekiq_metrics_exporter.rb b/lib/gitlab/metrics/sidekiq_metrics_exporter.rb
index 56e106b9612..71a5406815f 100644
--- a/lib/gitlab/metrics/sidekiq_metrics_exporter.rb
+++ b/lib/gitlab/metrics/sidekiq_metrics_exporter.rb
@@ -9,7 +9,7 @@ module Gitlab
LOG_FILENAME = File.join(Rails.root, 'log', 'sidekiq_exporter.log')
def enabled?
- Gitlab::Metrics.metrics_folder_present? && settings.enabled
+ ::Gitlab::Metrics.metrics_folder_present? && settings.enabled
end
def settings
diff --git a/lib/gitlab/metrics/subscribers/rails_cache.rb b/lib/gitlab/metrics/subscribers/rails_cache.rb
index f633e1a9d7c..01db507761b 100644
--- a/lib/gitlab/metrics/subscribers/rails_cache.rb
+++ b/lib/gitlab/metrics/subscribers/rails_cache.rb
@@ -64,7 +64,7 @@ module Gitlab
end
def metric_cache_operation_duration_seconds
- @metric_cache_operation_duration_seconds ||= Gitlab::Metrics.histogram(
+ @metric_cache_operation_duration_seconds ||= ::Gitlab::Metrics.histogram(
:gitlab_cache_operation_duration_seconds,
'Cache access time',
Transaction::BASE_LABELS.merge({ action: nil }),
@@ -73,7 +73,7 @@ module Gitlab
end
def metric_cache_misses_total
- @metric_cache_misses_total ||= Gitlab::Metrics.counter(
+ @metric_cache_misses_total ||= ::Gitlab::Metrics.counter(
:gitlab_cache_misses_total,
'Cache read miss',
Transaction::BASE_LABELS
diff --git a/lib/gitlab/metrics/transaction.rb b/lib/gitlab/metrics/transaction.rb
index 468d7cb56fc..e91803ecd62 100644
--- a/lib/gitlab/metrics/transaction.rb
+++ b/lib/gitlab/metrics/transaction.rb
@@ -64,7 +64,7 @@ module Gitlab
end
def add_metric(series, values, tags = {})
- @metrics << Metric.new("#{Metrics.series_prefix}#{series}", values, tags)
+ @metrics << Metric.new("#{::Gitlab::Metrics.series_prefix}#{series}", values, tags)
end
# Tracks a business level event
@@ -127,7 +127,7 @@ module Gitlab
hash
end
- Metrics.submit_metrics(submit_hashes)
+ ::Gitlab::Metrics.submit_metrics(submit_hashes)
end
def labels
diff --git a/lib/gitlab/project_template.rb b/lib/gitlab/project_template.rb
index ef656e5b2ce..9b6ff602fcd 100644
--- a/lib/gitlab/project_template.rb
+++ b/lib/gitlab/project_template.rb
@@ -28,11 +28,18 @@ module Gitlab
ProjectTemplate.new('rails', 'Ruby on Rails', _('Includes an MVC structure, Gemfile, Rakefile, along with many others, to help you get started.'), 'https://gitlab.com/gitlab-org/project-templates/rails', 'illustrations/logos/rails.svg'),
ProjectTemplate.new('spring', 'Spring', _('Includes an MVC structure, mvnw and pom.xml to help you get started.'), 'https://gitlab.com/gitlab-org/project-templates/spring', 'illustrations/logos/spring.svg'),
ProjectTemplate.new('express', 'NodeJS Express', _('Includes an MVC structure to help you get started.'), 'https://gitlab.com/gitlab-org/project-templates/express', 'illustrations/logos/express.svg'),
+ ProjectTemplate.new('dotnetcore', '.NET Core', _('A .NET Core console application template, customizable for any .NET Core project'), 'https://gitlab.com/gitlab-org/project-templates/dotnetcore', 'illustrations/logos/dotnet.svg'),
+ ProjectTemplate.new('gomicro', 'Go Micro', _('Go Micro is a framework for micro service development.'), 'https://gitlab.com/gitlab-org/project-templates/go-micro'),
ProjectTemplate.new('hugo', 'Pages/Hugo', _('Everything you need to create a GitLab Pages site using Hugo.'), 'https://gitlab.com/pages/hugo'),
ProjectTemplate.new('jekyll', 'Pages/Jekyll', _('Everything you need to create a GitLab Pages site using Jekyll.'), 'https://gitlab.com/pages/jekyll'),
ProjectTemplate.new('plainhtml', 'Pages/Plain HTML', _('Everything you need to create a GitLab Pages site using plain HTML.'), 'https://gitlab.com/pages/plain-html'),
ProjectTemplate.new('gitbook', 'Pages/GitBook', _('Everything you need to create a GitLab Pages site using GitBook.'), 'https://gitlab.com/pages/gitbook'),
- ProjectTemplate.new('hexo', 'Pages/Hexo', _('Everything you need to create a GitLab Pages site using Hexo.'), 'https://gitlab.com/pages/hexo')
+ ProjectTemplate.new('hexo', 'Pages/Hexo', _('Everything you need to create a GitLab Pages site using Hexo.'), 'https://gitlab.com/pages/hexo'),
+ ProjectTemplate.new('nfhugo', 'Netlify/Hugo', _('A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfhugo', 'illustrations/logos/netlify.svg'),
+ ProjectTemplate.new('nfjekyll', 'Netlify/Jekyll', _('A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfjekyll', 'illustrations/logos/netlify.svg'),
+ ProjectTemplate.new('nfplainhtml', 'Netlify/Plain HTML', _('A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfplain-html', 'illustrations/logos/netlify.svg'),
+ ProjectTemplate.new('nfgitbook', 'Netlify/GitBook', _('A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfgitbook', 'illustrations/logos/netlify.svg'),
+ ProjectTemplate.new('nfhexo', 'Netlify/Hexo', _('A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfhexo', 'illustrations/logos/netlify.svg')
].freeze
class << self
diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb
index 1153e69d3de..40b641b8317 100644
--- a/lib/gitlab/shell.rb
+++ b/lib/gitlab/shell.rb
@@ -280,7 +280,10 @@ module Gitlab
# add_namespace("default", "gitlab")
#
def add_namespace(storage, name)
- Gitlab::GitalyClient::NamespaceService.new(storage).add(name)
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/58012
+ Gitlab::GitalyClient.allow_n_plus_1_calls do
+ Gitlab::GitalyClient::NamespaceService.new(storage).add(name)
+ end
rescue GRPC::InvalidArgument => e
raise ArgumentError, e.message
end
@@ -337,16 +340,16 @@ module Gitlab
end
# rubocop: enable CodeReuse/ActiveRecord
+ def hooks_path
+ File.join(gitlab_shell_path, 'hooks')
+ end
+
protected
def gitlab_shell_path
File.expand_path(Gitlab.config.gitlab_shell.path)
end
- def gitlab_shell_hooks_path
- File.expand_path(Gitlab.config.gitlab_shell.hooks_path)
- end
-
def gitlab_shell_user_home
File.expand_path("~#{Gitlab.config.gitlab_shell.ssh_user}")
end
diff --git a/lib/gitlab/sidekiq_middleware/memory_killer.rb b/lib/gitlab/sidekiq_middleware/memory_killer.rb
new file mode 100644
index 00000000000..47333d257eb
--- /dev/null
+++ b/lib/gitlab/sidekiq_middleware/memory_killer.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SidekiqMiddleware
+ class MemoryKiller
+ # Default the RSS limit to 0, meaning the MemoryKiller is disabled
+ MAX_RSS = (ENV['SIDEKIQ_MEMORY_KILLER_MAX_RSS'] || 0).to_s.to_i
+ # Give Sidekiq 15 minutes of grace time after exceeding the RSS limit
+ GRACE_TIME = (ENV['SIDEKIQ_MEMORY_KILLER_GRACE_TIME'] || 15 * 60).to_s.to_i
+ # Wait 30 seconds for running jobs to finish during graceful shutdown
+ SHUTDOWN_WAIT = (ENV['SIDEKIQ_MEMORY_KILLER_SHUTDOWN_WAIT'] || 30).to_s.to_i
+
+ # Create a mutex used to ensure there will be only one thread waiting to
+ # shut Sidekiq down
+ MUTEX = Mutex.new
+
+ def call(worker, job, queue)
+ yield
+
+ current_rss = get_rss
+
+ return unless MAX_RSS > 0 && current_rss > MAX_RSS
+
+ Thread.new do
+ # Return if another thread is already waiting to shut Sidekiq down
+ next unless MUTEX.try_lock
+
+ Sidekiq.logger.warn "Sidekiq worker PID-#{pid} current RSS #{current_rss}"\
+ " exceeds maximum RSS #{MAX_RSS} after finishing job #{worker.class} JID-#{job['jid']}"
+ Sidekiq.logger.warn "Sidekiq worker PID-#{pid} will stop fetching new jobs in #{GRACE_TIME} seconds, and will be shut down #{SHUTDOWN_WAIT} seconds later"
+
+ # Wait `GRACE_TIME` to give the memory intensive job time to finish.
+ # Then, tell Sidekiq to stop fetching new jobs.
+ wait_and_signal(GRACE_TIME, 'SIGTSTP', 'stop fetching new jobs')
+
+ # Wait `SHUTDOWN_WAIT` to give already fetched jobs time to finish.
+ # Then, tell Sidekiq to gracefully shut down by giving jobs a few more
+ # moments to finish, killing and requeuing them if they didn't, and
+ # then terminating itself.
+ wait_and_signal(SHUTDOWN_WAIT, 'SIGTERM', 'gracefully shut down')
+
+ # Wait for Sidekiq to shutdown gracefully, and kill it if it didn't.
+ wait_and_signal(Sidekiq.options[:timeout] + 2, 'SIGKILL', 'die')
+ end
+ end
+
+ private
+
+ def get_rss
+ output, status = Gitlab::Popen.popen(%W(ps -o rss= -p #{pid}), Rails.root.to_s)
+ return 0 unless status.zero?
+
+ output.to_i
+ end
+
+ def wait_and_signal(time, signal, explanation)
+ Sidekiq.logger.warn "waiting #{time} seconds before sending Sidekiq worker PID-#{pid} #{signal} (#{explanation})"
+ sleep(time)
+
+ Sidekiq.logger.warn "sending Sidekiq worker PID-#{pid} #{signal} (#{explanation})"
+ Process.kill(signal, pid)
+ end
+
+ def pid
+ Process.pid
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/sidekiq_middleware/shutdown.rb b/lib/gitlab/sidekiq_middleware/shutdown.rb
deleted file mode 100644
index 19f3be83bce..00000000000
--- a/lib/gitlab/sidekiq_middleware/shutdown.rb
+++ /dev/null
@@ -1,135 +0,0 @@
-# frozen_string_literal: true
-
-require 'mutex_m'
-
-module Gitlab
- module SidekiqMiddleware
- class Shutdown
- extend Mutex_m
-
- # Default the RSS limit to 0, meaning the MemoryKiller is disabled
- MAX_RSS = (ENV['SIDEKIQ_MEMORY_KILLER_MAX_RSS'] || 0).to_s.to_i
- # Give Sidekiq 15 minutes of grace time after exceeding the RSS limit
- GRACE_TIME = (ENV['SIDEKIQ_MEMORY_KILLER_GRACE_TIME'] || 15 * 60).to_s.to_i
- # Wait 30 seconds for running jobs to finish during graceful shutdown
- SHUTDOWN_WAIT = (ENV['SIDEKIQ_MEMORY_KILLER_SHUTDOWN_WAIT'] || 30).to_s.to_i
-
- # This exception can be used to request that the middleware start shutting down Sidekiq
- WantShutdown = Class.new(StandardError)
-
- ShutdownWithoutRaise = Class.new(WantShutdown)
- private_constant :ShutdownWithoutRaise
-
- # For testing only, to avoid race conditions (?) in Rspec mocks.
- attr_reader :trace
-
- # We store the shutdown thread in a class variable to ensure that there
- # can be only one shutdown thread in the process.
- def self.create_shutdown_thread
- mu_synchronize do
- break unless @shutdown_thread.nil?
-
- @shutdown_thread = Thread.new { yield }
- end
- end
-
- # For testing only: so we can wait for the shutdown thread to finish.
- def self.shutdown_thread
- mu_synchronize { @shutdown_thread }
- end
-
- # For testing only: so that we can reset the global state before each test.
- def self.clear_shutdown_thread
- mu_synchronize { @shutdown_thread = nil }
- end
-
- def initialize
- @trace = Queue.new if Rails.env.test?
- end
-
- def call(worker, job, queue)
- shutdown_exception = nil
-
- begin
- yield
- check_rss!
- rescue WantShutdown => ex
- shutdown_exception = ex
- end
-
- return unless shutdown_exception
-
- self.class.create_shutdown_thread do
- do_shutdown(worker, job, shutdown_exception)
- end
-
- raise shutdown_exception unless shutdown_exception.is_a?(ShutdownWithoutRaise)
- end
-
- private
-
- def do_shutdown(worker, job, shutdown_exception)
- Sidekiq.logger.warn "Sidekiq worker PID-#{pid} shutting down because of #{shutdown_exception} after job "\
- "#{worker.class} JID-#{job['jid']}"
- Sidekiq.logger.warn "Sidekiq worker PID-#{pid} will stop fetching new jobs in #{GRACE_TIME} seconds, and will be shut down #{SHUTDOWN_WAIT} seconds later"
-
- # Wait `GRACE_TIME` to give the memory intensive job time to finish.
- # Then, tell Sidekiq to stop fetching new jobs.
- wait_and_signal(GRACE_TIME, 'SIGTSTP', 'stop fetching new jobs')
-
- # Wait `SHUTDOWN_WAIT` to give already fetched jobs time to finish.
- # Then, tell Sidekiq to gracefully shut down by giving jobs a few more
- # moments to finish, killing and requeuing them if they didn't, and
- # then terminating itself.
- wait_and_signal(SHUTDOWN_WAIT, 'SIGTERM', 'gracefully shut down')
-
- # Wait for Sidekiq to shutdown gracefully, and kill it if it didn't.
- wait_and_signal(Sidekiq.options[:timeout] + 2, 'SIGKILL', 'die')
- end
-
- def check_rss!
- return unless MAX_RSS > 0
-
- current_rss = get_rss
- return unless current_rss > MAX_RSS
-
- raise ShutdownWithoutRaise.new("current RSS #{current_rss} exceeds maximum RSS #{MAX_RSS}")
- end
-
- def get_rss
- output, status = Gitlab::Popen.popen(%W(ps -o rss= -p #{pid}), Rails.root.to_s)
- return 0 unless status.zero?
-
- output.to_i
- end
-
- def wait_and_signal(time, signal, explanation)
- Sidekiq.logger.warn "waiting #{time} seconds before sending Sidekiq worker PID-#{pid} #{signal} (#{explanation})"
- sleep(time)
-
- Sidekiq.logger.warn "sending Sidekiq worker PID-#{pid} #{signal} (#{explanation})"
- kill(signal, pid)
- end
-
- def pid
- Process.pid
- end
-
- def sleep(time)
- if Rails.env.test?
- @trace << [:sleep, time]
- else
- Kernel.sleep(time)
- end
- end
-
- def kill(signal, pid)
- if Rails.env.test?
- @trace << [:kill, signal, pid]
- else
- Process.kill(signal, pid)
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/slash_commands/application_help.rb b/lib/gitlab/slash_commands/application_help.rb
new file mode 100644
index 00000000000..0ea7554ba64
--- /dev/null
+++ b/lib/gitlab/slash_commands/application_help.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SlashCommands
+ class ApplicationHelp < BaseCommand
+ def initialize(params)
+ @params = params
+ end
+
+ def execute
+ Gitlab::SlashCommands::Presenters::Help.new(commands).present(trigger, params[:text])
+ end
+
+ private
+
+ def trigger
+ "#{params[:command]} [project name or alias]"
+ end
+
+ def commands
+ Gitlab::SlashCommands::Command.commands
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/slash_commands/command.rb b/lib/gitlab/slash_commands/command.rb
index 474c09b9c4d..7c963fcf38a 100644
--- a/lib/gitlab/slash_commands/command.rb
+++ b/lib/gitlab/slash_commands/command.rb
@@ -9,7 +9,8 @@ module Gitlab
Gitlab::SlashCommands::IssueNew,
Gitlab::SlashCommands::IssueSearch,
Gitlab::SlashCommands::IssueMove,
- Gitlab::SlashCommands::Deploy
+ Gitlab::SlashCommands::Deploy,
+ Gitlab::SlashCommands::Run
]
end
diff --git a/lib/gitlab/slash_commands/presenters/error.rb b/lib/gitlab/slash_commands/presenters/error.rb
new file mode 100644
index 00000000000..442f8796338
--- /dev/null
+++ b/lib/gitlab/slash_commands/presenters/error.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SlashCommands
+ module Presenters
+ class Error < Presenters::Base
+ def initialize(message)
+ @message = message
+ end
+
+ def message
+ ephemeral_response(text: @message)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/slash_commands/presenters/run.rb b/lib/gitlab/slash_commands/presenters/run.rb
new file mode 100644
index 00000000000..c4bbc231464
--- /dev/null
+++ b/lib/gitlab/slash_commands/presenters/run.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SlashCommands
+ module Presenters
+ class Run < Presenters::Base
+ # rubocop: disable CodeReuse/ActiveRecord
+ def present(pipeline)
+ build = pipeline.builds.take
+
+ if build && (responder = Chat::Responder.responder_for(build))
+ in_channel_response(responder.scheduled_output)
+ else
+ unsupported_chat_service
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ def unsupported_chat_service
+ ephemeral_response(text: 'Sorry, this chat service is currently not supported by GitLab ChatOps.')
+ end
+
+ def failed_to_schedule(command)
+ ephemeral_response(
+ text: 'The command could not be scheduled. Make sure that your ' \
+ 'project has a .gitlab-ci.yml that defines a job with the ' \
+ "name #{command.inspect}"
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/slash_commands/run.rb b/lib/gitlab/slash_commands/run.rb
new file mode 100644
index 00000000000..10a545e28ac
--- /dev/null
+++ b/lib/gitlab/slash_commands/run.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SlashCommands
+ # Slash command for triggering chatops jobs.
+ class Run < BaseCommand
+ def self.match(text)
+ /\Arun\s+(?<command>\S+)(\s+(?<arguments>.+))?\z/.match(text)
+ end
+
+ def self.help_message
+ 'run <command> <arguments>'
+ end
+
+ def self.available?(project)
+ Chat.available? && project.builds_enabled?
+ end
+
+ def self.allowed?(project, user)
+ can?(user, :create_pipeline, project)
+ end
+
+ def execute(match)
+ command = Chat::Command.new(
+ project: project,
+ chat_name: chat_name,
+ name: match[:command],
+ arguments: match[:arguments],
+ channel: params[:channel_id],
+ response_url: params[:response_url]
+ )
+
+ presenter = Gitlab::SlashCommands::Presenters::Run.new
+ pipeline = command.try_create_pipeline
+
+ if pipeline&.persisted?
+ presenter.present(pipeline)
+ else
+ presenter.failed_to_schedule(command.name)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/tracing.rb b/lib/gitlab/tracing.rb
index 0d9b0be1c8e..29517591c51 100644
--- a/lib/gitlab/tracing.rb
+++ b/lib/gitlab/tracing.rb
@@ -27,10 +27,11 @@ module Gitlab
def self.tracing_url
return unless tracing_url_enabled?
- tracing_url_template % {
- correlation_id: Gitlab::CorrelationId.current_id.to_s,
- service: Gitlab.process_name
- }
+ # Avoid using `format` since it can throw TypeErrors
+ # which we want to avoid on unsanitised env var input
+ tracing_url_template.to_s
+ .gsub(/\{\{\s*correlation_id\s*\}\}/, Gitlab::CorrelationId.current_id.to_s)
+ .gsub(/\{\{\s*service\s*\}\}/, Gitlab.process_name)
end
end
end
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index a65f4a8639c..0101ccc046a 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -64,12 +64,12 @@ module Gitlab
group_clusters_disabled: count(::Clusters::Cluster.disabled.group_type),
clusters_platforms_gke: count(::Clusters::Cluster.gcp_installed.enabled),
clusters_platforms_user: count(::Clusters::Cluster.user_provided.enabled),
- clusters_applications_helm: count(::Clusters::Applications::Helm.installed),
- clusters_applications_ingress: count(::Clusters::Applications::Ingress.installed),
- clusters_applications_cert_managers: count(::Clusters::Applications::CertManager.installed),
- clusters_applications_prometheus: count(::Clusters::Applications::Prometheus.installed),
- clusters_applications_runner: count(::Clusters::Applications::Runner.installed),
- clusters_applications_knative: count(::Clusters::Applications::Knative.installed),
+ clusters_applications_helm: count(::Clusters::Applications::Helm.available),
+ clusters_applications_ingress: count(::Clusters::Applications::Ingress.available),
+ clusters_applications_cert_managers: count(::Clusters::Applications::CertManager.available),
+ clusters_applications_prometheus: count(::Clusters::Applications::Prometheus.available),
+ clusters_applications_runner: count(::Clusters::Applications::Runner.available),
+ clusters_applications_knative: count(::Clusters::Applications::Knative.available),
in_review_folder: count(::Environment.in_review_folder),
groups: count(Group),
issues: count(Issue),
diff --git a/lib/sentry/client.rb b/lib/sentry/client.rb
index 4187014d49e..49ec196b103 100644
--- a/lib/sentry/client.rb
+++ b/lib/sentry/client.rb
@@ -54,7 +54,7 @@ module Sentry
def handle_response(response)
unless response.code == 200
- raise Client::Error, "Sentry response error: #{response.code}"
+ raise Client::Error, "Sentry response status code: #{response.code}"
end
response.as_json
diff --git a/lib/tasks/dev.rake b/lib/tasks/dev.rake
index 4beb94eeb8e..b1db4dc94a6 100644
--- a/lib/tasks/dev.rake
+++ b/lib/tasks/dev.rake
@@ -10,6 +10,7 @@ namespace :dev do
desc "GitLab | Eager load application"
task load: :environment do
+ Rails.configuration.eager_load = true
Rails.application.eager_load!
end
end
diff --git a/lib/tasks/gitlab/info.rake b/lib/tasks/gitlab/info.rake
index e97d77d20e0..b8798fb3cfd 100644
--- a/lib/tasks/gitlab/info.rake
+++ b/lib/tasks/gitlab/info.rake
@@ -58,7 +58,7 @@ namespace :gitlab do
puts "Omniauth Providers: #{omniauth_providers.join(', ')}" if Gitlab::Auth.omniauth_enabled?
# check Gitolite version
- gitlab_shell_version_file = "#{Gitlab.config.gitlab_shell.hooks_path}/../VERSION"
+ gitlab_shell_version_file = "#{Gitlab.config.gitlab_shell.path}/VERSION"
if File.readable?(gitlab_shell_version_file)
gitlab_shell_version = File.read(gitlab_shell_version_file)
end
@@ -72,7 +72,7 @@ namespace :gitlab do
puts "- #{name}: \t#{repository_storage.legacy_disk_path}"
end
end
- puts "Hooks:\t\t#{Gitlab.config.gitlab_shell.hooks_path}"
+ puts "GitLab Shell path:\t\t#{Gitlab.config.gitlab_shell.path}"
puts "Git:\t\t#{Gitlab.config.git.bin_path}"
end
end
diff --git a/lib/tasks/gitlab/storage.rake b/lib/tasks/gitlab/storage.rake
index f9ce3e1d338..a2136ce1b92 100644
--- a/lib/tasks/gitlab/storage.rake
+++ b/lib/tasks/gitlab/storage.rake
@@ -36,8 +36,54 @@ namespace :gitlab do
print "Enqueuing migration of #{legacy_projects_count} projects in batches of #{helper.batch_size}"
- helper.project_id_batches do |start, finish|
- storage_migrator.bulk_schedule(start: start, finish: finish)
+ helper.project_id_batches_migration do |start, finish|
+ storage_migrator.bulk_schedule_migration(start: start, finish: finish)
+
+ print '.'
+ end
+
+ puts ' Done!'
+ end
+
+ desc 'GitLab | Storage | Rollback existing projects to Legacy Storage'
+ task rollback_to_legacy: :environment do
+ if Gitlab::Database.read_only?
+ warn 'This task requires database write access. Exiting.'
+
+ next
+ end
+
+ storage_migrator = Gitlab::HashedStorage::Migrator.new
+ helper = Gitlab::HashedStorage::RakeHelper
+
+ if helper.range_single_item?
+ project = Project.with_storage_feature(:repository).find_by(id: helper.range_from)
+
+ unless project
+ warn "There are no projects that can be rolledback with ID=#{helper.range_from}"
+
+ next
+ end
+
+ puts "Enqueueing storage rollback of #{project.full_path} (ID=#{project.id})..."
+ storage_migrator.rollback(project)
+
+ next
+ end
+
+ hashed_projects_count = Project.with_storage_feature(:repository).count
+
+ if hashed_projects_count == 0
+ warn 'There are no projects that can have storage rolledback. Nothing to do!'
+
+ next
+ end
+
+ print "Enqueuing rollback of #{hashed_projects_count} projects in batches of #{helper.batch_size}"
+
+ helper.project_id_batches_rollback do |start, finish|
+ puts "Start: #{start} FINISH: #{finish}"
+ storage_migrator.bulk_schedule_rollback(start: start, finish: finish)
print '.'
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 7cbba0779f7..cb599e4744c 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -117,6 +117,9 @@ msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{label_for_message} unavailable"
+msgstr ""
+
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
@@ -282,6 +285,21 @@ msgstr ""
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr ""
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A collection of graphs regarding Continuous Integration"
msgstr ""
@@ -297,6 +315,9 @@ msgstr ""
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr ""
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr ""
@@ -363,18 +384,39 @@ msgstr ""
msgid "Add README"
msgstr ""
+msgid "Add a bullet list"
+msgstr ""
+
msgid "Add a general comment to this %{noteable_name}."
msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
+msgid "Add a link"
+msgstr ""
+
+msgid "Add a numbered list"
+msgstr ""
+
msgid "Add a table"
msgstr ""
+msgid "Add a task list"
+msgstr ""
+
+msgid "Add bold text"
+msgstr ""
+
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr ""
+msgid "Add italic text"
+msgstr ""
+
msgid "Add license"
msgstr ""
@@ -528,6 +570,9 @@ msgstr ""
msgid "Advanced settings"
msgstr ""
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "All"
msgstr ""
@@ -588,10 +633,10 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
-msgid "An error occured while fetching the releases. Please try again."
+msgid "An error occurred creating the new branch."
msgstr ""
-msgid "An error occurred creating the new branch."
+msgid "An error occurred fetching the dropdown data."
msgstr ""
msgid "An error occurred previewing the blob"
@@ -630,6 +675,9 @@ msgstr ""
msgid "An error occurred while fetching the pipeline."
msgstr ""
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr ""
@@ -840,9 +888,6 @@ msgstr ""
msgid "August"
msgstr ""
-msgid "Auth Token"
-msgstr ""
-
msgid "Authentication Log"
msgstr ""
@@ -945,6 +990,9 @@ msgstr ""
msgid "Average per day: %{average}"
msgstr ""
+msgid "Background Color"
+msgstr ""
+
msgid "Background Jobs"
msgstr ""
@@ -1323,6 +1371,9 @@ msgstr ""
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr ""
+msgid "Changes"
+msgstr ""
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
@@ -1389,9 +1440,6 @@ msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
-msgid "Choose which repositories you want to import."
-msgstr ""
-
msgid "CiStatusLabel|canceled"
msgstr ""
@@ -1536,9 +1584,6 @@ msgstr ""
msgid "Closed"
msgstr ""
-msgid "Closed (moved)"
-msgstr ""
-
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -1581,10 +1626,10 @@ msgstr ""
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
+msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr ""
-msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
msgstr ""
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
@@ -2447,6 +2492,9 @@ msgstr ""
msgid "Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}."
msgstr ""
+msgid "Customize colors"
+msgstr ""
+
msgid "Customize how FogBugz email addresses and usernames are imported into GitLab. In the next step, you'll be able to select the projects you want to import."
msgstr ""
@@ -2932,6 +2980,9 @@ msgstr ""
msgid "Enable group Runners"
msgstr ""
+msgid "Enable header and footer in emails"
+msgstr ""
+
msgid "Enable or disable version check and usage ping."
msgstr ""
@@ -2974,9 +3025,6 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
-msgid "Enter your Sentry API URL"
-msgstr ""
-
msgid "Environment variables"
msgstr ""
@@ -3151,6 +3199,33 @@ msgstr ""
msgid "Error:"
msgstr ""
+msgid "ErrorTracking|Active"
+msgstr ""
+
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
+msgstr ""
+
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
msgid "Errors"
msgstr ""
@@ -3289,7 +3364,7 @@ msgstr ""
msgid "Failed to load emoji list."
msgstr ""
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -3375,9 +3450,6 @@ msgstr ""
msgid "Filter..."
msgstr ""
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr ""
@@ -3429,6 +3501,12 @@ msgstr ""
msgid "Follow the steps below to export your Google Code project data."
msgstr ""
+msgid "Font Color"
+msgstr ""
+
+msgid "Footer message"
+msgstr ""
+
msgid "For internal projects, any logged in user can view pipelines and access job details (output logs and artifacts)"
msgstr ""
@@ -3459,7 +3537,7 @@ msgstr ""
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
-msgid "From %{provider_title}"
+msgid "From %{providerTitle}"
msgstr ""
msgid "From Bitbucket"
@@ -3486,9 +3564,15 @@ msgstr ""
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr ""
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr ""
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr ""
@@ -3573,15 +3657,24 @@ msgstr ""
msgid "Go Back"
msgstr ""
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr ""
+msgid "Go full screen"
+msgstr ""
+
msgid "Go to"
msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to project"
+msgstr ""
+
msgid "Google Code import"
msgstr ""
@@ -3747,6 +3840,9 @@ msgstr ""
msgid "GroupsTree|Search by name"
msgstr ""
+msgid "Header message"
+msgstr ""
+
msgid "Health Check"
msgstr ""
@@ -3953,6 +4049,21 @@ msgstr ""
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "In order to enable instance-level analytics, please ask an admin to enable %{usage_ping_link_start}usage ping%{usage_ping_link_end}."
msgstr ""
@@ -3998,6 +4109,12 @@ msgstr ""
msgid "Input your repository URL"
msgstr ""
+msgid "Insert a quote"
+msgstr ""
+
+msgid "Insert code"
+msgstr ""
+
msgid "Insert suggestion"
msgstr ""
@@ -4046,6 +4163,9 @@ msgstr ""
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid input, please avoid emojis"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -4064,6 +4184,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr ""
@@ -4082,7 +4205,7 @@ msgstr ""
msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable."
msgstr ""
-msgid "Issues, merge requests, pushes and comments."
+msgid "Issues, merge requests, pushes, and comments."
msgstr ""
msgid "It must have a header row and at least two columns: the first column is the issue title and the second column is the issue description. The separator is automatically detected."
@@ -4238,6 +4361,9 @@ msgstr ""
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Language"
+msgstr ""
+
msgid "Large File Storage"
msgstr ""
@@ -4306,6 +4432,9 @@ msgstr ""
msgid "Learn more about protected branches"
msgstr ""
+msgid "Learn more about signing commits"
+msgstr ""
+
msgid "Learn more in the"
msgstr ""
@@ -4428,6 +4557,9 @@ msgstr ""
msgid "Manifest file import"
msgstr ""
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr ""
@@ -4455,36 +4587,6 @@ msgstr ""
msgid "Markdown enabled"
msgstr ""
-msgid "MarkdownToolbar|Add a bullet list"
-msgstr ""
-
-msgid "MarkdownToolbar|Add a link"
-msgstr ""
-
-msgid "MarkdownToolbar|Add a numbered list"
-msgstr ""
-
-msgid "MarkdownToolbar|Add a table"
-msgstr ""
-
-msgid "MarkdownToolbar|Add a task list"
-msgstr ""
-
-msgid "MarkdownToolbar|Add bold text"
-msgstr ""
-
-msgid "MarkdownToolbar|Add italic text"
-msgstr ""
-
-msgid "MarkdownToolbar|Go full screen"
-msgstr ""
-
-msgid "MarkdownToolbar|Insert a quote"
-msgstr ""
-
-msgid "MarkdownToolbar|Insert code"
-msgstr ""
-
msgid "Max access level"
msgstr ""
@@ -4796,13 +4898,13 @@ msgstr ""
msgid "New Pages Domain"
msgstr ""
-msgid "New Pipeline Schedule"
+msgid "New Password"
msgstr ""
-msgid "New Snippet"
+msgid "New Pipeline Schedule"
msgstr ""
-msgid "New Snippets"
+msgid "New Snippet"
msgstr ""
msgid "New branch"
@@ -4862,6 +4964,12 @@ msgstr ""
msgid "No"
msgstr ""
+msgid "No %{providerTitle} repositories available to import"
+msgstr ""
+
+msgid "No Tag"
+msgstr ""
+
msgid "No activities found"
msgstr ""
@@ -4970,6 +5078,9 @@ msgstr ""
msgid "Not now"
msgstr ""
+msgid "Not started"
+msgstr ""
+
msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
msgstr ""
@@ -5146,6 +5257,9 @@ msgstr ""
msgid "Operations Dashboard"
msgstr ""
+msgid "Operations Settings"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -5449,6 +5563,9 @@ msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr ""
@@ -5458,6 +5575,9 @@ msgstr ""
msgid "Please select at least one filter to see results"
msgstr ""
+msgid "Please set a new password before proceeding."
+msgstr ""
+
msgid "Please solve the reCAPTCHA"
msgstr ""
@@ -5476,6 +5596,9 @@ msgstr ""
msgid "Preferences|Navigation theme"
msgstr ""
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr ""
+
msgid "Press Enter or click to search"
msgstr ""
@@ -5668,9 +5791,6 @@ 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 ""
@@ -5770,6 +5890,9 @@ msgstr ""
msgid "Project"
msgstr ""
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr ""
@@ -5812,6 +5935,9 @@ msgstr ""
msgid "Project export started. A download link will be sent by email."
msgstr ""
+msgid "Project has too many %{label_for_message} to search"
+msgstr ""
+
msgid "Project members"
msgstr ""
@@ -5872,6 +5998,9 @@ msgstr ""
msgid "Projects"
msgstr ""
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr ""
@@ -6320,6 +6449,9 @@ msgstr ""
msgid "Running"
msgstr ""
+msgid "Running…"
+msgstr ""
+
msgid "SSH Keys"
msgstr ""
@@ -6362,6 +6494,9 @@ msgstr ""
msgid "Schedules"
msgstr ""
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr ""
@@ -6578,6 +6713,9 @@ msgstr ""
msgid "Set max session time for web terminal."
msgstr ""
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr ""
@@ -6596,6 +6734,9 @@ msgstr ""
msgid "Set up new U2F device"
msgstr ""
+msgid "Set up new password"
+msgstr ""
+
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
@@ -6641,9 +6782,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr ""
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr ""
@@ -6703,6 +6850,24 @@ msgstr ""
msgid "Snippets"
msgstr ""
+msgid "SnippetsEmptyState|Explore public snippets"
+msgstr ""
+
+msgid "SnippetsEmptyState|New snippet"
+msgstr ""
+
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -6934,6 +7099,12 @@ msgstr ""
msgid "Starred projects"
msgstr ""
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
+msgstr ""
+
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
+msgstr ""
+
msgid "Stars"
msgstr ""
@@ -6973,6 +7144,9 @@ msgstr ""
msgid "Starts at (UTC)"
msgstr ""
+msgid "State your message to activate"
+msgstr ""
+
msgid "Status"
msgstr ""
@@ -7054,6 +7228,9 @@ msgstr ""
msgid "System default (%{default})"
msgstr ""
+msgid "System header and footer"
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
@@ -7303,6 +7480,9 @@ msgstr ""
msgid "There was an error loading users activity calendar."
msgstr ""
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr ""
@@ -7345,6 +7525,21 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr ""
@@ -7360,6 +7555,9 @@ msgstr ""
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This field is required."
+msgstr ""
+
msgid "This group"
msgstr ""
@@ -8354,6 +8552,9 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr ""
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
msgstr ""
@@ -8504,6 +8705,9 @@ msgstr ""
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
@@ -8597,6 +8801,9 @@ msgstr ""
msgid "attach a new file"
msgstr ""
+msgid "authored"
+msgstr ""
+
msgid "branch name"
msgstr ""
@@ -8664,9 +8871,6 @@ msgstr ""
msgid "here"
msgstr ""
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
-msgstr ""
-
msgid "https://your-bitbucket-server"
msgstr ""
diff --git a/package.json b/package.json
index 3cd023c0b0c..d846d214417 100644
--- a/package.json
+++ b/package.json
@@ -16,6 +16,7 @@
"prettier-staged-save": "node ./scripts/frontend/prettier.js save",
"prettier-all": "node ./scripts/frontend/prettier.js check-all",
"prettier-all-save": "node ./scripts/frontend/prettier.js save-all",
+ "stylelint": "node node_modules/stylelint/bin/stylelint.js app/assets/stylesheets/**/*.*",
"webpack": "webpack --config config/webpack.config.js",
"webpack-prod": "NODE_ENV=production webpack --config config/webpack.config.js"
},
@@ -28,10 +29,10 @@
"@babel/plugin-syntax-import-meta": "^7.2.0",
"@babel/preset-env": "^7.3.1",
"@gitlab/csslab": "^1.8.0",
- "@gitlab/svgs": "^1.52.0",
- "@gitlab/ui": "^2.0.2",
- "apollo-boost": "^0.1.20",
- "apollo-client": "^2.4.5",
+ "@gitlab/svgs": "^1.54.0",
+ "@gitlab/ui": "^2.0.4",
+ "apollo-boost": "^0.3.1",
+ "apollo-client": "^2.5.1",
"autosize": "^4.0.0",
"axios": "^0.17.1",
"babel-loader": "^8.0.5",
@@ -71,6 +72,7 @@
"graphql": "^14.0.2",
"imports-loader": "^0.8.0",
"jed": "^1.1.1",
+ "jest-transform-graphql": "^2.1.0",
"jquery": "^3.2.1",
"jquery-ujs": "1.2.2",
"jquery.waitforimages": "^2.2.0",
@@ -111,7 +113,7 @@
"url-loader": "^1.1.2",
"visibilityjs": "^1.2.4",
"vue": "^2.5.21",
- "vue-apollo": "^3.0.0-beta.25",
+ "vue-apollo": "^3.0.0-beta.28",
"vue-loader": "^15.4.2",
"vue-resource": "^1.5.1",
"vue-router": "^3.0.2",
@@ -126,17 +128,13 @@
"xterm": "^3.5.0"
},
"devDependencies": {
+ "@babel/plugin-transform-modules-commonjs": "^7.2.0",
"@gitlab/eslint-config": "^1.4.0",
"@vue/test-utils": "^1.0.0-beta.25",
"axios-mock-adapter": "^1.15.0",
- "babel-core": "^7.0.0-bridge",
- "babel-jest": "^23.6.0",
- "babel-plugin-dynamic-import-node": "^2.2.0",
+ "babel-jest": "^24.1.0",
"babel-plugin-istanbul": "^5.1.0",
"babel-plugin-rewire": "^1.2.0",
- "babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
- "babel-template": "^6.26.0",
- "babel-types": "^6.26.0",
"chalk": "^2.4.1",
"commander": "^2.18.0",
"docdash": "^1.0.2",
@@ -146,7 +144,7 @@
"eslint-plugin-html": "5.0.0",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-jasmine": "^2.10.1",
- "eslint-plugin-jest": "^22.1.0",
+ "eslint-plugin-jest": "^22.3.0",
"gettext-extractor": "^3.3.2",
"gettext-extractor-vue": "^4.0.1",
"graphql-tag": "^2.10.0",
@@ -154,8 +152,8 @@
"jasmine-core": "^2.9.0",
"jasmine-diff": "^0.1.3",
"jasmine-jquery": "^2.1.1",
- "jest": "^23.6.0",
- "jest-junit": "^5.2.0",
+ "jest": "^24.1.0",
+ "jest-junit": "^6.3.0",
"jsdoc": "^3.5.5",
"jsdoc-vue": "^1.0.0",
"karma": "^3.0.0",
@@ -168,11 +166,18 @@
"karma-webpack": "^4.0.0-beta.0",
"nodemon": "^1.18.9",
"pixelmatch": "^4.0.2",
+ "postcss": "^7.0.14",
"prettier": "1.16.1",
- "vue-jest": "^3.0.2",
+ "stylelint": "^9.10.1",
+ "stylelint-config-recommended": "^2.1.0",
+ "stylelint-scss": "^3.5.3",
+ "vue-jest": "^4.0.0-beta.2",
"webpack-dev-server": "^3.1.14",
"yarn-deduplicate": "^1.1.0"
},
+ "resolutions": {
+ "vue-jest/ts-jest": "24.0.0"
+ },
"engines": {
"node": ">=8.10.0",
"yarn": "^1.10.0"
diff --git a/qa/Gemfile b/qa/Gemfile
index 873eac1013f..f29006617ed 100644
--- a/qa/Gemfile
+++ b/qa/Gemfile
@@ -8,3 +8,4 @@ gem 'rspec', '~> 3.7'
gem 'selenium-webdriver', '~> 3.12'
gem 'airborne', '~> 0.2.13'
gem 'nokogiri', '~> 1.10.1'
+gem 'rspec-retry', '~> 0.6.1'
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index 9f84bdc3828..c3d9f558c23 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -76,6 +76,8 @@ GEM
rspec-mocks (3.7.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.7.0)
+ rspec-retry (0.6.1)
+ rspec-core (> 3.3)
rspec-support (3.7.0)
rubyzip (1.2.2)
selenium-webdriver (3.141.0)
@@ -101,6 +103,7 @@ DEPENDENCIES
pry-byebug (~> 3.5.1)
rake (~> 12.3.0)
rspec (~> 3.7)
+ rspec-retry (~> 0.6.1)
selenium-webdriver (~> 3.12)
BUNDLED WITH
diff --git a/qa/README.md b/qa/README.md
index 5e32496ea9f..735868e7640 100644
--- a/qa/README.md
+++ b/qa/README.md
@@ -1,21 +1,25 @@
-# GitLab QA - Integration tests for GitLab
+# GitLab QA - End-to-end tests for GitLab
-This directory contains integration tests for GitLab.
+This directory contains [end-to-end tests](doc/development/testing_guide/end_to_end_tests.md)
+for GitLab. It includes the test framework and the tests themselves.
+
+The tests can be found in `qa/specs/features` (not to be confused with the unit
+tests for the test framework, which are in `spec/`).
It is part of the [GitLab QA project](https://gitlab.com/gitlab-org/gitlab-qa).
## What is it?
-GitLab QA is an integration tests suite for GitLab.
+GitLab QA is an end-to-end tests suite for GitLab.
-These are black-box and entirely click-driven integration tests you can run
+These are black-box and entirely click-driven end-to-end tests you can run
against any existing instance.
## How does it work?
1. When we release a new version of GitLab, we build a Docker images for it.
1. Along with GitLab Docker Images we also build and publish GitLab QA images.
-1. GitLab QA project uses these images to execute integration tests.
+1. GitLab QA project uses these images to execute end-to-end tests.
## Validating GitLab views / partials / selectors in merge requests
@@ -38,6 +42,9 @@ following call would login to a local [GDK] instance and run all specs in
bin/qa Test::Instance::All http://localhost:3000
```
+Note: If you want to run tests requiring SSH against GDK, you
+will need to [modify your GDK setup](https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/run_qa_against_gdk.md).
+
### Writing tests
1. [Using page objects](qa/page/README.md)
diff --git a/qa/qa.rb b/qa/qa.rb
index 8c5411d3f6b..2b3ffabbbaa 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -260,6 +260,10 @@ module QA
autoload :Sidebar, 'qa/page/issuable/sidebar'
end
+ module Alert
+ autoload :AutoDevopsAlert, 'qa/page/alert/auto_devops_alert'
+ end
+
module Layout
autoload :Banner, 'qa/page/layout/banner'
end
@@ -367,6 +371,7 @@ module QA
end
autoload :Api, 'qa/support/api'
autoload :Waiter, 'qa/support/waiter'
+ autoload :Retrier, 'qa/support/retrier'
end
end
diff --git a/qa/qa/ce/strategy.rb b/qa/qa/ce/strategy.rb
index 6d1601dfa48..e0fbbed2567 100644
--- a/qa/qa/ce/strategy.rb
+++ b/qa/qa/ce/strategy.rb
@@ -8,7 +8,10 @@ module QA
end
def perform_before_hooks
- # noop
+ # The login page could take some time to load the first time it is visited.
+ # We visit the login page and wait for it to properly load only once before the tests.
+ QA::Runtime::Browser.visit(:gitlab, QA::Page::Main::Login)
+ QA::Page::Main::Login.perform(&:assert_page_loaded)
end
end
end
diff --git a/qa/qa/git/repository.rb b/qa/qa/git/repository.rb
index 0aa94101098..b3bad40a90f 100644
--- a/qa/qa/git/repository.rb
+++ b/qa/qa/git/repository.rb
@@ -12,6 +12,7 @@ module QA
module Git
class Repository
include Scenario::Actable
+ RepositoryCommandError = Class.new(StandardError)
attr_writer :use_lfs
attr_accessor :env_vars
@@ -205,6 +206,10 @@ module QA
output.chomp!
Runtime::Logger.debug "Git: output=[#{output}], exitstatus=[#{status.exitstatus}]"
+ unless status.success?
+ raise RepositoryCommandError, "The command #{command} failed (#{status.exitstatus}) with the following output:\n#{output}"
+ end
+
Result.new(status.exitstatus == 0, output)
end
diff --git a/qa/qa/page/alert/auto_devops_alert.rb b/qa/qa/page/alert/auto_devops_alert.rb
new file mode 100644
index 00000000000..8f66c805b77
--- /dev/null
+++ b/qa/qa/page/alert/auto_devops_alert.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Alert
+ class AutoDevopsAlert < Page::Base
+ view 'app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml' do
+ element :auto_devops_banner
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb
index 01ac161d26d..11ebd70292e 100644
--- a/qa/qa/page/base.rb
+++ b/qa/qa/page/base.rb
@@ -39,18 +39,9 @@ module QA
false
end
- def retry_on_exception(max_attempts: 3, reload: false, sleep_interval: 0.0)
- attempts = 0
-
- begin
+ def retry_on_exception(max_attempts: 3, reload: false, sleep_interval: 0.5)
+ QA::Support::Retrier.retry_on_exception(max_attempts: max_attempts, reload_page: (reload && self), sleep_interval: sleep_interval) do
yield
- rescue StandardError
- sleep sleep_interval
- refresh if reload
- attempts += 1
-
- retry if attempts < max_attempts
- raise
end
end
@@ -86,8 +77,8 @@ module QA
page.evaluate_script('xhr.status') == 200
end
- def find_element(name, text_filter = nil, wait: Capybara.default_max_wait_time)
- find(element_selector_css(name), wait: wait, text: text_filter)
+ def find_element(name, text: nil, wait: Capybara.default_max_wait_time)
+ find(element_selector_css(name), wait: wait, text: text)
end
def all_elements(name)
@@ -118,8 +109,8 @@ module QA
element.select value.to_s.capitalize
end
- def has_element?(name, wait: Capybara.default_max_wait_time)
- has_css?(element_selector_css(name), wait: wait)
+ def has_element?(name, text: nil, wait: Capybara.default_max_wait_time)
+ has_css?(element_selector_css(name), wait: wait, text: text)
end
def has_no_element?(name, wait: Capybara.default_max_wait_time)
@@ -162,6 +153,10 @@ module QA
click_link text
end
+ def click_body
+ find('body').click
+ end
+
def self.path
raise NotImplementedError
end
diff --git a/qa/qa/page/group/show.rb b/qa/qa/page/group/show.rb
index 9d6bd338027..41716326685 100644
--- a/qa/qa/page/group/show.rb
+++ b/qa/qa/page/group/show.rb
@@ -45,7 +45,7 @@ module QA
private
def select_kind(kind)
- retry_on_exception(sleep_interval: 1.0) do
+ QA::Support::Retrier.retry_on_exception(sleep_interval: 1.0) do
within_element(:new_project_or_subgroup_dropdown) do
# May need to click again because it is possible to click the button quicker than the JS is bound
wait(reload: false) do
diff --git a/qa/qa/page/label/index.rb b/qa/qa/page/label/index.rb
index f0d323ca3b4..de0cfa9f293 100644
--- a/qa/qa/page/label/index.rb
+++ b/qa/qa/page/label/index.rb
@@ -14,6 +14,10 @@ module QA
element :label_svg
end
+ view 'app/views/shared/empty_states/_priority_labels.html.haml' do
+ element :label_svg
+ end
+
def go_to_new_label
# The 'labels.svg' takes a fraction of a second to load after which the "New label" button shifts up a bit
# This can cause webdriver to miss the hit so we wait for the svg to load (implicitly with has_element?)
diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb
index e476cbe29a2..e03fe9ab83a 100644
--- a/qa/qa/page/main/login.rb
+++ b/qa/qa/page/main/login.rb
@@ -40,6 +40,12 @@ module QA
element :login_page
end
+ def assert_page_loaded
+ unless page_loaded?
+ raise QA::Runtime::Browser::NotRespondingError, "Login page did not load at #{QA::Page::Main::Login.perform(&:current_url)}"
+ end
+ end
+
def page_loaded?
wait(max: 60) do
has_element?(:login_page)
diff --git a/qa/qa/page/project/import/github.rb b/qa/qa/page/project/import/github.rb
index a3cde73d3f2..45c8d834a74 100644
--- a/qa/qa/page/project/import/github.rb
+++ b/qa/qa/page/project/import/github.rb
@@ -10,12 +10,11 @@ module QA
element :list_repos_button, "submit_tag _('List your GitHub repositories')" # rubocop:disable QA/ElementWithPattern
end
- view 'app/views/import/_githubish_status.html.haml' do
- element :project_import_row, 'data: { qa: { repo_path: repo.full_name } }' # rubocop:disable QA/ElementWithPattern
+ view 'app/assets/javascripts/import_projects/components/provider_repo_table_row.vue' do
+ element :project_import_row
element :project_namespace_select
- element :project_namespace_field, 'select_tag :namespace_id' # rubocop:disable QA/ElementWithPattern
- element :project_path_field, 'text_field_tag :path, sanitize_project_name(repo.name)' # rubocop:disable QA/ElementWithPattern
- element :import_button, "_('Import')" # rubocop:disable QA/ElementWithPattern
+ element :project_path_field
+ element :import_button
end
def add_personal_access_token(personal_access_token)
@@ -30,12 +29,19 @@ module QA
choose_test_namespace(full_path)
set_path(full_path, name)
import_project(full_path)
+ wait_for_success
end
private
def within_repo_path(full_path)
- page.within(%Q(tr[data-qa-repo-path="#{full_path}"])) do
+ wait(reload: false) do
+ has_element?(:project_import_row, text: full_path)
+ end
+
+ project_import_row = find_element(:project_import_row, text: full_path)
+
+ within(project_import_row) do
yield
end
end
@@ -45,18 +51,24 @@ module QA
click_element :project_namespace_select
end
- select_item(Runtime::Namespace.path)
+ search_and_select(Runtime::Namespace.path)
end
def set_path(full_path, name)
within_repo_path(full_path) do
- fill_in 'path', with: name
+ fill_element(:project_path_field, name)
end
end
def import_project(full_path)
within_repo_path(full_path) do
- click_button 'Import'
+ click_element(:import_button)
+ end
+ end
+
+ def wait_for_success
+ wait(max: 60, interval: 1.0, reload: false) do
+ page.has_content?('Done', wait: 1.0)
end
end
end
diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb
index 1028cc045a0..9df3db1bba0 100644
--- a/qa/qa/page/project/issue/show.rb
+++ b/qa/qa/page/project/issue/show.rb
@@ -23,6 +23,10 @@ module QA
element :filter_options
end
+ view 'app/assets/javascripts/notes/components/noteable_note.vue' do
+ element :noteable_note_item
+ end
+
# Adds a comment to an issue
# attachment option should be an absolute path
def comment(text, attachment: nil)
@@ -36,19 +40,32 @@ module QA
click_element :comment_button
end
+ def has_comment?(comment_text)
+ wait(reload: false) do
+ has_element?(:noteable_note_item, text: comment_text)
+ end
+ end
+
def select_comments_only_filter
- click_element :discussion_filter
- find_element(:filter_options, "Show comments only").click
+ select_filter_with_text('Show comments only')
end
def select_history_only_filter
- click_element :discussion_filter
- find_element(:filter_options, "Show history only").click
+ select_filter_with_text('Show history only')
end
def select_all_activities_filter
- click_element :discussion_filter
- find_element(:filter_options, "Show all activity").click
+ select_filter_with_text('Show all activity')
+ end
+
+ private
+
+ def select_filter_with_text(text)
+ retry_on_exception do
+ click_body
+ click_element :discussion_filter
+ find_element(:filter_options, text: text).click
+ end
end
end
end
diff --git a/qa/qa/page/project/job/show.rb b/qa/qa/page/project/job/show.rb
index 49c676c01f2..9c218f4ed8b 100644
--- a/qa/qa/page/project/job/show.rb
+++ b/qa/qa/page/project/job/show.rb
@@ -4,10 +4,6 @@ module QA::Page
COMPLETED_STATUSES = %w[passed failed canceled blocked skipped manual].freeze # excludes created, pending, running
PASSED_STATUS = 'passed'.freeze
- view 'app/assets/javascripts/jobs/components/job_app.vue' do
- element :loading_animation
- end
-
view 'app/assets/javascripts/jobs/components/job_log.vue' do
element :build_trace
end
@@ -20,22 +16,13 @@ module QA::Page
element :pipeline_path
end
- def completed?
- COMPLETED_STATUSES.include?(status_badge)
- end
-
def successful?(timeout: 60)
- wait(reload: false, max: timeout) do
- completed? && !trace_loading?
- end
+ raise "Timed out waiting for the build trace to load" unless loaded?
+ raise "Timed out waiting for the status to be a valid completed state" unless completed?(timeout: timeout)
status_badge == PASSED_STATUS
end
- def trace_loading?
- has_element?(:loading_animation)
- end
-
# Reminder: You may wish to wait for a particular job status before checking output
def output
find_element(:build_trace).text
@@ -43,6 +30,16 @@ module QA::Page
private
+ def loaded?(wait: 60)
+ has_element?(:build_trace, wait: wait)
+ end
+
+ def completed?(timeout: 60)
+ wait(reload: false, max: timeout) do
+ COMPLETED_STATUSES.include?(status_badge)
+ end
+ end
+
def status_badge
find_element(:status_badge).text
end
diff --git a/qa/qa/page/project/operations/kubernetes/show.rb b/qa/qa/page/project/operations/kubernetes/show.rb
index 98ac5c32d91..d4e1679b6bf 100644
--- a/qa/qa/page/project/operations/kubernetes/show.rb
+++ b/qa/qa/page/project/operations/kubernetes/show.rb
@@ -14,6 +14,11 @@ module QA
element :ingress_ip_address, 'id="ingress-ip-address"' # rubocop:disable QA/ElementWithPattern
end
+ view 'app/views/clusters/clusters/_form.html.haml' do
+ element :base_domain
+ element :save_domain
+ end
+
def install!(application_name)
within(".js-cluster-application-row-#{application_name}") do
page.has_button?('Install', wait: 30)
@@ -32,6 +37,14 @@ module QA
# ip address is assigned for the ingress controller
page.find('#ingress-ip-address', wait: 1200).value
end
+
+ def set_domain(domain)
+ fill_element :base_domain, domain
+ end
+
+ def save_domain
+ click_element :save_domain
+ end
end
end
end
diff --git a/qa/qa/page/project/pipeline/show.rb b/qa/qa/page/project/pipeline/show.rb
index f192f1fc64b..6f8a66bf527 100644
--- a/qa/qa/page/project/pipeline/show.rb
+++ b/qa/qa/page/project/pipeline/show.rb
@@ -18,6 +18,10 @@ module QA::Page
element :status_icon, 'ci-status-icon-${status}' # rubocop:disable QA/ElementWithPattern
end
+ view 'app/views/projects/pipelines/_info.html.haml' do
+ element :pipeline_badges
+ end
+
def running?
within('.ci-header-container') do
page.has_content?('running')
@@ -32,8 +36,14 @@ module QA::Page
end
end
+ def has_tag?(tag_name)
+ within_element(:pipeline_badges) do
+ has_selector?('.badge', text: tag_name)
+ end
+ end
+
def go_to_job(job_name)
- find_element(:job_link, job_name).click
+ find_element(:job_link, text: job_name).click
end
def go_to_first_job
diff --git a/qa/qa/page/project/settings/deploy_keys.rb b/qa/qa/page/project/settings/deploy_keys.rb
index 5da8d352e74..e0f9e84096d 100644
--- a/qa/qa/page/project/settings/deploy_keys.rb
+++ b/qa/qa/page/project/settings/deploy_keys.rb
@@ -33,14 +33,14 @@ module QA
def find_fingerprint(title)
within_project_deploy_keys do
- find_element(:key, title)
+ find_element(:key, text: title)
.find(element_selector_css(:key_fingerprint)).text
end
end
def has_key?(title, fingerprint)
within_project_deploy_keys do
- find_element(:key, title)
+ find_element(:key, text: title)
.has_css?(element_selector_css(:key_fingerprint), text: fingerprint)
end
end
diff --git a/qa/qa/resource/kubernetes_cluster.rb b/qa/qa/resource/kubernetes_cluster.rb
index 986b31da528..93a06be6818 100644
--- a/qa/qa/resource/kubernetes_cluster.rb
+++ b/qa/qa/resource/kubernetes_cluster.rb
@@ -12,10 +12,6 @@ module QA
Page::Project::Operations::Kubernetes::Show.perform(&:ingress_ip)
end
- attribute :domain do
- "#{ingress_ip}.nip.io"
- end
-
def fabricate!
@project.visit!
@@ -53,6 +49,12 @@ module QA
page.await_installed(:ingress) if @install_ingress
page.await_installed(:prometheus) if @install_prometheus
page.await_installed(:runner) if @install_runner
+
+ if @install_ingress
+ populate(:ingress_ip)
+ page.set_domain("#{ingress_ip}.nip.io")
+ page.save_domain
+ end
end
end
end
diff --git a/qa/qa/resource/project_imported_from_github.rb b/qa/qa/resource/project_imported_from_github.rb
index 3f02fe885a9..0d25e7dd842 100644
--- a/qa/qa/resource/project_imported_from_github.rb
+++ b/qa/qa/resource/project_imported_from_github.rb
@@ -4,7 +4,7 @@ require 'securerandom'
module QA
module Resource
- class ProjectImportedFromGithub < Project
+ class ProjectImportedFromGithub < Base
attr_accessor :name
attr_writer :personal_access_token, :github_repository_path
diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb
index 0bcf5e693f0..0b805b855ac 100644
--- a/qa/qa/runtime/browser.rb
+++ b/qa/qa/runtime/browser.rb
@@ -8,6 +8,8 @@ module QA
class Browser
include QA::Scenario::Actable
+ NotRespondingError = Class.new(RuntimeError)
+
def initialize
self.class.configure!
end
diff --git a/qa/qa/runtime/namespace.rb b/qa/qa/runtime/namespace.rb
index 704c65467e0..9d7c1aea508 100644
--- a/qa/qa/runtime/namespace.rb
+++ b/qa/qa/runtime/namespace.rb
@@ -8,7 +8,9 @@ module QA
end
def name
- Runtime::Env.namespace_name || "qa-test-#{time.strftime('%Y-%m-%d-%H-%M-%S')}"
+ # If any changes are made to the name tag, following script has to be considered:
+ # https://ops.gitlab.net/gitlab-com/gl-infra/traffic-generator/blob/master/bin/janitor.bash
+ @name ||= Runtime::Env.namespace_name || "qa-test-#{time.strftime('%Y-%m-%d-%H-%M-%S')}-#{SecureRandom.hex(8)}"
end
def path
diff --git a/qa/qa/service/kubernetes_cluster.rb b/qa/qa/service/kubernetes_cluster.rb
index c5f12255d72..41ab702d8b2 100644
--- a/qa/qa/service/kubernetes_cluster.rb
+++ b/qa/qa/service/kubernetes_cluster.rb
@@ -9,7 +9,7 @@ module QA
attr_reader :api_url, :ca_certificate, :token, :rbac
- def initialize(rbac: false)
+ def initialize(rbac: true)
@rbac = rbac
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
index 2fb8402edd8..6632c2977ef 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
@@ -1,8 +1,7 @@
# frozen_string_literal: true
module QA
- # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/72
- context 'Manage', :smoke, :quarantine do
+ context 'Manage', :smoke do
describe 'Project creation' do
it 'user creates a new project' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb
index 3ce48de2c25..a9eafd61a91 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
module QA
- context 'Manage', :orchestrated, :github do
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/58158
+ context 'Manage', :github, :quarantine do
describe 'Project import from GitHub' do
let(:imported_project) do
Resource::ProjectImportedFromGithub.fabricate! do |project|
@@ -48,20 +49,26 @@ module QA
end
def verify_issues_import
- Page::Project::Menu.act { click_issues }
- expect(page).to have_content('This is a sample issue')
+ QA::Support::Retrier.retry_on_exception do
+ Page::Project::Menu.act { click_issues }
+ expect(page).to have_content('This is a sample issue')
- click_link 'This is a sample issue'
+ click_link 'This is a sample issue'
- expect(page).to have_content('We should populate this project with issues, pull requests and wiki pages.')
+ expect(page).to have_content('We should populate this project with issues, pull requests and wiki pages.')
- # Comments
- expect(page).to have_content('This is a comment from @rymai.')
+ # Comments
+ comment_text = 'This is a comment from @rymai.'
- Page::Issuable::Sidebar.perform do |issuable|
- expect(issuable).to have_label('enhancement')
- expect(issuable).to have_label('help wanted')
- expect(issuable).to have_label('good first issue')
+ Page::Project::Issue::Show.perform do |issue_page|
+ expect(issue_page).to have_comment(comment_text)
+ end
+
+ Page::Issuable::Sidebar.perform do |issuable|
+ expect(issuable).to have_label('enhancement')
+ expect(issuable).to have_label('help wanted')
+ expect(issuable).to have_label('good first issue')
+ end
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb
index 60836a7b98d..f146636c49a 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb
@@ -2,7 +2,7 @@
module QA
# Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/31
- context 'Create', :quarantine do
+ context 'Create' do
describe 'Merge request squashing' do
it 'user squashes commits while merging' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb
index 2e4915b898d..99601e3d230 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ # https://gitlab.com/gitlab-org/quality/staging/issues/40
+ context 'Create', :quarantine do
describe 'Push mirror a repository over HTTP' do
it 'configures and syncs a (push) mirrored repository' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb
index 7d96da32423..243f0b83b77 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/37
+ context 'Create', :quarantine do
describe 'push after setting the file size limit via admin/application_settings' do
before(:all) do
push = Resource::Repository::ProjectPush.fabricate! do |p|
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 4464fb812b7..6aebd04af03 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
@@ -37,12 +37,7 @@ module QA
it 'user without push rights fails to push to the protected branch' do
create_protected_branch(allow_to_push: false)
- push = push_new_file(branch_name)
-
- expect(push.output)
- .to match(/remote\: GitLab\: You are not allowed to push code to protected branches on this project/)
- expect(push.output)
- .to match(/\[remote rejected\] #{branch_name} -> #{branch_name} \(pre-receive hook declined\)/)
+ expect { push_new_file(branch_name) }.to raise_error(QA::Git::Repository::RepositoryCommandError, /remote: GitLab: You are not allowed to push code to protected branches on this project\.([\s\S]+)\[remote rejected\] #{branch_name} -> #{branch_name} \(pre-receive hook declined\)/)
end
end
diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
index 2375aa4ce91..3f65eabc756 100644
--- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
@@ -95,7 +95,7 @@ module QA
Page::Project::Pipeline::Show.perform(&:go_to_first_job)
Page::Project::Job::Show.perform do |job|
- expect(job).to be_successful, "Job status did not become \"passed\"."
+ expect(job).to be_successful
expect(job.output).to include(sha1sum)
end
end
diff --git a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
index 8cd353fa250..2aa386f35ce 100644
--- a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
+++ b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
@@ -4,175 +4,190 @@ require 'pathname'
module QA
# Transient failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/68
- context 'Configure', :orchestrated, :kubernetes, :quarantine do
- describe 'Auto DevOps support' do
- def login
- Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.perform(&:sign_in_using_credentials)
- end
+ context 'Configure' do
+ def login
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+ end
- [true, false].each do |rbac|
- context "when rbac is #{rbac ? 'enabled' : 'disabled'}" do
- before(:all) do
- login
+ describe 'Auto DevOps support', :orchestrated, :kubernetes, :quarantine do
+ context 'when rbac is enabled' do
+ before(:all) do
+ login
- @project = Resource::Project.fabricate! do |p|
- p.name = Runtime::Env.auto_devops_project_name || 'project-with-autodevops'
- p.description = 'Project with Auto DevOps'
- end
+ @project = Resource::Project.fabricate! do |p|
+ p.name = Runtime::Env.auto_devops_project_name || 'project-with-autodevops'
+ p.description = 'Project with Auto DevOps'
+ end
- # Disable code_quality check in Auto DevOps pipeline as it takes
- # too long and times out the test
- Resource::CiVariable.fabricate! do |resource|
- resource.project = @project
- resource.key = 'CODE_QUALITY_DISABLED'
- resource.value = '1'
- end
+ # Disable code_quality check in Auto DevOps pipeline as it takes
+ # too long and times out the test
+ Resource::CiVariable.fabricate! do |resource|
+ resource.project = @project
+ resource.key = 'CODE_QUALITY_DISABLED'
+ resource.value = '1'
+ end
- # Create Auto DevOps compatible repo
- Resource::Repository::ProjectPush.fabricate! do |push|
- push.project = @project
- push.directory = Pathname
- .new(__dir__)
- .join('../../../../../fixtures/auto_devops_rack')
- push.commit_message = 'Create Auto DevOps compatible rack application'
- end
+ # Create Auto DevOps compatible repo
+ Resource::Repository::ProjectPush.fabricate! do |push|
+ push.project = @project
+ push.directory = Pathname
+ .new(__dir__)
+ .join('../../../../../fixtures/auto_devops_rack')
+ push.commit_message = 'Create Auto DevOps compatible rack application'
+ end
- # Create and connect K8s cluster
- @cluster = Service::KubernetesCluster.new(rbac: rbac).create!
- kubernetes_cluster = Resource::KubernetesCluster.fabricate! do |cluster|
- cluster.project = @project
- cluster.cluster = @cluster
- cluster.install_helm_tiller = true
- cluster.install_ingress = true
- cluster.install_prometheus = true
- cluster.install_runner = true
- end
+ # Create and connect K8s cluster
+ @cluster = Service::KubernetesCluster.new.create!
+ Resource::KubernetesCluster.fabricate! do |cluster|
+ cluster.project = @project
+ cluster.cluster = @cluster
+ cluster.install_helm_tiller = true
+ cluster.install_ingress = true
+ cluster.install_prometheus = true
+ cluster.install_runner = true
+ end
+ end
- kubernetes_cluster.populate(:ingress_ip)
- @project.visit!
- Page::Project::Menu.perform(&:click_ci_cd_settings)
- Page::Project::Settings::CICD.perform do |p|
- p.enable_auto_devops
- end
+ after(:all) do
+ @cluster&.remove!
+ end
+
+ it 'runs auto devops' do
+ @project.visit!
+ Page::Project::Menu.perform(&:click_ci_cd_pipelines)
+ Page::Project::Pipeline::Index.perform(&:go_to_latest_pipeline)
- kubernetes_cluster.populate(:domain)
+ Page::Project::Pipeline::Show.perform do |pipeline|
+ pipeline.go_to_job('build')
end
+ Page::Project::Job::Show.perform do |job|
+ expect(job).to be_successful(timeout: 600)
- after(:all) do
- @cluster&.remove!
+ job.click_element(:pipeline_path)
end
- before do
- login
+ Page::Project::Pipeline::Show.perform do |pipeline|
+ pipeline.go_to_job('test')
end
+ Page::Project::Job::Show.perform do |job|
+ expect(job).to be_successful(timeout: 600)
- it 'runs auto devops' do
- @project.visit!
- Page::Project::Menu.perform(&:click_ci_cd_pipelines)
- Page::Project::Pipeline::Index.perform(&:go_to_latest_pipeline)
+ job.click_element(:pipeline_path)
+ end
- Page::Project::Pipeline::Show.perform do |pipeline|
- pipeline.go_to_job('build')
- end
- Page::Project::Job::Show.perform do |job|
- expect(job).to be_sucessful(timeout: 600), "Job did not pass"
+ Page::Project::Pipeline::Show.perform do |pipeline|
+ pipeline.go_to_job('production')
+ end
+ Page::Project::Job::Show.perform do |job|
+ expect(job).to be_successful(timeout: 1200)
- job.click_element(:pipeline_path)
- end
+ job.click_element(:pipeline_path)
+ end
- Page::Project::Pipeline::Show.perform do |pipeline|
- pipeline.go_to_job('test')
+ Page::Project::Menu.perform(&:click_operations_environments)
+ Page::Project::Operations::Environments::Index.perform do |index|
+ index.go_to_environment('production')
+ end
+ Page::Project::Operations::Environments::Show.perform do |show|
+ show.view_deployment do
+ expect(page).to have_content('Hello World!')
end
- Page::Project::Job::Show.perform do |job|
- expect(job).to be_sucessful(timeout: 600), "Job did not pass"
+ end
+ end
- job.click_element(:pipeline_path)
- end
+ it 'user sets application secret variable and Auto DevOps passes it to container' do
+ # Set an application secret CI variable (prefixed with K8S_SECRET_)
+ Resource::CiVariable.fabricate! do |resource|
+ resource.project = @project
+ resource.key = 'K8S_SECRET_OPTIONAL_MESSAGE'
+ resource.value = 'You can see this application secret'
+ end
- Page::Project::Pipeline::Show.perform do |pipeline|
- pipeline.go_to_job('production')
- end
- Page::Project::Job::Show.perform do |job|
- expect(job).to be_sucessful(timeout: 1200), "Job did not pass"
+ # Our current Auto DevOps implementation won't update the production
+ # app if we only update a CI variable with no code change.
+ #
+ # Workaround: push new code and use the resultant pipeline.
+ Resource::Repository::ProjectPush.fabricate! do |push|
+ push.project = @project
+ push.commit_message = 'Force a Deployment change by pushing new code'
+ push.file_name = 'new_file.txt'
+ push.file_content = 'new file contents'
+ end
- job.click_element(:pipeline_path)
- end
+ Page::Project::Menu.perform(&:click_ci_cd_pipelines)
+ Page::Project::Pipeline::Index.perform(&:go_to_latest_pipeline)
- Page::Project::Menu.perform(&:click_operations_environments)
- Page::Project::Operations::Environments::Index.perform do |index|
- index.go_to_environment('production')
- end
- Page::Project::Operations::Environments::Show.perform do |show|
- show.view_deployment do
- expect(page).to have_content('Hello World!')
- end
- end
+ Page::Project::Pipeline::Show.perform do |pipeline|
+ pipeline.go_to_job('build')
end
+ Page::Project::Job::Show.perform do |job|
+ expect(job).to be_successful(timeout: 600)
- it 'user sets application secret variable and Auto DevOps passes it to container' do
- # Set an application secret CI variable (prefixed with K8S_SECRET_)
- Resource::CiVariable.fabricate! do |resource|
- resource.project = @project
- resource.key = 'K8S_SECRET_OPTIONAL_MESSAGE'
- resource.value = 'You can see this application secret'
- end
+ job.click_element(:pipeline_path)
+ end
- # Our current Auto DevOps implementation won't update the production
- # app if we only update a CI variable with no code change.
- #
- # Workaround: push new code and use the resultant pipeline.
- Resource::Repository::ProjectPush.fabricate! do |push|
- push.project = @project
- push.commit_message = 'Force a Deployment change by pushing new code'
- push.file_name = 'new_file.txt'
- push.file_content = 'new file contents'
- end
+ Page::Project::Pipeline::Show.perform do |pipeline|
+ pipeline.go_to_job('test')
+ end
+ Page::Project::Job::Show.perform do |job|
+ expect(job).to be_successful(timeout: 600)
- @project.visit!
- Page::Project::Menu.perform(&:click_ci_cd_pipelines)
- Page::Project::Pipeline::Index.perform(&:go_to_latest_pipeline)
+ job.click_element(:pipeline_path)
+ end
- Page::Project::Pipeline::Show.perform do |pipeline|
- pipeline.go_to_job('build')
- end
- Page::Project::Job::Show.perform do |job|
- expect(job).to be_sucessful(timeout: 600), "Job did not pass"
+ Page::Project::Pipeline::Show.perform do |pipeline|
+ pipeline.go_to_job('production')
+ end
+ Page::Project::Job::Show.perform do |job|
+ expect(job).to be_successful(timeout: 1200)
+ end
- job.click_element(:pipeline_path)
- end
+ Page::Project::Menu.perform(&:click_operations_environments)
- Page::Project::Pipeline::Show.perform do |pipeline|
- pipeline.go_to_job('test')
- end
- Page::Project::Job::Show.perform do |job|
- expect(job).to be_sucessful(timeout: 600), "Job did not pass"
+ Page::Project::Operations::Environments::Index.perform do |index|
+ index.go_to_environment('production')
+ end
- job.click_element(:pipeline_path)
+ Page::Project::Operations::Environments::Show.perform do |show|
+ show.view_deployment do
+ expect(page).to have_content('Hello World!')
+ expect(page).to have_content('You can see this application secret')
end
+ end
+ end
+ end
+ end
- Page::Project::Pipeline::Show.perform do |pipeline|
- pipeline.go_to_job('production')
- end
- Page::Project::Job::Show.perform do |job|
- expect(job).to be_sucessful(timeout: 1200), "Job did not pass"
+ describe 'Auto DevOps', :smoke do
+ it 'enables AutoDevOps by default' do
+ login
- job.click_element(:pipeline_path)
- end
+ project = Resource::Project.fabricate! do |p|
+ p.name = Runtime::Env.auto_devops_project_name || 'project-with-autodevops'
+ p.description = 'Project with AutoDevOps'
+ end
- Page::Project::Menu.perform(&:click_operations_environments)
+ project.visit!
- Page::Project::Operations::Environments::Index.perform do |index|
- index.go_to_environment('production')
- end
+ Page::Alert::AutoDevopsAlert.perform do |alert|
+ expect(alert).to have_text(/.*The Auto DevOps pipeline has been enabled.*/)
+ end
- Page::Project::Operations::Environments::Show.perform do |show|
- show.view_deployment do
- expect(page).to have_content('Hello World!')
- expect(page).to have_content('You can see this application secret')
- end
- end
- end
+ # Create AutoDevOps repo
+ Resource::Repository::ProjectPush.fabricate! do |push|
+ push.project = project
+ push.directory = Pathname
+ .new(__dir__)
+ .join('../../../../../fixtures/auto_devops_rack')
+ push.commit_message = 'Create AutoDevOps compatible Project'
+ end
+
+ Page::Project::Menu.perform(&:click_ci_cd_pipelines)
+ Page::Project::Pipeline::Index.perform(&:go_to_latest_pipeline)
+
+ Page::Project::Pipeline::Show.perform do |pipeline|
+ expect(pipeline).to have_tag('Auto DevOps')
end
end
end
diff --git a/qa/qa/support/page/logging.rb b/qa/qa/support/page/logging.rb
index 5e97a92a6e3..69b6332ecce 100644
--- a/qa/qa/support/page/logging.rb
+++ b/qa/qa/support/page/logging.rb
@@ -33,9 +33,9 @@ module QA
exists
end
- def find_element(name, text_filter = nil, wait: Capybara.default_max_wait_time)
+ def find_element(name, text: nil, wait: Capybara.default_max_wait_time)
msg = ["finding :#{name}"]
- msg << %Q(with text_filter "#{text_filter}") if text_filter
+ msg << %Q(with text "#{text}") if text
msg << "(wait: #{wait})"
log(msg.compact.join(' '))
@@ -76,10 +76,15 @@ module QA
super
end
- def has_element?(name, wait: Capybara.default_max_wait_time)
+ def has_element?(name, text: nil, wait: Capybara.default_max_wait_time)
found = super
- log("has_element? :#{name} returned #{found}")
+ msg = ["has_element? :#{name}"]
+ msg << %Q(with text "#{text}") if text
+ msg << "(wait: #{wait})"
+ msg << "returned: #{found}"
+
+ log(msg.compact.join(' '))
found
end
diff --git a/qa/qa/support/retrier.rb b/qa/qa/support/retrier.rb
new file mode 100644
index 00000000000..8be4e9f5365
--- /dev/null
+++ b/qa/qa/support/retrier.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module QA
+ module Support
+ module Retrier
+ module_function
+
+ def retry_on_exception(max_attempts: 3, reload_page: nil, sleep_interval: 0.5)
+ QA::Runtime::Logger.debug("with retry_on_exception: max_attempts #{max_attempts}; sleep_interval #{sleep_interval}")
+
+ attempts = 0
+
+ begin
+ QA::Runtime::Logger.debug("Attempt number #{attempts + 1}")
+ yield
+ rescue StandardError, RSpec::Expectations::ExpectationNotMetError
+ sleep sleep_interval
+ reload_page.refresh if reload_page
+ attempts += 1
+
+ retry if attempts < max_attempts
+ QA::Runtime::Logger.debug("Raising exception after #{max_attempts} attempts")
+ raise
+ end
+ end
+ end
+ end
+end
diff --git a/qa/spec/git/repository_spec.rb b/qa/spec/git/repository_spec.rb
index 62c81050bd9..0ded33a73a2 100644
--- a/qa/spec/git/repository_spec.rb
+++ b/qa/spec/git/repository_spec.rb
@@ -39,7 +39,7 @@ describe QA::Git::Repository do
describe '#clone' do
it 'is unable to resolve host' do
- expect(repository.clone).to include("fatal: unable to access 'http://root@foo/bar.git/'")
+ expect { repository.clone }.to raise_error(described_class::RepositoryCommandError, /The command .* failed \(128\) with the following output/)
end
end
@@ -49,7 +49,7 @@ describe QA::Git::Repository do
end
it 'fails to push changes' do
- expect(repository.push_changes).to include("error: failed to push some refs to 'http://root@foo/bar.git'")
+ expect { repository.push_changes }.to raise_error(described_class::RepositoryCommandError, /The command .* failed \(1\) with the following output/)
end
end
diff --git a/qa/spec/page/logging_spec.rb b/qa/spec/page/logging_spec.rb
index a6e9601cee4..707a7ff6d98 100644
--- a/qa/spec/page/logging_spec.rb
+++ b/qa/spec/page/logging_spec.rb
@@ -62,10 +62,10 @@ describe QA::Support::Page::Logging do
.to output(/found :element/).to_stdout_from_any_process
end
- it 'logs find_element with text_filter' do
- expect { subject.find_element(:element, 'foo') }
- .to output(/finding :element with text_filter "foo"/).to_stdout_from_any_process
- expect { subject.find_element(:element, 'foo') }
+ it 'logs find_element with text' do
+ expect { subject.find_element(:element, text: 'foo') }
+ .to output(/finding :element with text "foo"/).to_stdout_from_any_process
+ expect { subject.find_element(:element, text: 'foo') }
.to output(/found :element/).to_stdout_from_any_process
end
@@ -81,7 +81,12 @@ describe QA::Support::Page::Logging do
it 'logs has_element?' do
expect { subject.has_element?(:element) }
- .to output(/has_element\? :element returned true/).to_stdout_from_any_process
+ .to output(/has_element\? :element \(wait: 2\) returned: true/).to_stdout_from_any_process
+ end
+
+ it 'logs has_element? with text' do
+ expect { subject.has_element?(:element, text: "some text") }
+ .to output(/has_element\? :element with text \"some text\" \(wait: 2\) returned: true/).to_stdout_from_any_process
end
it 'logs has_no_element?' do
diff --git a/qa/spec/spec_helper.rb b/qa/spec/spec_helper.rb
index 3e7a6dd26ee..cbdd6e881b1 100644
--- a/qa/spec/spec_helper.rb
+++ b/qa/spec/spec_helper.rb
@@ -1,44 +1,21 @@
require_relative '../qa'
+require 'rspec/retry'
%w[helpers shared_examples].each do |d|
Dir[::File.join(__dir__, d, '**', '*.rb')].each { |f| require f }
end
RSpec.configure do |config|
- ServerNotRespondingError = Class.new(RuntimeError)
-
- # The login page could take some time to load the first time it is visited.
- # We visit the login page and wait for it to properly load only once at the beginning of the suite.
- config.before(:suite) do
- if QA::Runtime::Scenario.respond_to?(:gitlab_address)
- QA::Runtime::Browser.visit(:gitlab, QA::Page::Main::Login)
-
- unless QA::Page::Main::Login.perform(&:page_loaded?)
- raise ServerNotRespondingError, "Login page did not load at #{QA::Page::Main::Login.perform(&:current_url)}"
- end
+ config.before(:context) do
+ if self.class.metadata.keys.include?(:quarantine)
+ skip_or_run_quarantined_tests(self.class.metadata.keys, config.inclusion_filter.rules.keys)
end
end
config.before do |example|
QA::Runtime::Logger.debug("Starting test: #{example.full_description}") if QA::Runtime::Env.debug?
- # If quarantine is tagged, skip tests that have other metadata unless
- # they're also tagged. This lets us run quarantined tests in a particular
- # category without running tests in other categories.
- # E.g., if a test is tagged 'smoke' and 'quarantine', and another is tagged
- # 'ldap' and 'quarantine', if we wanted to run just quarantined smoke tests
- # using `--tag quarantine --tag smoke`, without this check we'd end up
- # running that ldap test as well.
- if config.inclusion_filter[:quarantine]
- skip("Running tests tagged with all of #{config.inclusion_filter.rules.keys}") unless quarantine_and_optional_other_tag?(example, config)
- end
- end
-
- config.before(:each, :quarantine) do |example|
- # Skip tests in quarantine unless we explicitly focus on them
- # We could use an exclusion filter, but this way the test report will list
- # the quarantined tests when they're not run so that we're aware of them
- skip('In quarantine') unless config.inclusion_filter[:quarantine]
+ skip_or_run_quarantined_tests(example.metadata.keys, config.inclusion_filter.rules.keys)
end
config.expect_with :rspec do |expectations|
@@ -55,20 +32,54 @@ RSpec.configure do |config|
config.profile_examples = 10
config.order = :random
Kernel.srand config.seed
+
+ # show retry status in spec process
+ config.verbose_retry = true
+
+ # show exception that triggers a retry if verbose_retry is set to true
+ config.display_try_failure_messages = true
+
+ config.around do |example|
+ retry_times = example.metadata.keys.include?(:quarantine) ? 1 : 3
+ example.run_with_retry retry: retry_times
+ end
+end
+
+# Skip tests in quarantine unless we explicitly focus on them.
+# Skip the entire context if a context is tagged. This avoids running before
+# blocks unnecessarily.
+# If quarantine is focussed, skip tests/contexts that have other metadata
+# unless they're also focussed. This lets us run quarantined tests in a
+# particular category without running tests in other categories.
+# E.g., if a test is tagged 'smoke' and 'quarantine', and another is tagged
+# 'ldap' and 'quarantine', if we wanted to run just quarantined smoke tests
+# using `--tag quarantine --tag smoke`, without this check we'd end up
+# running that ldap test as well.
+# We could use an exclusion filter, but this way the test report will list
+# the quarantined tests when they're not run so that we're aware of them
+def skip_or_run_quarantined_tests(metadata_keys, filter_keys)
+ included_filters = filters_other_than_quarantine(filter_keys)
+
+ if filter_keys.include?(:quarantine)
+ skip("Only running tests tagged with :quarantine and any of #{included_filters}") unless quarantine_and_optional_other_tag?(metadata_keys, included_filters)
+ else
+ skip('In quarantine') if metadata_keys.include?(:quarantine)
+ end
+end
+
+def filters_other_than_quarantine(filter_keys)
+ filter_keys.reject { |key| key == :quarantine }
end
# Checks if a test has the 'quarantine' tag and other tags in the inclusion filter.
#
# Returns true if
-# - the example metadata includes the quarantine tag
-# - and the metadata and inclusion filter both have any other tag
-# - or no other tags are in the inclusion filter
-def quarantine_and_optional_other_tag?(example, config)
- return false unless example.metadata.keys.include? :quarantine
-
- filters_other_than_quarantine = config.inclusion_filter.rules.keys.reject { |key| key == :quarantine }
-
- return true if filters_other_than_quarantine.empty?
+# - the metadata includes the quarantine tag
+# - and the metadata and inclusion filter both have any other tag
+# - or no other tags are in the inclusion filter
+def quarantine_and_optional_other_tag?(metadata_keys, included_filters)
+ return false unless metadata_keys.include? :quarantine
+ return true if included_filters.empty?
- filters_other_than_quarantine.any? { |key| example.metadata.keys.include? key }
+ included_filters.any? { |key| metadata_keys.include? key }
end
diff --git a/qa/spec/spec_helper_spec.rb b/qa/spec/spec_helper_spec.rb
index f001200fb52..27ec1ec80fe 100644
--- a/qa/spec/spec_helper_spec.rb
+++ b/qa/spec/spec_helper_spec.rb
@@ -10,49 +10,98 @@ describe 'rspec config tests' do
end
end
+ context 'default' do
+ it_behaves_like 'passing tests'
+ end
+
context 'foo', :foo do
it_behaves_like 'passing tests'
end
- context 'default' do
+ context 'quarantine', :quarantine do
+ it_behaves_like 'passing tests'
+ end
+
+ context 'bar quarantine', :bar, :quarantine do
it_behaves_like 'passing tests'
end
end
end
- context 'default config' do
- it 'tests are skipped if in quarantine' do
+ let(:group_2) do
+ RSpec.describe do
+ before(:all) do
+ @expectations = [1, 2, 3]
+ end
+
+ example 'not in quarantine' do
+ expect(@expectations.shift).to be(3)
+ end
+
+ example 'in quarantine', :quarantine do
+ expect(@expectations.shift).to be(3)
+ end
+ end
+ end
+
+ context 'with no tags focussed' do
+ before do
group.run
+ end
- foo_context = group.children.find { |c| c.description == "foo" }
- foo_examples = foo_context.descendant_filtered_examples
- expect(foo_examples.count).to eq(2)
+ context 'in a context tagged :foo' do
+ it 'skips tests in quarantine' do
+ context = group.children.find { |c| c.description == "foo" }
+ examples = context.descendant_filtered_examples
+ expect(examples.count).to eq(2)
- ex = foo_examples.find { |e| e.description == "not in quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
+ ex = examples.find { |e| e.description == "not in quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
- ex = foo_examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
- expect(ex.execution_result.pending_message).to eq('In quarantine')
+ ex = examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
+ expect(ex.execution_result.pending_message).to eq('In quarantine')
+ end
+ end
- default_context = group.children.find { |c| c.description == "default" }
- default_examples = default_context.descendant_filtered_examples
- expect(default_examples.count).to eq(2)
+ context 'in an untagged context' do
+ it 'skips tests in quarantine' do
+ context = group.children.find { |c| c.description == "default" }
+ examples = context.descendant_filtered_examples
+ expect(examples.count).to eq(2)
- ex = default_examples.find { |e| e.description == "not in quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
+ ex = examples.find { |e| e.description == "not in quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
- ex = default_examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
- expect(ex.execution_result.pending_message).to eq('In quarantine')
+ ex = examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
+ expect(ex.execution_result.pending_message).to eq('In quarantine')
+ end
+ end
+
+ context 'in a context tagged :quarantine' do
+ it 'skips all tests' do
+ context = group.children.find { |c| c.description == "quarantine" }
+ examples = context.descendant_filtered_examples
+ expect(examples.count).to eq(2)
+
+ ex = examples.find { |e| e.description == "not in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
+
+ ex = examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
+ expect(ex.execution_result.pending_message).to eq('In quarantine')
+ end
end
end
- context "with 'quarantine' tagged" do
+ context 'with :quarantine focussed' do
before do
RSpec.configure do |config|
config.inclusion_filter = :quarantine
end
+
+ group.run
end
after do
RSpec.configure do |config|
@@ -60,26 +109,44 @@ describe 'rspec config tests' do
end
end
- it "only quarantined tests are run" do
- group.run
+ context 'in an untagged context' do
+ it 'only runs quarantined tests' do
+ context = group.children.find { |c| c.description == "default" }
+ examples = context.descendant_filtered_examples
+ expect(examples.count).to be(1)
+
+ ex = examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
+ end
+ end
+
+ context 'in a context tagged :foo' do
+ it 'only runs quarantined tests' do
+ context = group.children.find { |c| c.description == "foo" }
+ examples = context.descendant_filtered_examples
+ expect(examples.count).to be(1)
- foo_context = group.children.find { |c| c.description == "foo" }
- foo_examples = foo_context.descendant_filtered_examples
- expect(foo_examples.count).to be(1)
+ ex = examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
+ end
+ end
- ex = foo_examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
+ context 'in a context tagged :quarantine' do
+ it 'runs all tests' do
+ context = group.children.find { |c| c.description == "quarantine" }
+ examples = context.descendant_filtered_examples
+ expect(examples.count).to be(2)
- default_context = group.children.find { |c| c.description == "default" }
- default_examples = default_context.descendant_filtered_examples
- expect(default_examples.count).to be(1)
+ ex = examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
- ex = default_examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
+ ex = examples.find { |e| e.description == "not in quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
+ end
end
end
- context "with 'foo' tagged" do
+ context 'with a non-quarantine tag (:foo) focussed' do
before do
RSpec.configure do |config|
config.inclusion_filter = :foo
@@ -93,30 +160,43 @@ describe 'rspec config tests' do
end
end
- it "tests are not run if not tagged 'foo'" do
- default_context = group.children.find { |c| c.description == "default" }
- expect(default_context.descendant_filtered_examples.count).to eq(0)
+ context 'in an untagged context' do
+ it 'runs no tests' do
+ context = group.children.find { |c| c.description == "default" }
+ expect(context.descendant_filtered_examples.count).to eq(0)
+ end
end
- it "tests are skipped if in quarantine" do
- foo_context = group.children.find { |c| c.description == "foo" }
- foo_examples = foo_context.descendant_filtered_examples
- expect(foo_examples.count).to eq(2)
+ context 'in a context tagged :foo' do
+ it 'skips quarantined tests' do
+ context = group.children.find { |c| c.description == "foo" }
+ examples = context.descendant_filtered_examples
+ expect(examples.count).to be(2)
+
+ ex = examples.find { |e| e.description == "not in quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
- ex = foo_examples.find { |e| e.description == "not in quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
+ ex = examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
+ expect(ex.execution_result.pending_message).to eq('In quarantine')
+ end
+ end
- ex = foo_examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
- expect(ex.execution_result.pending_message).to eq('In quarantine')
+ context 'in a context tagged :quarantine' do
+ it 'runs no tests' do
+ context = group.children.find { |c| c.description == "quarantine" }
+ expect(context.descendant_filtered_examples.count).to eq(0)
+ end
end
end
- context "with 'quarantine' and 'foo' tagged" do
+ context 'with :quarantine and a non-quarantine tag (:foo) focussed' do
before do
RSpec.configure do |config|
config.inclusion_filter = { quarantine: true, foo: true }
end
+
+ group.run
end
after do
RSpec.configure do |config|
@@ -124,38 +204,67 @@ describe 'rspec config tests' do
end
end
- it 'of tests tagged foo, only tests in quarantine run' do
- group.run
+ context 'in an untagged context' do
+ it 'ignores untagged tests and skips tests even if in quarantine' do
+ context = group.children.find { |c| c.description == "default" }
+ examples = context.descendant_filtered_examples
+ expect(examples.count).to eq(1)
+
+ ex = examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
+ end
+ end
- foo_context = group.children.find { |c| c.description == "foo" }
- foo_examples = foo_context.descendant_filtered_examples
- expect(foo_examples.count).to eq(2)
+ context 'in a context tagged :foo' do
+ it 'only runs quarantined tests' do
+ context = group.children.find { |c| c.description == "foo" }
+ examples = context.descendant_filtered_examples
+ expect(examples.count).to be(2)
- ex = foo_examples.find { |e| e.description == "not in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
- expect(ex.execution_result.pending_message).to eq('Running tests tagged with all of [:quarantine, :foo]')
+ ex = examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
- ex = foo_examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
+ ex = examples.find { |e| e.description == "not in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
+ end
end
- it 'if tests are not tagged they are skipped, even if they are in quarantine' do
- group.run
- default_context = group.children.find { |c| c.description == "default" }
- default_examples = default_context.descendant_filtered_examples
- expect(default_examples.count).to eq(1)
+ context 'in a context tagged :quarantine' do
+ it 'skips all tests' do
+ context = group.children.find { |c| c.description == "quarantine" }
+ examples = context.descendant_filtered_examples
+ expect(examples.count).to be(2)
+
+ ex = examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
+
+ ex = examples.find { |e| e.description == "not in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
+ end
+ end
+
+ context 'in a context tagged :bar and :quarantine' do
+ it 'skips all tests' do
+ context = group.children.find { |c| c.description == "quarantine" }
+ examples = context.descendant_filtered_examples
+ expect(examples.count).to be(2)
- ex = default_examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
- expect(ex.execution_result.pending_message).to eq('Running tests tagged with all of [:quarantine, :foo]')
+ ex = examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
+
+ ex = examples.find { |e| e.description == "not in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
+ end
end
end
- context "with 'foo' and 'bar' tagged" do
+ context 'with :quarantine and multiple non-quarantine tags focussed' do
before do
RSpec.configure do |config|
- config.inclusion_filter = { bar: true, foo: true }
+ config.inclusion_filter = { bar: true, foo: true, quarantine: true }
end
+
+ group.run
end
after do
RSpec.configure do |config|
@@ -163,102 +272,84 @@ describe 'rspec config tests' do
end
end
- it "runs tests tagged either 'foo' or 'bar'" do
- group = RSpec.describe do
- example 'foo', :foo do
- end
- example 'bar', :bar do
- end
- example 'foo and bar', :foo, :bar do
- end
- end
-
- group.run
- expect(group.examples.count).to eq(3)
-
- ex = group.examples.find { |e| e.description == "foo" }
- expect(ex.execution_result.status).to eq(:passed)
+ context 'in a context tagged :foo' do
+ it 'only runs quarantined tests' do
+ context = group.children.find { |c| c.description == "foo" }
+ examples = context.descendant_filtered_examples
+ expect(examples.count).to be(2)
- ex = group.examples.find { |e| e.description == "bar" }
- expect(ex.execution_result.status).to eq(:passed)
+ ex = examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
- ex = group.examples.find { |e| e.description == "foo and bar" }
- expect(ex.execution_result.status).to eq(:passed)
- end
-
- it "skips quarantined tests tagged 'foo' and/or 'bar'" do
- group = RSpec.describe do
- example 'foo in quarantine', :foo, :quarantine do
- end
- example 'foo and bar in quarantine', :foo, :bar, :quarantine do
- end
+ ex = examples.find { |e| e.description == "not in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
+ expect(ex.execution_result.pending_message).to eq('Only running tests tagged with :quarantine and any of [:bar, :foo]')
end
+ end
- group.run
- expect(group.examples.count).to eq(2)
+ context 'in a context tagged :quarantine' do
+ it 'skips all tests' do
+ context = group.children.find { |c| c.description == "quarantine" }
+ examples = context.descendant_filtered_examples
+ expect(examples.count).to be(2)
- ex = group.examples.find { |e| e.description == "foo in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
- expect(ex.execution_result.pending_message).to eq('In quarantine')
+ ex = examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
+ expect(ex.execution_result.pending_message).to eq('Only running tests tagged with :quarantine and any of [:bar, :foo]')
- ex = group.examples.find { |e| e.description == "foo and bar in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
- expect(ex.execution_result.pending_message).to eq('In quarantine')
+ ex = examples.find { |e| e.description == "not in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
+ expect(ex.execution_result.pending_message).to eq('Only running tests tagged with :quarantine and any of [:bar, :foo]')
+ end
end
- it "ignores quarantined tests not tagged either 'foo' or 'bar'" do
- group = RSpec.describe do
- example 'in quarantine', :quarantine do
- end
- end
+ context 'in a context tagged :bar and :quarantine' do
+ it 'runs all tests' do
+ context = group.children.find { |c| c.description == "bar quarantine" }
+ examples = context.descendant_filtered_examples
+ expect(examples.count).to be(2)
- group.run
+ ex = examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
- ex = group.examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to be_nil
+ ex = examples.find { |e| e.description == "not in quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
+ end
end
end
- context "with 'foo' and 'bar' and 'quarantined' tagged" do
- before do
- RSpec.configure do |config|
- config.inclusion_filter = { bar: true, foo: true, quarantine: true }
+ context 'rspec retry' do
+ context 'in an untagged context' do
+ before do
+ group_2.run
end
- end
- after do
- RSpec.configure do |config|
- config.inclusion_filter.clear
+
+ it 'should run example :retry times' do
+ examples = group_2.descendant_filtered_examples
+ ex = examples.find { |e| e.description == 'not in quarantine' }
+ expect(ex.execution_result.status).to eq(:passed)
end
end
- it "runs tests tagged 'quarantine' and 'foo' or 'bar'" do
- group = RSpec.describe do
- example 'foo', :foo do
- end
- example 'bar and quarantine', :bar, :quarantine do
- end
- example 'foo and bar', :foo, :bar do
- end
- example 'foo, bar, and quarantine', :foo, :bar, :quarantine do
+ context 'with :quarantine focussed' do
+ before do
+ RSpec.configure do |config|
+ config.inclusion_filter = :quarantine
end
+ group_2.run
end
- group.run
- expect(group.examples.count).to eq(4)
-
- ex = group.examples.find { |e| e.description == "foo" }
- expect(ex.execution_result.status).to eq(:pending)
- expect(ex.execution_result.pending_message).to eq('Running tests tagged with all of [:bar, :foo, :quarantine]')
-
- ex = group.examples.find { |e| e.description == "bar and quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
-
- ex = group.examples.find { |e| e.description == "foo and bar" }
- expect(ex.execution_result.status).to eq(:pending)
- expect(ex.execution_result.pending_message).to eq('Running tests tagged with all of [:bar, :foo, :quarantine]')
+ after do
+ RSpec.configure do |config|
+ config.inclusion_filter.clear
+ end
+ end
- ex = group.examples.find { |e| e.description == "foo, bar, and quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
+ it 'should run example once only' do
+ examples = group_2.descendant_filtered_examples
+ ex = examples.find { |e| e.description == 'in quarantine' }
+ expect(ex.execution_result.status).to eq(:failed)
+ end
end
end
end
diff --git a/scripts/build_assets_image b/scripts/build_assets_image
index 4e5ef977161..9afada244c8 100755
--- a/scripts/build_assets_image
+++ b/scripts/build_assets_image
@@ -22,6 +22,8 @@ mkdir -p assets_container.build/public
cp -r public/assets assets_container.build/public/
cp Dockerfile.assets assets_container.build/
docker build -t ${ASSETS_IMAGE_PATH}:${CI_COMMIT_REF_SLUG} -f assets_container.build/Dockerfile.assets assets_container.build/
+docker tag ${ASSETS_IMAGE_PATH}:${CI_COMMIT_REF_SLUG} ${ASSETS_IMAGE_PATH}:${CI_COMMIT_SHA}
docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
-docker push ${ASSETS_IMAGE_PATH}
+docker push ${ASSETS_IMAGE_PATH}:${CI_COMMIT_REF_SLUG}
+docker push ${ASSETS_IMAGE_PATH}:${CI_COMMIT_SHA}
diff --git a/scripts/lint-changelog-yaml b/scripts/lint-changelog-yaml
index 6553e02ffca..06d502c4676 100755
--- a/scripts/lint-changelog-yaml
+++ b/scripts/lint-changelog-yaml
@@ -3,7 +3,7 @@
require 'yaml'
invalid_changelogs = Dir['changelogs/**/*'].reject do |changelog|
- next true if changelog =~ /(archive\.md|unreleased(-ee)?)$/
+ next true if changelog =~ /((README|archive)\.md|unreleased(-ee)?)$/
next false unless changelog.end_with?('.yml')
begin
diff --git a/scripts/lint-doc.sh b/scripts/lint-doc.sh
index 848364b4a9b..bc73225c1bf 100755
--- a/scripts/lint-doc.sh
+++ b/scripts/lint-doc.sh
@@ -35,7 +35,7 @@ fi
# Do not use 'README.md', instead use 'index.md'
# Number of 'README.md's as of 2018-03-26
-NUMBER_READMES_CE=42
+NUMBER_READMES_CE=43
NUMBER_READMES_EE=46
FIND_READMES=$(find doc/ -name "README.md" | wc -l)
echo '=> Checking for new README.md files...'
diff --git a/scripts/static-analysis b/scripts/static-analysis
index 25ba7ec6c8e..642c50ec0a8 100755
--- a/scripts/static-analysis
+++ b/scripts/static-analysis
@@ -29,6 +29,7 @@ tasks = [
%w[bin/rake lint:all],
%w[bundle exec license_finder],
%w[yarn run eslint],
+ %w[yarn run stylelint],
%w[yarn run prettier-all],
%w[bundle exec rubocop --parallel],
%w[scripts/lint-conflicts.sh],
diff --git a/spec/config/application_spec.rb b/spec/config/application_spec.rb
new file mode 100644
index 00000000000..01ed81964c3
--- /dev/null
+++ b/spec/config/application_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Application do # rubocop:disable RSpec/FilePath
+ using RSpec::Parameterized::TableSyntax
+
+ FILTERED_PARAM = ActionDispatch::Http::ParameterFilter::FILTERED
+
+ context 'when parameters are logged' do
+ describe 'rails does not leak confidential parameters' do
+ def request_for_url(input_url)
+ env = Rack::MockRequest.env_for(input_url)
+ env['action_dispatch.parameter_filter'] = described_class.config.filter_parameters
+
+ ActionDispatch::Request.new(env)
+ end
+
+ where(:input_url, :output_query) do
+ '/' | {}
+ '/?safe=1' | { 'safe' => '1' }
+ '/?private_token=secret' | { 'private_token' => FILTERED_PARAM }
+ '/?mixed=1&private_token=secret' | { 'mixed' => '1', 'private_token' => FILTERED_PARAM }
+ '/?note=secret&noteable=1&prefix_note=2' | { 'note' => FILTERED_PARAM, 'noteable' => '1', 'prefix_note' => '2' }
+ '/?note[note]=secret&target_type=1' | { 'note' => FILTERED_PARAM, 'target_type' => '1' }
+ '/?safe[note]=secret&target_type=1' | { 'safe' => { 'note' => FILTERED_PARAM }, 'target_type' => '1' }
+ end
+
+ with_them do
+ it { expect(request_for_url(input_url).filtered_parameters).to eq(output_query) }
+ end
+ end
+ end
+end
diff --git a/spec/controllers/admin/appearances_controller_spec.rb b/spec/controllers/admin/appearances_controller_spec.rb
new file mode 100644
index 00000000000..621aa148301
--- /dev/null
+++ b/spec/controllers/admin/appearances_controller_spec.rb
@@ -0,0 +1,91 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Admin::AppearancesController do
+ let(:admin) { create(:admin) }
+ let(:header_message) { 'Header message' }
+ let(:footer_message) { 'Footer' }
+
+ describe 'POST #create' do
+ let(:create_params) do
+ {
+ title: 'Foo',
+ description: 'Bar',
+ header_message: header_message,
+ footer_message: footer_message
+ }
+ end
+
+ before do
+ sign_in(admin)
+ end
+
+ it 'creates appearance with footer and header message' do
+ post :create, params: { appearance: create_params }
+
+ expect(Appearance.current).to have_attributes(
+ header_message: header_message,
+ footer_message: footer_message,
+ email_header_and_footer_enabled: false,
+ message_background_color: '#E75E40',
+ message_font_color: '#FFFFFF'
+ )
+ end
+
+ context 'when enabling header and footer in email' do
+ it 'creates appearance with enabled flag' do
+ create_params[:email_header_and_footer_enabled] = true
+
+ post :create, params: { appearance: create_params }
+
+ expect(Appearance.current).to have_attributes(
+ header_message: header_message,
+ footer_message: footer_message,
+ email_header_and_footer_enabled: true
+ )
+ end
+ end
+ end
+
+ describe 'PUT #update' do
+ let(:update_params) do
+ {
+ header_message: header_message,
+ footer_message: footer_message
+ }
+ end
+
+ before do
+ create(:appearance)
+
+ sign_in(admin)
+ end
+
+ it 'updates appearance with footer and header message' do
+ put :update, params: { appearance: update_params }
+
+ expect(Appearance.current).to have_attributes(
+ header_message: header_message,
+ footer_message: footer_message,
+ email_header_and_footer_enabled: false,
+ message_background_color: '#E75E40',
+ message_font_color: '#FFFFFF'
+ )
+ end
+
+ context 'when enabling header and footer in email' do
+ it 'updates appearance with enabled flag' do
+ update_params[:email_header_and_footer_enabled] = true
+
+ post :update, params: { appearance: update_params }
+
+ expect(Appearance.current).to have_attributes(
+ header_message: header_message,
+ footer_message: footer_message,
+ email_header_and_footer_enabled: true
+ )
+ end
+ end
+ end
+end
diff --git a/spec/controllers/admin/runners_controller_spec.rb b/spec/controllers/admin/runners_controller_spec.rb
index 4cf14030ca1..82e24213408 100644
--- a/spec/controllers/admin/runners_controller_spec.rb
+++ b/spec/controllers/admin/runners_controller_spec.rb
@@ -1,18 +1,35 @@
require 'spec_helper'
describe Admin::RunnersController do
- let(:runner) { create(:ci_runner) }
+ let!(:runner) { create(:ci_runner) }
before do
sign_in(create(:admin))
end
describe '#index' do
+ render_views
+
it 'lists all runners' do
get :index
expect(response).to have_gitlab_http_status(200)
end
+
+ it 'avoids N+1 queries', :request_store do
+ get :index
+
+ control_count = ActiveRecord::QueryRecorder.new { get :index }.count
+
+ create(:ci_runner, :tagged_only)
+
+ # There is still an N+1 query for `runner.builds.count`
+ expect { get :index }.not_to exceed_query_limit(control_count + 1)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.body).to have_content('tag1')
+ expect(response.body).to have_content('tag2')
+ end
end
describe '#show' do
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
index 6b66cbd2651..cb24a6ef142 100644
--- a/spec/controllers/admin/users_controller_spec.rb
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -8,6 +8,31 @@ describe Admin::UsersController do
sign_in(admin)
end
+ describe 'GET #index' do
+ it 'retrieves all users' do
+ get :index
+
+ expect(assigns(:users)).to match_array([user, admin])
+ end
+
+ it 'filters by admins' do
+ get :index, params: { filter: 'admins' }
+
+ expect(assigns(:users)).to eq([admin])
+ end
+ end
+
+ describe 'GET :id' do
+ it 'finds a user case-insensitively' do
+ user = create(:user, username: 'CaseSensitive')
+
+ get :show, params: { id: user.username.downcase }
+
+ expect(response).to be_redirect
+ expect(response.location).to end_with(user.username)
+ end
+ end
+
describe 'DELETE #user with projects' do
let(:project) { create(:project, namespace: user.namespace) }
let!(:issue) { create(:issue, author: user) }
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index c9e520317e8..dca74bd5f84 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -665,6 +665,14 @@ describe ApplicationController do
expect(response.headers['Cache-Control']).to eq 'max-age=0, private, must-revalidate, no-store'
end
+
+ it 'does not set the "no-store" header for XHR requests' do
+ sign_in(user)
+
+ get :index, xhr: true
+
+ expect(response.headers['Cache-Control']).to eq 'max-age=0, private, must-revalidate'
+ end
end
end
end
diff --git a/spec/controllers/concerns/issuable_collections_spec.rb b/spec/controllers/concerns/issuable_collections_spec.rb
index 307c5d60c57..8580900215c 100644
--- a/spec/controllers/concerns/issuable_collections_spec.rb
+++ b/spec/controllers/concerns/issuable_collections_spec.rb
@@ -112,7 +112,8 @@ describe IssuableCollections do
assignee_username: 'user1',
author_id: '2',
author_username: 'user2',
- authorized_only: 'true',
+ authorized_only: 'yes',
+ confidential: true,
due_date: '2017-01-01',
group_id: '3',
iids: '4',
@@ -140,6 +141,7 @@ describe IssuableCollections do
'assignee_username' => 'user1',
'author_id' => '2',
'author_username' => 'user2',
+ 'confidential' => true,
'label_name' => 'foo',
'milestone_title' => 'bar',
'my_reaction_emoji' => 'thumbsup',
diff --git a/spec/controllers/concerns/send_file_upload_spec.rb b/spec/controllers/concerns/send_file_upload_spec.rb
index a07113a6156..aa71a247956 100644
--- a/spec/controllers/concerns/send_file_upload_spec.rb
+++ b/spec/controllers/concerns/send_file_upload_spec.rb
@@ -52,6 +52,23 @@ describe SendFileUpload do
end
end
+ context 'with inline image' do
+ let(:filename) { 'test.png' }
+ let(:params) { { disposition: 'inline', attachment: filename } }
+
+ it 'sends a file with inline disposition' do
+ # Notice the filename= is omitted from the disposition; this is because
+ # Rails 5 will append this header in send_file
+ expected_params = {
+ filename: 'test.png',
+ disposition: "inline; filename*=UTF-8''test.png"
+ }
+ expect(controller).to receive(:send_file).with(uploader.path, expected_params)
+
+ subject
+ end
+ end
+
context 'with attachment' do
let(:filename) { 'test.js' }
let(:params) { { attachment: filename } }
@@ -95,7 +112,7 @@ describe SendFileUpload do
it 'sends a file with a custom type' do
headers = double
- expected_headers = %r(response-content-disposition=attachment%3B%20filename%3D%22test.js%22%3B%20filename%2A%3DUTF-8%27%27test.js&response-content-type=application/ecmascript)
+ expected_headers = /response-content-disposition=attachment%3B%20filename%3D%22test.js%22%3B%20filename%2A%3DUTF-8%27%27test.js&response-content-type=application%2Fecmascript/
expect(Gitlab::Workhorse).to receive(:send_url).with(expected_headers).and_call_original
expect(headers).to receive(:store).with(Gitlab::Workhorse::SEND_DATA_HEADER, /^send-url:/)
diff --git a/spec/controllers/dashboard/milestones_controller_spec.rb b/spec/controllers/dashboard/milestones_controller_spec.rb
index 8b176e07bc8..4b164d0aa6b 100644
--- a/spec/controllers/dashboard/milestones_controller_spec.rb
+++ b/spec/controllers/dashboard/milestones_controller_spec.rb
@@ -3,11 +3,9 @@ require 'spec_helper'
describe Dashboard::MilestonesController do
let(:project) { create(:project) }
let(:group) { create(:group) }
- let(:public_group) { create(:group, :public) }
let(:user) { create(:user) }
let(:project_milestone) { create(:milestone, project: project) }
let(:group_milestone) { create(:milestone, group: group) }
- let!(:public_milestone) { create(:milestone, group: public_group) }
let(:milestone) do
DashboardMilestone.build(
[project],
@@ -45,6 +43,9 @@ describe Dashboard::MilestonesController do
end
describe "#index" do
+ let(:public_group) { create(:group, :public) }
+ let!(:public_milestone) { create(:milestone, group: public_group) }
+
render_views
it 'returns group and project milestones to which the user belongs' do
@@ -74,10 +75,10 @@ describe Dashboard::MilestonesController do
expect(response.body).not_to include(project_milestone.title)
end
- it 'should contain group and project milestones to which the user belongs to' do
+ it 'should show counts of group and project milestones to which the user belongs to' do
get :index
- expect(response.body).to include("Open\n<span class=\"badge badge-pill\">3</span>")
+ expect(response.body).to include("Open\n<span class=\"badge badge-pill\">2</span>")
expect(response.body).to include("Closed\n<span class=\"badge badge-pill\">0</span>")
end
end
diff --git a/spec/controllers/groups/clusters_controller_spec.rb b/spec/controllers/groups/clusters_controller_spec.rb
index 360030102e0..ef23ffaa843 100644
--- a/spec/controllers/groups/clusters_controller_spec.rb
+++ b/spec/controllers/groups/clusters_controller_spec.rb
@@ -453,7 +453,7 @@ describe Groups::ClustersController do
end
context 'when domain is invalid' do
- let(:domain) { 'not-a-valid-domain' }
+ let(:domain) { 'http://not-a-valid-domain' }
it 'should not update cluster attributes' do
go
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index 7d87b33e503..21e5122c06b 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -227,9 +227,7 @@ describe GroupsController do
context 'searching' do
before do
- # Remove in https://gitlab.com/gitlab-org/gitlab-ce/issues/54643
- stub_feature_flags(use_cte_for_group_issues_search: false)
- stub_feature_flags(use_subquery_for_group_issues_search: true)
+ stub_feature_flags(attempt_group_search_optimizations: true)
end
it 'works with popularity sort' do
diff --git a/spec/controllers/import/gitea_controller_spec.rb b/spec/controllers/import/gitea_controller_spec.rb
index 5ba64ab3eed..8cbec79095f 100644
--- a/spec/controllers/import/gitea_controller_spec.rb
+++ b/spec/controllers/import/gitea_controller_spec.rb
@@ -40,4 +40,12 @@ describe Import::GiteaController do
end
end
end
+
+ describe "GET realtime_changes" do
+ it_behaves_like 'a GitHub-ish import controller: GET realtime_changes' do
+ before do
+ assign_host_url
+ end
+ end
+ end
end
diff --git a/spec/controllers/import/github_controller_spec.rb b/spec/controllers/import/github_controller_spec.rb
index bca5f3f6589..162dff98ec5 100644
--- a/spec/controllers/import/github_controller_spec.rb
+++ b/spec/controllers/import/github_controller_spec.rb
@@ -60,4 +60,8 @@ describe Import::GithubController do
describe "POST create" do
it_behaves_like 'a GitHub-ish import controller: POST create'
end
+
+ describe "GET realtime_changes" do
+ it_behaves_like 'a GitHub-ish import controller: GET realtime_changes'
+ end
end
diff --git a/spec/controllers/profiles/preferences_controller_spec.rb b/spec/controllers/profiles/preferences_controller_spec.rb
index 760c0fab130..ee881f85233 100644
--- a/spec/controllers/profiles/preferences_controller_spec.rb
+++ b/spec/controllers/profiles/preferences_controller_spec.rb
@@ -43,7 +43,8 @@ describe Profiles::PreferencesController do
color_scheme_id: '1',
dashboard: 'stars',
theme_id: '2',
- first_day_of_week: '1'
+ first_day_of_week: '1',
+ preferred_language: 'jp'
}.with_indifferent_access
expect(user).to receive(:assign_attributes).with(ActionController::Parameters.new(prefs).permit!)
diff --git a/spec/controllers/projects/autocomplete_sources_controller_spec.rb b/spec/controllers/projects/autocomplete_sources_controller_spec.rb
new file mode 100644
index 00000000000..4bc72042710
--- /dev/null
+++ b/spec/controllers/projects/autocomplete_sources_controller_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::AutocompleteSourcesController do
+ set(:group) { create(:group) }
+ set(:project) { create(:project, namespace: group) }
+ set(:issue) { create(:issue, project: project) }
+ set(:user) { create(:user) }
+
+ describe 'GET members' do
+ before do
+ group.add_owner(user)
+ sign_in(user)
+ end
+
+ it 'returns an array of member object' do
+ get :members, format: :json, params: { namespace_id: group.path, project_id: project.path, type: issue.class.name, type_id: issue.id }
+
+ all = json_response.find {|member| member["username"] == 'all'}
+ the_group = json_response.find {|member| member["username"] == group.full_path}
+ the_user = json_response.find {|member| member["username"] == user.username}
+
+ expect(all.symbolize_keys).to include(username: 'all',
+ name: 'All Project and Group Members',
+ count: 1)
+
+ expect(the_group.symbolize_keys).to include(type: group.class.name,
+ name: group.full_name,
+ avatar_url: group.avatar_url,
+ count: 1)
+
+ expect(the_user.symbolize_keys).to include(type: user.class.name,
+ name: user.name,
+ avatar_url: user.avatar_url)
+ end
+ end
+end
diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb
index aa97a417a98..36ce1119100 100644
--- a/spec/controllers/projects/environments_controller_spec.rb
+++ b/spec/controllers/projects/environments_controller_spec.rb
@@ -54,9 +54,9 @@ describe Projects::EnvironmentsController do
it 'responds with a flat payload describing available environments' do
expect(environments.count).to eq 3
- expect(environments.first['name']).to eq 'production'
- expect(environments.second['name']).to eq 'staging/review-1'
- expect(environments.third['name']).to eq 'staging/review-2'
+ expect(environments.first).to include('name' => 'production', 'name_without_type' => 'production')
+ expect(environments.second).to include('name' => 'staging/review-1', 'name_without_type' => 'review-1')
+ expect(environments.third).to include('name' => 'staging/review-2', 'name_without_type' => 'review-2')
expect(json_response['available_count']).to eq 3
expect(json_response['stopped_count']).to eq 1
end
@@ -155,9 +155,9 @@ describe Projects::EnvironmentsController do
expect(response).to be_ok
expect(response).not_to render_template 'folder'
expect(json_response['environments'][0])
- .to include('name' => 'staging-1.0/review')
+ .to include('name' => 'staging-1.0/review', 'name_without_type' => 'review')
expect(json_response['environments'][1])
- .to include('name' => 'staging-1.0/zzz')
+ .to include('name' => 'staging-1.0/zzz', 'name_without_type' => 'zzz')
end
end
end
diff --git a/spec/controllers/projects/graphs_controller_spec.rb b/spec/controllers/projects/graphs_controller_spec.rb
index 73fb7307e11..8decd8f1382 100644
--- a/spec/controllers/projects/graphs_controller_spec.rb
+++ b/spec/controllers/projects/graphs_controller_spec.rb
@@ -24,4 +24,20 @@ describe Projects::GraphsController do
expect(response).to redirect_to action: :charts
end
end
+
+ describe 'charts' do
+ context 'when languages were previously detected' do
+ let!(:repository_language) { create(:repository_language, project: project) }
+
+ it 'sets the languages properly' do
+ get(:charts, params: { namespace_id: project.namespace.path, project_id: project.path, id: 'master' })
+
+ expect(assigns[:languages]).to eq(
+ [value: repository_language.share,
+ label: repository_language.name,
+ color: repository_language.color,
+ highlight: repository_language.color])
+ end
+ end
+ end
end
diff --git a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
index a6017d8e5e6..e85f32d6e30 100644
--- a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
@@ -4,10 +4,11 @@ describe Projects::MergeRequests::DiffsController do
include ProjectForksHelper
let(:project) { create(:project, :repository) }
- let(:user) { project.owner }
+ let(:user) { create(:user) }
let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
before do
+ project.add_maintainer(user)
sign_in(user)
end
@@ -114,16 +115,6 @@ describe Projects::MergeRequests::DiffsController do
expect(paths).to include(existing_path)
end
end
-
- context 'when the path does not exist in the diff' do
- before do
- diff_for_path(old_path: 'files/ruby/nopen.rb', new_path: 'files/ruby/nopen.rb')
- end
-
- it 'returns a 404' do
- expect(response).to have_gitlab_http_status(404)
- end
- end
end
context 'when the user cannot view the merge request' do
diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb
index 81892575889..0b0f5117784 100644
--- a/spec/controllers/projects/notes_controller_spec.rb
+++ b/spec/controllers/projects/notes_controller_spec.rb
@@ -252,8 +252,8 @@ describe Projects::NotesController do
note: 'some note',
noteable_id: merge_request.id.to_s,
noteable_type: 'MergeRequest',
- merge_request_diff_head_sha: 'sha',
- in_reply_to_discussion_id: nil
+ commit_id: nil,
+ merge_request_diff_head_sha: 'sha'
}).permit!
expect(Notes::CreateService).to receive(:new).with(project, user, service_params).and_return(double(execute: true))
@@ -266,6 +266,22 @@ describe Projects::NotesController do
end
end
+ context 'when creating a comment on a commit with SHA1 starting with a large number' do
+ let(:commit) { create(:commit, project: project, id: '842616594688d2351480dfebd67b3d8d15571e6d') }
+
+ it 'creates a note successfully' do
+ expect do
+ post :create, params: {
+ note: { note: 'some note', commit_id: commit.id },
+ namespace_id: project.namespace,
+ project_id: project,
+ target_type: 'commit',
+ target_id: commit.id
+ }
+ end.to change { Note.count }.by(1)
+ end
+ end
+
context 'when creating a commit comment from an MR fork' do
let(:project) { create(:project, :repository) }
diff --git a/spec/controllers/projects/pages_controller_spec.rb b/spec/controllers/projects/pages_controller_spec.rb
index 4b742a5d427..d6eece47804 100644
--- a/spec/controllers/projects/pages_controller_spec.rb
+++ b/spec/controllers/projects/pages_controller_spec.rb
@@ -42,6 +42,18 @@ describe Projects::PagesController do
expect(response).to have_gitlab_http_status(302)
end
+
+ context 'when user is developer' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'returns 404 status' do
+ delete :destroy, params: request_params
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
end
context 'pages disabled' do
diff --git a/spec/controllers/projects/settings/operations_controller_spec.rb b/spec/controllers/projects/settings/operations_controller_spec.rb
index d989ec22481..02a392f23c2 100644
--- a/spec/controllers/projects/settings/operations_controller_spec.rb
+++ b/spec/controllers/projects/settings/operations_controller_spec.rb
@@ -74,38 +74,55 @@ describe Projects::Settings::OperationsController do
{
error_tracking_setting_attributes: {
enabled: '1',
- api_url: 'http://url',
- token: 'token'
+ api_host: 'http://url',
+ token: 'token',
+ project: {
+ slug: 'sentry-project',
+ name: 'Sentry Project',
+ organization_slug: 'sentry-org',
+ organization_name: 'Sentry Org'
+ }
}
}
end
+
let(:error_tracking_permitted) do
ActionController::Parameters.new(error_tracking_params).permit!
end
- context 'when update succeeds' do
- before do
- stub_operations_update_service_returning(status: :success)
- end
-
- it 'shows a notice' do
- patch :update, params: project_params(project, error_tracking_params)
-
- expect(response).to redirect_to(operations_url)
- expect(flash[:notice]).to eq _('Your changes have been saved')
- end
- end
-
- context 'when update fails' do
- before do
- stub_operations_update_service_returning(status: :error)
+ context 'format json' do
+ context 'when update succeeds' do
+ before do
+ stub_operations_update_service_returning(status: :success)
+ end
+
+ it 'returns success status' do
+ patch :update,
+ params: project_params(project, error_tracking_params),
+ format: :json
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to eq('status' => 'success')
+ expect(flash[:notice]).to eq('Your changes have been saved')
+ end
end
- it 'renders show page' do
- patch :update, params: project_params(project, error_tracking_params)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to render_template(:show)
+ context 'when update fails' do
+ before do
+ stub_operations_update_service_returning(
+ status: :error,
+ message: 'error message'
+ )
+ end
+
+ it 'returns error' do
+ patch :update,
+ params: project_params(project, error_tracking_params),
+ format: :json
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['message']).not_to be_nil
+ end
end
end
diff --git a/spec/factories/ci/group_variables.rb b/spec/factories/ci/group_variables.rb
index 64716842b12..9bf520a2c0a 100644
--- a/spec/factories/ci/group_variables.rb
+++ b/spec/factories/ci/group_variables.rb
@@ -2,6 +2,7 @@ FactoryBot.define do
factory :ci_group_variable, class: Ci::GroupVariable do
sequence(:key) { |n| "VARIABLE_#{n}" }
value 'VARIABLE_VALUE'
+ masked false
trait(:protected) do
protected true
diff --git a/spec/factories/ci/variables.rb b/spec/factories/ci/variables.rb
index 3d014b9b54f..97a7c9ba252 100644
--- a/spec/factories/ci/variables.rb
+++ b/spec/factories/ci/variables.rb
@@ -2,6 +2,7 @@ FactoryBot.define do
factory :ci_variable, class: Ci::Variable do
sequence(:key) { |n| "VARIABLE_#{n}" }
value 'VARIABLE_VALUE'
+ masked false
trait(:protected) do
protected true
diff --git a/spec/factories/import_state.rb b/spec/factories/import_states.rb
index d6de26dccbc..d6de26dccbc 100644
--- a/spec/factories/import_state.rb
+++ b/spec/factories/import_states.rb
diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb
index 2392bfc4a53..18f724770b5 100644
--- a/spec/factories/merge_requests.rb
+++ b/spec/factories/merge_requests.rb
@@ -101,6 +101,15 @@ FactoryBot.define do
end
end
+ trait :with_merge_request_pipeline do
+ after(:build) do |merge_request|
+ merge_request.merge_request_pipelines << build(:ci_pipeline,
+ source: :merge_request_event,
+ merge_request: merge_request,
+ project: merge_request.source_project)
+ end
+ end
+
trait :deployed_review_app do
target_branch 'pages-deploy-target'
diff --git a/spec/factories/personal_access_tokens.rb b/spec/factories/personal_access_tokens.rb
index 1b12f84d7b8..e7fd22a96b2 100644
--- a/spec/factories/personal_access_tokens.rb
+++ b/spec/factories/personal_access_tokens.rb
@@ -1,13 +1,14 @@
FactoryBot.define do
factory :personal_access_token do
user
- token { SecureRandom.hex(50) }
sequence(:name) { |n| "PAT #{n}" }
revoked false
expires_at { 5.days.from_now }
scopes ['api']
impersonation false
+ after(:build) { |personal_access_token| personal_access_token.ensure_token }
+
trait :impersonation do
impersonation true
end
@@ -21,7 +22,7 @@ FactoryBot.define do
end
trait :invalid do
- token nil
+ token_digest nil
end
end
end
diff --git a/spec/factories/project_daily_statistics.rb b/spec/factories/project_daily_statistics.rb
new file mode 100644
index 00000000000..7e4142fa401
--- /dev/null
+++ b/spec/factories/project_daily_statistics.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :project_daily_statistic do
+ project
+ fetch_count 1
+ end
+end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index f7ef34d773b..30d3b22d868 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -313,6 +313,20 @@ FactoryBot.define do
end
end
+ factory :youtrack_project, parent: :project do
+ has_external_issue_tracker true
+
+ after :create do |project|
+ project.create_youtrack_service(
+ active: true,
+ properties: {
+ 'project_url' => 'http://youtrack/projects/project_guid_in_youtrack',
+ 'issues_url' => 'http://youtrack/issues/:id'
+ }
+ )
+ end
+ end
+
factory :jira_project, parent: :project do
has_external_issue_tracker true
jira_service
diff --git a/spec/factories/users.rb b/spec/factories/users.rb
index a47bd7cafca..1d2b724a5e5 100644
--- a/spec/factories/users.rb
+++ b/spec/factories/users.rb
@@ -73,11 +73,16 @@ FactoryBot.define do
end
after(:create) do |user, evaluator|
- user.identities << create(
- :identity,
+ identity_attrs = {
provider: evaluator.provider,
extern_uid: evaluator.extern_uid
- )
+ }
+
+ if evaluator.respond_to?(:saml_provider)
+ identity_attrs[:saml_provider] = evaluator.saml_provider
+ end
+
+ user.identities << create(:identity, identity_attrs)
end
end
diff --git a/spec/features/admin/admin_appearance_spec.rb b/spec/features/admin/admin_appearance_spec.rb
index 57215c0d1e9..83cd686818c 100644
--- a/spec/features/admin/admin_appearance_spec.rb
+++ b/spec/features/admin/admin_appearance_spec.rb
@@ -39,6 +39,38 @@ describe 'Admin Appearance' do
expect_custom_new_project_appearance(appearance)
end
+ context 'Custom system header and footer' do
+ before do
+ sign_in(create(:admin))
+ end
+
+ context 'when system header and footer messages are empty' do
+ it 'shows custom system header and footer fields' do
+ visit admin_appearances_path
+
+ expect(page).to have_field('appearance_header_message', with: '')
+ expect(page).to have_field('appearance_footer_message', with: '')
+ expect(page).to have_field('appearance_message_background_color')
+ expect(page).to have_field('appearance_message_font_color')
+ end
+ end
+
+ context 'when system header and footer messages are not empty' do
+ before do
+ appearance.update(header_message: 'Foo', footer_message: 'Bar')
+ end
+
+ it 'shows custom system header and footer fields' do
+ visit admin_appearances_path
+
+ expect(page).to have_field('appearance_header_message', with: appearance.header_message)
+ expect(page).to have_field('appearance_footer_message', with: appearance.footer_message)
+ expect(page).to have_field('appearance_message_background_color')
+ expect(page).to have_field('appearance_message_font_color')
+ end
+ end
+ end
+
it 'Custom sign-in page' do
visit new_user_session_path
diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb
index ed9c0ea9ac0..97b432a6751 100644
--- a/spec/features/admin/admin_runners_spec.rb
+++ b/spec/features/admin/admin_runners_spec.rb
@@ -141,6 +141,56 @@ describe "Admin Runners" do
end
end
+ describe 'filter by tag', :js do
+ it 'shows correct runner when tag matches' do
+ create :ci_runner, description: 'runner-blue', tag_list: ['blue']
+ create :ci_runner, description: 'runner-red', tag_list: ['red']
+
+ visit admin_runners_path
+
+ expect(page).to have_content 'runner-blue'
+ expect(page).to have_content 'runner-red'
+
+ input_filtered_search_keys('tag:blue')
+
+ expect(page).to have_content 'runner-blue'
+ expect(page).not_to have_content 'runner-red'
+ end
+
+ it 'shows no runner when tag does not match' do
+ create :ci_runner, description: 'runner-blue', tag_list: ['blue']
+ create :ci_runner, description: 'runner-red', tag_list: ['blue']
+
+ visit admin_runners_path
+
+ input_filtered_search_keys('tag:red')
+
+ expect(page).not_to have_content 'runner-blue'
+ expect(page).not_to have_content 'runner-blue'
+ expect(page).to have_text 'No runners found'
+ end
+
+ it 'shows correct runner when tag is selected and search term is entered' do
+ create :ci_runner, description: 'runner-a-1', tag_list: ['blue']
+ create :ci_runner, description: 'runner-a-2', tag_list: ['red']
+ create :ci_runner, description: 'runner-b-1', tag_list: ['blue']
+
+ visit admin_runners_path
+
+ input_filtered_search_keys('tag:blue')
+
+ expect(page).to have_content 'runner-a-1'
+ expect(page).to have_content 'runner-b-1'
+ expect(page).not_to have_content 'runner-a-2'
+
+ input_filtered_search_keys('tag:blue runner-a')
+
+ expect(page).to have_content 'runner-a-1'
+ expect(page).not_to have_content 'runner-b-1'
+ expect(page).not_to have_content 'runner-a-2'
+ end
+ end
+
it 'sorts by last contact date', :js do
create(:ci_runner, description: 'runner-1', created_at: '2018-07-12 15:37', contacted_at: '2018-07-12 15:37')
create(:ci_runner, description: 'runner-2', created_at: '2018-07-12 16:37', contacted_at: '2018-07-12 16:37')
diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb
index 6c4b04ab76b..9d1c1e3acc7 100644
--- a/spec/features/dashboard/projects_spec.rb
+++ b/spec/features/dashboard/projects_spec.rb
@@ -114,7 +114,16 @@ describe 'Dashboard Projects' do
end
end
- context 'when on Starred projects tab' do
+ context 'when on Starred projects tab', :js do
+ it 'shows the empty state when there are no starred projects' do
+ visit(starred_dashboard_projects_path)
+
+ element = page.find('.row.empty-state')
+
+ expect(element).to have_content("You don't have starred projects yet.")
+ expect(element.find('.svg-content img')['src']).to have_content('illustrations/starred_empty')
+ end
+
it 'shows only starred projects' do
user.toggle_star(project2)
diff --git a/spec/features/dashboard/shortcuts_spec.rb b/spec/features/dashboard/shortcuts_spec.rb
index cbddf117462..55f5ff04d01 100644
--- a/spec/features/dashboard/shortcuts_spec.rb
+++ b/spec/features/dashboard/shortcuts_spec.rb
@@ -44,7 +44,7 @@ describe 'Dashboard shortcuts', :js do
find('body').send_keys([:shift, 'S'])
find('.nothing-here-block')
- expect(page).to have_selector('.snippets-list-holder')
+ expect(page).to have_content('No snippets found')
find('body').send_keys([:shift, 'P'])
diff --git a/spec/features/dashboard/snippets_spec.rb b/spec/features/dashboard/snippets_spec.rb
index fb4263d74c4..0e248c8732d 100644
--- a/spec/features/dashboard/snippets_spec.rb
+++ b/spec/features/dashboard/snippets_spec.rb
@@ -13,6 +13,21 @@ describe 'Dashboard snippets' do
it_behaves_like 'paginated snippets'
end
+ context 'when there are no project snippets', :js do
+ let(:project) { create(:project, :public) }
+ before do
+ sign_in(project.owner)
+ visit dashboard_snippets_path
+ end
+
+ it 'shows the empty state when there are no snippets' do
+ element = page.find('.row.empty-state')
+
+ expect(element).to have_content("Snippets are small pieces of code or notes that you want to keep.")
+ expect(element.find('.svg-content img')['src']).to have_content('illustrations/snippets_empty')
+ end
+ end
+
context 'filtering by visibility' do
let(:user) { create(:user) }
let!(:snippets) do
diff --git a/spec/features/display_system_header_and_footer_bar_spec.rb b/spec/features/display_system_header_and_footer_bar_spec.rb
new file mode 100644
index 00000000000..af9d9a5834f
--- /dev/null
+++ b/spec/features/display_system_header_and_footer_bar_spec.rb
@@ -0,0 +1,137 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe 'Display system header and footer bar' do
+ let(:header_message) { "Foo" }
+ let(:footer_message) { "Bar" }
+
+ shared_examples 'system header is configured' do
+ it 'shows system header' do
+ expect(page).to have_css('.header-message')
+ end
+
+ it 'shows the correct content' do
+ page.within('.header-message') do
+ expect(page).to have_content(header_message)
+ end
+ end
+ end
+
+ shared_examples 'system footer is configured' do
+ it 'shows system footer' do
+ expect(page).to have_css('.footer-message')
+ end
+
+ it 'shows the correct content' do
+ page.within('.footer-message') do
+ expect(page).to have_content(footer_message)
+ end
+ end
+ end
+
+ shared_examples 'system header is not configured' do
+ it 'does not show system header' do
+ expect(page).not_to have_css('.header-message')
+ end
+ end
+
+ shared_examples 'system footer is not configured' do
+ it 'does not show system footer' do
+ expect(page).not_to have_css('.footer-message')
+ end
+ end
+
+ context 'when authenticated' do
+ context 'when system header and footer are not configured' do
+ before do
+ sign_in(create(:user))
+
+ visit root_path
+ end
+
+ it_behaves_like 'system header is not configured'
+ it_behaves_like 'system footer is not configured'
+ end
+
+ context 'when only system header is defined' do
+ before do
+ create(:appearance, header_message: header_message)
+
+ sign_in(create(:user))
+ visit root_path
+ end
+
+ it_behaves_like 'system header is configured'
+ it_behaves_like 'system footer is not configured'
+ end
+
+ context 'when only system footer is defined' do
+ before do
+ create(:appearance, footer_message: footer_message)
+
+ sign_in(create(:user))
+ visit root_path
+ end
+
+ it_behaves_like 'system header is not configured'
+ it_behaves_like 'system footer is configured'
+ end
+
+ context 'when system header and footer are defined' do
+ before do
+ create(:appearance, header_message: header_message, footer_message: footer_message)
+
+ sign_in(create(:user))
+ visit root_path
+ end
+
+ it_behaves_like 'system header is configured'
+ it_behaves_like 'system footer is configured'
+ end
+ end
+
+ context 'when not authenticated' do
+ context 'when system header and footer are not configured' do
+ before do
+ visit root_path
+ end
+
+ it_behaves_like 'system header is not configured'
+ it_behaves_like 'system footer is not configured'
+ end
+
+ context 'when only system header is defined' do
+ before do
+ create(:appearance, header_message: header_message)
+
+ visit root_path
+ end
+
+ it_behaves_like 'system header is configured'
+ it_behaves_like 'system footer is not configured'
+ end
+
+ context 'when only system footer is defined' do
+ before do
+ create(:appearance, footer_message: footer_message)
+
+ visit root_path
+ end
+
+ it_behaves_like 'system header is not configured'
+ it_behaves_like 'system footer is configured'
+ end
+
+ context 'when system header and footer are defined' do
+ before do
+ create(:appearance, header_message: header_message, footer_message: footer_message)
+
+ visit root_path
+ end
+
+ it_behaves_like 'system header is configured'
+ it_behaves_like 'system footer is configured'
+ end
+ end
+end
diff --git a/spec/features/group_variables_spec.rb b/spec/features/group_variables_spec.rb
index 57e3ddfb39c..1a53e7c9512 100644
--- a/spec/features/group_variables_spec.rb
+++ b/spec/features/group_variables_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe 'Group variables', :js do
let(:user) { create(:user) }
let(:group) { create(:group) }
- let!(:variable) { create(:ci_group_variable, key: 'test_key', value: 'test value', group: group) }
+ let!(:variable) { create(:ci_group_variable, key: 'test_key', value: 'test_value', group: group) }
let(:page_path) { group_settings_ci_cd_path(group) }
before do
diff --git a/spec/features/groups/labels/create_spec.rb b/spec/features/groups/labels/create_spec.rb
new file mode 100644
index 00000000000..f5062a65321
--- /dev/null
+++ b/spec/features/groups/labels/create_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Create a group label' do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+
+ before do
+ group.add_owner(user)
+ sign_in(user)
+ visit group_labels_path(group)
+ end
+
+ it 'creates a new label' do
+ click_link 'New label'
+ fill_in 'Title', with: 'test-label'
+ click_button 'Create label'
+
+ expect(page).to have_content 'test-label'
+ expect(current_path).to eq(group_labels_path(group))
+ end
+end
diff --git a/spec/features/groups/labels/index_spec.rb b/spec/features/groups/labels/index_spec.rb
index 0ce7dad4040..62308d3b518 100644
--- a/spec/features/groups/labels/index_spec.rb
+++ b/spec/features/groups/labels/index_spec.rb
@@ -1,9 +1,12 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Group labels' do
let(:user) { create(:user) }
let(:group) { create(:group) }
let!(:label) { create(:group_label, group: group) }
+ let!(:label2) { create(:group_label) }
before do
group.add_owner(user)
@@ -11,7 +14,16 @@ describe 'Group labels' do
visit group_labels_path(group)
end
- it 'label has edit button', :js do
+ it 'shows labels that belong to the group' do
+ expect(page).to have_content(label.name)
+ expect(page).not_to have_content(label2.name)
+ end
+
+ it 'shows a new label button' do
+ expect(page).to have_link('New label')
+ end
+
+ it 'shows an edit label button', :js do
expect(page).to have_selector('.label-action.edit')
end
end
diff --git a/spec/features/issuables/issuable_list_spec.rb b/spec/features/issuables/issuable_list_spec.rb
index 2f45ef856a5..7b6e9cd66b2 100644
--- a/spec/features/issuables/issuable_list_spec.rb
+++ b/spec/features/issuables/issuable_list_spec.rb
@@ -28,6 +28,22 @@ describe 'issuable list' do
expect(first('.fa-thumbs-down').find(:xpath, '..')).to have_content(1)
expect(first('.fa-comments').find(:xpath, '..')).to have_content(2)
end
+
+ it 'sorts labels alphabetically' do
+ label1 = create(:label, project: project, title: 'a')
+ label2 = create(:label, project: project, title: 'z')
+ label3 = create(:label, project: project, title: 'X')
+ label4 = create(:label, project: project, title: 'B')
+ issuable = create_issuable(issuable_type)
+ issuable.labels << [label1, label2, label3, label4]
+
+ visit_issuable_list(issuable_type)
+
+ expect(all('.label-link')[0].text).to have_content('B')
+ expect(all('.label-link')[1].text).to have_content('X')
+ expect(all('.label-link')[2].text).to have_content('a')
+ expect(all('.label-link')[3].text).to have_content('z')
+ end
end
it "counts merge requests closing issues icons for each issue" do
@@ -45,6 +61,14 @@ describe 'issuable list' do
end
end
+ def create_issuable(issuable_type)
+ if issuable_type == :issue
+ create(:issue, project: project)
+ else
+ create(:merge_request, source_project: project)
+ end
+ end
+
def create_issuables(issuable_type)
3.times do |n|
issuable =
diff --git a/spec/features/issues/filtered_search/dropdown_hint_spec.rb b/spec/features/issues/filtered_search/dropdown_hint_spec.rb
index 0e296ab2109..096756f19cc 100644
--- a/spec/features/issues/filtered_search/dropdown_hint_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_hint_spec.rb
@@ -66,7 +66,7 @@ describe 'Dropdown hint', :js do
it 'filters with text' do
filtered_search.set('a')
- expect(find(js_dropdown_hint)).to have_selector('.filter-dropdown .filter-dropdown-item', count: 4)
+ expect(find(js_dropdown_hint)).to have_selector('.filter-dropdown .filter-dropdown-item', count: 5)
end
end
@@ -119,6 +119,15 @@ describe 'Dropdown hint', :js do
expect_tokens([{ name: 'my-reaction' }])
expect_filtered_search_input_empty
end
+
+ it 'opens the yes-no dropdown when you click on confidential' do
+ click_hint('confidential')
+
+ expect(page).to have_css(js_dropdown_hint, visible: false)
+ expect(page).to have_css('#js-dropdown-confidential', visible: true)
+ expect_tokens([{ name: 'confidential' }])
+ expect_filtered_search_input_empty
+ end
end
describe 'selecting from dropdown with some input' do
diff --git a/spec/features/issues/filtered_search/search_bar_spec.rb b/spec/features/issues/filtered_search/search_bar_spec.rb
index 8abab3f35d6..da23aea1fc9 100644
--- a/spec/features/issues/filtered_search/search_bar_spec.rb
+++ b/spec/features/issues/filtered_search/search_bar_spec.rb
@@ -86,7 +86,7 @@ describe 'Search bar', :js do
expect(find('#js-dropdown-hint')).to have_selector('.filter-dropdown .filter-dropdown-item', count: original_size)
end
- it 'resets the dropdown filters' do
+ it 'resets the dropdown filters', :quarantine do
filtered_search.click
hint_offset = get_left_style(find('#js-dropdown-hint')['style'])
@@ -100,7 +100,7 @@ describe 'Search bar', :js do
find('.filtered-search-box .clear-search').click
filtered_search.click
- expect(find('#js-dropdown-hint')).to have_selector('.filter-dropdown .filter-dropdown-item', count: 5)
+ expect(find('#js-dropdown-hint')).to have_selector('.filter-dropdown .filter-dropdown-item', count: 6)
expect(get_left_style(find('#js-dropdown-hint')['style'])).to eq(hint_offset)
end
end
diff --git a/spec/features/issues/filtered_search/visual_tokens_spec.rb b/spec/features/issues/filtered_search/visual_tokens_spec.rb
index a4c34ce85f0..9fd661d80ae 100644
--- a/spec/features/issues/filtered_search/visual_tokens_spec.rb
+++ b/spec/features/issues/filtered_search/visual_tokens_spec.rb
@@ -59,13 +59,6 @@ describe 'Visual tokens', :js do
expect(page).to have_css('#js-dropdown-author', visible: false)
end
- it 'ends editing mode when scroll container is clicked' do
- find('.scroll-container').click
-
- expect_filtered_search_input_empty
- expect(page).to have_css('#js-dropdown-author', visible: false)
- end
-
describe 'selecting different author from dropdown' do
before do
filter_author_dropdown.find('.filter-dropdown-item .dropdown-light-content', text: "@#{user_rock.username}").click
@@ -109,13 +102,6 @@ describe 'Visual tokens', :js do
expect(page).to have_css('#js-dropdown-assignee', visible: false)
end
- it 'ends editing mode when scroll container is clicked' do
- find('.scroll-container').click
-
- expect_filtered_search_input_empty
- expect(page).to have_css('#js-dropdown-assignee', visible: false)
- end
-
describe 'selecting static option from dropdown' do
before do
find("#js-dropdown-assignee").find('.filter-dropdown-item', text: 'None').click
@@ -167,13 +153,6 @@ describe 'Visual tokens', :js do
expect_filtered_search_input_empty
expect(page).to have_css('#js-dropdown-milestone', visible: false)
end
-
- it 'ends editing mode when scroll container is clicked' do
- find('.scroll-container').click
-
- expect_filtered_search_input_empty
- expect(page).to have_css('#js-dropdown-milestone', visible: false)
- end
end
describe 'editing label token' do
diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb
index c22ad0d20ef..8eb413bdd8d 100644
--- a/spec/features/issues/gfm_autocomplete_spec.rb
+++ b/spec/features/issues/gfm_autocomplete_spec.rb
@@ -278,12 +278,7 @@ describe 'GFM autocomplete', :js do
end
end
- # This context has jsut one example in each contexts in order to improve spec performance.
- context 'labels', :quarantine do
- let!(:backend) { create(:label, project: project, title: 'backend') }
- let!(:bug) { create(:label, project: project, title: 'bug') }
- let!(:feature_proposal) { create(:label, project: project, title: 'feature proposal') }
-
+ context 'labels' do
it 'opens autocomplete menu for Labels when field starts with text with item escaping HTML characters' do
create(:label, project: project, title: label_xss_title)
@@ -298,83 +293,6 @@ describe 'GFM autocomplete', :js do
expect(find('.atwho-view-ul').text).to have_content('alert label')
end
end
-
- context 'when no labels are assigned' do
- it 'shows labels' do
- note = find('#note-body')
-
- # It should show all the labels on "~".
- type(note, '~')
- wait_for_requests
- expect_labels(shown: [backend, bug, feature_proposal])
-
- # It should show all the labels on "/label ~".
- type(note, '/label ~')
- expect_labels(shown: [backend, bug, feature_proposal])
-
- # It should show all the labels on "/relabel ~".
- type(note, '/relabel ~')
- expect_labels(shown: [backend, bug, feature_proposal])
-
- # It should show no labels on "/unlabel ~".
- type(note, '/unlabel ~')
- expect_labels(not_shown: [backend, bug, feature_proposal])
- end
- end
-
- context 'when some labels are assigned' do
- before do
- issue.labels << [backend]
- end
-
- it 'shows labels' do
- note = find('#note-body')
-
- # It should show all the labels on "~".
- type(note, '~')
- wait_for_requests
- expect_labels(shown: [backend, bug, feature_proposal])
-
- # It should show only unset labels on "/label ~".
- type(note, '/label ~')
- expect_labels(shown: [bug, feature_proposal], not_shown: [backend])
-
- # It should show all the labels on "/relabel ~".
- type(note, '/relabel ~')
- expect_labels(shown: [backend, bug, feature_proposal])
-
- # It should show only set labels on "/unlabel ~".
- type(note, '/unlabel ~')
- expect_labels(shown: [backend], not_shown: [bug, feature_proposal])
- end
- end
-
- context 'when all labels are assigned' do
- before do
- issue.labels << [backend, bug, feature_proposal]
- end
-
- it 'shows labels' do
- note = find('#note-body')
-
- # It should show all the labels on "~".
- type(note, '~')
- wait_for_requests
- expect_labels(shown: [backend, bug, feature_proposal])
-
- # It should show no labels on "/label ~".
- type(note, '/label ~')
- expect_labels(not_shown: [backend, bug, feature_proposal])
-
- # It should show all the labels on "/relabel ~".
- type(note, '/relabel ~')
- expect_labels(shown: [backend, bug, feature_proposal])
-
- # It should show all the labels on "/unlabel ~".
- type(note, '/unlabel ~')
- expect_labels(shown: [backend, bug, feature_proposal])
- end
- end
end
shared_examples 'autocomplete suggestions' do
diff --git a/spec/features/merge_request/maintainer_edits_fork_spec.rb b/spec/features/merge_request/maintainer_edits_fork_spec.rb
index 7839b97122c..aead98dae23 100644
--- a/spec/features/merge_request/maintainer_edits_fork_spec.rb
+++ b/spec/features/merge_request/maintainer_edits_fork_spec.rb
@@ -18,6 +18,8 @@ describe 'a maintainer edits files on a source-branch of an MR from a fork', :js
end
before do
+ stub_feature_flags(web_ide_default: false)
+
target_project.add_maintainer(user)
sign_in(user)
diff --git a/spec/features/merge_request/user_creates_image_diff_notes_spec.rb b/spec/features/merge_request/user_creates_image_diff_notes_spec.rb
index d19408ee87f..c837a6752f9 100644
--- a/spec/features/merge_request/user_creates_image_diff_notes_spec.rb
+++ b/spec/features/merge_request/user_creates_image_diff_notes_spec.rb
@@ -222,6 +222,11 @@ describe 'Merge request > User creates image diff notes', :js do
end
def create_image_diff_note
+ expand_text = 'Click to expand it.'
+ page.all('a', text: expand_text).each do |element|
+ element.click
+ end
+
find('.js-add-image-diff-note-button', match: :first).click
find('.diff-content .note-textarea').native.send_keys('image diff test comment')
click_button 'Comment'
diff --git a/spec/features/merge_request/user_creates_merge_request_spec.rb b/spec/features/merge_request/user_creates_merge_request_spec.rb
index 38b4e4a6d1b..ea2bb1503bb 100644
--- a/spec/features/merge_request/user_creates_merge_request_spec.rb
+++ b/spec/features/merge_request/user_creates_merge_request_spec.rb
@@ -8,6 +8,8 @@ describe "User creates a merge request", :js do
let(:user) { create(:user) }
before do
+ stub_feature_flags(approval_rules: false)
+
project.add_maintainer(user)
sign_in(user)
end
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 51b78d3e7d1..19edce1b562 100644
--- a/spec/features/merge_request/user_posts_diff_notes_spec.rb
+++ b/spec/features/merge_request/user_posts_diff_notes_spec.rb
@@ -178,7 +178,7 @@ describe 'Merge request > User posts diff notes', :js do
end
end
- describe 'with muliple note forms' do
+ describe 'with multiple note forms' do
before do
visit diffs_project_merge_request_path(project, merge_request, view: 'inline')
click_diff_line(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]'))
diff --git a/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb b/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb
index 7b473faa884..97b2aa82fce 100644
--- a/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb
+++ b/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb
@@ -49,7 +49,7 @@ describe 'Merge request > User sees merge request pipelines', :js do
let!(:merge_request_pipeline) do
Ci::CreatePipelineService.new(project, user, ref: 'feature')
- .execute(:merge_request, merge_request: merge_request)
+ .execute(:merge_request_event, merge_request: merge_request)
end
before do
@@ -81,7 +81,7 @@ describe 'Merge request > User sees merge request pipelines', :js do
let!(:merge_request_pipeline_2) do
Ci::CreatePipelineService.new(project, user, ref: 'feature')
- .execute(:merge_request, merge_request: merge_request)
+ .execute(:merge_request_event, merge_request: merge_request)
end
before do
@@ -220,7 +220,7 @@ describe 'Merge request > User sees merge request pipelines', :js do
let!(:merge_request_pipeline) do
Ci::CreatePipelineService.new(forked_project, user2, ref: 'feature')
- .execute(:merge_request, merge_request: merge_request)
+ .execute(:merge_request_event, merge_request: merge_request)
end
let(:forked_project) { fork_project(project, user2, repository: true) }
@@ -263,7 +263,7 @@ describe 'Merge request > User sees merge request pipelines', :js do
let!(:merge_request_pipeline_2) do
Ci::CreatePipelineService.new(forked_project, user2, ref: 'feature')
- .execute(:merge_request, merge_request: merge_request)
+ .execute(:merge_request_event, merge_request: merge_request)
end
before do
diff --git a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb
index 90d6841af0e..9909bfb5904 100644
--- a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb
+++ b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe 'User visits the profile preferences page' do
+ include Select2Helper
+
let(:user) { create(:user) }
before do
@@ -60,6 +62,28 @@ describe 'User visits the profile preferences page' do
end
end
+ describe 'User changes their language', :js do
+ it 'creates a flash message' do
+ select2('en', from: '#user_preferred_language')
+ click_button 'Save'
+
+ wait_for_requests
+
+ expect_preferences_saved_message
+ end
+
+ it 'updates their preference' do
+ wait_for_requests
+ select2('eo', from: '#user_preferred_language')
+ click_button 'Save'
+
+ wait_for_requests
+ refresh
+
+ expect(page).to have_css('html[lang="eo"]')
+ end
+ end
+
def expect_preferences_saved_message
page.within('.flash-container') do
expect(page).to have_content('Preferences saved.')
diff --git a/spec/features/project_variables_spec.rb b/spec/features/project_variables_spec.rb
index a93df3696d2..6bdf5df1036 100644
--- a/spec/features/project_variables_spec.rb
+++ b/spec/features/project_variables_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe 'Project variables', :js do
let(:user) { create(:user) }
let(:project) { create(:project) }
- let(:variable) { create(:ci_variable, key: 'test_key', value: 'test value') }
+ let(:variable) { create(:ci_variable, key: 'test_key', value: 'test_value') }
let(:page_path) { project_settings_ci_cd_path(project) }
before do
diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb
index 6e6c299ee2e..828f6f9921e 100644
--- a/spec/features/projects/blobs/edit_spec.rb
+++ b/spec/features/projects/blobs/edit_spec.rb
@@ -9,6 +9,10 @@ describe 'Editing file blob', :js do
let(:file_path) { project.repository.ls_files(project.repository.root_ref)[1] }
let(:readme_file_path) { 'README.md' }
+ before do
+ stub_feature_flags(web_ide_default: false)
+ end
+
context 'as a developer' do
let(:user) { create(:user) }
let(:role) { :developer }
@@ -77,7 +81,7 @@ describe 'Editing file blob', :js do
click_link 'Preview'
wait_for_requests
- # the above generates two seperate lists (not embedded) in CommonMark
+ # the above generates two separate lists (not embedded) in CommonMark
expect(page).to have_content("sublist")
expect(page).not_to have_xpath("//ol//li//ul")
end
diff --git a/spec/features/projects/branches_spec.rb b/spec/features/projects/branches_spec.rb
index 97757e8da92..ee71c843b80 100644
--- a/spec/features/projects/branches_spec.rb
+++ b/spec/features/projects/branches_spec.rb
@@ -229,6 +229,38 @@ describe 'Branches' do
end
end
+ describe 'comparing branches' do
+ before do
+ sign_in(user)
+ project.add_developer(user)
+ end
+
+ shared_examples 'compares branches' do
+ it 'compares branches' do
+ visit project_branches_path(project)
+
+ page.within first('.all-branches li') do
+ click_link 'Compare'
+ end
+
+ expect(page).to have_content 'Commits'
+ expect(page).to have_link 'Create merge request'
+ end
+ end
+
+ context 'on a read-only instance' do
+ before do
+ allow(Gitlab::Database).to receive(:read_only?).and_return(true)
+ end
+
+ it_behaves_like 'compares branches'
+ end
+
+ context 'on a read-write instance' do
+ it_behaves_like 'compares branches'
+ end
+ end
+
def sorted_branches(repository, count:, sort_by:, state: nil)
branches = repository.branches_sorted_by(sort_by)
branches = branches.select { |b| state == 'active' ? b.active? : b.stale? } if state
diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb
index 0c517d5f490..66c6545204b 100644
--- a/spec/features/projects/environments/environments_spec.rb
+++ b/spec/features/projects/environments/environments_spec.rb
@@ -38,6 +38,23 @@ describe 'Environments page', :js do
end
end
+ describe 'with environments spanning multiple pages', :js do
+ before do
+ allow(Kaminari.config).to receive(:default_per_page).and_return(3)
+ create_list(:environment, 4, project: project, state: :available)
+ end
+
+ it 'should render second page of pipelines' do
+ visit_environments(project, scope: 'available')
+
+ find('.js-next-button').click
+ wait_for_requests
+
+ expect(page).to have_selector('.gl-pagination .page', count: 2)
+ expect(find('.gl-pagination .page-item.active .page-link').text).to eq("2")
+ end
+ end
+
describe 'in stopped tab page' do
it 'should show no environments' do
visit_environments(project, scope: 'stopped')
diff --git a/spec/features/projects/files/user_creates_files_spec.rb b/spec/features/projects/files/user_creates_files_spec.rb
index a4f94b7a76d..dd2964c2186 100644
--- a/spec/features/projects/files/user_creates_files_spec.rb
+++ b/spec/features/projects/files/user_creates_files_spec.rb
@@ -12,6 +12,8 @@ describe 'Projects > Files > User creates files' do
let(:user) { create(:user) }
before do
+ stub_feature_flags(web_ide_default: false)
+
project.add_maintainer(user)
sign_in(user)
end
diff --git a/spec/features/projects/files/user_edits_files_spec.rb b/spec/features/projects/files/user_edits_files_spec.rb
index 9eb65ec159c..ec3930c26db 100644
--- a/spec/features/projects/files/user_edits_files_spec.rb
+++ b/spec/features/projects/files/user_edits_files_spec.rb
@@ -9,6 +9,8 @@ describe 'Projects > Files > User edits files', :js do
let(:user) { create(:user) }
before do
+ stub_feature_flags(web_ide_default: false)
+
sign_in(user)
end
diff --git a/spec/features/projects/pages_spec.rb b/spec/features/projects/pages_spec.rb
index 435fb229b69..f564ae34f11 100644
--- a/spec/features/projects/pages_spec.rb
+++ b/spec/features/projects/pages_spec.rb
@@ -13,16 +13,6 @@ describe 'Pages' do
sign_in(user)
end
- shared_examples 'no pages deployed' do
- it 'does not see anything to destroy' do
- visit project_pages_path(project)
-
- expect(page).to have_content('Configure pages')
- expect(page).not_to have_link('Remove pages')
- expect(page).not_to have_text('Only the project owner can remove pages')
- end
- end
-
context 'when user is the owner' do
before do
project.namespace.update(owner: user)
@@ -181,7 +171,12 @@ describe 'Pages' do
end
end
- it_behaves_like 'no pages deployed'
+ it 'does not see anything to destroy' do
+ visit project_pages_path(project)
+
+ expect(page).to have_content('Configure pages')
+ expect(page).not_to have_link('Remove pages')
+ end
describe 'project settings page' do
it 'renders "Pages" tab' do
@@ -208,22 +203,6 @@ describe 'Pages' do
end
end
- context 'when the user is not the owner' do
- context 'when pages deployed' do
- before do
- allow_any_instance_of(Project).to receive(:pages_deployed?) { true }
- end
-
- it 'sees "Only the project owner can remove pages" text' do
- visit project_pages_path(project)
-
- expect(page).to have_text('Only the project owner can remove pages')
- end
- end
-
- it_behaves_like 'no pages deployed'
- end
-
describe 'HTTPS settings', :js, :https_pages_enabled do
before do
project.namespace.update(owner: user)
@@ -233,7 +212,7 @@ describe 'Pages' do
it 'tries to change the setting' do
visit project_pages_path(project)
- expect(page).to have_content("Force domains with SSL certificates to use HTTPS")
+ expect(page).to have_content("Force HTTPS (requires valid certificates)")
uncheck :project_pages_https_only
@@ -282,58 +261,52 @@ describe 'Pages' do
visit project_pages_path(project)
expect(page).not_to have_field(:project_pages_https_only)
- expect(page).not_to have_content('Force domains with SSL certificates to use HTTPS')
+ expect(page).not_to have_content('Force HTTPS (requires valid certificates)')
expect(page).not_to have_button('Save')
end
end
end
describe 'Remove page' do
- context 'when user is the owner' do
- let(:project) { create :project, :repository }
-
- before do
- project.namespace.update(owner: user)
+ let(:project) { create :project, :repository }
+
+ context 'when pages are deployed' do
+ let(:pipeline) do
+ commit_sha = project.commit('HEAD').sha
+
+ project.ci_pipelines.create(
+ ref: 'HEAD',
+ sha: commit_sha,
+ source: :push,
+ protected: false
+ )
end
- context 'when pages are deployed' do
- let(:pipeline) do
- commit_sha = project.commit('HEAD').sha
-
- project.ci_pipelines.create(
- ref: 'HEAD',
- sha: commit_sha,
- source: :push,
- protected: false
- )
- end
-
- let(:ci_build) do
- create(
- :ci_build,
- project: project,
- pipeline: pipeline,
- ref: 'HEAD',
- legacy_artifacts_file: fixture_file_upload(File.join('spec/fixtures/pages.zip')),
- legacy_artifacts_metadata: fixture_file_upload(File.join('spec/fixtures/pages.zip.meta'))
- )
- end
+ let(:ci_build) do
+ create(
+ :ci_build,
+ project: project,
+ pipeline: pipeline,
+ ref: 'HEAD',
+ legacy_artifacts_file: fixture_file_upload(File.join('spec/fixtures/pages.zip')),
+ legacy_artifacts_metadata: fixture_file_upload(File.join('spec/fixtures/pages.zip.meta'))
+ )
+ end
- before do
- result = Projects::UpdatePagesService.new(project, ci_build).execute
- expect(result[:status]).to eq(:success)
- expect(project).to be_pages_deployed
- end
+ before do
+ result = Projects::UpdatePagesService.new(project, ci_build).execute
+ expect(result[:status]).to eq(:success)
+ expect(project).to be_pages_deployed
+ end
- it 'removes the pages' do
- visit project_pages_path(project)
+ it 'removes the pages' do
+ visit project_pages_path(project)
- expect(page).to have_link('Remove pages')
+ expect(page).to have_link('Remove pages')
- click_link 'Remove pages'
+ click_link 'Remove pages'
- expect(project.pages_deployed?).to be_falsey
- end
+ expect(project.pages_deployed?).to be_falsey
end
end
end
diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
index 72ef460d315..36b8c15b8b6 100644
--- a/spec/features/projects/pipelines/pipeline_spec.rb
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -666,7 +666,7 @@ describe 'Pipeline', :js do
let(:pipeline) do
create(:ci_pipeline,
- source: :merge_request,
+ source: :merge_request_event,
project: merge_request.source_project,
ref: 'feature',
sha: merge_request.diff_head_sha,
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index ffa165c5440..88d7c9ef8bd 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -485,7 +485,7 @@ describe 'Pipelines', :js do
it 'should show updated content' do
visit project_pipelines_path(project)
wait_for_requests
- page.find('.js-next-button a').click
+ page.find('.js-next-button .page-link').click
expect(page).to have_selector('.gl-pagination .page', count: 2)
end
diff --git a/spec/features/projects/services/user_activates_issue_tracker_spec.rb b/spec/features/projects/services/user_activates_issue_tracker_spec.rb
index 7cd5b12802b..74b9a2b20cd 100644
--- a/spec/features/projects/services/user_activates_issue_tracker_spec.rb
+++ b/spec/features/projects/services/user_activates_issue_tracker_spec.rb
@@ -6,11 +6,17 @@ describe 'User activates issue tracker', :js do
let(:url) { 'http://tracker.example.com' }
- def fill_form(active = true)
+ def fill_short_form(active = true)
check 'Active' if active
fill_in 'service_project_url', with: url
fill_in 'service_issues_url', with: "#{url}/:id"
+ end
+
+ def fill_full_form(active = true)
+ fill_short_form(active)
+ check 'Active' if active
+
fill_in 'service_new_issue_url', with: url
end
@@ -21,14 +27,20 @@ describe 'User activates issue tracker', :js do
visit project_settings_integrations_path(project)
end
- shared_examples 'external issue tracker activation' do |tracker:|
+ shared_examples 'external issue tracker activation' do |tracker:, skip_new_issue_url: false|
describe 'user sets and activates the Service' do
context 'when the connection test succeeds' do
before do
stub_request(:head, url).to_return(headers: { 'Content-Type' => 'application/json' })
click_link(tracker)
- fill_form
+
+ if skip_new_issue_url
+ fill_short_form
+ else
+ fill_full_form
+ end
+
click_button('Test settings and save changes')
wait_for_requests
end
@@ -50,7 +62,13 @@ describe 'User activates issue tracker', :js do
stub_request(:head, url).to_raise(HTTParty::Error)
click_link(tracker)
- fill_form
+
+ if skip_new_issue_url
+ fill_short_form
+ else
+ fill_full_form
+ end
+
click_button('Test settings and save changes')
wait_for_requests
@@ -69,7 +87,13 @@ describe 'User activates issue tracker', :js do
describe 'user sets the service but keeps it disabled' do
before do
click_link(tracker)
- fill_form(false)
+
+ if skip_new_issue_url
+ fill_short_form(false)
+ else
+ fill_full_form(false)
+ end
+
click_button('Save changes')
end
@@ -87,6 +111,7 @@ describe 'User activates issue tracker', :js do
end
it_behaves_like 'external issue tracker activation', tracker: 'Redmine'
+ it_behaves_like 'external issue tracker activation', tracker: 'YouTrack', skip_new_issue_url: true
it_behaves_like 'external issue tracker activation', tracker: 'Bugzilla'
it_behaves_like 'external issue tracker activation', tracker: 'Custom Issue Tracker'
end
diff --git a/spec/features/projects/services/user_activates_youtrack_spec.rb b/spec/features/projects/services/user_activates_youtrack_spec.rb
new file mode 100644
index 00000000000..bb6a030c1cf
--- /dev/null
+++ b/spec/features/projects/services/user_activates_youtrack_spec.rb
@@ -0,0 +1,89 @@
+require 'spec_helper'
+
+describe 'User activates issue tracker', :js do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+
+ let(:url) { 'http://tracker.example.com' }
+
+ def fill_form(active = true)
+ check 'Active' if active
+
+ fill_in 'service_project_url', with: url
+ fill_in 'service_issues_url', with: "#{url}/:id"
+ end
+
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+
+ visit project_settings_integrations_path(project)
+ end
+
+ shared_examples 'external issue tracker activation' do |tracker:|
+ describe 'user sets and activates the Service' do
+ context 'when the connection test succeeds' do
+ before do
+ stub_request(:head, url).to_return(headers: { 'Content-Type' => 'application/json' })
+
+ click_link(tracker)
+ fill_form
+ click_button('Test settings and save changes')
+ wait_for_requests
+ end
+
+ it 'activates the service' do
+ expect(page).to have_content("#{tracker} activated.")
+ expect(current_path).to eq(project_settings_integrations_path(project))
+ end
+
+ it 'shows the link in the menu' do
+ page.within('.nav-sidebar') do
+ expect(page).to have_link(tracker, href: url)
+ end
+ end
+ end
+
+ context 'when the connection test fails' do
+ it 'activates the service' do
+ stub_request(:head, url).to_raise(HTTParty::Error)
+
+ click_link(tracker)
+ fill_form
+ click_button('Test settings and save changes')
+ wait_for_requests
+
+ expect(find('.flash-container-page')).to have_content 'Test failed.'
+ expect(find('.flash-container-page')).to have_content 'Save anyway'
+
+ find('.flash-alert .flash-action').click
+ wait_for_requests
+
+ expect(page).to have_content("#{tracker} activated.")
+ expect(current_path).to eq(project_settings_integrations_path(project))
+ end
+ end
+ end
+
+ describe 'user sets the service but keeps it disabled' do
+ before do
+ click_link(tracker)
+ fill_form(false)
+ click_button('Save changes')
+ end
+
+ it 'saves but does not activate the service' do
+ expect(page).to have_content("#{tracker} settings saved, but not activated.")
+ expect(current_path).to eq(project_settings_integrations_path(project))
+ end
+
+ it 'does not show the external tracker link in the menu' do
+ page.within('.nav-sidebar') do
+ expect(page).not_to have_link(tracker, href: url)
+ end
+ end
+ end
+ end
+
+ it_behaves_like 'external issue tracker activation', tracker: 'YouTrack'
+end
diff --git a/spec/features/projects/settings/forked_project_settings_spec.rb b/spec/features/projects/settings/forked_project_settings_spec.rb
index df33d215602..dc0278370aa 100644
--- a/spec/features/projects/settings/forked_project_settings_spec.rb
+++ b/spec/features/projects/settings/forked_project_settings_spec.rb
@@ -7,6 +7,7 @@ describe 'Projects > Settings > For a forked project', :js do
let(:forked_project) { fork_project(original_project, user) }
before do
+ stub_feature_flags(approval_rules: false)
original_project.add_maintainer(user)
forked_project.add_maintainer(user)
sign_in(user)
diff --git a/spec/features/projects/settings/operations_settings_spec.rb b/spec/features/projects/settings/operations_settings_spec.rb
index 06290c67c70..af56cb0d4ee 100644
--- a/spec/features/projects/settings/operations_settings_spec.rb
+++ b/spec/features/projects/settings/operations_settings_spec.rb
@@ -20,4 +20,81 @@ describe 'Projects > Settings > For a forked project', :js do
expect(page).to have_selector('a[title="Operations"]', visible: false)
end
end
+
+ describe 'Settings > Operations' do
+ context 'error tracking settings form' do
+ let(:sentry_list_projects_url) { 'http://sentry.example.com/api/0/projects/' }
+
+ context 'success path' do
+ let(:projects_sample_response) do
+ Gitlab::Utils.deep_indifferent_access(
+ JSON.parse(fixture_file('sentry/list_projects_sample_response.json'))
+ )
+ end
+
+ before do
+ WebMock.stub_request(:get, sentry_list_projects_url)
+ .to_return(
+ status: 200,
+ headers: { 'Content-Type' => 'application/json' },
+ body: projects_sample_response.to_json
+ )
+ end
+
+ it 'successfully fills and submits the form' do
+ visit project_settings_operations_path(project)
+
+ wait_for_requests
+
+ expect(page).to have_content('Sentry API URL')
+ expect(page.body).to include('Error Tracking')
+ expect(page).to have_button('Connect')
+
+ check('Active')
+ fill_in('error-tracking-api-host', with: 'http://sentry.example.com')
+ fill_in('error-tracking-token', with: 'token')
+
+ click_button('Connect')
+
+ within('div#project-dropdown') do
+ click_button('Select project')
+ click_button('Sentry | Internal')
+ end
+
+ click_button('Save changes')
+
+ wait_for_requests
+
+ assert_text('Your changes have been saved')
+ end
+ end
+
+ context 'project dropdown fails to load' do
+ before do
+ WebMock.stub_request(:get, sentry_list_projects_url)
+ .to_return(
+ status: 400,
+ headers: { 'Content-Type' => 'application/json' },
+ body: {
+ message: 'Sentry response code: 401'
+ }.to_json
+ )
+ end
+
+ it 'displays error message' do
+ visit project_settings_operations_path(project)
+
+ wait_for_requests
+
+ check('Active')
+ fill_in('error-tracking-api-host', with: 'http://sentry.example.com')
+ fill_in('error-tracking-token', with: 'token')
+
+ click_button('Connect')
+
+ assert_text('Connection has failed. Re-check Auth Token and try again.')
+ end
+ end
+ end
+ end
end
diff --git a/spec/features/projects/wiki/markdown_preview_spec.rb b/spec/features/projects/wiki/markdown_preview_spec.rb
index 3b469fee867..49058d1372a 100644
--- a/spec/features/projects/wiki/markdown_preview_spec.rb
+++ b/spec/features/projects/wiki/markdown_preview_spec.rb
@@ -3,6 +3,7 @@ require 'spec_helper'
describe 'Projects > Wiki > User previews markdown changes', :js do
let(:user) { create(:user) }
let(:project) { create(:project, :wiki_repo, namespace: user.namespace) }
+ let(:wiki_page) { create(:wiki_page, wiki: project.wiki, attrs: { title: 'home', content: '[some link](other-page)' }) }
let(:wiki_content) do
<<-HEREDOC
[regular link](regular)
@@ -18,9 +19,7 @@ describe 'Projects > Wiki > User previews markdown changes', :js do
sign_in(user)
- visit project_path(project)
- find('.shortcuts-wiki').click
- click_link "Create your first page"
+ visit project_wiki_path(project, wiki_page)
end
context "while creating a new wiki page" do
@@ -171,7 +170,7 @@ describe 'Projects > Wiki > User previews markdown changes', :js do
fill_in :wiki_content, with: "1. one\n - sublist\n"
click_on "Preview"
- # the above generates two seperate lists (not embedded) in CommonMark
+ # the above generates two separate lists (not embedded) in CommonMark
expect(page).to have_content("sublist")
expect(page).not_to have_xpath("//ol//li//ul")
end
diff --git a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
index 48a0d675f2d..b1a7f167977 100644
--- a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
@@ -44,13 +44,7 @@ describe "User creates wiki page" do
end
it "shows non-escaped link in the pages list", :js do
- click_link("New page")
-
- page.within("#modal-new-wiki") do
- fill_in(:new_wiki_path, with: "one/two/three-test")
-
- click_on("Create page")
- end
+ fill_in(:wiki_title, with: "one/two/three-test")
page.within(".wiki-form") do
fill_in(:wiki_content, with: "wiki content")
diff --git a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
index f76e577b0d6..dbf8af3e5bb 100644
--- a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
@@ -26,12 +26,7 @@ describe 'User updates wiki page' do
end
it 'updates a page that has a path', :js do
- click_on('New page')
-
- page.within('#modal-new-wiki') do
- fill_in(:new_wiki_path, with: 'one/two/three-test')
- click_on('Create page')
- end
+ fill_in(:wiki_title, with: 'one/two/three-test')
page.within '.wiki-form' do
fill_in(:wiki_content, with: 'wiki content')
diff --git a/spec/features/projects/wiki/user_views_wiki_page_spec.rb b/spec/features/projects/wiki/user_views_wiki_page_spec.rb
index d4691b669c1..6e28ec0d7b2 100644
--- a/spec/features/projects/wiki/user_views_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_views_wiki_page_spec.rb
@@ -22,12 +22,7 @@ describe 'User views a wiki page' do
visit(project_wikis_path(project))
click_link "Create your first page"
- click_on('New page')
-
- page.within('#modal-new-wiki') do
- fill_in(:new_wiki_path, with: 'one/two/three-test')
- click_on('Create page')
- end
+ fill_in(:wiki_title, with: 'one/two/three-test')
page.within('.wiki-form') do
fill_in(:wiki_content, with: 'wiki content')
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index bc36c6f948f..dbf0d427976 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -4,6 +4,10 @@ describe 'Project' do
include ProjectForksHelper
include MobileHelpers
+ before do
+ stub_feature_flags(approval_rules: false)
+ end
+
describe 'creating from template' do
let(:user) { create(:user) }
let(:template) { Gitlab::ProjectTemplate.find(:rails) }
diff --git a/spec/features/users/signup_spec.rb b/spec/features/users/signup_spec.rb
index bfe11ddf673..957c3cfc583 100644
--- a/spec/features/users/signup_spec.rb
+++ b/spec/features/users/signup_spec.rb
@@ -49,6 +49,34 @@ describe 'Signup' do
expect(page).to have_content("Please create a username with only alphanumeric characters.")
end
+
+ it 'shows an error border if the username contains emojis' do
+ simulate_input('#new_user_username', 'ehsan😀')
+
+ expect(find('.username')).to have_css '.gl-field-error-outline'
+ end
+
+ it 'shows an error message if the username contains emojis' do
+ simulate_input('#new_user_username', 'ehsan😀')
+
+ expect(page).to have_content("Invalid input, please avoid emojis")
+ end
+ end
+
+ describe 'user\'s full name validation', :js do
+ before do
+ visit root_path
+ click_link 'Register'
+ simulate_input('#new_user_name', 'Ehsan 🦋')
+ end
+
+ it 'shows an error border if the user\'s fullname contains an emoji' do
+ expect(find('.name')).to have_css '.gl-field-error-outline'
+ end
+
+ it 'shows an error message if the username contains emojis' do
+ expect(page).to have_content("Invalid input, please avoid emojis")
+ end
end
context 'with no errors' do
diff --git a/spec/finders/admin/runners_finder_spec.rb b/spec/finders/admin/runners_finder_spec.rb
index 0b2325cc7ca..94ccb398801 100644
--- a/spec/finders/admin/runners_finder_spec.rb
+++ b/spec/finders/admin/runners_finder_spec.rb
@@ -37,6 +37,14 @@ describe Admin::RunnersFinder do
end
end
+ context 'filter by tag_name' do
+ it 'calls the corresponding scope on Ci::Runner' do
+ expect(Ci::Runner).to receive(:tagged_with).with(%w[tag1 tag2]).and_call_original
+
+ described_class.new(params: { tag_name: %w[tag1 tag2] }).execute
+ end
+ end
+
context 'sort' do
context 'without sort param' do
it 'sorts by created_at' do
diff --git a/spec/finders/autocomplete/acts_as_taggable_on/tags_finder_spec.rb b/spec/finders/autocomplete/acts_as_taggable_on/tags_finder_spec.rb
new file mode 100644
index 00000000000..79d2f9cdb45
--- /dev/null
+++ b/spec/finders/autocomplete/acts_as_taggable_on/tags_finder_spec.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Autocomplete::ActsAsTaggableOn::TagsFinder do
+ describe '#execute' do
+ context 'with empty params' do
+ it 'returns all tags' do
+ tag1 = ActsAsTaggableOn::Tag.create!(name: 'tag1')
+ tag2 = ActsAsTaggableOn::Tag.create!(name: 'tag2')
+
+ tags = described_class.new(params: {}).execute
+
+ expect(tags).to match_array [tag1, tag2]
+ end
+ end
+
+ context 'filter by search' do
+ context 'with an empty search term' do
+ it 'returns an empty collection' do
+ ActsAsTaggableOn::Tag.create!(name: 'tag1')
+ ActsAsTaggableOn::Tag.create!(name: 'tag2')
+
+ tags = described_class.new(params: { search: '' }).execute
+
+ expect(tags).to be_empty
+ end
+ end
+
+ context 'with a search containing 2 characters' do
+ it 'returns the tag that strictly matches the search term' do
+ tag1 = ActsAsTaggableOn::Tag.create!(name: 't1')
+ ActsAsTaggableOn::Tag.create!(name: 't11')
+
+ tags = described_class.new(params: { search: 't1' }).execute
+
+ expect(tags).to match_array [tag1]
+ end
+ end
+
+ context 'with a search containing 3 characters' do
+ it 'returns the tag that partially matches the search term' do
+ tag1 = ActsAsTaggableOn::Tag.create!(name: 'tag1')
+ tag2 = ActsAsTaggableOn::Tag.create!(name: 'tag11')
+
+ tags = described_class.new(params: { search: 'ag1' }).execute
+
+ expect(tags).to match_array [tag1, tag2]
+ end
+ end
+ end
+
+ context 'limit' do
+ it 'limits the result set by the limit constant' do
+ stub_const("#{described_class}::LIMIT", 1)
+
+ ActsAsTaggableOn::Tag.create!(name: 'tag1')
+ ActsAsTaggableOn::Tag.create!(name: 'tag2')
+
+ tags = described_class.new(params: { search: 'tag' }).execute
+
+ expect(tags.count).to eq 1
+ end
+ end
+ end
+end
diff --git a/spec/finders/concerns/finder_methods_spec.rb b/spec/finders/concerns/finder_methods_spec.rb
index a4ad331f613..e074e53c2c5 100644
--- a/spec/finders/concerns/finder_methods_spec.rb
+++ b/spec/finders/concerns/finder_methods_spec.rb
@@ -12,7 +12,7 @@ describe FinderMethods do
end
def execute
- Project.all
+ Project.all.order(id: :desc)
end
end
end
@@ -38,6 +38,16 @@ describe FinderMethods do
it 'raises not found the user does not have access' do
expect { finder.find_by!(id: unauthorized_project.id) }.to raise_error(ActiveRecord::RecordNotFound)
end
+
+ it 'ignores ordering' do
+ # Memoise the finder result so we can add message expectations to it
+ relation = finder.execute
+ allow(finder).to receive(:execute).and_return(relation)
+
+ expect(relation).to receive(:reorder).with(nil).and_call_original
+
+ finder.find_by!(id: authorized_project.id)
+ end
end
describe '#find' do
@@ -66,5 +76,15 @@ describe FinderMethods do
it 'returns nil when the user does not have access' do
expect(finder.find_by(id: unauthorized_project.id)).to be_nil
end
+
+ it 'ignores ordering' do
+ # Memoise the finder result so we can add message expectations to it
+ relation = finder.execute
+ allow(finder).to receive(:execute).and_return(relation)
+
+ expect(relation).to receive(:reorder).with(nil).and_call_original
+
+ finder.find_by(id: authorized_project.id)
+ end
end
end
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index 34cb09942be..55efab7dec3 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -416,6 +416,36 @@ describe IssuesFinder do
end
end
+ context 'filtering by closed_at' do
+ let!(:closed_issue1) { create(:issue, project: project1, state: :closed, closed_at: 1.week.ago) }
+ let!(:closed_issue2) { create(:issue, project: project2, state: :closed, closed_at: 1.week.from_now) }
+ let!(:closed_issue3) { create(:issue, project: project2, state: :closed, closed_at: 2.weeks.from_now) }
+
+ context 'through closed_after' do
+ let(:params) { { state: :closed, closed_after: closed_issue3.closed_at } }
+
+ it 'returns issues closed on or after the given date' do
+ expect(issues).to contain_exactly(closed_issue3)
+ end
+ end
+
+ context 'through closed_before' do
+ let(:params) { { state: :closed, closed_before: closed_issue1.closed_at } }
+
+ it 'returns issues closed on or before the given date' do
+ expect(issues).to contain_exactly(closed_issue1)
+ end
+ end
+
+ context 'through closed_after and closed_before' do
+ let(:params) { { state: :closed, closed_after: closed_issue2.closed_at, closed_before: closed_issue3.closed_at } }
+
+ it 'returns issues closed between the given dates' do
+ expect(issues).to contain_exactly(closed_issue2, closed_issue3)
+ end
+ end
+ end
+
context 'filtering by reaction name' do
context 'user searches by no reaction' do
let(:params) { { my_reaction_emoji: 'None' } }
@@ -460,6 +490,32 @@ describe IssuesFinder do
end
end
+ context 'filtering by confidential' do
+ set(:confidential_issue) { create(:issue, project: project1, confidential: true) }
+
+ context 'no filtering' do
+ it 'returns all issues' do
+ expect(issues).to contain_exactly(issue1, issue2, issue3, issue4, confidential_issue)
+ end
+ end
+
+ context 'user filters confidential issues' do
+ let(:params) { { confidential: true } }
+
+ it 'returns only confdential issues' do
+ expect(issues).to contain_exactly(confidential_issue)
+ end
+ end
+
+ context 'user filters only public issues' do
+ let(:params) { { confidential: false } }
+
+ it 'returns only confdential issues' do
+ expect(issues).to contain_exactly(issue1, issue2, issue3, issue4)
+ end
+ end
+ end
+
context 'when the user is unauthorized' do
let(:search_user) { nil }
@@ -526,7 +582,7 @@ describe IssuesFinder do
it 'returns the number of rows for the default state' do
finder = described_class.new(user)
- expect(finder.row_count).to eq(4)
+ expect(finder.row_count).to eq(5)
end
it 'returns the number of rows for a given state' do
@@ -659,7 +715,7 @@ describe IssuesFinder do
before do
allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
- stub_feature_flags(use_subquery_for_group_issues_search: true)
+ stub_feature_flags(attempt_group_search_optimizations: true)
end
context 'when there is no search param' do
@@ -690,12 +746,20 @@ describe IssuesFinder do
end
end
- context 'when the use_subquery_for_group_issues_search flag is disabled' do
+ context 'when the attempt_group_search_optimizations flag is disabled' do
let(:params) { { search: 'foo', attempt_group_search_optimizations: true } }
before do
- stub_feature_flags(use_subquery_for_group_issues_search: false)
+ stub_feature_flags(attempt_group_search_optimizations: false)
+ end
+
+ it 'returns false' do
+ expect(finder.use_subquery_for_search?).to be_falsey
end
+ end
+
+ context 'when force_cte? is true' do
+ let(:params) { { search: 'foo', attempt_group_search_optimizations: true, force_cte: true } }
it 'returns false' do
expect(finder.use_subquery_for_search?).to be_falsey
@@ -711,72 +775,59 @@ describe IssuesFinder do
end
end
- describe '#use_cte_for_search?' do
+ describe '#use_cte_for_count?' do
let(:finder) { described_class.new(nil, params) }
before do
allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
- stub_feature_flags(use_cte_for_group_issues_search: true)
- stub_feature_flags(use_subquery_for_group_issues_search: false)
+ stub_feature_flags(attempt_group_search_optimizations: true)
end
context 'when there is no search param' do
- let(:params) { { attempt_group_search_optimizations: true } }
+ let(:params) { { attempt_group_search_optimizations: true, force_cte: true } }
it 'returns false' do
- expect(finder.use_cte_for_search?).to be_falsey
+ expect(finder.use_cte_for_count?).to be_falsey
end
end
context 'when the database is not Postgres' do
- let(:params) { { search: 'foo', attempt_group_search_optimizations: true } }
+ let(:params) { { search: 'foo', force_cte: true, attempt_group_search_optimizations: true } }
before do
allow(Gitlab::Database).to receive(:postgresql?).and_return(false)
end
it 'returns false' do
- expect(finder.use_cte_for_search?).to be_falsey
+ expect(finder.use_cte_for_count?).to be_falsey
end
end
- context 'when the attempt_group_search_optimizations param is falsey' do
+ context 'when the force_cte param is falsey' do
let(:params) { { search: 'foo' } }
it 'returns false' do
- expect(finder.use_cte_for_search?).to be_falsey
- end
- end
-
- context 'when the use_cte_for_group_issues_search flag is disabled' do
- let(:params) { { search: 'foo', attempt_group_search_optimizations: true } }
-
- before do
- stub_feature_flags(use_cte_for_group_issues_search: false)
- end
-
- it 'returns false' do
- expect(finder.use_cte_for_search?).to be_falsey
+ expect(finder.use_cte_for_count?).to be_falsey
end
end
- context 'when use_subquery_for_search? is true' do
- let(:params) { { search: 'foo', attempt_group_search_optimizations: true } }
+ context 'when the attempt_group_search_optimizations flag is disabled' do
+ let(:params) { { search: 'foo', force_cte: true, attempt_group_search_optimizations: true } }
before do
- stub_feature_flags(use_subquery_for_group_issues_search: true)
+ stub_feature_flags(attempt_group_search_optimizations: false)
end
it 'returns false' do
- expect(finder.use_cte_for_search?).to be_falsey
+ expect(finder.use_cte_for_count?).to be_falsey
end
end
context 'when all conditions are met' do
- let(:params) { { search: 'foo', attempt_group_search_optimizations: true } }
+ let(:params) { { search: 'foo', force_cte: true, attempt_group_search_optimizations: true } }
it 'returns true' do
- expect(finder.use_cte_for_search?).to be_truthy
+ expect(finder.use_cte_for_count?).to be_truthy
end
end
end
diff --git a/spec/fixtures/api/schemas/entities/diff_viewer.json b/spec/fixtures/api/schemas/entities/diff_viewer.json
index 81325cd86c6..ae0fb32d3ac 100644
--- a/spec/fixtures/api/schemas/entities/diff_viewer.json
+++ b/spec/fixtures/api/schemas/entities/diff_viewer.json
@@ -14,6 +14,17 @@
"string",
"null"
]
+ },
+ "error_message": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "collapsed": {
+ "type": [
+ "boolean"
+ ]
}
},
"additionalProperties": false
diff --git a/spec/fixtures/api/schemas/environment.json b/spec/fixtures/api/schemas/environment.json
index f1d33e3ce7b..9a10ab18c30 100644
--- a/spec/fixtures/api/schemas/environment.json
+++ b/spec/fixtures/api/schemas/environment.json
@@ -20,6 +20,7 @@
"state": { "type": "string" },
"external_url": { "$ref": "types/nullable_string.json" },
"environment_type": { "$ref": "types/nullable_string.json" },
+ "name_without_type": { "type": "string" },
"has_stop_action": { "type": "boolean" },
"environment_path": { "type": "string" },
"stop_path": { "type": "string" },
diff --git a/spec/fixtures/api/schemas/public_api/v4/group_labels.json b/spec/fixtures/api/schemas/public_api/v4/group_labels.json
index f6c327abfdd..fbde45f2904 100644
--- a/spec/fixtures/api/schemas/public_api/v4/group_labels.json
+++ b/spec/fixtures/api/schemas/public_api/v4/group_labels.json
@@ -6,6 +6,7 @@
"id" : { "type": "integer" },
"name" : { "type": "string "},
"color" : { "type": "string "},
+ "text_color" : { "type": "string "},
"description" : { "type": "string "},
"open_issues_count" : { "type": "integer "},
"closed_issues_count" : { "type": "integer "},
diff --git a/spec/fixtures/security-reports/master/gl-container-scanning-report.json b/spec/fixtures/security-reports/master/gl-container-scanning-report.json
index 68c6099836b..03dfc647162 100644
--- a/spec/fixtures/security-reports/master/gl-container-scanning-report.json
+++ b/spec/fixtures/security-reports/master/gl-container-scanning-report.json
@@ -1,11 +1,14 @@
{
"image": "registry.gitlab.com/groulot/container-scanning-test/master:5f21de6956aee99ddb68ae49498662d9872f50ff",
"unapproved": [
- "CVE-2017-18018",
- "CVE-2016-2781",
- "CVE-2017-12424",
- "CVE-2007-5686",
- "CVE-2013-4235"
+ "CVE-2017-18269",
+ "CVE-2017-16997",
+ "CVE-2018-1000001",
+ "CVE-2016-10228",
+ "CVE-2018-18520",
+ "CVE-2010-4052",
+ "CVE-2018-16869",
+ "CVE-2018-18311"
],
"vulnerabilities": [
{
@@ -87,6 +90,16 @@
"link": "https://security-tracker.debian.org/tracker/CVE-2018-18311",
"severity": "Unknown",
"fixedby": "5.24.1-3+deb9u5"
+ },
+ {
+ "featurename": "foo",
+ "featureversion": "1.3",
+ "vulnerability": "CVE-2018-666",
+ "namespace": "debian:9",
+ "description": "Foo has a vulnerability nobody cares about and whitelist.",
+ "link": "https://security-tracker.debian.org/tracker/CVE-2018-666",
+ "severity": "Unknown",
+ "fixedby": "1.4"
}
]
}
diff --git a/spec/fixtures/trace/sample_trace b/spec/fixtures/trace/sample_trace
index 3d8beb0dec2..8f9747f8143 100644
--- a/spec/fixtures/trace/sample_trace
+++ b/spec/fixtures/trace/sample_trace
@@ -1795,7 +1795,7 @@ GroupsController
when requesting a redirected path
returns not found
PUT transfer
- when transfering to a subgroup goes right
+ when transferring to a subgroup goes right
should return a notice (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
should redirect to the new path (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
when converting to a root group goes right
@@ -2299,7 +2299,7 @@ Groups::TransferService
should update subgroups path (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
should update projects path (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
should create redirect for the subgroups and projects (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
- when transfering a group with nested groups and projects
+ when transferring a group with nested groups and projects
should update subgroups path (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
should update projects path (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
should create redirect for the subgroups and projects (PENDING: around hook at ./spec/spec_helper.rb:190 did not execute the example)
@@ -2426,9 +2426,9 @@ Groups::MilestonesController
lists legacy group milestones and group milestones
#show
when there is a title parameter
- searchs for a legacy group milestone
+ searches for a legacy group milestone
when there is not a title parameter
- searchs for a group milestone
+ searches for a group milestone
behaves like milestone tabs
#merge_requests
as html
@@ -3109,11 +3109,11 @@ Pending: (Failures listed here are expected and do not affect your suite's statu
# around hook at ./spec/spec_helper.rb:186 did not execute the example
# ./spec/controllers/groups_controller_spec.rb:129
- 15) GroupsController PUT transfer when transfering to a subgroup goes right should return a notice
+ 15) GroupsController PUT transfer when transferring to a subgroup goes right should return a notice
# around hook at ./spec/spec_helper.rb:190 did not execute the example
# ./spec/controllers/groups_controller_spec.rb:516
- 16) GroupsController PUT transfer when transfering to a subgroup goes right should redirect to the new path
+ 16) GroupsController PUT transfer when transferring to a subgroup goes right should redirect to the new path
# around hook at ./spec/spec_helper.rb:190 did not execute the example
# ./spec/controllers/groups_controller_spec.rb:520
@@ -3301,15 +3301,15 @@ Pending: (Failures listed here are expected and do not affect your suite's statu
# around hook at ./spec/spec_helper.rb:190 did not execute the example
# ./spec/services/groups/transfer_service_spec.rb:341
- 63) Groups::TransferService#execute when transferring a subgroup into another group when transfering a group with nested groups and projects should update subgroups path
+ 63) Groups::TransferService#execute when transferring a subgroup into another group when transferring a group with nested groups and projects should update subgroups path
# around hook at ./spec/spec_helper.rb:190 did not execute the example
# ./spec/services/groups/transfer_service_spec.rb:363
- 64) Groups::TransferService#execute when transferring a subgroup into another group when transfering a group with nested groups and projects should update projects path
+ 64) Groups::TransferService#execute when transferring a subgroup into another group when transferring a group with nested groups and projects should update projects path
# around hook at ./spec/spec_helper.rb:190 did not execute the example
# ./spec/services/groups/transfer_service_spec.rb:375
- 65) Groups::TransferService#execute when transferring a subgroup into another group when transfering a group with nested groups and projects should create redirect for the subgroups and projects
+ 65) Groups::TransferService#execute when transferring a subgroup into another group when transferring a group with nested groups and projects should create redirect for the subgroups and projects
# around hook at ./spec/spec_helper.rb:190 did not execute the example
# ./spec/services/groups/transfer_service_spec.rb:383
diff --git a/spec/frontend/__mocks__/file_mock.js b/spec/frontend/__mocks__/file_mock.js
new file mode 100644
index 00000000000..08d725cd4e4
--- /dev/null
+++ b/spec/frontend/__mocks__/file_mock.js
@@ -0,0 +1 @@
+export default '';
diff --git a/spec/javascripts/gfm_auto_complete_spec.js b/spec/frontend/gfm_auto_complete_spec.js
index a14031f43ed..b79e6e0fe7b 100644
--- a/spec/javascripts/gfm_auto_complete_spec.js
+++ b/spec/frontend/gfm_auto_complete_spec.js
@@ -6,55 +6,63 @@ import GfmAutoComplete from '~/gfm_auto_complete';
import 'vendor/jquery.caret';
import 'vendor/jquery.atwho';
-describe('GfmAutoComplete', function() {
+import { TEST_HOST } from 'helpers/test_constants';
+import labelsFixture from 'fixtures/autocomplete_sources/labels.json'; // eslint-disable-line import/no-unresolved
+
+describe('GfmAutoComplete', () => {
const gfmAutoCompleteCallbacks = GfmAutoComplete.prototype.getDefaultCallbacks.call({
fetchData: () => {},
});
- describe('DefaultOptions.sorter', function() {
- describe('assets loading', function() {
- beforeEach(function() {
- spyOn(GfmAutoComplete, 'isLoading').and.returnValue(true);
+ let atwhoInstance;
+ let sorterValue;
+
+ describe('DefaultOptions.sorter', () => {
+ describe('assets loading', () => {
+ let items;
- this.atwhoInstance = { setting: {} };
- this.items = [];
+ beforeEach(() => {
+ jest.spyOn(GfmAutoComplete, 'isLoading').mockReturnValue(true);
- this.sorterValue = gfmAutoCompleteCallbacks.sorter.call(this.atwhoInstance, '', this.items);
+ atwhoInstance = { setting: {} };
+ items = [];
+
+ sorterValue = gfmAutoCompleteCallbacks.sorter.call(atwhoInstance, '', items);
});
- it('should disable highlightFirst', function() {
- expect(this.atwhoInstance.setting.highlightFirst).toBe(false);
+ it('should disable highlightFirst', () => {
+ expect(atwhoInstance.setting.highlightFirst).toBe(false);
});
- it('should return the passed unfiltered items', function() {
- expect(this.sorterValue).toEqual(this.items);
+ it('should return the passed unfiltered items', () => {
+ expect(sorterValue).toEqual(items);
});
});
- describe('assets finished loading', function() {
- beforeEach(function() {
- spyOn(GfmAutoComplete, 'isLoading').and.returnValue(false);
- spyOn($.fn.atwho.default.callbacks, 'sorter');
+ describe('assets finished loading', () => {
+ beforeEach(() => {
+ jest.spyOn(GfmAutoComplete, 'isLoading').mockReturnValue(false);
+ jest.spyOn($.fn.atwho.default.callbacks, 'sorter').mockImplementation(() => {});
});
- it('should enable highlightFirst if alwaysHighlightFirst is set', function() {
- const atwhoInstance = { setting: { alwaysHighlightFirst: true } };
+ it('should enable highlightFirst if alwaysHighlightFirst is set', () => {
+ atwhoInstance = { setting: { alwaysHighlightFirst: true } };
gfmAutoCompleteCallbacks.sorter.call(atwhoInstance);
expect(atwhoInstance.setting.highlightFirst).toBe(true);
});
- it('should enable highlightFirst if a query is present', function() {
- const atwhoInstance = { setting: {} };
+ it('should enable highlightFirst if a query is present', () => {
+ atwhoInstance = { setting: {} };
gfmAutoCompleteCallbacks.sorter.call(atwhoInstance, 'query');
expect(atwhoInstance.setting.highlightFirst).toBe(true);
});
- it('should call the default atwho sorter', function() {
- const atwhoInstance = { setting: {} };
+ it('should call the default atwho sorter', () => {
+ atwhoInstance = { setting: {} };
const query = 'query';
const items = [];
@@ -71,7 +79,9 @@ describe('GfmAutoComplete', function() {
const beforeInsert = (context, value) =>
gfmAutoCompleteCallbacks.beforeInsert.call(context, value);
- const atwhoInstance = { setting: { skipSpecialCharacterTest: false } };
+ beforeEach(() => {
+ atwhoInstance = { setting: { skipSpecialCharacterTest: false } };
+ });
it('should not quote if value only contains alphanumeric charecters', () => {
expect(beforeInsert(atwhoInstance, '@user1')).toBe('@user1');
@@ -96,7 +106,7 @@ describe('GfmAutoComplete', function() {
});
});
- describe('DefaultOptions.matcher', function() {
+ describe('DefaultOptions.matcher', () => {
const defaultMatcher = (context, flag, subtext) =>
gfmAutoCompleteCallbacks.matcher.call(context, flag, subtext);
@@ -108,7 +118,10 @@ describe('GfmAutoComplete', function() {
hash[el] = null;
return hash;
}, {});
- const atwhoInstance = { setting: {}, app: { controllers: flagsHash } };
+
+ beforeEach(() => {
+ atwhoInstance = { setting: {}, app: { controllers: flagsHash } };
+ });
const minLen = 1;
const maxLen = 20;
@@ -182,38 +195,38 @@ describe('GfmAutoComplete', function() {
});
});
- describe('isLoading', function() {
- it('should be true with loading data object item', function() {
+ describe('isLoading', () => {
+ it('should be true with loading data object item', () => {
expect(GfmAutoComplete.isLoading({ name: 'loading' })).toBe(true);
});
- it('should be true with loading data array', function() {
+ it('should be true with loading data array', () => {
expect(GfmAutoComplete.isLoading(['loading'])).toBe(true);
});
- it('should be true with loading data object array', function() {
+ it('should be true with loading data object array', () => {
expect(GfmAutoComplete.isLoading([{ name: 'loading' }])).toBe(true);
});
- it('should be false with actual array data', function() {
+ it('should be false with actual array data', () => {
expect(
GfmAutoComplete.isLoading([{ title: 'Foo' }, { title: 'Bar' }, { title: 'Qux' }]),
).toBe(false);
});
- it('should be false with actual data item', function() {
+ it('should be false with actual data item', () => {
expect(GfmAutoComplete.isLoading({ title: 'Foo' })).toBe(false);
});
});
- describe('Issues.insertTemplateFunction', function() {
- it('should return default template', function() {
+ describe('Issues.insertTemplateFunction', () => {
+ it('should return default template', () => {
expect(GfmAutoComplete.Issues.insertTemplateFunction({ id: 5, title: 'Some Issue' })).toBe(
'${atwho-at}${id}', // eslint-disable-line no-template-curly-in-string
);
});
- it('should return reference when reference is set', function() {
+ it('should return reference when reference is set', () => {
expect(
GfmAutoComplete.Issues.insertTemplateFunction({
id: 5,
@@ -224,14 +237,14 @@ describe('GfmAutoComplete', function() {
});
});
- describe('Issues.templateFunction', function() {
- it('should return html with id and title', function() {
+ describe('Issues.templateFunction', () => {
+ it('should return html with id and title', () => {
expect(GfmAutoComplete.Issues.templateFunction({ id: 5, title: 'Some Issue' })).toBe(
'<li><small>5</small> Some Issue</li>',
);
});
- it('should replace id with reference if reference is set', function() {
+ it('should replace id with reference if reference is set', () => {
expect(
GfmAutoComplete.Issues.templateFunction({
id: 5,
@@ -241,4 +254,90 @@ describe('GfmAutoComplete', function() {
).toBe('<li><small>grp/proj#5</small> Some Issue</li>');
});
});
+
+ describe('labels', () => {
+ const dataSources = {
+ labels: `${TEST_HOST}/autocomplete_sources/labels`,
+ };
+
+ const allLabels = labelsFixture;
+ const assignedLabels = allLabels.filter(label => label.set);
+ const unassignedLabels = allLabels.filter(label => !label.set);
+
+ let autocomplete;
+ let $textarea;
+
+ beforeEach(() => {
+ autocomplete = new GfmAutoComplete(dataSources);
+ $textarea = $('<textarea></textarea>');
+ autocomplete.setup($textarea, { labels: true });
+ });
+
+ afterEach(() => {
+ autocomplete.destroy();
+ });
+
+ const triggerDropdown = text => {
+ $textarea
+ .trigger('focus')
+ .val(text)
+ .caret('pos', -1);
+ $textarea.trigger('keyup');
+
+ return new Promise(window.requestAnimationFrame);
+ };
+
+ const getDropdownItems = () => {
+ const dropdown = document.getElementById('at-view-labels');
+ const items = dropdown.getElementsByTagName('li');
+ return [].map.call(items, item => item.textContent.trim());
+ };
+
+ const expectLabels = ({ input, output }) =>
+ triggerDropdown(input).then(() => {
+ expect(getDropdownItems()).toEqual(output.map(label => label.title));
+ });
+
+ describe('with no labels assigned', () => {
+ beforeEach(() => {
+ autocomplete.cachedData['~'] = [...unassignedLabels];
+ });
+
+ it.each`
+ input | output
+ ${'~'} | ${unassignedLabels}
+ ${'/label ~'} | ${unassignedLabels}
+ ${'/relabel ~'} | ${unassignedLabels}
+ ${'/unlabel ~'} | ${[]}
+ `('$input shows $output.length labels', expectLabels);
+ });
+
+ describe('with some labels assigned', () => {
+ beforeEach(() => {
+ autocomplete.cachedData['~'] = allLabels;
+ });
+
+ it.each`
+ input | output
+ ${'~'} | ${allLabels}
+ ${'/label ~'} | ${unassignedLabels}
+ ${'/relabel ~'} | ${allLabels}
+ ${'/unlabel ~'} | ${assignedLabels}
+ `('$input shows $output.length labels', expectLabels);
+ });
+
+ describe('with all labels assigned', () => {
+ beforeEach(() => {
+ autocomplete.cachedData['~'] = [...assignedLabels];
+ });
+
+ it.each`
+ input | output
+ ${'~'} | ${assignedLabels}
+ ${'/label ~'} | ${[]}
+ ${'/relabel ~'} | ${assignedLabels}
+ ${'/unlabel ~'} | ${assignedLabels}
+ `('$input shows $output.length labels', expectLabels);
+ });
+ });
});
diff --git a/spec/javascripts/issuable_suggestions/components/app_spec.js b/spec/frontend/issuable_suggestions/components/app_spec.js
index 7bb8e26b81a..7bb8e26b81a 100644
--- a/spec/javascripts/issuable_suggestions/components/app_spec.js
+++ b/spec/frontend/issuable_suggestions/components/app_spec.js
diff --git a/spec/javascripts/issuable_suggestions/components/item_spec.js b/spec/frontend/issuable_suggestions/components/item_spec.js
index 7bd1fe678f4..7bd1fe678f4 100644
--- a/spec/javascripts/issuable_suggestions/components/item_spec.js
+++ b/spec/frontend/issuable_suggestions/components/item_spec.js
diff --git a/spec/javascripts/issuable_suggestions/mock_data.js b/spec/frontend/issuable_suggestions/mock_data.js
index 4f0f9ef8d62..4f0f9ef8d62 100644
--- a/spec/javascripts/issuable_suggestions/mock_data.js
+++ b/spec/frontend/issuable_suggestions/mock_data.js
diff --git a/spec/javascripts/lib/utils/ajax_cache_spec.js b/spec/frontend/lib/utils/ajax_cache_spec.js
index dc0b04173bf..e2ee70b9d69 100644
--- a/spec/javascripts/lib/utils/ajax_cache_spec.js
+++ b/spec/frontend/lib/utils/ajax_cache_spec.js
@@ -94,68 +94,54 @@ describe('AjaxCache', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
- spyOn(axios, 'get').and.callThrough();
+ jest.spyOn(axios, 'get');
});
afterEach(() => {
mock.restore();
});
- it('stores and returns data from Ajax call if cache is empty', done => {
+ it('stores and returns data from Ajax call if cache is empty', () => {
mock.onGet(dummyEndpoint).reply(200, dummyResponse);
- AjaxCache.retrieve(dummyEndpoint)
- .then(data => {
- expect(data).toEqual(dummyResponse);
- expect(AjaxCache.internalStorage[dummyEndpoint]).toEqual(dummyResponse);
- })
- .then(done)
- .catch(fail);
+ return AjaxCache.retrieve(dummyEndpoint).then(data => {
+ expect(data).toEqual(dummyResponse);
+ expect(AjaxCache.internalStorage[dummyEndpoint]).toEqual(dummyResponse);
+ });
});
- it('makes no Ajax call if request is pending', done => {
+ it('makes no Ajax call if request is pending', () => {
mock.onGet(dummyEndpoint).reply(200, dummyResponse);
- AjaxCache.retrieve(dummyEndpoint)
- .then(done)
- .catch(fail);
-
- AjaxCache.retrieve(dummyEndpoint)
- .then(done)
- .catch(fail);
-
- expect(axios.get.calls.count()).toBe(1);
+ return Promise.all([
+ AjaxCache.retrieve(dummyEndpoint),
+ AjaxCache.retrieve(dummyEndpoint),
+ ]).then(() => {
+ expect(axios.get).toHaveBeenCalledTimes(1);
+ });
});
- it('returns undefined if Ajax call fails and cache is empty', done => {
+ it('returns undefined if Ajax call fails and cache is empty', () => {
const errorMessage = 'Network Error';
mock.onGet(dummyEndpoint).networkError();
- AjaxCache.retrieve(dummyEndpoint)
- .then(data => fail(`Received unexpected data: ${JSON.stringify(data)}`))
- .catch(error => {
- expect(error.message).toBe(`${dummyEndpoint}: ${errorMessage}`);
- expect(error.textStatus).toBe(errorMessage);
- done();
- })
- .catch(fail);
+ expect.assertions(2);
+ return AjaxCache.retrieve(dummyEndpoint).catch(error => {
+ expect(error.message).toBe(`${dummyEndpoint}: ${errorMessage}`);
+ expect(error.textStatus).toBe(errorMessage);
+ });
});
- it('makes no Ajax call if matching data exists', done => {
+ it('makes no Ajax call if matching data exists', () => {
AjaxCache.internalStorage[dummyEndpoint] = dummyResponse;
- mock.onGet(dummyEndpoint).reply(() => {
- fail(new Error('expected no Ajax call!'));
- });
- AjaxCache.retrieve(dummyEndpoint)
- .then(data => {
- expect(data).toBe(dummyResponse);
- })
- .then(done)
- .catch(fail);
+ return AjaxCache.retrieve(dummyEndpoint).then(data => {
+ expect(data).toBe(dummyResponse);
+ expect(axios.get).not.toHaveBeenCalled();
+ });
});
- it('makes Ajax call even if matching data exists when forceRequest parameter is provided', done => {
+ it('makes Ajax call even if matching data exists when forceRequest parameter is provided', () => {
const oldDummyResponse = {
important: 'old dummy data',
};
@@ -164,21 +150,12 @@ describe('AjaxCache', () => {
mock.onGet(dummyEndpoint).reply(200, dummyResponse);
- // Call without forceRetrieve param
- AjaxCache.retrieve(dummyEndpoint)
- .then(data => {
- expect(data).toBe(oldDummyResponse);
- })
- .then(done)
- .catch(fail);
-
- // Call with forceRetrieve param
- AjaxCache.retrieve(dummyEndpoint, true)
- .then(data => {
- expect(data).toEqual(dummyResponse);
- })
- .then(done)
- .catch(fail);
+ return Promise.all([
+ AjaxCache.retrieve(dummyEndpoint),
+ AjaxCache.retrieve(dummyEndpoint, true),
+ ]).then(data => {
+ expect(data).toEqual([oldDummyResponse, dummyResponse]);
+ });
});
});
});
diff --git a/spec/frontend/notes/components/__snapshots__/discussion_jump_to_next_button_spec.js.snap b/spec/frontend/notes/components/__snapshots__/discussion_jump_to_next_button_spec.js.snap
new file mode 100644
index 00000000000..11d65ced180
--- /dev/null
+++ b/spec/frontend/notes/components/__snapshots__/discussion_jump_to_next_button_spec.js.snap
@@ -0,0 +1,20 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`JumpToNextDiscussionButton matches the snapshot 1`] = `
+<div
+ class="btn-group"
+ role="group"
+>
+ <button
+ class="btn btn-default discussion-next-btn"
+ data-original-title="Jump to next unresolved discussion"
+ title=""
+ >
+ <icon-stub
+ cssclasses=""
+ name="comment-next"
+ size="16"
+ />
+ </button>
+</div>
+`;
diff --git a/spec/frontend/notes/components/discussion_jump_to_next_button_spec.js b/spec/frontend/notes/components/discussion_jump_to_next_button_spec.js
new file mode 100644
index 00000000000..989b0458481
--- /dev/null
+++ b/spec/frontend/notes/components/discussion_jump_to_next_button_spec.js
@@ -0,0 +1,30 @@
+import JumpToNextDiscussionButton from '~/notes/components/discussion_jump_to_next_button.vue';
+import { shallowMount } from '@vue/test-utils';
+
+describe('JumpToNextDiscussionButton', () => {
+ let wrapper;
+
+ beforeEach(() => {
+ wrapper = shallowMount(JumpToNextDiscussionButton, {
+ sync: false,
+ });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('matches the snapshot', () => {
+ expect(wrapper.vm.$el).toMatchSnapshot();
+ });
+
+ it('emits onClick event on button click', () => {
+ const button = wrapper.find({ ref: 'button' });
+
+ button.trigger('click');
+
+ expect(wrapper.emitted()).toEqual({
+ onClick: [[]],
+ });
+ });
+});
diff --git a/spec/frontend/test_setup.js b/spec/frontend/test_setup.js
index 7ad2e97e7e6..d892889b98d 100644
--- a/spec/frontend/test_setup.js
+++ b/spec/frontend/test_setup.js
@@ -1,3 +1,7 @@
+import Vue from 'vue';
+import Translate from '~/vue_shared/translate';
+import axios from '~/lib/utils/axios_utils';
+
const testTimeoutInMs = 300;
jest.setTimeout(testTimeoutInMs);
@@ -14,3 +18,17 @@ afterEach(() => {
throw new Error(`Test took too long (${elapsedTimeInMs}ms > ${testTimeoutInMs}ms)!`);
}
});
+
+// fail tests for unmocked requests
+beforeEach(done => {
+ axios.defaults.adapter = config => {
+ const error = new Error(`Unexpected unmocked request: ${JSON.stringify(config, null, 2)}`);
+ error.config = config;
+ done.fail(error);
+ return Promise.reject(error);
+ };
+
+ done();
+});
+
+Vue.use(Translate);
diff --git a/spec/graphql/features/authorization_spec.rb b/spec/graphql/features/authorization_spec.rb
new file mode 100644
index 00000000000..a229d29afa6
--- /dev/null
+++ b/spec/graphql/features/authorization_spec.rb
@@ -0,0 +1,106 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Gitlab::Graphql::Authorization' do
+ set(:user) { create(:user) }
+
+ let(:test_object) { double(name: 'My name') }
+ let(:object_type) { object_type_class }
+ let(:query_type) { query_type_class(object_type, test_object) }
+ let(:schema) { schema_class(query_type) }
+
+ let(:execute) do
+ schema.execute(
+ query_string,
+ context: { current_user: user },
+ variables: {}
+ )
+ end
+
+ let(:result) { execute['data'] }
+
+ before do
+ # By default, disallow all permissions.
+ allow(Ability).to receive(:allowed?).and_return(false)
+ end
+
+ describe 'authorizing with a single permission' do
+ let(:query_string) { '{ singlePermission() { name } }' }
+
+ subject { result['singlePermission'] }
+
+ it 'should return the protected field when user has permission' do
+ permit(:foo)
+
+ expect(subject['name']).to eq(test_object.name)
+ end
+
+ it 'should return nil when user is not authorized' do
+ expect(subject).to be_nil
+ end
+ end
+
+ describe 'authorizing with an Array of permissions' do
+ let(:query_string) { '{ permissionCollection() { name } }' }
+
+ subject { result['permissionCollection'] }
+
+ it 'should return the protected field when user has all permissions' do
+ permit(:foo, :bar)
+
+ expect(subject['name']).to eq(test_object.name)
+ end
+
+ it 'should return nil when user only has one of the permissions' do
+ permit(:foo)
+
+ expect(subject).to be_nil
+ end
+
+ it 'should return nil when user only has none of the permissions' do
+ expect(subject).to be_nil
+ end
+ end
+
+ private
+
+ def permit(*permissions)
+ permissions.each do |permission|
+ allow(Ability).to receive(:allowed?).with(user, permission, test_object).and_return(true)
+ end
+ end
+
+ def object_type_class
+ Class.new(Types::BaseObject) do
+ graphql_name 'TestObject'
+
+ field :name, GraphQL::STRING_TYPE, null: true
+ end
+ end
+
+ def query_type_class(type, object)
+ Class.new(Types::BaseObject) do
+ graphql_name 'TestQuery'
+
+ field :single_permission, type,
+ null: true,
+ authorize: :foo,
+ resolve: ->(obj, args, ctx) { object }
+
+ field :permission_collection, type,
+ null: true,
+ resolve: ->(obj, args, ctx) { object } do
+ authorize [:foo, :bar]
+ end
+ end
+ end
+
+ def schema_class(query)
+ Class.new(GraphQL::Schema) do
+ use Gitlab::Graphql::Authorize
+
+ query(query)
+ end
+ end
+end
diff --git a/spec/graphql/resolvers/base_resolver_spec.rb b/spec/graphql/resolvers/base_resolver_spec.rb
new file mode 100644
index 00000000000..e3a34762b62
--- /dev/null
+++ b/spec/graphql/resolvers/base_resolver_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Resolvers::BaseResolver do
+ include GraphqlHelpers
+
+ let(:resolver) do
+ Class.new(described_class) do
+ def resolve(**args)
+ [args, args]
+ end
+ end
+ end
+
+ describe '.single' do
+ it 'returns a subclass from the resolver' do
+ expect(resolver.single.superclass).to eq(resolver)
+ end
+
+ it 'returns the same subclass every time' do
+ expect(resolver.single.object_id).to eq(resolver.single.object_id)
+ end
+
+ it 'returns a resolver that gives the first result from the original resolver' do
+ result = resolve(resolver.single, args: { test: 1 })
+
+ expect(result).to eq(test: 1)
+ end
+ end
+end
diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb
index 1a54ab540fc..5f9c180cbb7 100644
--- a/spec/graphql/resolvers/issues_resolver_spec.rb
+++ b/spec/graphql/resolvers/issues_resolver_spec.rb
@@ -5,16 +5,63 @@ describe Resolvers::IssuesResolver do
let(:current_user) { create(:user) }
set(:project) { create(:project) }
- set(:issue) { create(:issue, project: project) }
- set(:issue2) { create(:issue, project: project, title: 'foo') }
+ set(:issue1) { create(:issue, project: project, state: :opened, created_at: 3.hours.ago, updated_at: 3.hours.ago) }
+ set(:issue2) { create(:issue, project: project, state: :closed, title: 'foo', created_at: 1.hour.ago, updated_at: 1.hour.ago, closed_at: 1.hour.ago) }
+ set(:label1) { create(:label, project: project) }
+ set(:label2) { create(:label, project: project) }
before do
project.add_developer(current_user)
+ create(:label_link, label: label1, target: issue1)
+ create(:label_link, label: label1, target: issue2)
+ create(:label_link, label: label2, target: issue2)
end
describe '#resolve' do
it 'finds all issues' do
- expect(resolve_issues).to contain_exactly(issue, issue2)
+ expect(resolve_issues).to contain_exactly(issue1, issue2)
+ end
+
+ it 'filters by state' do
+ expect(resolve_issues(state: 'opened')).to contain_exactly(issue1)
+ expect(resolve_issues(state: 'closed')).to contain_exactly(issue2)
+ end
+
+ it 'filters by labels' do
+ expect(resolve_issues(label_name: [label1.title])).to contain_exactly(issue1, issue2)
+ expect(resolve_issues(label_name: [label1.title, label2.title])).to contain_exactly(issue2)
+ end
+
+ describe 'filters by created_at' do
+ it 'filters by created_before' do
+ expect(resolve_issues(created_before: 2.hours.ago)).to contain_exactly(issue1)
+ end
+
+ it 'filters by created_after' do
+ expect(resolve_issues(created_after: 2.hours.ago)).to contain_exactly(issue2)
+ end
+ end
+
+ describe 'filters by updated_at' do
+ it 'filters by updated_before' do
+ expect(resolve_issues(updated_before: 2.hours.ago)).to contain_exactly(issue1)
+ end
+
+ it 'filters by updated_after' do
+ expect(resolve_issues(updated_after: 2.hours.ago)).to contain_exactly(issue2)
+ end
+ end
+
+ describe 'filters by closed_at' do
+ let!(:issue3) { create(:issue, project: project, state: :closed, closed_at: 3.hours.ago) }
+
+ it 'filters by closed_before' do
+ expect(resolve_issues(closed_before: 2.hours.ago)).to contain_exactly(issue3)
+ end
+
+ it 'filters by closed_after' do
+ expect(resolve_issues(closed_after: 2.hours.ago)).to contain_exactly(issue2)
+ end
end
it 'searches issues' do
@@ -22,7 +69,7 @@ describe Resolvers::IssuesResolver do
end
it 'sort issues' do
- expect(resolve_issues(sort: 'created_desc')).to eq [issue2, issue]
+ expect(resolve_issues(sort: 'created_desc')).to eq [issue2, issue1]
end
it 'returns issues user can see' do
@@ -30,27 +77,31 @@ describe Resolvers::IssuesResolver do
create(:issue, confidential: true)
- expect(resolve_issues).to contain_exactly(issue, issue2)
+ expect(resolve_issues).to contain_exactly(issue1, issue2)
+ end
+
+ it 'finds a specific issue with iid' do
+ expect(resolve_issues(iid: issue1.iid)).to contain_exactly(issue1)
end
it 'finds a specific issue with iids' do
- expect(resolve_issues(iids: issue.iid)).to contain_exactly(issue)
+ expect(resolve_issues(iids: issue1.iid)).to contain_exactly(issue1)
end
it 'finds multiple issues with iids' do
- expect(resolve_issues(iids: [issue.iid, issue2.iid]))
- .to contain_exactly(issue, issue2)
+ expect(resolve_issues(iids: [issue1.iid, issue2.iid]))
+ .to contain_exactly(issue1, issue2)
end
it 'finds only the issues within the project we are looking at' do
another_project = create(:project)
- iids = [issue, issue2].map(&:iid)
+ iids = [issue1, issue2].map(&:iid)
iids.each do |iid|
create(:issue, project: another_project, iid: iid)
end
- expect(resolve_issues(iids: iids)).to contain_exactly(issue, issue2)
+ expect(resolve_issues(iids: iids)).to contain_exactly(issue1, issue2)
end
end
diff --git a/spec/graphql/resolvers/merge_request_resolver_spec.rb b/spec/graphql/resolvers/merge_requests_resolver_spec.rb
index 73993b3a039..ab3c426b2cd 100644
--- a/spec/graphql/resolvers/merge_request_resolver_spec.rb
+++ b/spec/graphql/resolvers/merge_requests_resolver_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Resolvers::MergeRequestResolver do
+describe Resolvers::MergeRequestsResolver do
include GraphqlHelpers
set(:project) { create(:project, :repository) }
@@ -16,9 +16,17 @@ describe Resolvers::MergeRequestResolver do
let(:other_iid) { other_merge_request.iid }
describe '#resolve' do
- it 'batch-resolves merge requests by target project full path and IID' do
+ it 'batch-resolves by target project full path and individual IID' do
result = batch(max_queries: 2) do
- [resolve_mr(project, iid_1), resolve_mr(project, iid_2)]
+ resolve_mr(project, iid: iid_1) + resolve_mr(project, iid: iid_2)
+ end
+
+ expect(result).to contain_exactly(merge_request_1, merge_request_2)
+ end
+
+ it 'batch-resolves by target project full path and IIDS' do
+ result = batch(max_queries: 2) do
+ resolve_mr(project, iids: [iid_1, iid_2])
end
expect(result).to contain_exactly(merge_request_1, merge_request_2)
@@ -26,20 +34,28 @@ describe Resolvers::MergeRequestResolver do
it 'can batch-resolve merge requests from different projects' do
result = batch(max_queries: 3) do
- [resolve_mr(project, iid_1), resolve_mr(project, iid_2), resolve_mr(other_project, other_iid)]
+ resolve_mr(project, iid: iid_1) +
+ resolve_mr(project, iid: iid_2) +
+ resolve_mr(other_project, iid: other_iid)
end
expect(result).to contain_exactly(merge_request_1, merge_request_2, other_merge_request)
end
- it 'resolves an unknown iid to nil' do
- result = batch { resolve_mr(project, -1) }
+ it 'resolves an unknown iid to be empty' do
+ result = batch { resolve_mr(project, iid: -1) }
+
+ expect(result).to be_empty
+ end
+
+ it 'resolves empty iids to be empty' do
+ result = batch { resolve_mr(project, iids: []) }
- expect(result).to be_nil
+ expect(result).to be_empty
end
end
- def resolve_mr(project, iid)
- resolve(described_class, obj: project, args: { iid: iid })
+ def resolve_mr(project, args)
+ resolve(described_class, obj: project, args: args)
end
end
diff --git a/spec/graphql/types/issuable_state_enum_spec.rb b/spec/graphql/types/issuable_state_enum_spec.rb
new file mode 100644
index 00000000000..65a80fa4176
--- /dev/null
+++ b/spec/graphql/types/issuable_state_enum_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GitlabSchema.types['IssuableState'] do
+ it { expect(described_class.graphql_name).to eq('IssuableState') }
+
+ it_behaves_like 'issuable state'
+end
diff --git a/spec/graphql/types/issue_state_enum_spec.rb b/spec/graphql/types/issue_state_enum_spec.rb
new file mode 100644
index 00000000000..de19e6fc505
--- /dev/null
+++ b/spec/graphql/types/issue_state_enum_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GitlabSchema.types['IssueState'] do
+ it { expect(described_class.graphql_name).to eq('IssueState') }
+
+ it_behaves_like 'issuable state'
+end
diff --git a/spec/graphql/types/merge_request_state_enum_spec.rb b/spec/graphql/types/merge_request_state_enum_spec.rb
new file mode 100644
index 00000000000..626e33b18d3
--- /dev/null
+++ b/spec/graphql/types/merge_request_state_enum_spec.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GitlabSchema.types['MergeRequestState'] do
+ it { expect(described_class.graphql_name).to eq('MergeRequestState') }
+
+ it_behaves_like 'issuable state'
+
+ it 'exposes all the existing merge request states' do
+ expect(described_class.values.keys).to include('merged')
+ end
+end
diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb
index 01d71abfac9..e8f1c84f8d6 100644
--- a/spec/graphql/types/project_type_spec.rb
+++ b/spec/graphql/types/project_type_spec.rb
@@ -6,12 +6,18 @@ describe GitlabSchema.types['Project'] do
it { expect(described_class.graphql_name).to eq('Project') }
describe 'nested merge request' do
+ it { expect(described_class).to have_graphql_field(:merge_requests) }
it { expect(described_class).to have_graphql_field(:merge_request) }
it 'authorizes the merge request' do
expect(described_class.fields['mergeRequest'])
.to require_graphql_authorizations(:read_merge_request)
end
+
+ it 'authorizes the merge requests' do
+ expect(described_class.fields['mergeRequests'])
+ .to require_graphql_authorizations(:read_merge_request)
+ end
end
describe 'nested issues' do
diff --git a/spec/helpers/appearances_helper_spec.rb b/spec/helpers/appearances_helper_spec.rb
new file mode 100644
index 00000000000..8d717b968dd
--- /dev/null
+++ b/spec/helpers/appearances_helper_spec.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe AppearancesHelper do
+ before do
+ user = create(:user)
+ allow(helper).to receive(:current_user).and_return(user)
+ end
+
+ describe '#header_message' do
+ it 'returns nil when header message field is not set' do
+ create(:appearance)
+
+ expect(helper.header_message).to be_nil
+ end
+
+ context 'when header message is set' do
+ it 'includes current message' do
+ message = "Foo bar"
+ create(:appearance, header_message: message)
+
+ expect(helper.header_message).to include(message)
+ end
+ end
+ end
+
+ describe '#footer_message' do
+ it 'returns nil when footer message field is not set' do
+ create(:appearance)
+
+ expect(helper.footer_message).to be_nil
+ end
+
+ context 'when footer message is set' do
+ it 'includes current message' do
+ message = "Foo bar"
+ create(:appearance, footer_message: message)
+
+ expect(helper.footer_message).to include(message)
+ end
+ end
+ end
+
+ describe '#brand_image' do
+ let!(:appearance) { create(:appearance, :with_logo) }
+
+ context 'when there is a logo' do
+ it 'returns a path' do
+ expect(helper.brand_image).to match(%r(img data-src="/uploads/-/system/appearance/.*png))
+ end
+ end
+
+ context 'when there is a logo but no associated upload' do
+ before do
+ # Legacy attachments were not tracked in the uploads table
+ appearance.logo.upload.destroy
+ appearance.reload
+ end
+
+ it 'falls back to using the original path' do
+ expect(helper.brand_image).to match(%r(img data-src="/uploads/-/system/appearance/.*png))
+ end
+ end
+ end
+
+ describe '#brand_title' do
+ it 'returns the default CE title when no appearance is present' do
+ allow(helper)
+ .to receive(:current_appearance)
+ .and_return(nil)
+
+ expect(helper.brand_title).to eq('GitLab Community Edition')
+ end
+ end
+end
diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb
index f709f152c92..2bc3933809f 100644
--- a/spec/helpers/blob_helper_spec.rb
+++ b/spec/helpers/blob_helper_spec.rb
@@ -50,12 +50,20 @@ describe BlobHelper do
end
it 'returns a link with the proper route' do
+ stub_feature_flags(web_ide_default: false)
link = edit_blob_button(project, 'master', 'README.md')
expect(Capybara.string(link).find_link('Edit')[:href]).to eq("/#{project.full_path}/edit/master/README.md")
end
+ it 'returns a link with a Web IDE route' do
+ link = edit_blob_button(project, 'master', 'README.md')
+
+ expect(Capybara.string(link).find_link('Edit')[:href]).to eq("/-/ide/project/#{project.full_path}/edit/master/-/README.md")
+ end
+
it 'returns a link with the passed link_opts on the expected route' do
+ stub_feature_flags(web_ide_default: false)
link = edit_blob_button(project, 'master', 'README.md', link_opts: { mr_id: 10 })
expect(Capybara.string(link).find_link('Edit')[:href]).to eq("/#{project.full_path}/edit/master/README.md?mr_id=10")
diff --git a/spec/helpers/emails_helper_spec.rb b/spec/helpers/emails_helper_spec.rb
index 23d7e41803e..03b4c19ec22 100644
--- a/spec/helpers/emails_helper_spec.rb
+++ b/spec/helpers/emails_helper_spec.rb
@@ -142,4 +142,58 @@ describe EmailsHelper do
end
end
end
+
+ describe 'header and footer messages' do
+ context 'when email_header_and_footer_enabled is enabled' do
+ it 'returns header and footer messages' do
+ create :appearance, header_message: 'Foo', footer_message: 'Bar', email_header_and_footer_enabled: true
+
+ aggregate_failures do
+ expect(html_header_message).to eq(%{<div class="header-message" style=""><p>Foo</p></div>})
+ expect(html_footer_message).to eq(%{<div class="footer-message" style=""><p>Bar</p></div>})
+ expect(text_header_message).to eq('Foo')
+ expect(text_footer_message).to eq('Bar')
+ end
+ end
+
+ context 'when header and footer messages are empty' do
+ it 'returns nil' do
+ create :appearance, header_message: '', footer_message: '', email_header_and_footer_enabled: true
+
+ aggregate_failures do
+ expect(html_header_message).to eq(nil)
+ expect(html_footer_message).to eq(nil)
+ expect(text_header_message).to eq(nil)
+ expect(text_footer_message).to eq(nil)
+ end
+ end
+ end
+
+ context 'when header and footer messages are nil' do
+ it 'returns nil' do
+ create :appearance, header_message: nil, footer_message: nil, email_header_and_footer_enabled: true
+
+ aggregate_failures do
+ expect(html_header_message).to eq(nil)
+ expect(html_footer_message).to eq(nil)
+ expect(text_header_message).to eq(nil)
+ expect(text_footer_message).to eq(nil)
+ end
+ end
+ end
+ end
+
+ context 'when email_header_and_footer_enabled is disabled' do
+ it 'returns header and footer messages' do
+ create :appearance, header_message: 'Foo', footer_message: 'Bar', email_header_and_footer_enabled: false
+
+ aggregate_failures do
+ expect(html_header_message).to eq(nil)
+ expect(html_footer_message).to eq(nil)
+ expect(text_header_message).to eq(nil)
+ expect(text_footer_message).to eq(nil)
+ end
+ end
+ end
+ end
end
diff --git a/spec/helpers/import_helper_spec.rb b/spec/helpers/import_helper_spec.rb
index af4931e3370..6e8c13db9fe 100644
--- a/spec/helpers/import_helper_spec.rb
+++ b/spec/helpers/import_helper_spec.rb
@@ -39,59 +39,12 @@ describe ImportHelper do
end
end
- describe '#provider_project_link' do
- context 'when provider is "github"' do
- let(:github_server_url) { nil }
- let(:provider) { OpenStruct.new(name: 'github', url: github_server_url) }
+ describe '#provider_project_link_url' do
+ let(:full_path) { '/repo/path' }
+ let(:host_url) { 'http://provider.com/' }
- before do
- stub_omniauth_setting(providers: [provider])
- end
-
- context 'when provider does not specify a custom URL' do
- it 'uses default GitHub URL' do
- expect(helper.provider_project_link('github', 'octocat/Hello-World'))
- .to include('href="https://github.com/octocat/Hello-World"')
- end
- end
-
- context 'when provider specify a custom URL' do
- let(:github_server_url) { 'https://github.company.com' }
-
- it 'uses custom URL' do
- expect(helper.provider_project_link('github', 'octocat/Hello-World'))
- .to include('href="https://github.company.com/octocat/Hello-World"')
- end
- end
-
- context "when custom URL contains a '/' char at the end" do
- let(:github_server_url) { 'https://github.company.com/' }
-
- it "doesn't render double slash" do
- expect(helper.provider_project_link('github', 'octocat/Hello-World'))
- .to include('href="https://github.company.com/octocat/Hello-World"')
- end
- end
-
- context 'when provider is missing' do
- it 'uses the default URL' do
- allow(Gitlab.config.omniauth).to receive(:providers).and_return([])
-
- expect(helper.provider_project_link('github', 'octocat/Hello-World'))
- .to include('href="https://github.com/octocat/Hello-World"')
- end
- end
- end
-
- context 'when provider is "gitea"' do
- before do
- assign(:gitea_host_url, 'https://try.gitea.io/')
- end
-
- it 'uses given host' do
- expect(helper.provider_project_link('gitea', 'octocat/Hello-World'))
- .to include('href="https://try.gitea.io/octocat/Hello-World"')
- end
+ it 'appends repo full path to provider host url' do
+ expect(helper.provider_project_link_url(host_url, full_path)).to match('http://provider.com/repo/path')
end
end
end
diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb
index c04f679bcf0..012678db9c2 100644
--- a/spec/helpers/labels_helper_spec.rb
+++ b/spec/helpers/labels_helper_spec.rb
@@ -236,4 +236,17 @@ describe LabelsHelper do
expect(labels_filter_path(format: :json)).to eq(dashboard_labels_path(format: :json))
end
end
+
+ describe 'labels_sorted_by_title' do
+ it 'sorts labels alphabetically' do
+ label1 = double(:label, title: 'a')
+ label2 = double(:label, title: 'B')
+ label3 = double(:label, title: 'c')
+ label4 = double(:label, title: 'D')
+ labels = [label1, label2, label3, label4]
+
+ expect(labels_sorted_by_title(labels))
+ .to match_array([label2, label4, label1, label3])
+ end
+ end
end
diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb
index 4c395248644..e0e8ebd0c3c 100644
--- a/spec/helpers/preferences_helper_spec.rb
+++ b/spec/helpers/preferences_helper_spec.rb
@@ -110,6 +110,13 @@ describe PreferencesHelper do
end
end
+ describe '#language_choices' do
+ it 'returns an array of all available languages' do
+ expect(helper.language_choices).to be_an(Array)
+ expect(helper.language_choices.map(&:second)).to eq(Gitlab::I18n.available_locales)
+ end
+ end
+
def stub_user(messages = {})
if messages.empty?
allow(helper).to receive(:current_user).and_return(nil)
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 49895b0680b..291eafece94 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -3,6 +3,56 @@ require 'spec_helper'
describe ProjectsHelper do
include ProjectForksHelper
+ describe '#error_tracking_setting_project_json' do
+ let(:project) { create(:project) }
+
+ context 'error tracking setting does not exist' do
+ before do
+ helper.instance_variable_set(:@project, project)
+ end
+
+ it 'returns nil' do
+ expect(helper.error_tracking_setting_project_json).to be_nil
+ end
+ end
+
+ context 'error tracking setting exists' do
+ let!(:error_tracking_setting) { create(:project_error_tracking_setting, project: project) }
+
+ context 'api_url present' do
+ let(:json) do
+ {
+ name: error_tracking_setting.project_name,
+ organization_name: error_tracking_setting.organization_name,
+ organization_slug: error_tracking_setting.organization_slug,
+ slug: error_tracking_setting.project_slug
+ }.to_json
+ end
+
+ before do
+ helper.instance_variable_set(:@project, project)
+ end
+
+ it 'returns error tracking json' do
+ expect(helper.error_tracking_setting_project_json).to eq(json)
+ end
+ end
+
+ context 'api_url not present' do
+ before do
+ project.error_tracking_setting.api_url = nil
+ project.error_tracking_setting.enabled = false
+
+ helper.instance_variable_set(:@project, project)
+ end
+
+ it 'returns nil' do
+ expect(helper.error_tracking_setting_project_json).to be_nil
+ end
+ end
+ end
+ end
+
describe "#project_status_css_class" do
it "returns appropriate class" do
expect(project_status_css_class("started")).to eq("table-active")
diff --git a/spec/javascripts/badges/store/actions_spec.js b/spec/javascripts/badges/store/actions_spec.js
index 2623465ebd6..e8d5f8c3aac 100644
--- a/spec/javascripts/badges/store/actions_spec.js
+++ b/spec/javascripts/badges/store/actions_spec.js
@@ -411,7 +411,7 @@ describe('Badges store actions', () => {
it('escapes user input', done => {
spyOn(axios, 'get').and.callFake(() => Promise.resolve({ data: createDummyBadgeResponse() }));
- badgeInForm.imageUrl = '&make-sandwhich=true';
+ badgeInForm.imageUrl = '&make-sandwich=true';
badgeInForm.linkUrl = '<script>I am dangerous!</script>';
actions
@@ -422,7 +422,7 @@ describe('Badges store actions', () => {
expect(url).toMatch(`^${dummyEndpointUrl}/render?`);
expect(url).toMatch('\\?link_url=%3Cscript%3EI%20am%20dangerous!%3C%2Fscript%3E&');
- expect(url).toMatch('&image_url=%26make-sandwhich%3Dtrue$');
+ expect(url).toMatch('&image_url=%26make-sandwich%3Dtrue$');
})
.then(done)
.catch(done.fail);
diff --git a/spec/javascripts/commit/pipelines/pipelines_spec.js b/spec/javascripts/commit/pipelines/pipelines_spec.js
index 04c8ab44405..fec01b1f0a3 100644
--- a/spec/javascripts/commit/pipelines/pipelines_spec.js
+++ b/spec/javascripts/commit/pipelines/pipelines_spec.js
@@ -87,7 +87,7 @@ describe('Pipelines table in Commits and Merge requests', function() {
};
vm.$nextTick(() => {
- vm.$el.querySelector('.js-next-button a').click();
+ vm.$el.querySelector('.js-next-button .page-link').click();
expect(vm.updateContent).toHaveBeenCalledWith({ page: '2' });
done();
diff --git a/spec/javascripts/diffs/components/app_spec.js b/spec/javascripts/diffs/components/app_spec.js
index a2cbc0f3c72..d81c433cca6 100644
--- a/spec/javascripts/diffs/components/app_spec.js
+++ b/spec/javascripts/diffs/components/app_spec.js
@@ -1,15 +1,24 @@
import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { GlLoadingIcon } from '@gitlab/ui';
import { TEST_HOST } from 'spec/test_constants';
import App from '~/diffs/components/app.vue';
import NoChanges from '~/diffs/components/no_changes.vue';
import DiffFile from '~/diffs/components/diff_file.vue';
+import Mousetrap from 'mousetrap';
+import CompareVersions from '~/diffs/components/compare_versions.vue';
+import HiddenFilesWarning from '~/diffs/components/hidden_files_warning.vue';
+import CommitWidget from '~/diffs/components/commit_widget.vue';
+import TreeList from '~/diffs/components/tree_list.vue';
import createDiffsStore from '../create_diffs_store';
+import diffsMockData from '../mock_data/merge_request_diffs';
+
+const mergeRequestDiff = { version_index: 1 };
describe('diffs/components/app', () => {
const oldMrTabs = window.mrTabs;
let store;
- let vm;
+ let wrapper;
function createComponent(props = {}, extendStore = () => {}) {
const localVue = createLocalVue();
@@ -21,7 +30,7 @@ describe('diffs/components/app', () => {
extendStore(store);
- vm = shallowMount(localVue.extend(App), {
+ wrapper = shallowMount(localVue.extend(App), {
localVue,
propsData: {
endpoint: `${TEST_HOST}/diff/endpoint`,
@@ -38,7 +47,6 @@ describe('diffs/components/app', () => {
// setup globals (needed for component to mount :/)
window.mrTabs = jasmine.createSpyObj('mrTabs', ['resetViewContainer']);
window.mrTabs.expandViewContainer = jasmine.createSpy();
- window.location.hash = 'ABC_123';
});
afterEach(() => {
@@ -46,23 +54,96 @@ describe('diffs/components/app', () => {
window.mrTabs = oldMrTabs;
// reset component
- vm.destroy();
+ wrapper.destroy();
+ });
+
+ it('displays loading icon on loading', () => {
+ createComponent({}, ({ state }) => {
+ state.diffs.isLoading = true;
+ });
+
+ expect(wrapper.contains(GlLoadingIcon)).toBe(true);
+ });
+
+ it('displays diffs container when not loading', () => {
+ createComponent();
+
+ expect(wrapper.contains(GlLoadingIcon)).toBe(false);
+ expect(wrapper.contains('#diffs')).toBe(true);
});
it('does not show commit info', () => {
createComponent();
- expect(vm.contains('.blob-commit-info')).toBe(false);
+ expect(wrapper.contains('.blob-commit-info')).toBe(false);
+ });
+
+ describe('row highlighting', () => {
+ beforeEach(() => {
+ window.location.hash = 'ABC_123';
+ });
+
+ it('sets highlighted row if hash exists in location object', done => {
+ createComponent({
+ shouldShow: true,
+ });
+
+ // Component uses $nextTick so we wait until that has finished
+ setTimeout(() => {
+ expect(store.state.diffs.highlightedRow).toBe('ABC_123');
+
+ done();
+ });
+ });
+
+ it('marks current diff file based on currently highlighted row', done => {
+ createComponent({
+ shouldShow: true,
+ });
+
+ // Component uses $nextTick so we wait until that has finished
+ setTimeout(() => {
+ expect(store.state.diffs.currentDiffFileId).toBe('ABC');
+
+ done();
+ });
+ });
+ });
+
+ describe('resizable', () => {
+ afterEach(() => {
+ localStorage.removeItem('mr_tree_list_width');
+ });
+
+ it('sets initial width when no localStorage has been set', () => {
+ createComponent();
+
+ expect(wrapper.vm.treeWidth).toEqual(320);
+ });
+
+ it('sets initial width to localStorage size', () => {
+ localStorage.setItem('mr_tree_list_width', '200');
+
+ createComponent();
+
+ expect(wrapper.vm.treeWidth).toEqual(200);
+ });
+
+ it('sets width of tree list', () => {
+ createComponent();
+
+ expect(wrapper.find('.js-diff-tree-list').element.style.width).toEqual('320px');
+ });
});
- it('sets highlighted row if hash exists in location object', done => {
+ it('marks current diff file based on currently highlighted row', done => {
createComponent({
shouldShow: true,
});
// Component uses $nextTick so we wait until that has finished
setTimeout(() => {
- expect(store.state.diffs.highlightedRow).toBe('ABC_123');
+ expect(store.state.diffs.currentDiffFileId).toBe('ABC');
done();
});
@@ -72,27 +153,248 @@ describe('diffs/components/app', () => {
it('renders empty state when no diff files exist', () => {
createComponent();
- expect(vm.contains(NoChanges)).toBe(true);
+ expect(wrapper.contains(NoChanges)).toBe(true);
});
it('does not render empty state when diff files exist', () => {
- createComponent({}, () => {
- store.state.diffs.diffFiles.push({
+ createComponent({}, ({ state }) => {
+ state.diffs.diffFiles.push({
id: 1,
});
});
- expect(vm.contains(NoChanges)).toBe(false);
- expect(vm.findAll(DiffFile).length).toBe(1);
+ expect(wrapper.contains(NoChanges)).toBe(false);
+ expect(wrapper.findAll(DiffFile).length).toBe(1);
});
it('does not render empty state when versions match', () => {
+ createComponent({}, ({ state }) => {
+ state.diffs.startVersion = mergeRequestDiff;
+ state.diffs.mergeRequestDiff = mergeRequestDiff;
+ });
+
+ expect(wrapper.contains(NoChanges)).toBe(false);
+ });
+ });
+
+ describe('keyboard shortcut navigation', () => {
+ const mappings = {
+ '[': -1,
+ k: -1,
+ ']': +1,
+ j: +1,
+ };
+ let spy;
+
+ describe('visible app', () => {
+ beforeEach(() => {
+ spy = jasmine.createSpy('spy');
+
+ createComponent({
+ shouldShow: true,
+ });
+ wrapper.setMethods({
+ jumpToFile: spy,
+ });
+ });
+
+ it('calls `jumpToFile()` with correct parameter whenever pre-defined key is pressed', done => {
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ Object.keys(mappings).forEach(function(key) {
+ Mousetrap.trigger(key);
+
+ expect(spy.calls.mostRecent().args).toEqual([mappings[key]]);
+ });
+
+ expect(spy.calls.count()).toEqual(Object.keys(mappings).length);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not call `jumpToFile()` when unknown key is pressed', done => {
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ Mousetrap.trigger('d');
+
+ expect(spy).not.toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('hideen app', () => {
+ beforeEach(() => {
+ spy = jasmine.createSpy('spy');
+
+ createComponent({
+ shouldShow: false,
+ });
+ wrapper.setMethods({
+ jumpToFile: spy,
+ });
+ });
+
+ it('stops calling `jumpToFile()` when application is hidden', done => {
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ Object.keys(mappings).forEach(function(key) {
+ Mousetrap.trigger(key);
+
+ expect(spy).not.toHaveBeenCalled();
+ });
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+ });
+
+ describe('jumpToFile', () => {
+ let spy;
+
+ beforeEach(() => {
+ spy = jasmine.createSpy();
+
createComponent({}, () => {
- store.state.diffs.startVersion = { version_index: 1 };
- store.state.diffs.mergeRequestDiff = { version_index: 1 };
+ store.state.diffs.diffFiles = [
+ { file_hash: '111', file_path: '111.js' },
+ { file_hash: '222', file_path: '222.js' },
+ { file_hash: '333', file_path: '333.js' },
+ ];
+ });
+
+ wrapper.setMethods({
+ scrollToFile: spy,
});
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('jumps to next and previous files in the list', done => {
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ wrapper.vm.jumpToFile(+1);
+
+ expect(spy.calls.mostRecent().args).toEqual(['222.js']);
+ store.state.diffs.currentDiffFileId = '222';
+ wrapper.vm.jumpToFile(+1);
+
+ expect(spy.calls.mostRecent().args).toEqual(['333.js']);
+ store.state.diffs.currentDiffFileId = '333';
+ wrapper.vm.jumpToFile(-1);
+
+ expect(spy.calls.mostRecent().args).toEqual(['222.js']);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not jump to previous file from the first one', done => {
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ store.state.diffs.currentDiffFileId = '333';
+
+ expect(wrapper.vm.currentDiffIndex).toEqual(2);
+
+ wrapper.vm.jumpToFile(+1);
+
+ expect(wrapper.vm.currentDiffIndex).toEqual(2);
+ expect(spy).not.toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not jump to next file from the last one', done => {
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ expect(wrapper.vm.currentDiffIndex).toEqual(0);
+
+ wrapper.vm.jumpToFile(-1);
+
+ expect(wrapper.vm.currentDiffIndex).toEqual(0);
+ expect(spy).not.toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('diffs', () => {
+ it('should render compare versions component', () => {
+ createComponent({}, ({ state }) => {
+ state.diffs.mergeRequestDiffs = diffsMockData;
+ state.diffs.targetBranchName = 'target-branch';
+ state.diffs.mergeRequestDiff = mergeRequestDiff;
+ });
+
+ expect(wrapper.contains(CompareVersions)).toBe(true);
+ expect(wrapper.find(CompareVersions).props()).toEqual(
+ jasmine.objectContaining({
+ targetBranch: {
+ branchName: 'target-branch',
+ versionIndex: -1,
+ path: '',
+ },
+ mergeRequestDiffs: diffsMockData,
+ mergeRequestDiff,
+ }),
+ );
+ });
+
+ it('should render hidden files warning if render overflow warning is present', () => {
+ createComponent({}, ({ state }) => {
+ state.diffs.renderOverflowWarning = true;
+ state.diffs.realSize = '5';
+ state.diffs.plainDiffPath = 'plain diff path';
+ state.diffs.emailPatchPath = 'email patch path';
+ state.diffs.size = 1;
+ });
+
+ expect(wrapper.contains(HiddenFilesWarning)).toBe(true);
+ expect(wrapper.find(HiddenFilesWarning).props()).toEqual(
+ jasmine.objectContaining({
+ total: '5',
+ plainDiffPath: 'plain diff path',
+ emailPatchPath: 'email patch path',
+ visible: 1,
+ }),
+ );
+ });
+
+ it('should display commit widget if store has a commit', () => {
+ createComponent({}, () => {
+ store.state.diffs.commit = {
+ author: 'John Doe',
+ };
+ });
+
+ expect(wrapper.contains(CommitWidget)).toBe(true);
+ });
+
+ it('should display diff file if there are diff files', () => {
+ createComponent({}, ({ state }) => {
+ state.diffs.diffFiles.push({ sha: '123' });
+ });
+
+ expect(wrapper.contains(DiffFile)).toBe(true);
+ });
+
+ it('should render tree list', () => {
+ createComponent();
- expect(vm.contains(NoChanges)).toBe(false);
+ expect(wrapper.find(TreeList).exists()).toBe(true);
});
});
});
diff --git a/spec/javascripts/diffs/components/changed_files_dropdown_spec.js b/spec/javascripts/diffs/components/changed_files_dropdown_spec.js
deleted file mode 100644
index 7237274eb43..00000000000
--- a/spec/javascripts/diffs/components/changed_files_dropdown_spec.js
+++ /dev/null
@@ -1 +0,0 @@
-// TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034
diff --git a/spec/javascripts/diffs/components/compare_versions_dropdown_spec.js b/spec/javascripts/diffs/components/compare_versions_dropdown_spec.js
index 53b9ac22fc0..8a3834d542f 100644
--- a/spec/javascripts/diffs/components/compare_versions_dropdown_spec.js
+++ b/spec/javascripts/diffs/components/compare_versions_dropdown_spec.js
@@ -1,34 +1,161 @@
-// TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034
import { shallowMount, createLocalVue } from '@vue/test-utils';
import CompareVersionsDropdown from '~/diffs/components/compare_versions_dropdown.vue';
import diffsMockData from '../mock_data/merge_request_diffs';
+import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
+
+const localVue = createLocalVue();
+const targetBranch = { branchName: 'tmp-wine-dev', versionIndex: -1 };
+const startVersion = { version_index: 4 };
+const mergeRequestVersion = {
+ version_path: '123',
+};
+const baseVersionPath = '/gnuwget/wget2/merge_requests/6/diffs?diff_id=37';
describe('CompareVersionsDropdown', () => {
let wrapper;
- const targetBranch = { branchName: 'tmp-wine-dev', versionIndex: -1 };
- const factory = (options = {}) => {
- const localVue = createLocalVue();
+ const findSelectedVersion = () => wrapper.find('.dropdown-menu-toggle');
+ const findVersionsListElements = () => wrapper.findAll('li');
+ const findLinkElement = index =>
+ findVersionsListElements()
+ .at(index)
+ .find('a');
+ const findLastLink = () => findLinkElement(findVersionsListElements().length - 1);
- wrapper = shallowMount(CompareVersionsDropdown, { localVue, ...options });
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(localVue.extend(CompareVersionsDropdown), {
+ localVue,
+ sync: false,
+ propsData: { ...props },
+ });
};
afterEach(() => {
wrapper.destroy();
});
- it('should render a correct base version link', () => {
- factory({
- propsData: {
- baseVersionPath: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=37',
+ describe('selected version name', () => {
+ it('shows latest version when latest is selected', () => {
+ createComponent({
+ mergeRequestVersion,
+ startVersion,
+ otherVersions: diffsMockData,
+ });
+
+ expect(findSelectedVersion().text()).toBe('latest version');
+ });
+
+ it('shows target branch name for base branch', () => {
+ createComponent({
+ targetBranch,
+ });
+
+ expect(findSelectedVersion().text()).toBe('tmp-wine-dev');
+ });
+
+ it('shows correct version for non-base and non-latest branches', () => {
+ createComponent({
+ startVersion,
+ targetBranch,
+ });
+
+ expect(findSelectedVersion().text()).toBe(`version ${startVersion.version_index}`);
+ });
+ });
+
+ describe('target versions list', () => {
+ it('should have the same length as otherVersions if merge request version is present', () => {
+ createComponent({
+ mergeRequestVersion,
+ otherVersions: diffsMockData,
+ });
+
+ expect(findVersionsListElements().length).toEqual(diffsMockData.length);
+ });
+
+ it('should have an otherVersions length plus 1 if no merge request version is present', () => {
+ createComponent({
+ targetBranch,
+ otherVersions: diffsMockData,
+ });
+
+ expect(findVersionsListElements().length).toEqual(diffsMockData.length + 1);
+ });
+
+ it('should have base branch link as active on base branch', () => {
+ createComponent({
+ targetBranch,
+ otherVersions: diffsMockData,
+ });
+
+ expect(findLastLink().classes()).toContain('is-active');
+ });
+
+ it('should have correct branch link as active if start version present', () => {
+ createComponent({
+ targetBranch,
+ startVersion,
+ otherVersions: diffsMockData,
+ });
+
+ expect(findLinkElement(0).classes()).toContain('is-active');
+ });
+
+ it('should render a correct base version link', () => {
+ createComponent({
+ baseVersionPath,
otherVersions: diffsMockData.slice(1),
targetBranch,
- },
+ });
+
+ expect(findLastLink().attributes('href')).toEqual(baseVersionPath);
+ expect(findLastLink().text()).toContain('(base)');
+ });
+
+ it('should not render commits count if no showCommitsCount is passed', () => {
+ createComponent({
+ otherVersions: diffsMockData,
+ targetBranch,
+ });
+
+ const commitsCount = diffsMockData[0].commits_count;
+
+ expect(findLinkElement(0).text()).not.toContain(`${commitsCount} commit`);
+ });
+
+ it('should render correct commits count if showCommitsCount is passed', () => {
+ createComponent({
+ otherVersions: diffsMockData,
+ targetBranch,
+ showCommitCount: true,
+ });
+
+ const commitsCount = diffsMockData[0].commits_count;
+
+ expect(findLinkElement(0).text()).toContain(`${commitsCount} commit`);
+ });
+
+ it('should render correct commit sha', () => {
+ createComponent({
+ otherVersions: diffsMockData,
+ targetBranch,
+ });
+
+ const commitShaElement = findLinkElement(0).find('.commit-sha');
+
+ expect(commitShaElement.text()).toBe(diffsMockData[0].short_commit_sha);
});
- const links = wrapper.findAll('a');
- const lastLink = links.wrappers[links.length - 1];
+ it('should render correct time-ago ', () => {
+ createComponent({
+ otherVersions: diffsMockData,
+ targetBranch,
+ });
+
+ const timeAgoElement = findLinkElement(0).find(TimeAgo);
- expect(lastLink.attributes('href')).toEqual(wrapper.props('baseVersionPath'));
+ expect(timeAgoElement.exists()).toBe(true);
+ expect(timeAgoElement.props('time')).toBe(diffsMockData[0].created_at);
+ });
});
});
diff --git a/spec/javascripts/diffs/components/diff_content_spec.js b/spec/javascripts/diffs/components/diff_content_spec.js
index 9e158327a77..a1bb51963d6 100644
--- a/spec/javascripts/diffs/components/diff_content_spec.js
+++ b/spec/javascripts/diffs/components/diff_content_spec.js
@@ -6,6 +6,7 @@ import { GREEN_BOX_IMAGE_URL, RED_BOX_IMAGE_URL } from 'spec/test_constants';
import '~/behaviors/markdown/render_gfm';
import diffFileMockData from '../mock_data/diff_file';
import discussionsMockData from '../mock_data/diff_discussions';
+import { diffViewerModes } from '~/ide/constants';
describe('DiffContent', () => {
const Component = Vue.extend(DiffContentComponent);
@@ -52,26 +53,39 @@ describe('DiffContent', () => {
describe('empty files', () => {
beforeEach(() => {
- vm.diffFile.empty = true;
vm.diffFile.highlighted_diff_lines = [];
vm.diffFile.parallel_diff_lines = [];
});
- it('should render a message', done => {
+ it('should render a no preview message if viewer returns no preview', done => {
+ vm.diffFile.viewer.name = diffViewerModes.no_preview;
vm.$nextTick(() => {
const block = vm.$el.querySelector('.diff-viewer .nothing-here-block');
expect(block).not.toBe(null);
- expect(block.textContent.trim()).toContain('Empty file');
+ expect(block.textContent.trim()).toContain('No preview for this file type');
+
+ done();
+ });
+ });
+
+ it('should render a not diffable message if viewer returns not diffable', done => {
+ vm.diffFile.viewer.name = diffViewerModes.not_diffable;
+ vm.$nextTick(() => {
+ const block = vm.$el.querySelector('.diff-viewer .nothing-here-block');
+
+ expect(block).not.toBe(null);
+ expect(block.textContent.trim()).toContain(
+ 'This diff was suppressed by a .gitattributes entry',
+ );
done();
});
});
it('should not render multiple messages', done => {
- vm.diffFile.mode_changed = true;
vm.diffFile.b_mode = '100755';
- vm.diffFile.viewer.name = 'mode_changed';
+ vm.diffFile.viewer.name = diffViewerModes.mode_changed;
vm.$nextTick(() => {
expect(vm.$el.querySelectorAll('.nothing-here-block').length).toBe(1);
@@ -81,6 +95,7 @@ describe('DiffContent', () => {
});
it('should not render diff table', done => {
+ vm.diffFile.viewer.name = diffViewerModes.no_preview;
vm.$nextTick(() => {
expect(vm.$el.querySelector('table')).toBe(null);
@@ -157,6 +172,7 @@ describe('DiffContent', () => {
vm.diffFile.new_sha = 'DEF';
vm.diffFile.old_path = 'test.abc';
vm.diffFile.old_sha = 'ABC';
+ vm.diffFile.viewer.name = diffViewerModes.added;
vm.$nextTick(() => {
expect(el.querySelectorAll('.js-diff-inline-view').length).toEqual(0);
diff --git a/spec/javascripts/diffs/components/diff_file_header_spec.js b/spec/javascripts/diffs/components/diff_file_header_spec.js
index 787a81fd88f..1201f066d2f 100644
--- a/spec/javascripts/diffs/components/diff_file_header_spec.js
+++ b/spec/javascripts/diffs/components/diff_file_header_spec.js
@@ -4,15 +4,15 @@ import diffsModule from '~/diffs/store/modules';
import notesModule from '~/notes/stores/modules';
import DiffFileHeader from '~/diffs/components/diff_file_header.vue';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import diffDiscussionsMockData from '../mock_data/diff_discussions';
+import { diffViewerModes } from '~/ide/constants';
Vue.use(Vuex);
-const discussionFixture = 'merge_requests/diff_discussion.json';
-
describe('diff_file_header', () => {
let vm;
let props;
- const diffDiscussionMock = getJSONFixture(discussionFixture)[0];
+ const diffDiscussionMock = diffDiscussionsMockData;
const Component = Vue.extend(DiffFileHeader);
const store = new Vuex.Store({
@@ -303,13 +303,13 @@ describe('diff_file_header', () => {
});
it('displays old and new path if the file was renamed', () => {
- props.diffFile.renamed_file = true;
+ props.diffFile.viewer.name = diffViewerModes.renamed;
vm = mountComponentWithStore(Component, { props, store });
expect(filePaths()).toHaveLength(2);
- expect(filePaths()[0]).toHaveText(props.diffFile.old_path);
- expect(filePaths()[1]).toHaveText(props.diffFile.new_path);
+ expect(filePaths()[0]).toHaveText(props.diffFile.old_path_html);
+ expect(filePaths()[1]).toHaveText(props.diffFile.new_path_html);
});
});
@@ -319,14 +319,12 @@ describe('diff_file_header', () => {
const button = vm.$el.querySelector('.btn-clipboard');
expect(button).not.toBe(null);
- expect(button.dataset.clipboardText).toBe(
- '{"text":"files/ruby/popen.rb","gfm":"`files/ruby/popen.rb`"}',
- );
+ expect(button.dataset.clipboardText).toBe('{"text":"CHANGELOG.rb","gfm":"`CHANGELOG.rb`"}');
});
describe('file mode', () => {
it('it displays old and new file mode if it changed', () => {
- props.diffFile.mode_changed = true;
+ props.diffFile.viewer.name = diffViewerModes.mode_changed;
vm = mountComponentWithStore(Component, { props, store });
@@ -338,7 +336,7 @@ describe('diff_file_header', () => {
});
it('does not display the file mode if it has not changed', () => {
- props.diffFile.mode_changed = false;
+ props.diffFile.viewer.name = diffViewerModes.text;
vm = mountComponentWithStore(Component, { props, store });
@@ -493,5 +491,89 @@ describe('diff_file_header', () => {
});
});
});
+
+ describe('file actions', () => {
+ it('should not render if diff file has a submodule', () => {
+ props.diffFile.submodule = 'submodule';
+ vm = mountComponentWithStore(Component, { props, store });
+
+ expect(vm.$el.querySelector('.file-actions')).toEqual(null);
+ });
+
+ it('should not render if add merge request buttons is false', () => {
+ props.addMergeRequestButtons = false;
+ vm = mountComponentWithStore(Component, { props, store });
+
+ expect(vm.$el.querySelector('.file-actions')).toEqual(null);
+ });
+
+ describe('with add merge request buttons enabled', () => {
+ beforeEach(() => {
+ props.addMergeRequestButtons = true;
+ props.diffFile.edit_path = 'edit-path';
+ });
+
+ const viewReplacedFileButton = () => vm.$el.querySelector('.js-view-replaced-file');
+ const viewFileButton = () => vm.$el.querySelector('.js-view-file-button');
+ const externalUrl = () => vm.$el.querySelector('.js-external-url');
+
+ it('should render if add merge request buttons is true and diff file does not have a submodule', () => {
+ vm = mountComponentWithStore(Component, { props, store });
+
+ expect(vm.$el.querySelector('.file-actions')).not.toEqual(null);
+ });
+
+ it('should not render view replaced file button if no replaced view path is present', () => {
+ vm = mountComponentWithStore(Component, { props, store });
+
+ expect(viewReplacedFileButton()).toEqual(null);
+ });
+
+ it('should render view replaced file button if replaced view path is present', () => {
+ props.diffFile.replaced_view_path = 'replaced-view-path';
+ vm = mountComponentWithStore(Component, { props, store });
+
+ expect(viewReplacedFileButton()).not.toEqual(null);
+ expect(viewReplacedFileButton().getAttribute('href')).toBe('replaced-view-path');
+ });
+
+ it('should render correct file view button path', () => {
+ props.diffFile.view_path = 'view-path';
+ vm = mountComponentWithStore(Component, { props, store });
+
+ expect(viewFileButton().getAttribute('href')).toBe('view-path');
+ });
+
+ it('should not render external url view link if diff file has no external url', () => {
+ vm = mountComponentWithStore(Component, { props, store });
+
+ expect(externalUrl()).toEqual(null);
+ });
+
+ it('should render external url view link if diff file has external url', () => {
+ props.diffFile.external_url = 'external_url';
+ vm = mountComponentWithStore(Component, { props, store });
+
+ expect(externalUrl()).not.toEqual(null);
+ expect(externalUrl().getAttribute('href')).toBe('external_url');
+ });
+ });
+
+ describe('without file blob', () => {
+ beforeEach(() => {
+ props.diffFile.blob = null;
+ props.addMergeRequestButtons = true;
+ vm = mountComponentWithStore(Component, { props, store });
+ });
+
+ it('should not render toggle discussions button', () => {
+ expect(vm.$el.querySelector('.js-btn-vue-toggle-comments')).toEqual(null);
+ });
+
+ it('should not render edit button', () => {
+ expect(vm.$el.querySelector('.js-edit-blob')).toEqual(null);
+ });
+ });
+ });
});
});
diff --git a/spec/javascripts/diffs/components/diff_file_spec.js b/spec/javascripts/diffs/components/diff_file_spec.js
index 1af49282c36..65a1c9b8f15 100644
--- a/spec/javascripts/diffs/components/diff_file_spec.js
+++ b/spec/javascripts/diffs/components/diff_file_spec.js
@@ -1,5 +1,6 @@
import Vue from 'vue';
import DiffFileComponent from '~/diffs/components/diff_file.vue';
+import { diffViewerModes, diffViewerErrors } from '~/ide/constants';
import store from '~/mr_notes/stores';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import diffFileMockData from '../mock_data/diff_file';
@@ -27,7 +28,6 @@ describe('DiffFile', () => {
expect(el.querySelector('.file-title-name').innerText.indexOf(file_path)).toBeGreaterThan(-1);
expect(el.querySelector('.js-syntax-highlight')).toBeDefined();
- expect(vm.file.renderIt).toEqual(false);
vm.file.renderIt = true;
vm.$nextTick(() => {
@@ -38,8 +38,8 @@ describe('DiffFile', () => {
describe('collapsed', () => {
it('should not have file content', done => {
expect(vm.$el.querySelectorAll('.diff-content').length).toEqual(1);
- expect(vm.file.collapsed).toEqual(false);
- vm.file.collapsed = true;
+ expect(vm.isCollapsed).toEqual(false);
+ vm.isCollapsed = true;
vm.file.renderIt = true;
vm.$nextTick(() => {
@@ -50,9 +50,8 @@ describe('DiffFile', () => {
});
it('should have collapsed text and link', done => {
- vm.file.renderIt = true;
- vm.file.collapsed = false;
- vm.file.highlighted_diff_lines = null;
+ vm.renderIt = true;
+ vm.isCollapsed = true;
vm.$nextTick(() => {
expect(vm.$el.innerText).toContain('This diff is collapsed');
@@ -63,8 +62,8 @@ describe('DiffFile', () => {
});
it('should have collapsed text and link even before rendered', done => {
- vm.file.renderIt = false;
- vm.file.collapsed = true;
+ vm.renderIt = false;
+ vm.isCollapsed = true;
vm.$nextTick(() => {
expect(vm.$el.innerText).toContain('This diff is collapsed');
@@ -75,10 +74,10 @@ describe('DiffFile', () => {
});
it('should be collapsed for renamed files', done => {
- vm.file.renderIt = true;
- vm.file.collapsed = false;
+ vm.renderIt = true;
+ vm.isCollapsed = false;
vm.file.highlighted_diff_lines = null;
- vm.file.renamed_file = true;
+ vm.file.viewer.name = diffViewerModes.renamed;
vm.$nextTick(() => {
expect(vm.$el.innerText).not.toContain('This diff is collapsed');
@@ -88,10 +87,10 @@ describe('DiffFile', () => {
});
it('should be collapsed for mode changed files', done => {
- vm.file.renderIt = true;
- vm.file.collapsed = false;
+ vm.renderIt = true;
+ vm.isCollapsed = false;
vm.file.highlighted_diff_lines = null;
- vm.file.mode_changed = true;
+ vm.file.viewer.name = diffViewerModes.mode_changed;
vm.$nextTick(() => {
expect(vm.$el.innerText).not.toContain('This diff is collapsed');
@@ -101,7 +100,7 @@ describe('DiffFile', () => {
});
it('should have loading icon while loading a collapsed diffs', done => {
- vm.file.collapsed = true;
+ vm.isCollapsed = true;
vm.isLoadingCollapsedDiff = true;
vm.$nextTick(() => {
@@ -116,7 +115,7 @@ describe('DiffFile', () => {
describe('too large diff', () => {
it('should have too large warning and blob link', done => {
const BLOB_LINK = '/file/view/path';
- vm.file.too_large = true;
+ vm.file.viewer.error = diffViewerErrors.too_large;
vm.file.view_path = BLOB_LINK;
vm.$nextTick(() => {
@@ -140,11 +139,11 @@ describe('DiffFile', () => {
vm.file.highlighted_diff_lines = undefined;
vm.file.parallel_diff_lines = [];
- vm.file.collapsed = true;
+ vm.isCollapsed = true;
vm.$nextTick()
.then(() => {
- vm.file.collapsed = false;
+ vm.isCollapsed = false;
return vm.$nextTick();
})
diff --git a/spec/javascripts/diffs/components/edit_button_spec.js b/spec/javascripts/diffs/components/edit_button_spec.js
index 7237274eb43..ccdae4cb312 100644
--- a/spec/javascripts/diffs/components/edit_button_spec.js
+++ b/spec/javascripts/diffs/components/edit_button_spec.js
@@ -1 +1,61 @@
-// TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import EditButton from '~/diffs/components/edit_button.vue';
+
+const localVue = createLocalVue();
+const editPath = 'test-path';
+
+describe('EditButton', () => {
+ let wrapper;
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(EditButton, {
+ localVue,
+ sync: false,
+ propsData: { ...props },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('has correct href attribute', () => {
+ createComponent({
+ editPath,
+ canCurrentUserFork: false,
+ });
+
+ expect(wrapper.attributes('href')).toBe(editPath);
+ });
+
+ it('emits a show fork message event if current user can fork', () => {
+ createComponent({
+ editPath,
+ canCurrentUserFork: true,
+ });
+ wrapper.trigger('click');
+
+ expect(wrapper.emitted('showForkMessage')).toBeTruthy();
+ });
+
+ it('doesnt emit a show fork message event if current user cannot fork', () => {
+ createComponent({
+ editPath,
+ canCurrentUserFork: false,
+ });
+ wrapper.trigger('click');
+
+ expect(wrapper.emitted('showForkMessage')).toBeFalsy();
+ });
+
+ it('doesnt emit a show fork message event if current user can modify blob', () => {
+ createComponent({
+ editPath,
+ canCurrentUserFork: true,
+ canModifyBlob: true,
+ });
+ wrapper.trigger('click');
+
+ expect(wrapper.emitted('showForkMessage')).toBeFalsy();
+ });
+});
diff --git a/spec/javascripts/diffs/components/hidden_files_warning_spec.js b/spec/javascripts/diffs/components/hidden_files_warning_spec.js
index 7237274eb43..5bf5ddd27bd 100644
--- a/spec/javascripts/diffs/components/hidden_files_warning_spec.js
+++ b/spec/javascripts/diffs/components/hidden_files_warning_spec.js
@@ -1 +1,48 @@
-// TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import HiddenFilesWarning from '~/diffs/components/hidden_files_warning.vue';
+
+const localVue = createLocalVue();
+const propsData = {
+ total: '10',
+ visible: 5,
+ plainDiffPath: 'plain-diff-path',
+ emailPatchPath: 'email-patch-path',
+};
+
+describe('HiddenFilesWarning', () => {
+ let wrapper;
+
+ const createComponent = () => {
+ wrapper = shallowMount(HiddenFilesWarning, {
+ localVue,
+ sync: false,
+ propsData,
+ });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('has a correct plain diff URL', () => {
+ const plainDiffLink = wrapper.findAll('a').wrappers.filter(x => x.text() === 'Plain diff')[0];
+
+ expect(plainDiffLink.attributes('href')).toBe(propsData.plainDiffPath);
+ });
+
+ it('has a correct email patch URL', () => {
+ const emailPatchLink = wrapper.findAll('a').wrappers.filter(x => x.text() === 'Email patch')[0];
+
+ expect(emailPatchLink.attributes('href')).toBe(propsData.emailPatchPath);
+ });
+
+ it('has a correct visible/total files text', () => {
+ const filesText = wrapper.find('strong');
+
+ expect(filesText.text()).toBe('5 of 10');
+ });
+});
diff --git a/spec/javascripts/diffs/components/tree_list_spec.js b/spec/javascripts/diffs/components/tree_list_spec.js
index 9e556698f34..cd7bf6405e5 100644
--- a/spec/javascripts/diffs/components/tree_list_spec.js
+++ b/spec/javascripts/diffs/components/tree_list_spec.js
@@ -28,7 +28,7 @@ describe('Diffs tree list component', () => {
localStorage.removeItem('mr_diff_tree_list');
- vm = mountComponentWithStore(Component, { store });
+ vm = mountComponentWithStore(Component, { store, props: { hideFileStats: false } });
});
afterEach(() => {
@@ -77,6 +77,16 @@ describe('Diffs tree list component', () => {
expect(vm.$el.querySelectorAll('.file-row')[1].textContent).toContain('app');
});
+ it('hides file stats', done => {
+ vm.hideFileStats = true;
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.file-row-stats')).toBe(null);
+
+ done();
+ });
+ });
+
it('calls toggleTreeOpen when clicking folder', () => {
spyOn(vm.$store, 'dispatch').and.stub();
diff --git a/spec/javascripts/diffs/mock_data/diff_discussions.js b/spec/javascripts/diffs/mock_data/diff_discussions.js
index c1e9f791925..4a091b4580b 100644
--- a/spec/javascripts/diffs/mock_data/diff_discussions.js
+++ b/spec/javascripts/diffs/mock_data/diff_discussions.js
@@ -266,7 +266,7 @@ export default {
blob_name: 'CHANGELOG',
blob_icon: '<i aria-hidden="true" data-hidden="true" class="fa fa-file-text-o fa-fw"></i>',
file_hash: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a',
- file_path: 'CHANGELOG',
+ file_path: 'CHANGELOG.rb',
new_file: false,
deleted_file: false,
renamed_file: false,
@@ -286,7 +286,7 @@ export default {
content_sha: 'c48ee0d1bf3b30453f5b32250ce03134beaa6d13',
stored_externally: null,
external_storage: null,
- old_path_html: ['CHANGELOG', 'CHANGELOG'],
+ old_path_html: 'CHANGELOG_OLD',
new_path_html: 'CHANGELOG',
context_lines_path:
'/gitlab-org/gitlab-test/blob/c48ee0d1bf3b30453f5b32250ce03134beaa6d13/CHANGELOG/diff',
@@ -485,6 +485,10 @@ export default {
},
},
],
+ viewer: {
+ name: 'text',
+ error: null,
+ },
},
diff_discussion: true,
truncated_diff_lines: [
diff --git a/spec/javascripts/diffs/mock_data/diff_file.js b/spec/javascripts/diffs/mock_data/diff_file.js
index 031c9842f2f..32af9ea8ddd 100644
--- a/spec/javascripts/diffs/mock_data/diff_file.js
+++ b/spec/javascripts/diffs/mock_data/diff_file.js
@@ -25,6 +25,8 @@ export default {
text: true,
viewer: {
name: 'text',
+ error: null,
+ collapsed: false,
},
added_lines: 2,
removed_lines: 0,
diff --git a/spec/javascripts/diffs/store/actions_spec.js b/spec/javascripts/diffs/store/actions_spec.js
index b53ae4cecfd..e47c7906fcb 100644
--- a/spec/javascripts/diffs/store/actions_spec.js
+++ b/spec/javascripts/diffs/store/actions_spec.js
@@ -29,6 +29,7 @@ import actions, {
renderFileForDiscussionId,
setRenderTreeList,
setShowWhitespace,
+ setRenderIt,
} from '~/diffs/store/actions';
import eventHub from '~/notes/event_hub';
import * as types from '~/diffs/store/mutation_types';
@@ -99,9 +100,10 @@ describe('DiffsStoreActions', () => {
});
describe('setHighlightedRow', () => {
- it('should set lineHash and fileHash of highlightedRow', () => {
+ it('should mark currently selected diff and set lineHash and fileHash of highlightedRow', () => {
testAction(setHighlightedRow, 'ABC_123', {}, [
{ type: types.SET_HIGHLIGHTED_ROW, payload: 'ABC_123' },
+ { type: types.UPDATE_CURRENT_DIFF_FILE_ID, payload: 'ABC' },
]);
});
});
@@ -262,12 +264,16 @@ describe('DiffsStoreActions', () => {
{
id: 1,
renderIt: false,
- collapsed: false,
+ viewer: {
+ collapsed: false,
+ },
},
{
id: 2,
renderIt: false,
- collapsed: false,
+ viewer: {
+ collapsed: false,
+ },
},
],
};
@@ -708,22 +714,6 @@ describe('DiffsStoreActions', () => {
expect(commit).toHaveBeenCalledWith(types.UPDATE_CURRENT_DIFF_FILE_ID, 'test');
});
-
- it('resets currentDiffId after timeout', () => {
- const state = {
- treeEntries: {
- path: {
- fileHash: 'test',
- },
- },
- };
-
- scrollToFile({ state, commit }, 'path');
-
- jasmine.clock().tick(1000);
-
- expect(commit.calls.argsFor(1)).toEqual([types.UPDATE_CURRENT_DIFF_FILE_ID, '']);
- });
});
describe('toggleShowTreeList', () => {
@@ -766,7 +756,9 @@ describe('DiffsStoreActions', () => {
diffFiles: [
{
file_hash: 'HASH',
- collapsed,
+ viewer: {
+ collapsed,
+ },
renderIt,
},
],
@@ -849,4 +841,10 @@ describe('DiffsStoreActions', () => {
expect(window.history.pushState).toHaveBeenCalled();
});
});
+
+ describe('setRenderIt', () => {
+ it('commits RENDER_FILE', done => {
+ testAction(setRenderIt, 'file', {}, [{ type: types.RENDER_FILE, payload: 'file' }], [], done);
+ });
+ });
});
diff --git a/spec/javascripts/diffs/store/getters_spec.js b/spec/javascripts/diffs/store/getters_spec.js
index 4f69dc92ab8..eab5703dfb2 100644
--- a/spec/javascripts/diffs/store/getters_spec.js
+++ b/spec/javascripts/diffs/store/getters_spec.js
@@ -51,13 +51,13 @@ describe('Diffs Module Getters', () => {
describe('hasCollapsedFile', () => {
it('returns true when all files are collapsed', () => {
- localState.diffFiles = [{ collapsed: true }, { collapsed: true }];
+ localState.diffFiles = [{ viewer: { collapsed: true } }, { viewer: { collapsed: true } }];
expect(getters.hasCollapsedFile(localState)).toEqual(true);
});
it('returns true when at least one file is collapsed', () => {
- localState.diffFiles = [{ collapsed: false }, { collapsed: true }];
+ localState.diffFiles = [{ viewer: { collapsed: false } }, { viewer: { collapsed: true } }];
expect(getters.hasCollapsedFile(localState)).toEqual(true);
});
@@ -270,4 +270,24 @@ describe('Diffs Module Getters', () => {
expect(getters.diffFilesLength(localState)).toBe(2);
});
});
+
+ describe('currentDiffIndex', () => {
+ it('returns index of currently selected diff in diffList', () => {
+ localState.diffFiles = [{ file_hash: '111' }, { file_hash: '222' }, { file_hash: '333' }];
+ localState.currentDiffFileId = '222';
+
+ expect(getters.currentDiffIndex(localState)).toEqual(1);
+
+ localState.currentDiffFileId = '333';
+
+ expect(getters.currentDiffIndex(localState)).toEqual(2);
+ });
+
+ it('returns 0 if no diff is selected yet or diff is not found', () => {
+ localState.diffFiles = [{ file_hash: '111' }, { file_hash: '222' }, { file_hash: '333' }];
+ localState.currentDiffFileId = '';
+
+ expect(getters.currentDiffIndex(localState)).toEqual(0);
+ });
+ });
});
diff --git a/spec/javascripts/diffs/store/mutations_spec.js b/spec/javascripts/diffs/store/mutations_spec.js
index a6f3f9b9dc3..09ee691b602 100644
--- a/spec/javascripts/diffs/store/mutations_spec.js
+++ b/spec/javascripts/diffs/store/mutations_spec.js
@@ -121,8 +121,14 @@ describe('DiffsStoreMutations', () => {
describe('ADD_COLLAPSED_DIFFS', () => {
it('should update the state with the given data for the given file hash', () => {
const fileHash = 123;
- const state = { diffFiles: [{}, { file_hash: fileHash, existing_field: 0 }] };
- const data = { diff_files: [{ file_hash: fileHash, extra_field: 1, existing_field: 1 }] };
+ const state = {
+ diffFiles: [{}, { file_hash: fileHash, existing_field: 0 }],
+ };
+ const data = {
+ diff_files: [
+ { file_hash: fileHash, extra_field: 1, existing_field: 1, viewer: { name: 'text' } },
+ ],
+ };
mutations[types.ADD_COLLAPSED_DIFFS](state, { file: state.diffFiles[1], data });
diff --git a/spec/javascripts/diffs/store/utils_spec.js b/spec/javascripts/diffs/store/utils_spec.js
index c5e413a29d8..599ea9cd420 100644
--- a/spec/javascripts/diffs/store/utils_spec.js
+++ b/spec/javascripts/diffs/store/utils_spec.js
@@ -14,7 +14,7 @@ import { MERGE_REQUEST_NOTEABLE_TYPE } from '~/notes/constants';
import diffFileMockData from '../mock_data/diff_file';
import { noteableDataMock } from '../../notes/mock_data';
-const getDiffFileMock = () => Object.assign({}, diffFileMockData);
+const getDiffFileMock = () => JSON.parse(JSON.stringify(diffFileMockData));
describe('DiffsStoreUtils', () => {
describe('findDiffFile', () => {
@@ -80,30 +80,44 @@ describe('DiffsStoreUtils', () => {
});
describe('addContextLines', () => {
- it('should add context lines properly with bottom parameter', () => {
+ it('should add context lines', () => {
const diffFile = getDiffFileMock();
const inlineLines = diffFile.highlighted_diff_lines;
const parallelLines = diffFile.parallel_diff_lines;
const lineNumbers = { oldLineNumber: 3, newLineNumber: 5 };
- const contextLines = [{ lineNumber: 42 }];
- const options = { inlineLines, parallelLines, contextLines, lineNumbers, bottom: true };
+ const contextLines = [{ lineNumber: 42, line_code: '123' }];
+ const options = { inlineLines, parallelLines, contextLines, lineNumbers };
const inlineIndex = utils.findIndexInInlineLines(inlineLines, lineNumbers);
const parallelIndex = utils.findIndexInParallelLines(parallelLines, lineNumbers);
const normalizedParallelLine = {
left: options.contextLines[0],
right: options.contextLines[0],
+ line_code: '123',
};
utils.addContextLines(options);
- expect(inlineLines[inlineLines.length - 1]).toEqual(contextLines[0]);
- expect(parallelLines[parallelLines.length - 1]).toEqual(normalizedParallelLine);
+ expect(inlineLines[inlineIndex]).toEqual(contextLines[0]);
+ expect(parallelLines[parallelIndex]).toEqual(normalizedParallelLine);
+ });
+
+ it('should add context lines properly with bottom parameter', () => {
+ const diffFile = getDiffFileMock();
+ const inlineLines = diffFile.highlighted_diff_lines;
+ const parallelLines = diffFile.parallel_diff_lines;
+ const lineNumbers = { oldLineNumber: 3, newLineNumber: 5 };
+ const contextLines = [{ lineNumber: 42, line_code: '123' }];
+ const options = { inlineLines, parallelLines, contextLines, lineNumbers, bottom: true };
+ const normalizedParallelLine = {
+ left: options.contextLines[0],
+ right: options.contextLines[0],
+ line_code: '123',
+ };
- delete options.bottom;
utils.addContextLines(options);
- expect(inlineLines[inlineIndex]).toEqual(contextLines[0]);
- expect(parallelLines[parallelIndex]).toEqual(normalizedParallelLine);
+ expect(inlineLines[inlineLines.length - 1]).toEqual(contextLines[0]);
+ expect(parallelLines[parallelLines.length - 1]).toEqual(normalizedParallelLine);
});
});
@@ -587,7 +601,7 @@ describe('DiffsStoreUtils', () => {
it('returns mode_changed if key has no match', () => {
expect(
utils.getDiffMode({
- mode_changed: true,
+ viewer: { name: 'mode_changed' },
}),
).toBe('mode_changed');
});
diff --git a/spec/javascripts/environments/environment_table_spec.js b/spec/javascripts/environments/environment_table_spec.js
index 52895f35f3a..ecd28594873 100644
--- a/spec/javascripts/environments/environment_table_spec.js
+++ b/spec/javascripts/environments/environment_table_spec.js
@@ -31,4 +31,224 @@ describe('Environment table', () => {
expect(vm.$el.getAttribute('class')).toContain('ci-table');
});
+
+ describe('sortEnvironments', () => {
+ it('should sort environments by last updated', () => {
+ const mockItems = [
+ {
+ name: 'old',
+ size: 3,
+ isFolder: false,
+ last_deployment: {
+ created_at: new Date(2019, 0, 5).toISOString(),
+ },
+ },
+ {
+ name: 'new',
+ size: 3,
+ isFolder: false,
+ last_deployment: {
+ created_at: new Date(2019, 1, 5).toISOString(),
+ },
+ },
+ {
+ name: 'older',
+ size: 3,
+ isFolder: false,
+ last_deployment: {
+ created_at: new Date(2018, 0, 5).toISOString(),
+ },
+ },
+ {
+ name: 'an environment with no deployment',
+ },
+ ];
+
+ vm = mountComponent(Component, {
+ environments: mockItems,
+ canReadEnvironment: true,
+ });
+
+ const [old, newer, older, noDeploy] = mockItems;
+
+ expect(vm.sortEnvironments(mockItems)).toEqual([newer, old, older, noDeploy]);
+ });
+
+ it('should push environments with no deployments to the bottom', () => {
+ const mockItems = [
+ {
+ name: 'production',
+ size: 1,
+ id: 2,
+ state: 'available',
+ external_url: 'https://google.com/production',
+ environment_type: null,
+ last_deployment: null,
+ has_stop_action: false,
+ environment_path: '/Commit451/lab-coat/environments/2',
+ stop_path: '/Commit451/lab-coat/environments/2/stop',
+ folder_path: '/Commit451/lab-coat/environments/folders/production',
+ created_at: '2019-01-17T16:26:10.064Z',
+ updated_at: '2019-01-17T16:27:37.717Z',
+ can_stop: true,
+ },
+ {
+ name: 'review/225addcibuildstatus',
+ size: 2,
+ isFolder: true,
+ isLoadingFolderContent: false,
+ folderName: 'review',
+ isOpen: false,
+ children: [],
+ id: 12,
+ state: 'available',
+ external_url: 'https://google.com/review/225addcibuildstatus',
+ environment_type: 'review',
+ last_deployment: null,
+ has_stop_action: false,
+ environment_path: '/Commit451/lab-coat/environments/12',
+ stop_path: '/Commit451/lab-coat/environments/12/stop',
+ folder_path: '/Commit451/lab-coat/environments/folders/review',
+ created_at: '2019-01-17T16:27:37.877Z',
+ updated_at: '2019-01-17T16:27:37.883Z',
+ can_stop: true,
+ },
+ {
+ name: 'staging',
+ size: 1,
+ id: 1,
+ state: 'available',
+ external_url: 'https://google.com/staging',
+ environment_type: null,
+ last_deployment: {
+ created_at: '2019-01-17T16:26:15.125Z',
+ scheduled_actions: [],
+ },
+ },
+ ];
+
+ vm = mountComponent(Component, {
+ environments: mockItems,
+ canReadEnvironment: true,
+ });
+
+ const [prod, review, staging] = mockItems;
+
+ expect(vm.sortEnvironments(mockItems)).toEqual([review, staging, prod]);
+ });
+
+ it('should sort environments by folder first', () => {
+ const mockItems = [
+ {
+ name: 'old',
+ size: 3,
+ isFolder: false,
+ last_deployment: {
+ created_at: new Date(2019, 0, 5).toISOString(),
+ },
+ },
+ {
+ name: 'new',
+ size: 3,
+ isFolder: false,
+ last_deployment: {
+ created_at: new Date(2019, 1, 5).toISOString(),
+ },
+ },
+ {
+ name: 'older',
+ size: 3,
+ isFolder: true,
+ children: [],
+ },
+ ];
+
+ vm = mountComponent(Component, {
+ environments: mockItems,
+ canReadEnvironment: true,
+ });
+
+ const [old, newer, older] = mockItems;
+
+ expect(vm.sortEnvironments(mockItems)).toEqual([older, newer, old]);
+ });
+
+ it('should break ties by name', () => {
+ const mockItems = [
+ {
+ name: 'old',
+ isFolder: false,
+ },
+ {
+ name: 'new',
+ isFolder: false,
+ },
+ {
+ folderName: 'older',
+ isFolder: true,
+ },
+ ];
+
+ vm = mountComponent(Component, {
+ environments: mockItems,
+ canReadEnvironment: true,
+ });
+
+ const [old, newer, older] = mockItems;
+
+ expect(vm.sortEnvironments(mockItems)).toEqual([older, newer, old]);
+ });
+ });
+
+ describe('sortedEnvironments', () => {
+ it('it should sort children as well', () => {
+ const mockItems = [
+ {
+ name: 'production',
+ last_deployment: null,
+ },
+ {
+ name: 'review/225addcibuildstatus',
+ isFolder: true,
+ folderName: 'review',
+ isOpen: true,
+ children: [
+ {
+ name: 'review/225addcibuildstatus',
+ last_deployment: {
+ created_at: '2019-01-17T16:26:15.125Z',
+ },
+ },
+ {
+ name: 'review/master',
+ last_deployment: {
+ created_at: '2019-02-17T16:26:15.125Z',
+ },
+ },
+ ],
+ },
+ {
+ name: 'staging',
+ last_deployment: {
+ created_at: '2019-01-17T16:26:15.125Z',
+ },
+ },
+ ];
+ const [production, review, staging] = mockItems;
+ const [addcibuildstatus, master] = mockItems[1].children;
+
+ vm = mountComponent(Component, {
+ environments: mockItems,
+ canReadEnvironment: true,
+ });
+
+ expect(vm.sortedEnvironments.map(env => env.name)).toEqual([
+ review.name,
+ staging.name,
+ production.name,
+ ]);
+
+ expect(vm.sortedEnvironments[0].children).toEqual([master, addcibuildstatus]);
+ });
+ });
});
diff --git a/spec/javascripts/environments/environments_app_spec.js b/spec/javascripts/environments/environments_app_spec.js
index 9220f7a264f..b6a244f7cd3 100644
--- a/spec/javascripts/environments/environments_app_spec.js
+++ b/spec/javascripts/environments/environments_app_spec.js
@@ -94,7 +94,7 @@ describe('Environment', () => {
it('should make an API request when page is clicked', done => {
spyOn(component, 'updateContent');
setTimeout(() => {
- component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
+ component.$el.querySelector('.gl-pagination li:nth-child(5) .page-link').click();
expect(component.updateContent).toHaveBeenCalledWith({ scope: 'available', page: '2' });
done();
diff --git a/spec/javascripts/environments/folder/environments_folder_view_spec.js b/spec/javascripts/environments/folder/environments_folder_view_spec.js
index d9ee7e74e28..69ddd26eef1 100644
--- a/spec/javascripts/environments/folder/environments_folder_view_spec.js
+++ b/spec/javascripts/environments/folder/environments_folder_view_spec.js
@@ -107,7 +107,7 @@ describe('Environments Folder View', () => {
it('should make an API request when changing page', done => {
spyOn(component, 'updateContent');
setTimeout(() => {
- component.$el.querySelector('.gl-pagination .js-last-button a').click();
+ component.$el.querySelector('.gl-pagination .js-last-button .page-link').click();
expect(component.updateContent).toHaveBeenCalledWith({
scope: component.scope,
diff --git a/spec/javascripts/error_tracking_settings/components/app_spec.js b/spec/javascripts/error_tracking_settings/components/app_spec.js
new file mode 100644
index 00000000000..2e52a45fd34
--- /dev/null
+++ b/spec/javascripts/error_tracking_settings/components/app_spec.js
@@ -0,0 +1,63 @@
+import Vuex from 'vuex';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import ErrorTrackingSettings from '~/error_tracking_settings/components/app.vue';
+import ErrorTrackingForm from '~/error_tracking_settings/components/error_tracking_form.vue';
+import ProjectDropdown from '~/error_tracking_settings/components/project_dropdown.vue';
+import createStore from '~/error_tracking_settings/store';
+import { TEST_HOST } from 'spec/test_constants';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('error tracking settings app', () => {
+ let store;
+ let wrapper;
+
+ function mountComponent() {
+ wrapper = shallowMount(ErrorTrackingSettings, {
+ localVue,
+ store, // Override the imported store
+ propsData: {
+ initialEnabled: 'true',
+ initialApiHost: TEST_HOST,
+ initialToken: 'someToken',
+ initialProject: null,
+ listProjectsEndpoint: TEST_HOST,
+ operationsSettingsEndpoint: TEST_HOST,
+ },
+ });
+ }
+
+ beforeEach(() => {
+ store = createStore();
+
+ mountComponent();
+ });
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ }
+ });
+
+ describe('section', () => {
+ it('renders the form and dropdown', () => {
+ expect(wrapper.find(ErrorTrackingForm).exists()).toBeTruthy();
+ expect(wrapper.find(ProjectDropdown).exists()).toBeTruthy();
+ });
+
+ it('renders the Save Changes button', () => {
+ expect(wrapper.find('.js-error-tracking-button').exists()).toBeTruthy();
+ });
+
+ it('enables the button by default', () => {
+ expect(wrapper.find('.js-error-tracking-button').attributes('disabled')).toBeFalsy();
+ });
+
+ it('disables the button when saving', () => {
+ store.state.settingsLoading = true;
+
+ expect(wrapper.find('.js-error-tracking-button').attributes('disabled')).toBeTruthy();
+ });
+ });
+});
diff --git a/spec/javascripts/error_tracking_settings/components/error_tracking_form_spec.js b/spec/javascripts/error_tracking_settings/components/error_tracking_form_spec.js
new file mode 100644
index 00000000000..23e57c4bbf1
--- /dev/null
+++ b/spec/javascripts/error_tracking_settings/components/error_tracking_form_spec.js
@@ -0,0 +1,91 @@
+import Vuex from 'vuex';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { GlButton, GlFormInput } from '@gitlab/ui';
+import ErrorTrackingForm from '~/error_tracking_settings/components/error_tracking_form.vue';
+import { defaultProps } from '../mock';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('error tracking settings form', () => {
+ let wrapper;
+
+ function mountComponent() {
+ wrapper = shallowMount(ErrorTrackingForm, {
+ localVue,
+ propsData: defaultProps,
+ });
+ }
+
+ beforeEach(() => {
+ mountComponent();
+ });
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ }
+ });
+
+ describe('an empty form', () => {
+ it('is rendered', () => {
+ expect(wrapper.findAll(GlFormInput).length).toBe(2);
+ expect(wrapper.find(GlFormInput).attributes('id')).toBe('error-tracking-api-host');
+ expect(
+ wrapper
+ .findAll(GlFormInput)
+ .at(1)
+ .attributes('id'),
+ ).toBe('error-tracking-token');
+
+ expect(wrapper.findAll(GlButton).exists()).toBe(true);
+ });
+
+ it('is rendered with labels and placeholders', () => {
+ const pageText = wrapper.text();
+
+ expect(pageText).toContain('Find your hostname in your Sentry account settings page');
+ expect(pageText).toContain(
+ "After adding your Auth Token, use the 'Connect' button to load projects",
+ );
+
+ expect(pageText).not.toContain('Connection has failed. Re-check Auth Token and try again');
+ expect(
+ wrapper
+ .findAll(GlFormInput)
+ .at(0)
+ .attributes('placeholder'),
+ ).toContain('https://mysentryserver.com');
+ });
+ });
+
+ describe('after a successful connection', () => {
+ beforeEach(() => {
+ wrapper.setProps({ connectSuccessful: true });
+ });
+
+ it('shows the success checkmark', () => {
+ expect(wrapper.find('.js-error-tracking-connect-success').isVisible()).toBe(true);
+ });
+
+ it('does not show an error', () => {
+ expect(wrapper.text()).not.toContain(
+ 'Connection has failed. Re-check Auth Token and try again',
+ );
+ });
+ });
+
+ describe('after an unsuccessful connection', () => {
+ beforeEach(() => {
+ wrapper.setProps({ connectError: true });
+ });
+
+ it('does not show the check mark', () => {
+ expect(wrapper.find('.js-error-tracking-connect-success').isVisible()).toBe(false);
+ });
+
+ it('shows an error', () => {
+ expect(wrapper.text()).toContain('Connection has failed. Re-check Auth Token and try again');
+ });
+ });
+});
diff --git a/spec/javascripts/error_tracking_settings/components/project_dropdown_spec.js b/spec/javascripts/error_tracking_settings/components/project_dropdown_spec.js
new file mode 100644
index 00000000000..8e5dbe28452
--- /dev/null
+++ b/spec/javascripts/error_tracking_settings/components/project_dropdown_spec.js
@@ -0,0 +1,109 @@
+import _ from 'underscore';
+import Vuex from 'vuex';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import ProjectDropdown from '~/error_tracking_settings/components/project_dropdown.vue';
+import { defaultProps, projectList, staleProject } from '../mock';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('error tracking settings project dropdown', () => {
+ let wrapper;
+
+ function mountComponent() {
+ wrapper = shallowMount(ProjectDropdown, {
+ localVue,
+ propsData: {
+ ..._.pick(
+ defaultProps,
+ 'dropdownLabel',
+ 'invalidProjectLabel',
+ 'projects',
+ 'projectSelectionLabel',
+ 'selectedProject',
+ 'token',
+ ),
+ hasProjects: false,
+ isProjectInvalid: false,
+ },
+ });
+ }
+
+ beforeEach(() => {
+ mountComponent();
+ });
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ }
+ });
+
+ describe('empty project list', () => {
+ it('renders the dropdown', () => {
+ expect(wrapper.find('#project-dropdown').exists()).toBeTruthy();
+ expect(wrapper.find(GlDropdown).exists()).toBeTruthy();
+ });
+
+ it('shows helper text', () => {
+ expect(wrapper.find('.js-project-dropdown-label').exists()).toBeTruthy();
+ expect(wrapper.find('.js-project-dropdown-label').text()).toContain(
+ 'To enable project selection',
+ );
+ });
+
+ it('does not show an error', () => {
+ expect(wrapper.find('.js-project-dropdown-error').exists()).toBeFalsy();
+ });
+
+ it('does not contain any dropdown items', () => {
+ expect(wrapper.find(GlDropdownItem).exists()).toBeFalsy();
+ expect(wrapper.find(GlDropdown).props('text')).toBe('No projects available');
+ });
+ });
+
+ describe('populated project list', () => {
+ beforeEach(() => {
+ wrapper.setProps({ projects: _.clone(projectList), hasProjects: true });
+ });
+
+ it('renders the dropdown', () => {
+ expect(wrapper.find('#project-dropdown').exists()).toBeTruthy();
+ expect(wrapper.find(GlDropdown).exists()).toBeTruthy();
+ });
+
+ it('contains a number of dropdown items', () => {
+ expect(wrapper.find(GlDropdownItem).exists()).toBeTruthy();
+ expect(wrapper.findAll(GlDropdownItem).length).toBe(2);
+ });
+ });
+
+ describe('selected project', () => {
+ const selectedProject = _.clone(projectList[0]);
+
+ beforeEach(() => {
+ wrapper.setProps({ projects: _.clone(projectList), selectedProject, hasProjects: true });
+ });
+
+ it('does not show helper text', () => {
+ expect(wrapper.find('.js-project-dropdown-label').exists()).toBeFalsy();
+ expect(wrapper.find('.js-project-dropdown-error').exists()).toBeFalsy();
+ });
+ });
+
+ describe('invalid project selected', () => {
+ beforeEach(() => {
+ wrapper.setProps({
+ projects: _.clone(projectList),
+ selectedProject: staleProject,
+ isProjectInvalid: true,
+ });
+ });
+
+ it('displays a error', () => {
+ expect(wrapper.find('.js-project-dropdown-label').exists()).toBeFalsy();
+ expect(wrapper.find('.js-project-dropdown-error').exists()).toBeTruthy();
+ });
+ });
+});
diff --git a/spec/javascripts/error_tracking_settings/mock.js b/spec/javascripts/error_tracking_settings/mock.js
new file mode 100644
index 00000000000..32cdba33c14
--- /dev/null
+++ b/spec/javascripts/error_tracking_settings/mock.js
@@ -0,0 +1,92 @@
+import createStore from '~/error_tracking_settings/store';
+import { TEST_HOST } from 'spec/test_constants';
+
+const defaultStore = createStore();
+
+export const projectList = [
+ {
+ name: 'name',
+ slug: 'slug',
+ organizationName: 'organizationName',
+ organizationSlug: 'organizationSlug',
+ },
+ {
+ name: 'name2',
+ slug: 'slug2',
+ organizationName: 'organizationName2',
+ organizationSlug: 'organizationSlug2',
+ },
+];
+
+export const staleProject = {
+ name: 'staleName',
+ slug: 'staleSlug',
+ organizationName: 'staleOrganizationName',
+ organizationSlug: 'staleOrganizationSlug',
+};
+
+export const normalizedProject = {
+ name: 'name',
+ slug: 'slug',
+ organizationName: 'organization_name',
+ organizationSlug: 'organization_slug',
+};
+
+export const sampleBackendProject = {
+ name: normalizedProject.name,
+ slug: normalizedProject.slug,
+ organization_name: normalizedProject.organizationName,
+ organization_slug: normalizedProject.organizationSlug,
+};
+
+export const sampleFrontendSettings = {
+ apiHost: 'apiHost',
+ enabled: false,
+ token: 'token',
+ selectedProject: {
+ slug: normalizedProject.slug,
+ name: normalizedProject.name,
+ organizationName: normalizedProject.organizationName,
+ organizationSlug: normalizedProject.organizationSlug,
+ },
+};
+
+export const transformedSettings = {
+ api_host: 'apiHost',
+ enabled: false,
+ token: 'token',
+ project: {
+ slug: normalizedProject.slug,
+ name: normalizedProject.name,
+ organization_name: normalizedProject.organizationName,
+ organization_slug: normalizedProject.organizationSlug,
+ },
+};
+
+export const defaultProps = {
+ ...defaultStore.state,
+ ...defaultStore.getters,
+};
+
+export const initialEmptyState = {
+ apiHost: '',
+ enabled: false,
+ project: null,
+ token: '',
+ listProjectsEndpoint: TEST_HOST,
+ operationsSettingsEndpoint: TEST_HOST,
+};
+
+export const initialPopulatedState = {
+ apiHost: 'apiHost',
+ enabled: true,
+ project: JSON.stringify(projectList[0]),
+ token: 'token',
+ listProjectsEndpoint: TEST_HOST,
+ operationsSettingsEndpoint: TEST_HOST,
+};
+
+export const projectWithHtmlTemplate = {
+ ...projectList[0],
+ name: '<strong>bold</strong>',
+};
diff --git a/spec/javascripts/error_tracking_settings/store/actions_spec.js b/spec/javascripts/error_tracking_settings/store/actions_spec.js
new file mode 100644
index 00000000000..0255b3a7aa4
--- /dev/null
+++ b/spec/javascripts/error_tracking_settings/store/actions_spec.js
@@ -0,0 +1,191 @@
+import MockAdapter from 'axios-mock-adapter';
+import testAction from 'spec/helpers/vuex_action_helper';
+import { TEST_HOST } from 'spec/test_constants';
+import axios from '~/lib/utils/axios_utils';
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+import actionsDefaultExport, * as actions from '~/error_tracking_settings/store/actions';
+import * as types from '~/error_tracking_settings/store/mutation_types';
+import defaultState from '~/error_tracking_settings/store/state';
+import { projectList } from '../mock';
+
+describe('error tracking settings actions', () => {
+ let state;
+
+ describe('project list actions', () => {
+ let mock;
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ state = { ...defaultState(), listProjectsEndpoint: TEST_HOST };
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ it('should request and transform the project list', done => {
+ mock.onPost(TEST_HOST).reply(() => [200, { projects: projectList }]);
+ testAction(
+ actions.fetchProjects,
+ null,
+ state,
+ [],
+ [
+ { type: 'requestProjects' },
+ {
+ type: 'receiveProjectsSuccess',
+ payload: projectList.map(convertObjectPropsToCamelCase),
+ },
+ ],
+ () => {
+ expect(mock.history.post.length).toBe(1);
+ done();
+ },
+ );
+ });
+
+ it('should handle a server error', done => {
+ mock.onPost(`${TEST_HOST}.json`).reply(() => [400]);
+ testAction(
+ actions.fetchProjects,
+ null,
+ state,
+ [],
+ [
+ { type: 'requestProjects' },
+ {
+ type: 'receiveProjectsError',
+ },
+ ],
+ () => {
+ expect(mock.history.post.length).toBe(1);
+ done();
+ },
+ );
+ });
+
+ it('should request projects correctly', done => {
+ testAction(actions.requestProjects, null, state, [{ type: types.RESET_CONNECT }], [], done);
+ });
+
+ it('should receive projects correctly', done => {
+ const testPayload = [];
+ testAction(
+ actions.receiveProjectsSuccess,
+ testPayload,
+ state,
+ [
+ { type: types.UPDATE_CONNECT_SUCCESS },
+ { type: types.RECEIVE_PROJECTS, payload: testPayload },
+ ],
+ [],
+ done,
+ );
+ });
+
+ it('should handle errors when receiving projects', done => {
+ const testPayload = [];
+ testAction(
+ actions.receiveProjectsError,
+ testPayload,
+ state,
+ [{ type: types.UPDATE_CONNECT_ERROR }, { type: types.CLEAR_PROJECTS }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('save changes actions', () => {
+ let mock;
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ state = {
+ operationsSettingsEndpoint: TEST_HOST,
+ };
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ it('should save the page', done => {
+ const refreshCurrentPage = spyOnDependency(actionsDefaultExport, 'refreshCurrentPage');
+ mock.onPatch(TEST_HOST).reply(200);
+ testAction(actions.updateSettings, null, state, [], [{ type: 'requestSettings' }], () => {
+ expect(mock.history.patch.length).toBe(1);
+ expect(refreshCurrentPage).toHaveBeenCalled();
+ done();
+ });
+ });
+
+ it('should handle a server error', done => {
+ mock.onPatch(TEST_HOST).reply(400);
+ testAction(
+ actions.updateSettings,
+ null,
+ state,
+ [],
+ [
+ { type: 'requestSettings' },
+ {
+ type: 'receiveSettingsError',
+ payload: new Error('Request failed with status code 400'),
+ },
+ ],
+ () => {
+ expect(mock.history.patch.length).toBe(1);
+ done();
+ },
+ );
+ });
+
+ it('should request to save the page', done => {
+ testAction(
+ actions.requestSettings,
+ null,
+ state,
+ [{ type: types.UPDATE_SETTINGS_LOADING, payload: true }],
+ [],
+ done,
+ );
+ });
+
+ it('should handle errors when requesting to save the page', done => {
+ testAction(
+ actions.receiveSettingsError,
+ {},
+ state,
+ [{ type: types.UPDATE_SETTINGS_LOADING, payload: false }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('generic actions to update the store', () => {
+ const testData = 'test';
+ it('should reset the `connect success` flag when updating the api host', done => {
+ testAction(
+ actions.updateApiHost,
+ testData,
+ state,
+ [{ type: types.UPDATE_API_HOST, payload: testData }, { type: types.RESET_CONNECT }],
+ [],
+ done,
+ );
+ });
+
+ it('should reset the `connect success` flag when updating the token', done => {
+ testAction(
+ actions.updateToken,
+ testData,
+ state,
+ [{ type: types.UPDATE_TOKEN, payload: testData }, { type: types.RESET_CONNECT }],
+ [],
+ done,
+ );
+ });
+ });
+});
diff --git a/spec/javascripts/error_tracking_settings/store/getters_spec.js b/spec/javascripts/error_tracking_settings/store/getters_spec.js
new file mode 100644
index 00000000000..2c5ff084b8a
--- /dev/null
+++ b/spec/javascripts/error_tracking_settings/store/getters_spec.js
@@ -0,0 +1,93 @@
+import * as getters from '~/error_tracking_settings/store/getters';
+import defaultState from '~/error_tracking_settings/store/state';
+import { projectList, projectWithHtmlTemplate, staleProject } from '../mock';
+
+describe('Error Tracking Settings - Getters', () => {
+ let state;
+
+ beforeEach(() => {
+ state = defaultState();
+ });
+
+ describe('hasProjects', () => {
+ it('should reflect when no projects exist', () => {
+ expect(getters.hasProjects(state)).toEqual(false);
+ });
+
+ it('should reflect when projects exist', () => {
+ state.projects = projectList;
+
+ expect(getters.hasProjects(state)).toEqual(true);
+ });
+ });
+
+ describe('isProjectInvalid', () => {
+ const mockGetters = { hasProjects: true };
+ it('should show when a project is valid', () => {
+ state.projects = projectList;
+ [state.selectedProject] = projectList;
+
+ expect(getters.isProjectInvalid(state, mockGetters)).toEqual(false);
+ });
+
+ it('should show when a project is invalid', () => {
+ state.projects = projectList;
+ state.selectedProject = staleProject;
+
+ expect(getters.isProjectInvalid(state, mockGetters)).toEqual(true);
+ });
+ });
+
+ describe('dropdownLabel', () => {
+ const mockGetters = { hasProjects: false };
+ it('should display correctly when there are no projects available', () => {
+ expect(getters.dropdownLabel(state, mockGetters)).toEqual('No projects available');
+ });
+
+ it('should display correctly when a project is selected', () => {
+ [state.selectedProject] = projectList;
+
+ expect(getters.dropdownLabel(state, mockGetters)).toEqual('organizationName | name');
+ });
+
+ it('should display correctly when no project is selected', () => {
+ state.projects = projectList;
+
+ expect(getters.dropdownLabel(state, { hasProjects: true })).toEqual('Select project');
+ });
+ });
+
+ describe('invalidProjectLabel', () => {
+ it('should display an error containing the project name', () => {
+ [state.selectedProject] = projectList;
+
+ expect(getters.invalidProjectLabel(state)).toEqual(
+ 'Project "name" is no longer available. Select another project to continue.',
+ );
+ });
+
+ it('should properly escape the label text', () => {
+ state.selectedProject = projectWithHtmlTemplate;
+
+ expect(getters.invalidProjectLabel(state)).toEqual(
+ 'Project "&lt;strong&gt;bold&lt;/strong&gt;" is no longer available. Select another project to continue.',
+ );
+ });
+ });
+
+ describe('projectSelectionLabel', () => {
+ it('should show the correct message when the token is empty', () => {
+ expect(getters.projectSelectionLabel(state)).toEqual(
+ 'To enable project selection, enter a valid Auth Token',
+ );
+ });
+
+ it('should show the correct message when token exists', () => {
+ state.token = 'test-token';
+
+ expect(getters.projectSelectionLabel(state)).toEqual(
+ "Click 'Connect' to re-establish the connection to Sentry and activate the dropdown.",
+ );
+ });
+ });
+});
diff --git a/spec/javascripts/error_tracking_settings/store/mutation_spec.js b/spec/javascripts/error_tracking_settings/store/mutation_spec.js
new file mode 100644
index 00000000000..bb1f1da784e
--- /dev/null
+++ b/spec/javascripts/error_tracking_settings/store/mutation_spec.js
@@ -0,0 +1,82 @@
+import { TEST_HOST } from 'spec/test_constants';
+import mutations from '~/error_tracking_settings/store/mutations';
+import defaultState from '~/error_tracking_settings/store/state';
+import * as types from '~/error_tracking_settings/store/mutation_types';
+import {
+ initialEmptyState,
+ initialPopulatedState,
+ projectList,
+ sampleBackendProject,
+ normalizedProject,
+} from '../mock';
+
+describe('error tracking settings mutations', () => {
+ describe('mutations', () => {
+ let state;
+
+ beforeEach(() => {
+ state = defaultState();
+ });
+
+ it('should create an empty initial state correctly', () => {
+ mutations[types.SET_INITIAL_STATE](state, {
+ ...initialEmptyState,
+ });
+
+ expect(state.apiHost).toEqual('');
+ expect(state.enabled).toEqual(false);
+ expect(state.selectedProject).toEqual(null);
+ expect(state.token).toEqual('');
+ expect(state.listProjectsEndpoint).toEqual(TEST_HOST);
+ expect(state.operationsSettingsEndpoint).toEqual(TEST_HOST);
+ });
+
+ it('should populate the initial state correctly', () => {
+ mutations[types.SET_INITIAL_STATE](state, {
+ ...initialPopulatedState,
+ });
+
+ expect(state.apiHost).toEqual('apiHost');
+ expect(state.enabled).toEqual(true);
+ expect(state.selectedProject).toEqual(projectList[0]);
+ expect(state.token).toEqual('token');
+ expect(state.listProjectsEndpoint).toEqual(TEST_HOST);
+ expect(state.operationsSettingsEndpoint).toEqual(TEST_HOST);
+ });
+
+ it('should receive projects successfully', () => {
+ mutations[types.RECEIVE_PROJECTS](state, [sampleBackendProject]);
+
+ expect(state.projects).toEqual([normalizedProject]);
+ });
+
+ it('should strip out unnecessary project properties', () => {
+ mutations[types.RECEIVE_PROJECTS](state, [
+ { ...sampleBackendProject, extra_property: 'extra_property' },
+ ]);
+
+ expect(state.projects).toEqual([normalizedProject]);
+ });
+
+ it('should update state when connect is successful', () => {
+ mutations[types.UPDATE_CONNECT_SUCCESS](state);
+
+ expect(state.connectSuccessful).toBe(true);
+ expect(state.connectError).toBe(false);
+ });
+
+ it('should update state when connect fails', () => {
+ mutations[types.UPDATE_CONNECT_ERROR](state);
+
+ expect(state.connectSuccessful).toBe(false);
+ expect(state.connectError).toBe(true);
+ });
+
+ it('should update state when connect is reset', () => {
+ mutations[types.RESET_CONNECT](state);
+
+ expect(state.connectSuccessful).toBe(false);
+ expect(state.connectError).toBe(false);
+ });
+ });
+});
diff --git a/spec/javascripts/error_tracking_settings/utils_spec.js b/spec/javascripts/error_tracking_settings/utils_spec.js
new file mode 100644
index 00000000000..4b144f7daf1
--- /dev/null
+++ b/spec/javascripts/error_tracking_settings/utils_spec.js
@@ -0,0 +1,29 @@
+import { transformFrontendSettings } from '~/error_tracking_settings/utils';
+import { sampleFrontendSettings, transformedSettings } from './mock';
+
+describe('error tracking settings utils', () => {
+ describe('data transform functions', () => {
+ it('should transform settings successfully for the backend', () => {
+ expect(transformFrontendSettings(sampleFrontendSettings)).toEqual(transformedSettings);
+ });
+
+ it('should transform empty values in the settings object to null', () => {
+ const emptyFrontendSettingsObject = {
+ apiHost: '',
+ enabled: false,
+ token: '',
+ selectedProject: null,
+ };
+ const transformedEmptySettingsObject = {
+ api_host: null,
+ enabled: false,
+ token: null,
+ project: null,
+ };
+
+ expect(transformFrontendSettings(emptyFrontendSettingsObject)).toEqual(
+ transformedEmptySettingsObject,
+ );
+ });
+ });
+});
diff --git a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
index 9aa3cbaa231..6230da77f49 100644
--- a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
@@ -755,6 +755,17 @@ describe('Filtered Search Visual Tokens', () => {
expect(updateUserTokenAppearanceSpy.calls.count()).toBe(0);
});
+ it('does not update user token appearance for `None` filter', () => {
+ const { tokenNameElement } = findElements(authorToken);
+
+ const tokenName = tokenNameElement.innerText;
+ const tokenValue = 'None';
+
+ subject.renderVisualTokenValue(authorToken, tokenName, tokenValue);
+
+ expect(updateUserTokenAppearanceSpy.calls.count()).toBe(0);
+ });
+
it('does not update user token appearance for `none` filter', () => {
const { tokenNameElement } = findElements(authorToken);
diff --git a/spec/javascripts/fixtures/autocomplete_sources.rb b/spec/javascripts/fixtures/autocomplete_sources.rb
new file mode 100644
index 00000000000..c117fb7cd24
--- /dev/null
+++ b/spec/javascripts/fixtures/autocomplete_sources.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::AutocompleteSourcesController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ set(:admin) { create(:admin) }
+ set(:group) { create(:group, name: 'frontend-fixtures') }
+ set(:project) { create(:project, namespace: group, path: 'autocomplete-sources-project') }
+ set(:issue) { create(:issue, project: project) }
+
+ before(:all) do
+ clean_frontend_fixtures('autocomplete_sources/')
+ end
+
+ before do
+ sign_in(admin)
+ end
+
+ it 'autocomplete_sources/labels.json' do |example|
+ issue.labels << create(:label, project: project, title: 'bug')
+ issue.labels << create(:label, project: project, title: 'critical')
+
+ create(:label, project: project, title: 'feature')
+ create(:label, project: project, title: 'documentation')
+
+ get :labels,
+ format: :json,
+ params: {
+ namespace_id: group.path,
+ project_id: project.path,
+ type: issue.class.name,
+ type_id: issue.id
+ }
+
+ expect(response).to be_success
+ store_frontend_fixture(response, example.description)
+ end
+end
diff --git a/spec/javascripts/fixtures/blob.rb b/spec/javascripts/fixtures/blob.rb
index 1b2a3b484bb..cd66d98f92a 100644
--- a/spec/javascripts/fixtures/blob.rb
+++ b/spec/javascripts/fixtures/blob.rb
@@ -15,6 +15,7 @@ describe Projects::BlobController, '(JavaScript fixtures)', type: :controller do
before do
sign_in(admin)
+ allow(SecureRandom).to receive(:hex).and_return('securerandomhex:thereisnospoon')
end
after do
diff --git a/spec/javascripts/fixtures/commit.rb b/spec/javascripts/fixtures/commit.rb
index f0e4bb50c67..295f13b34a4 100644
--- a/spec/javascripts/fixtures/commit.rb
+++ b/spec/javascripts/fixtures/commit.rb
@@ -16,6 +16,7 @@ describe Projects::CommitController, '(JavaScript fixtures)', type: :controller
before do
project.add_maintainer(user)
sign_in(user)
+ allow(SecureRandom).to receive(:hex).and_return('securerandomhex:thereisnospoon')
end
it 'commit/show.html.raw' do |example|
diff --git a/spec/javascripts/fixtures/deploy_keys.rb b/spec/javascripts/fixtures/deploy_keys.rb
index efbda955972..a333d9c0150 100644
--- a/spec/javascripts/fixtures/deploy_keys.rb
+++ b/spec/javascripts/fixtures/deploy_keys.rb
@@ -25,7 +25,7 @@ describe Projects::DeployKeysController, '(JavaScript fixtures)', type: :control
render_views
it 'deploy_keys/keys.json' do |example|
- create(:deploy_key, public: true)
+ create(:rsa_deploy_key_2048, public: true)
project_key = create(:deploy_key, key: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQCdMHEHyhRjbhEZVddFn6lTWdgEy5Q6Bz4nwGB76xWZI5YT/1WJOMEW+sL5zYd31kk7sd3FJ5L9ft8zWMWrr/iWXQikC2cqZK24H1xy+ZUmrRuJD4qGAaIVoyyzBL+avL+lF8J5lg6YSw8gwJY/lX64/vnJHUlWw2n5BF8IFOWhiw== dummy@gitlab.com')
internal_key = create(:deploy_key, key: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDNd/UJWhPrpb+b/G5oL109y57yKuCxE+WUGJGYaj7WQKsYRJmLYh1mgjrl+KVyfsWpq4ylOxIfFSnN9xBBFN8mlb0Fma5DC7YsSsibJr3MZ19ZNBprwNcdogET7aW9I0In7Wu5f2KqI6e5W/spJHCy4JVxzVMUvk6Myab0LnJ2iQ== dummy@gitlab.com')
create(:deploy_keys_project, project: project, deploy_key: project_key)
diff --git a/spec/javascripts/fixtures/groups.rb b/spec/javascripts/fixtures/groups.rb
index f8d55fc97c3..03136f4e661 100644
--- a/spec/javascripts/fixtures/groups.rb
+++ b/spec/javascripts/fixtures/groups.rb
@@ -4,7 +4,7 @@ describe 'Groups (JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
let(:admin) { create(:admin) }
- let(:group) { create(:group, name: 'frontend-fixtures-group' )}
+ let(:group) { create(:group, name: 'frontend-fixtures-group', runners_token: 'runnerstoken:intabulasreferre')}
render_views
diff --git a/spec/javascripts/fixtures/issues.rb b/spec/javascripts/fixtures/issues.rb
index 18fb1bebf8b..9b8e90c2a43 100644
--- a/spec/javascripts/fixtures/issues.rb
+++ b/spec/javascripts/fixtures/issues.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
- let(:admin) { create(:admin) }
+ let(:admin) { create(:admin, feed_token: 'feedtoken:coldfeed') }
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project_empty_repo, namespace: namespace, path: 'issues-project') }
diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/javascripts/fixtures/merge_requests.rb
index 26e81f06c0b..eb37be87e1d 100644
--- a/spec/javascripts/fixtures/merge_requests.rb
+++ b/spec/javascripts/fixtures/merge_requests.rb
@@ -35,6 +35,7 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont
before do
sign_in(admin)
+ allow(Discussion).to receive(:build_discussion_id).and_return(['discussionid:ceterumcenseo'])
end
after do
@@ -54,8 +55,10 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont
end
it 'merge_requests/merged_merge_request.html.raw' do |example|
- allow_any_instance_of(MergeRequest).to receive(:source_branch_exists?).and_return(true)
- allow_any_instance_of(MergeRequest).to receive(:can_remove_source_branch?).and_return(true)
+ expect_next_instance_of(MergeRequest) do |merge_request|
+ allow(merge_request).to receive(:source_branch_exists?).and_return(true)
+ allow(merge_request).to receive(:can_remove_source_branch?).and_return(true)
+ end
render_merge_request(example.description, merged_merge_request)
end
diff --git a/spec/javascripts/fixtures/projects.rb b/spec/javascripts/fixtures/projects.rb
index 9b48646f8f0..85f02923804 100644
--- a/spec/javascripts/fixtures/projects.rb
+++ b/spec/javascripts/fixtures/projects.rb
@@ -3,13 +3,13 @@ require 'spec_helper'
describe 'Projects (JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
+ runners_token = 'runnerstoken:intabulasreferre'
+
let(:admin) { create(:admin) }
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project, namespace: namespace, path: 'builds-project') }
+ let(:project) { create(:project, namespace: namespace, path: 'builds-project', runners_token: runners_token) }
let(:project_with_repo) { create(:project, :repository, description: 'Code and stuff') }
- let(:project_variable_populated) { create(:project, namespace: namespace, path: 'builds-project2') }
- let!(:variable1) { create(:ci_variable, project: project_variable_populated) }
- let!(:variable2) { create(:ci_variable, project: project_variable_populated) }
+ let(:project_variable_populated) { create(:project, namespace: namespace, path: 'builds-project2', runners_token: runners_token) }
render_views
@@ -20,6 +20,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do
before do
project.add_maintainer(admin)
sign_in(admin)
+ allow(SecureRandom).to receive(:hex).and_return('securerandomhex:thereisnospoon')
end
after do
@@ -70,6 +71,9 @@ describe 'Projects (JavaScript fixtures)', type: :controller do
end
it 'projects/ci_cd_settings_with_variables.html.raw' do |example|
+ create(:ci_variable, project: project_variable_populated)
+ create(:ci_variable, project: project_variable_populated)
+
get :show, params: {
namespace_id: project_variable_populated.namespace.to_param,
project_id: project_variable_populated
diff --git a/spec/javascripts/fixtures/snippet.rb b/spec/javascripts/fixtures/snippet.rb
index a14837e4d4a..bcd6546f3df 100644
--- a/spec/javascripts/fixtures/snippet.rb
+++ b/spec/javascripts/fixtures/snippet.rb
@@ -7,7 +7,6 @@ describe SnippetsController, '(JavaScript fixtures)', type: :controller do
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project, :repository, namespace: namespace, path: 'branches-project') }
let(:snippet) { create(:personal_snippet, title: 'snippet.md', content: '# snippet', file_name: 'snippet.md', author: admin) }
- let!(:snippet_note) { create(:discussion_note_on_snippet, noteable: snippet, project: project, author: admin, note: '- [ ] Task List Item') }
render_views
@@ -17,6 +16,7 @@ describe SnippetsController, '(JavaScript fixtures)', type: :controller do
before do
sign_in(admin)
+ allow(Discussion).to receive(:build_discussion_id).and_return(['discussionid:ceterumcenseo'])
end
after do
@@ -24,6 +24,8 @@ describe SnippetsController, '(JavaScript fixtures)', type: :controller do
end
it 'snippets/show.html.raw' do |example|
+ create(:discussion_note_on_snippet, noteable: snippet, project: project, author: admin, note: '- [ ] Task List Item')
+
get(:show, params: { id: snippet.to_param })
expect(response).to be_success
diff --git a/spec/javascripts/fixtures/u2f.rb b/spec/javascripts/fixtures/u2f.rb
index f0aa874bf75..5cdbadef639 100644
--- a/spec/javascripts/fixtures/u2f.rb
+++ b/spec/javascripts/fixtures/u2f.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
context 'U2F' do
include JavaScriptFixturesHelpers
- let(:user) { create(:user, :two_factor_via_u2f) }
+ let(:user) { create(:user, :two_factor_via_u2f, otp_secret: 'otpsecret:coolkids') }
before(:all) do
clean_frontend_fixtures('u2f/')
@@ -33,6 +33,7 @@ context 'U2F' do
before do
sign_in(user)
+ allow_any_instance_of(Profiles::TwoFactorAuthsController).to receive(:build_qr_code).and_return('qrcode:blackandwhitesquares')
end
it 'u2f/register.html.raw' do |example|
diff --git a/spec/javascripts/helpers/vuex_action_helper.js b/spec/javascripts/helpers/vuex_action_helper.js
index 1972408356e..88652202a8e 100644
--- a/spec/javascripts/helpers/vuex_action_helper.js
+++ b/spec/javascripts/helpers/vuex_action_helper.js
@@ -84,7 +84,10 @@ export default (
done();
};
- const result = action({ commit, state, dispatch, rootState: state, rootGetters: state }, payload);
+ const result = action(
+ { commit, state, dispatch, rootState: state, rootGetters: state, getters: state },
+ payload,
+ );
return new Promise(resolve => {
setImmediate(resolve);
diff --git a/spec/javascripts/ide/components/new_dropdown/modal_spec.js b/spec/javascripts/ide/components/new_dropdown/modal_spec.js
index 595a2f927e9..d94cc1a8faa 100644
--- a/spec/javascripts/ide/components/new_dropdown/modal_spec.js
+++ b/spec/javascripts/ide/components/new_dropdown/modal_spec.js
@@ -41,6 +41,15 @@ describe('new file modal component', () => {
expect(vm.$el.querySelector('.label-bold').textContent.trim()).toBe('Name');
});
+ it(`${type === 'tree' ? 'does not show' : 'shows'} file templates`, () => {
+ const templateFilesEl = vm.$el.querySelector('.file-templates');
+ if (type === 'tree') {
+ expect(templateFilesEl).toBeNull();
+ } else {
+ expect(templateFilesEl instanceof Element).toBeTruthy();
+ }
+ });
+
describe('createEntryInStore', () => {
it('$emits create', () => {
spyOn(vm, 'createTempEntry');
diff --git a/spec/javascripts/import_projects/components/import_projects_table_spec.js b/spec/javascripts/import_projects/components/import_projects_table_spec.js
new file mode 100644
index 00000000000..a1ff84ce259
--- /dev/null
+++ b/spec/javascripts/import_projects/components/import_projects_table_spec.js
@@ -0,0 +1,186 @@
+import Vue from 'vue';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import store from '~/import_projects/store';
+import importProjectsTable from '~/import_projects/components/import_projects_table.vue';
+import STATUS_MAP from '~/import_projects/constants';
+import setTimeoutPromise from '../../helpers/set_timeout_promise_helper';
+
+describe('ImportProjectsTable', () => {
+ let vm;
+ let mock;
+ const reposPath = '/repos-path';
+ const jobsPath = '/jobs-path';
+ const providerTitle = 'THE PROVIDER';
+ const providerRepo = { id: 10, sanitizedName: 'sanitizedName', fullName: 'fullName' };
+ const importedProject = {
+ id: 1,
+ fullPath: 'fullPath',
+ importStatus: 'started',
+ providerLink: 'providerLink',
+ importSource: 'importSource',
+ };
+
+ function createComponent() {
+ const ImportProjectsTable = Vue.extend(importProjectsTable);
+
+ const component = new ImportProjectsTable({
+ store,
+ propsData: {
+ providerTitle,
+ },
+ }).$mount();
+
+ component.$store.dispatch('stopJobsPolling');
+
+ return component;
+ }
+
+ beforeEach(() => {
+ store.dispatch('setInitialData', { reposPath });
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ mock.restore();
+ });
+
+ it('renders a loading icon whilst repos are loading', done => {
+ mock.restore(); // Stop the mock adapter from responding to the request, keeping the spinner up
+
+ vm = createComponent();
+
+ setTimeoutPromise()
+ .then(() => {
+ expect(vm.$el.querySelector('.js-loading-button-icon')).not.toBeNull();
+ })
+ .then(() => done())
+ .catch(() => done.fail());
+ });
+
+ it('renders a table with imported projects and provider repos', done => {
+ const response = {
+ importedProjects: [importedProject],
+ providerRepos: [providerRepo],
+ namespaces: [{ path: 'path' }],
+ };
+ mock.onGet(reposPath).reply(200, response);
+
+ vm = createComponent();
+
+ setTimeoutPromise()
+ .then(() => {
+ expect(vm.$el.querySelector('.js-loading-button-icon')).toBeNull();
+ expect(vm.$el.querySelector('.table')).not.toBeNull();
+ expect(vm.$el.querySelector('.import-jobs-from-col').innerText).toMatch(
+ `From ${providerTitle}`,
+ );
+
+ expect(vm.$el.querySelector('.js-imported-project')).not.toBeNull();
+ expect(vm.$el.querySelector('.js-provider-repo')).not.toBeNull();
+ })
+ .then(() => done())
+ .catch(() => done.fail());
+ });
+
+ it('renders an empty state if there are no imported projects or provider repos', done => {
+ const response = {
+ importedProjects: [],
+ providerRepos: [],
+ namespaces: [],
+ };
+ mock.onGet(reposPath).reply(200, response);
+
+ vm = createComponent();
+
+ setTimeoutPromise()
+ .then(() => {
+ expect(vm.$el.querySelector('.js-loading-button-icon')).toBeNull();
+ expect(vm.$el.querySelector('.table')).toBeNull();
+ expect(vm.$el.innerText).toMatch(`No ${providerTitle} repositories available to import`);
+ })
+ .then(() => done())
+ .catch(() => done.fail());
+ });
+
+ it('imports provider repos if bulk import button is clicked', done => {
+ const importPath = '/import-path';
+ const response = {
+ importedProjects: [],
+ providerRepos: [providerRepo],
+ namespaces: [{ path: 'path' }],
+ };
+
+ mock.onGet(reposPath).replyOnce(200, response);
+ mock.onPost(importPath).replyOnce(200, importedProject);
+
+ store.dispatch('setInitialData', { importPath });
+
+ vm = createComponent();
+
+ setTimeoutPromise()
+ .then(() => {
+ expect(vm.$el.querySelector('.js-imported-project')).toBeNull();
+ expect(vm.$el.querySelector('.js-provider-repo')).not.toBeNull();
+
+ vm.$el.querySelector('.js-import-all').click();
+ })
+ .then(() => setTimeoutPromise())
+ .then(() => {
+ expect(vm.$el.querySelector('.js-imported-project')).not.toBeNull();
+ expect(vm.$el.querySelector('.js-provider-repo')).toBeNull();
+ })
+ .then(() => done())
+ .catch(() => done.fail());
+ });
+
+ it('polls to update the status of imported projects', done => {
+ const importPath = '/import-path';
+ const response = {
+ importedProjects: [importedProject],
+ providerRepos: [],
+ namespaces: [{ path: 'path' }],
+ };
+ const updatedProjects = [
+ {
+ id: importedProject.id,
+ importStatus: 'finished',
+ },
+ ];
+
+ mock.onGet(reposPath).replyOnce(200, response);
+
+ store.dispatch('setInitialData', { importPath, jobsPath });
+
+ vm = createComponent();
+
+ setTimeoutPromise()
+ .then(() => {
+ const statusObject = STATUS_MAP[importedProject.importStatus];
+
+ expect(vm.$el.querySelector('.js-imported-project')).not.toBeNull();
+ expect(vm.$el.querySelector(`.${statusObject.textClass}`).textContent).toMatch(
+ statusObject.text,
+ );
+
+ expect(vm.$el.querySelector(`.ic-status_${statusObject.icon}`)).not.toBeNull();
+
+ mock.onGet(jobsPath).replyOnce(200, updatedProjects);
+ return vm.$store.dispatch('restartJobsPolling');
+ })
+ .then(() => setTimeoutPromise())
+ .then(() => {
+ const statusObject = STATUS_MAP[updatedProjects[0].importStatus];
+
+ expect(vm.$el.querySelector('.js-imported-project')).not.toBeNull();
+ expect(vm.$el.querySelector(`.${statusObject.textClass}`).textContent).toMatch(
+ statusObject.text,
+ );
+
+ expect(vm.$el.querySelector(`.ic-status_${statusObject.icon}`)).not.toBeNull();
+ })
+ .then(() => done())
+ .catch(() => done.fail());
+ });
+});
diff --git a/spec/javascripts/import_projects/components/imported_project_table_row_spec.js b/spec/javascripts/import_projects/components/imported_project_table_row_spec.js
new file mode 100644
index 00000000000..8af3b5954a9
--- /dev/null
+++ b/spec/javascripts/import_projects/components/imported_project_table_row_spec.js
@@ -0,0 +1,50 @@
+import Vue from 'vue';
+import store from '~/import_projects/store';
+import importedProjectTableRow from '~/import_projects/components/imported_project_table_row.vue';
+import STATUS_MAP from '~/import_projects/constants';
+
+describe('ImportedProjectTableRow', () => {
+ let vm;
+ const project = {
+ id: 1,
+ fullPath: 'fullPath',
+ importStatus: 'finished',
+ providerLink: 'providerLink',
+ importSource: 'importSource',
+ };
+
+ function createComponent() {
+ const ImportedProjectTableRow = Vue.extend(importedProjectTableRow);
+
+ return new ImportedProjectTableRow({
+ store,
+ propsData: {
+ project: {
+ ...project,
+ },
+ },
+ }).$mount();
+ }
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders an imported project table row', () => {
+ vm = createComponent();
+
+ const providerLink = vm.$el.querySelector('.js-provider-link');
+ const statusObject = STATUS_MAP[project.importStatus];
+
+ expect(vm.$el.classList.contains('js-imported-project')).toBe(true);
+ expect(providerLink.href).toMatch(project.providerLink);
+ expect(providerLink.textContent).toMatch(project.importSource);
+ expect(vm.$el.querySelector('.js-full-path').textContent).toMatch(project.fullPath);
+ expect(vm.$el.querySelector(`.${statusObject.textClass}`).textContent).toMatch(
+ statusObject.text,
+ );
+
+ expect(vm.$el.querySelector(`.ic-status_${statusObject.icon}`)).not.toBeNull();
+ expect(vm.$el.querySelector('.js-go-to-project').href).toMatch(project.fullPath);
+ });
+});
diff --git a/spec/javascripts/import_projects/components/provider_repo_table_row_spec.js b/spec/javascripts/import_projects/components/provider_repo_table_row_spec.js
new file mode 100644
index 00000000000..69377f8d685
--- /dev/null
+++ b/spec/javascripts/import_projects/components/provider_repo_table_row_spec.js
@@ -0,0 +1,91 @@
+import Vue from 'vue';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import store from '~/import_projects/store';
+import providerRepoTableRow from '~/import_projects/components/provider_repo_table_row.vue';
+import STATUS_MAP, { STATUSES } from '~/import_projects/constants';
+import setTimeoutPromise from '../../helpers/set_timeout_promise_helper';
+
+describe('ProviderRepoTableRow', () => {
+ let vm;
+ const repo = {
+ id: 10,
+ sanitizedName: 'sanitizedName',
+ fullName: 'fullName',
+ providerLink: 'providerLink',
+ };
+
+ function createComponent() {
+ const ProviderRepoTableRow = Vue.extend(providerRepoTableRow);
+
+ return new ProviderRepoTableRow({
+ store,
+ propsData: {
+ repo: {
+ ...repo,
+ },
+ },
+ }).$mount();
+ }
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders a provider repo table row', () => {
+ vm = createComponent();
+
+ const providerLink = vm.$el.querySelector('.js-provider-link');
+ const statusObject = STATUS_MAP[STATUSES.NONE];
+
+ expect(vm.$el.classList.contains('js-provider-repo')).toBe(true);
+ expect(providerLink.href).toMatch(repo.providerLink);
+ expect(providerLink.textContent).toMatch(repo.fullName);
+ expect(vm.$el.querySelector(`.${statusObject.textClass}`).textContent).toMatch(
+ statusObject.text,
+ );
+
+ expect(vm.$el.querySelector(`.ic-status_${statusObject.icon}`)).not.toBeNull();
+ expect(vm.$el.querySelector('.js-import-button')).not.toBeNull();
+ });
+
+ it('renders a select2 namespace select', () => {
+ vm = createComponent();
+
+ const dropdownTrigger = vm.$el.querySelector('.js-namespace-select');
+
+ expect(dropdownTrigger).not.toBeNull();
+ expect(dropdownTrigger.classList.contains('select2-container')).toBe(true);
+
+ dropdownTrigger.click();
+
+ expect(vm.$el.querySelector('.select2-drop')).not.toBeNull();
+ });
+
+ it('imports repo when clicking import button', done => {
+ const importPath = '/import-path';
+ const defaultTargetNamespace = 'user';
+ const ciCdOnly = true;
+ const mock = new MockAdapter(axios);
+
+ store.dispatch('setInitialData', { importPath, defaultTargetNamespace, ciCdOnly });
+ mock.onPost(importPath).replyOnce(200);
+ spyOn(store, 'dispatch').and.returnValue(new Promise(() => {}));
+
+ vm = createComponent();
+
+ vm.$el.querySelector('.js-import-button').click();
+
+ setTimeoutPromise()
+ .then(() => {
+ expect(store.dispatch).toHaveBeenCalledWith('fetchImport', {
+ repo,
+ newName: repo.sanitizedName,
+ targetNamespace: defaultTargetNamespace,
+ });
+ })
+ .then(() => mock.restore())
+ .then(done)
+ .catch(done.fail);
+ });
+});
diff --git a/spec/javascripts/import_projects/store/actions_spec.js b/spec/javascripts/import_projects/store/actions_spec.js
new file mode 100644
index 00000000000..77850ee3283
--- /dev/null
+++ b/spec/javascripts/import_projects/store/actions_spec.js
@@ -0,0 +1,284 @@
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+import {
+ SET_INITIAL_DATA,
+ REQUEST_REPOS,
+ RECEIVE_REPOS_SUCCESS,
+ RECEIVE_REPOS_ERROR,
+ REQUEST_IMPORT,
+ RECEIVE_IMPORT_SUCCESS,
+ RECEIVE_IMPORT_ERROR,
+ RECEIVE_JOBS_SUCCESS,
+} from '~/import_projects/store/mutation_types';
+import {
+ setInitialData,
+ requestRepos,
+ receiveReposSuccess,
+ receiveReposError,
+ fetchRepos,
+ requestImport,
+ receiveImportSuccess,
+ receiveImportError,
+ fetchImport,
+ receiveJobsSuccess,
+ fetchJobs,
+ clearJobsEtagPoll,
+ stopJobsPolling,
+} from '~/import_projects/store/actions';
+import state from '~/import_projects/store/state';
+import testAction from 'spec/helpers/vuex_action_helper';
+import { TEST_HOST } from 'spec/test_constants';
+
+describe('import_projects store actions', () => {
+ let localState;
+ const repoId = 1;
+ const repos = [{ id: 1 }, { id: 2 }];
+ const importPayload = { newName: 'newName', targetNamespace: 'targetNamespace', repo: { id: 1 } };
+
+ beforeEach(() => {
+ localState = state();
+ });
+
+ describe('setInitialData', () => {
+ it(`commits ${SET_INITIAL_DATA} mutation`, done => {
+ const initialData = {
+ reposPath: 'reposPath',
+ provider: 'provider',
+ jobsPath: 'jobsPath',
+ importPath: 'impapp/assets/javascripts/vue_shared/components/select2_select.vueortPath',
+ defaultTargetNamespace: 'defaultTargetNamespace',
+ ciCdOnly: 'ciCdOnly',
+ canSelectNamespace: 'canSelectNamespace',
+ };
+
+ testAction(
+ setInitialData,
+ initialData,
+ localState,
+ [{ type: SET_INITIAL_DATA, payload: initialData }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('requestRepos', () => {
+ it(`requestRepos commits ${REQUEST_REPOS} mutation`, done => {
+ testAction(
+ requestRepos,
+ null,
+ localState,
+ [{ type: REQUEST_REPOS, payload: null }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('receiveReposSuccess', () => {
+ it(`commits ${RECEIVE_REPOS_SUCCESS} mutation`, done => {
+ testAction(
+ receiveReposSuccess,
+ repos,
+ localState,
+ [{ type: RECEIVE_REPOS_SUCCESS, payload: repos }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('receiveReposError', () => {
+ it(`commits ${RECEIVE_REPOS_ERROR} mutation`, done => {
+ testAction(receiveReposError, repos, localState, [{ type: RECEIVE_REPOS_ERROR }], [], done);
+ });
+ });
+
+ describe('fetchRepos', () => {
+ let mock;
+
+ beforeEach(() => {
+ localState.reposPath = `${TEST_HOST}/endpoint.json`;
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => mock.restore());
+
+ it('dispatches requestRepos and receiveReposSuccess actions on a successful request', done => {
+ const payload = { imported_projects: [{}], provider_repos: [{}], namespaces: [{}] };
+ mock.onGet(`${TEST_HOST}/endpoint.json`).reply(200, payload);
+
+ testAction(
+ fetchRepos,
+ null,
+ localState,
+ [],
+ [
+ { type: 'requestRepos' },
+ {
+ type: 'receiveReposSuccess',
+ payload: convertObjectPropsToCamelCase(payload, { deep: true }),
+ },
+ {
+ type: 'fetchJobs',
+ },
+ ],
+ done,
+ );
+ });
+
+ it('dispatches requestRepos and receiveReposSuccess actions on an unsuccessful request', done => {
+ mock.onGet(`${TEST_HOST}/endpoint.json`).reply(500);
+
+ testAction(
+ fetchRepos,
+ null,
+ localState,
+ [],
+ [{ type: 'requestRepos' }, { type: 'receiveReposError' }],
+ done,
+ );
+ });
+ });
+
+ describe('requestImport', () => {
+ it(`commits ${REQUEST_IMPORT} mutation`, done => {
+ testAction(
+ requestImport,
+ repoId,
+ localState,
+ [{ type: REQUEST_IMPORT, payload: repoId }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('receiveImportSuccess', () => {
+ it(`commits ${RECEIVE_IMPORT_SUCCESS} mutation`, done => {
+ const payload = { importedProject: { name: 'imported/project' }, repoId: 2 };
+
+ testAction(
+ receiveImportSuccess,
+ payload,
+ localState,
+ [{ type: RECEIVE_IMPORT_SUCCESS, payload }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('receiveImportError', () => {
+ it(`commits ${RECEIVE_IMPORT_ERROR} mutation`, done => {
+ testAction(
+ receiveImportError,
+ repoId,
+ localState,
+ [{ type: RECEIVE_IMPORT_ERROR, payload: repoId }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('fetchImport', () => {
+ let mock;
+
+ beforeEach(() => {
+ localState.importPath = `${TEST_HOST}/endpoint.json`;
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => mock.restore());
+
+ it('dispatches requestImport and receiveImportSuccess actions on a successful request', done => {
+ const importedProject = { name: 'imported/project' };
+ const importRepoId = importPayload.repo.id;
+ mock.onPost(`${TEST_HOST}/endpoint.json`).reply(200, importedProject);
+
+ testAction(
+ fetchImport,
+ importPayload,
+ localState,
+ [],
+ [
+ { type: 'requestImport', payload: importRepoId },
+ {
+ type: 'receiveImportSuccess',
+ payload: {
+ importedProject: convertObjectPropsToCamelCase(importedProject, { deep: true }),
+ repoId: importRepoId,
+ },
+ },
+ ],
+ done,
+ );
+ });
+
+ it('dispatches requestImport and receiveImportSuccess actions on an unsuccessful request', done => {
+ mock.onPost(`${TEST_HOST}/endpoint.json`).reply(500);
+
+ testAction(
+ fetchImport,
+ importPayload,
+ localState,
+ [],
+ [
+ { type: 'requestImport', payload: importPayload.repo.id },
+ { type: 'receiveImportError', payload: { repoId: importPayload.repo.id } },
+ ],
+ done,
+ );
+ });
+ });
+
+ describe('receiveJobsSuccess', () => {
+ it(`commits ${RECEIVE_JOBS_SUCCESS} mutation`, done => {
+ testAction(
+ receiveJobsSuccess,
+ repos,
+ localState,
+ [{ type: RECEIVE_JOBS_SUCCESS, payload: repos }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('fetchJobs', () => {
+ let mock;
+
+ beforeEach(() => {
+ localState.jobsPath = `${TEST_HOST}/endpoint.json`;
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ stopJobsPolling();
+ clearJobsEtagPoll();
+ });
+
+ afterEach(() => mock.restore());
+
+ it('dispatches requestJobs and receiveJobsSuccess actions on a successful request', done => {
+ const updatedProjects = [{ name: 'imported/project' }, { name: 'provider/repo' }];
+ mock.onGet(`${TEST_HOST}/endpoint.json`).reply(200, updatedProjects);
+
+ testAction(
+ fetchJobs,
+ null,
+ localState,
+ [],
+ [
+ {
+ type: 'receiveJobsSuccess',
+ payload: convertObjectPropsToCamelCase(updatedProjects, { deep: true }),
+ },
+ ],
+ done,
+ );
+ });
+ });
+});
diff --git a/spec/javascripts/import_projects/store/getters_spec.js b/spec/javascripts/import_projects/store/getters_spec.js
new file mode 100644
index 00000000000..e5e4a95f473
--- /dev/null
+++ b/spec/javascripts/import_projects/store/getters_spec.js
@@ -0,0 +1,83 @@
+import {
+ namespaceSelectOptions,
+ isImportingAnyRepo,
+ hasProviderRepos,
+ hasImportedProjects,
+} from '~/import_projects/store/getters';
+import state from '~/import_projects/store/state';
+
+describe('import_projects store getters', () => {
+ let localState;
+
+ beforeEach(() => {
+ localState = state();
+ });
+
+ describe('namespaceSelectOptions', () => {
+ const namespaces = [{ fullPath: 'namespace-0' }, { fullPath: 'namespace-1' }];
+ const defaultTargetNamespace = 'current-user';
+
+ it('returns an options array with a "Users" and "Groups" optgroups', () => {
+ localState.namespaces = namespaces;
+ localState.defaultTargetNamespace = defaultTargetNamespace;
+
+ const optionsArray = namespaceSelectOptions(localState);
+ const groupsGroup = optionsArray[0];
+ const usersGroup = optionsArray[1];
+
+ expect(groupsGroup.text).toBe('Groups');
+ expect(usersGroup.text).toBe('Users');
+
+ groupsGroup.children.forEach((child, index) => {
+ expect(child.id).toBe(namespaces[index].fullPath);
+ expect(child.text).toBe(namespaces[index].fullPath);
+ });
+
+ expect(usersGroup.children.length).toBe(1);
+ expect(usersGroup.children[0].id).toBe(defaultTargetNamespace);
+ expect(usersGroup.children[0].text).toBe(defaultTargetNamespace);
+ });
+ });
+
+ describe('isImportingAnyRepo', () => {
+ it('returns true if there are any reposBeingImported', () => {
+ localState.reposBeingImported = new Array(1);
+
+ expect(isImportingAnyRepo(localState)).toBe(true);
+ });
+
+ it('returns false if there are no reposBeingImported', () => {
+ localState.reposBeingImported = [];
+
+ expect(isImportingAnyRepo(localState)).toBe(false);
+ });
+ });
+
+ describe('hasProviderRepos', () => {
+ it('returns true if there are any providerRepos', () => {
+ localState.providerRepos = new Array(1);
+
+ expect(hasProviderRepos(localState)).toBe(true);
+ });
+
+ it('returns false if there are no providerRepos', () => {
+ localState.providerRepos = [];
+
+ expect(hasProviderRepos(localState)).toBe(false);
+ });
+ });
+
+ describe('hasImportedProjects', () => {
+ it('returns true if there are any importedProjects', () => {
+ localState.importedProjects = new Array(1);
+
+ expect(hasImportedProjects(localState)).toBe(true);
+ });
+
+ it('returns false if there are no importedProjects', () => {
+ localState.importedProjects = [];
+
+ expect(hasImportedProjects(localState)).toBe(false);
+ });
+ });
+});
diff --git a/spec/javascripts/import_projects/store/mutations_spec.js b/spec/javascripts/import_projects/store/mutations_spec.js
new file mode 100644
index 00000000000..8db8e9819ba
--- /dev/null
+++ b/spec/javascripts/import_projects/store/mutations_spec.js
@@ -0,0 +1,34 @@
+import * as types from '~/import_projects/store/mutation_types';
+import mutations from '~/import_projects/store/mutations';
+
+describe('import_projects store mutations', () => {
+ describe(types.RECEIVE_IMPORT_SUCCESS, () => {
+ it('removes repoId from reposBeingImported and providerRepos, adds to importedProjects', () => {
+ const repoId = 1;
+ const state = {
+ reposBeingImported: [repoId],
+ providerRepos: [{ id: repoId }],
+ importedProjects: [],
+ };
+ const importedProject = { id: repoId };
+
+ mutations[types.RECEIVE_IMPORT_SUCCESS](state, { importedProject, repoId });
+
+ expect(state.reposBeingImported.includes(repoId)).toBe(false);
+ expect(state.providerRepos.some(repo => repo.id === repoId)).toBe(false);
+ expect(state.importedProjects.some(repo => repo.id === repoId)).toBe(true);
+ });
+ });
+
+ describe(types.RECEIVE_JOBS_SUCCESS, () => {
+ it('updates importStatus of existing importedProjects', () => {
+ const repoId = 1;
+ const state = { importedProjects: [{ id: repoId, importStatus: 'started' }] };
+ const updatedProjects = [{ id: repoId, importStatus: 'finished' }];
+
+ mutations[types.RECEIVE_JOBS_SUCCESS](state, updatedProjects);
+
+ expect(state.importedProjects[0].importStatus).toBe(updatedProjects[0].importStatus);
+ });
+ });
+});
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js
index 3eff3f655ee..0bb43c94f6a 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js
+++ b/spec/javascripts/lib/utils/common_utils_spec.js
@@ -409,13 +409,6 @@ describe('common_utils', () => {
});
});
- describe('convertPermissionToBoolean', () => {
- it('should convert a boolean in a string to a boolean', () => {
- expect(commonUtils.convertPermissionToBoolean('true')).toEqual(true);
- expect(commonUtils.convertPermissionToBoolean('false')).toEqual(false);
- });
- });
-
describe('backOff', () => {
beforeEach(() => {
// shortcut our timeouts otherwise these tests will take a long time to finish
@@ -855,6 +848,7 @@ describe('common_utils', () => {
});
it('returns true when provided `el` is in viewport', () => {
+ el.setAttribute('style', `position: absolute; right: ${window.innerWidth + 0.2};`);
document.body.appendChild(el);
expect(commonUtils.isInViewport(el)).toBe(true);
diff --git a/spec/javascripts/lib/utils/number_utility_spec.js b/spec/javascripts/lib/utils/number_utility_spec.js
index 94c6214c86a..818404bad81 100644
--- a/spec/javascripts/lib/utils/number_utility_spec.js
+++ b/spec/javascripts/lib/utils/number_utility_spec.js
@@ -4,6 +4,7 @@ import {
bytesToMiB,
bytesToGiB,
numberToHumanSize,
+ sum,
} from '~/lib/utils/number_utils';
describe('Number Utils', () => {
@@ -87,4 +88,14 @@ describe('Number Utils', () => {
expect(numberToHumanSize(10737418240)).toEqual('10.00 GiB');
});
});
+
+ describe('sum', () => {
+ it('should add up two values', () => {
+ expect(sum(1, 2)).toEqual(3);
+ });
+
+ it('should add up all the values in an array when passed to a reducer', () => {
+ expect([1, 2, 3, 4, 5].reduce(sum)).toEqual(15);
+ });
+ });
});
diff --git a/spec/javascripts/lib/utils/poll_spec.js b/spec/javascripts/lib/utils/poll_spec.js
index d0da659c3d7..138041a349f 100644
--- a/spec/javascripts/lib/utils/poll_spec.js
+++ b/spec/javascripts/lib/utils/poll_spec.js
@@ -153,6 +153,36 @@ describe('Poll', () => {
});
});
+ describe('enable', () => {
+ it('should enable polling upon a response', done => {
+ jasmine.clock().install();
+
+ const Polling = new Poll({
+ resource: service,
+ method: 'fetch',
+ data: { page: 1 },
+ successCallback: () => {},
+ });
+
+ Polling.enable({
+ data: { page: 4 },
+ response: { status: 200, headers: { 'poll-interval': 1 } },
+ });
+
+ jasmine.clock().tick(1);
+ jasmine.clock().uninstall();
+
+ waitForAllCallsToFinish(service, 1, () => {
+ Polling.stop();
+
+ expect(service.fetch.calls.count()).toEqual(1);
+ expect(service.fetch).toHaveBeenCalledWith({ page: 4 });
+ expect(Polling.options.data).toEqual({ page: 4 });
+ done();
+ });
+ });
+ });
+
describe('restart', () => {
it('should restart polling when its called', done => {
mockServiceCall(service, { status: 200, headers: { 'poll-interval': 1 } });
@@ -171,6 +201,7 @@ describe('Poll', () => {
});
spyOn(Polling, 'stop').and.callThrough();
+ spyOn(Polling, 'enable').and.callThrough();
spyOn(Polling, 'restart').and.callThrough();
Polling.makeRequest();
@@ -181,6 +212,7 @@ describe('Poll', () => {
expect(service.fetch.calls.count()).toEqual(2);
expect(service.fetch).toHaveBeenCalledWith({ page: 4 });
expect(Polling.stop).toHaveBeenCalled();
+ expect(Polling.enable).toHaveBeenCalled();
expect(Polling.restart).toHaveBeenCalled();
expect(Polling.options.data).toEqual({ page: 4 });
done();
diff --git a/spec/javascripts/monitoring/charts/area_spec.js b/spec/javascripts/monitoring/charts/area_spec.js
index 0b36fc9f5f7..d334ef7ba4f 100644
--- a/spec/javascripts/monitoring/charts/area_spec.js
+++ b/spec/javascripts/monitoring/charts/area_spec.js
@@ -127,7 +127,7 @@ describe('Area component', () => {
});
it('formats tooltip content', () => {
- expect(areaChart.vm.tooltip.content).toBe('CPU (Cores) 5.556');
+ expect(areaChart.vm.tooltip.content).toBe('CPU 5.556');
});
});
@@ -213,7 +213,7 @@ describe('Area component', () => {
describe('yAxisLabel', () => {
it('constructs a label for the chart y-axis', () => {
- expect(areaChart.vm.yAxisLabel).toBe('CPU (Cores)');
+ expect(areaChart.vm.yAxisLabel).toBe('CPU');
});
});
});
diff --git a/spec/javascripts/notes/components/discussion_filter_note_spec.js b/spec/javascripts/notes/components/discussion_filter_note_spec.js
new file mode 100644
index 00000000000..52d2e7ce947
--- /dev/null
+++ b/spec/javascripts/notes/components/discussion_filter_note_spec.js
@@ -0,0 +1,93 @@
+import Vue from 'vue';
+import DiscussionFilterNote from '~/notes/components/discussion_filter_note.vue';
+import eventHub from '~/notes/event_hub';
+
+import mountComponent from '../../helpers/vue_mount_component_helper';
+
+describe('DiscussionFilterNote component', () => {
+ let vm;
+
+ const createComponent = () => {
+ const Component = Vue.extend(DiscussionFilterNote);
+
+ return mountComponent(Component);
+ };
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('computed', () => {
+ describe('timelineContent', () => {
+ it('returns string containing instruction for switching feed type', () => {
+ expect(vm.timelineContent).toBe(
+ "You're only seeing <b>other activity</b> in the feed. To add a comment, switch to one of the following options.",
+ );
+ });
+ });
+ });
+
+ describe('methods', () => {
+ describe('selectFilter', () => {
+ it('emits `dropdownSelect` event on `eventHub` with provided param', () => {
+ spyOn(eventHub, '$emit');
+
+ vm.selectFilter(1);
+
+ expect(eventHub.$emit).toHaveBeenCalledWith('dropdownSelect', 1);
+ });
+ });
+ });
+
+ describe('template', () => {
+ it('renders component container element', () => {
+ expect(vm.$el.classList.contains('discussion-filter-note')).toBe(true);
+ });
+
+ it('renders comment icon element', () => {
+ expect(vm.$el.querySelector('.timeline-icon svg use').getAttribute('xlink:href')).toContain(
+ 'comment',
+ );
+ });
+
+ it('renders filter information note', () => {
+ expect(vm.$el.querySelector('.timeline-content').innerText.trim()).toContain(
+ "You're only seeing other activity in the feed. To add a comment, switch to one of the following options.",
+ );
+ });
+
+ it('renders filter buttons', () => {
+ const buttonsContainerEl = vm.$el.querySelector('.discussion-filter-actions');
+
+ expect(buttonsContainerEl.querySelector('button:first-child').innerText.trim()).toContain(
+ 'Show all activity',
+ );
+
+ expect(buttonsContainerEl.querySelector('button:last-child').innerText.trim()).toContain(
+ 'Show comments only',
+ );
+ });
+
+ it('clicking `Show all activity` button calls `selectFilter("all")` method', () => {
+ const showAllBtn = vm.$el.querySelector('.discussion-filter-actions button:first-child');
+ spyOn(vm, 'selectFilter');
+
+ showAllBtn.dispatchEvent(new Event('click'));
+
+ expect(vm.selectFilter).toHaveBeenCalledWith(0);
+ });
+
+ it('clicking `Show comments only` button calls `selectFilter("comments")` method', () => {
+ const showAllBtn = vm.$el.querySelector('.discussion-filter-actions button:last-child');
+ spyOn(vm, 'selectFilter');
+
+ showAllBtn.dispatchEvent(new Event('click'));
+
+ expect(vm.selectFilter).toHaveBeenCalledWith(1);
+ });
+ });
+});
diff --git a/spec/javascripts/notes/components/discussion_filter_spec.js b/spec/javascripts/notes/components/discussion_filter_spec.js
index 91dab58ba7f..1c366aee8e2 100644
--- a/spec/javascripts/notes/components/discussion_filter_spec.js
+++ b/spec/javascripts/notes/components/discussion_filter_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import createStore from '~/notes/stores';
import DiscussionFilter from '~/notes/components/discussion_filter.vue';
-import { DISCUSSION_FILTERS_DEFAULT_VALUE } from '~/notes/constants';
+import { DISCUSSION_FILTERS_DEFAULT_VALUE, DISCUSSION_FILTER_TYPES } from '~/notes/constants';
import { mountComponentWithStore } from '../../helpers/vue_mount_component_helper';
import { discussionFiltersMock, discussionMock } from '../mock_data';
@@ -54,14 +54,18 @@ describe('DiscussionFilter component', () => {
});
it('updates to the selected item', () => {
- const filterItem = vm.$el.querySelector('.dropdown-menu li:last-child button');
+ const filterItem = vm.$el.querySelector(
+ `.dropdown-menu li[data-filter-type="${DISCUSSION_FILTER_TYPES.HISTORY}"] button`,
+ );
filterItem.click();
expect(vm.currentFilter.title).toEqual(filterItem.textContent.trim());
});
it('only updates when selected filter changes', () => {
- const filterItem = vm.$el.querySelector('.dropdown-menu li:first-child button');
+ const filterItem = vm.$el.querySelector(
+ `.dropdown-menu li[data-filter-type="${DISCUSSION_FILTER_TYPES.ALL}"] button`,
+ );
spyOn(vm, 'filterDiscussion');
filterItem.click();
@@ -70,21 +74,27 @@ describe('DiscussionFilter component', () => {
});
it('disables commenting when "Show history only" filter is applied', () => {
- const filterItem = vm.$el.querySelector('.dropdown-menu li:last-child button');
+ const filterItem = vm.$el.querySelector(
+ `.dropdown-menu li[data-filter-type="${DISCUSSION_FILTER_TYPES.HISTORY}"] button`,
+ );
filterItem.click();
expect(vm.$store.state.commentsDisabled).toBe(true);
});
it('enables commenting when "Show history only" filter is not applied', () => {
- const filterItem = vm.$el.querySelector('.dropdown-menu li:first-child button');
+ const filterItem = vm.$el.querySelector(
+ `.dropdown-menu li[data-filter-type="${DISCUSSION_FILTER_TYPES.ALL}"] button`,
+ );
filterItem.click();
expect(vm.$store.state.commentsDisabled).toBe(false);
});
it('renders a dropdown divider for the default filter', () => {
- const defaultFilter = vm.$el.querySelector('.dropdown-menu li:first-child');
+ const defaultFilter = vm.$el.querySelector(
+ `.dropdown-menu li[data-filter-type="${DISCUSSION_FILTER_TYPES.ALL}"]`,
+ );
expect(defaultFilter.lastChild.classList).toContain('dropdown-divider');
});
diff --git a/spec/javascripts/notes/components/discussion_jump_to_next_button_spec.js b/spec/javascripts/notes/components/discussion_jump_to_next_button_spec.js
deleted file mode 100644
index c41b29fa788..00000000000
--- a/spec/javascripts/notes/components/discussion_jump_to_next_button_spec.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import jumpToNextDiscussionButton from '~/notes/components/discussion_jump_to_next_button.vue';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
-
-const localVue = createLocalVue();
-
-describe('jumpToNextDiscussionButton', () => {
- let wrapper;
-
- beforeEach(() => {
- wrapper = shallowMount(jumpToNextDiscussionButton, {
- localVue,
- sync: false,
- });
- });
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- it('emits onClick event on button click', done => {
- const button = wrapper.find({ ref: 'button' });
-
- button.trigger('click');
-
- localVue.nextTick(() => {
- expect(wrapper.emitted()).toEqual({
- onClick: [[]],
- });
-
- done();
- });
- });
-});
diff --git a/spec/javascripts/notes/components/discussion_resolve_with_issue_button_spec.js b/spec/javascripts/notes/components/discussion_resolve_with_issue_button_spec.js
new file mode 100644
index 00000000000..b2a91c9919a
--- /dev/null
+++ b/spec/javascripts/notes/components/discussion_resolve_with_issue_button_spec.js
@@ -0,0 +1,31 @@
+import { GlButton } from '@gitlab/ui';
+import ResolveWithIssueButton from '~/notes/components/discussion_resolve_with_issue_button.vue';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { TEST_HOST } from 'spec/test_constants';
+
+const localVue = createLocalVue();
+
+describe('ResolveWithIssueButton', () => {
+ let wrapper;
+ const url = `${TEST_HOST}/hello-world/`;
+
+ beforeEach(() => {
+ wrapper = shallowMount(ResolveWithIssueButton, {
+ localVue,
+ sync: false,
+ propsData: {
+ url,
+ },
+ });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('it should have a link with the provided link property as href', () => {
+ const button = wrapper.find(GlButton);
+
+ expect(button.attributes().href).toBe(url);
+ });
+});
diff --git a/spec/javascripts/notes/components/note_actions/reply_button_spec.js b/spec/javascripts/notes/components/note_actions/reply_button_spec.js
index 11e1664a3f4..11fb89808d9 100644
--- a/spec/javascripts/notes/components/note_actions/reply_button_spec.js
+++ b/spec/javascripts/notes/components/note_actions/reply_button_spec.js
@@ -3,27 +3,14 @@ import { createLocalVue, mount } from '@vue/test-utils';
import ReplyButton from '~/notes/components/note_actions/reply_button.vue';
describe('ReplyButton', () => {
- const noteId = 'dummy-note-id';
-
let wrapper;
- let convertToDiscussion;
beforeEach(() => {
const localVue = createLocalVue();
- convertToDiscussion = jasmine.createSpy('convertToDiscussion');
localVue.use(Vuex);
- const store = new Vuex.Store({
- actions: {
- convertToDiscussion,
- },
- });
wrapper = mount(ReplyButton, {
- propsData: {
- noteId,
- },
- store,
sync: false,
localVue,
});
@@ -33,14 +20,13 @@ describe('ReplyButton', () => {
wrapper.destroy();
});
- it('dispatches convertToDiscussion with note ID on click', () => {
+ it('emits startReplying on click', () => {
const button = wrapper.find({ ref: 'button' });
button.trigger('click');
- expect(convertToDiscussion).toHaveBeenCalledTimes(1);
- const [, payload] = convertToDiscussion.calls.argsFor(0);
-
- expect(payload).toBe(noteId);
+ expect(wrapper.emitted()).toEqual({
+ startReplying: [[]],
+ });
});
});
diff --git a/spec/javascripts/notes/components/note_app_spec.js b/spec/javascripts/notes/components/note_app_spec.js
index d5c0bf6b25d..d716ece3766 100644
--- a/spec/javascripts/notes/components/note_app_spec.js
+++ b/spec/javascripts/notes/components/note_app_spec.js
@@ -83,6 +83,8 @@ describe('note_app', () => {
describe('render', () => {
beforeEach(() => {
+ setFixtures('<div class="js-discussions-count"></div>');
+
Vue.http.interceptors.push(mockData.individualNoteInterceptor);
wrapper = mountComponent();
});
@@ -124,9 +126,24 @@ describe('note_app', () => {
expect(wrapper.find('.js-main-target-form').exists()).toBe(false);
});
+ it('should render discussion filter note `commentsDisabled` is true', () => {
+ store.state.commentsDisabled = true;
+ wrapper = mountComponent();
+
+ expect(wrapper.find('.js-discussion-filter-note').exists()).toBe(true);
+ });
+
it('should render form comment button as disabled', () => {
expect(wrapper.find('.js-note-new-discussion').attributes('disabled')).toEqual('disabled');
});
+
+ it('updates discussions badge', done => {
+ setTimeout(() => {
+ expect(document.querySelector('.js-discussions-count').textContent).toEqual('2');
+
+ done();
+ });
+ });
});
describe('while fetching data', () => {
diff --git a/spec/javascripts/notes/components/note_form_spec.js b/spec/javascripts/notes/components/note_form_spec.js
index 5db20fd285f..7cc324cfe44 100644
--- a/spec/javascripts/notes/components/note_form_spec.js
+++ b/spec/javascripts/notes/components/note_form_spec.js
@@ -1,17 +1,15 @@
-import Vue from 'vue';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
import createStore from '~/notes/stores';
-import issueNoteForm from '~/notes/components/note_form.vue';
+import NoteForm from '~/notes/components/note_form.vue';
+import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import { noteableDataMock, notesDataMock } from '../mock_data';
-import { keyboardDownEvent } from '../../issue_show/helpers';
describe('issue_note_form component', () => {
let store;
- let vm;
+ let wrapper;
let props;
beforeEach(() => {
- const Component = Vue.extend(issueNoteForm);
-
store = createStore();
store.dispatch('setNoteableData', noteableDataMock);
store.dispatch('setNotesData', notesDataMock);
@@ -22,26 +20,35 @@ describe('issue_note_form component', () => {
noteId: '545',
};
- vm = new Component({
+ const localVue = createLocalVue();
+ wrapper = shallowMount(NoteForm, {
store,
propsData: props,
- }).$mount();
+ // see https://gitlab.com/gitlab-org/gitlab-ce/issues/56317 for the following
+ localVue,
+ sync: false,
+ });
});
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
});
describe('noteHash', () => {
it('returns note hash string based on `noteId`', () => {
- expect(vm.noteHash).toBe(`#note_${props.noteId}`);
+ expect(wrapper.vm.noteHash).toBe(`#note_${props.noteId}`);
});
it('return note hash as `#` when `noteId` is empty', done => {
- vm.noteId = '';
- Vue.nextTick()
+ wrapper.setProps({
+ ...props,
+ noteId: '',
+ });
+
+ wrapper.vm
+ .$nextTick()
.then(() => {
- expect(vm.noteHash).toBe('#');
+ expect(wrapper.vm.noteHash).toBe('#');
})
.then(done)
.catch(done.fail);
@@ -50,95 +57,127 @@ describe('issue_note_form component', () => {
describe('conflicts editing', () => {
it('should show conflict message if note changes outside the component', done => {
- vm.isEditing = true;
- vm.noteBody = 'Foo';
+ wrapper.setProps({
+ ...props,
+ isEditing: true,
+ noteBody: 'Foo',
+ });
+
const message =
'This comment has changed since you started editing, please review the updated comment to ensure information is not lost.';
- Vue.nextTick(() => {
- expect(
- vm.$el
- .querySelector('.js-conflict-edit-warning')
- .textContent.replace(/\s+/g, ' ')
- .trim(),
- ).toEqual(message);
- done();
- });
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ const conflictWarning = wrapper.find('.js-conflict-edit-warning');
+
+ expect(conflictWarning.exists()).toBe(true);
+ expect(
+ conflictWarning
+ .text()
+ .replace(/\s+/g, ' ')
+ .trim(),
+ ).toBe(message);
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('form', () => {
it('should render text area with placeholder', () => {
- expect(vm.$el.querySelector('textarea').getAttribute('placeholder')).toEqual(
+ const textarea = wrapper.find('textarea');
+
+ expect(textarea.attributes('placeholder')).toEqual(
'Write a comment or drag your files here…',
);
});
it('should link to markdown docs', () => {
const { markdownDocsPath } = notesDataMock;
+ const markdownField = wrapper.find(MarkdownField);
+ const markdownFieldProps = markdownField.props();
- expect(vm.$el.querySelector(`a[href="${markdownDocsPath}"]`).textContent.trim()).toEqual(
- 'Markdown',
- );
+ expect(markdownFieldProps.markdownDocsPath).toBe(markdownDocsPath);
});
describe('keyboard events', () => {
+ let textarea;
+
+ beforeEach(() => {
+ textarea = wrapper.find('textarea');
+ textarea.setValue('Foo');
+ });
+
describe('up', () => {
it('should ender edit mode', () => {
- spyOn(vm, 'editMyLastNote').and.callThrough();
- vm.$el.querySelector('textarea').value = 'Foo';
- vm.$el.querySelector('textarea').dispatchEvent(keyboardDownEvent(38, true));
+ // TODO: do not spy on vm
+ spyOn(wrapper.vm, 'editMyLastNote').and.callThrough();
+
+ textarea.trigger('keydown.up');
- expect(vm.editMyLastNote).toHaveBeenCalled();
+ expect(wrapper.vm.editMyLastNote).toHaveBeenCalled();
});
});
describe('enter', () => {
it('should save note when cmd+enter is pressed', () => {
- spyOn(vm, 'handleUpdate').and.callThrough();
- vm.$el.querySelector('textarea').value = 'Foo';
- vm.$el.querySelector('textarea').dispatchEvent(keyboardDownEvent(13, true));
+ textarea.trigger('keydown.enter', { metaKey: true });
+
+ const { handleFormUpdate } = wrapper.emitted();
- expect(vm.handleUpdate).toHaveBeenCalled();
+ expect(handleFormUpdate.length).toBe(1);
});
it('should save note when ctrl+enter is pressed', () => {
- spyOn(vm, 'handleUpdate').and.callThrough();
- vm.$el.querySelector('textarea').value = 'Foo';
- vm.$el.querySelector('textarea').dispatchEvent(keyboardDownEvent(13, false, true));
+ textarea.trigger('keydown.enter', { ctrlKey: true });
- expect(vm.handleUpdate).toHaveBeenCalled();
+ const { handleFormUpdate } = wrapper.emitted();
+
+ expect(handleFormUpdate.length).toBe(1);
});
});
});
describe('actions', () => {
it('should be possible to cancel', done => {
- spyOn(vm, 'cancelHandler').and.callThrough();
- vm.isEditing = true;
+ // TODO: do not spy on vm
+ spyOn(wrapper.vm, 'cancelHandler').and.callThrough();
+ wrapper.setProps({
+ ...props,
+ isEditing: true,
+ });
- Vue.nextTick(() => {
- vm.$el.querySelector('.note-edit-cancel').click();
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ const cancelButton = wrapper.find('.note-edit-cancel');
+ cancelButton.trigger('click');
- Vue.nextTick(() => {
- expect(vm.cancelHandler).toHaveBeenCalled();
- done();
- });
- });
+ expect(wrapper.vm.cancelHandler).toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
});
it('should be possible to update the note', done => {
- vm.isEditing = true;
-
- Vue.nextTick(() => {
- vm.$el.querySelector('textarea').value = 'Foo';
- vm.$el.querySelector('.js-vue-issue-save').click();
-
- Vue.nextTick(() => {
- expect(vm.isSubmitting).toEqual(true);
- done();
- });
+ wrapper.setProps({
+ ...props,
+ isEditing: true,
});
+
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ const textarea = wrapper.find('textarea');
+ textarea.setValue('Foo');
+ const saveButton = wrapper.find('.js-vue-issue-save');
+ saveButton.trigger('click');
+
+ expect(wrapper.vm.isSubmitting).toEqual(true);
+ })
+ .then(done)
+ .catch(done.fail);
});
});
});
diff --git a/spec/javascripts/notes/components/noteable_discussion_spec.js b/spec/javascripts/notes/components/noteable_discussion_spec.js
index 2eae22e095f..2b93fb9fb45 100644
--- a/spec/javascripts/notes/components/noteable_discussion_spec.js
+++ b/spec/javascripts/notes/components/noteable_discussion_spec.js
@@ -2,6 +2,7 @@ import { shallowMount, createLocalVue } from '@vue/test-utils';
import createStore from '~/notes/stores';
import noteableDiscussion from '~/notes/components/noteable_discussion.vue';
import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue';
+import ResolveWithIssueButton from '~/notes/components/discussion_resolve_with_issue_button.vue';
import '~/behaviors/markdown/render_gfm';
import { noteableDataMock, discussionMock, notesDataMock } from '../mock_data';
import mockDiffFile from '../../diffs/mock_data/diff_file';
@@ -238,4 +239,42 @@ describe('noteable_discussion component', () => {
});
});
});
+
+ describe('for resolved discussion', () => {
+ beforeEach(() => {
+ const discussion = getJSONFixture(discussionWithTwoUnresolvedNotes)[0];
+ wrapper.setProps({ discussion });
+ });
+
+ it('does not display a button to resolve with issue', () => {
+ const button = wrapper.find(ResolveWithIssueButton);
+
+ expect(button.exists()).toBe(false);
+ });
+ });
+
+ describe('for unresolved discussion', () => {
+ beforeEach(done => {
+ const discussion = {
+ ...getJSONFixture(discussionWithTwoUnresolvedNotes)[0],
+ expanded: true,
+ };
+ discussion.notes = discussion.notes.map(note => ({
+ ...note,
+ resolved: false,
+ }));
+
+ wrapper.setProps({ discussion });
+ wrapper.vm
+ .$nextTick()
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('displays a button to resolve with issue', () => {
+ const button = wrapper.find(ResolveWithIssueButton);
+
+ expect(button.exists()).toBe(true);
+ });
+ });
});
diff --git a/spec/javascripts/notes/stores/actions_spec.js b/spec/javascripts/notes/stores/actions_spec.js
index 73f960dd21e..94ce6d8e222 100644
--- a/spec/javascripts/notes/stores/actions_spec.js
+++ b/spec/javascripts/notes/stores/actions_spec.js
@@ -1,8 +1,11 @@
import Vue from 'vue';
import $ from 'jquery';
import _ from 'underscore';
+import { TEST_HOST } from 'spec/test_constants';
import { headersInterceptor } from 'spec/helpers/vue_resource_helper';
import * as actions from '~/notes/stores/actions';
+import * as mutationTypes from '~/notes/stores/mutation_types';
+import * as notesConstants from '~/notes/constants';
import createStore from '~/notes/stores';
import mrWidgetEventHub from '~/vue_merge_request_widget/event_hub';
import testAction from '../../helpers/vuex_action_helper';
@@ -599,4 +602,153 @@ describe('Actions Notes Store', () => {
);
});
});
+
+ describe('updateOrCreateNotes', () => {
+ let commit;
+ let dispatch;
+ let state;
+
+ beforeEach(() => {
+ commit = jasmine.createSpy('commit');
+ dispatch = jasmine.createSpy('dispatch');
+ state = {};
+ });
+
+ afterEach(() => {
+ commit.calls.reset();
+ dispatch.calls.reset();
+ });
+
+ it('Updates existing note', () => {
+ const note = { id: 1234 };
+ const getters = { notesById: { 1234: note } };
+
+ actions.updateOrCreateNotes({ commit, state, getters, dispatch }, [note]);
+
+ expect(commit.calls.allArgs()).toEqual([[mutationTypes.UPDATE_NOTE, note]]);
+ });
+
+ it('Creates a new note if none exisits', () => {
+ const note = { id: 1234 };
+ const getters = { notesById: {} };
+ actions.updateOrCreateNotes({ commit, state, getters, dispatch }, [note]);
+
+ expect(commit.calls.allArgs()).toEqual([[mutationTypes.ADD_NEW_NOTE, note]]);
+ });
+
+ describe('Discussion notes', () => {
+ let note;
+ let getters;
+
+ beforeEach(() => {
+ note = { id: 1234 };
+ getters = { notesById: {} };
+ });
+
+ it('Adds a reply to an existing discussion', () => {
+ state = { discussions: [note] };
+ const discussionNote = {
+ ...note,
+ type: notesConstants.DISCUSSION_NOTE,
+ discussion_id: 1234,
+ };
+
+ actions.updateOrCreateNotes({ commit, state, getters, dispatch }, [discussionNote]);
+
+ expect(commit.calls.allArgs()).toEqual([
+ [mutationTypes.ADD_NEW_REPLY_TO_DISCUSSION, discussionNote],
+ ]);
+ });
+
+ it('fetches discussions for diff notes', () => {
+ state = { discussions: [], notesData: { discussionsPath: 'Hello world' } };
+ const diffNote = { ...note, type: notesConstants.DIFF_NOTE, discussion_id: 1234 };
+
+ actions.updateOrCreateNotes({ commit, state, getters, dispatch }, [diffNote]);
+
+ expect(dispatch.calls.allArgs()).toEqual([
+ ['fetchDiscussions', { path: state.notesData.discussionsPath }],
+ ]);
+ });
+
+ it('Adds a new note', () => {
+ state = { discussions: [] };
+ const discussionNote = {
+ ...note,
+ type: notesConstants.DISCUSSION_NOTE,
+ discussion_id: 1234,
+ };
+
+ actions.updateOrCreateNotes({ commit, state, getters, dispatch }, [discussionNote]);
+
+ expect(commit.calls.allArgs()).toEqual([[mutationTypes.ADD_NEW_NOTE, discussionNote]]);
+ });
+ });
+ });
+
+ describe('replyToDiscussion', () => {
+ let res = { discussion: { notes: [] } };
+ const payload = { endpoint: TEST_HOST, data: {} };
+ const interceptor = (request, next) => {
+ next(
+ request.respondWith(JSON.stringify(res), {
+ status: 200,
+ }),
+ );
+ };
+
+ beforeEach(() => {
+ Vue.http.interceptors.push(interceptor);
+ });
+
+ afterEach(() => {
+ Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor);
+ });
+
+ it('updates discussion if response contains disussion', done => {
+ testAction(
+ actions.replyToDiscussion,
+ payload,
+ {
+ notesById: {},
+ },
+ [{ type: mutationTypes.UPDATE_DISCUSSION, payload: res.discussion }],
+ [
+ { type: 'updateMergeRequestWidget' },
+ { type: 'startTaskList' },
+ { type: 'updateResolvableDiscussonsCounts' },
+ ],
+ done,
+ );
+ });
+
+ it('adds a reply to a discussion', done => {
+ res = {};
+
+ testAction(
+ actions.replyToDiscussion,
+ payload,
+ {
+ notesById: {},
+ },
+ [{ type: mutationTypes.ADD_NEW_REPLY_TO_DISCUSSION, payload: res }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('removeConvertedDiscussion', () => {
+ it('commits CONVERT_TO_DISCUSSION with noteId', done => {
+ const noteId = 'dummy-id';
+ testAction(
+ actions.removeConvertedDiscussion,
+ noteId,
+ {},
+ [{ type: 'REMOVE_CONVERTED_DISCUSSION', payload: noteId }],
+ [],
+ done,
+ );
+ });
+ });
});
diff --git a/spec/javascripts/notes/stores/getters_spec.js b/spec/javascripts/notes/stores/getters_spec.js
index c066975a43b..8f3c493dd4c 100644
--- a/spec/javascripts/notes/stores/getters_spec.js
+++ b/spec/javascripts/notes/stores/getters_spec.js
@@ -261,4 +261,12 @@ describe('Getters Notes Store', () => {
expect(getters.firstUnresolvedDiscussionId(state, localGettersFalsy)(false)).toBeFalsy();
});
});
+
+ describe('getDiscussion', () => {
+ it('returns discussion by ID', () => {
+ state.discussions.push({ id: '1' });
+
+ expect(getters.getDiscussion(state)('1')).toEqual({ id: '1' });
+ });
+ });
});
diff --git a/spec/javascripts/notes/stores/mutation_spec.js b/spec/javascripts/notes/stores/mutation_spec.js
index 4f8d3069bb5..aa08bf59a7f 100644
--- a/spec/javascripts/notes/stores/mutation_spec.js
+++ b/spec/javascripts/notes/stores/mutation_spec.js
@@ -527,17 +527,32 @@ describe('Notes Store mutations', () => {
id: 42,
individual_note: true,
};
- state = { discussions: [discussion] };
+ state = { convertedDisscussionIds: [] };
});
- it('toggles individual_note', () => {
+ it('adds a discussion to convertedDisscussionIds', () => {
mutations.CONVERT_TO_DISCUSSION(state, discussion.id);
- expect(discussion.individual_note).toBe(false);
+ expect(state.convertedDisscussionIds).toContain(discussion.id);
});
+ });
+
+ describe('REMOVE_CONVERTED_DISCUSSION', () => {
+ let discussion;
+ let state;
+
+ beforeEach(() => {
+ discussion = {
+ id: 42,
+ individual_note: true,
+ };
+ state = { convertedDisscussionIds: [41, 42] };
+ });
+
+ it('removes a discussion from convertedDisscussionIds', () => {
+ mutations.REMOVE_CONVERTED_DISCUSSION(state, discussion.id);
- it('throws if discussion was not found', () => {
- expect(() => mutations.CONVERT_TO_DISCUSSION(state, 99)).toThrow();
+ expect(state.convertedDisscussionIds).not.toContain(discussion.id);
});
});
});
diff --git a/spec/javascripts/pipelines/pipelines_spec.js b/spec/javascripts/pipelines/pipelines_spec.js
index 97ded16db69..78187b69563 100644
--- a/spec/javascripts/pipelines/pipelines_spec.js
+++ b/spec/javascripts/pipelines/pipelines_spec.js
@@ -446,7 +446,7 @@ describe('Pipelines', () => {
};
vm.$nextTick(() => {
- vm.$el.querySelector('.js-next-button a').click();
+ vm.$el.querySelector('.js-next-button .page-link').click();
expect(vm.updateContent).toHaveBeenCalledWith({ scope: 'all', page: '2' });
diff --git a/spec/javascripts/vue_mr_widget/components/deployment_spec.js b/spec/javascripts/vue_mr_widget/components/deployment_spec.js
index e355416bd27..42bf3b7df09 100644
--- a/spec/javascripts/vue_mr_widget/components/deployment_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/deployment_spec.js
@@ -6,32 +6,36 @@ import mountComponent from '../../helpers/vue_mount_component_helper';
describe('Deployment component', () => {
const Component = Vue.extend(deploymentComponent);
- const deploymentMockData = {
- id: 15,
- name: 'review/diplo',
- url: '/root/review-apps/environments/15',
- stop_url: '/root/review-apps/environments/15/stop',
- metrics_url: '/root/review-apps/environments/15/deployments/1/metrics',
- metrics_monitoring_url: '/root/review-apps/environments/15/metrics',
- external_url: 'http://gitlab.com.',
- external_url_formatted: 'gitlab',
- deployed_at: '2017-03-22T22:44:42.258Z',
- deployed_at_formatted: 'Mar 22, 2017 10:44pm',
- changes: [
- {
- path: 'index.html',
- external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html',
- },
- {
- path: 'imgs/gallery.html',
- external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html',
- },
- {
- path: 'about/',
- external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/',
- },
- ],
- };
+ let deploymentMockData;
+
+ beforeEach(() => {
+ deploymentMockData = {
+ id: 15,
+ name: 'review/diplo',
+ url: '/root/review-apps/environments/15',
+ stop_url: '/root/review-apps/environments/15/stop',
+ metrics_url: '/root/review-apps/environments/15/deployments/1/metrics',
+ metrics_monitoring_url: '/root/review-apps/environments/15/metrics',
+ external_url: 'http://gitlab.com.',
+ external_url_formatted: 'gitlab',
+ deployed_at: '2017-03-22T22:44:42.258Z',
+ deployed_at_formatted: 'Mar 22, 2017 10:44pm',
+ changes: [
+ {
+ path: 'index.html',
+ external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html',
+ },
+ {
+ path: 'imgs/gallery.html',
+ external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html',
+ },
+ {
+ path: 'about/',
+ external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/',
+ },
+ ],
+ };
+ });
let vm;
@@ -207,6 +211,31 @@ describe('Deployment component', () => {
});
});
+ describe('with a single change', () => {
+ beforeEach(() => {
+ deploymentMockData.changes = deploymentMockData.changes.slice(0, 1);
+
+ vm = mountComponent(Component, {
+ deployment: { ...deploymentMockData },
+ showMetrics: true,
+ });
+ });
+
+ it('renders the link to the review app without dropdown', () => {
+ expect(vm.$el.querySelector('.js-mr-wigdet-deployment-dropdown')).toBeNull();
+ expect(vm.$el.querySelector('.js-deploy-url-feature-flag')).not.toBeNull();
+ });
+
+ it('renders the link to the review app linked to to the first change', () => {
+ const expectedUrl = deploymentMockData.changes[0].external_url;
+ const deployUrl = vm.$el.querySelector('.js-deploy-url-feature-flag');
+
+ expect(vm.$el.querySelector('.js-mr-wigdet-deployment-dropdown')).toBeNull();
+ expect(deployUrl).not.toBeNull();
+ expect(deployUrl.href).toEqual(expectedUrl);
+ });
+ });
+
describe('deployment status', () => {
describe('running', () => {
beforeEach(() => {
diff --git a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
index ff08a46b922..3e8f73646c8 100644
--- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
+++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
@@ -21,6 +21,7 @@ describe('mrWidgetOptions', () => {
const COLLABORATION_MESSAGE = 'Allows commits from members who can merge to the target branch';
beforeEach(() => {
+ gon.features = { approvalRules: false };
// Prevent component mounting
delete mrWidgetOptions.el;
@@ -31,6 +32,7 @@ describe('mrWidgetOptions', () => {
});
afterEach(() => {
+ gon.features = null;
vm.$destroy();
});
diff --git a/spec/javascripts/vue_shared/components/changed_file_icon_spec.js b/spec/javascripts/vue_shared/components/changed_file_icon_spec.js
index 5b1038840c7..634ba8403d5 100644
--- a/spec/javascripts/vue_shared/components/changed_file_icon_spec.js
+++ b/spec/javascripts/vue_shared/components/changed_file_icon_spec.js
@@ -5,27 +5,40 @@ import createComponent from 'spec/helpers/vue_mount_component_helper';
describe('Changed file icon', () => {
let vm;
- beforeEach(() => {
+ function factory(props = {}) {
const component = Vue.extend(changedFileIcon);
vm = createComponent(component, {
+ ...props,
file: {
tempFile: false,
changed: true,
},
});
- });
+ }
afterEach(() => {
vm.$destroy();
});
+ it('centers icon', () => {
+ factory({
+ isCentered: true,
+ });
+
+ expect(vm.$el.classList).toContain('ml-auto');
+ });
+
describe('changedIcon', () => {
it('equals file-modified when not a temp file and has changes', () => {
+ factory();
+
expect(vm.changedIcon).toBe('file-modified');
});
it('equals file-addition when a temp file', () => {
+ factory();
+
vm.file.tempFile = true;
expect(vm.changedIcon).toBe('file-addition');
@@ -34,10 +47,14 @@ describe('Changed file icon', () => {
describe('changedIconClass', () => {
it('includes file-modified when not a temp file', () => {
+ factory();
+
expect(vm.changedIconClass).toContain('file-modified');
});
it('includes file-addition when a temp file', () => {
+ factory();
+
vm.file.tempFile = true;
expect(vm.changedIconClass).toContain('file-addition');
diff --git a/spec/javascripts/vue_shared/components/diff_viewer/diff_viewer_spec.js b/spec/javascripts/vue_shared/components/diff_viewer/diff_viewer_spec.js
index 6add6cdac4d..660eaddf01f 100644
--- a/spec/javascripts/vue_shared/components/diff_viewer/diff_viewer_spec.js
+++ b/spec/javascripts/vue_shared/components/diff_viewer/diff_viewer_spec.js
@@ -22,6 +22,7 @@ describe('DiffViewer', () => {
createComponent({
diffMode: 'replaced',
+ diffViewerMode: 'image',
newPath: GREEN_BOX_IMAGE_URL,
newSha: 'ABC',
oldPath: RED_BOX_IMAGE_URL,
@@ -45,6 +46,7 @@ describe('DiffViewer', () => {
it('renders fallback download diff display', done => {
createComponent({
diffMode: 'replaced',
+ diffViewerMode: 'added',
newPath: 'test.abc',
newSha: 'ABC',
oldPath: 'testold.abc',
@@ -72,6 +74,7 @@ describe('DiffViewer', () => {
it('renders renamed component', () => {
createComponent({
diffMode: 'renamed',
+ diffViewerMode: 'renamed',
newPath: 'test.abc',
newSha: 'ABC',
oldPath: 'testold.abc',
@@ -84,6 +87,7 @@ describe('DiffViewer', () => {
it('renders mode changed component', () => {
createComponent({
diffMode: 'mode_changed',
+ diffViewerMode: 'image',
newPath: 'test.abc',
newSha: 'ABC',
oldPath: 'testold.abc',
diff --git a/spec/javascripts/vue_shared/components/issue/related_issuable_item_spec.js b/spec/javascripts/vue_shared/components/issue/related_issuable_item_spec.js
new file mode 100644
index 00000000000..42198e92eea
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/issue/related_issuable_item_spec.js
@@ -0,0 +1,194 @@
+import Vue from 'vue';
+import { mount, createLocalVue } from '@vue/test-utils';
+import RelatedIssuableItem from '~/vue_shared/components/issue/related_issuable_item.vue';
+import { defaultMilestone, defaultAssignees } from './related_issuable_mock_data';
+
+describe('RelatedIssuableItem', () => {
+ let wrapper;
+ const props = {
+ idKey: 1,
+ displayReference: 'gitlab-org/gitlab-test#1',
+ pathIdSeparator: '#',
+ path: `${gl.TEST_HOST}/path`,
+ title: 'title',
+ confidential: true,
+ dueDate: '1990-12-31',
+ weight: 10,
+ createdAt: '2018-12-01T00:00:00.00Z',
+ milestone: defaultMilestone,
+ assignees: defaultAssignees,
+ eventNamespace: 'relatedIssue',
+ };
+ const slots = {
+ dueDate: '<div class="js-due-date-slot"></div>',
+ weight: '<div class="js-weight-slot"></div>',
+ };
+
+ beforeEach(() => {
+ const localVue = createLocalVue();
+
+ wrapper = mount(localVue.extend(RelatedIssuableItem), {
+ localVue,
+ slots,
+ sync: false,
+ propsData: props,
+ });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('contains issuable-info-container class when canReorder is false', () => {
+ expect(wrapper.props('canReorder')).toBe(false);
+ expect(wrapper.find('.issuable-info-container').exists()).toBe(true);
+ });
+
+ it('does not render token state', () => {
+ expect(wrapper.find('.text-secondary svg').exists()).toBe(false);
+ });
+
+ it('does not render remove button', () => {
+ expect(wrapper.find({ ref: 'removeButton' }).exists()).toBe(false);
+ });
+
+ describe('token title', () => {
+ it('links to computedPath', () => {
+ expect(wrapper.find('.item-title a').attributes('href')).toEqual(wrapper.props('path'));
+ });
+
+ it('renders confidential icon', () => {
+ expect(wrapper.find('.confidential-icon').exists()).toBe(true);
+ });
+
+ it('renders title', () => {
+ expect(wrapper.find('.item-title a').text()).toEqual(props.title);
+ });
+ });
+
+ describe('token state', () => {
+ let tokenState;
+
+ beforeEach(done => {
+ wrapper.setProps({ state: 'opened' });
+
+ Vue.nextTick(() => {
+ tokenState = wrapper.find('.issue-token-state-icon-open');
+
+ done();
+ });
+ });
+
+ it('renders if hasState', () => {
+ expect(tokenState.exists()).toBe(true);
+ });
+
+ it('renders state title', () => {
+ const stateTitle = tokenState.attributes('data-original-title');
+
+ expect(stateTitle).toContain('<span class="bold">Opened</span>');
+ expect(stateTitle).toContain(
+ '<span class="text-tertiary">Dec 1, 2018 12:00am GMT+0000</span>',
+ );
+ });
+
+ it('renders aria label', () => {
+ expect(tokenState.attributes('aria-label')).toEqual('opened');
+ });
+
+ it('renders open icon when open state', () => {
+ expect(tokenState.classes('issue-token-state-icon-open')).toBe(true);
+ });
+
+ it('renders close icon when close state', done => {
+ wrapper.setProps({
+ state: 'closed',
+ closedAt: '2018-12-01T00:00:00.00Z',
+ });
+
+ Vue.nextTick(() => {
+ expect(tokenState.classes('issue-token-state-icon-closed')).toBe(true);
+
+ done();
+ });
+ });
+ });
+
+ describe('token metadata', () => {
+ let tokenMetadata;
+
+ beforeEach(done => {
+ Vue.nextTick(() => {
+ tokenMetadata = wrapper.find('.item-meta');
+
+ done();
+ });
+ });
+
+ it('renders item path and ID', () => {
+ const pathAndID = tokenMetadata.find('.item-path-id').text();
+
+ expect(pathAndID).toContain('gitlab-org/gitlab-test');
+ expect(pathAndID).toContain('#1');
+ });
+
+ it('renders milestone icon and name', () => {
+ const milestoneIcon = tokenMetadata.find('.item-milestone svg use');
+ const milestoneTitle = tokenMetadata.find('.item-milestone .milestone-title');
+
+ expect(milestoneIcon.attributes('href')).toContain('clock');
+ expect(milestoneTitle.text()).toContain('Milestone title');
+ });
+
+ it('renders due date component', () => {
+ expect(tokenMetadata.find('.js-due-date-slot').exists()).toBe(true);
+ });
+
+ it('renders weight component', () => {
+ expect(tokenMetadata.find('.js-weight-slot').exists()).toBe(true);
+ });
+ });
+
+ describe('token assignees', () => {
+ it('renders assignees avatars', () => {
+ expect(wrapper.findAll('.item-assignees .user-avatar-link').length).toBe(2);
+ expect(wrapper.find('.item-assignees .avatar-counter').text()).toContain('+2');
+ });
+ });
+
+ describe('remove button', () => {
+ let removeBtn;
+
+ beforeEach(done => {
+ wrapper.setProps({ canRemove: true });
+ Vue.nextTick(() => {
+ removeBtn = wrapper.find({ ref: 'removeButton' });
+
+ done();
+ });
+ });
+
+ it('renders if canRemove', () => {
+ expect(removeBtn.exists()).toBe(true);
+ });
+
+ it('renders disabled button when removeDisabled', done => {
+ wrapper.vm.removeDisabled = true;
+
+ Vue.nextTick(() => {
+ expect(removeBtn.attributes('disabled')).toEqual('disabled');
+
+ done();
+ });
+ });
+
+ it('triggers onRemoveRequest when clicked', () => {
+ removeBtn.trigger('click');
+
+ const { relatedIssueRemoveRequest } = wrapper.emitted();
+
+ expect(relatedIssueRemoveRequest.length).toBe(1);
+ expect(relatedIssueRemoveRequest[0]).toEqual([props.idKey]);
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/issue/related_issuable_mock_data.js b/spec/javascripts/vue_shared/components/issue/related_issuable_mock_data.js
new file mode 100644
index 00000000000..26bfdd7551e
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/issue/related_issuable_mock_data.js
@@ -0,0 +1,111 @@
+export const defaultProps = {
+ endpoint: '/foo/bar/issues/1/related_issues',
+ currentNamespacePath: 'foo',
+ currentProjectPath: 'bar',
+};
+
+export const issuable1 = {
+ id: 200,
+ epic_issue_id: 1,
+ confidential: false,
+ reference: 'foo/bar#123',
+ displayReference: '#123',
+ title: 'some title',
+ path: '/foo/bar/issues/123',
+ state: 'opened',
+};
+
+export const issuable2 = {
+ id: 201,
+ epic_issue_id: 2,
+ confidential: false,
+ reference: 'foo/bar#124',
+ displayReference: '#124',
+ title: 'some other thing',
+ path: '/foo/bar/issues/124',
+ state: 'opened',
+};
+
+export const issuable3 = {
+ id: 202,
+ epic_issue_id: 3,
+ confidential: false,
+ reference: 'foo/bar#125',
+ displayReference: '#125',
+ title: 'some other other thing',
+ path: '/foo/bar/issues/125',
+ state: 'opened',
+};
+
+export const issuable4 = {
+ id: 203,
+ epic_issue_id: 4,
+ confidential: false,
+ reference: 'foo/bar#126',
+ displayReference: '#126',
+ title: 'some other other other thing',
+ path: '/foo/bar/issues/126',
+ state: 'opened',
+};
+
+export const issuable5 = {
+ id: 204,
+ epic_issue_id: 5,
+ confidential: false,
+ reference: 'foo/bar#127',
+ displayReference: '#127',
+ title: 'some other other other thing',
+ path: '/foo/bar/issues/127',
+ state: 'opened',
+};
+
+export const defaultMilestone = {
+ id: 1,
+ state: 'active',
+ title: 'Milestone title',
+ start_date: '2018-01-01',
+ due_date: '2019-12-31',
+};
+
+export const defaultAssignees = [
+ {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url: `${gl.TEST_HOST}`,
+ web_url: `${gl.TEST_HOST}/root`,
+ status_tooltip_html: null,
+ path: '/root',
+ },
+ {
+ id: 13,
+ name: 'Brooks Beatty',
+ username: 'brynn_champlin',
+ state: 'active',
+ avatar_url: `${gl.TEST_HOST}`,
+ web_url: `${gl.TEST_HOST}/brynn_champlin`,
+ status_tooltip_html: null,
+ path: '/brynn_champlin',
+ },
+ {
+ id: 6,
+ name: 'Bryce Turcotte',
+ username: 'melynda',
+ state: 'active',
+ avatar_url: `${gl.TEST_HOST}`,
+ web_url: `${gl.TEST_HOST}/melynda`,
+ status_tooltip_html: null,
+ path: '/melynda',
+ },
+ {
+ id: 20,
+ name: 'Conchita Eichmann',
+ username: 'juliana_gulgowski',
+ state: 'active',
+ avatar_url: `${gl.TEST_HOST}`,
+ web_url: `${gl.TEST_HOST}/juliana_gulgowski`,
+ status_tooltip_html: null,
+ path: '/juliana_gulgowski',
+ },
+];
diff --git a/spec/javascripts/vue_shared/components/panel_resizer_spec.js b/spec/javascripts/vue_shared/components/panel_resizer_spec.js
index 49a580be06b..caabe3aa260 100644
--- a/spec/javascripts/vue_shared/components/panel_resizer_spec.js
+++ b/spec/javascripts/vue_shared/components/panel_resizer_spec.js
@@ -44,7 +44,10 @@ describe('Panel Resizer component', () => {
});
expect(vm.$el.tagName).toEqual('DIV');
- expect(vm.$el.getAttribute('class')).toBe('drag-handle drag-left');
+ expect(vm.$el.getAttribute('class')).toBe(
+ 'position-absolute position-top-0 position-bottom-0 drag-handle position-left-0',
+ );
+
expect(vm.$el.getAttribute('style')).toBe('cursor: ew-resize;');
});
@@ -55,7 +58,9 @@ describe('Panel Resizer component', () => {
});
expect(vm.$el.tagName).toEqual('DIV');
- expect(vm.$el.getAttribute('class')).toBe('drag-handle drag-right');
+ expect(vm.$el.getAttribute('class')).toBe(
+ 'position-absolute position-top-0 position-bottom-0 drag-handle position-right-0',
+ );
});
it('drag the resizer', () => {
diff --git a/spec/javascripts/vue_shared/components/table_pagination_spec.js b/spec/javascripts/vue_shared/components/table_pagination_spec.js
index 0dcb712e720..42cd41381dc 100644
--- a/spec/javascripts/vue_shared/components/table_pagination_spec.js
+++ b/spec/javascripts/vue_shared/components/table_pagination_spec.js
@@ -22,10 +22,10 @@ describe('Pagination component', () => {
it('should not render anything', () => {
component = mountComponent({
pageInfo: {
- nextPage: 1,
+ nextPage: NaN,
page: 1,
perPage: 20,
- previousPage: null,
+ previousPage: NaN,
total: 15,
totalPages: 1,
},
@@ -53,7 +53,29 @@ describe('Pagination component', () => {
component.$el.querySelector('.js-previous-button').classList.contains('disabled'),
).toEqual(true);
- component.$el.querySelector('.js-previous-button a').click();
+ component.$el.querySelector('.js-previous-button .page-link').click();
+
+ expect(spy).not.toHaveBeenCalled();
+ });
+
+ it('should be disabled and non clickable when total and totalPages are NaN', () => {
+ component = mountComponent({
+ pageInfo: {
+ nextPage: 2,
+ page: 1,
+ perPage: 20,
+ previousPage: NaN,
+ total: NaN,
+ totalPages: NaN,
+ },
+ change: spy,
+ });
+
+ expect(
+ component.$el.querySelector('.js-previous-button').classList.contains('disabled'),
+ ).toEqual(true);
+
+ component.$el.querySelector('.js-previous-button .page-link').click();
expect(spy).not.toHaveBeenCalled();
});
@@ -71,7 +93,25 @@ describe('Pagination component', () => {
change: spy,
});
- component.$el.querySelector('.js-previous-button a').click();
+ component.$el.querySelector('.js-previous-button .page-link').click();
+
+ expect(spy).toHaveBeenCalledWith(1);
+ });
+
+ it('should be enabled and clickable when total and totalPages are NaN', () => {
+ component = mountComponent({
+ pageInfo: {
+ nextPage: 3,
+ page: 2,
+ perPage: 20,
+ previousPage: 1,
+ total: NaN,
+ totalPages: NaN,
+ },
+ change: spy,
+ });
+
+ component.$el.querySelector('.js-previous-button .page-link').click();
expect(spy).toHaveBeenCalledWith(1);
});
@@ -91,7 +131,29 @@ describe('Pagination component', () => {
change: spy,
});
- const button = component.$el.querySelector('.js-first-button a');
+ const button = component.$el.querySelector('.js-first-button .page-link');
+
+ expect(button.textContent.trim()).toEqual('« First');
+
+ button.click();
+
+ expect(spy).toHaveBeenCalledWith(1);
+ });
+
+ it('should call the change callback with the first page when total and totalPages are NaN', () => {
+ component = mountComponent({
+ pageInfo: {
+ nextPage: 3,
+ page: 2,
+ perPage: 20,
+ previousPage: 1,
+ total: NaN,
+ totalPages: NaN,
+ },
+ change: spy,
+ });
+
+ const button = component.$el.querySelector('.js-first-button .page-link');
expect(button.textContent.trim()).toEqual('« First');
@@ -115,7 +177,7 @@ describe('Pagination component', () => {
change: spy,
});
- const button = component.$el.querySelector('.js-last-button a');
+ const button = component.$el.querySelector('.js-last-button .page-link');
expect(button.textContent.trim()).toEqual('Last »');
@@ -123,16 +185,32 @@ describe('Pagination component', () => {
expect(spy).toHaveBeenCalledWith(5);
});
+
+ it('should not render', () => {
+ component = mountComponent({
+ pageInfo: {
+ nextPage: 3,
+ page: 2,
+ perPage: 20,
+ previousPage: 1,
+ total: NaN,
+ totalPages: NaN,
+ },
+ change: spy,
+ });
+
+ expect(component.$el.querySelector('.js-last-button .page-link')).toBeNull();
+ });
});
describe('next button', () => {
it('should be disabled and non clickable', () => {
component = mountComponent({
pageInfo: {
- nextPage: 5,
+ nextPage: NaN,
page: 5,
perPage: 20,
- previousPage: 1,
+ previousPage: 4,
total: 84,
totalPages: 5,
},
@@ -141,7 +219,27 @@ describe('Pagination component', () => {
expect(component.$el.querySelector('.js-next-button').textContent.trim()).toEqual('Next');
- component.$el.querySelector('.js-next-button a').click();
+ component.$el.querySelector('.js-next-button .page-link').click();
+
+ expect(spy).not.toHaveBeenCalled();
+ });
+
+ it('should be disabled and non clickable when total and totalPages are NaN', () => {
+ component = mountComponent({
+ pageInfo: {
+ nextPage: NaN,
+ page: 5,
+ perPage: 20,
+ previousPage: 4,
+ total: NaN,
+ totalPages: NaN,
+ },
+ change: spy,
+ });
+
+ expect(component.$el.querySelector('.js-next-button').textContent.trim()).toEqual('Next');
+
+ component.$el.querySelector('.js-next-button .page-link').click();
expect(spy).not.toHaveBeenCalled();
});
@@ -159,7 +257,25 @@ describe('Pagination component', () => {
change: spy,
});
- component.$el.querySelector('.js-next-button a').click();
+ component.$el.querySelector('.js-next-button .page-link').click();
+
+ expect(spy).toHaveBeenCalledWith(4);
+ });
+
+ it('should be enabled and clickable when total and totalPages are NaN', () => {
+ component = mountComponent({
+ pageInfo: {
+ nextPage: 4,
+ page: 3,
+ perPage: 20,
+ previousPage: 2,
+ total: NaN,
+ totalPages: NaN,
+ },
+ change: spy,
+ });
+
+ component.$el.querySelector('.js-next-button .page-link').click();
expect(spy).toHaveBeenCalledWith(4);
});
@@ -181,22 +297,56 @@ describe('Pagination component', () => {
expect(component.$el.querySelectorAll('.page').length).toEqual(5);
});
+
+ it('should not render any page', () => {
+ component = mountComponent({
+ pageInfo: {
+ nextPage: 4,
+ page: 3,
+ perPage: 20,
+ previousPage: 2,
+ total: NaN,
+ totalPages: NaN,
+ },
+ change: spy,
+ });
+
+ expect(component.$el.querySelectorAll('.page').length).toEqual(0);
+ });
});
- it('should render the spread operator', () => {
- component = mountComponent({
- pageInfo: {
- nextPage: 4,
- page: 3,
- perPage: 20,
- previousPage: 2,
- total: 84,
- totalPages: 10,
- },
- change: spy,
+ describe('spread operator', () => {
+ it('should render', () => {
+ component = mountComponent({
+ pageInfo: {
+ nextPage: 4,
+ page: 3,
+ perPage: 20,
+ previousPage: 2,
+ total: 84,
+ totalPages: 10,
+ },
+ change: spy,
+ });
+
+ expect(component.$el.querySelector('.separator').textContent.trim()).toEqual('...');
});
- expect(component.$el.querySelector('.separator').textContent.trim()).toEqual('...');
+ it('should not render', () => {
+ component = mountComponent({
+ pageInfo: {
+ nextPage: 4,
+ page: 3,
+ perPage: 20,
+ previousPage: 2,
+ total: NaN,
+ totalPages: NaN,
+ },
+ change: spy,
+ });
+
+ expect(component.$el.querySelector('.separator')).toBeNull();
+ });
});
});
});
diff --git a/spec/lib/api/helpers/pagination_spec.rb b/spec/lib/api/helpers/pagination_spec.rb
index 2890aa4ae38..6e215ea1561 100644
--- a/spec/lib/api/helpers/pagination_spec.rb
+++ b/spec/lib/api/helpers/pagination_spec.rb
@@ -2,6 +2,8 @@ require 'spec_helper'
describe API::Helpers::Pagination do
let(:resource) { Project.all }
+ let(:incoming_api_projects_url) { "#{Gitlab.config.gitlab.url}:8080/api/v4/projects" }
+ let(:canonical_api_projects_url) { "#{Gitlab.config.gitlab.url}/api/v4/projects" }
subject do
Class.new.include(described_class).new
@@ -9,13 +11,19 @@ describe API::Helpers::Pagination do
describe '#paginate (keyset pagination)' do
let(:value) { spy('return value') }
+ let(:base_query) do
+ {
+ pagination: 'keyset',
+ foo: 'bar',
+ bar: 'baz'
+ }
+ end
+ let(:query) { base_query }
before do
- allow(value).to receive(:to_query).and_return(value)
-
allow(subject).to receive(:header).and_return(value)
- allow(subject).to receive(:params).and_return(value)
- allow(subject).to receive(:request).and_return(value)
+ allow(subject).to receive(:params).and_return(query)
+ allow(subject).to receive(:request).and_return(double(url: "#{incoming_api_projects_url}?#{query.to_query}"))
end
context 'when resource can be paginated' do
@@ -28,10 +36,7 @@ describe API::Helpers::Pagination do
end
describe 'first page' do
- before do
- allow(subject).to receive(:params)
- .and_return({ pagination: 'keyset', per_page: 2 })
- end
+ let(:query) { base_query.merge(per_page: 2) }
it 'returns appropriate amount of resources' do
expect(subject.paginate(resource).count).to eq 2
@@ -43,7 +48,7 @@ describe API::Helpers::Pagination do
it 'adds appropriate headers' do
expect_header('X-Per-Page', '2')
- expect_header('X-Next-Page', "#{value}?ks_prev_id=#{projects[1].id}&pagination=keyset&per_page=2")
+ expect_header('X-Next-Page', "#{canonical_api_projects_url}?#{query.merge(ks_prev_id: projects[1].id).to_query}")
expect_header('Link', anything) do |_key, val|
expect(val).to include('rel="next"')
@@ -54,10 +59,7 @@ describe API::Helpers::Pagination do
end
describe 'second page' do
- before do
- allow(subject).to receive(:params)
- .and_return({ pagination: 'keyset', per_page: 2, ks_prev_id: projects[1].id })
- end
+ let(:query) { base_query.merge(per_page: 2, ks_prev_id: projects[1].id) }
it 'returns appropriate amount of resources' do
expect(subject.paginate(resource).count).to eq 1
@@ -69,7 +71,7 @@ describe API::Helpers::Pagination do
it 'adds appropriate headers' do
expect_header('X-Per-Page', '2')
- expect_header('X-Next-Page', "#{value}?ks_prev_id=#{projects[2].id}&pagination=keyset&per_page=2")
+ expect_header('X-Next-Page', "#{canonical_api_projects_url}?#{query.merge(ks_prev_id: projects[2].id).to_query}")
expect_header('Link', anything) do |_key, val|
expect(val).to include('rel="next"')
@@ -80,10 +82,7 @@ describe API::Helpers::Pagination do
end
describe 'third page' do
- before do
- allow(subject).to receive(:params)
- .and_return({ pagination: 'keyset', per_page: 2, ks_prev_id: projects[2].id })
- end
+ let(:query) { base_query.merge(per_page: 2, ks_prev_id: projects[2].id) }
it 'returns appropriate amount of resources' do
expect(subject.paginate(resource).count).to eq 0
@@ -91,6 +90,7 @@ describe API::Helpers::Pagination do
it 'adds appropriate headers' do
expect_header('X-Per-Page', '2')
+ expect_no_header('X-Next-Page')
expect(subject).not_to receive(:header).with('Link')
subject.paginate(resource)
@@ -99,10 +99,7 @@ describe API::Helpers::Pagination do
context 'if order' do
context 'is not present' do
- before do
- allow(subject).to receive(:params)
- .and_return({ pagination: 'keyset', per_page: 2 })
- end
+ let(:query) { base_query.merge(per_page: 2) }
it 'is not present it adds default order(:id) desc' do
resource.order_values = []
@@ -144,9 +141,7 @@ describe API::Helpers::Pagination do
# (key is the id)
end
- it 'it also orders by primary key' do
- allow(subject).to receive(:params)
- .and_return({ pagination: 'keyset', per_page: 2 })
+ it 'also orders by primary key' do
paginated_relation = subject.paginate(resource)
expect(paginated_relation.order_values).to be_present
@@ -157,46 +152,45 @@ describe API::Helpers::Pagination do
expect(paginated_relation.order_values.second.expr.name).to eq :id
end
- it 'it returns the right records (first page)' do
- allow(subject).to receive(:params)
- .and_return({ pagination: 'keyset', per_page: 2 })
+ it 'returns the right records (first page)' do
result = subject.paginate(resource)
expect(result.first).to eq(projects[1])
expect(result.second).to eq(projects[3])
end
- it 'it returns the right records (second 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 })
- result = subject.paginate(resource)
+ describe 'second page' do
+ let(:query) { base_query.merge(ks_prev_id: projects[3].id, ks_prev_name: projects[3].name, per_page: 2) }
- expect(result.first).to eq(projects[2])
- expect(result.second).to eq(projects[6])
- end
+ it 'returns the right records (second page)' do
+ result = subject.paginate(resource)
- it 'it returns the right records (third page), note increased per_page' do
- allow(subject).to receive(:params)
- .and_return({ pagination: 'keyset', ks_prev_id: projects[6].id, ks_prev_name: projects[6].name, per_page: 5 })
- result = subject.paginate(resource)
+ expect(result.first).to eq(projects[2])
+ expect(result.second).to eq(projects[6])
+ end
- expect(result.size).to eq(3)
- expect(result.first).to eq(projects[0])
- expect(result.second).to eq(projects[4])
- expect(result.last).to eq(projects[5])
+ it 'returns the right link to the next page' do
+ expect_header('X-Per-Page', '2')
+ expect_header('X-Next-Page', "#{canonical_api_projects_url}?#{query.merge(ks_prev_id: projects[6].id, ks_prev_name: projects[6].name).to_query}")
+ expect_header('Link', anything) do |_key, val|
+ expect(val).to include('rel="next"')
+ end
+
+ subject.paginate(resource)
+ end
end
- 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 })
+ describe 'third page' do
+ let(:query) { base_query.merge(ks_prev_id: projects[6].id, ks_prev_name: projects[6].name, per_page: 5) }
- 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
+ it 'returns the right records (third page), note increased per_page' do
+ result = subject.paginate(resource)
- subject.paginate(resource)
+ expect(result.size).to eq(3)
+ expect(result.first).to eq(projects[0])
+ expect(result.second).to eq(projects[4])
+ expect(result.last).to eq(projects[5])
+ end
end
end
end
@@ -205,25 +199,13 @@ describe API::Helpers::Pagination do
describe '#paginate (default offset-based pagination)' do
let(:value) { spy('return value') }
+ let(:base_query) { { foo: 'bar', bar: 'baz' } }
+ let(:query) { base_query }
before do
- allow(value).to receive(:to_query).and_return(value)
-
allow(subject).to receive(:header).and_return(value)
- allow(subject).to receive(:params).and_return(value)
- allow(subject).to receive(:request).and_return(value)
- end
-
- describe 'required instance methods' do
- let(:return_spy) { spy }
-
- it 'requires some instance methods' do
- expect_message(:header)
- expect_message(:params)
- expect_message(:request)
-
- subject.paginate(resource)
- end
+ allow(subject).to receive(:params).and_return(query)
+ allow(subject).to receive(:request).and_return(double(url: "#{incoming_api_projects_url}?#{query.to_query}"))
end
context 'when resource can be paginated' do
@@ -232,11 +214,6 @@ describe API::Helpers::Pagination do
end
describe 'first page' do
- before do
- allow(subject).to receive(:params)
- .and_return({ page: 1, per_page: 2 })
- end
-
shared_examples 'response with pagination headers' do
it 'adds appropriate headers' do
expect_header('X-Total', '3')
@@ -247,9 +224,9 @@ describe API::Helpers::Pagination do
expect_header('X-Prev-Page', '')
expect_header('Link', anything) do |_key, val|
- expect(val).to include('rel="first"')
- expect(val).to include('rel="last"')
- expect(val).to include('rel="next"')
+ expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first"))
+ expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="last"))
+ expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="next"))
expect(val).not_to include('rel="prev"')
end
@@ -267,6 +244,8 @@ describe API::Helpers::Pagination do
end
end
+ let(:query) { base_query.merge(page: 1, per_page: 2) }
+
context 'when the api_kaminari_count_with_limit feature flag is unset' do
it_behaves_like 'paginated response'
it_behaves_like 'response with pagination headers'
@@ -311,9 +290,9 @@ describe API::Helpers::Pagination do
expect_header('X-Prev-Page', '')
expect_header('Link', anything) do |_key, val|
- expect(val).to include('rel="first"')
+ expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first"))
+ expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="next"))
expect(val).not_to include('rel="last"')
- expect(val).to include('rel="next"')
expect(val).not_to include('rel="prev"')
end
@@ -324,10 +303,7 @@ describe API::Helpers::Pagination do
end
describe 'second page' do
- before do
- allow(subject).to receive(:params)
- .and_return({ page: 2, per_page: 2 })
- end
+ let(:query) { base_query.merge(page: 2, per_page: 2) }
it 'returns appropriate amount of resources' do
expect(subject.paginate(resource).count).to eq 1
@@ -342,9 +318,9 @@ describe API::Helpers::Pagination do
expect_header('X-Prev-Page', '1')
expect_header('Link', anything) do |_key, val|
- expect(val).to include('rel="first"')
- expect(val).to include('rel="last"')
- expect(val).to include('rel="prev"')
+ expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first"))
+ expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="last"))
+ expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="prev"))
expect(val).not_to include('rel="next"')
end
@@ -376,10 +352,7 @@ describe API::Helpers::Pagination do
context 'when resource empty' do
describe 'first page' do
- before do
- allow(subject).to receive(:params)
- .and_return({ page: 1, per_page: 2 })
- end
+ let(:query) { base_query.merge(page: 1, per_page: 2) }
it 'returns appropriate amount of resources' do
expect(subject.paginate(resource).count).to eq 0
@@ -394,8 +367,8 @@ describe API::Helpers::Pagination do
expect_header('X-Prev-Page', '')
expect_header('Link', anything) do |_key, val|
- expect(val).to include('rel="first"')
- expect(val).to include('rel="last"')
+ expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first"))
+ expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="last"))
expect(val).not_to include('rel="prev"')
expect(val).not_to include('rel="next"')
expect(val).not_to include('page=0')
diff --git a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
index a0270d93d50..43222ddb5e2 100644
--- a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
@@ -121,6 +121,42 @@ describe Banzai::Filter::ExternalIssueReferenceFilter do
end
end
+ context "youtrack project" do
+ let(:project) { create(:youtrack_project) }
+
+ before do
+ project.update!(issues_enabled: false)
+ end
+
+ context "with right markdown" do
+ let(:issue) { ExternalIssue.new("YT-123", project) }
+ let(:reference) { issue.to_reference }
+
+ it_behaves_like "external issue tracker"
+ end
+
+ context "with underscores in the prefix" do
+ let(:issue) { ExternalIssue.new("PRJ_1-123", project) }
+ let(:reference) { issue.to_reference }
+
+ it_behaves_like "external issue tracker"
+ end
+
+ context "with lowercase letters in the prefix" do
+ let(:issue) { ExternalIssue.new("YTkPrj-123", project) }
+ let(:reference) { issue.to_reference }
+
+ it_behaves_like "external issue tracker"
+ end
+
+ context "with a single-letter prefix" do
+ let(:issue) { ExternalIssue.new("T-123", project) }
+ let(:reference) { issue.to_reference }
+
+ it_behaves_like "external issue tracker"
+ end
+ end
+
context "jira project" do
let(:project) { create(:jira_project) }
let(:reference) { issue.to_reference }
diff --git a/spec/lib/banzai/filter/footnote_filter_spec.rb b/spec/lib/banzai/filter/footnote_filter_spec.rb
index 2e50e4e2351..c6dcb4e46fd 100644
--- a/spec/lib/banzai/filter/footnote_filter_spec.rb
+++ b/spec/lib/banzai/filter/footnote_filter_spec.rb
@@ -11,6 +11,7 @@ describe Banzai::Filter::FootnoteFilter do
let(:footnote) do
<<~EOF
<p>first<sup><a href="#fn1" id="fnref1">1</a></sup> and second<sup><a href="#fn2" id="fnref2">2</a></sup></p>
+ <p>same reference<sup><a href="#fn1" id="fnref1">1</a></sup></p>
<ol>
<li id="fn1">
<p>one <a href="#fnref1">↩</a></p>
@@ -25,6 +26,7 @@ describe Banzai::Filter::FootnoteFilter do
let(:filtered_footnote) do
<<~EOF
<p>first<sup class="footnote-ref"><a href="#fn1-#{identifier}" id="fnref1-#{identifier}">1</a></sup> and second<sup class="footnote-ref"><a href="#fn2-#{identifier}" id="fnref2-#{identifier}">2</a></sup></p>
+ <p>same reference<sup class="footnote-ref"><a href="#fn1-#{identifier}" id="fnref1-#{identifier}">1</a></sup></p>
<section class="footnotes"><ol>
<li id="fn1-#{identifier}">
<p>one <a href="#fnref1-#{identifier}" class="footnote-backref">↩</a></p>
diff --git a/spec/lib/bitbucket_server/client_spec.rb b/spec/lib/bitbucket_server/client_spec.rb
index b021e69800a..4f0d57ca8a6 100644
--- a/spec/lib/bitbucket_server/client_spec.rb
+++ b/spec/lib/bitbucket_server/client_spec.rb
@@ -64,7 +64,7 @@ describe BitbucketServer::Client do
let(:url) { "#{base_uri}rest/api/1.0/projects/SOME-PROJECT/repos/my-repo/branches" }
it 'requests Bitbucket to create a branch' do
- stub_request(:post, url).to_return(status: 204, headers: headers, body: '{}')
+ stub_request(:post, url).to_return(status: 204, headers: headers, body: nil)
subject.create_branch(project, repo_slug, branch, sha)
@@ -78,7 +78,7 @@ describe BitbucketServer::Client do
let(:url) { "#{base_uri}rest/branch-utils/1.0/projects/SOME-PROJECT/repos/my-repo/branches" }
it 'requests Bitbucket to create a branch' do
- stub_request(:delete, url).to_return(status: 204, headers: headers, body: '{}')
+ stub_request(:delete, url).to_return(status: 204, headers: headers, body: nil)
subject.delete_branch(project, repo_slug, branch, sha)
diff --git a/spec/lib/feature_spec.rb b/spec/lib/feature_spec.rb
index 630732614b2..a7163048370 100644
--- a/spec/lib/feature_spec.rb
+++ b/spec/lib/feature_spec.rb
@@ -186,13 +186,14 @@ describe Feature do
describe Feature::Target do
describe '#targets' do
let(:project) { create(:project) }
+ let(:group) { create(:group) }
let(:user_name) { project.owner.username }
- subject { described_class.new(user: user_name, project: project.full_path) }
+ subject { described_class.new(user: user_name, project: project.full_path, group: group.full_path) }
it 'returns all found targets' do
expect(subject.targets).to be_an(Array)
- expect(subject.targets).to eq([project.owner, project])
+ expect(subject.targets).to eq([project.owner, project, group])
end
end
end
diff --git a/spec/lib/gitlab/background_migration/digest_column_spec.rb b/spec/lib/gitlab/background_migration/digest_column_spec.rb
index 3e107ac3027..a25dcb06005 100644
--- a/spec/lib/gitlab/background_migration/digest_column_spec.rb
+++ b/spec/lib/gitlab/background_migration/digest_column_spec.rb
@@ -22,7 +22,7 @@ describe Gitlab::BackgroundMigration::DigestColumn, :migration, schema: 20180913
it 'erases token' do
expect { subject.perform(PersonalAccessToken, :token, :token_digest, 1, 2) }.to(
- change { PersonalAccessToken.find(1).token }.from('token-01').to(nil))
+ change { PersonalAccessToken.find(1).read_attribute(:token) }.from('token-01').to(nil))
end
end
@@ -39,7 +39,7 @@ describe Gitlab::BackgroundMigration::DigestColumn, :migration, schema: 20180913
it 'leaves token empty' do
expect { subject.perform(PersonalAccessToken, :token, :token_digest, 1, 2) }.not_to(
- change { PersonalAccessToken.find(1).token }.from(nil))
+ change { PersonalAccessToken.find(1).read_attribute(:token) }.from(nil))
end
end
end
diff --git a/spec/lib/gitlab/chat/command_spec.rb b/spec/lib/gitlab/chat/command_spec.rb
new file mode 100644
index 00000000000..46d23ab2b62
--- /dev/null
+++ b/spec/lib/gitlab/chat/command_spec.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Chat::Command do
+ let(:chat_name) { create(:chat_name) }
+
+ let(:command) do
+ described_class.new(
+ project: project,
+ chat_name: chat_name,
+ name: 'spinach',
+ arguments: 'foo',
+ channel: '123',
+ response_url: 'http://example.com'
+ )
+ end
+
+ describe '#try_create_pipeline' do
+ let(:project) { create(:project) }
+
+ it 'returns nil when the command is not valid' do
+ expect(command)
+ .to receive(:valid?)
+ .and_return(false)
+
+ expect(command.try_create_pipeline).to be_nil
+ end
+
+ it 'tries to create the pipeline when a command is valid' do
+ expect(command)
+ .to receive(:valid?)
+ .and_return(true)
+
+ expect(command)
+ .to receive(:create_pipeline)
+
+ command.try_create_pipeline
+ end
+ end
+
+ describe '#create_pipeline' do
+ let(:project) { create(:project, :test_repo) }
+ let(:pipeline) { command.create_pipeline }
+
+ before do
+ stub_repository_ci_yaml_file(sha: project.commit.id)
+
+ project.add_developer(chat_name.user)
+ end
+
+ it 'creates the pipeline' do
+ expect(pipeline).to be_persisted
+ end
+
+ it 'creates the chat data for the pipeline' do
+ expect(pipeline.chat_data).to be_an_instance_of(Ci::PipelineChatData)
+ end
+
+ it 'stores the chat name ID in the chat data' do
+ expect(pipeline.chat_data.chat_name_id).to eq(chat_name.id)
+ end
+
+ it 'stores the response URL in the chat data' do
+ expect(pipeline.chat_data.response_url).to eq('http://example.com')
+ end
+
+ it 'creates the environment variables for the pipeline' do
+ vars = pipeline.variables.each_with_object({}) do |row, hash|
+ hash[row.key] = row.value
+ end
+
+ expect(vars['CHAT_INPUT']).to eq('foo')
+ expect(vars['CHAT_CHANNEL']).to eq('123')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/chat/output_spec.rb b/spec/lib/gitlab/chat/output_spec.rb
new file mode 100644
index 00000000000..b179f9e9d0a
--- /dev/null
+++ b/spec/lib/gitlab/chat/output_spec.rb
@@ -0,0 +1,101 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Chat::Output do
+ let(:build) do
+ create(:ci_build, pipeline: create(:ci_pipeline, source: :chat))
+ end
+
+ let(:output) { described_class.new(build) }
+
+ describe '#to_s' do
+ it 'returns the build output as a String' do
+ trace = Gitlab::Ci::Trace.new(build)
+
+ trace.set("echo hello\nhello")
+
+ allow(build)
+ .to receive(:trace)
+ .and_return(trace)
+
+ allow(output)
+ .to receive(:read_offset_and_length)
+ .and_return([0, 13])
+
+ expect(output.to_s).to eq('he')
+ end
+ end
+
+ describe '#read_offset_and_length' do
+ context 'without the chat_reply trace section' do
+ it 'falls back to using the build_script trace section' do
+ expect(output)
+ .to receive(:find_build_trace_section)
+ .with('chat_reply')
+ .and_return(nil)
+
+ expect(output)
+ .to receive(:find_build_trace_section)
+ .with('build_script')
+ .and_return({ name: 'build_script', byte_start: 1, byte_end: 4 })
+
+ expect(output.read_offset_and_length).to eq([1, 3])
+ end
+ end
+
+ context 'without the build_script trace section' do
+ it 'raises MissingBuildSectionError' do
+ expect { output.read_offset_and_length }
+ .to raise_error(described_class::MissingBuildSectionError)
+ end
+ end
+
+ context 'with the chat_reply trace section' do
+ it 'returns the read offset and length as an Array' do
+ trace = Gitlab::Ci::Trace.new(build)
+
+ allow(build)
+ .to receive(:trace)
+ .and_return(trace)
+
+ allow(trace)
+ .to receive(:extract_sections)
+ .and_return([{ name: 'chat_reply', byte_start: 1, byte_end: 4 }])
+
+ expect(output.read_offset_and_length).to eq([1, 3])
+ end
+ end
+ end
+
+ describe '#without_executed_command_line' do
+ it 'returns the input without the first line' do
+ expect(output.without_executed_command_line("hello\nworld"))
+ .to eq('world')
+ end
+
+ it 'returns an empty String when the input is empty' do
+ expect(output.without_executed_command_line('')).to eq('')
+ end
+
+ it 'returns an empty String when the input consits of a single newline' do
+ expect(output.without_executed_command_line("\n")).to eq('')
+ end
+ end
+
+ describe '#find_build_trace_section' do
+ it 'returns nil when no section could be found' do
+ expect(output.find_build_trace_section('foo')).to be_nil
+ end
+
+ it 'returns the trace section when it could be found' do
+ section = { name: 'chat_reply', byte_start: 1, byte_end: 4 }
+
+ allow(output)
+ .to receive(:trace_sections)
+ .and_return([section])
+
+ expect(output.find_build_trace_section('chat_reply')).to eq(section)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/chat/responder/base_spec.rb b/spec/lib/gitlab/chat/responder/base_spec.rb
new file mode 100644
index 00000000000..7fa9bad9d38
--- /dev/null
+++ b/spec/lib/gitlab/chat/responder/base_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Chat::Responder::Base do
+ let(:project) { double(:project) }
+ let(:pipeline) { double(:pipeline, project: project) }
+ let(:build) { double(:build, pipeline: pipeline) }
+ let(:responder) { described_class.new(build) }
+
+ describe '#pipeline' do
+ it 'returns the pipeline' do
+ expect(responder.pipeline).to eq(pipeline)
+ end
+ end
+
+ describe '#project' do
+ it 'returns the project' do
+ expect(responder.project).to eq(project)
+ end
+ end
+
+ describe '#success' do
+ it 'raises NotImplementedError' do
+ expect { responder.success }.to raise_error(NotImplementedError)
+ end
+ end
+
+ describe '#failure' do
+ it 'raises NotImplementedError' do
+ expect { responder.failure }.to raise_error(NotImplementedError)
+ end
+ end
+
+ describe '#send_response' do
+ it 'raises NotImplementedError' do
+ expect { responder.send_response('hello') }
+ .to raise_error(NotImplementedError)
+ end
+ end
+
+ describe '#scheduled_output' do
+ it 'raises NotImplementedError' do
+ expect { responder.scheduled_output }
+ .to raise_error(NotImplementedError)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/chat/responder/slack_spec.rb b/spec/lib/gitlab/chat/responder/slack_spec.rb
new file mode 100644
index 00000000000..a1553232b32
--- /dev/null
+++ b/spec/lib/gitlab/chat/responder/slack_spec.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Chat::Responder::Slack do
+ let(:chat_name) { create(:chat_name, chat_id: 'U123') }
+
+ let(:pipeline) do
+ pipeline = create(:ci_pipeline)
+
+ pipeline.create_chat_data!(
+ response_url: 'http://example.com',
+ chat_name_id: chat_name.id
+ )
+
+ pipeline
+ end
+
+ let(:build) { create(:ci_build, pipeline: pipeline) }
+ let(:responder) { described_class.new(build) }
+
+ describe '#send_response' do
+ it 'sends a response back to Slack' do
+ expect(Gitlab::HTTP).to receive(:post).with(
+ 'http://example.com',
+ { headers: { Accept: 'application/json' }, body: 'hello'.to_json }
+ )
+
+ responder.send_response('hello')
+ end
+ end
+
+ describe '#success' do
+ it 'returns the output for a successful build' do
+ expect(responder)
+ .to receive(:send_response)
+ .with(hash_including(text: /<@U123>:.+hello/, response_type: :in_channel))
+
+ responder.success('hello')
+ end
+
+ it 'limits the output to a fixed size' do
+ expect(responder)
+ .to receive(:send_response)
+ .with(hash_including(text: /The output is too large/))
+
+ responder.success('a' * 4000)
+ end
+
+ it 'does not send a response if the output is empty' do
+ expect(responder).not_to receive(:send_response)
+
+ responder.success('')
+ end
+ end
+
+ describe '#failure' do
+ it 'returns the output for a failed build' do
+ expect(responder).to receive(:send_response).with(
+ hash_including(
+ text: /<@U123>:.+Sorry, the build failed!/,
+ response_type: :in_channel
+ )
+ )
+
+ responder.failure
+ end
+ end
+
+ describe '#scheduled_output' do
+ it 'returns the output for a scheduled build' do
+ output = responder.scheduled_output
+
+ expect(output).to eq({ text: '' })
+ end
+ end
+end
diff --git a/spec/lib/gitlab/chat/responder_spec.rb b/spec/lib/gitlab/chat/responder_spec.rb
new file mode 100644
index 00000000000..9893689cba9
--- /dev/null
+++ b/spec/lib/gitlab/chat/responder_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Chat::Responder do
+ describe '.responder_for' do
+ context 'using a regular build' do
+ it 'returns nil' do
+ build = create(:ci_build)
+
+ expect(described_class.responder_for(build)).to be_nil
+ end
+ end
+
+ context 'using a chat build' do
+ it 'returns the responder for the build' do
+ pipeline = create(:ci_pipeline)
+ build = create(:ci_build, pipeline: pipeline)
+ service = double(:service, chat_responder: Gitlab::Chat::Responder::Slack)
+ chat_name = double(:chat_name, service: service)
+ chat_data = double(:chat_data, chat_name: chat_name)
+
+ allow(pipeline)
+ .to receive(:chat_data)
+ .and_return(chat_data)
+
+ expect(described_class.responder_for(build))
+ .to be_an_instance_of(Gitlab::Chat::Responder::Slack)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/chat_spec.rb b/spec/lib/gitlab/chat_spec.rb
new file mode 100644
index 00000000000..d61c4b36668
--- /dev/null
+++ b/spec/lib/gitlab/chat_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+describe Gitlab::Chat, :use_clean_rails_memory_store_caching do
+ describe '.available?' do
+ it 'returns true when the chatops feature is available' do
+ allow(Feature)
+ .to receive(:enabled?)
+ .with(:chatops, default_enabled: true)
+ .and_return(true)
+
+ expect(described_class).to be_available
+ end
+
+ it 'returns false when the chatops feature is not available' do
+ allow(Feature)
+ .to receive(:enabled?)
+ .with(:chatops, default_enabled: true)
+ .and_return(false)
+
+ expect(described_class).not_to be_available
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/build/policy/changes_spec.rb b/spec/lib/gitlab/ci/build/policy/changes_spec.rb
index 5fee37bb43e..92cf0376c02 100644
--- a/spec/lib/gitlab/ci/build/policy/changes_spec.rb
+++ b/spec/lib/gitlab/ci/build/policy/changes_spec.rb
@@ -73,9 +73,9 @@ describe Gitlab::Ci::Build::Policy::Changes do
expect(policy).not_to be_satisfied_by(pipeline, seed)
end
- context 'when pipelines does not run for a branch update' do
+ context 'when modified paths can not be evaluated' do
before do
- pipeline.before_sha = Gitlab::Git::BLANK_SHA
+ allow(pipeline).to receive(:modified_paths) { nil }
end
it 'is always satisfied' do
@@ -115,5 +115,57 @@ describe Gitlab::Ci::Build::Policy::Changes do
expect(policy).not_to be_satisfied_by(pipeline, seed)
end
end
+
+ context 'when branch is created' do
+ let(:pipeline) do
+ create(:ci_empty_pipeline, project: project,
+ ref: 'feature',
+ source: source,
+ sha: '0b4bc9a4',
+ before_sha: Gitlab::Git::BLANK_SHA,
+ merge_request: merge_request)
+ end
+
+ let(:ci_build) do
+ build(:ci_build, pipeline: pipeline, project: project, ref: 'feature')
+ end
+
+ let(:seed) { double('build seed', to_resource: ci_build) }
+
+ context 'when source is merge request' do
+ let(:source) { :merge_request_event }
+
+ let(:merge_request) do
+ create(:merge_request,
+ source_project: project,
+ source_branch: 'feature',
+ target_project: project,
+ target_branch: 'master')
+ end
+
+ it 'is satified by changes in the merge request' do
+ policy = described_class.new(%w[files/ruby/feature.rb])
+
+ expect(policy).to be_satisfied_by(pipeline, seed)
+ end
+
+ it 'is not satified by changes not in the merge request' do
+ policy = described_class.new(%w[foo.rb])
+
+ expect(policy).not_to be_satisfied_by(pipeline, seed)
+ end
+ end
+
+ context 'when source is push' do
+ let(:source) { :push }
+ let(:merge_request) { nil }
+
+ it 'is always satified' do
+ policy = described_class.new(%w[foo.rb])
+
+ expect(policy).to be_satisfied_by(pipeline, seed)
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/build/policy/refs_spec.rb b/spec/lib/gitlab/ci/build/policy/refs_spec.rb
index 553fc0fb9bf..b4ddbf89b70 100644
--- a/spec/lib/gitlab/ci/build/policy/refs_spec.rb
+++ b/spec/lib/gitlab/ci/build/policy/refs_spec.rb
@@ -68,6 +68,20 @@ describe Gitlab::Ci::Build::Policy::Refs do
expect(described_class.new(%w[triggers]))
.not_to be_satisfied_by(pipeline)
end
+
+ context 'when source is merge_request_event' do
+ let(:pipeline) { build_stubbed(:ci_pipeline, source: :merge_request_event) }
+
+ it 'is satisfied with only: merge_request' do
+ expect(described_class.new(%w[merge_requests]))
+ .to be_satisfied_by(pipeline)
+ end
+
+ it 'is not satisfied with only: merge_request_event' do
+ expect(described_class.new(%w[merge_request_events]))
+ .not_to be_satisfied_by(pipeline)
+ end
+ end
end
context 'when matching a ref by a regular expression' do
diff --git a/spec/lib/gitlab/ci/config/entry/global_spec.rb b/spec/lib/gitlab/ci/config/entry/global_spec.rb
index 7651f594a4c..e23efff18d5 100644
--- a/spec/lib/gitlab/ci/config/entry/global_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/global_spec.rb
@@ -13,7 +13,7 @@ describe Gitlab::Ci::Config::Entry::Global do
expect(described_class.nodes.keys)
.to match_array(%i[before_script image services
after_script variables stages
- types cache])
+ types cache include])
end
end
end
@@ -42,7 +42,7 @@ describe Gitlab::Ci::Config::Entry::Global do
end
it 'creates node object for each entry' do
- expect(global.descendants.count).to eq 8
+ expect(global.descendants.count).to eq 9
end
it 'creates node object using valid class' do
@@ -189,7 +189,7 @@ describe Gitlab::Ci::Config::Entry::Global do
describe '#nodes' do
it 'instantizes all nodes' do
- expect(global.descendants.count).to eq 8
+ expect(global.descendants.count).to eq 9
end
it 'contains unspecified nodes' do
diff --git a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
index fab071405df..3debd42ac65 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
@@ -96,11 +96,13 @@ describe Gitlab::Ci::Pipeline::Chain::Build do
context 'when pipeline is running for a merge request' do
let(:command) do
Gitlab::Ci::Pipeline::Chain::Command.new(
- source: :merge_request,
+ source: :merge_request_event,
origin_ref: 'feature',
checkout_sha: project.commit.id,
after_sha: nil,
before_sha: nil,
+ source_sha: merge_request.diff_head_sha,
+ target_sha: merge_request.target_branch_sha,
trigger_request: nil,
schedule: nil,
merge_request: merge_request,
@@ -115,8 +117,13 @@ describe Gitlab::Ci::Pipeline::Chain::Build do
end
it 'correctly indicated that this is a merge request pipeline' do
- expect(pipeline).to be_merge_request
+ expect(pipeline).to be_merge_request_event
expect(pipeline.merge_request).to eq(merge_request)
end
+
+ it 'correctly sets souce sha and target sha to pipeline' do
+ expect(pipeline.source_sha).to eq(merge_request.diff_head_sha)
+ expect(pipeline.target_sha).to eq(merge_request.target_branch_sha)
+ end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
index 6aa802ce6fd..dab0fb51bcc 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
@@ -161,6 +161,54 @@ describe Gitlab::Ci::Pipeline::Chain::Command do
end
end
+ describe '#source_sha' do
+ subject { command.source_sha }
+
+ let(:command) do
+ described_class.new(project: project,
+ source_sha: source_sha,
+ merge_request: merge_request)
+ end
+
+ let(:merge_request) do
+ create(:merge_request, target_project: project, source_project: project)
+ end
+
+ let(:source_sha) { nil }
+
+ context 'when source_sha is specified' do
+ let(:source_sha) { 'abc' }
+
+ it 'returns the specified value' do
+ is_expected.to eq('abc')
+ end
+ end
+ end
+
+ describe '#target_sha' do
+ subject { command.target_sha }
+
+ let(:command) do
+ described_class.new(project: project,
+ target_sha: target_sha,
+ merge_request: merge_request)
+ end
+
+ let(:merge_request) do
+ create(:merge_request, target_project: project, source_project: project)
+ end
+
+ let(:target_sha) { nil }
+
+ context 'when target_sha is specified' do
+ let(:target_sha) { 'abc' }
+
+ it 'returns the specified value' do
+ is_expected.to eq('abc')
+ end
+ end
+ end
+
describe '#protected_ref?' do
let(:command) { described_class.new(project: project, origin_ref: 'my-branch') }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs_spec.rb
new file mode 100644
index 00000000000..7c1c016b4bb
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Pipeline::Chain::RemoveUnwantedChatJobs do
+ let(:project) { create(:project, :repository) }
+
+ let(:pipeline) do
+ build(:ci_pipeline_with_one_job, project: project, ref: 'master')
+ end
+
+ let(:command) do
+ double(:command, project: project, chat_data: { command: 'echo' })
+ end
+
+ describe '#perform!' do
+ it 'removes unwanted jobs for chat pipelines' do
+ allow(pipeline).to receive(:chat?).and_return(true)
+
+ pipeline.config_processor.jobs[:echo] = double(:job)
+
+ described_class.new(pipeline, command).perform!
+
+ expect(pipeline.config_processor.jobs.keys).to eq([:echo])
+ end
+ end
+
+ it 'does not remove any jobs for non-chat pipelines' do
+ described_class.new(pipeline, command).perform!
+
+ expect(pipeline.config_processor.jobs.keys).to eq([:rspec])
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb
index 053bc421649..e6c6a82b463 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb
@@ -115,7 +115,7 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Config do
let(:pipeline) { build_stubbed(:ci_pipeline, project: project) }
let(:merge_request_pipeline) do
- build(:ci_pipeline, source: :merge_request, project: project)
+ build(:ci_pipeline, source: :merge_request_event, project: project)
end
let(:chain) { described_class.new(merge_request_pipeline, command).tap(&:perform!) }
diff --git a/spec/lib/gitlab/ci/variables/collection/item_spec.rb b/spec/lib/gitlab/ci/variables/collection/item_spec.rb
index 8bf44acb228..3ff2fe18c15 100644
--- a/spec/lib/gitlab/ci/variables/collection/item_spec.rb
+++ b/spec/lib/gitlab/ci/variables/collection/item_spec.rb
@@ -6,7 +6,7 @@ describe Gitlab::Ci::Variables::Collection::Item do
let(:expected_value) { variable_value }
let(:variable) do
- { key: variable_key, value: variable_value, public: true }
+ { key: variable_key, value: variable_value, public: true, masked: false }
end
describe '.new' do
@@ -88,7 +88,7 @@ describe Gitlab::Ci::Variables::Collection::Item do
resource = described_class.fabricate(variable)
expect(resource).to be_a(described_class)
- expect(resource).to eq(key: 'CI_VAR', value: '123', public: false)
+ expect(resource).to eq(key: 'CI_VAR', value: '123', public: false, masked: false)
end
it 'supports using another collection item' do
@@ -134,7 +134,21 @@ describe Gitlab::Ci::Variables::Collection::Item do
.to_runner_variable
expect(runner_variable)
- .to eq(key: 'VAR', value: 'value', public: true, file: true)
+ .to eq(key: 'VAR', value: 'value', public: true, file: true, masked: false)
+ end
+ end
+
+ context 'when variable masking is disabled' do
+ before do
+ stub_feature_flags(variable_masking: false)
+ end
+
+ it 'does not expose the masked field to the runner' do
+ runner_variable = described_class
+ .new(key: 'VAR', value: 'value', masked: true)
+ .to_runner_variable
+
+ expect(runner_variable).to eq(key: 'VAR', value: 'value', public: true)
end
end
end
diff --git a/spec/lib/gitlab/ci/variables/collection_spec.rb b/spec/lib/gitlab/ci/variables/collection_spec.rb
index 5c91816a586..edb209c0cf4 100644
--- a/spec/lib/gitlab/ci/variables/collection_spec.rb
+++ b/spec/lib/gitlab/ci/variables/collection_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe Gitlab::Ci::Variables::Collection do
describe '.new' do
it 'can be initialized with an array' do
- variable = { key: 'VAR', value: 'value', public: true }
+ variable = { key: 'VAR', value: 'value', public: true, masked: false }
collection = described_class.new([variable])
@@ -93,7 +93,7 @@ describe Gitlab::Ci::Variables::Collection do
collection = described_class.new([{ key: 'TEST', value: '1' }])
expect(collection.to_runner_variables)
- .to eq [{ key: 'TEST', value: '1', public: true }]
+ .to eq [{ key: 'TEST', value: '1', public: true, masked: false }]
end
end
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index 91139d421f5..29638ef47c5 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -602,6 +602,85 @@ module Gitlab
end
end
+ describe "Include" do
+ let(:opts) { {} }
+
+ let(:config) do
+ {
+ include: include_content,
+ rspec: { script: "test" }
+ }
+ end
+
+ subject { Gitlab::Ci::YamlProcessor.new(YAML.dump(config), opts) }
+
+ context "when validating a ci config file with no project context" do
+ context "when an array is provided" do
+ let(:include_content) { ["/local.gitlab-ci.yml"] }
+
+ it "does not return any error" do
+ expect { subject }.not_to raise_error
+ end
+ end
+
+ context "when an array of wrong keyed object is provided" do
+ let(:include_content) { [{ yolo: "/local.gitlab-ci.yml" }] }
+
+ it "returns a validation error" do
+ expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError)
+ end
+ end
+
+ context "when an array of mixed typed objects is provided" do
+ let(:include_content) do
+ [
+ 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml',
+ '/templates/.after-script-template.yml',
+ { template: 'Auto-DevOps.gitlab-ci.yml' }
+ ]
+ end
+
+ it "does not return any error" do
+ expect { subject }.not_to raise_error
+ end
+ end
+
+ context "when the include type is incorrect" do
+ let(:include_content) { { name: "/local.gitlab-ci.yml" } }
+
+ it "returns an invalid configuration error" do
+ expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError)
+ end
+ end
+ end
+
+ context "when validating a ci config file within a project" do
+ let(:include_content) { "/local.gitlab-ci.yml" }
+ let(:project) { create(:project, :repository) }
+ let(:opts) { { project: project, sha: project.commit.sha } }
+
+ context "when the included internal file is present" do
+ before do
+ expect(project.repository).to receive(:blob_data_at)
+ .and_return(YAML.dump({ job1: { script: 'hello' } }))
+ end
+
+ it "does not return an error" do
+ expect { subject }.not_to raise_error
+ end
+ end
+
+ context "when the included internal file is not present" do
+ it "returns an error with missing file details" do
+ expect { subject }.to raise_error(
+ Gitlab::Ci::YamlProcessor::ValidationError,
+ "Local file `#{include_content}` does not exist!"
+ )
+ end
+ end
+ end
+ end
+
describe "When" do
%w(on_success on_failure always).each do |when_state|
it "returns #{when_state} when defined" do
diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb
index caf9fc5442c..17d5eae24f5 100644
--- a/spec/lib/gitlab/current_settings_spec.rb
+++ b/spec/lib/gitlab/current_settings_spec.rb
@@ -143,7 +143,7 @@ describe Gitlab::CurrentSettings do
it_behaves_like 'a non-persisted ApplicationSetting object'
- it 'uses the value from the DB attribute if present and not overriden by an accessor' do
+ it 'uses the value from the DB attribute if present and not overridden by an accessor' do
expect(current_settings.home_page_url).to eq(db_settings.home_page_url)
end
end
diff --git a/spec/lib/gitlab/danger/helper_spec.rb b/spec/lib/gitlab/danger/helper_spec.rb
new file mode 100644
index 00000000000..66cd8171c12
--- /dev/null
+++ b/spec/lib/gitlab/danger/helper_spec.rb
@@ -0,0 +1,303 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'rspec-parameterized'
+require 'webmock/rspec'
+
+require 'gitlab/danger/helper'
+
+describe Gitlab::Danger::Helper do
+ using RSpec::Parameterized::TableSyntax
+
+ class FakeDanger
+ include Gitlab::Danger::Helper
+
+ attr_reader :git
+
+ def initialize(git:)
+ @git = git
+ end
+ end
+
+ let(:teammate_json) do
+ <<~JSON
+ [
+ {
+ "username": "in-gitlab-ce",
+ "name": "CE maintainer",
+ "projects":{ "gitlab-ce": "maintainer backend" }
+ },
+ {
+ "username": "in-gitlab-ee",
+ "name": "EE reviewer",
+ "projects":{ "gitlab-ee": "reviewer frontend" }
+ }
+ ]
+ JSON
+ end
+
+ let(:ce_teammate_matcher) do
+ satisfy do |teammate|
+ teammate.username == 'in-gitlab-ce' &&
+ teammate.name == 'CE maintainer' &&
+ teammate.projects == { 'gitlab-ce' => 'maintainer backend' }
+ end
+ end
+
+ let(:ee_teammate_matcher) do
+ satisfy do |teammate|
+ teammate.username == 'in-gitlab-ee' &&
+ teammate.name == 'EE reviewer' &&
+ teammate.projects == { 'gitlab-ee' => 'reviewer frontend' }
+ end
+ end
+
+ let(:fake_git) { double('fake-git') }
+
+ subject(:helper) { FakeDanger.new(git: fake_git) }
+
+ describe '#all_changed_files' do
+ subject { helper.all_changed_files }
+
+ it 'interprets a list of changes from the danger git plugin' do
+ expect(fake_git).to receive(:added_files) { %w[a b c.old] }
+ expect(fake_git).to receive(:modified_files) { %w[d e] }
+ expect(fake_git)
+ .to receive(:renamed_files)
+ .at_least(:once)
+ .and_return([{ before: 'c.old', after: 'c.new' }])
+
+ is_expected.to contain_exactly('a', 'b', 'c.new', 'd', 'e')
+ end
+ end
+
+ describe '#ee?' do
+ subject { helper.ee? }
+
+ it 'returns true if CI_PROJECT_NAME if set to gitlab-ee' do
+ stub_env('CI_PROJECT_NAME', 'gitlab-ee')
+ expect(File).not_to receive(:exist?)
+
+ is_expected.to be_truthy
+ end
+
+ it 'delegates to CHANGELOG-EE.md existence if CI_PROJECT_NAME is set to something else' do
+ stub_env('CI_PROJECT_NAME', 'something else')
+ expect(File).to receive(:exist?).with('../../CHANGELOG-EE.md') { true }
+
+ is_expected.to be_truthy
+ end
+
+ it 'returns true if CHANGELOG-EE.md exists' do
+ stub_env('CI_PROJECT_NAME', nil)
+ expect(File).to receive(:exist?).with('../../CHANGELOG-EE.md') { true }
+
+ is_expected.to be_truthy
+ end
+
+ it "returns false if CHANGELOG-EE.md doesn't exist" do
+ stub_env('CI_PROJECT_NAME', nil)
+ expect(File).to receive(:exist?).with('../../CHANGELOG-EE.md') { false }
+
+ is_expected.to be_falsy
+ end
+ end
+
+ describe '#project_name' do
+ subject { helper.project_name }
+
+ it 'returns gitlab-ee if ee? returns true' do
+ expect(helper).to receive(:ee?) { true }
+
+ is_expected.to eq('gitlab-ee')
+ end
+
+ it 'returns gitlab-ce if ee? returns false' do
+ expect(helper).to receive(:ee?) { false }
+
+ is_expected.to eq('gitlab-ce')
+ end
+ end
+
+ describe '#team' do
+ subject(:team) { helper.team }
+
+ context 'HTTP failure' do
+ before do
+ WebMock
+ .stub_request(:get, 'https://about.gitlab.com/roulette.json')
+ .to_return(status: 404)
+ end
+
+ it 'raises a pretty error' do
+ expect { team }.to raise_error(/Failed to read/)
+ end
+ end
+
+ context 'JSON failure' do
+ before do
+ WebMock
+ .stub_request(:get, 'https://about.gitlab.com/roulette.json')
+ .to_return(body: 'INVALID JSON')
+ end
+
+ it 'raises a pretty error' do
+ expect { team }.to raise_error(/Failed to parse/)
+ end
+ end
+
+ context 'success' do
+ before do
+ WebMock
+ .stub_request(:get, 'https://about.gitlab.com/roulette.json')
+ .to_return(body: teammate_json)
+ end
+
+ it 'returns an array of teammates' do
+ is_expected.to contain_exactly(ce_teammate_matcher, ee_teammate_matcher)
+ end
+
+ it 'memoizes the result' do
+ expect(team.object_id).to eq(helper.team.object_id)
+ end
+ end
+ end
+
+ describe '#project_team' do
+ subject { helper.project_team }
+
+ before do
+ WebMock
+ .stub_request(:get, 'https://about.gitlab.com/roulette.json')
+ .to_return(body: teammate_json)
+ end
+
+ it 'filters team by project_name' do
+ expect(helper)
+ .to receive(:project_name)
+ .at_least(:once)
+ .and_return('gitlab-ce')
+
+ is_expected.to contain_exactly(ce_teammate_matcher)
+ end
+ end
+
+ describe '#changes_by_category' do
+ it 'categorizes changed files' do
+ expect(fake_git).to receive(:added_files) { %w[foo foo.md foo.rb foo.js db/foo qa/foo ee/changelogs/foo.yml] }
+ allow(fake_git).to receive(:modified_files) { [] }
+ allow(fake_git).to receive(:renamed_files) { [] }
+
+ expect(helper.changes_by_category).to eq(
+ backend: %w[foo.rb],
+ database: %w[db/foo],
+ docs: %w[foo.md],
+ frontend: %w[foo.js],
+ none: %w[ee/changelogs/foo.yml],
+ qa: %w[qa/foo],
+ unknown: %w[foo]
+ )
+ end
+ end
+
+ describe '#category_for_file' do
+ where(:path, :expected_category) do
+ 'doc/foo' | :docs
+ 'CONTRIBUTING.md' | :docs
+ 'LICENSE' | :docs
+ 'MAINTENANCE.md' | :docs
+ 'PHILOSOPHY.md' | :docs
+ 'PROCESS.md' | :docs
+ 'README.md' | :docs
+
+ 'ee/doc/foo' | :unknown
+ 'ee/README' | :unknown
+
+ 'app/assets/foo' | :frontend
+ 'app/views/foo' | :frontend
+ 'public/foo' | :frontend
+ 'spec/javascripts/foo' | :frontend
+ 'spec/frontend/bar' | :frontend
+ 'vendor/assets/foo' | :frontend
+ 'jest.config.js' | :frontend
+ 'package.json' | :frontend
+ 'yarn.lock' | :frontend
+
+ 'ee/app/assets/foo' | :frontend
+ 'ee/app/views/foo' | :frontend
+ 'ee/spec/javascripts/foo' | :frontend
+ 'ee/spec/frontend/bar' | :frontend
+
+ 'app/models/foo' | :backend
+ 'bin/foo' | :backend
+ 'config/foo' | :backend
+ 'danger/foo' | :backend
+ 'lib/foo' | :backend
+ 'rubocop/foo' | :backend
+ 'scripts/foo' | :backend
+ 'spec/foo' | :backend
+ 'spec/foo/bar' | :backend
+
+ 'ee/app/foo' | :backend
+ 'ee/bin/foo' | :backend
+ 'ee/spec/foo' | :backend
+ 'ee/spec/foo/bar' | :backend
+
+ 'generator_templates/foo' | :backend
+ 'vendor/languages.yml' | :backend
+ 'vendor/licenses.csv' | :backend
+
+ 'Dangerfile' | :backend
+ 'Gemfile' | :backend
+ 'Gemfile.lock' | :backend
+ 'Procfile' | :backend
+ 'Rakefile' | :backend
+ '.gitlab-ci.yml' | :backend
+ 'FOO_VERSION' | :backend
+
+ 'ee/FOO_VERSION' | :unknown
+
+ 'db/foo' | :database
+ 'qa/foo' | :qa
+
+ 'ee/db/foo' | :database
+ 'ee/qa/foo' | :qa
+
+ 'changelogs/foo' | :none
+ 'ee/changelogs/foo' | :none
+ 'locale/gitlab.pot' | :none
+
+ 'FOO' | :unknown
+ 'foo' | :unknown
+
+ 'foo/bar.rb' | :backend
+ 'foo/bar.js' | :frontend
+ 'foo/bar.txt' | :docs
+ 'foo/bar.md' | :docs
+ end
+
+ with_them do
+ subject { helper.category_for_file(path) }
+
+ it { is_expected.to eq(expected_category) }
+ end
+ end
+
+ describe '#label_for_category' do
+ where(:category, :expected_label) do
+ :backend | '~backend'
+ :database | '~database'
+ :docs | '~Documentation'
+ :foo | '~foo'
+ :frontend | '~frontend'
+ :none | ''
+ :qa | '~QA'
+ end
+
+ with_them do
+ subject { helper.label_for_category(category) }
+
+ it { is_expected.to eq(expected_label) }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
index 248cca25a2c..81419e51635 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
@@ -19,7 +19,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :delete
Project.find(project.id)
end
- describe "#remove_last_ocurrence" do
+ describe "#remove_last_occurrence" do
it "removes only the last occurrence of a string" do
input = "this/is/a-word-to-replace/namespace/with/a-word-to-replace"
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 8a9e78ba3c3..e3dd02f1478 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -31,7 +31,7 @@ describe Gitlab::Git::Repository, :seed_helper do
describe '.create_hooks' do
let(:repo_path) { File.join(storage_path, 'hook-test.git') }
let(:hooks_dir) { File.join(repo_path, 'hooks') }
- let(:target_hooks_dir) { Gitlab.config.gitlab_shell.hooks_path }
+ let(:target_hooks_dir) { Gitlab::Shell.new.hooks_path }
let(:existing_target) { File.join(repo_path, 'foobar') }
before do
@@ -1704,6 +1704,37 @@ describe Gitlab::Git::Repository, :seed_helper do
end
end
+ describe '#merge_to_ref' do
+ let(:repository) { mutable_repository }
+ let(:branch_head) { '6d394385cf567f80a8fd85055db1ab4c5295806f' }
+ let(:left_sha) { 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660' }
+ let(:right_branch) { 'test-master' }
+ let(:target_ref) { 'refs/merge-requests/999/merge' }
+
+ before do
+ repository.create_branch(right_branch, branch_head) unless repository.branch_exists?(right_branch)
+ end
+
+ def merge_to_ref
+ repository.merge_to_ref(user, left_sha, right_branch, target_ref, 'Merge message')
+ end
+
+ it 'generates a commit in the target_ref' do
+ expect(repository.ref_exists?(target_ref)).to be(false)
+
+ commit_sha = merge_to_ref
+ ref_head = repository.commit(target_ref)
+
+ expect(commit_sha).to be_present
+ expect(repository.ref_exists?(target_ref)).to be(true)
+ expect(ref_head.id).to eq(commit_sha)
+ end
+
+ it 'does not change the right branch HEAD' do
+ expect { merge_to_ref }.not_to change { repository.find_branch(right_branch).target }
+ end
+ end
+
describe '#merge' do
let(:repository) { mutable_repository }
let(:source_sha) { '913c66a37b4a45b9769037c55c2d238bd0942d2e' }
@@ -1914,7 +1945,7 @@ describe Gitlab::Git::Repository, :seed_helper do
imported_repo.create_from_bundle(valid_bundle_path)
hooks_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access { File.join(imported_repo.path, 'hooks') }
- expect(File.readlink(hooks_path)).to eq(Gitlab.config.gitlab_shell.hooks_path)
+ expect(File.readlink(hooks_path)).to eq(Gitlab::Shell.new.hooks_path)
end
it 'raises an error if the bundle is an attempted malicious payload' do
diff --git a/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb b/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb
index 95bf7685ade..13cf52fd795 100644
--- a/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb
+++ b/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb
@@ -100,4 +100,22 @@ describe Gitlab::Graphql::Authorize::AuthorizeResource do
expect { fake_class.new.find_object }.to raise_error(/Implement #find_object in #{fake_class.name}/)
end
end
+
+ describe '#authorize' do
+ it 'adds permissions from subclasses to those of superclasses when used on classes' do
+ base_class = Class.new do
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+
+ authorize :base_authorization
+ end
+
+ sub_class = Class.new(base_class) do
+ authorize :sub_authorization
+ end
+
+ expect(base_class.required_permissions).to contain_exactly(:base_authorization)
+ expect(sub_class.required_permissions)
+ .to contain_exactly(:base_authorization, :sub_authorization)
+ end
+ end
end
diff --git a/spec/lib/gitlab/graphql/authorize/instrumentation_spec.rb b/spec/lib/gitlab/graphql/authorize/instrumentation_spec.rb
new file mode 100644
index 00000000000..cf3a8bcc8b4
--- /dev/null
+++ b/spec/lib/gitlab/graphql/authorize/instrumentation_spec.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Graphql::Authorize::Instrumentation do
+ describe '#build_checker' do
+ let(:current_user) { double(:current_user) }
+ let(:abilities) { [double(:first_ability), double(:last_ability)] }
+
+ let(:checker) do
+ described_class.new.__send__(:build_checker, current_user, abilities)
+ end
+
+ it 'returns a checker which checks for a single object' do
+ object = double(:object)
+
+ abilities.each do |ability|
+ spy_ability_check_for(ability, object, passed: true)
+ end
+
+ expect(checker.call(object)).to eq(object)
+ end
+
+ it 'returns a checker which checks for all objects' do
+ objects = [double(:first), double(:last)]
+
+ abilities.each do |ability|
+ objects.each do |object|
+ spy_ability_check_for(ability, object, passed: true)
+ end
+ end
+
+ expect(checker.call(objects)).to eq(objects)
+ end
+
+ context 'when some objects would not pass the check' do
+ it 'returns nil when it is single object' do
+ disallowed = double(:object)
+
+ spy_ability_check_for(abilities.first, disallowed, passed: false)
+
+ expect(checker.call(disallowed)).to be_nil
+ end
+
+ it 'returns only objects which passed when there are more than one' do
+ allowed = double(:allowed)
+ disallowed = double(:disallowed)
+
+ spy_ability_check_for(abilities.first, disallowed, passed: false)
+
+ abilities.each do |ability|
+ spy_ability_check_for(ability, allowed, passed: true)
+ end
+
+ expect(checker.call([disallowed, allowed]))
+ .to contain_exactly(allowed)
+ end
+ end
+
+ def spy_ability_check_for(ability, object, passed: true)
+ expect(Ability)
+ .to receive(:allowed?)
+ .with(current_user, ability, object)
+ .and_return(passed)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/graphql/authorize_spec.rb b/spec/lib/gitlab/graphql/authorize_spec.rb
deleted file mode 100644
index 9c17a3b0e4b..00000000000
--- a/spec/lib/gitlab/graphql/authorize_spec.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Graphql::Authorize do
- describe '#authorize' do
- it 'adds permissions from subclasses to those of superclasses when used on classes' do
- base_class = Class.new do
- extend Gitlab::Graphql::Authorize
-
- authorize :base_authorization
- end
- sub_class = Class.new(base_class) do
- authorize :sub_authorization
- end
-
- expect(base_class.required_permissions).to contain_exactly(:base_authorization)
- expect(sub_class.required_permissions)
- .to contain_exactly(:base_authorization, :sub_authorization)
- end
- end
-end
diff --git a/spec/lib/gitlab/hashed_storage/migrator_spec.rb b/spec/lib/gitlab/hashed_storage/migrator_spec.rb
index 3942f168ceb..6154b3e2f76 100644
--- a/spec/lib/gitlab/hashed_storage/migrator_spec.rb
+++ b/spec/lib/gitlab/hashed_storage/migrator_spec.rb
@@ -1,21 +1,29 @@
require 'spec_helper'
describe Gitlab::HashedStorage::Migrator do
- describe '#bulk_schedule' do
- it 'schedules job to StorageMigratorWorker' do
+ describe '#bulk_schedule_migration' do
+ it 'schedules job to HashedStorage::MigratorWorker' do
Sidekiq::Testing.fake! do
- expect { subject.bulk_schedule(start: 1, finish: 5) }.to change(HashedStorage::MigratorWorker.jobs, :size).by(1)
+ expect { subject.bulk_schedule_migration(start: 1, finish: 5) }.to change(HashedStorage::MigratorWorker.jobs, :size).by(1)
+ end
+ end
+ end
+
+ describe '#bulk_schedule_rollback' do
+ it 'schedules job to HashedStorage::RollbackerWorker' do
+ Sidekiq::Testing.fake! do
+ expect { subject.bulk_schedule_rollback(start: 1, finish: 5) }.to change(HashedStorage::RollbackerWorker.jobs, :size).by(1)
end
end
end
describe '#bulk_migrate' do
- let(:projects) { create_list(:project, 2, :legacy_storage) }
+ let(:projects) { create_list(:project, 2, :legacy_storage, :empty_repo) }
let(:ids) { projects.map(&:id) }
- it 'enqueue jobs to ProjectMigrateHashedStorageWorker' do
+ it 'enqueue jobs to HashedStorage::ProjectMigrateWorker' do
Sidekiq::Testing.fake! do
- expect { subject.bulk_migrate(start: ids.min, finish: ids.max) }.to change(ProjectMigrateHashedStorageWorker.jobs, :size).by(2)
+ expect { subject.bulk_migrate(start: ids.min, finish: ids.max) }.to change(HashedStorage::ProjectMigrateWorker.jobs, :size).by(2)
end
end
@@ -32,13 +40,53 @@ describe Gitlab::HashedStorage::Migrator do
subject.bulk_migrate(start: ids.min, finish: ids.max)
end
- it 'has migrated projects set as writable' do
+ it 'has all projects migrated and set as writable' do
perform_enqueued_jobs do
subject.bulk_migrate(start: ids.min, finish: ids.max)
end
projects.each do |project|
- expect(project.reload.repository_read_only?).to be_falsey
+ project.reload
+
+ expect(project.hashed_storage?(:repository)).to be_truthy
+ expect(project.repository_read_only?).to be_falsey
+ end
+ end
+ end
+
+ describe '#bulk_rollback' do
+ let(:projects) { create_list(:project, 2, :empty_repo) }
+ let(:ids) { projects.map(&:id) }
+
+ it 'enqueue jobs to HashedStorage::ProjectRollbackWorker' do
+ Sidekiq::Testing.fake! do
+ expect { subject.bulk_rollback(start: ids.min, finish: ids.max) }.to change(HashedStorage::ProjectRollbackWorker.jobs, :size).by(2)
+ end
+ end
+
+ it 'rescues and log exceptions' do
+ allow_any_instance_of(Project).to receive(:rollback_to_legacy_storage!).and_raise(StandardError)
+ expect { subject.bulk_rollback(start: ids.min, finish: ids.max) }.not_to raise_error
+ end
+
+ it 'delegates each project in specified range to #rollback' do
+ projects.each do |project|
+ expect(subject).to receive(:rollback).with(project)
+ end
+
+ subject.bulk_rollback(start: ids.min, finish: ids.max)
+ end
+
+ it 'has all projects rolledback and set as writable' do
+ perform_enqueued_jobs do
+ subject.bulk_rollback(start: ids.min, finish: ids.max)
+ end
+
+ projects.each do |project|
+ project.reload
+
+ expect(project.legacy_storage?).to be_truthy
+ expect(project.repository_read_only?).to be_falsey
end
end
end
@@ -48,7 +96,7 @@ describe Gitlab::HashedStorage::Migrator do
it 'enqueues project migration job' do
Sidekiq::Testing.fake! do
- expect { subject.migrate(project) }.to change(ProjectMigrateHashedStorageWorker.jobs, :size).by(1)
+ expect { subject.migrate(project) }.to change(HashedStorage::ProjectMigrateWorker.jobs, :size).by(1)
end
end
@@ -79,7 +127,7 @@ describe Gitlab::HashedStorage::Migrator do
it 'doesnt enqueue any migration job' do
Sidekiq::Testing.fake! do
- expect { subject.migrate(project) }.not_to change(ProjectMigrateHashedStorageWorker.jobs, :size)
+ expect { subject.migrate(project) }.not_to change(HashedStorage::ProjectMigrateWorker.jobs, :size)
end
end
@@ -88,4 +136,50 @@ describe Gitlab::HashedStorage::Migrator do
end
end
end
+
+ describe '#rollback' do
+ let(:project) { create(:project, :empty_repo) }
+
+ it 'enqueues project rollback job' do
+ Sidekiq::Testing.fake! do
+ expect { subject.rollback(project) }.to change(HashedStorage::ProjectRollbackWorker.jobs, :size).by(1)
+ end
+ end
+
+ it 'rescues and log exceptions' do
+ allow(project).to receive(:rollback_to_hashed_storage!).and_raise(StandardError)
+
+ expect { subject.rollback(project) }.not_to raise_error
+ end
+
+ it 'rolls-back project storage' do
+ perform_enqueued_jobs do
+ subject.rollback(project)
+ end
+
+ expect(project.reload.legacy_storage?).to be_truthy
+ end
+
+ it 'has rolled-back project set as writable' do
+ perform_enqueued_jobs do
+ subject.rollback(project)
+ end
+
+ expect(project.reload.repository_read_only?).to be_falsey
+ end
+
+ context 'when project is already on legacy storage' do
+ let(:project) { create(:project, :legacy_storage, :empty_repo) }
+
+ it 'doesnt enqueue any rollback job' do
+ Sidekiq::Testing.fake! do
+ expect { subject.rollback(project) }.not_to change(HashedStorage::ProjectRollbackWorker.jobs, :size)
+ end
+ end
+
+ it 'returns false' do
+ expect(subject.rollback(project)).to be_falsey
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index c15b360b563..018a5d3dd3d 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -131,6 +131,7 @@ ci_pipelines:
- merge_request
- deployments
- environments
+- chat_data
pipeline_variables:
- pipeline
stages:
@@ -232,6 +233,7 @@ project:
- pushover_service
- jira_service
- redmine_service
+- youtrack_service
- custom_issue_tracker_service
- bugzilla_service
- gitlab_issue_tracker_service
diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json
index 1327f414498..773651dd226 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -6630,6 +6630,26 @@
"deploy_keys": [],
"services": [
{
+ "id": 101,
+ "title": "YouTrack",
+ "project_id": 5,
+ "created_at": "2016-06-14T15:01:51.327Z",
+ "updated_at": "2016-06-14T15:01:51.327Z",
+ "active": false,
+ "properties": {},
+ "template": false,
+ "push_events": true,
+ "issues_events": true,
+ "merge_requests_events": true,
+ "tag_push_events": true,
+ "note_events": true,
+ "job_events": true,
+ "type": "YoutrackService",
+ "category": "issue_tracker",
+ "default": false,
+ "wiki_page_events": true
+ },
+ {
"id": 100,
"title": "JetBrains TeamCity CI",
"project_id": 5,
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 46fdfba953b..cfc3e0ce926 100644
--- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
@@ -78,6 +78,14 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
expect(saved_project_json['releases']).not_to be_empty
end
+ it 'has no author on releases' do
+ expect(saved_project_json['releases'].first['author']).to be_nil
+ end
+
+ it 'has the author ID on releases' do
+ expect(saved_project_json['releases'].first['author_id']).not_to be_nil
+ end
+
it 'has issues' do
expect(saved_project_json['issues']).not_to be_empty
end
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index baca8f6d542..ee96e5c4d42 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -235,6 +235,8 @@ Ci::Pipeline:
- ref
- sha
- before_sha
+- source_sha
+- target_sha
- push_data
- created_at
- updated_at
diff --git a/spec/lib/gitlab/import_export/shared_spec.rb b/spec/lib/gitlab/import_export/shared_spec.rb
index f2d750c6595..2c288cff6ef 100644
--- a/spec/lib/gitlab/import_export/shared_spec.rb
+++ b/spec/lib/gitlab/import_export/shared_spec.rb
@@ -14,6 +14,16 @@ describe Gitlab::ImportExport::Shared do
expect(subject.errors).to eq(['Error importing into [FILTERED] Permission denied @ unlink_internal - [FILTERED]'])
end
+ it 'updates the import JID' do
+ import_state = create(:import_state, project: project, jid: 'jid-test')
+
+ expect_next_instance_of(Gitlab::Import::Logger) do |logger|
+ expect(logger).to receive(:error).with(hash_including(import_jid: import_state.jid))
+ end
+
+ subject.error(error)
+ end
+
it 'calls the error logger with the full message' do
expect(subject).to receive(:log_error).with(hash_including(message: error.message))
diff --git a/spec/lib/gitlab/issuable_metadata_spec.rb b/spec/lib/gitlab/issuable_metadata_spec.rb
index 42635a68ee1..6ec86163233 100644
--- a/spec/lib/gitlab/issuable_metadata_spec.rb
+++ b/spec/lib/gitlab/issuable_metadata_spec.rb
@@ -28,12 +28,12 @@ describe Gitlab::IssuableMetadata do
expect(data.count).to eq(2)
expect(data[issue.id].upvotes).to eq(1)
expect(data[issue.id].downvotes).to eq(0)
- expect(data[issue.id].notes_count).to eq(0)
+ expect(data[issue.id].user_notes_count).to eq(0)
expect(data[issue.id].merge_requests_count).to eq(1)
expect(data[closed_issue.id].upvotes).to eq(0)
expect(data[closed_issue.id].downvotes).to eq(1)
- expect(data[closed_issue.id].notes_count).to eq(0)
+ expect(data[closed_issue.id].user_notes_count).to eq(0)
expect(data[closed_issue.id].merge_requests_count).to eq(0)
end
end
@@ -51,12 +51,12 @@ describe Gitlab::IssuableMetadata do
expect(data.count).to eq(2)
expect(data[merge_request.id].upvotes).to eq(1)
expect(data[merge_request.id].downvotes).to eq(1)
- expect(data[merge_request.id].notes_count).to eq(1)
+ expect(data[merge_request.id].user_notes_count).to eq(1)
expect(data[merge_request.id].merge_requests_count).to eq(0)
expect(data[merge_request_closed.id].upvotes).to eq(0)
expect(data[merge_request_closed.id].downvotes).to eq(0)
- expect(data[merge_request_closed.id].notes_count).to eq(0)
+ expect(data[merge_request_closed.id].user_notes_count).to eq(0)
expect(data[merge_request_closed.id].merge_requests_count).to eq(0)
end
end
diff --git a/spec/lib/gitlab/json_cache_spec.rb b/spec/lib/gitlab/json_cache_spec.rb
index b52078e8556..2cae8ec031a 100644
--- a/spec/lib/gitlab/json_cache_spec.rb
+++ b/spec/lib/gitlab/json_cache_spec.rb
@@ -297,13 +297,39 @@ describe Gitlab::JsonCache do
expect(result).to eq(broadcast_message)
end
+ context 'when the cached value is an instance of ActiveRecord::Base' do
+ it 'returns a persisted record when id is set' do
+ backend.write(expanded_key, broadcast_message.to_json)
+
+ result = cache.fetch(key, as: BroadcastMessage) { 'block result' }
+
+ expect(result).to be_persisted
+ end
+
+ it 'returns a new record when id is nil' do
+ backend.write(expanded_key, build(:broadcast_message).to_json)
+
+ result = cache.fetch(key, as: BroadcastMessage) { 'block result' }
+
+ expect(result).to be_new_record
+ end
+
+ it 'returns a new record when id is missing' do
+ backend.write(expanded_key, build(:broadcast_message).attributes.except('id').to_json)
+
+ result = cache.fetch(key, as: BroadcastMessage) { 'block result' }
+
+ expect(result).to be_new_record
+ end
+ end
+
it "returns the result of the block when 'as' option is nil" do
result = cache.fetch(key, as: nil) { 'block result' }
expect(result).to eq('block result')
end
- it "returns the result of the block when 'as' option is not informed" do
+ it "returns the result of the block when 'as' option is missing" do
result = cache.fetch(key) { 'block result' }
expect(result).to eq('block result')
diff --git a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
index 27c802f34ec..95b6b3fd953 100644
--- a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
@@ -30,7 +30,7 @@ describe Gitlab::Kubernetes::Helm::Pod do
it 'should generate the appropriate specifications for the container' do
container = subject.generate.spec.containers.first
expect(container.name).to eq('helm')
- expect(container.image).to eq('registry.gitlab.com/gitlab-org/cluster-integration/helm-install-image/releases/2.12.2-kube-1.11.0')
+ expect(container.image).to eq('registry.gitlab.com/gitlab-org/cluster-integration/helm-install-image/releases/2.12.3-kube-1.11.7')
expect(container.env.count).to eq(3)
expect(container.env.map(&:name)).to match_array([:HELM_VERSION, :TILLER_NAMESPACE, :COMMAND_SCRIPT])
expect(container.command).to match_array(["/bin/sh"])
diff --git a/spec/lib/gitlab/lfs_token_spec.rb b/spec/lib/gitlab/lfs_token_spec.rb
index 1ec1ba19e39..8961ecc4be0 100644
--- a/spec/lib/gitlab/lfs_token_spec.rb
+++ b/spec/lib/gitlab/lfs_token_spec.rb
@@ -4,10 +4,8 @@ require 'spec_helper'
describe Gitlab::LfsToken, :clean_gitlab_redis_shared_state do
describe '#token' do
- shared_examples 'an LFS token generator' do
+ shared_examples 'a valid LFS token' do
it 'returns a computed token' do
- expect(Settings).to receive(:attr_encrypted_db_key_base).and_return('fbnbv6hdjweo53qka7kza8v8swxc413c05pb51qgtfte0bygh1p2e508468hfsn5ntmjcyiz7h1d92ashpet5pkdyejg7g8or3yryhuso4h8o5c73h429d9c3r6bjnet').twice
-
token = lfs_token.token
expect(token).not_to be_nil
@@ -20,11 +18,7 @@ describe Gitlab::LfsToken, :clean_gitlab_redis_shared_state do
let(:actor) { create(:user, username: 'test_user_lfs_1') }
let(:lfs_token) { described_class.new(actor) }
- before do
- allow(actor).to receive(:encrypted_password).and_return('$2a$04$ETfzVS5spE9Hexn9wh6NUenCHG1LyZ2YdciOYxieV1WLSa8DHqOFO')
- end
-
- it_behaves_like 'an LFS token generator'
+ it_behaves_like 'a valid LFS token'
it 'returns the correct username' do
expect(lfs_token.actor_name).to eq(actor.username)
@@ -40,11 +34,7 @@ describe Gitlab::LfsToken, :clean_gitlab_redis_shared_state do
let(:actor) { create(:key, user: user) }
let(:lfs_token) { described_class.new(actor) }
- before do
- allow(user).to receive(:encrypted_password).and_return('$2a$04$C1GAMKsOKouEbhKy2JQoe./3LwOfQAZc.hC8zW2u/wt8xgukvnlV.')
- end
-
- it_behaves_like 'an LFS token generator'
+ it_behaves_like 'a valid LFS token'
it 'returns the correct username' do
expect(lfs_token.actor_name).to eq(user.username)
@@ -65,7 +55,7 @@ describe Gitlab::LfsToken, :clean_gitlab_redis_shared_state do
allow(actor).to receive(:id).and_return(actor_id)
end
- it_behaves_like 'an LFS token generator'
+ it_behaves_like 'a valid LFS token'
it 'returns the correct username' do
expect(lfs_token.actor_name).to eq("lfs+deploy-key-#{actor_id}")
@@ -87,10 +77,6 @@ describe Gitlab::LfsToken, :clean_gitlab_redis_shared_state do
let(:actor) { create(:user, username: 'test_user_lfs_1') }
let(:lfs_token) { described_class.new(actor) }
- before do
- allow(actor).to receive(:encrypted_password).and_return('$2a$04$ETfzVS5spE9Hexn9wh6NUenCHG1LyZ2YdciOYxieV1WLSa8DHqOFO')
- end
-
context 'for an HMAC token' do
before do
# We're not interested in testing LegacyRedisDeviseToken here
@@ -240,4 +226,18 @@ describe Gitlab::LfsToken, :clean_gitlab_redis_shared_state do
end
end
end
+
+ describe '#authentication_payload' do
+ it 'returns a Hash designed for gitlab-shell' do
+ actor = create(:user)
+ lfs_token = described_class.new(actor)
+ repo_http_path = 'http://localhost/user/repo.git'
+ authentication_payload = lfs_token.authentication_payload(repo_http_path)
+
+ expect(authentication_payload[:username]).to eq(actor.username)
+ expect(authentication_payload[:repository_http_path]).to eq(repo_http_path)
+ expect(authentication_payload[:lfs_token]).to be_a String
+ expect(authentication_payload[:expires_in]).to eq(described_class::DEFAULT_EXPIRE_TIME)
+ end
+ end
end
diff --git a/spec/lib/gitlab/profiler_spec.rb b/spec/lib/gitlab/profiler_spec.rb
index 8bb0c1a0b8a..9f2214f7ce7 100644
--- a/spec/lib/gitlab/profiler_spec.rb
+++ b/spec/lib/gitlab/profiler_spec.rb
@@ -1,8 +1,6 @@
require 'spec_helper'
describe Gitlab::Profiler do
- RSpec::Matchers.define_negated_matcher :not_change, :change
-
let(:null_logger) { Logger.new('/dev/null') }
let(:private_token) { 'private' }
@@ -187,7 +185,7 @@ describe Gitlab::Profiler do
end
it 'does not modify the standard Rails loggers' do
- expect { described_class.with_custom_logger(nil) { } }
+ expect { described_class.with_custom_logger(nil) {} }
.to not_change { ActiveRecord::Base.logger }
.and not_change { ActionController::Base.logger }
.and not_change { ActiveSupport::LogSubscriber.colorize_logging }
@@ -204,7 +202,7 @@ describe Gitlab::Profiler do
end
it 'cleans up ApplicationController afterwards' do
- expect { described_class.with_user(user) { } }
+ expect { described_class.with_user(user) {} }
.to not_change { ActionController.instance_methods(false) }
end
end
@@ -213,7 +211,7 @@ describe Gitlab::Profiler do
it 'does not define methods on ApplicationController' do
expect(ApplicationController).not_to receive(:define_method)
- described_class.with_user(nil) { }
+ described_class.with_user(nil) {}
end
end
end
diff --git a/spec/lib/gitlab/project_template_spec.rb b/spec/lib/gitlab/project_template_spec.rb
index 1cc2bde50e9..115097e0d26 100644
--- a/spec/lib/gitlab/project_template_spec.rb
+++ b/spec/lib/gitlab/project_template_spec.rb
@@ -7,11 +7,18 @@ describe Gitlab::ProjectTemplate do
described_class.new('rails', 'Ruby on Rails', 'Includes an MVC structure, .gitignore, Gemfile, and more great stuff', 'https://gitlab.com/gitlab-org/project-templates/rails'),
described_class.new('spring', 'Spring', 'Includes an MVC structure, .gitignore, Gemfile, and more great stuff', 'https://gitlab.com/gitlab-org/project-templates/spring'),
described_class.new('express', 'NodeJS Express', 'Includes an MVC structure, .gitignore, Gemfile, and more great stuff', 'https://gitlab.com/gitlab-org/project-templates/express'),
+ described_class.new('dotnetcore', '.NET Core', 'A .NET Core console application template, customizable for any .NET Core project', 'https://gitlab.com/gitlab-org/project-templates/dotnetcore'),
+ described_class.new('gomicro', 'Go Micro', 'Go Micro is a framework for micro service development.', 'https://gitlab.com/gitlab-org/project-templates/go-micro'),
described_class.new('hugo', 'Pages/Hugo', 'Everything you need to get started using a Hugo Pages site.', 'https://gitlab.com/pages/hugo'),
described_class.new('jekyll', 'Pages/Jekyll', 'Everything you need to get started using a Jekyll Pages site.', 'https://gitlab.com/pages/jekyll'),
described_class.new('plainhtml', 'Pages/Plain HTML', 'Everything you need to get started using a plain HTML Pages site.', 'https://gitlab.com/pages/plain-html'),
described_class.new('gitbook', 'Pages/GitBook', 'Everything you need to get started using a GitBook Pages site.', 'https://gitlab.com/pages/gitbook'),
- described_class.new('hexo', 'Pages/Hexo', 'Everything you need to get started using a plan Hexo Pages site.', 'https://gitlab.com/pages/hexo')
+ described_class.new('hexo', 'Pages/Hexo', 'Everything you need to get started using a Hexo Pages site.', 'https://gitlab.com/pages/hexo'),
+ described_class.new('nfhugo', 'Netlify/Hugo', _('A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfhugo'),
+ described_class.new('nfjekyll', 'Netlify/Jekyll', _('A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfjekyll'),
+ described_class.new('nfplainhtml', 'Netlify/Plain HTML', _('A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfplain-html'),
+ described_class.new('nfgitbook', 'Netlify/GitBook', _('A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfgitbook'),
+ described_class.new('nfhexo', 'Netlify/Hexo', _('A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfhexo')
]
expect(described_class.all).to be_an(Array)
diff --git a/spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb b/spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb
index 420218a695a..936447b8474 100644
--- a/spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb
+++ b/spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb
@@ -15,7 +15,7 @@ describe Gitlab::Prometheus::Queries::MatchedMetricQuery do
[{ '__name__' => 'metric_a' },
{ '__name__' => 'metric_b' }]
end
- let(:partialy_empty_series_info) { [{ '__name__' => 'metric_a', 'environment' => '' }] }
+ let(:partially_empty_series_info) { [{ '__name__' => 'metric_a', 'environment' => '' }] }
let(:empty_series_info) { [] }
let(:client) { double('prometheus_client') }
@@ -60,7 +60,7 @@ describe Gitlab::Prometheus::Queries::MatchedMetricQuery do
context 'one of the series info was not found' do
before do
- allow(client).to receive(:series).and_return(partialy_empty_series_info)
+ allow(client).to receive(:series).and_return(partially_empty_series_info)
end
it 'responds with one active and one missing metric' do
expect(subject.query).to eq([{ group: 'name', priority: 1, active_metrics: 1, metrics_missing_requirements: 1 }])
diff --git a/spec/lib/gitlab/serializer/pagination_spec.rb b/spec/lib/gitlab/serializer/pagination_spec.rb
index 1bc6536439e..c54be78f050 100644
--- a/spec/lib/gitlab/serializer/pagination_spec.rb
+++ b/spec/lib/gitlab/serializer/pagination_spec.rb
@@ -1,16 +1,12 @@
require 'spec_helper'
describe Gitlab::Serializer::Pagination do
- let(:request) { spy('request') }
+ let(:request) { double(url: "#{Gitlab.config.gitlab.url}:8080/api/v4/projects?#{query.to_query}", query_parameters: query) }
let(:response) { spy('response') }
let(:headers) { spy('headers') }
before do
- allow(request).to receive(:query_parameters)
- .and_return(params)
-
- allow(response).to receive(:headers)
- .and_return(headers)
+ allow(response).to receive(:headers).and_return(headers)
end
let(:pagination) { described_class.new(request, response) }
@@ -19,7 +15,7 @@ describe Gitlab::Serializer::Pagination do
subject { pagination.paginate(resource) }
let(:resource) { User.all }
- let(:params) { { page: 1, per_page: 2 } }
+ let(:query) { { page: 1, per_page: 2 } }
context 'when a multiple resources are present in relation' do
before do
diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb
index 033e1bf81a1..d6aadf0f7de 100644
--- a/spec/lib/gitlab/shell_spec.rb
+++ b/spec/lib/gitlab/shell_spec.rb
@@ -393,7 +393,6 @@ describe Gitlab::Shell do
before do
allow(Gitlab.config.gitlab_shell).to receive(:path).and_return(gitlab_shell_path)
- allow(Gitlab.config.gitlab_shell).to receive(:hooks_path).and_return(gitlab_shell_hooks_path)
allow(Gitlab.config.gitlab_shell).to receive(:git_timeout).and_return(800)
end
diff --git a/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb b/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb
new file mode 100644
index 00000000000..5df56178df2
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb
@@ -0,0 +1,63 @@
+require 'spec_helper'
+
+describe Gitlab::SidekiqMiddleware::MemoryKiller do
+ subject { described_class.new }
+ let(:pid) { 999 }
+
+ let(:worker) { double(:worker, class: 'TestWorker') }
+ let(:job) { { 'jid' => 123 } }
+ let(:queue) { 'test_queue' }
+
+ def run
+ thread = subject.call(worker, job, queue) { nil }
+ thread&.join
+ end
+
+ before do
+ allow(subject).to receive(:get_rss).and_return(10.kilobytes)
+ allow(subject).to receive(:pid).and_return(pid)
+ end
+
+ context 'when MAX_RSS is set to 0' do
+ before do
+ stub_const("#{described_class}::MAX_RSS", 0)
+ end
+
+ it 'does nothing' do
+ expect(subject).not_to receive(:sleep)
+
+ run
+ end
+ end
+
+ context 'when MAX_RSS is exceeded' do
+ before do
+ stub_const("#{described_class}::MAX_RSS", 5.kilobytes)
+ end
+
+ it 'sends the STP, TERM and KILL signals at expected times' do
+ expect(subject).to receive(:sleep).with(15 * 60).ordered
+ expect(Process).to receive(:kill).with('SIGTSTP', pid).ordered
+
+ expect(subject).to receive(:sleep).with(30).ordered
+ expect(Process).to receive(:kill).with('SIGTERM', pid).ordered
+
+ expect(subject).to receive(:sleep).with(10).ordered
+ expect(Process).to receive(:kill).with('SIGKILL', pid).ordered
+
+ run
+ end
+ end
+
+ context 'when MAX_RSS is not exceeded' do
+ before do
+ stub_const("#{described_class}::MAX_RSS", 15.kilobytes)
+ end
+
+ it 'does nothing' do
+ expect(subject).not_to receive(:sleep)
+
+ run
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sidekiq_middleware/shutdown_spec.rb b/spec/lib/gitlab/sidekiq_middleware/shutdown_spec.rb
deleted file mode 100644
index 0001795c3f0..00000000000
--- a/spec/lib/gitlab/sidekiq_middleware/shutdown_spec.rb
+++ /dev/null
@@ -1,88 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::SidekiqMiddleware::Shutdown do
- subject { described_class.new }
-
- let(:pid) { Process.pid }
- let(:worker) { double(:worker, class: 'TestWorker') }
- let(:job) { { 'jid' => 123 } }
- let(:queue) { 'test_queue' }
- let(:block) { proc { nil } }
-
- def run
- subject.call(worker, job, queue) { block.call }
- described_class.shutdown_thread&.join
- end
-
- def pop_trace
- subject.trace.pop(true)
- end
-
- before do
- allow(subject).to receive(:get_rss).and_return(10.kilobytes)
- described_class.clear_shutdown_thread
- end
-
- context 'when MAX_RSS is set to 0' do
- before do
- stub_const("#{described_class}::MAX_RSS", 0)
- end
-
- it 'does nothing' do
- expect(subject).not_to receive(:sleep)
-
- run
- end
- end
-
- def expect_shutdown_sequence
- expect(pop_trace).to eq([:sleep, 15 * 60])
- expect(pop_trace).to eq([:kill, 'SIGTSTP', pid])
-
- expect(pop_trace).to eq([:sleep, 30])
- expect(pop_trace).to eq([:kill, 'SIGTERM', pid])
-
- expect(pop_trace).to eq([:sleep, 10])
- expect(pop_trace).to eq([:kill, 'SIGKILL', pid])
- end
-
- context 'when MAX_RSS is exceeded' do
- before do
- stub_const("#{described_class}::MAX_RSS", 5.kilobytes)
- end
-
- it 'sends the TSTP, TERM and KILL signals at expected times' do
- run
-
- expect_shutdown_sequence
- end
- end
-
- context 'when MAX_RSS is not exceeded' do
- before do
- stub_const("#{described_class}::MAX_RSS", 15.kilobytes)
- end
-
- it 'does nothing' do
- expect(subject).not_to receive(:sleep)
-
- run
- end
- end
-
- context 'when WantShutdown is raised' do
- let(:block) { proc { raise described_class::WantShutdown } }
-
- it 'starts the shutdown sequence and re-raises the exception' do
- expect { run }.to raise_exception(described_class::WantShutdown)
-
- # We can't expect 'run' to have joined on the shutdown thread, because
- # it hit an exception.
- shutdown_thread = described_class.shutdown_thread
- expect(shutdown_thread).not_to be_nil
- shutdown_thread.join
-
- expect_shutdown_sequence
- end
- end
-end
diff --git a/spec/lib/gitlab/slash_commands/application_help_spec.rb b/spec/lib/gitlab/slash_commands/application_help_spec.rb
new file mode 100644
index 00000000000..b203a1ee79c
--- /dev/null
+++ b/spec/lib/gitlab/slash_commands/application_help_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::SlashCommands::ApplicationHelp do
+ let(:params) { { command: '/gitlab', text: 'help' } }
+
+ describe '#execute' do
+ subject do
+ described_class.new(params).execute
+ end
+
+ it 'displays the help section' do
+ expect(subject[:response_type]).to be(:ephemeral)
+ expect(subject[:text]).to include('Available commands')
+ expect(subject[:text]).to include('/gitlab [project name or alias] issue show')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/slash_commands/presenters/error_spec.rb b/spec/lib/gitlab/slash_commands/presenters/error_spec.rb
new file mode 100644
index 00000000000..30ff81510c1
--- /dev/null
+++ b/spec/lib/gitlab/slash_commands/presenters/error_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::SlashCommands::Presenters::Error do
+ subject { described_class.new('Error').message }
+
+ it { is_expected.to be_a(Hash) }
+
+ it 'shows the error message' do
+ expect(subject[:response_type]).to be(:ephemeral)
+ expect(subject[:status]).to eq(200)
+ expect(subject[:text]).to eq('Error')
+ end
+end
diff --git a/spec/lib/gitlab/slash_commands/presenters/run_spec.rb b/spec/lib/gitlab/slash_commands/presenters/run_spec.rb
new file mode 100644
index 00000000000..f3ab01ef6bb
--- /dev/null
+++ b/spec/lib/gitlab/slash_commands/presenters/run_spec.rb
@@ -0,0 +1,79 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::SlashCommands::Presenters::Run do
+ let(:presenter) { described_class.new }
+
+ describe '#present' do
+ context 'when no builds are present' do
+ it 'returns an error' do
+ builds = double(:builds, take: nil)
+ pipeline = double(:pipeline, builds: builds)
+
+ expect(presenter)
+ .to receive(:unsupported_chat_service)
+
+ presenter.present(pipeline)
+ end
+ end
+
+ context 'when a responder could be found' do
+ it 'returns the output for a scheduled pipeline' do
+ responder = double(:responder, scheduled_output: 'hello')
+ build = double(:build)
+ builds = double(:builds, take: build)
+ pipeline = double(:pipeline, builds: builds)
+
+ allow(Gitlab::Chat::Responder)
+ .to receive(:responder_for)
+ .with(build)
+ .and_return(responder)
+
+ expect(presenter)
+ .to receive(:in_channel_response)
+ .with('hello')
+
+ presenter.present(pipeline)
+ end
+ end
+
+ context 'when a responder could not be found' do
+ it 'returns an error' do
+ build = double(:build)
+ builds = double(:builds, take: build)
+ pipeline = double(:pipeline, builds: builds)
+
+ allow(Gitlab::Chat::Responder)
+ .to receive(:responder_for)
+ .with(build)
+ .and_return(nil)
+
+ expect(presenter)
+ .to receive(:unsupported_chat_service)
+
+ presenter.present(pipeline)
+ end
+ end
+ end
+
+ describe '#unsupported_chat_service' do
+ it 'returns an ephemeral response' do
+ expect(presenter)
+ .to receive(:ephemeral_response)
+ .with(text: /Sorry, this chat service is currently not supported/)
+
+ presenter.unsupported_chat_service
+ end
+ end
+
+ describe '#failed_to_schedule' do
+ it 'returns an ephemeral response' do
+ expect(presenter)
+ .to receive(:ephemeral_response)
+ .with(text: /The command could not be scheduled/)
+
+ presenter.failed_to_schedule('foo')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/slash_commands/run_spec.rb b/spec/lib/gitlab/slash_commands/run_spec.rb
new file mode 100644
index 00000000000..900fae05719
--- /dev/null
+++ b/spec/lib/gitlab/slash_commands/run_spec.rb
@@ -0,0 +1,123 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::SlashCommands::Run do
+ describe '.available?' do
+ it 'returns true when builds are enabled for the project' do
+ project = double(:project, builds_enabled?: true)
+
+ allow(Gitlab::Chat)
+ .to receive(:available?)
+ .and_return(true)
+
+ expect(described_class.available?(project)).to eq(true)
+ end
+
+ it 'returns false when builds are disabled for the project' do
+ project = double(:project, builds_enabled?: false)
+
+ expect(described_class.available?(project)).to eq(false)
+ end
+
+ it 'returns false when chatops is not available' do
+ allow(Gitlab::Chat)
+ .to receive(:available?)
+ .and_return(false)
+
+ project = double(:project, builds_enabled?: true)
+
+ expect(described_class.available?(project)).to eq(false)
+ end
+ end
+
+ describe '.allowed?' do
+ it 'returns true when the user can create a pipeline' do
+ project = create(:project)
+
+ expect(described_class.allowed?(project, project.creator)).to eq(true)
+ end
+
+ it 'returns false when the user can not create a pipeline' do
+ project = create(:project)
+ user = create(:user)
+
+ expect(described_class.allowed?(project, user)).to eq(false)
+ end
+ end
+
+ describe '#execute' do
+ let(:chat_name) { create(:chat_name) }
+ let(:project) { create(:project) }
+
+ let(:command) do
+ described_class.new(project, chat_name, response_url: 'http://example.com')
+ end
+
+ context 'when a pipeline could not be scheduled' do
+ it 'returns an error' do
+ expect_any_instance_of(Gitlab::Chat::Command)
+ .to receive(:try_create_pipeline)
+ .and_return(nil)
+
+ expect_any_instance_of(Gitlab::SlashCommands::Presenters::Run)
+ .to receive(:failed_to_schedule)
+ .with('foo')
+
+ command.execute(command: 'foo', arguments: '')
+ end
+ end
+
+ context 'when a pipeline could be created but the chat service was not supported' do
+ it 'returns an error' do
+ build = double(:build)
+ pipeline = double(
+ :pipeline,
+ builds: double(:relation, take: build),
+ persisted?: true
+ )
+
+ expect_any_instance_of(Gitlab::Chat::Command)
+ .to receive(:try_create_pipeline)
+ .and_return(pipeline)
+
+ expect(Gitlab::Chat::Responder)
+ .to receive(:responder_for)
+ .with(build)
+ .and_return(nil)
+
+ expect_any_instance_of(Gitlab::SlashCommands::Presenters::Run)
+ .to receive(:unsupported_chat_service)
+
+ command.execute(command: 'foo', arguments: '')
+ end
+ end
+
+ context 'using a valid pipeline' do
+ it 'schedules the pipeline' do
+ responder = double(:responder, scheduled_output: 'hello')
+ build = double(:build)
+ pipeline = double(
+ :pipeline,
+ builds: double(:relation, take: build),
+ persisted?: true
+ )
+
+ expect_any_instance_of(Gitlab::Chat::Command)
+ .to receive(:try_create_pipeline)
+ .and_return(pipeline)
+
+ expect(Gitlab::Chat::Responder)
+ .to receive(:responder_for)
+ .with(build)
+ .and_return(responder)
+
+ expect_any_instance_of(Gitlab::SlashCommands::Presenters::Run)
+ .to receive(:in_channel_response)
+ .with(responder.scheduled_output)
+
+ command.execute(command: 'foo', arguments: '')
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/tracing_spec.rb b/spec/lib/gitlab/tracing_spec.rb
index 2cddc895026..566b5050e47 100644
--- a/spec/lib/gitlab/tracing_spec.rb
+++ b/spec/lib/gitlab/tracing_spec.rb
@@ -44,12 +44,15 @@ describe Gitlab::Tracing do
describe '.tracing_url' do
where(:tracing_url_enabled?, :tracing_url_template, :correlation_id, :process_name, :tracing_url) do
- false | "https://localhost" | "123" | "web" | nil
- true | "https://localhost" | "123" | "web" | "https://localhost"
- true | "https://localhost?service=%{service}" | "123" | "web" | "https://localhost?service=web"
- true | "https://localhost?c=%{correlation_id}" | "123" | "web" | "https://localhost?c=123"
- true | "https://localhost?c=%{correlation_id}&s=%{service}" | "123" | "web" | "https://localhost?c=123&s=web"
- true | "https://localhost?c=%{correlation_id}" | nil | "web" | "https://localhost?c="
+ false | "https://localhost" | "123" | "web" | nil
+ true | "https://localhost" | "123" | "web" | "https://localhost"
+ true | "https://localhost?service={{ service }}" | "123" | "web" | "https://localhost?service=web"
+ true | "https://localhost?c={{ correlation_id }}" | "123" | "web" | "https://localhost?c=123"
+ true | "https://localhost?c={{ correlation_id }}&s={{ service }}" | "123" | "web" | "https://localhost?c=123&s=web"
+ true | "https://localhost?c={{ correlation_id }}" | nil | "web" | "https://localhost?c="
+ true | "https://localhost?c={{ correlation_id }}&s=%22{{ service }}%22" | "123" | "web" | "https://localhost?c=123&s=%22web%22"
+ true | "https://localhost?c={{correlation_id}}&s={{service}}" | "123" | "web" | "https://localhost?c=123&s=web"
+ true | "https://localhost?c={{correlation_id }}&s={{ service}}" | "123" | "web" | "https://localhost?c=123&s=web"
end
with_them do
diff --git a/spec/lib/object_storage/direct_upload_spec.rb b/spec/lib/object_storage/direct_upload_spec.rb
index 1024e1a25ea..8ccbd90ddb8 100644
--- a/spec/lib/object_storage/direct_upload_spec.rb
+++ b/spec/lib/object_storage/direct_upload_spec.rb
@@ -121,7 +121,7 @@ describe ObjectStorage::DirectUpload do
expect(subject[:MultipartUpload][:PartURLs].length).to eq(2)
end
- it 'part size is mimimum, 5MB' do
+ it 'part size is minimum, 5MB' do
expect(subject[:MultipartUpload][:PartSize]).to eq(5.megabyte)
end
end
diff --git a/spec/lib/sentry/client_spec.rb b/spec/lib/sentry/client_spec.rb
index 6fbf60a6222..88e7e2e5ebb 100644
--- a/spec/lib/sentry/client_spec.rb
+++ b/spec/lib/sentry/client_spec.rb
@@ -36,7 +36,7 @@ describe Sentry::Client do
end
it 'does not follow redirects' do
- expect { subject }.to raise_exception(Sentry::Client::Error, 'Sentry response error: 302')
+ expect { subject }.to raise_exception(Sentry::Client::Error, 'Sentry response status code: 302')
expect(redirect_req_stub).to have_been_requested
expect(redirected_req_stub).not_to have_been_requested
end
diff --git a/spec/mailers/abuse_report_mailer_spec.rb b/spec/mailers/abuse_report_mailer_spec.rb
index bda892083b3..f96870cc112 100644
--- a/spec/mailers/abuse_report_mailer_spec.rb
+++ b/spec/mailers/abuse_report_mailer_spec.rb
@@ -4,25 +4,24 @@ describe AbuseReportMailer do
include EmailSpec::Matchers
describe '.notify' do
- context 'with admin_notification_email set' do
- before do
- stub_application_setting(admin_notification_email: 'admin@example.com')
- end
+ before do
+ stub_application_setting(admin_notification_email: 'admin@example.com')
+ end
- it 'sends to the admin_notification_email' do
- report = create(:abuse_report)
+ let(:report) { create(:abuse_report) }
+
+ subject { described_class.notify(report.id) }
- mail = described_class.notify(report.id)
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
- expect(mail).to deliver_to 'admin@example.com'
+ context 'with admin_notification_email set' do
+ it 'sends to the admin_notification_email' do
+ is_expected.to deliver_to 'admin@example.com'
end
it 'includes the user in the subject' do
- report = create(:abuse_report)
-
- mail = described_class.notify(report.id)
-
- expect(mail).to have_subject "#{report.user.name} (#{report.user.username}) was reported for abuse"
+ is_expected.to have_subject "#{report.user.name} (#{report.user.username}) was reported for abuse"
end
end
diff --git a/spec/mailers/email_rejection_mailer_spec.rb b/spec/mailers/email_rejection_mailer_spec.rb
new file mode 100644
index 00000000000..bbe0a50ae8e
--- /dev/null
+++ b/spec/mailers/email_rejection_mailer_spec.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe EmailRejectionMailer do
+ include EmailSpec::Matchers
+
+ describe '#rejection' do
+ let(:raw_email) { 'From: someone@example.com\nraw email here' }
+
+ subject { described_class.rejection('some rejection reason', raw_email) }
+
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
+ end
+end
diff --git a/spec/mailers/emails/auto_devops_spec.rb b/spec/mailers/emails/auto_devops_spec.rb
index 839caf3f50e..dd7c12c3143 100644
--- a/spec/mailers/emails/auto_devops_spec.rb
+++ b/spec/mailers/emails/auto_devops_spec.rb
@@ -13,6 +13,9 @@ describe Emails::AutoDevops do
subject { Notify.autodevops_disabled_email(pipeline, owner.email) }
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
+
it 'sents email with correct subject' do
is_expected.to have_subject("#{project.name} | Auto DevOps pipeline was disabled for #{project.name}")
end
diff --git a/spec/mailers/emails/issues_spec.rb b/spec/mailers/emails/issues_spec.rb
index 09253cf8003..5b5bd6f4308 100644
--- a/spec/mailers/emails/issues_spec.rb
+++ b/spec/mailers/emails/issues_spec.rb
@@ -29,5 +29,14 @@ describe Emails::Issues do
expect(subject).to have_body_text "23, 34, 58"
end
+
+ context 'with header and footer' do
+ let(:results) { { success: 165, error_lines: [], parse_error: false } }
+
+ subject { Notify.import_issues_csv_email(user.id, project.id, results) }
+
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
+ end
end
end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 4f578c48d5b..15b04c9d066 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -53,6 +53,8 @@ describe Notify do
end
it_behaves_like 'it should show Gmail Actions View Issue link'
it_behaves_like 'an unsubscribeable thread'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'has the correct subject and body' do
aggregate_failures do
@@ -72,6 +74,9 @@ describe Notify do
context 'when sent with a reason' do
subject { described_class.new_issue_email(issue.assignees.first.id, issue.id, NotificationReason::ASSIGNED) }
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
+
it 'includes the reason in a header' do
is_expected.to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED)
end
@@ -99,6 +104,8 @@ describe Notify do
end
it_behaves_like 'it should show Gmail Actions View Issue link'
it_behaves_like 'an unsubscribeable thread'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -118,6 +125,9 @@ describe Notify do
context 'when sent with a reason' do
subject { described_class.reassigned_issue_email(recipient.id, issue.id, [previous_assignee.id], current_user.id, NotificationReason::ASSIGNED) }
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
+
it 'includes the reason in a header' do
is_expected.to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED)
end
@@ -134,6 +144,8 @@ describe Notify do
it_behaves_like 'it should show Gmail Actions View Issue link'
it_behaves_like 'a user cannot unsubscribe through footer link'
it_behaves_like 'an email with a labels subscriptions link in its footer'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -173,6 +185,8 @@ describe Notify do
end
it_behaves_like 'it should show Gmail Actions View Issue link'
it_behaves_like 'an unsubscribeable thread'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -199,6 +213,8 @@ describe Notify do
end
it_behaves_like 'it should show Gmail Actions View Issue link'
it_behaves_like 'an unsubscribeable thread'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'contains description about action taken' do
is_expected.to have_body_text 'Issue was moved to another project'
@@ -226,6 +242,8 @@ describe Notify do
end
it_behaves_like 'it should show Gmail Actions View Merge request link'
it_behaves_like 'an unsubscribeable thread'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'has the correct subject and body' do
aggregate_failures do
@@ -243,6 +261,9 @@ describe Notify do
context 'when sent with a reason' do
subject { described_class.new_merge_request_email(merge_request.assignee_id, merge_request.id, NotificationReason::ASSIGNED) }
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
+
it 'includes the reason in a header' do
is_expected.to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED)
end
@@ -270,6 +291,8 @@ describe Notify do
end
it_behaves_like 'it should show Gmail Actions View Merge request link'
it_behaves_like "an unsubscribeable thread"
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -289,6 +312,9 @@ describe Notify do
context 'when sent with a reason' do
subject { described_class.reassigned_merge_request_email(recipient.id, merge_request.id, previous_assignee.id, current_user.id, NotificationReason::ASSIGNED) }
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
+
it 'includes the reason in a header' do
is_expected.to have_header('X-GitLab-NotificationReason', NotificationReason::ASSIGNED)
end
@@ -313,6 +339,8 @@ describe Notify do
it_behaves_like 'it should show Gmail Actions View Merge request link'
it_behaves_like "an unsubscribeable thread"
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'contains the description' do
is_expected.to have_body_text(merge_request.description)
@@ -329,6 +357,8 @@ describe Notify do
it_behaves_like 'it should show Gmail Actions View Merge request link'
it_behaves_like 'a user cannot unsubscribe through footer link'
it_behaves_like 'an email with a labels subscriptions link in its footer'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -352,6 +382,8 @@ describe Notify do
end
it_behaves_like 'it should show Gmail Actions View Merge request link'
it_behaves_like 'an unsubscribeable thread'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -379,6 +411,8 @@ describe Notify do
end
it_behaves_like 'it should show Gmail Actions View Merge request link'
it_behaves_like 'an unsubscribeable thread'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'is sent as the merge author' do
sender = subject.header[:from].addrs[0]
@@ -413,6 +447,8 @@ describe Notify do
end
it_behaves_like 'it should show Gmail Actions View Merge request link'
it_behaves_like 'an unsubscribeable thread'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'is sent as the merge request author' do
sender = subject.header[:from].addrs[0]
@@ -442,6 +478,8 @@ describe Notify do
end
it_behaves_like 'it should show Gmail Actions View Merge request link'
it_behaves_like 'an unsubscribeable thread'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'is sent as the push user' do
sender = subject.header[:from].addrs[0]
@@ -482,6 +520,9 @@ describe Notify do
subject { described_class.note_issue_email(recipient.id, third_note.id) }
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
+
it 'has In-Reply-To header pointing to previous note in discussion' do
expect(subject.header['In-Reply-To'].message_ids).to eq(["note_#{second_note.id}@#{host}"])
end
@@ -502,6 +543,9 @@ describe Notify do
subject { described_class.note_issue_email(recipient.id, note.id) }
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
+
it 'has In-Reply-To header pointing to the issue' do
expect(subject.header['In-Reply-To'].message_ids).to eq(["issue_#{note.noteable.id}@#{host}"])
end
@@ -518,6 +562,9 @@ describe Notify do
subject { described_class.note_project_snippet_email(project_snippet_note.author_id, project_snippet_note.id) }
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
+
it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
let(:model) { project_snippet }
end
@@ -535,6 +582,8 @@ describe Notify do
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'has the correct subject and body' do
is_expected.to have_subject("#{project.name} | Project was moved")
@@ -559,6 +608,8 @@ describe Notify do
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'contains all the useful information' do
to_emails = subject.header[:to].addrs.map(&:address)
@@ -582,6 +633,8 @@ describe Notify do
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'contains all the useful information' do
is_expected.to have_subject "Access to the #{project.full_name} project was denied"
@@ -599,6 +652,8 @@ describe Notify do
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'contains all the useful information' do
is_expected.to have_subject "Access to the #{project.full_name} project was granted"
@@ -629,6 +684,8 @@ describe Notify do
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'contains all the useful information' do
is_expected.to have_subject "Invitation to join the #{project.full_name} project"
@@ -653,6 +710,8 @@ describe Notify do
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'contains all the useful information' do
is_expected.to have_subject 'Invitation accepted'
@@ -676,6 +735,8 @@ describe Notify do
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'contains all the useful information' do
is_expected.to have_subject 'Invitation declined'
@@ -708,6 +769,8 @@ describe Notify do
end
it_behaves_like 'it should show Gmail Actions View Commit link'
it_behaves_like 'a user cannot unsubscribe through footer link'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'has the correct subject and body' do
aggregate_failures do
@@ -732,6 +795,8 @@ describe Notify do
end
it_behaves_like 'it should show Gmail Actions View Merge request link'
it_behaves_like 'an unsubscribeable thread'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'has the correct subject and body' do
aggregate_failures do
@@ -756,6 +821,8 @@ describe Notify do
end
it_behaves_like 'it should show Gmail Actions View Issue link'
it_behaves_like 'an unsubscribeable thread'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'has the correct subject and body' do
aggregate_failures do
@@ -819,6 +886,8 @@ describe Notify do
end
it_behaves_like 'it should show Gmail Actions View Commit link'
it_behaves_like 'a user cannot unsubscribe through footer link'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'has the correct subject' do
is_expected.to have_subject "Re: #{project.name} | #{commit.title} (#{commit.short_id})"
@@ -845,6 +914,8 @@ describe Notify do
end
it_behaves_like 'it should show Gmail Actions View Merge request link'
it_behaves_like 'an unsubscribeable thread'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'has the correct subject' do
is_expected.to have_referable_subject(merge_request, reply: true)
@@ -871,6 +942,8 @@ describe Notify do
end
it_behaves_like 'it should show Gmail Actions View Issue link'
it_behaves_like 'an unsubscribeable thread'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'has the correct subject' do
is_expected.to have_referable_subject(issue, reply: true)
@@ -948,6 +1021,8 @@ describe Notify do
it_behaves_like 'an email for a note on a diff discussion', :diff_note_on_commit
it_behaves_like 'it should show Gmail Actions View Commit link'
it_behaves_like 'a user cannot unsubscribe through footer link'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
end
describe 'on a merge request' do
@@ -958,6 +1033,8 @@ describe Notify do
it_behaves_like 'an email for a note on a diff discussion', :diff_note_on_merge_request
it_behaves_like 'it should show Gmail Actions View Merge request link'
it_behaves_like 'an unsubscribeable thread'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
end
end
end
@@ -976,6 +1053,8 @@ describe Notify do
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'contains all the useful information' do
to_emails = subject.header[:to].addrs.map(&:address)
@@ -998,6 +1077,8 @@ describe Notify do
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'contains all the useful information' do
is_expected.to have_subject "Access to the #{group.name} group was denied"
@@ -1014,6 +1095,8 @@ describe Notify do
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'contains all the useful information' do
is_expected.to have_subject "Access to the #{group.name} group was granted"
@@ -1044,6 +1127,8 @@ describe Notify do
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'contains all the useful information' do
is_expected.to have_subject "Invitation to join the #{group.name} group"
@@ -1068,6 +1153,8 @@ describe Notify do
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'contains all the useful information' do
is_expected.to have_subject 'Invitation accepted'
@@ -1091,6 +1178,8 @@ describe Notify do
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'contains all the useful information' do
is_expected.to have_subject 'Invitation declined'
@@ -1140,6 +1229,8 @@ describe Notify do
it_behaves_like 'a user cannot unsubscribe through footer link'
it_behaves_like 'an email with X-GitLab headers containing project details'
it_behaves_like 'an email that contains a header with author username'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -1165,6 +1256,8 @@ describe Notify do
it_behaves_like "a user cannot unsubscribe through footer link"
it_behaves_like 'an email with X-GitLab headers containing project details'
it_behaves_like 'an email that contains a header with author username'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -1189,6 +1282,8 @@ describe Notify do
it_behaves_like 'a user cannot unsubscribe through footer link'
it_behaves_like 'an email with X-GitLab headers containing project details'
it_behaves_like 'an email that contains a header with author username'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -1210,6 +1305,8 @@ describe Notify do
it_behaves_like 'a user cannot unsubscribe through footer link'
it_behaves_like 'an email with X-GitLab headers containing project details'
it_behaves_like 'an email that contains a header with author username'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -1237,6 +1334,8 @@ describe Notify do
it_behaves_like 'a user cannot unsubscribe through footer link'
it_behaves_like 'an email with X-GitLab headers containing project details'
it_behaves_like 'an email that contains a header with author username'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -1328,6 +1427,8 @@ describe Notify do
it_behaves_like 'a user cannot unsubscribe through footer link'
it_behaves_like 'an email with X-GitLab headers containing project details'
it_behaves_like 'an email that contains a header with author username'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -1348,6 +1449,11 @@ describe Notify do
describe 'HTML emails setting' do
let(:multipart_mail) { described_class.project_was_moved_email(project.id, user.id, "gitlab/gitlab") }
+ subject { multipart_mail }
+
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
+
context 'when disabled' do
it 'only sends the text template' do
stub_application_setting(html_emails_enabled: false)
@@ -1386,6 +1492,8 @@ describe Notify do
subject { described_class.note_personal_snippet_email(personal_snippet_note.author_id, personal_snippet_note.id) }
it_behaves_like 'a user cannot unsubscribe through footer link'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
it 'has the correct subject and body' do
is_expected.to have_referable_subject(personal_snippet, reply: true)
diff --git a/spec/mailers/repository_check_mailer_spec.rb b/spec/mailers/repository_check_mailer_spec.rb
index 00613c7b671..384660f7221 100644
--- a/spec/mailers/repository_check_mailer_spec.rb
+++ b/spec/mailers/repository_check_mailer_spec.rb
@@ -17,5 +17,12 @@ describe RepositoryCheckMailer do
expect(mail).to have_subject 'GitLab Admin | 3 projects failed their last repository check'
end
+
+ context 'with footer and header' do
+ subject { described_class.notify(1) }
+
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
+ end
end
end
diff --git a/spec/migrations/clean_up_noteable_id_for_notes_on_commits_spec.rb b/spec/migrations/clean_up_noteable_id_for_notes_on_commits_spec.rb
new file mode 100644
index 00000000000..bb3038ada6e
--- /dev/null
+++ b/spec/migrations/clean_up_noteable_id_for_notes_on_commits_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20190228092516_clean_up_noteable_id_for_notes_on_commits.rb')
+
+describe CleanUpNoteableIdForNotesOnCommits, :migration do
+ let(:notes) { table(:notes) }
+
+ before do
+ notes.create!(noteable_type: 'Commit', commit_id: '3d0a182204cece4857f81c6462720e0ad1af39c9', noteable_id: 3, note: 'Test')
+ notes.create!(noteable_type: 'Commit', commit_id: '3d0a182204cece4857f81c6462720e0ad1af39c9', noteable_id: 3, note: 'Test')
+ notes.create!(noteable_type: 'Commit', commit_id: '3d0a182204cece4857f81c6462720e0ad1af39c9', noteable_id: 3, note: 'Test')
+
+ notes.create!(noteable_type: 'Issue', noteable_id: 1, note: 'Test')
+ notes.create!(noteable_type: 'MergeRequest', noteable_id: 1, note: 'Test')
+ notes.create!(noteable_type: 'Snippet', noteable_id: 1, note: 'Test')
+ end
+
+ it 'clears noteable_id for notes on commits' do
+ expect { migrate! }.to change { dirty_notes_on_commits.count }.from(3).to(0)
+ end
+
+ it 'does not clear noteable_id for other notes' do
+ expect { migrate! }.not_to change { other_notes.count }
+ end
+
+ def dirty_notes_on_commits
+ notes.where(noteable_type: 'Commit').where('noteable_id IS NOT NULL')
+ end
+
+ def other_notes
+ notes.where("noteable_type != 'Commit' AND noteable_id IS NOT NULL")
+ end
+end
diff --git a/spec/models/appearance_spec.rb b/spec/models/appearance_spec.rb
index cc76a2019ec..3e95aa2b5dd 100644
--- a/spec/models/appearance_spec.rb
+++ b/spec/models/appearance_spec.rb
@@ -63,4 +63,37 @@ describe Appearance do
%i(logo header_logo favicon).each do |logo_type|
it_behaves_like 'logo paths', logo_type
end
+
+ describe 'validations' do
+ let(:triplet) { '#000' }
+ let(:hex) { '#AABBCC' }
+
+ it { is_expected.to allow_value(nil).for(:message_background_color) }
+ it { is_expected.to allow_value(triplet).for(:message_background_color) }
+ it { is_expected.to allow_value(hex).for(:message_background_color) }
+ it { is_expected.not_to allow_value('000').for(:message_background_color) }
+
+ it { is_expected.to allow_value(nil).for(:message_font_color) }
+ it { is_expected.to allow_value(triplet).for(:message_font_color) }
+ it { is_expected.to allow_value(hex).for(:message_font_color) }
+ it { is_expected.not_to allow_value('000').for(:message_font_color) }
+ end
+
+ describe 'email_header_and_footer_enabled' do
+ context 'default email_header_and_footer_enabled flag value' do
+ it 'returns email_header_and_footer_enabled as true' do
+ appearance = build(:appearance)
+
+ expect(appearance.email_header_and_footer_enabled?).to eq(false)
+ end
+ end
+
+ context 'when setting email_header_and_footer_enabled flag value' do
+ it 'returns email_header_and_footer_enabled as true' do
+ appearance = build(:appearance, email_header_and_footer_enabled: true)
+
+ expect(appearance.email_header_and_footer_enabled?).to eq(true)
+ end
+ end
+ end
end
diff --git a/spec/models/board_group_recent_visit_spec.rb b/spec/models/board_group_recent_visit_spec.rb
index 59ad4e5417e..558be61824f 100644
--- a/spec/models/board_group_recent_visit_spec.rb
+++ b/spec/models/board_group_recent_visit_spec.rb
@@ -50,15 +50,25 @@ describe BoardGroupRecentVisit do
end
describe '#latest' do
- it 'returns the most recent visited' do
- board2 = create(:board, group: group)
- board3 = create(:board, group: group)
+ def create_visit(time)
+ create :board_group_recent_visit, group: group, user: user, updated_at: time
+ end
- create :board_group_recent_visit, group: board.group, board: board, user: user, updated_at: 7.days.ago
- create :board_group_recent_visit, group: board2.group, board: board2, user: user, updated_at: 5.days.ago
- recent = create :board_group_recent_visit, group: board3.group, board: board3, user: user, updated_at: 1.day.ago
+ it 'returns the most recent visited' do
+ create_visit(7.days.ago)
+ create_visit(5.days.ago)
+ recent = create_visit(1.day.ago)
expect(described_class.latest(user, group)).to eq recent
end
+
+ it 'returns last 3 visited boards' do
+ create_visit(7.days.ago)
+ visit1 = create_visit(3.days.ago)
+ visit2 = create_visit(2.days.ago)
+ visit3 = create_visit(5.days.ago)
+
+ expect(described_class.latest(user, group, count: 3)).to eq([visit2, visit1, visit3])
+ end
end
end
diff --git a/spec/models/board_project_recent_visit_spec.rb b/spec/models/board_project_recent_visit_spec.rb
index 275581945fa..e404fb3bbdb 100644
--- a/spec/models/board_project_recent_visit_spec.rb
+++ b/spec/models/board_project_recent_visit_spec.rb
@@ -50,15 +50,25 @@ describe BoardProjectRecentVisit do
end
describe '#latest' do
- it 'returns the most recent visited' do
- board2 = create(:board, project: project)
- board3 = create(:board, project: project)
+ def create_visit(time)
+ create :board_project_recent_visit, project: project, user: user, updated_at: time
+ end
- create :board_project_recent_visit, project: board.project, board: board, user: user, updated_at: 7.days.ago
- create :board_project_recent_visit, project: board2.project, board: board2, user: user, updated_at: 5.days.ago
- recent = create :board_project_recent_visit, project: board3.project, board: board3, user: user, updated_at: 1.day.ago
+ it 'returns the most recent visited' do
+ create_visit(7.days.ago)
+ create_visit(5.days.ago)
+ recent = create_visit(1.day.ago)
expect(described_class.latest(user, project)).to eq recent
end
+
+ it 'returns last 3 visited boards' do
+ create_visit(7.days.ago)
+ visit1 = create_visit(3.days.ago)
+ visit2 = create_visit(2.days.ago)
+ visit3 = create_visit(5.days.ago)
+
+ expect(described_class.latest(user, project, count: 3)).to eq([visit2, visit1, visit3])
+ end
end
end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 47865e4d08f..fc75d3e23fb 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -23,6 +23,7 @@ describe Ci::Build do
it { is_expected.to validate_presence_of(:ref) }
it { is_expected.to respond_to(:has_trace?) }
it { is_expected.to respond_to(:trace) }
+ it { is_expected.to delegate_method(:merge_request_event?).to(:pipeline) }
it { is_expected.to be_a(ArtifactMigratable) }
@@ -2113,55 +2114,55 @@ describe Ci::Build do
context 'returns variables' do
let(:predefined_variables) do
[
- { key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true },
- { key: 'CI_PIPELINE_URL', value: project.web_url + "/pipelines/#{pipeline.id}", public: true },
- { key: 'CI_JOB_ID', value: build.id.to_s, public: true },
- { key: 'CI_JOB_URL', value: project.web_url + "/-/jobs/#{build.id}", public: true },
- { key: 'CI_JOB_TOKEN', value: 'my-token', public: false },
- { key: 'CI_BUILD_ID', value: build.id.to_s, public: true },
- { key: 'CI_BUILD_TOKEN', value: 'my-token', public: false },
- { key: 'CI_REGISTRY_USER', value: 'gitlab-ci-token', public: true },
- { key: 'CI_REGISTRY_PASSWORD', value: 'my-token', public: false },
- { key: 'CI_REPOSITORY_URL', value: build.repo_url, public: false },
- { key: 'CI', value: 'true', public: true },
- { key: 'GITLAB_CI', value: 'true', public: true },
- { key: 'GITLAB_FEATURES', value: project.licensed_features.join(','), public: true },
- { key: 'CI_SERVER_NAME', value: 'GitLab', public: true },
- { key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true },
- { key: 'CI_SERVER_VERSION_MAJOR', value: Gitlab.version_info.major.to_s, public: true },
- { key: 'CI_SERVER_VERSION_MINOR', value: Gitlab.version_info.minor.to_s, public: true },
- { key: 'CI_SERVER_VERSION_PATCH', value: Gitlab.version_info.patch.to_s, public: true },
- { key: 'CI_SERVER_REVISION', value: Gitlab.revision, public: true },
- { key: 'CI_JOB_NAME', value: 'test', public: true },
- { key: 'CI_JOB_STAGE', value: 'test', public: true },
- { key: 'CI_COMMIT_SHA', value: build.sha, public: true },
- { key: 'CI_COMMIT_SHORT_SHA', value: build.short_sha, public: true },
- { key: 'CI_COMMIT_BEFORE_SHA', value: build.before_sha, public: true },
- { key: 'CI_COMMIT_REF_NAME', value: build.ref, public: true },
- { key: 'CI_COMMIT_REF_SLUG', value: build.ref_slug, public: true },
- { key: 'CI_NODE_TOTAL', value: '1', public: true },
- { key: 'CI_BUILD_REF', value: build.sha, public: true },
- { key: 'CI_BUILD_BEFORE_SHA', value: build.before_sha, public: true },
- { key: 'CI_BUILD_REF_NAME', value: build.ref, public: true },
- { key: 'CI_BUILD_REF_SLUG', value: build.ref_slug, public: true },
- { key: 'CI_BUILD_NAME', value: 'test', public: true },
- { key: 'CI_BUILD_STAGE', value: 'test', public: true },
- { key: 'CI_PROJECT_ID', value: project.id.to_s, public: true },
- { key: 'CI_PROJECT_NAME', value: project.path, public: true },
- { key: 'CI_PROJECT_PATH', value: project.full_path, public: true },
- { key: 'CI_PROJECT_PATH_SLUG', value: project.full_path_slug, public: true },
- { key: 'CI_PROJECT_NAMESPACE', value: project.namespace.full_path, public: true },
- { key: 'CI_PROJECT_URL', value: project.web_url, public: true },
- { key: 'CI_PROJECT_VISIBILITY', value: 'private', public: true },
- { key: 'CI_PAGES_DOMAIN', value: Gitlab.config.pages.host, public: true },
- { key: 'CI_PAGES_URL', value: project.pages_url, public: true },
- { key: 'CI_API_V4_URL', value: 'http://localhost/api/v4', public: true },
- { key: 'CI_PIPELINE_IID', value: pipeline.iid.to_s, public: true },
- { key: 'CI_CONFIG_PATH', value: pipeline.ci_yaml_file_path, public: true },
- { key: 'CI_PIPELINE_SOURCE', value: pipeline.source, public: true },
- { key: 'CI_COMMIT_MESSAGE', value: pipeline.git_commit_message, public: true },
- { key: 'CI_COMMIT_TITLE', value: pipeline.git_commit_title, public: true },
- { key: 'CI_COMMIT_DESCRIPTION', value: pipeline.git_commit_description, public: true }
+ { key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true, masked: false },
+ { key: 'CI_PIPELINE_URL', value: project.web_url + "/pipelines/#{pipeline.id}", public: true, masked: false },
+ { key: 'CI_JOB_ID', value: build.id.to_s, public: true, masked: false },
+ { key: 'CI_JOB_URL', value: project.web_url + "/-/jobs/#{build.id}", public: true, masked: false },
+ { key: 'CI_JOB_TOKEN', value: 'my-token', public: false, masked: false },
+ { key: 'CI_BUILD_ID', value: build.id.to_s, public: true, masked: false },
+ { key: 'CI_BUILD_TOKEN', value: 'my-token', public: false, masked: false },
+ { key: 'CI_REGISTRY_USER', value: 'gitlab-ci-token', public: true, masked: false },
+ { key: 'CI_REGISTRY_PASSWORD', value: 'my-token', public: false, masked: false },
+ { key: 'CI_REPOSITORY_URL', value: build.repo_url, public: false, masked: false },
+ { key: 'CI', value: 'true', public: true, masked: false },
+ { key: 'GITLAB_CI', value: 'true', public: true, masked: false },
+ { key: 'GITLAB_FEATURES', value: project.licensed_features.join(','), public: true, masked: false },
+ { key: 'CI_SERVER_NAME', value: 'GitLab', public: true, masked: false },
+ { key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true, masked: false },
+ { key: 'CI_SERVER_VERSION_MAJOR', value: Gitlab.version_info.major.to_s, public: true, masked: false },
+ { key: 'CI_SERVER_VERSION_MINOR', value: Gitlab.version_info.minor.to_s, public: true, masked: false },
+ { key: 'CI_SERVER_VERSION_PATCH', value: Gitlab.version_info.patch.to_s, public: true, masked: false },
+ { key: 'CI_SERVER_REVISION', value: Gitlab.revision, public: true, masked: false },
+ { key: 'CI_JOB_NAME', value: 'test', public: true, masked: false },
+ { key: 'CI_JOB_STAGE', value: 'test', public: true, masked: false },
+ { key: 'CI_COMMIT_SHA', value: build.sha, public: true, masked: false },
+ { key: 'CI_COMMIT_SHORT_SHA', value: build.short_sha, public: true, masked: false },
+ { key: 'CI_COMMIT_BEFORE_SHA', value: build.before_sha, public: true, masked: false },
+ { key: 'CI_COMMIT_REF_NAME', value: build.ref, public: true, masked: false },
+ { key: 'CI_COMMIT_REF_SLUG', value: build.ref_slug, public: true, masked: false },
+ { key: 'CI_NODE_TOTAL', value: '1', public: true, masked: false },
+ { key: 'CI_BUILD_REF', value: build.sha, public: true, masked: false },
+ { key: 'CI_BUILD_BEFORE_SHA', value: build.before_sha, public: true, masked: false },
+ { key: 'CI_BUILD_REF_NAME', value: build.ref, public: true, masked: false },
+ { key: 'CI_BUILD_REF_SLUG', value: build.ref_slug, public: true, masked: false },
+ { key: 'CI_BUILD_NAME', value: 'test', public: true, masked: false },
+ { key: 'CI_BUILD_STAGE', value: 'test', public: true, masked: false },
+ { key: 'CI_PROJECT_ID', value: project.id.to_s, public: true, masked: false },
+ { key: 'CI_PROJECT_NAME', value: project.path, public: true, masked: false },
+ { key: 'CI_PROJECT_PATH', value: project.full_path, public: true, masked: false },
+ { key: 'CI_PROJECT_PATH_SLUG', value: project.full_path_slug, public: true, masked: false },
+ { key: 'CI_PROJECT_NAMESPACE', value: project.namespace.full_path, public: true, masked: false },
+ { key: 'CI_PROJECT_URL', value: project.web_url, public: true, masked: false },
+ { key: 'CI_PROJECT_VISIBILITY', value: 'private', public: true, masked: false },
+ { key: 'CI_PAGES_DOMAIN', value: Gitlab.config.pages.host, public: true, masked: false },
+ { key: 'CI_PAGES_URL', value: project.pages_url, public: true, masked: false },
+ { key: 'CI_API_V4_URL', value: 'http://localhost/api/v4', public: true, masked: false },
+ { key: 'CI_PIPELINE_IID', value: pipeline.iid.to_s, public: true, masked: false },
+ { key: 'CI_CONFIG_PATH', value: pipeline.ci_yaml_file_path, public: true, masked: false },
+ { key: 'CI_PIPELINE_SOURCE', value: pipeline.source, public: true, masked: false },
+ { key: 'CI_COMMIT_MESSAGE', value: pipeline.git_commit_message, public: true, masked: false },
+ { key: 'CI_COMMIT_TITLE', value: pipeline.git_commit_title, public: true, masked: false },
+ { key: 'CI_COMMIT_DESCRIPTION', value: pipeline.git_commit_description, public: true, masked: false }
]
end
@@ -2174,10 +2175,10 @@ describe Ci::Build do
describe 'variables ordering' do
context 'when variables hierarchy is stubbed' do
- let(:build_pre_var) { { key: 'build', value: 'value', public: true } }
- let(:project_pre_var) { { key: 'project', value: 'value', public: true } }
- let(:pipeline_pre_var) { { key: 'pipeline', value: 'value', public: true } }
- let(:build_yaml_var) { { key: 'yaml', value: 'value', public: true } }
+ let(:build_pre_var) { { key: 'build', value: 'value', public: true, masked: false } }
+ let(:project_pre_var) { { key: 'project', value: 'value', public: true, masked: false } }
+ let(:pipeline_pre_var) { { key: 'pipeline', value: 'value', public: true, masked: false } }
+ let(:build_yaml_var) { { key: 'yaml', value: 'value', public: true, masked: false } }
before do
allow(build).to receive(:predefined_variables) { [build_pre_var] }
@@ -2199,7 +2200,7 @@ describe Ci::Build do
project_pre_var,
pipeline_pre_var,
build_yaml_var,
- { key: 'secret', value: 'value', public: false }])
+ { key: 'secret', value: 'value', public: false, masked: false }])
end
end
@@ -2232,10 +2233,10 @@ describe Ci::Build do
context 'when build has user' do
let(:user_variables) do
[
- { key: 'GITLAB_USER_ID', value: user.id.to_s, public: true },
- { key: 'GITLAB_USER_EMAIL', value: user.email, public: true },
- { key: 'GITLAB_USER_LOGIN', value: user.username, public: true },
- { key: 'GITLAB_USER_NAME', value: user.name, public: true }
+ { key: 'GITLAB_USER_ID', value: user.id.to_s, public: true, masked: false },
+ { key: 'GITLAB_USER_EMAIL', value: user.email, public: true, masked: false },
+ { key: 'GITLAB_USER_LOGIN', value: user.username, public: true, masked: false },
+ { key: 'GITLAB_USER_NAME', value: user.name, public: true, masked: false }
]
end
@@ -2249,8 +2250,8 @@ describe Ci::Build do
context 'when build has an environment' do
let(:environment_variables) do
[
- { key: 'CI_ENVIRONMENT_NAME', value: 'production', public: true },
- { key: 'CI_ENVIRONMENT_SLUG', value: 'prod-slug', public: true }
+ { key: 'CI_ENVIRONMENT_NAME', value: 'production', public: true, masked: false },
+ { key: 'CI_ENVIRONMENT_SLUG', value: 'prod-slug', public: true, masked: false }
]
end
@@ -2285,7 +2286,7 @@ describe Ci::Build do
before do
environment_variables <<
- { key: 'CI_ENVIRONMENT_URL', value: url, public: true }
+ { key: 'CI_ENVIRONMENT_URL', value: url, public: true, masked: false }
end
context 'when the URL was set from the job' do
@@ -2322,7 +2323,7 @@ describe Ci::Build do
end
let(:manual_variable) do
- { key: 'CI_JOB_MANUAL', value: 'true', public: true }
+ { key: 'CI_JOB_MANUAL', value: 'true', public: true, masked: false }
end
it { is_expected.to include(manual_variable) }
@@ -2330,7 +2331,7 @@ describe Ci::Build do
context 'when build is for tag' do
let(:tag_variable) do
- { key: 'CI_COMMIT_TAG', value: 'master', public: true }
+ { key: 'CI_COMMIT_TAG', value: 'master', public: true, masked: false }
end
before do
@@ -2342,7 +2343,7 @@ describe Ci::Build do
context 'when CI variable is defined' do
let(:ci_variable) do
- { key: 'SECRET_KEY', value: 'secret_value', public: false }
+ { key: 'SECRET_KEY', value: 'secret_value', public: false, masked: false }
end
before do
@@ -2357,7 +2358,7 @@ describe Ci::Build do
let(:ref) { Gitlab::Git::BRANCH_REF_PREFIX + build.ref }
let(:protected_variable) do
- { key: 'PROTECTED_KEY', value: 'protected_value', public: false }
+ { key: 'PROTECTED_KEY', value: 'protected_value', public: false, masked: false }
end
before do
@@ -2389,7 +2390,7 @@ describe Ci::Build do
context 'when group CI variable is defined' do
let(:ci_variable) do
- { key: 'SECRET_KEY', value: 'secret_value', public: false }
+ { key: 'SECRET_KEY', value: 'secret_value', public: false, masked: false }
end
before do
@@ -2404,7 +2405,7 @@ describe Ci::Build do
let(:ref) { Gitlab::Git::BRANCH_REF_PREFIX + build.ref }
let(:protected_variable) do
- { key: 'PROTECTED_KEY', value: 'protected_value', public: false }
+ { key: 'PROTECTED_KEY', value: 'protected_value', public: false, masked: false }
end
before do
@@ -2443,11 +2444,11 @@ describe Ci::Build do
let(:trigger_request) { create(:ci_trigger_request, pipeline: pipeline, trigger: trigger) }
let(:user_trigger_variable) do
- { key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1', public: false }
+ { key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1', public: false, masked: false }
end
let(:predefined_trigger_variable) do
- { key: 'CI_PIPELINE_TRIGGERED', value: 'true', public: true }
+ { key: 'CI_PIPELINE_TRIGGERED', value: 'true', public: true, masked: false }
end
before do
@@ -2479,7 +2480,7 @@ describe Ci::Build do
context 'when pipeline has a variable' do
let!(:pipeline_variable) { create(:ci_pipeline_variable, pipeline: pipeline) }
- it { is_expected.to include(pipeline_variable.to_runner_variable) }
+ it { is_expected.to include(key: pipeline_variable.key, value: pipeline_variable.value, public: false, masked: false) }
end
context 'when a job was triggered by a pipeline schedule' do
@@ -2496,16 +2497,16 @@ describe Ci::Build do
pipeline_schedule.reload
end
- it { is_expected.to include(pipeline_schedule_variable.to_runner_variable) }
+ it { is_expected.to include(key: pipeline_schedule_variable.key, value: pipeline_schedule_variable.value, public: false, masked: false) }
end
context 'when container registry is enabled' do
let(:container_registry_enabled) { true }
let(:ci_registry) do
- { key: 'CI_REGISTRY', value: 'registry.example.com', public: true }
+ { key: 'CI_REGISTRY', value: 'registry.example.com', public: true, masked: false }
end
let(:ci_registry_image) do
- { key: 'CI_REGISTRY_IMAGE', value: project.container_registry_url, public: true }
+ { key: 'CI_REGISTRY_IMAGE', value: project.container_registry_url, public: true, masked: false }
end
context 'and is disabled for project' do
@@ -2534,13 +2535,13 @@ describe Ci::Build do
build.update(runner: runner)
end
- it { is_expected.to include({ key: 'CI_RUNNER_ID', value: runner.id.to_s, public: true }) }
- it { is_expected.to include({ key: 'CI_RUNNER_DESCRIPTION', value: 'description', public: true }) }
- it { is_expected.to include({ key: 'CI_RUNNER_TAGS', value: 'docker, linux', public: true }) }
+ it { is_expected.to include({ key: 'CI_RUNNER_ID', value: runner.id.to_s, public: true, masked: false }) }
+ it { is_expected.to include({ key: 'CI_RUNNER_DESCRIPTION', value: 'description', public: true, masked: false }) }
+ it { is_expected.to include({ key: 'CI_RUNNER_TAGS', value: 'docker, linux', public: true, masked: false }) }
end
context 'when build is for a deployment' do
- let(:deployment_variable) { { key: 'KUBERNETES_TOKEN', value: 'TOKEN', public: false } }
+ let(:deployment_variable) { { key: 'KUBERNETES_TOKEN', value: 'TOKEN', public: false, masked: false } }
before do
build.environment = 'production'
@@ -2554,7 +2555,7 @@ describe Ci::Build do
end
context 'when project has custom CI config path' do
- let(:ci_config_path) { { key: 'CI_CONFIG_PATH', value: 'custom', public: true } }
+ let(:ci_config_path) { { key: 'CI_CONFIG_PATH', value: 'custom', public: true, masked: false } }
before do
project.update(ci_config_path: 'custom')
@@ -2571,7 +2572,7 @@ describe Ci::Build do
it "includes AUTO_DEVOPS_DOMAIN" do
is_expected.to include(
- { key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true })
+ { key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true, masked: false })
end
end
@@ -2582,7 +2583,7 @@ describe Ci::Build do
it "includes AUTO_DEVOPS_DOMAIN" do
is_expected.not_to include(
- { key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true })
+ { key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true, masked: false })
end
end
end
@@ -2597,9 +2598,9 @@ describe Ci::Build do
variables = subject.reverse.uniq { |variable| variable[:key] }.reverse
expect(variables)
- .not_to include(key: 'MYVAR', value: 'myvar', public: true)
+ .not_to include(key: 'MYVAR', value: 'myvar', public: true, masked: false)
expect(variables)
- .to include(key: 'MYVAR', value: 'pipeline value', public: false)
+ .to include(key: 'MYVAR', value: 'pipeline value', public: false, masked: false)
end
end
@@ -2615,13 +2616,13 @@ describe Ci::Build do
it 'includes CI_NODE_INDEX' do
is_expected.to include(
- { key: 'CI_NODE_INDEX', value: index.to_s, public: true }
+ { key: 'CI_NODE_INDEX', value: index.to_s, public: true, masked: false }
)
end
it 'includes correct CI_NODE_TOTAL' do
is_expected.to include(
- { key: 'CI_NODE_TOTAL', value: total.to_s, public: true }
+ { key: 'CI_NODE_TOTAL', value: total.to_s, public: true, masked: false }
)
end
end
@@ -2640,7 +2641,7 @@ describe Ci::Build do
it 'returns static predefined variables' do
expect(build.variables.size).to be >= 28
expect(build.variables)
- .to include(key: 'CI_COMMIT_REF_NAME', value: 'feature', public: true)
+ .to include(key: 'CI_COMMIT_REF_NAME', value: 'feature', public: true, masked: false)
expect(build).not_to be_persisted
end
end
@@ -2650,8 +2651,8 @@ describe Ci::Build do
let(:deploy_token_variables) do
[
- { key: 'CI_DEPLOY_USER', value: deploy_token.username, public: true },
- { key: 'CI_DEPLOY_PASSWORD', value: deploy_token.token, public: false }
+ { key: 'CI_DEPLOY_USER', value: deploy_token.username, public: true, masked: false },
+ { key: 'CI_DEPLOY_PASSWORD', value: deploy_token.token, public: false, masked: false }
]
end
@@ -2710,7 +2711,7 @@ describe Ci::Build do
end
expect(variables)
- .to include(key: 'CI_COMMIT_REF_NAME', value: 'feature', public: true)
+ .to include(key: 'CI_COMMIT_REF_NAME', value: 'feature', public: true, masked: false)
end
it 'does not return prohibited variables' do
@@ -2733,6 +2734,122 @@ describe Ci::Build do
end
end
+ describe '#secret_group_variables' do
+ subject { build.secret_group_variables }
+
+ let!(:variable) { create(:ci_group_variable, protected: true, group: group) }
+
+ context 'when ref is branch' do
+ let(:build) { create(:ci_build, ref: 'master', tag: false, project: project) }
+
+ context 'when ref is protected' do
+ before do
+ create(:protected_branch, :developers_can_merge, name: 'master', project: project)
+ end
+
+ it { is_expected.to include(variable) }
+ end
+
+ context 'when ref is not protected' do
+ it { is_expected.not_to include(variable) }
+ end
+ end
+
+ context 'when ref is tag' do
+ let(:build) { create(:ci_build, ref: 'v1.1.0', tag: true, project: project) }
+
+ context 'when ref is protected' do
+ before do
+ create(:protected_tag, project: project, name: 'v*')
+ end
+
+ it { is_expected.to include(variable) }
+ end
+
+ context 'when ref is not protected' do
+ it { is_expected.not_to include(variable) }
+ end
+ end
+
+ context 'when ref is merge request' do
+ let(:merge_request) { create(:merge_request, :with_merge_request_pipeline) }
+ let(:pipeline) { merge_request.merge_request_pipelines.first }
+ let(:build) { create(:ci_build, ref: merge_request.source_branch, tag: false, pipeline: pipeline, project: project) }
+
+ context 'when ref is protected' do
+ before do
+ create(:protected_branch, :developers_can_merge, name: merge_request.source_branch, project: project)
+ end
+
+ it 'does not return protected variables as it is not supported for merge request pipelines' do
+ is_expected.not_to include(variable)
+ end
+ end
+
+ context 'when ref is not protected' do
+ it { is_expected.not_to include(variable) }
+ end
+ end
+ end
+
+ describe '#secret_project_variables' do
+ subject { build.secret_project_variables }
+
+ let!(:variable) { create(:ci_variable, protected: true, project: project) }
+
+ context 'when ref is branch' do
+ let(:build) { create(:ci_build, ref: 'master', tag: false, project: project) }
+
+ context 'when ref is protected' do
+ before do
+ create(:protected_branch, :developers_can_merge, name: 'master', project: project)
+ end
+
+ it { is_expected.to include(variable) }
+ end
+
+ context 'when ref is not protected' do
+ it { is_expected.not_to include(variable) }
+ end
+ end
+
+ context 'when ref is tag' do
+ let(:build) { create(:ci_build, ref: 'v1.1.0', tag: true, project: project) }
+
+ context 'when ref is protected' do
+ before do
+ create(:protected_tag, project: project, name: 'v*')
+ end
+
+ it { is_expected.to include(variable) }
+ end
+
+ context 'when ref is not protected' do
+ it { is_expected.not_to include(variable) }
+ end
+ end
+
+ context 'when ref is merge request' do
+ let(:merge_request) { create(:merge_request, :with_merge_request_pipeline) }
+ let(:pipeline) { merge_request.merge_request_pipelines.first }
+ let(:build) { create(:ci_build, ref: merge_request.source_branch, tag: false, pipeline: pipeline, project: project) }
+
+ context 'when ref is protected' do
+ before do
+ create(:protected_branch, :developers_can_merge, name: merge_request.source_branch, project: project)
+ end
+
+ it 'does not return protected variables as it is not supported for merge request pipelines' do
+ is_expected.not_to include(variable)
+ end
+ end
+
+ context 'when ref is not protected' do
+ it { is_expected.not_to include(variable) }
+ end
+ end
+ end
+
describe '#scoped_variables_hash' do
context 'when overriding CI variables' do
before do
diff --git a/spec/models/ci/build_trace_chunk_spec.rb b/spec/models/ci/build_trace_chunk_spec.rb
index d214fdf369a..59db347582b 100644
--- a/spec/models/ci/build_trace_chunk_spec.rb
+++ b/spec/models/ci/build_trace_chunk_spec.rb
@@ -171,7 +171,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
end
shared_examples_for 'Scheduling sidekiq worker to flush data to persist store' do
- context 'when new data fullfilled chunk size' do
+ context 'when new data fulfilled chunk size' do
let(:new_data) { 'a' * described_class::CHUNK_SIZE }
it 'schedules trace chunk flush worker' do
@@ -193,7 +193,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
end
shared_examples_for 'Scheduling no sidekiq worker' do
- context 'when new data fullfilled chunk size' do
+ context 'when new data fulfilled chunk size' do
let(:new_data) { 'a' * described_class::CHUNK_SIZE }
it 'does not schedule trace chunk flush worker' do
diff --git a/spec/models/ci/group_variable_spec.rb b/spec/models/ci/group_variable_spec.rb
index 1b10501701c..21d96bf3454 100644
--- a/spec/models/ci/group_variable_spec.rb
+++ b/spec/models/ci/group_variable_spec.rb
@@ -5,6 +5,7 @@ describe Ci::GroupVariable do
it { is_expected.to include_module(HasVariable) }
it { is_expected.to include_module(Presentable) }
+ it { is_expected.to include_module(Maskable) }
it { is_expected.to validate_uniqueness_of(:key).scoped_to(:group_id).with_message(/\(\w+\) has already been taken/) }
describe '.unprotected' do
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 72a0df96a80..462793b259b 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe Ci::Pipeline, :mailer do
+ include ProjectForksHelper
+
let(:user) { create(:user) }
set(:project) { create(:project) }
@@ -22,6 +24,7 @@ describe Ci::Pipeline, :mailer do
it { is_expected.to have_many(:builds) }
it { is_expected.to have_many(:auto_canceled_pipelines) }
it { is_expected.to have_many(:auto_canceled_jobs) }
+ it { is_expected.to have_one(:chat_data) }
it { is_expected.to validate_presence_of(:sha) }
it { is_expected.to validate_presence_of(:status) }
@@ -77,11 +80,11 @@ describe Ci::Pipeline, :mailer do
context 'when merge request pipelines exist' do
let!(:merge_request_pipeline_1) do
- create(:ci_pipeline, source: :merge_request, merge_request: merge_request)
+ create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request)
end
let!(:merge_request_pipeline_2) do
- create(:ci_pipeline, source: :merge_request, merge_request: merge_request)
+ create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request)
end
let(:merge_request) do
@@ -103,11 +106,11 @@ describe Ci::Pipeline, :mailer do
let!(:branch_pipeline_2) { create(:ci_pipeline, source: :push) }
let!(:merge_request_pipeline_1) do
- create(:ci_pipeline, source: :merge_request, merge_request: merge_request)
+ create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request)
end
let!(:merge_request_pipeline_2) do
- create(:ci_pipeline, source: :merge_request, merge_request: merge_request)
+ create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request)
end
let(:merge_request) do
@@ -127,11 +130,137 @@ describe Ci::Pipeline, :mailer do
end
end
- describe '.merge_request' do
- subject { described_class.merge_request }
+ describe '.detached_merge_request_pipelines' do
+ subject { described_class.detached_merge_request_pipelines(merge_request) }
+
+ let!(:pipeline) do
+ create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, target_sha: target_sha)
+ end
+
+ let(:merge_request) { create(:merge_request) }
+ let(:target_sha) { nil }
+
+ it 'returns detached merge request pipelines' do
+ is_expected.to eq([pipeline])
+ end
+
+ context 'when target sha exists' do
+ let(:target_sha) { merge_request.target_branch_sha }
+
+ it 'returns empty array' do
+ is_expected.to be_empty
+ end
+ end
+ end
+
+ describe '#detached_merge_request_pipeline?' do
+ subject { pipeline.detached_merge_request_pipeline? }
+
+ let!(:pipeline) do
+ create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, target_sha: target_sha)
+ end
+
+ let(:merge_request) { create(:merge_request) }
+ let(:target_sha) { nil }
+
+ it { is_expected.to be_truthy }
+
+ context 'when target sha exists' do
+ let(:target_sha) { merge_request.target_branch_sha }
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
+ describe '.merge_request_pipelines' do
+ subject { described_class.merge_request_pipelines(merge_request) }
+
+ let!(:pipeline) do
+ create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, target_sha: target_sha)
+ end
+
+ let(:merge_request) { create(:merge_request) }
+ let(:target_sha) { merge_request.target_branch_sha }
+
+ it 'returns merge pipelines' do
+ is_expected.to eq([pipeline])
+ end
+
+ context 'when target sha is empty' do
+ let(:target_sha) { nil }
+
+ it 'returns empty array' do
+ is_expected.to be_empty
+ end
+ end
+ end
+
+ describe '#merge_request_pipeline?' do
+ subject { pipeline.merge_request_pipeline? }
+
+ let!(:pipeline) do
+ create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, target_sha: target_sha)
+ end
+
+ let(:merge_request) { create(:merge_request) }
+ let(:target_sha) { merge_request.target_branch_sha }
+
+ it { is_expected.to be_truthy }
+
+ context 'when target sha is empty' do
+ let(:target_sha) { nil }
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
+ describe '.mergeable_merge_request_pipelines' do
+ subject { described_class.mergeable_merge_request_pipelines(merge_request) }
+
+ let!(:pipeline) do
+ create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, target_sha: target_sha)
+ end
+
+ let(:merge_request) { create(:merge_request) }
+ let(:target_sha) { merge_request.target_branch_sha }
+
+ it 'returns mergeable merge pipelines' do
+ is_expected.to eq([pipeline])
+ end
+
+ context 'when target sha does not point the head of the target branch' do
+ let(:target_sha) { merge_request.diff_head_sha }
+
+ it 'returns empty array' do
+ is_expected.to be_empty
+ end
+ end
+ end
+
+ describe '#mergeable_merge_request_pipeline?' do
+ subject { pipeline.mergeable_merge_request_pipeline? }
+
+ let!(:pipeline) do
+ create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, target_sha: target_sha)
+ end
+
+ let(:merge_request) { create(:merge_request) }
+ let(:target_sha) { merge_request.target_branch_sha }
+
+ it { is_expected.to be_truthy }
+
+ context 'when target sha does not point the head of the target branch' do
+ let(:target_sha) { merge_request.diff_head_sha }
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
+ describe '.merge_request_event' do
+ subject { described_class.merge_request_event }
context 'when there is a merge request pipeline' do
- let!(:pipeline) { create(:ci_pipeline, source: :merge_request, merge_request: merge_request) }
+ let!(:pipeline) { create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request) }
let(:merge_request) { create(:merge_request) }
it 'returns merge request pipeline first' do
@@ -152,7 +281,7 @@ describe Ci::Pipeline, :mailer do
let(:pipeline) { build(:ci_pipeline, source: source, merge_request: merge_request) }
context 'when source is merge request' do
- let(:source) { :merge_request }
+ let(:source) { :merge_request_event }
context 'when merge request is specified' do
let(:merge_request) { create(:merge_request, source_project: project, source_branch: 'feature', target_project: project, target_branch: 'master') }
@@ -376,7 +505,7 @@ describe Ci::Pipeline, :mailer do
context 'when source is merge request' do
let(:pipeline) do
- create(:ci_pipeline, source: :merge_request, merge_request: merge_request)
+ create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request)
end
let(:merge_request) do
@@ -397,10 +526,12 @@ describe Ci::Pipeline, :mailer do
'CI_MERGE_REQUEST_PROJECT_PATH' => merge_request.project.full_path,
'CI_MERGE_REQUEST_PROJECT_URL' => merge_request.project.web_url,
'CI_MERGE_REQUEST_TARGET_BRANCH_NAME' => merge_request.target_branch.to_s,
+ 'CI_MERGE_REQUEST_TARGET_BRANCH_SHA' => pipeline.target_sha.to_s,
'CI_MERGE_REQUEST_SOURCE_PROJECT_ID' => merge_request.source_project.id.to_s,
'CI_MERGE_REQUEST_SOURCE_PROJECT_PATH' => merge_request.source_project.full_path,
'CI_MERGE_REQUEST_SOURCE_PROJECT_URL' => merge_request.source_project.web_url,
- 'CI_MERGE_REQUEST_SOURCE_BRANCH_NAME' => merge_request.source_branch.to_s)
+ 'CI_MERGE_REQUEST_SOURCE_BRANCH_NAME' => merge_request.source_branch.to_s,
+ 'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA' => pipeline.source_sha.to_s)
end
context 'when source project does not exist' do
@@ -966,7 +1097,7 @@ describe Ci::Pipeline, :mailer do
context 'when source is merge request' do
let(:pipeline) do
- create(:ci_pipeline, source: :merge_request, merge_request: merge_request)
+ create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request)
end
let(:merge_request) do
@@ -1016,7 +1147,7 @@ describe Ci::Pipeline, :mailer do
context 'when ref is merge request' do
let(:pipeline) do
create(:ci_pipeline,
- source: :merge_request,
+ source: :merge_request_event,
merge_request: merge_request)
end
@@ -1172,8 +1303,26 @@ describe Ci::Pipeline, :mailer do
pipeline.update_column(:before_sha, Gitlab::Git::BLANK_SHA)
end
- it 'raises an error' do
- expect { pipeline.modified_paths }.to raise_error(ArgumentError)
+ it 'returns nil' do
+ expect(pipeline.modified_paths).to be_nil
+ end
+ end
+
+ context 'when source is merge request' do
+ let(:pipeline) do
+ create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request)
+ end
+
+ let(:merge_request) do
+ create(:merge_request,
+ source_project: project,
+ source_branch: 'feature',
+ target_project: project,
+ target_branch: 'master')
+ end
+
+ it 'returns merge request modified paths' do
+ expect(pipeline.modified_paths).to match(merge_request.modified_paths)
end
end
end
@@ -2095,66 +2244,81 @@ describe Ci::Pipeline, :mailer do
describe "#all_merge_requests" do
let(:project) { create(:project) }
- let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master') }
- it "returns all merge requests having the same source branch" do
- merge_request = create(:merge_request, source_project: project, source_branch: pipeline.ref)
-
- expect(pipeline.all_merge_requests).to eq([merge_request])
- end
+ shared_examples 'a method that returns all merge requests for a given pipeline' do
+ let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: pipeline_project, ref: 'master') }
- it "doesn't return merge requests having a different source branch" do
- create(:merge_request, source_project: project, source_branch: 'feature', target_branch: 'master')
-
- expect(pipeline.all_merge_requests).to be_empty
- end
+ it "returns all merge requests having the same source branch" do
+ merge_request = create(:merge_request, source_project: pipeline_project, target_project: project, source_branch: pipeline.ref)
- context 'when there is a merge request pipeline' do
- let(:source_branch) { 'feature' }
- let(:target_branch) { 'master' }
-
- let!(:pipeline) do
- create(:ci_pipeline,
- source: :merge_request,
- project: project,
- ref: source_branch,
- merge_request: merge_request)
+ expect(pipeline.all_merge_requests).to eq([merge_request])
end
- let(:merge_request) do
- create(:merge_request,
- source_project: project,
- source_branch: source_branch,
- target_project: project,
- target_branch: target_branch)
- end
+ it "doesn't return merge requests having a different source branch" do
+ create(:merge_request, source_project: pipeline_project, target_project: project, source_branch: 'feature', target_branch: 'master')
- it 'returns an associated merge request' do
- expect(pipeline.all_merge_requests).to eq([merge_request])
+ expect(pipeline.all_merge_requests).to be_empty
end
- context 'when there is another merge request pipeline that targets a different branch' do
- let(:target_branch_2) { 'merge-test' }
+ context 'when there is a merge request pipeline' do
+ let(:source_branch) { 'feature' }
+ let(:target_branch) { 'master' }
- let!(:pipeline_2) do
+ let!(:pipeline) do
create(:ci_pipeline,
- source: :merge_request,
- project: project,
+ source: :merge_request_event,
+ project: pipeline_project,
ref: source_branch,
- merge_request: merge_request_2)
+ merge_request: merge_request)
end
- let(:merge_request_2) do
+ let(:merge_request) do
create(:merge_request,
- source_project: project,
+ source_project: pipeline_project,
source_branch: source_branch,
target_project: project,
- target_branch: target_branch_2)
+ target_branch: target_branch)
end
- it 'does not return an associated merge request' do
- expect(pipeline.all_merge_requests).not_to include(merge_request_2)
+ it 'returns an associated merge request' do
+ expect(pipeline.all_merge_requests).to eq([merge_request])
end
+
+ context 'when there is another merge request pipeline that targets a different branch' do
+ let(:target_branch_2) { 'merge-test' }
+
+ let!(:pipeline_2) do
+ create(:ci_pipeline,
+ source: :merge_request_event,
+ project: pipeline_project,
+ ref: source_branch,
+ merge_request: merge_request_2)
+ end
+
+ let(:merge_request_2) do
+ create(:merge_request,
+ source_project: pipeline_project,
+ source_branch: source_branch,
+ target_project: project,
+ target_branch: target_branch_2)
+ end
+
+ it 'does not return an associated merge request' do
+ expect(pipeline.all_merge_requests).not_to include(merge_request_2)
+ end
+ end
+ end
+ end
+
+ it_behaves_like 'a method that returns all merge requests for a given pipeline' do
+ let(:pipeline_project) { project }
+ end
+
+ context 'for a fork' do
+ let(:fork) { fork_project(project) }
+
+ it_behaves_like 'a method that returns all merge requests for a given pipeline' do
+ let(:pipeline_project) { fork }
end
end
end
diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb
index 875e8b2b682..02c07a2bd83 100644
--- a/spec/models/ci/variable_spec.rb
+++ b/spec/models/ci/variable_spec.rb
@@ -6,6 +6,7 @@ describe Ci::Variable do
describe 'validations' do
it { is_expected.to include_module(HasVariable) }
it { is_expected.to include_module(Presentable) }
+ it { is_expected.to include_module(Maskable) }
it { is_expected.to validate_uniqueness_of(:key).scoped_to(:project_id, :environment_scope).with_message(/\(\w+\) has already been taken/) }
end
diff --git a/spec/models/clusters/applications/helm_spec.rb b/spec/models/clusters/applications/helm_spec.rb
index 64f6d9c8bb4..f16eff92167 100644
--- a/spec/models/clusters/applications/helm_spec.rb
+++ b/spec/models/clusters/applications/helm_spec.rb
@@ -3,16 +3,17 @@ require 'rails_helper'
describe Clusters::Applications::Helm do
include_examples 'cluster application core specs', :clusters_applications_helm
- describe '.installed' do
- subject { described_class.installed }
+ describe '.available' do
+ subject { described_class.available }
let!(:installed_cluster) { create(:clusters_applications_helm, :installed) }
+ let!(:updated_cluster) { create(:clusters_applications_helm, :updated) }
before do
create(:clusters_applications_helm, :errored)
end
- it { is_expected.to contain_exactly(installed_cluster) }
+ it { is_expected.to contain_exactly(installed_cluster, updated_cluster) }
end
describe '#issue_client_cert' do
diff --git a/spec/models/clusters/applications/ingress_spec.rb b/spec/models/clusters/applications/ingress_spec.rb
index 03ca18c6943..d5fd42509a3 100644
--- a/spec/models/clusters/applications/ingress_spec.rb
+++ b/spec/models/clusters/applications/ingress_spec.rb
@@ -16,18 +16,6 @@ describe Clusters::Applications::Ingress do
allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_async)
end
- describe '.installed' do
- subject { described_class.installed }
-
- let!(:cluster) { create(:clusters_applications_ingress, :installed) }
-
- before do
- create(:clusters_applications_ingress, :errored)
- end
-
- it { is_expected.to contain_exactly(cluster) }
- end
-
describe '#make_installed!' do
before do
application.make_installed!
diff --git a/spec/models/clusters/applications/knative_spec.rb b/spec/models/clusters/applications/knative_spec.rb
index cd29e0d4f53..006b922ab27 100644
--- a/spec/models/clusters/applications/knative_spec.rb
+++ b/spec/models/clusters/applications/knative_spec.rb
@@ -24,30 +24,6 @@ describe Clusters::Applications::Knative do
it { expect(knative_no_rbac).to be_not_installable }
end
- describe '.installed' do
- subject { described_class.installed }
-
- let!(:cluster) { create(:clusters_applications_knative, :installed) }
-
- before do
- create(:clusters_applications_knative, :errored)
- end
-
- it { is_expected.to contain_exactly(cluster) }
- end
-
- describe '#make_installed' do
- subject { described_class.installed }
-
- let!(:cluster) { create(:clusters_applications_knative, :installed) }
-
- before do
- create(:clusters_applications_knative, :errored)
- end
-
- it { is_expected.to contain_exactly(cluster) }
- end
-
describe 'make_installed with external_ip' do
before do
application.make_installed!
diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb
index caf59b0fc31..81708b0c2ed 100644
--- a/spec/models/clusters/applications/prometheus_spec.rb
+++ b/spec/models/clusters/applications/prometheus_spec.rb
@@ -9,18 +9,6 @@ describe Clusters::Applications::Prometheus do
include_examples 'cluster application helm specs', :clusters_applications_prometheus
include_examples 'cluster application initial status specs'
- describe '.installed' do
- subject { described_class.installed }
-
- let!(:cluster) { create(:clusters_applications_prometheus, :installed) }
-
- before do
- create(:clusters_applications_prometheus, :errored)
- end
-
- it { is_expected.to contain_exactly(cluster) }
- end
-
describe 'transition to installed' do
let(:project) { create(:project) }
let(:cluster) { create(:cluster, :with_installed_helm, projects: [project]) }
@@ -39,65 +27,6 @@ describe Clusters::Applications::Prometheus do
end
end
- describe '#ready' do
- let(:project) { create(:project) }
- let(:cluster) { create(:cluster, projects: [project]) }
-
- it 'returns true when installed' do
- application = build(:clusters_applications_prometheus, :installed, cluster: cluster)
-
- expect(application).to be_ready
- end
-
- it 'returns false when not_installable' do
- application = build(:clusters_applications_prometheus, :not_installable, cluster: cluster)
-
- expect(application).not_to be_ready
- end
-
- it 'returns false when installable' do
- application = build(:clusters_applications_prometheus, :installable, cluster: cluster)
-
- expect(application).not_to be_ready
- end
-
- it 'returns false when scheduled' do
- application = build(:clusters_applications_prometheus, :scheduled, cluster: cluster)
-
- expect(application).not_to be_ready
- end
-
- it 'returns false when installing' do
- application = build(:clusters_applications_prometheus, :installing, cluster: cluster)
-
- expect(application).not_to be_ready
- end
-
- it 'returns false when errored' do
- application = build(:clusters_applications_prometheus, :errored, cluster: cluster)
-
- expect(application).not_to be_ready
- end
-
- it 'returns true when updating' do
- application = build(:clusters_applications_prometheus, :updating, cluster: cluster)
-
- expect(application).to be_ready
- end
-
- it 'returns true when updated' do
- application = build(:clusters_applications_prometheus, :updated, cluster: cluster)
-
- expect(application).to be_ready
- end
-
- it 'returns true when errored' do
- application = build(:clusters_applications_prometheus, :update_errored, cluster: cluster)
-
- expect(application).to be_ready
- end
- end
-
describe '#prometheus_client' do
context 'cluster is nil' do
it 'returns nil' do
@@ -192,7 +121,7 @@ describe Clusters::Applications::Prometheus do
end
context 'with knative installed' do
- let(:knative) { create(:clusters_applications_knative, :installed ) }
+ let(:knative) { create(:clusters_applications_knative, :updated ) }
let(:prometheus) { create(:clusters_applications_prometheus, cluster: knative.cluster) }
subject { prometheus.install_command }
diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb
index 38758ff97bc..6972fc03415 100644
--- a/spec/models/clusters/applications/runner_spec.rb
+++ b/spec/models/clusters/applications/runner_spec.rb
@@ -11,18 +11,6 @@ describe Clusters::Applications::Runner do
it { is_expected.to belong_to(:runner) }
- describe '.installed' do
- subject { described_class.installed }
-
- let!(:cluster) { create(:clusters_applications_runner, :installed) }
-
- before do
- create(:clusters_applications_runner, :errored)
- end
-
- it { is_expected.to contain_exactly(cluster) }
- end
-
describe '#install_command' do
let(:kubeclient) { double('kubernetes client') }
let(:gitlab_runner) { create(:clusters_applications_runner, runner: ci_runner) }
@@ -34,7 +22,7 @@ describe Clusters::Applications::Runner do
it 'should be initialized with 4 arguments' do
expect(subject.name).to eq('runner')
expect(subject.chart).to eq('runner/gitlab-runner')
- expect(subject.version).to eq('0.1.45')
+ expect(subject.version).to eq('0.2.0')
expect(subject).to be_rbac
expect(subject.repository).to eq('https://charts.gitlab.io')
expect(subject.files).to eq(gitlab_runner.files)
@@ -52,7 +40,7 @@ describe Clusters::Applications::Runner do
let(:gitlab_runner) { create(:clusters_applications_runner, :errored, runner: ci_runner, version: '0.1.13') }
it 'should be initialized with the locked version' do
- expect(subject.version).to eq('0.1.45')
+ expect(subject.version).to eq('0.2.0')
end
end
end
diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb
index 92ce2b0999a..3feed4e9718 100644
--- a/spec/models/clusters/cluster_spec.rb
+++ b/spec/models/clusters/cluster_spec.rb
@@ -265,12 +265,12 @@ describe Clusters::Cluster do
it { is_expected.to be_valid }
end
- context 'when cluster has an invalid domain' do
- let(:cluster) { build(:cluster, domain: 'not-valid-domain') }
+ context 'when cluster is not a valid hostname' do
+ let(:cluster) { build(:cluster, domain: 'http://not.a.valid.hostname') }
it 'should add an error on domain' do
expect(subject).not_to be_valid
- expect(subject.errors[:domain].first).to eq('is not a fully qualified domain name')
+ expect(subject.errors[:domain].first).to eq('contains invalid characters (valid characters: [a-z0-9\\-])')
end
end
diff --git a/spec/models/commit_collection_spec.rb b/spec/models/commit_collection_spec.rb
index 12e59b35428..0f5d03ff458 100644
--- a/spec/models/commit_collection_spec.rb
+++ b/spec/models/commit_collection_spec.rb
@@ -12,26 +12,26 @@ describe CommitCollection do
end
end
- describe '.committers' do
+ describe '.authors' do
it 'returns a relation of users when users are found' do
- user = create(:user, email: commit.committer_email.upcase)
+ user = create(:user, email: commit.author_email.upcase)
collection = described_class.new(project, [commit])
- expect(collection.committers).to contain_exactly(user)
+ expect(collection.authors).to contain_exactly(user)
end
- it 'returns empty array when committers cannot be found' do
+ it 'returns empty array when authors cannot be found' do
collection = described_class.new(project, [commit])
- expect(collection.committers).to be_empty
+ expect(collection.authors).to be_empty
end
it 'excludes authors of merge commits' do
commit = project.commit("60ecb67744cb56576c30214ff52294f8ce2def98")
- create(:user, email: commit.committer_email.upcase)
+ create(:user, email: commit.author_email.upcase)
collection = described_class.new(project, [commit])
- expect(collection.committers).to be_empty
+ expect(collection.authors).to be_empty
end
end
diff --git a/spec/models/concerns/has_ref_spec.rb b/spec/models/concerns/has_ref_spec.rb
index 8aed72d77a4..8aa2fecb18c 100644
--- a/spec/models/concerns/has_ref_spec.rb
+++ b/spec/models/concerns/has_ref_spec.rb
@@ -16,6 +16,16 @@ describe HasRef do
it 'return true when tag is set to false' do
is_expected.to be_truthy
end
+
+ context 'when it was triggered by merge request' do
+ let(:merge_request) { create(:merge_request, :with_merge_request_pipeline) }
+ let(:pipeline) { merge_request.merge_request_pipelines.first }
+ let(:build) { create(:ci_build, pipeline: pipeline) }
+
+ it 'returns false' do
+ is_expected.to be_falsy
+ end
+ end
end
context 'is not a tag' do
@@ -55,5 +65,15 @@ describe HasRef do
is_expected.to start_with(Gitlab::Git::BRANCH_REF_PREFIX)
end
end
+
+ context 'when it is triggered by a merge request' do
+ let(:merge_request) { create(:merge_request, :with_merge_request_pipeline) }
+ let(:pipeline) { merge_request.merge_request_pipelines.first }
+ let(:build) { create(:ci_build, tag: false, pipeline: pipeline) }
+
+ it 'returns nil' do
+ is_expected.to be_nil
+ end
+ end
end
end
diff --git a/spec/models/concerns/has_variable_spec.rb b/spec/models/concerns/has_variable_spec.rb
index 3fbe86c5b56..bff96e12ffa 100644
--- a/spec/models/concerns/has_variable_spec.rb
+++ b/spec/models/concerns/has_variable_spec.rb
@@ -57,7 +57,7 @@ describe HasVariable do
describe '#to_runner_variable' do
it 'returns a hash for the runner' do
expect(subject.to_runner_variable)
- .to eq(key: subject.key, value: subject.value, public: false)
+ .to include(key: subject.key, value: subject.value, public: false)
end
end
end
diff --git a/spec/models/concerns/maskable_spec.rb b/spec/models/concerns/maskable_spec.rb
new file mode 100644
index 00000000000..aeba7ad862f
--- /dev/null
+++ b/spec/models/concerns/maskable_spec.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Maskable do
+ let(:variable) { build(:ci_variable) }
+
+ describe 'masked value validations' do
+ subject { variable }
+
+ context 'when variable is masked' do
+ before do
+ subject.masked = true
+ end
+
+ it { is_expected.not_to allow_value('hello').for(:value) }
+ it { is_expected.not_to allow_value('hello world').for(:value) }
+ it { is_expected.not_to allow_value('hello$VARIABLEworld').for(:value) }
+ it { is_expected.not_to allow_value('hello\rworld').for(:value) }
+ it { is_expected.to allow_value('helloworld').for(:value) }
+ end
+
+ context 'when variable is not masked' do
+ before do
+ subject.masked = false
+ end
+
+ it { is_expected.to allow_value('hello').for(:value) }
+ it { is_expected.to allow_value('hello world').for(:value) }
+ it { is_expected.to allow_value('hello$VARIABLEworld').for(:value) }
+ it { is_expected.to allow_value('hello\rworld').for(:value) }
+ it { is_expected.to allow_value('helloworld').for(:value) }
+ end
+ end
+
+ describe 'REGEX' do
+ subject { Maskable::REGEX }
+
+ it 'does not match strings shorter than 8 letters' do
+ expect(subject.match?('hello')).to eq(false)
+ end
+
+ it 'does not match strings with spaces' do
+ expect(subject.match?('hello world')).to eq(false)
+ end
+
+ it 'does not match strings with shell variables' do
+ expect(subject.match?('hello$VARIABLEworld')).to eq(false)
+ end
+
+ it 'does not match strings with escape characters' do
+ expect(subject.match?('hello\rworld')).to eq(false)
+ end
+
+ it 'does not match strings that span more than one line' do
+ string = <<~EOS
+ hello
+ world
+ EOS
+
+ expect(subject.match?(string)).to eq(false)
+ end
+
+ it 'matches valid strings' do
+ expect(subject.match?('helloworld')).to eq(true)
+ end
+ end
+
+ describe '#to_runner_variable' do
+ subject { variable.to_runner_variable }
+
+ it 'exposes the masked attribute' do
+ expect(subject).to include(:masked)
+ end
+ end
+end
diff --git a/spec/models/concerns/reactive_caching_spec.rb b/spec/models/concerns/reactive_caching_spec.rb
index 97a4c212f1c..03ae45e6b17 100644
--- a/spec/models/concerns/reactive_caching_spec.rb
+++ b/spec/models/concerns/reactive_caching_spec.rb
@@ -25,7 +25,7 @@ describe ReactiveCaching, :use_clean_rails_memory_store_caching do
def result
with_reactive_cache do |data|
- data / 2
+ data
end
end
end
@@ -64,7 +64,7 @@ describe ReactiveCaching, :use_clean_rails_memory_store_caching do
stub_reactive_cache(instance, 4)
end
- it { is_expected.to eq(2) }
+ it { is_expected.to eq(4) }
it 'does not enqueue a background worker' do
expect(ReactiveCachingWorker).not_to receive(:perform_async)
@@ -94,6 +94,14 @@ describe ReactiveCaching, :use_clean_rails_memory_store_caching do
end
end
end
+
+ context 'when cache contains non-nil but blank value' do
+ before do
+ stub_reactive_cache(instance, false)
+ end
+
+ it { is_expected.to eq(false) }
+ end
end
describe '#clear_reactive_cache!' do
diff --git a/spec/models/concerns/sortable_spec.rb b/spec/models/concerns/sortable_spec.rb
index 39c16ae60af..0a9d2021a19 100644
--- a/spec/models/concerns/sortable_spec.rb
+++ b/spec/models/concerns/sortable_spec.rb
@@ -99,7 +99,7 @@ describe Sortable do
expect(ordered_group_names('id_desc')).to eq(%w(bbb BB AAA aa))
end
- it 'sorts groups by name via case-insentitive comparision' do
+ it 'sorts groups by name via case-insensitive comparision' do
expect(ordered_group_names('name_asc')).to eq(%w(aa AAA BB bbb))
expect(ordered_group_names('name_desc')).to eq(%w(bbb BB AAA aa))
end
diff --git a/spec/models/concerns/token_authenticatable_spec.rb b/spec/models/concerns/token_authenticatable_spec.rb
index 55d83bc3a6b..40cb4eef60a 100644
--- a/spec/models/concerns/token_authenticatable_spec.rb
+++ b/spec/models/concerns/token_authenticatable_spec.rb
@@ -97,14 +97,31 @@ describe ApplicationSetting, 'TokenAuthenticatable' do
end
describe PersonalAccessToken, 'TokenAuthenticatable' do
- let(:personal_access_token_name) { 'test-pat-01' }
+ shared_examples 'changes personal access token' do
+ it 'sets new token' do
+ subject
+
+ expect(personal_access_token.token).to eq(token_value)
+ expect(personal_access_token.token_digest).to eq(Gitlab::CryptoHelper.sha256(token_value))
+ end
+ end
+
+ shared_examples 'does not change personal access token' do
+ it 'sets new token' do
+ subject
+
+ expect(personal_access_token.token).to be(nil)
+ expect(personal_access_token.token_digest).to eq(token_digest)
+ end
+ end
+
let(:token_value) { 'token' }
+ let(:token_digest) { Gitlab::CryptoHelper.sha256(token_value) }
let(:user) { create(:user) }
let(:personal_access_token) do
- described_class.new(name: personal_access_token_name,
+ described_class.new(name: 'test-pat-01',
user_id: user.id,
scopes: [:api],
- token: token,
token_digest: token_digest)
end
@@ -115,239 +132,71 @@ describe PersonalAccessToken, 'TokenAuthenticatable' do
describe '.find_by_token' do
subject { PersonalAccessToken.find_by_token(token_value) }
- before do
+ it 'finds the token' do
personal_access_token.save
- end
- context 'token_digest already exists' do
- let(:token) { nil }
- let(:token_digest) { Gitlab::CryptoHelper.sha256(token_value) }
-
- it 'finds the token' do
- expect(subject).not_to be_nil
- expect(subject.name).to eql(personal_access_token_name)
- end
- end
-
- context 'token_digest does not exist' do
- let(:token) { token_value }
- let(:token_digest) { nil }
-
- it 'finds the token' do
- expect(subject).not_to be_nil
- expect(subject.name).to eql(personal_access_token_name)
- end
+ expect(subject).to eq(personal_access_token)
end
end
describe '#set_token' do
let(:new_token_value) { 'new-token' }
- subject { personal_access_token.set_token(new_token_value) }
-
- context 'token_digest already exists' do
- let(:token) { nil }
- let(:token_digest) { Gitlab::CryptoHelper.sha256(token_value) }
- it 'overwrites token_digest' do
- subject
-
- expect(personal_access_token.read_attribute('token')).to be_nil
- expect(personal_access_token.token).to eql(new_token_value)
- expect(personal_access_token.token_digest).to eql( Gitlab::CryptoHelper.sha256(new_token_value))
- end
- end
-
- context 'token_digest does not exist but token does' do
- let(:token) { token_value }
- let(:token_digest) { nil }
-
- it 'creates new token_digest and clears token' do
- subject
-
- expect(personal_access_token.read_attribute('token')).to be_nil
- expect(personal_access_token.token).to eql(new_token_value)
- expect(personal_access_token.token_digest).to eql(Gitlab::CryptoHelper.sha256(new_token_value))
- end
- end
-
- context 'token_digest does not exist, nor token' do
- let(:token) { nil }
- let(:token_digest) { nil }
+ subject { personal_access_token.set_token(new_token_value) }
- it 'creates new token_digest' do
- subject
+ it 'sets new token' do
+ subject
- expect(personal_access_token.read_attribute('token')).to be_nil
- expect(personal_access_token.token).to eql(new_token_value)
- expect(personal_access_token.token_digest).to eql( Gitlab::CryptoHelper.sha256(new_token_value))
- end
+ expect(personal_access_token.token).to eq(new_token_value)
+ expect(personal_access_token.token_digest).to eq(Gitlab::CryptoHelper.sha256(new_token_value))
end
end
describe '#ensure_token' do
subject { personal_access_token.ensure_token }
- context 'token_digest already exists' do
- let(:token) { nil }
- let(:token_digest) { Gitlab::CryptoHelper.sha256(token_value) }
-
- it 'does not change token fields' do
- subject
-
- expect(personal_access_token.read_attribute('token')).to be_nil
- expect(personal_access_token.token).to be_nil
- expect(personal_access_token.token_digest).to eql( Gitlab::CryptoHelper.sha256(token_value))
- end
- end
-
- context 'token_digest does not exist but token does' do
- let(:token) { token_value }
+ context 'token_digest does not exist' do
let(:token_digest) { nil }
- it 'does not change token fields' do
- subject
-
- expect(personal_access_token.read_attribute('token')).to eql(token_value)
- expect(personal_access_token.token).to eql(token_value)
- expect(personal_access_token.token_digest).to be_nil
- end
+ it_behaves_like 'changes personal access token'
end
- context 'token_digest does not exist, nor token' do
- let(:token) { nil }
- let(:token_digest) { nil }
-
- it 'creates token_digest' do
- subject
+ context 'token_digest already generated' do
+ let(:token_digest) { 's3cr3t' }
- expect(personal_access_token.read_attribute('token')).to be_nil
- expect(personal_access_token.token).to eql(token_value)
- expect(personal_access_token.token_digest).to eql( Gitlab::CryptoHelper.sha256(token_value))
- end
+ it_behaves_like 'does not change personal access token'
end
end
describe '#ensure_token!' do
subject { personal_access_token.ensure_token! }
- context 'token_digest already exists' do
- let(:token) { nil }
- let(:token_digest) { Gitlab::CryptoHelper.sha256(token_value) }
-
- it 'does not change token fields' do
- subject
-
- expect(personal_access_token.read_attribute('token')).to be_nil
- expect(personal_access_token.token).to be_nil
- expect(personal_access_token.token_digest).to eql( Gitlab::CryptoHelper.sha256(token_value))
- end
- end
-
- context 'token_digest does not exist but token does' do
- let(:token) { token_value }
+ context 'token_digest does not exist' do
let(:token_digest) { nil }
- it 'does not change token fields' do
- subject
-
- expect(personal_access_token.read_attribute('token')).to eql(token_value)
- expect(personal_access_token.token).to eql(token_value)
- expect(personal_access_token.token_digest).to be_nil
- end
+ it_behaves_like 'changes personal access token'
end
- context 'token_digest does not exist, nor token' do
- let(:token) { nil }
- let(:token_digest) { nil }
+ context 'token_digest already generated' do
+ let(:token_digest) { 's3cr3t' }
- it 'creates token_digest' do
- subject
-
- expect(personal_access_token.read_attribute('token')).to be_nil
- expect(personal_access_token.token).to eql(token_value)
- expect(personal_access_token.token_digest).to eql( Gitlab::CryptoHelper.sha256(token_value))
- end
+ it_behaves_like 'does not change personal access token'
end
end
describe '#reset_token!' do
subject { personal_access_token.reset_token! }
- context 'token_digest already exists' do
- let(:token) { nil }
- let(:token_digest) { Gitlab::CryptoHelper.sha256('old-token') }
-
- it 'creates new token_digest' do
- subject
-
- expect(personal_access_token.read_attribute('token')).to be_nil
- expect(personal_access_token.token).to eql(token_value)
- expect(personal_access_token.token_digest).to eql( Gitlab::CryptoHelper.sha256(token_value))
- end
- end
-
- context 'token_digest does not exist but token does' do
- let(:token) { 'old-token' }
- let(:token_digest) { nil }
-
- it 'creates new token_digest and clears token' do
- subject
-
- expect(personal_access_token.read_attribute('token')).to be_nil
- expect(personal_access_token.token).to eql(token_value)
- expect(personal_access_token.token_digest).to eql(Gitlab::CryptoHelper.sha256(token_value))
- end
- end
-
- context 'token_digest does not exist, nor token' do
- let(:token) { nil }
+ context 'token_digest does not exist' do
let(:token_digest) { nil }
- it 'creates new token_digest' do
- subject
-
- expect(personal_access_token.read_attribute('token')).to be_nil
- expect(personal_access_token.token).to eql(token_value)
- expect(personal_access_token.token_digest).to eql( Gitlab::CryptoHelper.sha256(token_value))
- end
- end
-
- context 'token_digest exists and newly generated token would be the same' do
- let(:token) { nil }
- let(:token_digest) { Gitlab::CryptoHelper.sha256('old-token') }
-
- before do
- personal_access_token.save
- allow(Devise).to receive(:friendly_token).and_return(
- 'old-token', token_value, 'boom!')
- end
-
- it 'regenerates a new token_digest' do
- subject
-
- expect(personal_access_token.read_attribute('token')).to be_nil
- expect(personal_access_token.token).to eql(token_value)
- expect(personal_access_token.token_digest).to eql( Gitlab::CryptoHelper.sha256(token_value))
- end
+ it_behaves_like 'changes personal access token'
end
- context 'token exists and newly generated token would be the same' do
- let(:token) { 'old-token' }
- let(:token_digest) { nil }
-
- before do
- personal_access_token.save
- allow(Devise).to receive(:friendly_token).and_return(
- 'old-token', token_value, 'boom!')
- end
+ context 'token_digest already generated' do
+ let(:token_digest) { 's3cr3t' }
- it 'regenerates a new token_digest' do
- subject
-
- expect(personal_access_token.read_attribute('token')).to be_nil
- expect(personal_access_token.token).to eql(token_value)
- expect(personal_access_token.token_digest).to eql( Gitlab::CryptoHelper.sha256(token_value))
- end
+ it_behaves_like 'changes personal access token'
end
end
end
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index 2d554326f05..ab1b306e597 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -164,6 +164,28 @@ describe Environment do
end
end
+ describe '#name_without_type' do
+ context 'when it is inside a folder' do
+ subject(:environment) do
+ create(:environment, name: 'staging/review-1')
+ end
+
+ it 'returns name without folder' do
+ expect(environment.name_without_type).to eq 'review-1'
+ end
+ end
+
+ context 'when the environment if a top-level item itself' do
+ subject(:environment) do
+ create(:environment, name: 'production')
+ end
+
+ it 'returns full name' do
+ expect(environment.name_without_type).to eq 'production'
+ end
+ end
+ end
+
describe '#nullify_external_url' do
it 'replaces a blank url with nil' do
env = build(:environment, external_url: "")
diff --git a/spec/models/error_tracking/project_error_tracking_setting_spec.rb b/spec/models/error_tracking/project_error_tracking_setting_spec.rb
index d30228b863c..cbde13a2c7a 100644
--- a/spec/models/error_tracking/project_error_tracking_setting_spec.rb
+++ b/spec/models/error_tracking/project_error_tracking_setting_spec.rb
@@ -62,11 +62,32 @@ describe ErrorTracking::ProjectErrorTrackingSetting do
end
context 'URL path' do
- it 'fails validation with wrong path' do
+ it 'fails validation without api/0/projects' do
subject.api_url = 'http://gitlab.com/project1/something'
expect(subject).not_to be_valid
- expect(subject.errors.messages[:api_url]).to include('path needs to start with /api/0/projects')
+ expect(subject.errors.messages[:api_url]).to include('is invalid')
+ end
+
+ it 'fails validation without org and project slugs' do
+ subject.api_url = 'http://gitlab.com/api/0/projects/'
+
+ expect(subject).not_to be_valid
+ expect(subject.errors.messages[:project]).to include('is a required field')
+ end
+
+ it 'fails validation when api_url has extra parts' do
+ subject.api_url = 'http://gitlab.com/api/0/projects/org/proj/something'
+
+ expect(subject).not_to be_valid
+ expect(subject.errors.messages[:api_url]).to include("is invalid")
+ end
+
+ it 'fails validation when api_url has less parts' do
+ subject.api_url = 'http://gitlab.com/api/0/projects/org'
+
+ expect(subject).not_to be_valid
+ expect(subject.errors.messages[:api_url]).to include("is invalid")
end
it 'passes validation with correct path' do
@@ -145,6 +166,24 @@ describe ErrorTracking::ProjectErrorTrackingSetting do
expect(result).to be_nil
end
end
+
+ context 'when sentry client raises exception' do
+ let(:sentry_client) { spy(:sentry_client) }
+
+ before do
+ synchronous_reactive_cache(subject)
+
+ allow(subject).to receive(:sentry_client).and_return(sentry_client)
+ allow(sentry_client).to receive(:list_issues).with(opts)
+ .and_raise(Sentry::Client::Error, 'error message')
+ end
+
+ it 'returns error' do
+ expect(result).to eq(error: 'error message')
+ expect(subject).to have_received(:sentry_client)
+ expect(sentry_client).to have_received(:list_issues)
+ end
+ end
end
describe '#list_sentry_projects' do
@@ -257,6 +296,16 @@ describe ErrorTracking::ProjectErrorTrackingSetting do
expect(api_url).to eq(':::')
end
+
+ it 'returns nil when api_host is blank' do
+ api_url = described_class.build_api_url_from(
+ api_host: '',
+ organization_slug: 'org-slug',
+ project_slug: 'proj-slug'
+ )
+
+ expect(api_url).to be_nil
+ end
end
describe '#api_host' do
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index 5d18e085a6f..6101df2e099 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -765,6 +765,15 @@ describe Issue do
end
end
+ describe '.confidential_only' do
+ it 'only returns confidential_only issues' do
+ create(:issue)
+ confidential_issue = create(:issue, confidential: true)
+
+ expect(described_class.confidential_only).to eq([confidential_issue])
+ end
+ end
+
it_behaves_like 'throttled touch' do
subject { create(:issue, updated_at: 1.hour.ago) }
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index afa87b8a62d..07cb4c9c1e3 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -435,6 +435,7 @@ describe MergeRequest do
it 'does not cache issues from external trackers' do
issue = ExternalIssue.new('JIRA-123', subject.project)
commit = double('commit1', safe_message: "Fixes #{issue.to_reference}")
+
allow(subject).to receive(:commits).and_return([commit])
expect { subject.cache_merge_request_closes_issues!(subject.author) }.not_to raise_error
@@ -1023,23 +1024,23 @@ describe MergeRequest do
end
end
- describe '#committers' do
- it 'returns all the committers of every commit in the merge request' do
- users = subject.commits.map(&:committer_email).uniq.map do |email|
+ describe '#commit_authors' do
+ it 'returns all the authors of every commit in the merge request' do
+ users = subject.commits.map(&:author_email).uniq.map do |email|
create(:user, email: email)
end
- expect(subject.committers).to match_array(users)
+ expect(subject.commit_authors).to match_array(users)
end
- it 'returns an empty array if no committer is associated with a user' do
- expect(subject.committers).to be_empty
+ it 'returns an empty array if no author is associated with a user' do
+ expect(subject.commit_authors).to be_empty
end
end
describe '#authors' do
- it 'returns a list with all the committers in the merge request and author' do
- users = subject.commits.map(&:committer_email).uniq.map do |email|
+ it 'returns a list with all the commit authors in the merge request and author' do
+ users = subject.commits.map(&:author_email).uniq.map do |email|
create(:user, email: email)
end
@@ -1332,7 +1333,7 @@ describe MergeRequest do
let!(:merge_request_pipeline) do
create(:ci_pipeline,
- source: :merge_request,
+ source: :merge_request_event,
project: project,
ref: source_ref,
sha: shas.second,
@@ -1371,7 +1372,7 @@ describe MergeRequest do
let!(:merge_request_pipeline_2) do
create(:ci_pipeline,
- source: :merge_request,
+ source: :merge_request_event,
project: project,
ref: source_ref,
sha: shas.first,
@@ -1398,7 +1399,7 @@ describe MergeRequest do
let!(:merge_request_pipeline_2) do
create(:ci_pipeline,
- source: :merge_request,
+ source: :merge_request_event,
project: project,
ref: source_ref,
sha: shas.first,
@@ -2604,8 +2605,9 @@ describe MergeRequest do
let!(:first_pipeline) { create(:ci_pipeline_without_jobs, pipeline_arguments) }
let!(:last_pipeline) { create(:ci_pipeline_without_jobs, pipeline_arguments) }
+ let!(:last_pipeline_with_other_ref) { create(:ci_pipeline_without_jobs, pipeline_arguments.merge(ref: 'other')) }
- it 'returns latest pipeline' do
+ it 'returns latest pipeline for the target branch' do
expect(merge_request.base_pipeline).to eq(last_pipeline)
end
end
diff --git a/spec/models/project_daily_statistic_spec.rb b/spec/models/project_daily_statistic_spec.rb
new file mode 100644
index 00000000000..86210af15d8
--- /dev/null
+++ b/spec/models/project_daily_statistic_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ProjectDailyStatistic do
+ it { is_expected.to belong_to(:project) }
+end
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index 788b3179b01..5428fcb1271 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -177,9 +177,10 @@ describe JiraService do
expect(WebMock).to have_requested(:post, @remote_link_url).with(
body: hash_including(
GlobalID: 'GitLab',
+ relationship: 'mentioned on',
object: {
url: "#{Gitlab.config.gitlab.url}/#{project.full_path}/commit/#{commit_id}",
- title: "GitLab: Solved by commit #{commit_id}.",
+ title: "Solved by commit #{commit_id}.",
icon: { title: 'GitLab', url16x16: favicon_path },
status: { resolved: true }
}
diff --git a/spec/models/project_services/slack_slash_commands_service_spec.rb b/spec/models/project_services/slack_slash_commands_service_spec.rb
index 0d95f454819..5c4bce90ace 100644
--- a/spec/models/project_services/slack_slash_commands_service_spec.rb
+++ b/spec/models/project_services/slack_slash_commands_service_spec.rb
@@ -38,4 +38,11 @@ describe SlackSlashCommandsService do
end
end
end
+
+ describe '#chat_responder' do
+ it 'returns the responder to use for Slack' do
+ expect(described_class.new.chat_responder)
+ .to eq(Gitlab::Chat::Responder::Slack)
+ end
+ end
end
diff --git a/spec/models/project_services/youtrack_service_spec.rb b/spec/models/project_services/youtrack_service_spec.rb
new file mode 100644
index 00000000000..9524b526a46
--- /dev/null
+++ b/spec/models/project_services/youtrack_service_spec.rb
@@ -0,0 +1,38 @@
+require 'spec_helper'
+
+describe YoutrackService do
+ describe 'Associations' do
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
+ end
+
+ describe 'Validations' do
+ context 'when service is active' do
+ before do
+ subject.active = true
+ end
+
+ it { is_expected.to validate_presence_of(:project_url) }
+ it { is_expected.to validate_presence_of(:issues_url) }
+ it_behaves_like 'issue tracker service URL attribute', :project_url
+ it_behaves_like 'issue tracker service URL attribute', :issues_url
+ end
+
+ context 'when service is inactive' do
+ before do
+ subject.active = false
+ end
+
+ it { is_expected.not_to validate_presence_of(:project_url) }
+ it { is_expected.not_to validate_presence_of(:issues_url) }
+ end
+ end
+
+ describe '.reference_pattern' do
+ it_behaves_like 'allows project key on reference pattern'
+
+ it 'does allow project prefix on the reference' do
+ expect(described_class.reference_pattern.match('YT-123')[:issue]).to eq('YT-123')
+ end
+ end
+end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 1f9088c2e6b..b2392f9521f 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -50,6 +50,7 @@ describe Project do
it { is_expected.to have_one(:teamcity_service) }
it { is_expected.to have_one(:jira_service) }
it { is_expected.to have_one(:redmine_service) }
+ it { is_expected.to have_one(:youtrack_service) }
it { is_expected.to have_one(:custom_issue_tracker_service) }
it { is_expected.to have_one(:bugzilla_service) }
it { is_expected.to have_one(:gitlab_issue_tracker_service) }
@@ -428,6 +429,30 @@ describe Project do
end
end
+ describe '#ci_pipelines' do
+ let(:project) { create(:project) }
+
+ before do
+ create(:ci_pipeline, project: project, ref: 'master', source: :web)
+ create(:ci_pipeline, project: project, ref: 'master', source: :external)
+ end
+
+ it 'has ci pipelines' do
+ expect(project.ci_pipelines.size).to eq(2)
+ end
+
+ context 'when builds are disabled' do
+ before do
+ project.project_feature.update_attribute(:builds_access_level, ProjectFeature::DISABLED)
+ end
+
+ it 'should return .external pipelines' do
+ expect(project.ci_pipelines).to all(have_attributes(source: 'external'))
+ expect(project.ci_pipelines.size).to eq(1)
+ end
+ end
+ end
+
describe 'project token' do
it 'sets an random token if none provided' do
project = FactoryBot.create(:project, runners_token: '')
@@ -458,6 +483,7 @@ describe Project do
it { is_expected.to delegate_method(:name).to(:owner).with_prefix(true).with_arguments(allow_nil: true) }
it { is_expected.to delegate_method(:group_clusters_enabled?).to(:group).with_arguments(allow_nil: true) }
it { is_expected.to delegate_method(:root_ancestor).to(:namespace).with_arguments(allow_nil: true) }
+ it { is_expected.to delegate_method(:last_pipeline).to(:commit).with_arguments(allow_nil: true) }
end
describe '#to_reference_with_postfix' do
@@ -2335,6 +2361,18 @@ describe Project do
end
end
+ describe '#daily_statistics_enabled?' do
+ it { is_expected.to be_daily_statistics_enabled }
+
+ context 'when :project_daily_statistics is disabled for the project' do
+ before do
+ stub_feature_flags(project_daily_statistics: { thing: subject, enabled: false })
+ end
+
+ it { is_expected.not_to be_daily_statistics_enabled }
+ end
+ end
+
describe '#change_head' do
let(:project) { create(:project, :repository) }
@@ -2486,6 +2524,16 @@ describe Project do
end
end
+ describe '#set_repository_writable!' do
+ it 'sets repository_read_only to false' do
+ project = create(:project, :read_only)
+
+ expect { project.set_repository_writable! }
+ .to change(project, :repository_read_only)
+ .from(true).to(false)
+ end
+ end
+
describe '#pushes_since_gc' do
let(:project) { create(:project) }
@@ -3382,28 +3430,42 @@ describe Project do
project.migrate_to_hashed_storage!
end
- it 'schedules ProjectMigrateHashedStorageWorker with delayed start when the project repo is in use' do
+ it 'schedules HashedStorage::ProjectMigrateWorker with delayed start when the project repo is in use' do
Gitlab::ReferenceCounter.new(project.gl_repository(is_wiki: false)).increase
- expect(ProjectMigrateHashedStorageWorker).to receive(:perform_in)
+ expect(HashedStorage::ProjectMigrateWorker).to receive(:perform_in)
project.migrate_to_hashed_storage!
end
- it 'schedules ProjectMigrateHashedStorageWorker with delayed start when the wiki repo is in use' do
+ it 'schedules HashedStorage::ProjectMigrateWorker with delayed start when the wiki repo is in use' do
Gitlab::ReferenceCounter.new(project.gl_repository(is_wiki: true)).increase
- expect(ProjectMigrateHashedStorageWorker).to receive(:perform_in)
+ expect(HashedStorage::ProjectMigrateWorker).to receive(:perform_in)
project.migrate_to_hashed_storage!
end
- it 'schedules ProjectMigrateHashedStorageWorker' do
- expect(ProjectMigrateHashedStorageWorker).to receive(:perform_async).with(project.id)
+ it 'schedules HashedStorage::ProjectMigrateWorker' do
+ expect(HashedStorage::ProjectMigrateWorker).to receive(:perform_async).with(project.id)
project.migrate_to_hashed_storage!
end
end
+
+ describe '#rollback_to_legacy_storage!' do
+ let(:project) { create(:project, :empty_repo, :legacy_storage) }
+
+ it 'returns nil' do
+ expect(project.rollback_to_legacy_storage!).to be_nil
+ end
+
+ it 'does not run validations' do
+ expect(project).not_to receive(:valid?)
+
+ project.rollback_to_legacy_storage!
+ end
+ end
end
context 'hashed storage' do
@@ -3479,11 +3541,35 @@ describe Project do
project = create(:project, storage_version: 1, skip_disk_validation: true)
Sidekiq::Testing.fake! do
- expect { project.migrate_to_hashed_storage! }.to change(ProjectMigrateHashedStorageWorker.jobs, :size).by(1)
+ expect { project.migrate_to_hashed_storage! }.to change(HashedStorage::ProjectMigrateWorker.jobs, :size).by(1)
end
end
end
end
+
+ describe '#rollback_to_legacy_storage!' do
+ let(:project) { create(:project, :repository, skip_disk_validation: true) }
+
+ it 'returns true' do
+ expect(project.rollback_to_legacy_storage!).to be_truthy
+ end
+
+ it 'does not run validations' do
+ expect(project).not_to receive(:valid?)
+
+ project.rollback_to_legacy_storage!
+ end
+
+ it 'does not flag as read-only' do
+ expect { project.rollback_to_legacy_storage! }.not_to change { project.repository_read_only }
+ end
+
+ it 'enqueues a job' do
+ Sidekiq::Testing.fake! do
+ expect { project.rollback_to_legacy_storage! }.to change(HashedStorage::ProjectRollbackWorker.jobs, :size).by(1)
+ end
+ end
+ end
end
describe '#gl_repository' do
diff --git a/spec/models/prometheus_metric_spec.rb b/spec/models/prometheus_metric_spec.rb
index 2b978c1c8ff..3610408c138 100644
--- a/spec/models/prometheus_metric_spec.rb
+++ b/spec/models/prometheus_metric_spec.rb
@@ -4,7 +4,6 @@ require 'spec_helper'
describe PrometheusMetric do
subject { build(:prometheus_metric) }
- let(:other_project) { build(:project) }
it_behaves_like 'having unique enum values'
@@ -16,17 +15,17 @@ describe PrometheusMetric do
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
+ where(:common, :with_project, :result) do
+ false | true | true
+ false | false | false
+ true | true | false
+ true | false | true
end
with_them do
before do
subject.common = common
- subject.project = project
+ subject.project = with_project ? build(:project) : nil
end
it { expect(subject.valid?).to eq(result) }
diff --git a/spec/models/releases/link_spec.rb b/spec/models/releases/link_spec.rb
index 06ed1438688..4dd26c976cc 100644
--- a/spec/models/releases/link_spec.rb
+++ b/spec/models/releases/link_spec.rb
@@ -77,4 +77,28 @@ describe Releases::Link do
it { is_expected.to be_truthy }
end
+
+ describe 'supported protocols' do
+ where(:protocol) do
+ %w(http https ftp)
+ end
+
+ with_them do
+ let(:link) { build(:release_link, url: protocol + '://assets.com/download') }
+
+ it 'will be valid' do
+ expect(link).to be_valid
+ end
+ end
+ end
+
+ describe 'unsupported protocol' do
+ context 'for torrent' do
+ let(:link) { build(:release_link, url: 'torrent://assets.com/download') }
+
+ it 'will be invalid' do
+ expect(link).to be_invalid
+ end
+ end
+ end
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index f78760bf567..17201d8b90a 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -1373,6 +1373,29 @@ describe Repository do
end
end
+ describe '#merge_to_ref' do
+ let(:merge_request) do
+ create(:merge_request, source_branch: 'feature',
+ target_branch: 'master',
+ source_project: project)
+ end
+
+ it 'writes merge of source and target to MR merge_ref_path' do
+ merge_commit_id = repository.merge_to_ref(user,
+ merge_request.diff_head_sha,
+ merge_request,
+ merge_request.merge_ref_path,
+ 'Custom message')
+
+ merge_commit = repository.commit(merge_commit_id)
+
+ expect(merge_commit.message).to eq('Custom message')
+ expect(merge_commit.author_name).to eq(user.name)
+ expect(merge_commit.author_email).to eq(user.commit_email)
+ expect(repository.blob_at(merge_commit.id, 'files/ruby/feature.rb')).to be_present
+ end
+ end
+
describe '#ff_merge' do
before do
repository.add_branch(user, 'ff-target', 'feature~5')
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 78477ab0a5a..85b157a9435 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -925,6 +925,21 @@ describe User do
expect(user.manageable_groups).to contain_exactly(group, subgroup)
end
end
+
+ describe '#manageable_groups_with_routes' do
+ it 'eager loads routes from manageable groups' do
+ control_count =
+ ActiveRecord::QueryRecorder.new(skip_cached: false) do
+ user.manageable_groups_with_routes.map(&:route)
+ end.count
+
+ create(:group, parent: subgroup)
+
+ expect do
+ user.manageable_groups_with_routes.map(&:route)
+ end.not_to exceed_all_query_limit(control_count)
+ end
+ end
end
end
@@ -961,43 +976,43 @@ describe User do
end
end
- describe '.filter' do
+ describe '.filter_items' do
let(:user) { double }
it 'filters by active users by default' do
expect(described_class).to receive(:active).and_return([user])
- expect(described_class.filter(nil)).to include user
+ expect(described_class.filter_items(nil)).to include user
end
it 'filters by admins' do
expect(described_class).to receive(:admins).and_return([user])
- expect(described_class.filter('admins')).to include user
+ expect(described_class.filter_items('admins')).to include user
end
it 'filters by blocked' do
expect(described_class).to receive(:blocked).and_return([user])
- expect(described_class.filter('blocked')).to include user
+ expect(described_class.filter_items('blocked')).to include user
end
it 'filters by two_factor_disabled' do
expect(described_class).to receive(:without_two_factor).and_return([user])
- expect(described_class.filter('two_factor_disabled')).to include user
+ expect(described_class.filter_items('two_factor_disabled')).to include user
end
it 'filters by two_factor_enabled' do
expect(described_class).to receive(:with_two_factor).and_return([user])
- expect(described_class.filter('two_factor_enabled')).to include user
+ expect(described_class.filter_items('two_factor_enabled')).to include user
end
it 'filters by wop' do
expect(described_class).to receive(:without_projects).and_return([user])
- expect(described_class.filter('wop')).to include user
+ expect(described_class.filter_items('wop')).to include user
end
end
diff --git a/spec/policies/board_policy_spec.rb b/spec/policies/board_policy_spec.rb
new file mode 100644
index 00000000000..4b76d65ef69
--- /dev/null
+++ b/spec/policies/board_policy_spec.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe BoardPolicy do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :private) }
+ let(:group) { create(:group, :private) }
+ let(:group_board) { create(:board, group: group) }
+ let(:project_board) { create(:board, project: project) }
+
+ let(:board_permissions) do
+ [
+ :read_parent,
+ :read_milestone,
+ :read_issue
+ ]
+ end
+
+ def expect_allowed(*permissions)
+ permissions.each { |p| is_expected.to be_allowed(p) }
+ end
+
+ def expect_disallowed(*permissions)
+ permissions.each { |p| is_expected.not_to be_allowed(p) }
+ end
+
+ context 'group board' do
+ subject { described_class.new(user, group_board) }
+
+ context 'user has access' do
+ before do
+ group.add_developer(user)
+ end
+
+ it do
+ expect_allowed(*board_permissions)
+ end
+ end
+
+ context 'user does not have access' do
+ it do
+ expect_disallowed(*board_permissions)
+ end
+ end
+ end
+
+ context 'project board' do
+ subject { described_class.new(user, project_board) }
+
+ context 'user has access' do
+ before do
+ project.add_developer(user)
+ end
+
+ it do
+ expect_allowed(*board_permissions)
+ end
+ end
+
+ context 'user does not have access' do
+ it do
+ expect_disallowed(*board_permissions)
+ end
+ end
+ end
+end
diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb
index be1804c5ce0..af6d6f084a9 100644
--- a/spec/policies/group_policy_spec.rb
+++ b/spec/policies/group_policy_spec.rb
@@ -83,7 +83,7 @@ describe GroupPolicy do
end
it do
- expect_allowed(:read_group, :read_label)
+ expect_allowed(:read_group, :read_list, :read_label)
end
context 'in subgroups', :nested_groups do
@@ -91,7 +91,7 @@ describe GroupPolicy do
let(:project) { create(:project, namespace: subgroup) }
it do
- expect_allowed(:read_group, :read_label)
+ expect_allowed(:read_group, :read_list, :read_label)
end
end
end
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index 93a468f585b..997bdc82af6 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -49,6 +49,7 @@ describe ProjectPolicy do
admin_project_member admin_note admin_wiki admin_project
admin_commit_status admin_build admin_container_image
admin_pipeline admin_environment admin_deployment destroy_release add_cluster
+ daily_statistics
]
end
diff --git a/spec/presenters/ci/build_runner_presenter_spec.rb b/spec/presenters/ci/build_runner_presenter_spec.rb
index 170e0ac5717..f50bcf54b46 100644
--- a/spec/presenters/ci/build_runner_presenter_spec.rb
+++ b/spec/presenters/ci/build_runner_presenter_spec.rb
@@ -98,4 +98,72 @@ describe Ci::BuildRunnerPresenter do
end
end
end
+
+ describe '#ref_type' do
+ subject { presenter.ref_type }
+
+ let(:build) { create(:ci_build, tag: tag) }
+ let(:tag) { true }
+
+ it 'returns the correct ref type' do
+ is_expected.to eq('tag')
+ end
+
+ context 'when tag is false' do
+ let(:tag) { false }
+
+ it 'returns the correct ref type' do
+ is_expected.to eq('branch')
+ end
+ end
+ end
+
+ describe '#git_depth' do
+ subject { presenter.git_depth }
+
+ let(:build) { create(:ci_build) }
+
+ it 'returns the correct git depth' do
+ is_expected.to eq(0)
+ end
+
+ context 'when GIT_DEPTH variable is specified' do
+ before do
+ create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 1, pipeline: build.pipeline)
+ end
+
+ it 'returns the correct git depth' do
+ is_expected.to eq(1)
+ end
+ end
+ end
+
+ describe '#refspecs' do
+ subject { presenter.refspecs }
+
+ let(:build) { create(:ci_build) }
+
+ it 'returns the correct refspecs' do
+ is_expected.to contain_exactly('+refs/tags/*:refs/tags/*',
+ '+refs/heads/*:refs/remotes/origin/*')
+ end
+
+ context 'when GIT_DEPTH variable is specified' do
+ before do
+ create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 1, pipeline: build.pipeline)
+ end
+
+ it 'returns the correct refspecs' do
+ is_expected.to contain_exactly("+refs/heads/#{build.ref}:refs/remotes/origin/#{build.ref}")
+ end
+
+ context 'when ref is tag' do
+ let(:build) { create(:ci_build, :tag) }
+
+ it 'returns the correct refspecs' do
+ is_expected.to contain_exactly("+refs/tags/#{build.ref}:refs/tags/#{build.ref}")
+ end
+ end
+ end
+ end
end
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 6b9bc6eda6a..066f1d6862a 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -237,7 +237,7 @@ describe API::Commits do
end
describe 'create' do
- let(:message) { 'Created file' }
+ let(:message) { 'Created a new file with a very very looooooooooooooooooooooooooooooooooooooooooooooong commit message' }
let(:invalid_c_params) do
{
branch: 'master',
@@ -1457,4 +1457,42 @@ describe API::Commits do
expect(response).to have_gitlab_http_status(404)
end
end
+
+ describe 'GET /projects/:id/repository/commits/:sha/signature' do
+ let!(:project) { create(:project, :repository, :public) }
+ let(:project_id) { project.id }
+ let(:commit_id) { project.repository.commit.id }
+ let(:route) { "/projects/#{project_id}/repository/commits/#{commit_id}/signature" }
+
+ context 'when commit does not exist' do
+ let(:commit_id) { 'unknown' }
+
+ it_behaves_like '404 response' do
+ let(:request) { get api(route, current_user) }
+ let(:message) { '404 Commit Not Found' }
+ end
+ end
+
+ context 'unsigned commit' do
+ it_behaves_like '404 response' do
+ let(:request) { get api(route, current_user) }
+ let(:message) { '404 GPG Signature Not Found'}
+ end
+ end
+
+ context 'signed commit' do
+ let(:commit) { project.repository.commit(GpgHelpers::SIGNED_COMMIT_SHA) }
+ let(:commit_id) { commit.id }
+
+ it 'returns correct JSON' do
+ get api(route, current_user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['gpg_key_id']).to eq(commit.signature.gpg_key_id)
+ expect(json_response['gpg_key_subkey_id']).to eq(commit.signature.gpg_key_subkey_id)
+ expect(json_response['gpg_key_primary_keyid']).to eq(commit.signature.gpg_key_primary_keyid)
+ expect(json_response['verification_status']).to eq(commit.signature.verification_status)
+ end
+ end
+ end
end
diff --git a/spec/requests/api/features_spec.rb b/spec/requests/api/features_spec.rb
index 22a9e36ca31..57a57e69a00 100644
--- a/spec/requests/api/features_spec.rb
+++ b/spec/requests/api/features_spec.rb
@@ -163,6 +163,40 @@ describe API::Features do
end
end
+ context 'when enabling for a group by path' do
+ context 'when the group exists' do
+ it 'sets the feature gate' do
+ group = create(:group)
+
+ post api("/features/#{feature_name}", admin), params: { value: 'true', group: group.full_path }
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response).to eq(
+ 'name' => 'my_feature',
+ 'state' => 'conditional',
+ 'gates' => [
+ { 'key' => 'boolean', 'value' => false },
+ { 'key' => 'actors', 'value' => ["Group:#{group.id}"] }
+ ])
+ end
+ end
+
+ context 'when the group does not exist' do
+ it 'sets no new values and keeps the feature disabled' do
+ post api("/features/#{feature_name}", admin), params: { value: 'true', group: 'not/a/group' }
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response).to eq(
+ "name" => "my_feature",
+ "state" => "off",
+ "gates" => [
+ { "key" => "boolean", "value" => false }
+ ]
+ )
+ end
+ end
+ end
+
it 'creates a feature with the given percentage if passed an integer' do
post api("/features/#{feature_name}", admin), params: { value: '50' }
diff --git a/spec/requests/api/graphql/project/issues_spec.rb b/spec/requests/api/graphql/project/issues_spec.rb
index 355336ad7e2..c2934430821 100644
--- a/spec/requests/api/graphql/project/issues_spec.rb
+++ b/spec/requests/api/graphql/project/issues_spec.rb
@@ -56,4 +56,38 @@ describe 'getting an issue list for a project' do
expect(issues_data).to eq []
end
end
+
+ context 'when there is a confidential issue' do
+ let!(:confidential_issue) do
+ create(:issue, :confidential, project: project)
+ end
+
+ context 'when the user cannot see confidential issues' do
+ it 'returns issues without confidential issues' do
+ post_graphql(query, current_user: current_user)
+
+ expect(issues_data.size).to eq(2)
+
+ issues_data.each do |issue|
+ expect(issue.dig('node', 'confidential')).to eq(false)
+ end
+ end
+ end
+
+ context 'when the user can see confidential issues' do
+ it 'returns issues with confidential issues' do
+ project.add_developer(current_user)
+
+ post_graphql(query, current_user: current_user)
+
+ expect(issues_data.size).to eq(3)
+
+ confidentials = issues_data.map do |issue|
+ issue.dig('node', 'confidential')
+ end
+
+ expect(confidentials).to eq([true, false, false])
+ end
+ end
+ end
end
diff --git a/spec/requests/api/group_variables_spec.rb b/spec/requests/api/group_variables_spec.rb
index e52f4c70407..66b9aae4b58 100644
--- a/spec/requests/api/group_variables_spec.rb
+++ b/spec/requests/api/group_variables_spec.rb
@@ -87,12 +87,12 @@ describe API::GroupVariables do
it 'creates variable' do
expect do
- post api("/groups/#{group.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'VALUE_2', protected: true }
+ post api("/groups/#{group.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'PROTECTED_VALUE_2', protected: true }
end.to change {group.variables.count}.by(1)
expect(response).to have_gitlab_http_status(201)
expect(json_response['key']).to eq('TEST_VARIABLE_2')
- expect(json_response['value']).to eq('VALUE_2')
+ expect(json_response['value']).to eq('PROTECTED_VALUE_2')
expect(json_response['protected']).to be_truthy
end
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index 6a943b5237a..cd85151ec1b 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -167,6 +167,7 @@ describe API::Internal do
expect(response).to have_gitlab_http_status(200)
expect(json_response['username']).to eq(user.username)
expect(json_response['repository_http_path']).to eq(project.http_url_to_repo)
+ expect(json_response['expires_in']).to eq(Gitlab::LfsToken::DEFAULT_EXPIRE_TIME)
expect(Gitlab::LfsToken.new(key).token_valid?(json_response['lfs_token'])).to be_truthy
end
@@ -324,6 +325,7 @@ describe API::Internal do
expect(response).to have_gitlab_http_status(200)
expect(json_response["status"]).to be_truthy
expect(json_response["repository_path"]).to eq('/')
+ expect(json_response["gl_project_path"]).to eq(project.wiki.full_path)
expect(json_response["gl_repository"]).to eq("wiki-#{project.id}")
expect(user.reload.last_activity_on).to be_nil
end
@@ -336,6 +338,7 @@ describe API::Internal do
expect(response).to have_gitlab_http_status(200)
expect(json_response["status"]).to be_truthy
expect(json_response["repository_path"]).to eq('/')
+ expect(json_response["gl_project_path"]).to eq(project.wiki.full_path)
expect(json_response["gl_repository"]).to eq("wiki-#{project.id}")
expect(user.reload.last_activity_on).to eql(Date.today)
end
@@ -349,6 +352,7 @@ describe API::Internal do
expect(json_response["status"]).to be_truthy
expect(json_response["repository_path"]).to eq('/')
expect(json_response["gl_repository"]).to eq("project-#{project.id}")
+ expect(json_response["gl_project_path"]).to eq(project.full_path)
expect(json_response["gitaly"]).not_to be_nil
expect(json_response["gitaly"]["repository"]).not_to be_nil
expect(json_response["gitaly"]["repository"]["storage_name"]).to eq(project.repository.gitaly_repository.storage_name)
@@ -368,6 +372,7 @@ describe API::Internal do
expect(json_response["status"]).to be_truthy
expect(json_response["repository_path"]).to eq('/')
expect(json_response["gl_repository"]).to eq("project-#{project.id}")
+ expect(json_response["gl_project_path"]).to eq(project.full_path)
expect(json_response["gitaly"]).not_to be_nil
expect(json_response["gitaly"]["repository"]).not_to be_nil
expect(json_response["gitaly"]["repository"]["storage_name"]).to eq(project.repository.gitaly_repository.storage_name)
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 04908378a24..01bab2a1361 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -183,6 +183,18 @@ describe API::Issues do
expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
end
+ it 'returns only confidential issues' do
+ get api('/issues', user), params: { confidential: true, scope: 'all' }
+
+ expect_paginated_array_response(confidential_issue.id)
+ end
+
+ it 'returns only public issues' do
+ get api('/issues', user), params: { confidential: false }
+
+ expect_paginated_array_response([issue.id, closed_issue.id])
+ end
+
it 'returns issues reacted by the authenticated user' do
issue2 = create(:issue, project: project, author: user, assignees: [user])
create(:award_emoji, awardable: issue2, user: user2, name: 'star')
@@ -354,15 +366,43 @@ describe API::Issues do
end
it 'returns an empty array if iid does not exist' do
- get api("/issues", user), params: { iids: [99999] }
+ get api("/issues", user), params: { iids: [0] }
expect_paginated_array_response([])
end
- it 'sorts by created_at descending by default' do
- get api('/issues', user)
+ context 'without sort params' do
+ it 'sorts by created_at descending by default' do
+ get api('/issues', user)
- expect_paginated_array_response([issue.id, closed_issue.id])
+ expect_paginated_array_response([issue.id, closed_issue.id])
+ end
+
+ context 'with 2 issues with same created_at' do
+ let!(:closed_issue2) do
+ create :closed_issue,
+ author: user,
+ assignees: [user],
+ project: project,
+ milestone: milestone,
+ created_at: closed_issue.created_at,
+ updated_at: 1.hour.ago,
+ title: issue_title,
+ description: issue_description
+ end
+
+ it 'page breaks first page correctly' do
+ get api('/issues?per_page=2', user)
+
+ expect_paginated_array_response([issue.id, closed_issue2.id])
+ end
+
+ it 'page breaks second page correctly' do
+ get api('/issues?per_page=2&page=2', user)
+
+ expect_paginated_array_response([closed_issue.id])
+ end
+ end
end
it 'sorts ascending when requested' do
@@ -393,6 +433,24 @@ describe API::Issues do
expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('public_api/v4/issues')
end
+
+ it 'returns a related merge request count of 0 if there are no related merge requests' do
+ get api('/issues', user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/issues')
+ expect(json_response.first).to include('merge_requests_count' => 0)
+ end
+
+ it 'returns a related merge request count > 0 if there are related merge requests' do
+ create(:merge_requests_closing_issues, issue: issue)
+
+ get api('/issues', user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/issues')
+ expect(json_response.first).to include('merge_requests_count' => 1)
+ end
end
end
@@ -511,6 +569,18 @@ describe API::Issues do
expect_paginated_array_response([group_confidential_issue.id, group_issue.id])
end
+ it 'returns only confidential issues' do
+ get api(base_url, user), params: { confidential: true }
+
+ expect_paginated_array_response(group_confidential_issue.id)
+ end
+
+ it 'returns only public issues' do
+ get api(base_url, user), params: { confidential: false }
+
+ expect_paginated_array_response([group_closed_issue.id, group_issue.id])
+ end
+
it 'returns an array of labeled group issues' do
get api(base_url, user), params: { labels: group_label.title }
@@ -557,7 +627,7 @@ describe API::Issues do
end
it 'returns an empty array if iid does not exist' do
- get api(base_url, user), params: { iids: [99999] }
+ get api(base_url, user), params: { iids: [0] }
expect_paginated_array_response([])
end
@@ -613,10 +683,38 @@ describe API::Issues do
expect_paginated_array_response(group_confidential_issue.id)
end
- it 'sorts by created_at descending by default' do
- get api(base_url, user)
+ context 'without sort params' do
+ it 'sorts by created_at descending by default' do
+ get api(base_url, user)
- expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id, group_issue.id])
+ expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id, group_issue.id])
+ end
+
+ context 'with 2 issues with same created_at' do
+ let!(:group_issue2) do
+ create :issue,
+ author: user,
+ assignees: [user],
+ project: group_project,
+ milestone: group_milestone,
+ updated_at: 1.hour.ago,
+ title: issue_title,
+ description: issue_description,
+ created_at: group_issue.created_at
+ end
+
+ it 'page breaks first page correctly' do
+ get api("#{base_url}?per_page=3", user)
+
+ expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id, group_issue2.id])
+ end
+
+ it 'page breaks second page correctly' do
+ get api("#{base_url}?per_page=3&page=2", user)
+
+ expect_paginated_array_response([group_issue.id])
+ end
+ end
end
it 'sorts ascending when requested' do
@@ -708,6 +806,18 @@ describe API::Issues do
expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
end
+ it 'returns only confidential issues' do
+ get api("#{base_url}/issues", author), params: { confidential: true }
+
+ expect_paginated_array_response(confidential_issue.id)
+ end
+
+ it 'returns only public issues' do
+ get api("#{base_url}/issues", author), params: { confidential: false }
+
+ expect_paginated_array_response([issue.id, closed_issue.id])
+ end
+
it 'returns project confidential issues for assignee' do
get api("#{base_url}/issues", assignee)
@@ -763,7 +873,7 @@ describe API::Issues do
end
it 'returns an empty array if iid does not exist' do
- get api("#{base_url}/issues", user), params: { iids: [99999] }
+ get api("#{base_url}/issues", user), params: { iids: [0] }
expect_paginated_array_response([])
end
@@ -828,10 +938,38 @@ describe API::Issues do
expect_paginated_array_response([issue.id, closed_issue.id])
end
- it 'sorts by created_at descending by default' do
- get api("#{base_url}/issues", user)
+ context 'without sort params' do
+ it 'sorts by created_at descending by default' do
+ get api("#{base_url}/issues", user)
- expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
+ expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
+ end
+
+ context 'with 2 issues with same created_at' do
+ let!(:closed_issue2) do
+ create :closed_issue,
+ author: user,
+ assignees: [user],
+ project: project,
+ milestone: milestone,
+ created_at: closed_issue.created_at,
+ updated_at: 1.hour.ago,
+ title: issue_title,
+ description: issue_description
+ end
+
+ it 'page breaks first page correctly' do
+ get api("#{base_url}/issues?per_page=3", user)
+
+ expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue2.id])
+ end
+
+ it 'page breaks second page correctly' do
+ get api("#{base_url}/issues?per_page=3&page=2", user)
+
+ expect_paginated_array_response([closed_issue.id])
+ end
+ end
end
it 'sorts ascending when requested' do
@@ -1771,7 +1909,7 @@ describe API::Issues do
end
it "returns 404 when issue doesn't exists" do
- get api("/projects/#{project.id}/issues/9999/closed_by", user)
+ get api("/projects/#{project.id}/issues/0/closed_by", user)
expect(response).to have_gitlab_http_status(404)
end
@@ -1792,7 +1930,7 @@ describe API::Issues do
description: "See #{issue.to_reference}"
}
create(:merge_request, attributes).tap do |merge_request|
- create(:note, :system, project: project, noteable: issue, author: user, note: merge_request.to_reference(full: true))
+ create(:note, :system, project: issue.project, noteable: issue, author: user, note: merge_request.to_reference(full: true))
end
end
@@ -1829,6 +1967,24 @@ describe API::Issues do
expect_paginated_array_response(related_mr.id)
end
+ it 'returns merge requests cross-project wide' do
+ project2 = create(:project, :public, creator_id: user.id, namespace: user.namespace)
+ merge_request = create_referencing_mr(user, project2, issue)
+
+ get_related_merge_requests(project.id, issue.iid, user)
+
+ expect_paginated_array_response([related_mr.id, merge_request.id])
+ end
+
+ it 'does not generate references to projects with no access' do
+ private_project = create(:project, :private)
+ create_referencing_mr(private_project.creator, private_project, issue)
+
+ get_related_merge_requests(project.id, issue.iid, user)
+
+ expect_paginated_array_response(related_mr.id)
+ end
+
context 'no merge request mentioned a issue' do
it 'returns empty array' do
get_related_merge_requests(project.id, closed_issue.iid, user)
@@ -1838,7 +1994,7 @@ describe API::Issues do
end
it "returns 404 when issue doesn't exists" do
- get_related_merge_requests(project.id, 999999, user)
+ get_related_merge_requests(project.id, 0, user)
expect(response).to have_gitlab_http_status(404)
end
diff --git a/spec/requests/api/keys_spec.rb b/spec/requests/api/keys_spec.rb
index 3c4719964b6..f37d84fddef 100644
--- a/spec/requests/api/keys_spec.rb
+++ b/spec/requests/api/keys_spec.rb
@@ -16,7 +16,7 @@ describe API::Keys do
context 'when authenticated' do
it 'returns 404 for non-existing key' do
- get api('/keys/999999', admin)
+ get api('/keys/0', admin)
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 Not found')
end
diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb
index 49eea2e362b..518181e4d93 100644
--- a/spec/requests/api/labels_spec.rb
+++ b/spec/requests/api/labels_spec.rb
@@ -20,9 +20,9 @@ describe API::Labels do
create(:labeled_merge_request, labels: [priority_label], author: user, source_project: project )
expected_keys = %w(
- id name color description
+ id name color text_color description
open_issues_count closed_issues_count open_merge_requests_count
- subscribed priority
+ subscribed priority is_project_label
)
get api("/projects/#{project.id}/labels", user)
@@ -43,27 +43,33 @@ describe API::Labels do
expect(label1_response['open_merge_requests_count']).to eq(0)
expect(label1_response['name']).to eq(label1.name)
expect(label1_response['color']).to be_present
+ expect(label1_response['text_color']).to be_present
expect(label1_response['description']).to be_nil
expect(label1_response['priority']).to be_nil
expect(label1_response['subscribed']).to be_falsey
+ expect(label1_response['is_project_label']).to be_truthy
expect(group_label_response['open_issues_count']).to eq(1)
expect(group_label_response['closed_issues_count']).to eq(0)
expect(group_label_response['open_merge_requests_count']).to eq(0)
expect(group_label_response['name']).to eq(group_label.name)
expect(group_label_response['color']).to be_present
+ expect(group_label_response['text_color']).to be_present
expect(group_label_response['description']).to be_nil
expect(group_label_response['priority']).to be_nil
expect(group_label_response['subscribed']).to be_falsey
+ expect(group_label_response['is_project_label']).to be_falsey
expect(priority_label_response['open_issues_count']).to eq(0)
expect(priority_label_response['closed_issues_count']).to eq(0)
expect(priority_label_response['open_merge_requests_count']).to eq(1)
expect(priority_label_response['name']).to eq(priority_label.name)
expect(priority_label_response['color']).to be_present
+ expect(priority_label_response['text_color']).to be_present
expect(priority_label_response['description']).to be_nil
expect(priority_label_response['priority']).to eq(3)
expect(priority_label_response['subscribed']).to be_falsey
+ expect(priority_label_response['is_project_label']).to be_truthy
end
end
diff --git a/spec/requests/api/merge_request_diffs_spec.rb b/spec/requests/api/merge_request_diffs_spec.rb
index 6530dc956cb..8a67d98fc4c 100644
--- a/spec/requests/api/merge_request_diffs_spec.rb
+++ b/spec/requests/api/merge_request_diffs_spec.rb
@@ -30,7 +30,7 @@ describe API::MergeRequestDiffs, 'MergeRequestDiffs' do
end
it 'returns a 404 when merge_request_iid not found' do
- get api("/projects/#{project.id}/merge_requests/999/versions", user)
+ get api("/projects/#{project.id}/merge_requests/0/versions", user)
expect(response).to have_gitlab_http_status(404)
end
end
@@ -53,7 +53,7 @@ describe API::MergeRequestDiffs, 'MergeRequestDiffs' do
end
it 'returns a 404 when merge_request version_id is not found' do
- get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/versions/999", user)
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/versions/0", user)
expect(response).to have_gitlab_http_status(404)
end
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 0f5f6e38819..6272bb38d59 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -320,6 +320,18 @@ describe API::MergeRequests do
expect(json_response.first['title']).to eq merge_request_closed.title
expect(json_response.first['id']).to eq merge_request_closed.id
end
+
+ it 'avoids N+1 queries' do
+ control = ActiveRecord::QueryRecorder.new do
+ get api("/projects/#{project.id}/merge_requests", user)
+ end.count
+
+ create(:merge_request, author: user, assignee: user, source_project: project, target_project: project, created_at: base_time)
+
+ expect do
+ get api("/projects/#{project.id}/merge_requests", user)
+ end.not_to exceed_query_limit(control)
+ end
end
describe "GET /groups/:id/merge_requests" do
@@ -372,6 +384,7 @@ describe API::MergeRequests do
expect(json_response['force_close_merge_request']).to be_falsy
expect(json_response['changes_count']).to eq(merge_request.merge_request_diff.real_size)
expect(json_response['merge_error']).to eq(merge_request.merge_error)
+ expect(json_response['user']['can_merge']).to be_truthy
expect(json_response).not_to include('rebase_in_progress')
end
@@ -440,7 +453,7 @@ describe API::MergeRequests do
end
it "returns a 404 error if merge_request_iid not found" do
- get api("/projects/#{project.id}/merge_requests/999", user)
+ get api("/projects/#{project.id}/merge_requests/0", user)
expect(response).to have_gitlab_http_status(404)
end
@@ -499,6 +512,15 @@ describe API::MergeRequests do
expect(json_response['allow_maintainer_to_push']).to be_truthy
end
end
+
+ it 'indicates if a user cannot merge the MR' do
+ user2 = create(:user)
+ project.add_reporter(user2)
+
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user2)
+
+ expect(json_response['user']['can_merge']).to be_falsy
+ end
end
describe 'GET /projects/:id/merge_requests/:merge_request_iid/participants' do
@@ -521,7 +543,7 @@ describe API::MergeRequests do
end
it 'returns a 404 when merge_request_iid not found' do
- get api("/projects/#{project.id}/merge_requests/999/commits", user)
+ get api("/projects/#{project.id}/merge_requests/0/commits", user)
expect(response).to have_gitlab_http_status(404)
end
@@ -541,7 +563,7 @@ describe API::MergeRequests do
end
it 'returns a 404 when merge_request_iid not found' do
- get api("/projects/#{project.id}/merge_requests/999/changes", user)
+ get api("/projects/#{project.id}/merge_requests/0/changes", user)
expect(response).to have_gitlab_http_status(404)
end
@@ -974,6 +996,82 @@ describe API::MergeRequests do
expect(squash_commit.message).to eq(merge_request.default_squash_commit_message)
end
end
+
+ describe "the should_remove_source_branch param" do
+ let(:source_repository) { merge_request.source_project.repository }
+ let(:source_branch) { merge_request.source_branch }
+
+ it 'removes the source branch when set' do
+ put(
+ api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user),
+ params: { should_remove_source_branch: true }
+ )
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(source_repository.branch_exists?(source_branch)).to be_falsy
+ end
+ end
+ end
+
+ describe "PUT /projects/:id/merge_requests/:merge_request_iid/merge_to_ref" do
+ let(:pipeline) { create(:ci_pipeline_without_jobs) }
+ let(:url) do
+ "/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge_to_ref"
+ end
+
+ it 'returns the generated ID from the merge service in case of success' do
+ put api(url, user), params: { merge_commit_message: 'Custom message' }
+
+ commit = project.commit(json_response['commit_id'])
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['commit_id']).to be_present
+ expect(commit.message).to eq('Custom message')
+ end
+
+ it "returns 400 if branch can't be merged" do
+ merge_request.update!(state: 'merged')
+
+ put api(url, user)
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message'])
+ .to eq("Merge request is not mergeable to #{merge_request.merge_ref_path}")
+ end
+
+ it 'returns 403 if user has no permissions to merge to the ref' do
+ user2 = create(:user)
+ project.add_reporter(user2)
+
+ put api(url, user2)
+
+ expect(response).to have_gitlab_http_status(403)
+ expect(json_response['message']).to eq('403 Forbidden')
+ end
+
+ it 'returns 404 for an invalid merge request IID' do
+ put api("/projects/#{project.id}/merge_requests/12345/merge_to_ref", user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ it "returns 404 if the merge request id is used instead of iid" do
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ it "returns 400 when merge method is not supported" do
+ merge_request.project.update!(merge_method: 'ff')
+
+ put api(url, user)
+
+ expected_error =
+ 'Fast-forward to refs/merge-requests/1/merge is currently not supported.'
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']).to eq(expected_error)
+ end
end
describe "PUT /projects/:id/merge_requests/:merge_request_iid" do
diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb
index 145356c4df5..2e376109b42 100644
--- a/spec/requests/api/namespaces_spec.rb
+++ b/spec/requests/api/namespaces_spec.rb
@@ -149,7 +149,7 @@ describe API::Namespaces do
context "when namespace doesn't exist" do
it 'returns not-found' do
- get api('/namespaces/9999', request_actor)
+ get api('/namespaces/0', request_actor)
expect(response).to have_gitlab_http_status(404)
end
diff --git a/spec/requests/api/project_milestones_spec.rb b/spec/requests/api/project_milestones_spec.rb
index 49b5dfb0b33..895f05a98e8 100644
--- a/spec/requests/api/project_milestones_spec.rb
+++ b/spec/requests/api/project_milestones_spec.rb
@@ -23,13 +23,13 @@ describe API::ProjectMilestones do
end
it 'returns 404 response when the project does not exists' do
- delete api("/projects/999/milestones/#{milestone.id}", user)
+ delete api("/projects/0/milestones/#{milestone.id}", user)
expect(response).to have_gitlab_http_status(404)
end
it 'returns 404 response when the milestone does not exists' do
- delete api("/projects/#{project.id}/milestones/999", user)
+ delete api("/projects/#{project.id}/milestones/0", user)
expect(response).to have_gitlab_http_status(404)
end
@@ -49,4 +49,74 @@ describe API::ProjectMilestones do
params: { state_event: 'close' }
end
end
+
+ describe 'POST /projects/:id/milestones/:milestone_id/promote' do
+ let(:group) { create(:group) }
+
+ before do
+ project.update(namespace: group)
+ end
+
+ context 'when user does not have permission to promote milestone' do
+ before do
+ group.add_guest(user)
+ end
+
+ it 'returns 403' do
+ post api("/projects/#{project.id}/milestones/#{milestone.id}/promote", user)
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+
+ context 'when user has permission' do
+ before do
+ group.add_developer(user)
+ end
+
+ it 'returns 200' do
+ post api("/projects/#{project.id}/milestones/#{milestone.id}/promote", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(group.milestones.first.title).to eq(milestone.title)
+ end
+
+ it 'returns 200 for closed milestone' do
+ post api("/projects/#{project.id}/milestones/#{closed_milestone.id}/promote", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(group.milestones.first.title).to eq(closed_milestone.title)
+ end
+ end
+
+ context 'when no such resources' do
+ before do
+ group.add_developer(user)
+ end
+
+ it 'returns 404 response when the project does not exist' do
+ post api("/projects/0/milestones/#{milestone.id}/promote", user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ it 'returns 404 response when the milestone does not exist' do
+ post api("/projects/#{project.id}/milestones/0/promote", user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ context 'when project does not belong to group' do
+ before do
+ project.update(namespace: user.namespace)
+ end
+
+ it 'returns 403' do
+ post api("/projects/#{project.id}/milestones/#{milestone.id}/promote", user)
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+ end
end
diff --git a/spec/requests/api/project_statistics_spec.rb b/spec/requests/api/project_statistics_spec.rb
new file mode 100644
index 00000000000..184d0a72c37
--- /dev/null
+++ b/spec/requests/api/project_statistics_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe API::ProjectStatistics do
+ let(:maintainer) { create(:user) }
+ let(:public_project) { create(:project, :public) }
+
+ before do
+ public_project.add_maintainer(maintainer)
+ end
+
+ describe 'GET /projects/:id/statistics' do
+ let!(:fetch_statistics1) { create(:project_daily_statistic, project: public_project, fetch_count: 30, date: 29.days.ago) }
+ let!(:fetch_statistics2) { create(:project_daily_statistic, project: public_project, fetch_count: 4, date: 3.days.ago) }
+ let!(:fetch_statistics3) { create(:project_daily_statistic, project: public_project, fetch_count: 3, date: 2.days.ago) }
+ let!(:fetch_statistics4) { create(:project_daily_statistic, project: public_project, fetch_count: 2, date: 1.day.ago) }
+ let!(:fetch_statistics5) { create(:project_daily_statistic, project: public_project, fetch_count: 1, date: Date.today) }
+ let!(:fetch_statistics_other_project) { create(:project_daily_statistic, project: create(:project), fetch_count: 29, date: 29.days.ago) }
+
+ it 'returns the fetch statistics of the last 30 days' do
+ get api("/projects/#{public_project.id}/statistics", maintainer)
+
+ expect(response).to have_gitlab_http_status(200)
+ fetches = json_response['fetches']
+ expect(fetches['total']).to eq(40)
+ expect(fetches['days'].length).to eq(5)
+ expect(fetches['days'].first).to eq({ 'count' => fetch_statistics5.fetch_count, 'date' => fetch_statistics5.date.to_s })
+ expect(fetches['days'].last).to eq({ 'count' => fetch_statistics1.fetch_count, 'date' => fetch_statistics1.date.to_s })
+ end
+
+ it 'excludes the fetch statistics older than 30 days' do
+ create(:project_daily_statistic, fetch_count: 31, project: public_project, date: 30.days.ago)
+
+ get api("/projects/#{public_project.id}/statistics", maintainer)
+
+ expect(response).to have_gitlab_http_status(200)
+ fetches = json_response['fetches']
+ expect(fetches['total']).to eq(40)
+ expect(fetches['days'].length).to eq(5)
+ expect(fetches['days'].last).to eq({ 'count' => fetch_statistics1.fetch_count, 'date' => fetch_statistics1.date.to_s })
+ end
+
+ it 'responds with 403 when the user is not a maintainer of the repository' do
+ developer = create(:user)
+ public_project.add_developer(developer)
+
+ get api("/projects/#{public_project.id}/statistics", developer)
+
+ expect(response).to have_gitlab_http_status(403)
+ expect(json_response['message']).to eq('403 Forbidden')
+ end
+
+ it 'responds with 404 when daily_statistics_enabled? is false' do
+ stub_feature_flags(project_daily_statistics: { thing: public_project, enabled: false })
+
+ get api("/projects/#{public_project.id}/statistics", maintainer)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+end
diff --git a/spec/requests/api/project_templates_spec.rb b/spec/requests/api/project_templates_spec.rb
index ab5d4de7ff7..80e5033dab4 100644
--- a/spec/requests/api/project_templates_spec.rb
+++ b/spec/requests/api/project_templates_spec.rb
@@ -92,6 +92,22 @@ describe API::ProjectTemplates do
expect(json_response['name']).to eq('Actionscript')
end
+ it 'returns C++ gitignore' do
+ get api("/projects/#{public_project.id}/templates/gitignores/C++")
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/template')
+ expect(json_response['name']).to eq('C++')
+ end
+
+ it 'returns C++ gitignore for URL-encoded names' do
+ get api("/projects/#{public_project.id}/templates/gitignores/C%2B%2B")
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/template')
+ expect(json_response['name']).to eq('C++')
+ end
+
it 'returns a specific gitlab_ci_yml' do
get api("/projects/#{public_project.id}/templates/gitlab_ci_ymls/Android")
@@ -125,6 +141,18 @@ describe API::ProjectTemplates do
expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('public_api/v4/license')
end
+
+ shared_examples 'path traversal attempt' do |template_type|
+ it 'rejects invalid filenames' do
+ get api("/projects/#{public_project.id}/templates/#{template_type}/%2e%2e%2fPython%2ea")
+
+ expect(response).to have_gitlab_http_status(500)
+ end
+ end
+
+ TemplateFinder::VENDORED_TEMPLATES.each do |template_type, _|
+ it_behaves_like 'path traversal attempt', template_type
+ end
end
describe 'GET /projects/:id/templates/licenses/:key' do
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index cfa7a1a31a3..856fe1bbe89 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -4,6 +4,15 @@ require 'spec_helper'
shared_examples 'languages and percentages JSON response' do
let(:expected_languages) { project.repository.languages.map { |language| language.values_at(:label, :value)}.to_h }
+ before do
+ allow(project.repository).to receive(:languages).and_return(
+ [{ value: 66.69, label: "Ruby", color: "#701516", highlight: "#701516" },
+ { value: 22.98, label: "JavaScript", color: "#f1e05a", highlight: "#f1e05a" },
+ { value: 7.91, label: "HTML", color: "#e34c26", highlight: "#e34c26" },
+ { value: 2.42, label: "CoffeeScript", color: "#244776", highlight: "#244776" }]
+ )
+ end
+
it 'returns expected language values' do
get api("/projects/#{project.id}/languages", user)
@@ -11,6 +20,23 @@ shared_examples 'languages and percentages JSON response' do
expect(json_response).to eq(expected_languages)
expect(json_response.count).to be > 1
end
+
+ context 'when the languages were detected before' do
+ before do
+ Projects::DetectRepositoryLanguagesService.new(project, project.owner).execute
+ end
+
+ it 'returns the detection from the database' do
+ # Allow this to happen once, so the expected languages can be determined
+ expect(project.repository).to receive(:languages).once
+
+ get api("/projects/#{project.id}/languages", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to eq(expected_languages)
+ expect(json_response.count).to be > 1
+ end
+ end
end
describe API::Projects do
@@ -752,7 +778,7 @@ describe API::Projects do
let!(:public_project) { create(:project, :public, name: 'public_project', creator_id: user4.id, namespace: user4.namespace) }
it 'returns error when user not found' do
- get api('/users/9999/projects/')
+ get api('/users/0/projects/')
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
@@ -1359,7 +1385,7 @@ describe API::Projects do
end
it 'fails if forked_from project which does not exist' do
- post api("/projects/#{project_fork_target.id}/fork/9999", admin)
+ post api("/projects/#{project_fork_target.id}/fork/0", admin)
expect(response).to have_gitlab_http_status(404)
end
@@ -1910,7 +1936,7 @@ describe API::Projects do
end
it 'returns not_found(404) for not existing project' do
- get api("/projects/9999999999/languages", user)
+ get api("/projects/0/languages", user)
expect(response).to have_gitlab_http_status(:not_found)
end
@@ -1995,6 +2021,11 @@ describe API::Projects do
let(:project) do
create(:project, :repository, creator: user, namespace: user.namespace)
end
+
+ let(:project2) do
+ create(:project, :repository, creator: user, namespace: user.namespace)
+ end
+
let(:group) { create(:group) }
let(:group2) do
group = create(:group, name: 'group2_name')
@@ -2010,6 +2041,7 @@ describe API::Projects do
before do
project.add_reporter(user2)
+ project2.add_reporter(user2)
end
context 'when authenticated' do
@@ -2124,6 +2156,48 @@ describe API::Projects do
expect(response).to have_gitlab_http_status(201)
expect(json_response['namespace']['name']).to eq(group.name)
end
+
+ it 'accepts a path for the target project' do
+ post api("/projects/#{project.id}/fork", user2), params: { path: 'foobar' }
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['name']).to eq(project.name)
+ expect(json_response['path']).to eq('foobar')
+ expect(json_response['owner']['id']).to eq(user2.id)
+ expect(json_response['namespace']['id']).to eq(user2.namespace.id)
+ expect(json_response['forked_from_project']['id']).to eq(project.id)
+ expect(json_response['import_status']).to eq('scheduled')
+ expect(json_response).to include("import_error")
+ end
+
+ it 'fails to fork if path is already taken' do
+ post api("/projects/#{project.id}/fork", user2), params: { path: 'foobar' }
+ post api("/projects/#{project2.id}/fork", user2), params: { path: 'foobar' }
+
+ expect(response).to have_gitlab_http_status(409)
+ expect(json_response['message']['path']).to eq(['has already been taken'])
+ end
+
+ it 'accepts a name for the target project' do
+ post api("/projects/#{project.id}/fork", user2), params: { name: 'My Random Project' }
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['name']).to eq('My Random Project')
+ expect(json_response['path']).to eq(project.path)
+ expect(json_response['owner']['id']).to eq(user2.id)
+ expect(json_response['namespace']['id']).to eq(user2.namespace.id)
+ expect(json_response['forked_from_project']['id']).to eq(project.id)
+ expect(json_response['import_status']).to eq('scheduled')
+ expect(json_response).to include("import_error")
+ end
+
+ it 'fails to fork if name is already taken' do
+ post api("/projects/#{project.id}/fork", user2), params: { name: 'My Random Project' }
+ post api("/projects/#{project2.id}/fork", user2), params: { name: 'My Random Project' }
+
+ expect(response).to have_gitlab_http_status(409)
+ expect(json_response['message']['name']).to eq(['has already been taken'])
+ end
end
context 'when unauthenticated' do
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
index d7ddd97e8c8..9087cccb759 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/runner_spec.rb
@@ -417,7 +417,9 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
'ref' => job.ref,
'sha' => job.sha,
'before_sha' => job.before_sha,
- 'ref_type' => 'branch' }
+ 'ref_type' => 'branch',
+ 'refspecs' => %w[+refs/heads/*:refs/remotes/origin/* +refs/tags/*:refs/tags/*],
+ 'depth' => 0 }
end
let(:expected_steps) do
@@ -434,9 +436,9 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
end
let(:expected_variables) do
- [{ 'key' => 'CI_JOB_NAME', 'value' => 'spinach', 'public' => true },
- { 'key' => 'CI_JOB_STAGE', 'value' => 'test', 'public' => true },
- { 'key' => 'DB_NAME', 'value' => 'postgres', 'public' => true }]
+ [{ 'key' => 'CI_JOB_NAME', 'value' => 'spinach', 'public' => true, 'masked' => false },
+ { 'key' => 'CI_JOB_STAGE', 'value' => 'test', 'public' => true, 'masked' => false },
+ { 'key' => 'DB_NAME', 'value' => 'postgres', 'public' => true, 'masked' => false }]
end
let(:expected_artifacts) do
@@ -489,6 +491,29 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
expect(response).to have_gitlab_http_status(201)
expect(json_response['git_info']['ref_type']).to eq('tag')
end
+
+ context 'when GIT_DEPTH is specified' do
+ before do
+ create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 1, pipeline: pipeline)
+ end
+
+ it 'specifies refspecs' do
+ request_job
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['git_info']['refspecs']).to include("+refs/tags/#{job.ref}:refs/tags/#{job.ref}")
+ end
+ end
+
+ context 'when GIT_DEPTH is not specified' do
+ it 'specifies refspecs' do
+ request_job
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['git_info']['refspecs'])
+ .to contain_exactly('+refs/tags/*:refs/tags/*', '+refs/heads/*:refs/remotes/origin/*')
+ end
+ end
end
context 'when job is made for branch' do
@@ -498,6 +523,55 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
expect(response).to have_gitlab_http_status(201)
expect(json_response['git_info']['ref_type']).to eq('branch')
end
+
+ context 'when GIT_DEPTH is specified' do
+ before do
+ create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 1, pipeline: pipeline)
+ end
+
+ it 'specifies refspecs' do
+ request_job
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['git_info']['refspecs']).to include("+refs/heads/#{job.ref}:refs/remotes/origin/#{job.ref}")
+ end
+ end
+
+ context 'when GIT_DEPTH is not specified' do
+ it 'specifies refspecs' do
+ request_job
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['git_info']['refspecs'])
+ .to contain_exactly('+refs/tags/*:refs/tags/*', '+refs/heads/*:refs/remotes/origin/*')
+ end
+ end
+ end
+
+ context 'when job is made for merge request' do
+ let(:pipeline) { create(:ci_pipeline_without_jobs, source: :merge_request_event, project: project, ref: 'feature', merge_request: merge_request) }
+ let!(:job) { create(:ci_build, pipeline: pipeline, name: 'spinach', ref: 'feature', stage: 'test', stage_idx: 0) }
+ let(:merge_request) { create(:merge_request) }
+
+ it 'sets branch as ref_type' do
+ request_job
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['git_info']['ref_type']).to eq('branch')
+ end
+
+ context 'when GIT_DEPTH is specified' do
+ before do
+ create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 1, pipeline: pipeline)
+ end
+
+ it 'returns the overwritten git depth for merge request refspecs' do
+ request_job
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['git_info']['depth']).to eq(1)
+ end
+ end
end
it 'updates runner info' do
@@ -526,6 +600,15 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
expect(runner.reload.ip_address).to eq('123.222.123.222')
end
+ it "handles multiple X-Forwarded-For addresses" do
+ post api('/jobs/request'),
+ params: { token: runner.token },
+ headers: { 'User-Agent' => user_agent, 'X-Forwarded-For' => '123.222.123.222, 127.0.0.1' }
+
+ expect(response).to have_gitlab_http_status 201
+ expect(runner.reload.ip_address).to eq('123.222.123.222')
+ end
+
context 'when concurrently updating a job' do
before do
expect_any_instance_of(Ci::Build).to receive(:run!)
@@ -657,12 +740,12 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
context 'when triggered job is available' do
let(:expected_variables) do
- [{ 'key' => 'CI_JOB_NAME', 'value' => 'spinach', 'public' => true },
- { 'key' => 'CI_JOB_STAGE', 'value' => 'test', 'public' => true },
- { 'key' => 'CI_PIPELINE_TRIGGERED', 'value' => 'true', 'public' => true },
- { 'key' => 'DB_NAME', 'value' => 'postgres', 'public' => true },
- { 'key' => 'SECRET_KEY', 'value' => 'secret_value', 'public' => false },
- { 'key' => 'TRIGGER_KEY_1', 'value' => 'TRIGGER_VALUE_1', 'public' => false }]
+ [{ 'key' => 'CI_JOB_NAME', 'value' => 'spinach', 'public' => true, 'masked' => false },
+ { 'key' => 'CI_JOB_STAGE', 'value' => 'test', 'public' => true, 'masked' => false },
+ { 'key' => 'CI_PIPELINE_TRIGGERED', 'value' => 'true', 'public' => true, 'masked' => false },
+ { 'key' => 'DB_NAME', 'value' => 'postgres', 'public' => true, 'masked' => false },
+ { 'key' => 'SECRET_KEY', 'value' => 'secret_value', 'public' => false, 'masked' => false },
+ { 'key' => 'TRIGGER_KEY_1', 'value' => 'TRIGGER_VALUE_1', 'public' => false, 'masked' => false }]
end
let(:trigger) { create(:ci_trigger, project: project) }
diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb
index 7f11c8c9fe8..5548e3fd01a 100644
--- a/spec/requests/api/runners_spec.rb
+++ b/spec/requests/api/runners_spec.rb
@@ -90,6 +90,17 @@ describe API::Runners do
expect(response).to have_gitlab_http_status(400)
end
+
+ it 'filters runners by tag_list' do
+ create(:ci_runner, :project, description: 'Runner tagged with tag1 and tag2', projects: [project], tag_list: %w[tag1 tag2])
+ create(:ci_runner, :project, description: 'Runner tagged with tag2', projects: [project], tag_list: ['tag2'])
+
+ get api('/runners?tag_list=tag1,tag2', user)
+
+ expect(json_response).to match_array [
+ a_hash_including('description' => 'Runner tagged with tag1 and tag2')
+ ]
+ end
end
context 'unauthorized user' do
@@ -181,6 +192,17 @@ describe API::Runners do
expect(response).to have_gitlab_http_status(400)
end
+
+ it 'filters runners by tag_list' do
+ create(:ci_runner, :project, description: 'Runner tagged with tag1 and tag2', projects: [project], tag_list: %w[tag1 tag2])
+ create(:ci_runner, :project, description: 'Runner tagged with tag2', projects: [project], tag_list: ['tag2'])
+
+ get api('/runners/all?tag_list=tag1,tag2', admin)
+
+ expect(json_response).to match_array [
+ a_hash_including('description' => 'Runner tagged with tag1 and tag2')
+ ]
+ end
end
context 'without admin privileges' do
@@ -241,7 +263,7 @@ describe API::Runners do
end
it 'returns 404 if runner does not exists' do
- get api('/runners/9999', admin)
+ get api('/runners/0', admin)
expect(response).to have_gitlab_http_status(404)
end
@@ -394,7 +416,7 @@ describe API::Runners do
end
it 'returns 404 if runner does not exists' do
- update_runner(9999, admin, description: 'test')
+ update_runner(0, admin, description: 'test')
expect(response).to have_gitlab_http_status(404)
end
@@ -468,7 +490,7 @@ describe API::Runners do
end
it 'returns 404 if runner does not exists' do
- delete api('/runners/9999', admin)
+ delete api('/runners/0', admin)
expect(response).to have_gitlab_http_status(404)
end
@@ -573,7 +595,7 @@ describe API::Runners do
context "when runner doesn't exist" do
it 'returns 404' do
- get api('/runners/9999/jobs', admin)
+ get api('/runners/0/jobs', admin)
expect(response).to have_gitlab_http_status(404)
end
@@ -626,7 +648,7 @@ describe API::Runners do
context "when runner doesn't exist" do
it 'returns 404' do
- get api('/runners/9999/jobs', user)
+ get api('/runners/0/jobs', user)
expect(response).to have_gitlab_http_status(404)
end
@@ -716,6 +738,17 @@ describe API::Runners do
expect(response).to have_gitlab_http_status(400)
end
+
+ it 'filters runners by tag_list' do
+ create(:ci_runner, :project, description: 'Runner tagged with tag1 and tag2', projects: [project], tag_list: %w[tag1 tag2])
+ create(:ci_runner, :project, description: 'Runner tagged with tag2', projects: [project], tag_list: ['tag2'])
+
+ get api("/projects/#{project.id}/runners?tag_list=tag1,tag2", user)
+
+ expect(json_response).to match_array [
+ a_hash_including('description' => 'Runner tagged with tag1 and tag2')
+ ]
+ end
end
context 'authorized user without maintainer privileges' do
@@ -857,7 +890,7 @@ describe API::Runners do
end
it 'returns 404 is runner is not found' do
- delete api("/projects/#{project.id}/runners/9999", user)
+ delete api("/projects/#{project.id}/runners/0", user)
expect(response).to have_gitlab_http_status(404)
end
diff --git a/spec/requests/api/search_spec.rb b/spec/requests/api/search_spec.rb
index 831f47debeb..c48ca832c85 100644
--- a/spec/requests/api/search_spec.rb
+++ b/spec/requests/api/search_spec.rb
@@ -126,7 +126,7 @@ describe API::Search do
context 'when group does not exist' do
it 'returns 404 error' do
- get api('/groups/9999/search', user), params: { scope: 'issues', search: 'awesome' }
+ get api('/groups/0/search', user), params: { scope: 'issues', search: 'awesome' }
expect(response).to have_gitlab_http_status(404)
end
@@ -222,7 +222,7 @@ describe API::Search do
context 'when project does not exist' do
it 'returns 404 error' do
- get api('/projects/9999/search', user), params: { scope: 'issues', search: 'awesome' }
+ get api('/projects/0/search', user), params: { scope: 'issues', search: 'awesome' }
expect(response).to have_gitlab_http_status(404)
end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index b381431306d..a879426589d 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -335,7 +335,7 @@ describe API::Users do
end
it "returns a 404 error if user id not found" do
- get api("/users/9999", user)
+ get api("/users/0", user)
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
@@ -732,7 +732,7 @@ describe API::Users do
end
it "returns 404 for non-existing user" do
- put api("/users/999999", admin), params: { bio: 'update should fail' }
+ put api("/users/0", admin), params: { bio: 'update should fail' }
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
@@ -836,7 +836,7 @@ describe API::Users do
end
it "returns 400 for invalid ID" do
- post api("/users/999999/keys", admin)
+ post api("/users/0/keys", admin)
expect(response).to have_gitlab_http_status(400)
end
end
@@ -895,7 +895,7 @@ describe API::Users do
it 'returns 404 error if user not found' do
user.keys << key
user.save
- delete api("/users/999999/keys/#{key.id}", admin)
+ delete api("/users/0/keys/#{key.id}", admin)
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
end
@@ -930,7 +930,7 @@ describe API::Users do
end
it 'returns 400 for invalid ID' do
- post api('/users/999999/gpg_keys', admin)
+ post api('/users/0/gpg_keys', admin)
expect(response).to have_gitlab_http_status(400)
end
@@ -951,7 +951,7 @@ describe API::Users do
context 'when authenticated' do
it 'returns 404 for non-existing user' do
- get api('/users/999999/gpg_keys', admin)
+ get api('/users/0/gpg_keys', admin)
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
@@ -1007,7 +1007,7 @@ describe API::Users do
user.keys << key
user.save
- delete api("/users/999999/gpg_keys/#{gpg_key.id}", admin)
+ delete api("/users/0/gpg_keys/#{gpg_key.id}", admin)
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
@@ -1051,7 +1051,7 @@ describe API::Users do
user.gpg_keys << gpg_key
user.save
- post api("/users/999999/gpg_keys/#{gpg_key.id}/revoke", admin)
+ post api("/users/0/gpg_keys/#{gpg_key.id}/revoke", admin)
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
@@ -1089,7 +1089,7 @@ describe API::Users do
end
it "returns a 400 for invalid ID" do
- post api("/users/999999/emails", admin)
+ post api("/users/0/emails", admin)
expect(response).to have_gitlab_http_status(400)
end
@@ -1121,7 +1121,7 @@ describe API::Users do
context 'when authenticated' do
it 'returns 404 for non-existing user' do
- get api('/users/999999/emails', admin)
+ get api('/users/0/emails', admin)
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
end
@@ -1177,7 +1177,7 @@ describe API::Users do
it 'returns 404 error if user not found' do
user.emails << email
user.save
- delete api("/users/999999/emails/#{email.id}", admin)
+ delete api("/users/0/emails/#{email.id}", admin)
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
end
@@ -1227,7 +1227,7 @@ describe API::Users do
end
it "returns 404 for non-existing user" do
- perform_enqueued_jobs { delete api("/users/999999", admin) }
+ perform_enqueued_jobs { delete api("/users/0", admin) }
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
end
@@ -1778,7 +1778,7 @@ describe API::Users do
end
it 'returns a 404 error if user id not found' do
- post api('/users/9999/block', admin)
+ post api('/users/0/block', admin)
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
end
@@ -1816,7 +1816,7 @@ describe API::Users do
end
it 'returns a 404 error if user id not found' do
- post api('/users/9999/block', admin)
+ post api('/users/0/block', admin)
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
end
diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb
index cdac5b2f400..5df6baf0ddf 100644
--- a/spec/requests/api/variables_spec.rb
+++ b/spec/requests/api/variables_spec.rb
@@ -73,12 +73,12 @@ describe API::Variables do
context 'authorized user with proper permissions' do
it 'creates variable' do
expect do
- post api("/projects/#{project.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'VALUE_2', protected: true }
+ post api("/projects/#{project.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'PROTECTED_VALUE_2', protected: true }
end.to change {project.variables.count}.by(1)
expect(response).to have_gitlab_http_status(201)
expect(json_response['key']).to eq('TEST_VARIABLE_2')
- expect(json_response['value']).to eq('VALUE_2')
+ expect(json_response['value']).to eq('PROTECTED_VALUE_2')
expect(json_response['protected']).to be_truthy
end
diff --git a/spec/requests/api/wikis_spec.rb b/spec/requests/api/wikis_spec.rb
index 6109829aad1..d1b58aac104 100644
--- a/spec/requests/api/wikis_spec.rb
+++ b/spec/requests/api/wikis_spec.rb
@@ -100,6 +100,8 @@ describe API::Wikis do
shared_examples_for 'updates wiki page' do
it 'updates the wiki page' do
+ put(api(url, user), params: payload)
+
expect(response).to have_gitlab_http_status(200)
expect(json_response.size).to eq(4)
expect(json_response.keys).to match_array(expected_keys_with_content)
@@ -107,6 +109,16 @@ describe API::Wikis do
expect(json_response['slug']).to eq(payload[:title].tr(' ', '-'))
expect(json_response['title']).to eq(payload[:title])
end
+
+ [:title, :content, :format].each do |part|
+ it "it updates with wiki with missing #{part}" do
+ payload.delete(part)
+
+ put(api(url, user), params: payload)
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
end
shared_examples_for '403 Forbidden' do
@@ -528,8 +540,6 @@ describe API::Wikis do
context 'when user is developer' do
before do
project.add_developer(user)
-
- put(api(url, user), params: payload)
end
include_examples 'updates wiki page'
@@ -537,6 +547,10 @@ describe API::Wikis do
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
+ before do
+ put(api(url, user), params: payload)
+ end
+
include_examples '404 Wiki Page Not Found'
end
end
@@ -544,8 +558,6 @@ describe API::Wikis do
context 'when user is maintainer' do
before do
project.add_maintainer(user)
-
- put(api(url, user), params: payload)
end
include_examples 'updates wiki page'
@@ -553,6 +565,10 @@ describe API::Wikis do
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
+ before do
+ put(api(url, user), params: payload)
+ end
+
include_examples '404 Wiki Page Not Found'
end
end
@@ -572,8 +588,6 @@ describe API::Wikis do
context 'when user is developer' do
before do
project.add_developer(user)
-
- put(api(url, user), params: payload)
end
include_examples 'updates wiki page'
@@ -581,6 +595,10 @@ describe API::Wikis do
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
+ before do
+ put(api(url, user), params: payload)
+ end
+
include_examples '404 Wiki Page Not Found'
end
end
@@ -588,8 +606,6 @@ describe API::Wikis do
context 'when user is maintainer' do
before do
project.add_maintainer(user)
-
- put(api(url, user), params: payload)
end
include_examples 'updates wiki page'
@@ -597,6 +613,10 @@ describe API::Wikis do
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
+ before do
+ put(api(url, user), params: payload)
+ end
+
include_examples '404 Wiki Page Not Found'
end
end
@@ -605,10 +625,6 @@ describe API::Wikis do
context 'when wiki belongs to a group project' do
let(:project) { create(:project, :wiki_repo, namespace: group) }
- before do
- put(api(url, user), params: payload)
- end
-
include_examples 'updates wiki page'
end
end
diff --git a/spec/routing/import_routing_spec.rb b/spec/routing/import_routing_spec.rb
index 78ff9c6e6fd..106f92082e4 100644
--- a/spec/routing/import_routing_spec.rb
+++ b/spec/routing/import_routing_spec.rb
@@ -23,6 +23,11 @@ require 'spec_helper'
# end
shared_examples 'importer routing' do
let(:except_actions) { [] }
+ let(:is_realtime) { false }
+
+ before do
+ except_actions.push(is_realtime ? :jobs : :realtime_changes)
+ end
it 'to #create' do
expect(post("/import/#{provider}")).to route_to("import/#{provider}#create") unless except_actions.include?(:create)
@@ -43,17 +48,22 @@ shared_examples 'importer routing' do
it 'to #jobs' do
expect(get("/import/#{provider}/jobs")).to route_to("import/#{provider}#jobs") unless except_actions.include?(:jobs)
end
+
+ it 'to #realtime_changes' do
+ expect(get("/import/#{provider}/realtime_changes")).to route_to("import/#{provider}#realtime_changes") unless except_actions.include?(:realtime_changes)
+ end
end
# personal_access_token_import_github POST /import/github/personal_access_token(.:format) import/github#personal_access_token
# status_import_github GET /import/github/status(.:format) import/github#status
# callback_import_github GET /import/github/callback(.:format) import/github#callback
-# jobs_import_github GET /import/github/jobs(.:format) import/github#jobs
+# realtime_changes_import_github GET /import/github/realtime_changes(.:format) import/github#jobs
# import_github POST /import/github(.:format) import/github#create
# new_import_github GET /import/github/new(.:format) import/github#new
describe Import::GithubController, 'routing' do
it_behaves_like 'importer routing' do
let(:provider) { 'github' }
+ let(:is_realtime) { true }
end
it 'to #personal_access_token' do
@@ -63,13 +73,14 @@ end
# personal_access_token_import_gitea POST /import/gitea/personal_access_token(.:format) import/gitea#personal_access_token
# status_import_gitea GET /import/gitea/status(.:format) import/gitea#status
-# jobs_import_gitea GET /import/gitea/jobs(.:format) import/gitea#jobs
+# realtime_changes_import_gitea GET /import/gitea/realtime_changes(.:format) import/gitea#jobs
# import_gitea POST /import/gitea(.:format) import/gitea#create
# new_import_gitea GET /import/gitea/new(.:format) import/gitea#new
describe Import::GiteaController, 'routing' do
it_behaves_like 'importer routing' do
let(:except_actions) { [:callback] }
let(:provider) { 'gitea' }
+ let(:is_realtime) { true }
end
it 'to #personal_access_token' do
diff --git a/spec/serializers/diff_file_entity_spec.rb b/spec/serializers/diff_file_entity_spec.rb
index 073c13c2cbb..92b649f5b6c 100644
--- a/spec/serializers/diff_file_entity_spec.rb
+++ b/spec/serializers/diff_file_entity_spec.rb
@@ -22,7 +22,7 @@ describe DiffFileEntity do
let(:request) { EntityRequest.new(project: project, current_user: user) }
let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
let(:entity) { described_class.new(diff_file, request: request, merge_request: merge_request) }
- let(:exposed_urls) { %i(load_collapsed_diff_url edit_path view_path context_lines_path) }
+ let(:exposed_urls) { %i(edit_path view_path context_lines_path) }
it_behaves_like 'diff file entity'
@@ -38,6 +38,12 @@ describe DiffFileEntity do
expect(response[attribute]).to include(merge_request.target_project.to_param)
end
end
+
+ it 'exposes load_collapsed_diff_url if the file viewer is collapsed' do
+ allow(diff_file.viewer).to receive(:collapsed?) { true }
+
+ expect(subject).to include(:load_collapsed_diff_url)
+ end
end
context '#parallel_diff_lines' do
diff --git a/spec/serializers/environment_serializer_spec.rb b/spec/serializers/environment_serializer_spec.rb
index 3541bd5f12e..375a28a8c72 100644
--- a/spec/serializers/environment_serializer_spec.rb
+++ b/spec/serializers/environment_serializer_spec.rb
@@ -124,10 +124,10 @@ describe EnvironmentSerializer do
end
context 'when used with pagination' do
- let(:request) { spy('request') }
+ let(:request) { double(url: "#{Gitlab.config.gitlab.url}:8080/api/v4/projects?#{query.to_query}", query_parameters: query) }
let(:response) { spy('response') }
let(:resource) { Environment.all }
- let(:pagination) { { page: 1, per_page: 2 } }
+ let(:query) { { page: 1, per_page: 2 } }
let(:serializer) do
described_class
@@ -135,11 +135,6 @@ describe EnvironmentSerializer do
.with_pagination(request, response)
end
- before do
- allow(request).to receive(:query_parameters)
- .and_return(pagination)
- end
-
subject { serializer.represent(resource) }
it 'creates a paginated serializer' do
diff --git a/spec/serializers/namespace_basic_entity_spec.rb b/spec/serializers/namespace_basic_entity_spec.rb
new file mode 100644
index 00000000000..f8b71ceb9f3
--- /dev/null
+++ b/spec/serializers/namespace_basic_entity_spec.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe NamespaceBasicEntity do
+ set(:group) { create(:group) }
+ let(:entity) do
+ described_class.represent(group)
+ end
+
+ describe '#as_json' do
+ subject { entity.as_json }
+
+ it 'includes required fields' do
+ expect(subject).to include :id, :full_path
+ end
+ end
+end
diff --git a/spec/serializers/namespace_serializer_spec.rb b/spec/serializers/namespace_serializer_spec.rb
new file mode 100644
index 00000000000..6e5bdd8c52d
--- /dev/null
+++ b/spec/serializers/namespace_serializer_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe NamespaceSerializer do
+ it 'represents NamespaceBasicEntity entities' do
+ expect(described_class.entity_class).to eq(NamespaceBasicEntity)
+ end
+end
diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb
index 79aa32b29bb..2bdcb2a45f6 100644
--- a/spec/serializers/pipeline_serializer_spec.rb
+++ b/spec/serializers/pipeline_serializer_spec.rb
@@ -38,15 +38,9 @@ describe PipelineSerializer do
end
context 'when used with pagination' do
- let(:request) { spy('request') }
+ let(:request) { double(url: "#{Gitlab.config.gitlab.url}:8080/api/v4/projects?#{query.to_query}", query_parameters: query) }
let(:response) { spy('response') }
- let(:pagination) { {} }
-
- before do
- allow(request)
- .to receive(:query_parameters)
- .and_return(pagination)
- end
+ let(:query) { {} }
let(:serializer) do
described_class.new(current_user: user)
@@ -60,7 +54,7 @@ describe PipelineSerializer do
context 'when resource is not paginatable' do
context 'when a single pipeline object is being serialized' do
let(:resource) { create(:ci_empty_pipeline) }
- let(:pagination) { { page: 1, per_page: 1 } }
+ let(:query) { { page: 1, per_page: 1 } }
it 'raises error' do
expect { subject }.to raise_error(
@@ -71,7 +65,7 @@ describe PipelineSerializer do
context 'when resource is paginatable relation' do
let(:resource) { Ci::Pipeline.all }
- let(:pagination) { { page: 1, per_page: 2 } }
+ let(:query) { { page: 1, per_page: 2 } }
context 'when a single pipeline object is present in relation' do
before do
diff --git a/spec/serializers/project_import_entity_spec.rb b/spec/serializers/project_import_entity_spec.rb
new file mode 100644
index 00000000000..e476da82729
--- /dev/null
+++ b/spec/serializers/project_import_entity_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ProjectImportEntity do
+ include ImportHelper
+
+ set(:project) { create(:project, import_status: :started, import_source: 'namespace/project') }
+ let(:provider_url) { 'https://provider.com' }
+ let(:entity) { described_class.represent(project, provider_url: provider_url) }
+
+ describe '#as_json' do
+ subject { entity.as_json }
+
+ it 'includes required fields' do
+ expect(subject[:import_source]).to eq(project.import_source)
+ expect(subject[:import_status]).to eq(project.import_status)
+ expect(subject[:human_import_status_name]).to eq(project.human_import_status_name)
+ expect(subject[:provider_link]).to eq(provider_project_link_url(provider_url, project[:import_source]))
+ end
+ end
+end
diff --git a/spec/serializers/project_serializer_spec.rb b/spec/serializers/project_serializer_spec.rb
new file mode 100644
index 00000000000..22f958fc17f
--- /dev/null
+++ b/spec/serializers/project_serializer_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ProjectSerializer do
+ set(:project) { create(:project) }
+ let(:provider_url) { 'http://provider.com' }
+
+ context 'when serializer option is :import' do
+ subject do
+ described_class.new.represent(project, serializer: :import, provider_url: provider_url)
+ end
+
+ before do
+ allow(ProjectImportEntity).to receive(:represent)
+ end
+
+ it 'represents with ProjectImportEntity' do
+ subject
+
+ expect(ProjectImportEntity)
+ .to have_received(:represent)
+ .with(project, serializer: :import, provider_url: provider_url, request: an_instance_of(EntityRequest))
+ end
+ end
+
+ context 'when serializer option is omitted' do
+ subject do
+ described_class.new.represent(project)
+ end
+
+ before do
+ allow(ProjectEntity).to receive(:represent)
+ end
+
+ it 'represents with ProjectEntity' do
+ subject
+
+ expect(ProjectEntity)
+ .to have_received(:represent)
+ .with(project, request: an_instance_of(EntityRequest))
+ end
+ end
+end
diff --git a/spec/serializers/provider_repo_entity_spec.rb b/spec/serializers/provider_repo_entity_spec.rb
new file mode 100644
index 00000000000..9a1160d16d5
--- /dev/null
+++ b/spec/serializers/provider_repo_entity_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ProviderRepoEntity do
+ include ImportHelper
+
+ let(:provider_repo) { { id: 1, full_name: 'full/name', name: 'name', owner: { login: 'owner' } } }
+ let(:provider) { :github }
+ let(:provider_url) { 'https://github.com' }
+ let(:entity) { described_class.represent(provider_repo, provider: provider, provider_url: provider_url) }
+
+ describe '#as_json' do
+ subject { entity.as_json }
+
+ it 'includes required fields' do
+ expect(subject[:id]).to eq(provider_repo[:id])
+ expect(subject[:full_name]).to eq(provider_repo[:full_name])
+ expect(subject[:owner_name]).to eq(provider_repo[:owner][:login])
+ expect(subject[:sanitized_name]).to eq(sanitize_project_name(provider_repo[:name]))
+ expect(subject[:provider_link]).to eq(provider_project_link_url(provider_url, provider_repo[:full_name]))
+ end
+ end
+end
diff --git a/spec/serializers/provider_repo_serializer_spec.rb b/spec/serializers/provider_repo_serializer_spec.rb
new file mode 100644
index 00000000000..f2be30c36d9
--- /dev/null
+++ b/spec/serializers/provider_repo_serializer_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ProviderRepoSerializer do
+ it 'represents ProviderRepoEntity entities' do
+ expect(described_class.entity_class).to eq(ProviderRepoEntity)
+ end
+end
diff --git a/spec/services/boards/visits/latest_service_spec.rb b/spec/services/boards/visits/latest_service_spec.rb
index e55d599e2cc..c8a0a5e4243 100644
--- a/spec/services/boards/visits/latest_service_spec.rb
+++ b/spec/services/boards/visits/latest_service_spec.rb
@@ -23,6 +23,12 @@ describe Boards::Visits::LatestService do
service.execute
end
+
+ it 'queries for last N visits' do
+ expect(BoardProjectRecentVisit).to receive(:latest).with(user, project, count: 5).once
+
+ described_class.new(project_board.parent, user, count: 5).execute
+ end
end
context 'when a group board' do
@@ -42,6 +48,12 @@ describe Boards::Visits::LatestService do
service.execute
end
+
+ it 'queries for last N visits' do
+ expect(BoardGroupRecentVisit).to receive(:latest).with(user, group, count: 5).once
+
+ described_class.new(group_board.parent, user, count: 5).execute
+ end
end
end
end
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index 8497e90bd8b..24707cd2d41 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -12,6 +12,7 @@ describe Ci::CreatePipelineService do
end
describe '#execute' do
+ # rubocop:disable Metrics/ParameterLists
def execute_service(
source: :push,
after: project.commit.id,
@@ -20,17 +21,22 @@ describe Ci::CreatePipelineService do
trigger_request: nil,
variables_attributes: nil,
merge_request: nil,
- push_options: nil)
+ push_options: nil,
+ source_sha: nil,
+ target_sha: nil)
params = { ref: ref,
before: '00000000',
after: after,
commits: [{ message: message }],
variables_attributes: variables_attributes,
- push_options: push_options }
+ push_options: push_options,
+ source_sha: source_sha,
+ target_sha: target_sha }
described_class.new(project, user, params).execute(
source, trigger_request: trigger_request, merge_request: merge_request)
end
+ # rubocop:enable Metrics/ParameterLists
context 'valid params' do
let(:pipeline) { execute_service }
@@ -679,7 +685,11 @@ describe Ci::CreatePipelineService do
describe 'Merge request pipelines' do
let(:pipeline) do
- execute_service(source: source, merge_request: merge_request, ref: ref_name)
+ execute_service(source: source,
+ merge_request: merge_request,
+ ref: ref_name,
+ source_sha: source_sha,
+ target_sha: target_sha)
end
before do
@@ -687,9 +697,11 @@ describe Ci::CreatePipelineService do
end
let(:ref_name) { 'refs/heads/feature' }
+ let(:source_sha) { project.commit(ref_name).id }
+ let(:target_sha) { nil }
context 'when source is merge request' do
- let(:source) { :merge_request }
+ let(:source) { :merge_request_event }
context "when config has merge_requests keywords" do
let(:config) do
@@ -722,11 +734,27 @@ describe Ci::CreatePipelineService do
it 'creates a merge request pipeline' do
expect(pipeline).to be_persisted
- expect(pipeline).to be_merge_request
+ expect(pipeline).to be_merge_request_event
expect(pipeline.merge_request).to eq(merge_request)
expect(pipeline.builds.order(:stage_id).map(&:name)).to eq(%w[test])
end
+ it 'persists the specified source sha' do
+ expect(pipeline.source_sha).to eq(source_sha)
+ end
+
+ it 'does not persist target sha for detached merge request pipeline' do
+ expect(pipeline.target_sha).to be_nil
+ end
+
+ context 'when target sha is specified' do
+ let(:target_sha) { merge_request.target_branch_sha }
+
+ it 'persists the target sha' do
+ expect(pipeline.target_sha).to eq(target_sha)
+ end
+ end
+
context 'when ref is tag' do
let(:ref_name) { 'refs/tags/v1.1.0' }
diff --git a/spec/services/clusters/applications/create_service_spec.rb b/spec/services/clusters/applications/create_service_spec.rb
index 3f621ed5944..cbdef008b07 100644
--- a/spec/services/clusters/applications/create_service_spec.rb
+++ b/spec/services/clusters/applications/create_service_spec.rb
@@ -26,12 +26,6 @@ describe Clusters::Applications::CreateService do
end.to change(cluster, :application_helm)
end
- it 'schedules an install via worker' do
- expect(ClusterInstallAppWorker).to receive(:perform_async).with('helm', anything).once
-
- subject
- end
-
context 'application already installed' do
let!(:application) { create(:clusters_applications_helm, :installed, cluster: cluster) }
@@ -42,88 +36,101 @@ describe Clusters::Applications::CreateService do
end
it 'schedules an upgrade for the application' do
- expect(Clusters::Applications::ScheduleInstallationService).to receive(:new).with(application).and_call_original
+ expect(ClusterUpgradeAppWorker).to receive(:perform_async)
subject
end
end
- context 'cert manager application' do
- let(:params) do
- {
- application: 'cert_manager',
- email: 'test@example.com'
- }
- end
-
+ context 'known applications' do
before do
- allow_any_instance_of(Clusters::Applications::ScheduleInstallationService).to receive(:execute)
+ create(:clusters_applications_helm, :installed, cluster: cluster)
end
- it 'creates the application' do
- expect do
- subject
+ context 'cert manager application' do
+ let(:params) do
+ {
+ application: 'cert_manager',
+ email: 'test@example.com'
+ }
+ end
- cluster.reload
- end.to change(cluster, :application_cert_manager)
- end
+ before do
+ expect_any_instance_of(Clusters::Applications::CertManager)
+ .to receive(:make_scheduled!)
+ .and_call_original
+ end
- it 'sets the email' do
- expect(subject.email).to eq('test@example.com')
- end
- end
+ it 'creates the application' do
+ expect do
+ subject
- context 'jupyter application' do
- let(:params) do
- {
- application: 'jupyter',
- hostname: 'example.com'
- }
- end
+ cluster.reload
+ end.to change(cluster, :application_cert_manager)
+ end
- before do
- allow_any_instance_of(Clusters::Applications::ScheduleInstallationService).to receive(:execute)
+ it 'sets the email' do
+ expect(subject.email).to eq('test@example.com')
+ end
end
- it 'creates the application' do
- expect do
- subject
+ context 'jupyter application' do
+ let(:params) do
+ {
+ application: 'jupyter',
+ hostname: 'example.com'
+ }
+ end
- cluster.reload
- end.to change(cluster, :application_jupyter)
- end
+ before do
+ create(:clusters_applications_ingress, :installed, external_ip: "127.0.0.0", cluster: cluster)
+ expect_any_instance_of(Clusters::Applications::Jupyter)
+ .to receive(:make_scheduled!)
+ .and_call_original
+ end
- it 'sets the hostname' do
- expect(subject.hostname).to eq('example.com')
- end
+ it 'creates the application' do
+ expect do
+ subject
- it 'sets the oauth_application' do
- expect(subject.oauth_application).to be_present
- end
- end
+ cluster.reload
+ end.to change(cluster, :application_jupyter)
+ end
- context 'knative application' do
- let(:params) do
- {
- application: 'knative',
- hostname: 'example.com'
- }
- end
+ it 'sets the hostname' do
+ expect(subject.hostname).to eq('example.com')
+ end
- before do
- allow_any_instance_of(Clusters::Applications::ScheduleInstallationService).to receive(:execute)
+ it 'sets the oauth_application' do
+ expect(subject.oauth_application).to be_present
+ end
end
- it 'creates the application' do
- expect do
- subject
+ context 'knative application' do
+ let(:params) do
+ {
+ application: 'knative',
+ hostname: 'example.com'
+ }
+ end
- cluster.reload
- end.to change(cluster, :application_knative)
- end
+ before do
+ expect_any_instance_of(Clusters::Applications::Knative)
+ .to receive(:make_scheduled!)
+ .and_call_original
+ end
- it 'sets the hostname' do
- expect(subject.hostname).to eq('example.com')
+ it 'creates the application' do
+ expect do
+ subject
+
+ cluster.reload
+ end.to change(cluster, :application_knative)
+ end
+
+ it 'sets the hostname' do
+ expect(subject.hostname).to eq('example.com')
+ end
end
end
@@ -140,19 +147,21 @@ describe Clusters::Applications::CreateService do
using RSpec::Parameterized::TableSyntax
- before do
- allow_any_instance_of(Clusters::Applications::ScheduleInstallationService).to receive(:execute)
- end
-
- where(:application, :association, :allowed) do
- 'helm' | :application_helm | true
- 'ingress' | :application_ingress | true
- 'runner' | :application_runner | false
- 'jupyter' | :application_jupyter | false
- 'prometheus' | :application_prometheus | false
+ where(:application, :association, :allowed, :pre_create_helm) do
+ 'helm' | :application_helm | true | false
+ 'ingress' | :application_ingress | true | true
+ 'runner' | :application_runner | false | true
+ 'jupyter' | :application_jupyter | false | true
+ 'prometheus' | :application_prometheus | false | true
end
with_them do
+ before do
+ klass = "Clusters::Applications::#{application.titleize}"
+ allow_any_instance_of(klass.constantize).to receive(:make_scheduled!).and_call_original
+ create(:clusters_applications_helm, :installed, cluster: cluster) if pre_create_helm
+ end
+
let(:params) { { application: application } }
it 'executes for each application' do
@@ -168,5 +177,68 @@ describe Clusters::Applications::CreateService do
end
end
end
+
+ context 'when application is installable' do
+ shared_examples 'installable applications' do
+ it 'makes the application scheduled' do
+ expect do
+ subject
+ end.to change { Clusters::Applications::Helm.with_status(:scheduled).count }.by(1)
+ end
+
+ it 'schedules an install via worker' do
+ expect(ClusterInstallAppWorker)
+ .to receive(:perform_async)
+ .with(*worker_arguments)
+ .once
+
+ subject
+ end
+ end
+
+ context 'when application is associated with a cluster' do
+ let(:application) { create(:clusters_applications_helm, :installable, cluster: cluster) }
+ let(:worker_arguments) { [application.name, application.id] }
+
+ it_behaves_like 'installable applications'
+ end
+
+ context 'when application is not associated with a cluster' do
+ let(:worker_arguments) { [params[:application], kind_of(Numeric)] }
+
+ it_behaves_like 'installable applications'
+ end
+ end
+
+ context 'when installation is already in progress' do
+ let!(:application) { create(:clusters_applications_helm, :installing, cluster: cluster) }
+
+ it 'raises an exception' do
+ expect { subject }
+ .to raise_exception(StateMachines::InvalidTransition)
+ .and not_change(application.class.with_status(:scheduled), :count)
+ end
+
+ it 'does not schedule a cluster worker' do
+ expect(ClusterInstallAppWorker).not_to receive(:perform_async)
+ end
+ end
+
+ context 'when application is installed' do
+ %i(installed updated).each do |status|
+ let(:application) { create(:clusters_applications_helm, status, cluster: cluster) }
+
+ it 'schedules an upgrade via worker' do
+ expect(ClusterUpgradeAppWorker)
+ .to receive(:perform_async)
+ .with(application.name, application.id)
+ .once
+
+ subject
+
+ expect(application.reload).to be_scheduled
+ end
+ end
+ end
end
end
diff --git a/spec/services/clusters/applications/schedule_installation_service_spec.rb b/spec/services/clusters/applications/schedule_installation_service_spec.rb
deleted file mode 100644
index 8380932dfaa..00000000000
--- a/spec/services/clusters/applications/schedule_installation_service_spec.rb
+++ /dev/null
@@ -1,77 +0,0 @@
-require 'spec_helper'
-
-describe Clusters::Applications::ScheduleInstallationService do
- def count_scheduled
- application&.class&.with_status(:scheduled)&.count || 0
- end
-
- shared_examples 'a failing service' do
- it 'raise an exception' do
- expect(ClusterInstallAppWorker).not_to receive(:perform_async)
- count_before = count_scheduled
-
- expect { service.execute }.to raise_error(StandardError)
- expect(count_scheduled).to eq(count_before)
- end
- end
-
- describe '#execute' do
- let(:service) { described_class.new(application) }
-
- context 'when application is installable' do
- let(:application) { create(:clusters_applications_helm, :installable) }
-
- it 'make the application scheduled' do
- expect(ClusterInstallAppWorker).to receive(:perform_async).with(application.name, kind_of(Numeric)).once
-
- expect { service.execute }.to change { application.class.with_status(:scheduled).count }.by(1)
- end
- end
-
- context 'when installation is already in progress' do
- let(:application) { create(:clusters_applications_helm, :installing) }
-
- it_behaves_like 'a failing service'
- end
-
- context 'when application is nil' do
- let(:application) { nil }
-
- it_behaves_like 'a failing service'
- end
-
- context 'when application cannot be persisted' do
- let(:application) { create(:clusters_applications_helm) }
-
- before do
- expect(application).to receive(:make_scheduled!).once.and_raise(ActiveRecord::RecordInvalid)
- end
-
- it_behaves_like 'a failing service'
- end
-
- context 'when application is installed' do
- let(:application) { create(:clusters_applications_helm, :installed) }
-
- it 'schedules an upgrade via worker' do
- expect(ClusterUpgradeAppWorker).to receive(:perform_async).with(application.name, application.id).once
-
- service.execute
-
- expect(application).to be_scheduled
- end
- end
-
- context 'when application is updated' do
- let(:application) { create(:clusters_applications_helm, :updated) }
-
- it 'schedules an upgrade via worker' do
- expect(ClusterUpgradeAppWorker).to receive(:perform_async).with(application.name, application.id).once
-
- service.execute
-
- expect(application).to be_scheduled
- end
- end
- end
-end
diff --git a/spec/services/error_tracking/list_issues_service_spec.rb b/spec/services/error_tracking/list_issues_service_spec.rb
index d9dab1d705c..9d4fc62f923 100644
--- a/spec/services/error_tracking/list_issues_service_spec.rb
+++ b/spec/services/error_tracking/list_issues_service_spec.rb
@@ -45,7 +45,23 @@ describe ErrorTracking::ListIssuesService do
it 'result is not ready' do
expect(result).to eq(
- status: :error, http_status: :no_content, message: 'not ready')
+ status: :error, http_status: :no_content, message: 'Not ready. Try again later')
+ end
+ end
+
+ context 'when list_sentry_issues returns error' do
+ before do
+ allow(error_tracking_setting)
+ .to receive(:list_sentry_issues)
+ .and_return(error: 'Sentry response status code: 401')
+ end
+
+ it 'returns the error' do
+ expect(result).to eq(
+ status: :error,
+ http_status: :bad_request,
+ message: 'Sentry response status code: 401'
+ )
end
end
end
@@ -58,7 +74,11 @@ describe ErrorTracking::ListIssuesService do
it 'returns error' do
result = subject.execute
- expect(result).to include(status: :error, message: 'access denied')
+ expect(result).to include(
+ status: :error,
+ message: 'Access denied',
+ http_status: :unauthorized
+ )
end
end
@@ -70,7 +90,7 @@ describe ErrorTracking::ListIssuesService do
it 'raises error' do
result = subject.execute
- expect(result).to include(status: :error, message: 'not enabled')
+ expect(result).to include(status: :error, message: 'Error Tracking is not enabled')
end
end
end
diff --git a/spec/services/error_tracking/list_projects_service_spec.rb b/spec/services/error_tracking/list_projects_service_spec.rb
index ee9c59e3f65..a92d3376f7b 100644
--- a/spec/services/error_tracking/list_projects_service_spec.rb
+++ b/spec/services/error_tracking/list_projects_service_spec.rb
@@ -32,7 +32,7 @@ describe ErrorTracking::ListProjectsService do
end
context 'set model attributes to new values' do
- let(:new_api_url) { new_api_host + 'api/0/projects/' }
+ let(:new_api_url) { new_api_host + 'api/0/projects/org/proj/' }
before do
expect(error_tracking_setting).to receive(:list_sentry_projects)
@@ -53,11 +53,11 @@ describe ErrorTracking::ListProjectsService do
context 'sentry client raises exception' do
before do
expect(error_tracking_setting).to receive(:list_sentry_projects)
- .and_raise(Sentry::Client::Error, 'Sentry response error: 500')
+ .and_raise(Sentry::Client::Error, 'Sentry response status code: 500')
end
it 'returns error response' do
- expect(result[:message]).to eq('Sentry response error: 500')
+ expect(result[:message]).to eq('Sentry response status code: 500')
expect(result[:http_status]).to eq(:bad_request)
end
end
@@ -121,7 +121,7 @@ describe ErrorTracking::ListProjectsService do
context 'error_tracking_setting is nil' do
let(:error_tracking_setting) { build(:project_error_tracking_setting) }
- let(:new_api_url) { new_api_host + 'api/0/projects/' }
+ let(:new_api_url) { new_api_host + 'api/0/projects/org/proj/' }
before do
expect(project).to receive(:build_error_tracking_setting).once
diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index b46aa65818d..a04a4d5fc36 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -194,7 +194,7 @@ describe MergeRequests::CreateService do
merge_request.reload
expect(merge_request.merge_request_pipelines.count).to eq(1)
- expect(merge_request.actual_head_pipeline).to be_merge_request
+ expect(merge_request.actual_head_pipeline).to be_merge_request_event
end
context 'when there are no commits between source branch and target branch' do
@@ -226,7 +226,7 @@ describe MergeRequests::CreateService do
end
it 'sets the latest merge request pipeline as the head pipeline' do
- expect(merge_request.actual_head_pipeline).to be_merge_request
+ expect(merge_request.actual_head_pipeline).to be_merge_request_event
end
end
diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb
index 04a62aa454d..ede79b87bcc 100644
--- a/spec/services/merge_requests/merge_service_spec.rb
+++ b/spec/services/merge_requests/merge_service_spec.rb
@@ -224,6 +224,18 @@ describe MergeRequests::MergeService do
expect(Rails.logger).to have_received(:error).with(a_string_matching(error_message))
end
+ it 'logs and saves error if user is not authorized' do
+ unauthorized_user = create(:user)
+ project.add_reporter(unauthorized_user)
+
+ service = described_class.new(project, unauthorized_user)
+
+ service.execute(merge_request)
+
+ expect(merge_request.merge_error)
+ .to eq('You are not allowed to merge this merge request')
+ end
+
it 'logs and saves error if there is an PreReceiveError exception' do
error_message = 'error message'
diff --git a/spec/services/merge_requests/merge_to_ref_service_spec.rb b/spec/services/merge_requests/merge_to_ref_service_spec.rb
new file mode 100644
index 00000000000..96f2fde7117
--- /dev/null
+++ b/spec/services/merge_requests/merge_to_ref_service_spec.rb
@@ -0,0 +1,201 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe MergeRequests::MergeToRefService do
+ shared_examples_for 'MergeService for target ref' do
+ it 'target_ref has the same state of target branch' do
+ repo = merge_request.target_project.repository
+
+ process_merge_to_ref
+ merge_service.execute(merge_request)
+
+ ref_commits = repo.commits(merge_request.merge_ref_path, limit: 3)
+ target_branch_commits = repo.commits(merge_request.target_branch, limit: 3)
+
+ ref_commits.zip(target_branch_commits).each do |ref_commit, target_branch_commit|
+ expect(ref_commit.parents).to eq(target_branch_commit.parents)
+ end
+ end
+ end
+
+ set(:user) { create(:user) }
+ let(:merge_request) { create(:merge_request, :simple) }
+ let(:project) { merge_request.project }
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ describe '#execute' do
+ let(:service) do
+ described_class.new(project, user,
+ commit_message: 'Awesome message',
+ 'should_remove_source_branch' => true)
+ end
+
+ def process_merge_to_ref
+ perform_enqueued_jobs do
+ service.execute(merge_request)
+ end
+ end
+
+ it 'writes commit to merge ref' do
+ repository = project.repository
+ target_ref = merge_request.merge_ref_path
+
+ expect(repository.ref_exists?(target_ref)).to be(false)
+
+ result = service.execute(merge_request)
+
+ ref_head = repository.commit(target_ref)
+
+ expect(result[:status]).to eq(:success)
+ expect(result[:commit_id]).to be_present
+ expect(repository.ref_exists?(target_ref)).to be(true)
+ expect(ref_head.id).to eq(result[:commit_id])
+ end
+
+ it 'does not send any mail' do
+ expect { process_merge_to_ref }.not_to change { ActionMailer::Base.deliveries.count }
+ end
+
+ it 'does not change the MR state' do
+ expect { process_merge_to_ref }.not_to change { merge_request.state }
+ end
+
+ it 'does not create notes' do
+ expect { process_merge_to_ref }.not_to change { merge_request.notes.count }
+ end
+
+ it 'does not delete the source branch' do
+ expect(DeleteBranchService).not_to receive(:new)
+
+ process_merge_to_ref
+ end
+
+ it 'returns error when feature is disabled' do
+ stub_feature_flags(merge_to_tmp_merge_ref_path: false)
+
+ result = service.execute(merge_request)
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq('Feature is not enabled')
+ end
+
+ it 'returns an error when the failing to process the merge' do
+ allow(project.repository).to receive(:merge_to_ref).and_return(nil)
+
+ result = service.execute(merge_request)
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq('Conflicts detected during merge')
+ end
+
+ context 'commit history comparison with regular MergeService' do
+ let(:merge_ref_service) do
+ described_class.new(project, user, {})
+ end
+
+ let(:merge_service) do
+ MergeRequests::MergeService.new(project, user, {})
+ end
+
+ context 'when merge commit' do
+ it_behaves_like 'MergeService for target ref'
+ end
+
+ context 'when merge commit with squash' do
+ before do
+ merge_request.update!(squash: true, source_branch: 'master', target_branch: 'feature')
+ end
+
+ it_behaves_like 'MergeService for target ref'
+ end
+ end
+
+ context 'merge pre-condition checks' do
+ before do
+ merge_request.project.update!(merge_method: merge_method)
+ end
+
+ context 'when semi-linear merge method' do
+ let(:merge_method) { :rebase_merge }
+
+ it 'return error when MR should be able to fast-forward' do
+ allow(merge_request).to receive(:should_be_rebased?) { true }
+
+ error_message = 'Fast-forward merge is not possible. Please update your source branch.'
+
+ result = service.execute(merge_request)
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq(error_message)
+ end
+ end
+
+ context 'when fast-forward merge method' do
+ let(:merge_method) { :ff }
+
+ it 'returns error' do
+ error_message = "Fast-forward to #{merge_request.merge_ref_path} is currently not supported."
+
+ result = service.execute(merge_request)
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq(error_message)
+ end
+ end
+
+ context 'when MR is not mergeable to ref' do
+ let(:merge_method) { :merge }
+
+ it 'returns error' do
+ allow(merge_request).to receive(:mergeable_to_ref?) { false }
+
+ error_message = "Merge request is not mergeable to #{merge_request.merge_ref_path}"
+
+ result = service.execute(merge_request)
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq(error_message)
+ end
+ end
+ end
+
+ context 'does not close related todos' do
+ let(:merge_request) { create(:merge_request, assignee: user, author: user) }
+ let(:project) { merge_request.project }
+ let!(:todo) do
+ create(:todo, :assigned,
+ project: project,
+ author: user,
+ user: user,
+ target: merge_request)
+ end
+
+ before do
+ allow(service).to receive(:execute_hooks)
+
+ perform_enqueued_jobs do
+ service.execute(merge_request)
+ todo.reload
+ end
+ end
+
+ it { expect(todo).not_to be_done }
+ end
+
+ it 'returns error when user has no authorization to admin the merge request' do
+ unauthorized_user = create(:user)
+ project.add_reporter(unauthorized_user)
+
+ service = described_class.new(project, unauthorized_user)
+
+ result = service.execute(merge_request)
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq('You are not allowed to merge to this ref')
+ end
+ end
+end
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 9e9dc5a576c..43ceb1dcbee 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -173,12 +173,12 @@ describe MergeRequests::RefreshService do
it 'sets the latest merge request pipeline as a head pipeline' do
@merge_request.reload
- expect(@merge_request.actual_head_pipeline).to be_merge_request
+ expect(@merge_request.actual_head_pipeline).to be_merge_request_event
end
it 'returns pipelines in correct order' do
@merge_request.reload
- expect(@merge_request.all_pipelines.first).to be_merge_request
+ expect(@merge_request.all_pipelines.first).to be_merge_request_event
expect(@merge_request.all_pipelines.second).to be_push
end
end
diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb
index 48f1d696ff6..1645b67c329 100644
--- a/spec/services/notes/create_service_spec.rb
+++ b/spec/services/notes/create_service_spec.rb
@@ -311,7 +311,14 @@ describe Notes::CreateService do
end
it 'converts existing note to DiscussionNote' do
- expect { subject }.to change { existing_note.reload.type }.from(nil).to('DiscussionNote')
+ expect do
+ existing_note
+
+ Timecop.freeze(Time.now + 1.minute) { subject }
+
+ existing_note.reload
+ end.to change { existing_note.type }.from(nil).to('DiscussionNote')
+ .and change { existing_note.updated_at }
end
end
end
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 6a5a6989607..9ba4a11104a 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -177,7 +177,7 @@ describe NotificationService, :mailer do
end
end
- context 'when recieving a non-existent method' do
+ context 'when receiving a non-existent method' do
it 'raises NoMethodError' do
expect { async.foo(key) }.to raise_error(NoMethodError)
end
diff --git a/spec/services/projects/fetch_statistics_increment_service_spec.rb b/spec/services/projects/fetch_statistics_increment_service_spec.rb
new file mode 100644
index 00000000000..fcfb138aad6
--- /dev/null
+++ b/spec/services/projects/fetch_statistics_increment_service_spec.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+module Projects
+ describe FetchStatisticsIncrementService do
+ let(:project) { create(:project) }
+
+ describe '#execute' do
+ subject { described_class.new(project).execute }
+
+ it 'creates a new record for today with count == 1' do
+ expect { subject }.to change { ProjectDailyStatistic.count }.by(1)
+ created_stat = ProjectDailyStatistic.last
+
+ expect(created_stat.fetch_count).to eq(1)
+ expect(created_stat.project).to eq(project)
+ expect(created_stat.date).to eq(Date.today)
+ end
+
+ it "doesn't increment previous days statistics" do
+ yesterday_stat = create(:project_daily_statistic, fetch_count: 5, project: project, date: 1.day.ago)
+
+ expect { subject }.not_to change { yesterday_stat.reload.fetch_count }
+ end
+
+ context 'when the record already exists for today' do
+ let!(:project_daily_stat) { create(:project_daily_statistic, fetch_count: 5, project: project, date: Date.today) }
+
+ it 'increments the today record count by 1' do
+ expect { subject }.to change { project_daily_stat.reload.fetch_count }.to(6)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb b/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb
index 61dbb57ec08..639dd930618 100644
--- a/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb
+++ b/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb
@@ -70,10 +70,10 @@ describe Projects::HashedStorage::MigrateAttachmentsService do
FileUtils.mkdir_p(base_path(hashed_storage))
end
- it 'raises AttachmentMigrationError' do
+ it 'raises AttachmentCannotMoveError' do
expect(FileUtils).not_to receive(:mv).with(base_path(legacy_storage), base_path(hashed_storage))
- expect { service.execute }.to raise_error(Projects::HashedStorage::AttachmentMigrationError)
+ expect { service.execute }.to raise_error(Projects::HashedStorage::AttachmentCannotMoveError)
end
end
end
@@ -86,6 +86,8 @@ describe Projects::HashedStorage::MigrateAttachmentsService do
context '#new_disk_path' do
it 'returns new disk_path for project' do
+ service.execute
+
expect(service.new_disk_path).to eq(project.disk_path)
end
end
diff --git a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb
index 0772dc4b85b..e77e2198439 100644
--- a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb
+++ b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb
@@ -28,7 +28,17 @@ describe Projects::HashedStorage::MigrateRepositoryService do
it 'fails when a git operation is in progress' do
allow(project).to receive(:repo_reference_count) { 1 }
- expect { service.execute }.to raise_error(Projects::HashedStorage::RepositoryMigrationError)
+ expect { service.execute }.to raise_error(Projects::HashedStorage::RepositoryInUseError)
+ end
+ end
+
+ context 'when repository doesnt exist on disk' do
+ let(:project) { create(:project, :legacy_storage) }
+
+ it 'skips the disk change but increase the version' do
+ service.execute
+
+ expect(project.hashed_storage?(:repository)).to be_truthy
end
end
diff --git a/spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb b/spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb
new file mode 100644
index 00000000000..6f4154d6011
--- /dev/null
+++ b/spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb
@@ -0,0 +1,100 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::HashedStorage::RollbackAttachmentsService do
+ subject(:service) { described_class.new(project, logger: nil) }
+
+ let(:project) { create(:project, :repository, skip_disk_validation: true) }
+ let(:legacy_storage) { Storage::LegacyProject.new(project) }
+ let(:hashed_storage) { Storage::HashedProject.new(project) }
+
+ let!(:upload) { Upload.find_by(path: file_uploader.upload_path) }
+ let(:file_uploader) { build(:file_uploader, project: project) }
+ let(:old_disk_path) { File.join(base_path(hashed_storage), upload.path) }
+ let(:new_disk_path) { File.join(base_path(legacy_storage), upload.path) }
+
+ context '#execute' do
+ context 'when succeeds' do
+ it 'moves attachments to legacy storage layout' do
+ expect(File.file?(old_disk_path)).to be_truthy
+ expect(File.file?(new_disk_path)).to be_falsey
+ expect(File.exist?(base_path(hashed_storage))).to be_truthy
+ expect(File.exist?(base_path(legacy_storage))).to be_falsey
+ expect(FileUtils).to receive(:mv).with(base_path(hashed_storage), base_path(legacy_storage)).and_call_original
+
+ service.execute
+
+ expect(File.exist?(base_path(legacy_storage))).to be_truthy
+ expect(File.exist?(base_path(hashed_storage))).to be_falsey
+ expect(File.file?(old_disk_path)).to be_falsey
+ expect(File.file?(new_disk_path)).to be_truthy
+ end
+
+ it 'returns true' do
+ expect(service.execute).to be_truthy
+ end
+
+ it 'sets skipped to false' do
+ service.execute
+
+ expect(service.skipped?).to be_falsey
+ end
+ end
+
+ context 'when original folder does not exist anymore' do
+ before do
+ FileUtils.rm_rf(base_path(hashed_storage))
+ end
+
+ it 'skips moving folders and go to next' do
+ expect(FileUtils).not_to receive(:mv).with(base_path(hashed_storage), base_path(legacy_storage))
+
+ service.execute
+
+ expect(File.exist?(base_path(legacy_storage))).to be_falsey
+ expect(File.file?(new_disk_path)).to be_falsey
+ end
+
+ it 'returns true' do
+ expect(service.execute).to be_truthy
+ end
+
+ it 'sets skipped to true' do
+ service.execute
+
+ expect(service.skipped?).to be_truthy
+ end
+ end
+
+ context 'when target folder already exists' do
+ before do
+ FileUtils.mkdir_p(base_path(legacy_storage))
+ end
+
+ it 'raises AttachmentCannotMoveError' do
+ expect(FileUtils).not_to receive(:mv).with(base_path(legacy_storage), base_path(hashed_storage))
+
+ expect { service.execute }.to raise_error(Projects::HashedStorage::AttachmentCannotMoveError)
+ end
+ end
+ end
+
+ context '#old_disk_path' do
+ it 'returns old disk_path for project' do
+ expect(service.old_disk_path).to eq(project.disk_path)
+ end
+ end
+
+ context '#new_disk_path' do
+ it 'returns new disk_path for project' do
+ service.execute
+
+ expect(service.new_disk_path).to eq(project.full_path)
+ end
+ end
+
+ def base_path(storage)
+ File.join(FileUploader.root, storage.disk_path)
+ end
+end
diff --git a/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb b/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb
new file mode 100644
index 00000000000..41927934501
--- /dev/null
+++ b/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb
@@ -0,0 +1,111 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::HashedStorage::RollbackRepositoryService, :clean_gitlab_redis_shared_state do
+ include GitHelpers
+
+ let(:gitlab_shell) { Gitlab::Shell.new }
+ let(:project) { create(:project, :repository, :wiki_repo, storage_version: ::Project::HASHED_STORAGE_FEATURES[:repository]) }
+ let(:legacy_storage) { Storage::LegacyProject.new(project) }
+ let(:hashed_storage) { Storage::HashedProject.new(project) }
+
+ subject(:service) { described_class.new(project, project.disk_path) }
+
+ describe '#execute' do
+ let(:old_disk_path) { hashed_storage.disk_path }
+ let(:new_disk_path) { legacy_storage.disk_path }
+
+ before do
+ allow(service).to receive(:gitlab_shell) { gitlab_shell }
+ end
+
+ context 'repository lock' do
+ it 'tries to lock the repository' do
+ expect(service).to receive(:try_to_set_repository_read_only!)
+
+ service.execute
+ end
+
+ it 'fails when a git operation is in progress' do
+ allow(project).to receive(:repo_reference_count) { 1 }
+
+ expect { service.execute }.to raise_error(Projects::HashedStorage::RepositoryInUseError)
+ end
+ end
+
+ context 'when repository doesnt exist on disk' do
+ let(:project) { create(:project) }
+
+ it 'skips the disk change but decrease the version' do
+ service.execute
+
+ expect(project.legacy_storage?).to be_truthy
+ end
+ end
+
+ context 'when succeeds' do
+ it 'renames project and wiki repositories' do
+ service.execute
+
+ expect(gitlab_shell.exists?(project.repository_storage, "#{new_disk_path}.git")).to be_truthy
+ expect(gitlab_shell.exists?(project.repository_storage, "#{new_disk_path}.wiki.git")).to be_truthy
+ end
+
+ it 'updates project to be legacy and not read-only' do
+ service.execute
+
+ expect(project.legacy_storage?).to be_truthy
+ expect(project.repository_read_only).to be_falsey
+ end
+
+ it 'move operation is called for both repositories' do
+ expect_move_repository(old_disk_path, new_disk_path)
+ expect_move_repository("#{old_disk_path}.wiki", "#{new_disk_path}.wiki")
+
+ service.execute
+ end
+
+ it 'writes project full path to .git/config' do
+ service.execute
+
+ rugged_config = rugged_repo(project.repository).config['gitlab.fullpath']
+
+ expect(rugged_config).to eq project.full_path
+ end
+ end
+
+ context 'when one move fails' do
+ it 'rolls repositories back to original name' do
+ allow(service).to receive(:move_repository).and_call_original
+ allow(service).to receive(:move_repository).with(old_disk_path, new_disk_path).once { false } # will disable first move only
+
+ expect(service).to receive(:rollback_folder_move).and_call_original
+
+ service.execute
+
+ expect(gitlab_shell.exists?(project.repository_storage, "#{new_disk_path}.git")).to be_falsey
+ expect(gitlab_shell.exists?(project.repository_storage, "#{new_disk_path}.wiki.git")).to be_falsey
+ expect(project.repository_read_only?).to be_falsey
+ end
+
+ context 'when rollback fails' do
+ before do
+ legacy_storage.ensure_storage_path_exists
+ gitlab_shell.mv_repository(project.repository_storage, old_disk_path, new_disk_path)
+ end
+
+ it 'does not try to move nil repository over existing' do
+ expect(gitlab_shell).not_to receive(:mv_repository).with(project.repository_storage, old_disk_path, new_disk_path)
+ expect_move_repository("#{old_disk_path}.wiki", "#{new_disk_path}.wiki")
+
+ service.execute
+ end
+ end
+ end
+
+ def expect_move_repository(from_name, to_name)
+ expect(gitlab_shell).to receive(:mv_repository).with(project.repository_storage, from_name, to_name).and_call_original
+ end
+ end
+end
diff --git a/spec/services/projects/hashed_storage/rollback_service_spec.rb b/spec/services/projects/hashed_storage/rollback_service_spec.rb
new file mode 100644
index 00000000000..427d1535559
--- /dev/null
+++ b/spec/services/projects/hashed_storage/rollback_service_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::HashedStorage::RollbackService do
+ let(:project) { create(:project, :empty_repo, :wiki_repo) }
+ let(:logger) { double }
+
+ subject(:service) { described_class.new(project, project.full_path, logger: logger) }
+
+ describe '#execute' do
+ context 'attachments rollback' do
+ let(:attachments_service_class) { Projects::HashedStorage::RollbackAttachmentsService }
+ let(:attachments_service) { attachments_service_class.new(project, logger: logger) }
+
+ it 'delegates rollback to Projects::HashedStorage::RollbackAttachmentsService' do
+ expect(attachments_service_class).to receive(:new)
+ .with(project, logger: logger)
+ .and_return(attachments_service)
+ expect(attachments_service).to receive(:execute)
+
+ service.execute
+ end
+
+ it 'does not delegate rollback if repository is in legacy storage already' do
+ project.storage_version = nil
+ expect(attachments_service_class).not_to receive(:new)
+
+ service.execute
+ end
+ end
+
+ context 'repository rollback' do
+ let(:repository_service_class) { Projects::HashedStorage::RollbackRepositoryService }
+ let(:repository_service) { repository_service_class.new(project, project.full_path, logger: logger) }
+
+ it 'delegates rollback to RollbackRepositoryService' do
+ project.storage_version = ::Project::HASHED_STORAGE_FEATURES[:repository]
+
+ expect(repository_service_class).to receive(:new)
+ .with(project, project.full_path, logger: logger)
+ .and_return(repository_service)
+ expect(repository_service).to receive(:execute)
+
+ service.execute
+ end
+
+ it 'does not delegate rollback if repository is in legacy storage already' do
+ project.storage_version = nil
+
+ expect(repository_service_class).not_to receive(:new)
+
+ service.execute
+ end
+ end
+ end
+end
diff --git a/spec/services/projects/operations/update_service_spec.rb b/spec/services/projects/operations/update_service_spec.rb
index 6afae3da80c..86b1ec83f50 100644
--- a/spec/services/projects/operations/update_service_spec.rb
+++ b/spec/services/projects/operations/update_service_spec.rb
@@ -17,8 +17,14 @@ describe Projects::Operations::UpdateService do
{
error_tracking_setting_attributes: {
enabled: false,
- api_url: 'http://gitlab.com/api/0/projects/org/project',
- token: 'token'
+ api_host: 'http://gitlab.com/',
+ token: 'token',
+ project: {
+ slug: 'project',
+ name: 'Project',
+ organization_slug: 'org',
+ organization_name: 'Org'
+ }
}
}
end
@@ -32,8 +38,30 @@ describe Projects::Operations::UpdateService do
project.reload
expect(project.error_tracking_setting).not_to be_enabled
- expect(project.error_tracking_setting.api_url).to eq('http://gitlab.com/api/0/projects/org/project')
+ expect(project.error_tracking_setting.api_url).to eq(
+ 'http://gitlab.com/api/0/projects/org/project/'
+ )
expect(project.error_tracking_setting.token).to eq('token')
+ expect(project.error_tracking_setting[:project_name]).to eq('Project')
+ expect(project.error_tracking_setting[:organization_name]).to eq('Org')
+ end
+
+ context 'disable error tracking' do
+ before do
+ params[:error_tracking_setting_attributes][:api_host] = ''
+ params[:error_tracking_setting_attributes][:enabled] = false
+ end
+
+ it 'can set api_url to nil' do
+ expect(result[:status]).to eq(:success)
+
+ project.reload
+ expect(project.error_tracking_setting).not_to be_enabled
+ expect(project.error_tracking_setting.api_url).to be_nil
+ expect(project.error_tracking_setting.token).to eq('token')
+ expect(project.error_tracking_setting[:project_name]).to eq('Project')
+ expect(project.error_tracking_setting[:organization_name]).to eq('Org')
+ end
end
end
@@ -42,8 +70,14 @@ describe Projects::Operations::UpdateService do
{
error_tracking_setting_attributes: {
enabled: true,
- api_url: 'http://gitlab.com/api/0/projects/org/project',
- token: 'token'
+ api_host: 'http://gitlab.com/',
+ token: 'token',
+ project: {
+ slug: 'project',
+ name: 'Project',
+ organization_slug: 'org',
+ organization_name: 'Org'
+ }
}
}
end
@@ -52,8 +86,12 @@ describe Projects::Operations::UpdateService do
expect(result[:status]).to eq(:success)
expect(project.error_tracking_setting).to be_enabled
- expect(project.error_tracking_setting.api_url).to eq('http://gitlab.com/api/0/projects/org/project')
+ expect(project.error_tracking_setting.api_url).to eq(
+ 'http://gitlab.com/api/0/projects/org/project/'
+ )
expect(project.error_tracking_setting.token).to eq('token')
+ expect(project.error_tracking_setting[:project_name]).to eq('Project')
+ expect(project.error_tracking_setting[:organization_name]).to eq('Org')
end
end
diff --git a/spec/services/prometheus/adapter_service_spec.rb b/spec/services/prometheus/adapter_service_spec.rb
index 335fc5844aa..505e2935e93 100644
--- a/spec/services/prometheus/adapter_service_spec.rb
+++ b/spec/services/prometheus/adapter_service_spec.rb
@@ -22,7 +22,15 @@ describe Prometheus::AdapterService do
context "prometheus service can't execute queries" do
let(:prometheus_service) { double(:prometheus_service, can_query?: false) }
- context 'with cluster with prometheus installed' do
+ context 'with cluster with prometheus not available' do
+ let!(:prometheus) { create(:clusters_applications_prometheus, :installable, cluster: cluster) }
+
+ it 'returns nil' do
+ expect(subject.prometheus_adapter).to be_nil
+ end
+ end
+
+ context 'with cluster with prometheus available' do
let!(:prometheus) { create(:clusters_applications_prometheus, :installed, cluster: cluster) }
it 'returns application handling all environments' do
diff --git a/spec/services/suggestions/apply_service_spec.rb b/spec/services/suggestions/apply_service_spec.rb
index 8e77d582eb4..fe85b5c9065 100644
--- a/spec/services/suggestions/apply_service_spec.rb
+++ b/spec/services/suggestions/apply_service_spec.rb
@@ -362,6 +362,17 @@ describe Suggestions::ApplyService do
project.add_maintainer(user)
end
+ context 'diff file was not found' do
+ it 'returns error message' do
+ expect(suggestion.note).to receive(:latest_diff_file) { nil }
+
+ result = subject.execute(suggestion)
+
+ expect(result).to eq(message: 'The file was not found',
+ status: :error)
+ end
+ end
+
context 'suggestion was already applied' do
it 'returns success status' do
result = subject.execute(suggestion)
diff --git a/spec/services/suggestions/create_service_spec.rb b/spec/services/suggestions/create_service_spec.rb
index f1142c88a69..1b4b15b8eaa 100644
--- a/spec/services/suggestions/create_service_spec.rb
+++ b/spec/services/suggestions/create_service_spec.rb
@@ -9,14 +9,18 @@ describe Suggestions::CreateService do
target_project: project_with_repo)
end
- let(:position) do
- Gitlab::Diff::Position.new(old_path: "files/ruby/popen.rb",
- new_path: "files/ruby/popen.rb",
- old_line: nil,
- new_line: 14,
- diff_refs: merge_request.diff_refs)
+ def build_position(args = {})
+ default_args = { old_path: "files/ruby/popen.rb",
+ new_path: "files/ruby/popen.rb",
+ old_line: nil,
+ new_line: 14,
+ diff_refs: merge_request.diff_refs }
+
+ Gitlab::Diff::Position.new(default_args.merge(args))
end
+ let(:position) { build_position }
+
let(:markdown) do
<<-MARKDOWN.strip_heredoc
```suggestion
@@ -74,6 +78,21 @@ describe Suggestions::CreateService do
end
end
+ context 'should not create suggestions' do
+ let(:note) do
+ create(:diff_note_on_merge_request, project: project_with_repo,
+ noteable: merge_request,
+ position: position,
+ note: markdown)
+ end
+
+ it 'creates no suggestion when diff file is not found' do
+ expect(note).to receive(:latest_diff_file) { nil }
+
+ expect { subject.execute }.not_to change(Suggestion, :count)
+ end
+ end
+
context 'should create suggestions' do
let(:note) do
create(:diff_note_on_merge_request, project: project_with_repo,
@@ -104,6 +123,22 @@ describe Suggestions::CreateService do
expect(suggestion_2).to have_attributes(from_content: " vars = {\n",
to_content: " xpto\n baz\n")
end
+
+ context 'outdated position note' do
+ let!(:outdated_diff) { merge_request.merge_request_diff }
+ let!(:latest_diff) { merge_request.create_merge_request_diff }
+ let(:outdated_position) { build_position(diff_refs: outdated_diff.diff_refs) }
+ let(:position) { build_position(diff_refs: latest_diff.diff_refs) }
+
+ it 'uses the correct position when creating the suggestion' do
+ expect(note.position)
+ .to receive(:diff_file)
+ .with(project_with_repo.repository)
+ .and_call_original
+
+ subject.execute
+ end
+ end
end
end
end
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 82544ab0413..b917de14b2e 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -807,9 +807,10 @@ describe SystemNoteService do
expect(WebMock).to have_requested(:post, jira_api_remote_link_url(jira_issue)).with(
body: hash_including(
GlobalID: "GitLab",
+ relationship: 'mentioned on',
object: {
url: project_commit_url(project, commit),
- title: "GitLab: Mentioned on commit - #{commit.title}",
+ title: "Commit - #{commit.title}",
icon: { title: "GitLab", url16x16: favicon_path },
status: { resolved: false }
}
@@ -833,9 +834,10 @@ describe SystemNoteService do
expect(WebMock).to have_requested(:post, jira_api_remote_link_url(jira_issue)).with(
body: hash_including(
GlobalID: "GitLab",
+ relationship: 'mentioned on',
object: {
url: project_issue_url(project, issue),
- title: "GitLab: Mentioned on issue - #{issue.title}",
+ title: "Issue - #{issue.title}",
icon: { title: "GitLab", url16x16: favicon_path },
status: { resolved: false }
}
@@ -859,9 +861,10 @@ describe SystemNoteService do
expect(WebMock).to have_requested(:post, jira_api_remote_link_url(jira_issue)).with(
body: hash_including(
GlobalID: "GitLab",
+ relationship: 'mentioned on',
object: {
url: project_snippet_url(project, snippet),
- title: "GitLab: Mentioned on snippet - #{snippet.title}",
+ title: "Snippet - #{snippet.title}",
icon: { title: "GitLab", url16x16: favicon_path },
status: { resolved: false }
}
diff --git a/spec/services/users/activity_service_spec.rb b/spec/services/users/activity_service_spec.rb
index 719b4adf212..3c0a4ac8e18 100644
--- a/spec/services/users/activity_service_spec.rb
+++ b/spec/services/users/activity_service_spec.rb
@@ -26,6 +26,12 @@ describe Users::ActivityService do
.from(last_activity_on)
.to(Date.today)
end
+
+ it 'tries to obtain ExclusiveLease' do
+ expect(Gitlab::ExclusiveLease).to receive(:new).and_call_original
+
+ subject.execute
+ end
end
context 'when a bad object is passed' do
@@ -46,6 +52,12 @@ describe Users::ActivityService do
it 'does not update last_activity_on' do
expect { subject.execute }.not_to change(user, :last_activity_on)
end
+
+ it 'does not try to obtain ExclusiveLease' do
+ expect(Gitlab::ExclusiveLease).not_to receive(:new)
+
+ subject.execute
+ end
end
context 'when in GitLab read-only instance' do
diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb
index 5945a7dc0ad..747e04fb18c 100644
--- a/spec/services/web_hook_service_spec.rb
+++ b/spec/services/web_hook_service_spec.rb
@@ -102,7 +102,7 @@ describe WebHookService do
exception = exception_class.new('Exception message')
WebMock.stub_request(:post, project_hook.url).to_raise(exception)
- expect(service_instance.execute).to eq({ status: :error, message: exception.message })
+ expect(service_instance.execute).to eq({ status: :error, message: exception.to_s })
expect { service_instance.execute }.not_to raise_error
end
end
diff --git a/spec/support/controllers/githubish_import_controller_shared_examples.rb b/spec/support/controllers/githubish_import_controller_shared_examples.rb
index 697f999e4c4..5bb1269a19d 100644
--- a/spec/support/controllers/githubish_import_controller_shared_examples.rb
+++ b/spec/support/controllers/githubish_import_controller_shared_examples.rb
@@ -58,36 +58,54 @@ end
shared_examples 'a GitHub-ish import controller: GET status' do
let(:new_import_url) { public_send("new_import_#{provider}_url") }
let(:user) { create(:user) }
- let(:repo) { OpenStruct.new(login: 'vim', full_name: 'asd/vim') }
+ let(:repo) { OpenStruct.new(login: 'vim', full_name: 'asd/vim', name: 'vim', owner: { login: 'owner' }) }
let(:org) { OpenStruct.new(login: 'company') }
- let(:org_repo) { OpenStruct.new(login: 'company', full_name: 'company/repo') }
- let(:extra_assign_expectations) { {} }
+ let(:org_repo) { OpenStruct.new(login: 'company', full_name: 'company/repo', name: 'repo', owner: { login: 'owner' }) }
before do
assign_session_token(provider)
end
- it "assigns variables" do
- project = create(:project, import_type: provider, namespace: user.namespace)
+ it "returns variables for json request" do
+ project = create(:project, import_type: provider, namespace: user.namespace, import_status: :finished, import_source: 'example/repo')
+ group = create(:group)
+ group.add_owner(user)
stub_client(repos: [repo, org_repo], orgs: [org], org_repos: [org_repo])
- get :status
+ get :status, format: :json
- expect(assigns(:already_added_projects)).to eq([project])
- expect(assigns(:repos)).to eq([repo, org_repo])
- extra_assign_expectations.each do |key, value|
- expect(assigns(key)).to eq(value)
- end
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response.dig("imported_projects", 0, "id")).to eq(project.id)
+ expect(json_response.dig("provider_repos", 0, "id")).to eq(repo.id)
+ expect(json_response.dig("provider_repos", 1, "id")).to eq(org_repo.id)
+ expect(json_response.dig("namespaces", 0, "id")).to eq(group.id)
end
it "does not show already added project" do
- project = create(:project, import_type: provider, namespace: user.namespace, import_source: 'asd/vim')
+ project = create(:project, import_type: provider, namespace: user.namespace, import_status: :finished, import_source: 'asd/vim')
stub_client(repos: [repo], orgs: [])
+ get :status, format: :json
+
+ expect(json_response.dig("imported_projects", 0, "id")).to eq(project.id)
+ expect(json_response.dig("provider_repos")).to eq([])
+ end
+
+ it "touches the etag cache store" do
+ expect(stub_client(repos: [], orgs: [])).to receive(:repos)
+ expect_next_instance_of(Gitlab::EtagCaching::Store) do |store|
+ expect(store).to receive(:touch) { "realtime_changes_import_#{provider}_path" }
+ end
+
+ get :status, format: :json
+ end
+
+ it "requests provider repos list" do
+ expect(stub_client(repos: [], orgs: [])).to receive(:repos)
+
get :status
- expect(assigns(:already_added_projects)).to eq([project])
- expect(assigns(:repos)).to eq([])
+ expect(response).to have_gitlab_http_status(200)
end
it "handles an invalid access token" do
@@ -100,13 +118,32 @@ shared_examples 'a GitHub-ish import controller: GET status' do
expect(controller).to redirect_to(new_import_url)
expect(flash[:alert]).to eq("Access denied to your #{Gitlab::ImportSources.title(provider.to_s)} account.")
end
+
+ it "does not produce N+1 database queries" do
+ stub_client(repos: [repo], orgs: [])
+ group_a = create(:group)
+ group_a.add_owner(user)
+ create(:project, :import_started, import_type: provider, namespace: user.namespace)
+
+ control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do
+ get :status, format: :json
+ end.count
+
+ stub_client(repos: [repo, org_repo], orgs: [])
+ group_b = create(:group)
+ group_b.add_owner(user)
+ create(:project, :import_started, import_type: provider, namespace: user.namespace)
+
+ expect { get :status, format: :json }
+ .not_to exceed_all_query_limit(control_count)
+ end
end
shared_examples 'a GitHub-ish import controller: POST create' do
let(:user) { create(:user) }
- let(:project) { create(:project) }
let(:provider_username) { user.username }
let(:provider_user) { OpenStruct.new(login: provider_username) }
+ let(:project) { create(:project, import_type: provider, import_status: :finished, import_source: "#{provider_username}/vim") }
let(:provider_repo) do
OpenStruct.new(
name: 'vim',
@@ -145,6 +182,17 @@ shared_examples 'a GitHub-ish import controller: POST create' do
expect(json_response['errors']).to eq('Name is invalid, Path is old')
end
+ it "touches the etag cache store" do
+ allow(Gitlab::LegacyGithubImport::ProjectCreator)
+ .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider)
+ .and_return(double(execute: project))
+ expect_next_instance_of(Gitlab::EtagCaching::Store) do |store|
+ expect(store).to receive(:touch) { "realtime_changes_import_#{provider}_path" }
+ end
+
+ post :create, format: :json
+ end
+
context "when the repository owner is the provider user" do
context "when the provider user and GitLab user's usernames match" do
it "takes the current user's namespace" do
@@ -351,7 +399,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do
it 'does not create a new namespace under the user namespace' do
expect(Gitlab::LegacyGithubImport::ProjectCreator)
.to receive(:new).with(provider_repo, test_name, user.namespace, user, access_params, type: provider)
- .and_return(double(execute: build_stubbed(:project)))
+ .and_return(double(execute: project))
expect { post :create, params: { target_namespace: "#{user.namespace_path}/test_group", new_name: test_name }, format: :js }
.not_to change { Namespace.count }
@@ -365,7 +413,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do
it 'does not take the selected namespace and name' do
expect(Gitlab::LegacyGithubImport::ProjectCreator)
.to receive(:new).with(provider_repo, test_name, user.namespace, user, access_params, type: provider)
- .and_return(double(execute: build_stubbed(:project)))
+ .and_return(double(execute: project))
post :create, params: { target_namespace: 'foo/foobar/bar', new_name: test_name }, format: :js
end
@@ -373,7 +421,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do
it 'does not create the namespaces' do
allow(Gitlab::LegacyGithubImport::ProjectCreator)
.to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider)
- .and_return(double(execute: build_stubbed(:project)))
+ .and_return(double(execute: project))
expect { post :create, params: { target_namespace: 'foo/foobar/bar', new_name: test_name }, format: :js }
.not_to change { Namespace.count }
@@ -390,7 +438,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do
expect(Gitlab::LegacyGithubImport::ProjectCreator)
.to receive(:new).with(provider_repo, test_name, group, user, access_params, type: provider)
- .and_return(double(execute: build_stubbed(:project)))
+ .and_return(double(execute: project))
post :create, params: { target_namespace: 'foo', new_name: test_name }, format: :js
end
@@ -407,3 +455,20 @@ shared_examples 'a GitHub-ish import controller: POST create' do
end
end
end
+
+shared_examples 'a GitHub-ish import controller: GET realtime_changes' do
+ let(:user) { create(:user) }
+
+ before do
+ assign_session_token(provider)
+ end
+
+ it 'sets a Poll-Interval header' do
+ project = create(:project, import_type: provider, namespace: user.namespace, import_status: :finished, import_source: 'example/repo')
+
+ get :realtime_changes
+
+ expect(json_response).to eq([{ "id" => project.id, "import_status" => project.import_status }])
+ expect(Integer(response.headers['Poll-Interval'])).to be > -1
+ end
+end
diff --git a/spec/support/features/variable_list_shared_examples.rb b/spec/support/features/variable_list_shared_examples.rb
index 0a464d77cb7..73156d18c1b 100644
--- a/spec/support/features/variable_list_shared_examples.rb
+++ b/spec/support/features/variable_list_shared_examples.rb
@@ -8,7 +8,7 @@ shared_examples 'variable list' do
it 'adds new CI variable' do
page.within('.js-ci-variable-list-section .js-row:last-child') do
find('.js-ci-variable-input-key').set('key')
- find('.js-ci-variable-input-value').set('key value')
+ find('.js-ci-variable-input-value').set('key_value')
end
click_button('Save variables')
@@ -19,7 +19,7 @@ shared_examples 'variable list' do
# We check the first row because it re-sorts to alphabetical order on refresh
page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do
expect(find('.js-ci-variable-input-key').value).to eq('key')
- expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key value')
+ expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key_value')
end
end
@@ -44,7 +44,7 @@ shared_examples 'variable list' do
it 'adds new protected variable' do
page.within('.js-ci-variable-list-section .js-row:last-child') do
find('.js-ci-variable-input-key').set('key')
- find('.js-ci-variable-input-value').set('key value')
+ find('.js-ci-variable-input-value').set('key_value')
find('.ci-variable-protected-item .js-project-feature-toggle').click
expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true')
@@ -58,7 +58,7 @@ shared_examples 'variable list' do
# We check the first row because it re-sorts to alphabetical order on refresh
page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do
expect(find('.js-ci-variable-input-key').value).to eq('key')
- expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key value')
+ expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key_value')
expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true')
end
end
diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb
index ea3a03879c5..e468ee4676d 100644
--- a/spec/support/helpers/graphql_helpers.rb
+++ b/spec/support/helpers/graphql_helpers.rb
@@ -84,7 +84,7 @@ module GraphqlHelpers
QUERY
end
- def all_graphql_fields_for(class_name)
+ def all_graphql_fields_for(class_name, parent_types = Set.new)
type = GitlabSchema.types[class_name.to_s]
return "" unless type
@@ -92,8 +92,17 @@ module GraphqlHelpers
# We can't guess arguments, so skip fields that require them
next if required_arguments?(field)
+ singular_field_type = field_type(field)
+
+ # If field type is the same as parent type, then we're hitting into
+ # mutual dependency. Break it from infinite recursion
+ next if parent_types.include?(singular_field_type)
+
if nested_fields?(field)
- "#{name} { #{all_graphql_fields_for(field_type(field))} }"
+ fields =
+ all_graphql_fields_for(singular_field_type, parent_types | [type])
+
+ "#{name} { #{fields} }"
else
name
end
diff --git a/spec/support/helpers/reactive_caching_helpers.rb b/spec/support/helpers/reactive_caching_helpers.rb
index a575aa99b79..b76b53db0b9 100644
--- a/spec/support/helpers/reactive_caching_helpers.rb
+++ b/spec/support/helpers/reactive_caching_helpers.rb
@@ -10,7 +10,7 @@ module ReactiveCachingHelpers
def stub_reactive_cache(subject = nil, data = nil, *qualifiers)
allow(ReactiveCachingWorker).to receive(:perform_async)
allow(ReactiveCachingWorker).to receive(:perform_in)
- write_reactive_cache(subject, data, *qualifiers) if data
+ write_reactive_cache(subject, data, *qualifiers) unless data.nil?
end
def synchronous_reactive_cache(subject)
diff --git a/spec/support/helpers/stub_feature_flags.rb b/spec/support/helpers/stub_feature_flags.rb
index 4061a8d1bc9..48258692304 100644
--- a/spec/support/helpers/stub_feature_flags.rb
+++ b/spec/support/helpers/stub_feature_flags.rb
@@ -1,11 +1,30 @@
module StubFeatureFlags
# Stub Feature flags with `flag_name: true/false`
#
- # @param [Hash] features where key is feature name and value is boolean whether enabled or not
+ # @param [Hash] features where key is feature name and value is boolean whether enabled or not.
+ # Alternatively, you can specify Hash to enable the flag on a specific thing.
+ #
+ # Examples
+ # - `stub_feature_flags(ci_live_trace: false)` ... Disable `ci_live_trace`
+ # feature flag globally.
+ # - `stub_feature_flags(ci_live_trace: { enabled: false, thing: project })` ...
+ # Disable `ci_live_trace` feature flag on the specified project.
def stub_feature_flags(features)
- features.each do |feature_name, enabled|
- allow(Feature).to receive(:enabled?).with(feature_name, any_args) { enabled }
- allow(Feature).to receive(:enabled?).with(feature_name.to_s, any_args) { enabled }
+ features.each do |feature_name, option|
+ if option.is_a?(Hash)
+ enabled, thing = option.values_at(:enabled, :thing)
+ else
+ enabled = option
+ thing = nil
+ end
+
+ if thing
+ allow(Feature).to receive(:enabled?).with(feature_name, thing, any_args) { enabled }
+ allow(Feature).to receive(:enabled?).with(feature_name.to_s, thing, any_args) { enabled }
+ else
+ allow(Feature).to receive(:enabled?).with(feature_name, any_args) { enabled }
+ allow(Feature).to receive(:enabled?).with(feature_name.to_s, any_args) { enabled }
+ end
end
end
end
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index f485eb7b0eb..80a22134021 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -63,7 +63,8 @@ module TestEnv
'after-create-delete-modify-move' => 'ba3faa7',
'with-codeowners' => '219560e',
'submodule_inside_folder' => 'b491b92',
- 'png-lfs' => 'fe42f41'
+ 'png-lfs' => 'fe42f41',
+ 'sha-starting-with-large-number' => '8426165'
}.freeze
# gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily
@@ -146,12 +147,15 @@ module TestEnv
version: Gitlab::Shell.version_required,
task: 'gitlab:shell:install')
- create_fake_git_hooks
+ # gitlab-shell hooks don't work in our test environment because they try to make internal API calls
+ sabotage_gitlab_shell_hooks
end
- def create_fake_git_hooks
- # gitlab-shell hooks don't work in our test environment because they try to make internal API calls
- hooks_dir = File.join(Gitlab.config.gitlab_shell.path, 'hooks')
+ def sabotage_gitlab_shell_hooks
+ create_fake_git_hooks(Gitlab::Shell.new.hooks_path)
+ end
+
+ def create_fake_git_hooks(hooks_dir)
%w[pre-receive post-receive update].each do |hook|
File.open(File.join(hooks_dir, hook), 'w', 0755) { |f| f.puts '#!/bin/sh' }
end
@@ -168,6 +172,7 @@ module TestEnv
task: "gitlab:gitaly:install[#{install_gitaly_args}]") do
Gitlab::SetupHelper.create_gitaly_configuration(gitaly_dir, { 'default' => repos_path }, force: true)
+ create_fake_git_hooks(File.join(gitaly_dir, 'ruby/git-hooks'))
start_gitaly(gitaly_dir)
end
end
diff --git a/spec/support/matchers/graphql_matchers.rb b/spec/support/matchers/graphql_matchers.rb
index 7be84838e00..7894484f590 100644
--- a/spec/support/matchers/graphql_matchers.rb
+++ b/spec/support/matchers/graphql_matchers.rb
@@ -1,8 +1,6 @@
RSpec::Matchers.define :require_graphql_authorizations do |*expected|
match do |field|
- field_definition = field.metadata[:type_class]
- expect(field_definition).to respond_to(:required_permissions)
- expect(field_definition.required_permissions).to contain_exactly(*expected)
+ expect(field.metadata[:authorize]).to eq(*expected)
end
end
diff --git a/spec/support/matchers/not_changed_matcher.rb b/spec/support/matchers/not_changed_matcher.rb
new file mode 100644
index 00000000000..8ef4694982d
--- /dev/null
+++ b/spec/support/matchers/not_changed_matcher.rb
@@ -0,0 +1,3 @@
+# frozen_string_literal: true
+
+RSpec::Matchers.define_negated_matcher :not_change, :change
diff --git a/spec/support/shared_contexts/services_shared_context.rb b/spec/support/shared_contexts/services_shared_context.rb
index 23f9b46ae0c..d92e8318fa0 100644
--- a/spec/support/shared_contexts/services_shared_context.rb
+++ b/spec/support/shared_contexts/services_shared_context.rb
@@ -19,7 +19,7 @@ Service.available_services_names.each do |service|
elsif service == 'irker' && k == :server_port
hash.merge!(k => 1234)
elsif service == 'jira' && k == :jira_issue_transition_id
- hash.merge!(k => 1234)
+ hash.merge!(k => '1,2,3')
else
hash.merge!(k => "someword")
end
diff --git a/spec/support/shared_examples/graphql/issuable_state_shared_examples.rb b/spec/support/shared_examples/graphql/issuable_state_shared_examples.rb
new file mode 100644
index 00000000000..713f0a879c1
--- /dev/null
+++ b/spec/support/shared_examples/graphql/issuable_state_shared_examples.rb
@@ -0,0 +1,5 @@
+RSpec.shared_examples 'issuable state' do
+ it 'exposes all the existing issuable states' do
+ expect(described_class.values.keys).to include(*%w[opened closed locked])
+ end
+end
diff --git a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
index c96a65cb56a..b8c19cab0c4 100644
--- a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
+++ b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
@@ -9,6 +9,19 @@ shared_examples 'cluster application status specs' do |application_name|
end
end
+ describe '.available' do
+ subject { described_class.available }
+
+ let!(:installed_cluster) { create(application_name, :installed) }
+ let!(:updated_cluster) { create(application_name, :updated) }
+
+ before do
+ create(application_name, :errored)
+ end
+
+ it { is_expected.to contain_exactly(installed_cluster, updated_cluster) }
+ end
+
describe 'status state machine' do
describe '#make_installing' do
subject { create(application_name, :scheduled) }
diff --git a/spec/support/shared_examples/notify_shared_examples.rb b/spec/support/shared_examples/notify_shared_examples.rb
index a38354060cf..4fff1c4e228 100644
--- a/spec/support/shared_examples/notify_shared_examples.rb
+++ b/spec/support/shared_examples/notify_shared_examples.rb
@@ -252,3 +252,31 @@ shared_examples 'a note email' do
end
end
end
+
+shared_examples 'appearance header and footer enabled' do
+ it "contains header and footer" do
+ create :appearance, header_message: "Foo", footer_message: "Bar", email_header_and_footer_enabled: true
+
+ aggregate_failures do
+ expect(subject.html_part).to have_body_text("<div class=\"header-message\" style=\"\"><p>Foo</p></div>")
+ expect(subject.html_part).to have_body_text("<div class=\"footer-message\" style=\"\"><p>Bar</p></div>")
+
+ expect(subject.text_part).to have_body_text(/^Foo/)
+ expect(subject.text_part).to have_body_text(/Bar$/)
+ end
+ end
+end
+
+shared_examples 'appearance header and footer not enabled' do
+ it "does not contain header and footer" do
+ create :appearance, header_message: "Foo", footer_message: "Bar", email_header_and_footer_enabled: false
+
+ aggregate_failures do
+ expect(subject.html_part).not_to have_body_text("<div class=\"header-message\" style=\"\"><p>Foo</p></div>")
+ expect(subject.html_part).not_to have_body_text("<div class=\"footer-message\" style=\"\"><p>Bar</p></div>")
+
+ expect(subject.text_part).not_to have_body_text(/^Foo/)
+ expect(subject.text_part).not_to have_body_text(/Bar$/)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/merge_requests_list.rb b/spec/support/shared_examples/requests/api/merge_requests_list.rb
index 453f42251c8..6713ec47ace 100644
--- a/spec/support/shared_examples/requests/api/merge_requests_list.rb
+++ b/spec/support/shared_examples/requests/api/merge_requests_list.rb
@@ -257,6 +257,38 @@ shared_examples 'merge requests list' do
expect(response_dates).to eq(response_dates.sort.reverse)
end
+ context '2 merge requests with equal created_at' do
+ let!(:closed_mr2) do
+ create :merge_request,
+ state: 'closed',
+ milestone: milestone1,
+ author: user,
+ assignee: user,
+ source_project: project,
+ target_project: project,
+ title: "Test",
+ created_at: @mr_earlier.created_at
+ end
+
+ it 'page breaks first page correctly' do
+ get api("#{endpoint_path}?sort=desc&per_page=4", user)
+
+ response_ids = json_response.map { |merge_request| merge_request['id'] }
+
+ expect(response_ids).to include(closed_mr2.id)
+ expect(response_ids).not_to include(@mr_earlier.id)
+ end
+
+ it 'page breaks second page correctly' do
+ get api("#{endpoint_path}?sort=desc&per_page=4&page=2", user)
+
+ response_ids = json_response.map { |merge_request| merge_request['id'] }
+
+ expect(response_ids).not_to include(closed_mr2.id)
+ expect(response_ids).to include(@mr_earlier.id)
+ end
+ end
+
it 'returns an array of merge_requests ordered by updated_at' do
path = endpoint_path + '?order_by=updated_at'
diff --git a/spec/support/shared_examples/requests/api/notes.rb b/spec/support/shared_examples/requests/api/notes.rb
index 71499e85654..57eefd5ef01 100644
--- a/spec/support/shared_examples/requests/api/notes.rb
+++ b/spec/support/shared_examples/requests/api/notes.rb
@@ -8,13 +8,45 @@ shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
create_list(:note, 3, params)
end
- it 'sorts by created_at in descending order by default' do
- get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user)
+ context 'without sort params' do
+ it 'sorts by created_at in descending order by default' do
+ get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user)
- response_dates = json_response.map { |note| note['created_at'] }
+ response_dates = json_response.map { |note| note['created_at'] }
- expect(json_response.length).to eq(4)
- expect(response_dates).to eq(response_dates.sort.reverse)
+ expect(json_response.length).to eq(4)
+ expect(response_dates).to eq(response_dates.sort.reverse)
+ end
+
+ context '2 notes with equal created_at' do
+ before do
+ @first_note = Note.first
+
+ params = { noteable: noteable, author: user }
+ params[:project] = parent if parent.is_a?(Project)
+ params[:created_at] = @first_note.created_at
+
+ @note2 = create(:note, params)
+ end
+
+ it 'page breaks first page correctly' do
+ get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes?per_page=4", user)
+
+ response_ids = json_response.map { |note| note['id'] }
+
+ expect(response_ids).to include(@note2.id)
+ expect(response_ids).not_to include(@first_note.id)
+ end
+
+ it 'page breaks second page correctly' do
+ get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes?per_page=4&page=2", user)
+
+ response_ids = json_response.map { |note| note['id'] }
+
+ expect(response_ids).not_to include(@note2.id)
+ expect(response_ids).to include(@first_note.id)
+ end
+ end
end
it 'sorts by ascending order when requested' do
diff --git a/spec/support/shared_examples/serializers/diff_file_entity_examples.rb b/spec/support/shared_examples/serializers/diff_file_entity_examples.rb
index 1770308f789..96cb71be737 100644
--- a/spec/support/shared_examples/serializers/diff_file_entity_examples.rb
+++ b/spec/support/shared_examples/serializers/diff_file_entity_examples.rb
@@ -6,9 +6,9 @@ shared_examples 'diff file base entity' do
:submodule_tree_url, :old_path_html,
:new_path_html, :blob, :can_modify_blob,
:file_hash, :file_path, :old_path, :new_path,
- :collapsed, :text, :diff_refs, :stored_externally,
+ :viewer, :diff_refs, :stored_externally,
:external_storage, :renamed_file, :deleted_file,
- :mode_changed, :a_mode, :b_mode, :new_file)
+ :a_mode, :b_mode, :new_file)
end
# Converted diff files from GitHub import does not contain blob file
@@ -30,9 +30,9 @@ shared_examples 'diff file entity' do
it_behaves_like 'diff file base entity'
it 'exposes correct attributes' do
- expect(subject).to include(:too_large, :added_lines, :removed_lines,
+ expect(subject).to include(:added_lines, :removed_lines,
:context_lines_path, :highlighted_diff_lines,
- :parallel_diff_lines, :empty)
+ :parallel_diff_lines)
end
it 'includes viewer' do
diff --git a/spec/support/shared_examples/services/boards/issues_move_service.rb b/spec/support/shared_examples/services/boards/issues_move_service.rb
index ec44b99d10e..9dbd1d8e867 100644
--- a/spec/support/shared_examples/services/boards/issues_move_service.rb
+++ b/spec/support/shared_examples/services/boards/issues_move_service.rb
@@ -39,6 +39,22 @@ shared_examples 'issues move service' do |group|
end
end
+ context 'when moving to backlog' do
+ let(:milestone) { create(:milestone, project: project) }
+ let!(:backlog) { create(:backlog_list, board: board1) }
+
+ let(:issue) { create(:labeled_issue, project: project, labels: [bug, development, testing, regression], milestone: milestone) }
+ let(:params) { { board_id: board1.id, from_list_id: list2.id, to_list_id: backlog.id } }
+
+ it 'keeps labels and milestone' do
+ described_class.new(parent, user, params).execute(issue)
+ issue.reload
+
+ expect(issue.labels).to contain_exactly(bug, regression)
+ expect(issue.milestone).to eq(milestone)
+ end
+ end
+
context 'when moving from closed' do
let(:issue) { create(:labeled_issue, :closed, project: project, labels: [bug]) }
let(:params) { { board_id: board1.id, from_list_id: closed.id, to_list_id: list2.id } }
diff --git a/spec/tasks/gitlab/shell_rake_spec.rb b/spec/tasks/gitlab/shell_rake_spec.rb
index 0ed5d3e27b9..a9d14070177 100644
--- a/spec/tasks/gitlab/shell_rake_spec.rb
+++ b/spec/tasks/gitlab/shell_rake_spec.rb
@@ -8,7 +8,7 @@ describe 'gitlab:shell rake tasks' do
end
after do
- TestEnv.create_fake_git_hooks
+ TestEnv.sabotage_gitlab_shell_hooks
end
describe 'install task' do
diff --git a/spec/views/layouts/_head.html.haml_spec.rb b/spec/views/layouts/_head.html.haml_spec.rb
index 9d1efcabb80..cbb4199954a 100644
--- a/spec/views/layouts/_head.html.haml_spec.rb
+++ b/spec/views/layouts/_head.html.haml_spec.rb
@@ -62,6 +62,14 @@ describe 'layouts/_head' do
end
end
+ it 'adds selected syntax highlight stylesheet' do
+ allow_any_instance_of(PreferencesHelper).to receive(:user_color_scheme).and_return("solarised-light")
+
+ render
+
+ expect(rendered).to match('<link rel="stylesheet" media="all" href="/stylesheets/highlight/themes/solarised-light.css" />')
+ end
+
def stub_helper_with_safe_string(method)
allow_any_instance_of(PageLayoutHelper).to receive(method)
.and_return(%q{foo" http-equiv="refresh}.html_safe)
diff --git a/spec/views/projects/commits/_commit.html.haml_spec.rb b/spec/views/projects/commits/_commit.html.haml_spec.rb
index 00547e433c4..6bf1b5fd2d0 100644
--- a/spec/views/projects/commits/_commit.html.haml_spec.rb
+++ b/spec/views/projects/commits/_commit.html.haml_spec.rb
@@ -1,15 +1,15 @@
require 'spec_helper'
describe 'projects/commits/_commit.html.haml' do
+ let(:project) { create(:project, :repository) }
+ let(:commit) { project.repository.commit(ref) }
+
before do
allow(view).to receive(:current_application_settings).and_return(Gitlab::CurrentSettings.current_application_settings)
end
- context 'with a singed commit' do
- let(:project) { create(:project, :repository) }
- let(:repository) { project.repository }
+ context 'with a signed commit' do
let(:ref) { GpgHelpers::SIGNED_COMMIT_SHA }
- let(:commit) { repository.commit(ref) }
it 'does not display a loading spinner for GPG status' do
render partial: 'projects/commits/commit', locals: {
@@ -23,4 +23,55 @@ describe 'projects/commits/_commit.html.haml' do
end
end
end
+
+ context 'with ci status' do
+ let(:ref) { 'master' }
+ let(:user) { create(:user) }
+
+ before do
+ allow(view).to receive(:current_user).and_return(user)
+
+ project.add_developer(user)
+
+ create(
+ :ci_empty_pipeline,
+ ref: 'master',
+ sha: commit.id,
+ status: 'success',
+ project: project
+ )
+ end
+
+ context 'when pipelines are disabled' do
+ before do
+ allow(project).to receive(:builds_enabled?).and_return(false)
+ end
+
+ it 'does not display a ci status icon' do
+ render partial: 'projects/commits/commit', locals: {
+ project: project,
+ ref: ref,
+ commit: commit
+ }
+
+ expect(rendered).not_to have_css('.ci-status-link')
+ end
+ end
+
+ context 'when pipelines are enabled' do
+ before do
+ allow(project).to receive(:builds_enabled?).and_return(true)
+ end
+
+ it 'does display a ci status icon when pipelines are enabled' do
+ render partial: 'projects/commits/commit', locals: {
+ project: project,
+ ref: ref,
+ commit: commit
+ }
+
+ expect(rendered).to have_css('.ci-status-link')
+ end
+ end
+ end
end
diff --git a/spec/views/projects/issues/_merge_requests_status.html.haml_spec.rb b/spec/views/projects/issues/_merge_requests_status.html.haml_spec.rb
index 02c225292ce..9424795749d 100644
--- a/spec/views/projects/issues/_merge_requests_status.html.haml_spec.rb
+++ b/spec/views/projects/issues/_merge_requests_status.html.haml_spec.rb
@@ -2,6 +2,12 @@
require 'spec_helper'
describe 'projects/issues/_merge_requests_status.html.haml' do
+ around do |ex|
+ Timecop.freeze(Date.new(2018, 7, 22)) do
+ ex.run
+ end
+ end
+
it 'shows date of status change in tooltip' do
merge_request = create(:merge_request, created_at: 1.month.ago)
diff --git a/spec/views/projects/issues/show.html.haml_spec.rb b/spec/views/projects/issues/show.html.haml_spec.rb
index ff88efd0e31..1d9c6d36ad7 100644
--- a/spec/views/projects/issues/show.html.haml_spec.rb
+++ b/spec/views/projects/issues/show.html.haml_spec.rb
@@ -21,12 +21,24 @@ describe 'projects/issues/show' do
allow(issue).to receive(:closed?).and_return(true)
end
- it 'shows "Closed (moved)" if an issue has been moved' do
- allow(issue).to receive(:moved?).and_return(true)
+ context 'when the issue was moved' do
+ let(:new_issue) { create(:issue, project: project, author: user) }
- render
+ before do
+ issue.moved_to = new_issue
+ end
+
+ it 'shows "Closed (moved)" if an issue has been moved' do
+ render
+
+ expect(rendered).to have_selector('.status-box-issue-closed:not(.hidden)', text: 'Closed (moved)')
+ end
+
+ it 'links "moved" to the new issue the original issue was moved to' do
+ render
- expect(rendered).to have_selector('.status-box-issue-closed:not(.hidden)', text: 'Closed (moved)')
+ expect(rendered).to have_selector("a[href=\"#{issue_path(new_issue)}\"]", text: 'moved')
+ end
end
it 'shows "Closed" if an issue has not been moved' do
diff --git a/spec/views/projects/settings/operations/show.html.haml_spec.rb b/spec/views/projects/settings/operations/show.html.haml_spec.rb
index 8e34521c7c8..1bca8bba940 100644
--- a/spec/views/projects/settings/operations/show.html.haml_spec.rb
+++ b/spec/views/projects/settings/operations/show.html.haml_spec.rb
@@ -30,7 +30,6 @@ describe 'projects/settings/operations/show' do
expect(rendered).to have_content _('Error Tracking')
expect(rendered).to have_content _('To link Sentry to GitLab, enter your Sentry URL and Auth Token')
- expect(rendered).to have_content _('Active')
end
end
end
diff --git a/spec/workers/build_finished_worker_spec.rb b/spec/workers/build_finished_worker_spec.rb
index acd8da11d8d..ccb26849e67 100644
--- a/spec/workers/build_finished_worker_spec.rb
+++ b/spec/workers/build_finished_worker_spec.rb
@@ -26,5 +26,24 @@ describe BuildFinishedWorker do
.not_to raise_error
end
end
+
+ it 'schedules a ChatNotification job for a chat build' do
+ build = create(:ci_build, :success, pipeline: create(:ci_pipeline, source: :chat))
+
+ expect(ChatNotificationWorker)
+ .to receive(:perform_async)
+ .with(build.id)
+
+ described_class.new.perform(build.id)
+ end
+
+ it 'does not schedule a ChatNotification job for a regular build' do
+ build = create(:ci_build, :success, pipeline: create(:ci_pipeline))
+
+ expect(ChatNotificationWorker)
+ .not_to receive(:perform_async)
+
+ described_class.new.perform(build.id)
+ end
end
end
diff --git a/spec/workers/chat_notification_worker_spec.rb b/spec/workers/chat_notification_worker_spec.rb
new file mode 100644
index 00000000000..91695674f5d
--- /dev/null
+++ b/spec/workers/chat_notification_worker_spec.rb
@@ -0,0 +1,91 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ChatNotificationWorker do
+ let(:worker) { described_class.new }
+ let(:chat_build) do
+ create(:ci_build, pipeline: create(:ci_pipeline, source: :chat))
+ end
+
+ describe '#perform' do
+ it 'does nothing when the build no longer exists' do
+ expect(worker).not_to receive(:send_response)
+
+ worker.perform(-1)
+ end
+
+ it 'sends a response for an existing build' do
+ expect(worker)
+ .to receive(:send_response)
+ .with(an_instance_of(Ci::Build))
+
+ worker.perform(chat_build.id)
+ end
+
+ it 'reschedules the job if the trace sections could not be found' do
+ expect(worker)
+ .to receive(:send_response)
+ .and_raise(Gitlab::Chat::Output::MissingBuildSectionError)
+
+ expect(described_class)
+ .to receive(:perform_in)
+ .with(described_class::RESCHEDULE_INTERVAL, chat_build.id)
+
+ worker.perform(chat_build.id)
+ end
+ end
+
+ describe '#send_response' do
+ context 'when a responder could not be found' do
+ it 'does nothing' do
+ expect(Gitlab::Chat::Responder)
+ .to receive(:responder_for)
+ .with(chat_build)
+ .and_return(nil)
+
+ expect(worker.send_response(chat_build)).to be_nil
+ end
+ end
+
+ context 'when a responder could be found' do
+ let(:responder) { double(:responder) }
+
+ before do
+ allow(Gitlab::Chat::Responder)
+ .to receive(:responder_for)
+ .with(chat_build)
+ .and_return(responder)
+ end
+
+ it 'sends the response for a succeeded build' do
+ output = double(:output, to_s: 'this is the build output')
+
+ expect(chat_build)
+ .to receive(:success?)
+ .and_return(true)
+
+ expect(responder)
+ .to receive(:success)
+ .with(an_instance_of(String))
+
+ expect(Gitlab::Chat::Output)
+ .to receive(:new)
+ .with(chat_build)
+ .and_return(output)
+
+ worker.send_response(chat_build)
+ end
+
+ it 'sends the response for a failed build' do
+ expect(chat_build)
+ .to receive(:success?)
+ .and_return(false)
+
+ expect(responder).to receive(:failure)
+
+ worker.send_response(chat_build)
+ end
+ end
+ end
+end
diff --git a/spec/workers/project_migrate_hashed_storage_worker_spec.rb b/spec/workers/hashed_storage/project_migrate_worker_spec.rb
index 333eb6a0569..340e722aa7e 100644
--- a/spec/workers/project_migrate_hashed_storage_worker_spec.rb
+++ b/spec/workers/hashed_storage/project_migrate_worker_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe ProjectMigrateHashedStorageWorker, :clean_gitlab_redis_shared_state do
+describe HashedStorage::ProjectMigrateWorker, :clean_gitlab_redis_shared_state do
include ExclusiveLeaseHelpers
describe '#perform' do
diff --git a/spec/workers/hashed_storage/project_rollback_worker_spec.rb b/spec/workers/hashed_storage/project_rollback_worker_spec.rb
new file mode 100644
index 00000000000..d833553c0ec
--- /dev/null
+++ b/spec/workers/hashed_storage/project_rollback_worker_spec.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe HashedStorage::ProjectRollbackWorker, :clean_gitlab_redis_shared_state do
+ include ExclusiveLeaseHelpers
+
+ describe '#perform' do
+ let(:project) { create(:project, :empty_repo) }
+ let(:lease_key) { "project_migrate_hashed_storage_worker:#{project.id}" }
+ let(:lease_timeout) { described_class::LEASE_TIMEOUT }
+ let(:rollback_service) { ::Projects::HashedStorage::RollbackService }
+
+ it 'skips when project no longer exists' do
+ expect(rollback_service).not_to receive(:new)
+
+ subject.perform(-1)
+ end
+
+ it 'skips when project is pending delete' do
+ pending_delete_project = create(:project, :empty_repo, pending_delete: true)
+
+ expect(rollback_service).not_to receive(:new)
+
+ subject.perform(pending_delete_project.id)
+ end
+
+ it 'delegates rollback to service class when have exclusive lease' do
+ stub_exclusive_lease(lease_key, 'uuid', timeout: lease_timeout)
+
+ service_spy = spy
+
+ allow(rollback_service)
+ .to receive(:new).with(project, project.disk_path, logger: subject.logger)
+ .and_return(service_spy)
+
+ subject.perform(project.id)
+
+ expect(service_spy).to have_received(:execute)
+ end
+
+ it 'skips when it cant acquire the exclusive lease' do
+ stub_exclusive_lease_taken(lease_key, timeout: lease_timeout)
+
+ expect(rollback_service).not_to receive(:new)
+
+ subject.perform(project.id)
+ end
+ end
+end
diff --git a/spec/workers/hashed_storage/rollbacker_worker_spec.rb b/spec/workers/hashed_storage/rollbacker_worker_spec.rb
new file mode 100644
index 00000000000..4055f380978
--- /dev/null
+++ b/spec/workers/hashed_storage/rollbacker_worker_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe HashedStorage::RollbackerWorker do
+ subject(:worker) { described_class.new }
+ let(:projects) { create_list(:project, 2, :empty_repo) }
+ let(:ids) { projects.map(&:id) }
+
+ describe '#perform' do
+ it 'delegates to MigratorService' do
+ expect_any_instance_of(Gitlab::HashedStorage::Migrator).to receive(:bulk_rollback).with(start: 5, finish: 10)
+
+ worker.perform(5, 10)
+ end
+
+ it 'rollsback projects in the specified range' do
+ perform_enqueued_jobs do
+ worker.perform(ids.min, ids.max)
+ end
+
+ projects.each do |project|
+ expect(project.reload.legacy_storage?).to be_truthy
+ end
+ end
+ end
+end
diff --git a/spec/workers/project_daily_statistics_worker_spec.rb b/spec/workers/project_daily_statistics_worker_spec.rb
new file mode 100644
index 00000000000..8640add99e5
--- /dev/null
+++ b/spec/workers/project_daily_statistics_worker_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe ProjectDailyStatisticsWorker, '#perform' do
+ let(:worker) { described_class.new }
+ let(:project) { create(:project) }
+
+ describe '#perform' do
+ context 'with a non-existing project' do
+ it 'does nothing' do
+ expect(Projects::FetchStatisticsIncrementService).not_to receive(:new)
+
+ worker.perform(-1)
+ end
+ end
+
+ context 'with an existing project without a repository' do
+ it 'does nothing' do
+ expect(Projects::FetchStatisticsIncrementService).not_to receive(:new)
+
+ worker.perform(project.id)
+ end
+ end
+
+ it 'calls daily_statistics_service with the given project' do
+ project = create(:project, :repository)
+
+ expect_next_instance_of(Projects::FetchStatisticsIncrementService, project) do |service|
+ expect(service).to receive(:execute)
+ end
+
+ worker.perform(project.id)
+ end
+ end
+end
diff --git a/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb b/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb
index 963237ceadf..a13a3046f55 100644
--- a/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb
+++ b/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb
@@ -39,7 +39,7 @@ describe UpdateHeadPipelineForMergeRequestWorker do
let!(:merge_request_pipeline) do
create(:ci_pipeline,
project: project,
- source: :merge_request,
+ source: :merge_request_event,
sha: latest_sha,
merge_request: merge_request)
end
diff --git a/vendor/project_templates/dotnetcore.tar.gz b/vendor/project_templates/dotnetcore.tar.gz
new file mode 100644
index 00000000000..b5dae407f83
--- /dev/null
+++ b/vendor/project_templates/dotnetcore.tar.gz
Binary files differ
diff --git a/vendor/project_templates/gomicro.tar.gz b/vendor/project_templates/gomicro.tar.gz
new file mode 100644
index 00000000000..54a5afcfb69
--- /dev/null
+++ b/vendor/project_templates/gomicro.tar.gz
Binary files differ
diff --git a/vendor/project_templates/nfgitbook.tar.gz b/vendor/project_templates/nfgitbook.tar.gz
new file mode 100644
index 00000000000..e10873e1a9e
--- /dev/null
+++ b/vendor/project_templates/nfgitbook.tar.gz
Binary files differ
diff --git a/vendor/project_templates/nfhexo.tar.gz b/vendor/project_templates/nfhexo.tar.gz
new file mode 100644
index 00000000000..25632f241cc
--- /dev/null
+++ b/vendor/project_templates/nfhexo.tar.gz
Binary files differ
diff --git a/vendor/project_templates/nfhugo.tar.gz b/vendor/project_templates/nfhugo.tar.gz
new file mode 100644
index 00000000000..14e6289b841
--- /dev/null
+++ b/vendor/project_templates/nfhugo.tar.gz
Binary files differ
diff --git a/vendor/project_templates/nfjekyll.tar.gz b/vendor/project_templates/nfjekyll.tar.gz
new file mode 100644
index 00000000000..3b93f8661b5
--- /dev/null
+++ b/vendor/project_templates/nfjekyll.tar.gz
Binary files differ
diff --git a/vendor/project_templates/nfplainhtml.tar.gz b/vendor/project_templates/nfplainhtml.tar.gz
new file mode 100644
index 00000000000..cdf5ea9fe12
--- /dev/null
+++ b/vendor/project_templates/nfplainhtml.tar.gz
Binary files differ
diff --git a/yarn.lock b/yarn.lock
index 8ddcd305d48..efd115205db 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,41 +2,41 @@
# yarn lockfile v1
-"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.0.0-beta.35":
+"@babel/code-frame@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8"
integrity sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==
dependencies:
"@babel/highlight" "^7.0.0"
-"@babel/core@^7.2.2":
- version "7.2.2"
- resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.2.2.tgz#07adba6dde27bb5ad8d8672f15fde3e08184a687"
- integrity sha512-59vB0RWt09cAct5EIe58+NzGP4TFSD3Bz//2/ELy3ZeTeKF6VTD1AXlH8BGGbCX0PuobZBsIzO7IAI9PH67eKw==
+"@babel/core@>=7.1.0", "@babel/core@^7.1.0", "@babel/core@^7.2.2":
+ version "7.3.4"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.3.4.tgz#921a5a13746c21e32445bf0798680e9d11a6530b"
+ integrity sha512-jRsuseXBo9pN197KnDwhhaaBzyZr2oIcLHHTt2oDdQrej5Qp57dCCJafWx5ivU8/alEYDpssYqv1MUqcxwQlrA==
dependencies:
"@babel/code-frame" "^7.0.0"
- "@babel/generator" "^7.2.2"
+ "@babel/generator" "^7.3.4"
"@babel/helpers" "^7.2.0"
- "@babel/parser" "^7.2.2"
+ "@babel/parser" "^7.3.4"
"@babel/template" "^7.2.2"
- "@babel/traverse" "^7.2.2"
- "@babel/types" "^7.2.2"
+ "@babel/traverse" "^7.3.4"
+ "@babel/types" "^7.3.4"
convert-source-map "^1.1.0"
debug "^4.1.0"
json5 "^2.1.0"
- lodash "^4.17.10"
+ lodash "^4.17.11"
resolve "^1.3.2"
semver "^5.4.1"
source-map "^0.5.0"
-"@babel/generator@^7.0.0", "@babel/generator@^7.2.2":
- version "7.2.2"
- resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.2.2.tgz#18c816c70962640eab42fe8cae5f3947a5c65ccc"
- integrity sha512-I4o675J/iS8k+P38dvJ3IBGqObLXyQLTxtrR4u9cSUJOURvafeEWb/pFMOTwtNrmq73mJzyF6ueTbO1BtN0Zeg==
+"@babel/generator@^7.0.0", "@babel/generator@^7.3.4":
+ version "7.3.4"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.3.4.tgz#9aa48c1989257877a9d971296e5b73bfe72e446e"
+ integrity sha512-8EXhHRFqlVVWXPezBW5keTiQi/rJMQTg/Y9uVCEZ0CAF3PKtCCaVRnp64Ii1ujhkoDhhF1fVsImoN4yJ2uz4Wg==
dependencies:
- "@babel/types" "^7.2.2"
+ "@babel/types" "^7.3.4"
jsesc "^2.5.1"
- lodash "^4.17.10"
+ lodash "^4.17.11"
source-map "^0.5.0"
trim-right "^1.0.1"
@@ -224,10 +224,10 @@
esutils "^2.0.2"
js-tokens "^4.0.0"
-"@babel/parser@^7.0.0", "@babel/parser@^7.2.2", "@babel/parser@^7.2.3":
- version "7.2.3"
- resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.2.3.tgz#32f5df65744b70888d17872ec106b02434ba1489"
- integrity sha512-0LyEcVlfCoFmci8mXx8A5oIkpkOgyo8dRHtxBnK9RRBwxO2+JZPNsqtVEZQ7mJFPxnXF9lfmU24mHOPI0qnlkA==
+"@babel/parser@^7.0.0", "@babel/parser@^7.2.2", "@babel/parser@^7.3.4":
+ version "7.3.4"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.3.4.tgz#a43357e4bbf4b92a437fb9e465c192848287f27c"
+ integrity sha512-tXZCqWtlOOP4wgCp6RjRvLmfuhnqTLy9VHwRochJBCP2nDm27JnnuFEnXFASVyQNHk36jD1tAammsCEEqgscIQ==
"@babel/plugin-proposal-async-generator-functions@^7.2.0":
version "7.2.0"
@@ -315,7 +315,7 @@
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-syntax-object-rest-spread@^7.2.0":
+"@babel/plugin-syntax-object-rest-spread@^7.0.0", "@babel/plugin-syntax-object-rest-spread@^7.2.0":
version "7.2.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz#3b7a3e733510c57e820b9142a6579ac8b0dfad2e"
integrity sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA==
@@ -609,28 +609,28 @@
"@babel/parser" "^7.2.2"
"@babel/types" "^7.2.2"
-"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.1.5", "@babel/traverse@^7.2.2", "@babel/traverse@^7.2.3":
- version "7.2.3"
- resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.2.3.tgz#7ff50cefa9c7c0bd2d81231fdac122f3957748d8"
- integrity sha512-Z31oUD/fJvEWVR0lNZtfgvVt512ForCTNKYcJBGbPb1QZfve4WGH8Wsy7+Mev33/45fhP/hwQtvgusNdcCMgSw==
+"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.1.5", "@babel/traverse@^7.2.3", "@babel/traverse@^7.3.4":
+ version "7.3.4"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.3.4.tgz#1330aab72234f8dea091b08c4f8b9d05c7119e06"
+ integrity sha512-TvTHKp6471OYEcE/91uWmhR6PrrYywQntCHSaZ8CM8Vmp+pjAusal4nGB2WCCQd0rvI7nOMKn9GnbcvTUz3/ZQ==
dependencies:
"@babel/code-frame" "^7.0.0"
- "@babel/generator" "^7.2.2"
+ "@babel/generator" "^7.3.4"
"@babel/helper-function-name" "^7.1.0"
"@babel/helper-split-export-declaration" "^7.0.0"
- "@babel/parser" "^7.2.3"
- "@babel/types" "^7.2.2"
+ "@babel/parser" "^7.3.4"
+ "@babel/types" "^7.3.4"
debug "^4.1.0"
globals "^11.1.0"
- lodash "^4.17.10"
+ lodash "^4.17.11"
-"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2":
- version "7.2.2"
- resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.2.2.tgz#44e10fc24e33af524488b716cdaee5360ea8ed1e"
- integrity sha512-fKCuD6UFUMkR541eDWL+2ih/xFZBXPOg/7EQFeTluMDebfqR4jrpaCjLhkWlQS4hT6nRa2PMEgXKbRB5/H2fpg==
+"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.3.4":
+ version "7.3.4"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.3.4.tgz#bf482eaeaffb367a28abbf9357a94963235d90ed"
+ integrity sha512-WEkp8MsLftM7O/ty580wAmZzN1nDmCACc5+jFzUt+GUFNNIi3LdRlueYz0YIlmJhlZx1QYDMZL5vdWCL0fNjFQ==
dependencies:
esutils "^2.0.2"
- lodash "^4.17.10"
+ lodash "^4.17.11"
to-fast-properties "^2.0.0"
"@gitlab/csslab@^1.8.0":
@@ -653,15 +653,15 @@
eslint-plugin-promise "^4.0.1"
eslint-plugin-vue "^5.0.0"
-"@gitlab/svgs@^1.52.0":
- version "1.52.0"
- resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.52.0.tgz#870b0112a18b10d2cde5470a48b05025193cd207"
- integrity sha512-xqDYIaSY1MJuCa7lRIDTTPoXn6x57So2qchxwmELE+SAJxlYlpYgDKCNWcGawhyTZRDZuG/qFBWp0sMeTQD//A==
+"@gitlab/svgs@^1.54.0":
+ version "1.54.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.54.0.tgz#00320e845efd46716042cde0c348b990d4908daf"
+ integrity sha512-DR17iy8TM5IbXEacqiDP0p8SuC/J8EL+98xbfVz5BKvRsPHpeZJQNlBF/petIV5d+KWM5A9v3GZTY7uMU7z/JQ==
-"@gitlab/ui@^2.0.2":
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-2.0.2.tgz#611571c931181fb783f57f712e1c2388059b301b"
- integrity sha512-rUWVhWmM9EkwIEruYJEjizrQKe7TzNyKArwWY/nfEL4HptDtwbe+xHfR8IJHbpql3oI87cTO3BheMxYF6b2Ebg==
+"@gitlab/ui@^2.0.4":
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-2.0.4.tgz#ba86f6e5868ef7bc7f504cef9ca504c2d2f6bffd"
+ integrity sha512-dJ+KKpeqIAPYZtYZeciXhC/whNiGPVRjp5IgjQRddh3zsreqmfwQq58nSH7HepAAIepaqTe0UFuzBgrSWvVM6w==
dependencies:
babel-standalone "^6.26.0"
bootstrap-vue "^2.0.0-rc.11"
@@ -674,6 +674,19 @@
vue "^2.5.21"
vue-loader "^15.4.2"
+"@mrmlnc/readdir-enhanced@^2.2.1":
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde"
+ integrity sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==
+ dependencies:
+ call-me-maybe "^1.0.1"
+ glob-to-regexp "^0.3.0"
+
+"@nodelib/fs.stat@^1.1.2":
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b"
+ integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==
+
"@sindresorhus/is@^0.7.0":
version "0.7.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd"
@@ -684,11 +697,6 @@
resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.0.tgz#d1d55958d1fccc5527d4aba29fc9c4b942f563ff"
integrity sha512-7WcbyctkE8GTzogDb0ulRAEw7v8oIS54ft9mQTU7PfM0hp5e+8kpa+HeQ7IQrFbKtJXBKcZ4bh+Em9dTw5L6AQ==
-"@types/async@2.0.50":
- version "2.0.50"
- resolved "https://registry.yarnpkg.com/@types/async/-/async-2.0.50.tgz#117540e026d64e1846093abbd5adc7e27fda7bcb"
- integrity sha512-VMhZMMQgV1zsR+lX/0IBfAk+8Eb7dPVMWiQGFAt3qjo5x7Ml6b77jUo0e1C3ToD+XRDXqtrfw+6AB0uUsPEr3Q==
-
"@types/events@*":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86"
@@ -728,16 +736,6 @@
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45"
integrity sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ==
-"@types/strip-bom@^3.0.0":
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/@types/strip-bom/-/strip-bom-3.0.0.tgz#14a8ec3956c2e81edb7520790aecf21c290aebd2"
- integrity sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I=
-
-"@types/strip-json-comments@0.0.30":
- version "0.0.30"
- resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1"
- integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==
-
"@types/tapable@*":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.4.tgz#b4ffc7dc97b498c969b360a41eee247f82616370"
@@ -750,6 +748,28 @@
dependencies:
source-map "^0.6.1"
+"@types/unist@*", "@types/unist@^2.0.0":
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.2.tgz#5dc0a7f76809b7518c0df58689cd16a19bd751c6"
+ integrity sha512-iHI60IbyfQilNubmxsq4zqSjdynlmc2Q/QvH9kjzg9+CCYVVzq1O6tc7VBzSygIwnmOt07w80IG6HDQvjv3Liw==
+
+"@types/vfile-message@*":
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/@types/vfile-message/-/vfile-message-1.0.1.tgz#e1e9895cc6b36c462d4244e64e6d0b6eaf65355a"
+ integrity sha512-mlGER3Aqmq7bqR1tTTIVHq8KSAFFRyGbrxuM8C/H82g6k7r2fS+IMEkIu3D7JHzG10NvPdR8DNx0jr0pwpp4dA==
+ dependencies:
+ "@types/node" "*"
+ "@types/unist" "*"
+
+"@types/vfile@^3.0.0":
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/@types/vfile/-/vfile-3.0.2.tgz#19c18cd232df11ce6fa6ad80259bc86c366b09b9"
+ integrity sha512-b3nLFGaGkJ9rzOcuXRfHkZMdjsawuDD0ENL9fzTophtBg8FJHSGbH7daXkEpcwy3v7Xol3pAvsmlYyFhR4pqJw==
+ dependencies:
+ "@types/node" "*"
+ "@types/unist" "*"
+ "@types/vfile-message" "*"
+
"@types/webpack@^4.4.19":
version "4.4.23"
resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.4.23.tgz#059d6f4598cfd65ddee0e2db38317ef989696712"
@@ -766,20 +786,20 @@
resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d"
integrity sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg==
-"@vue/component-compiler-utils@^2.0.0":
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/@vue/component-compiler-utils/-/component-compiler-utils-2.2.0.tgz#bbbb7ed38a9a8a7c93abe7ef2e54a90a04b631b4"
- integrity sha512-pS4zlcdD7BvedyB+IfiTfrbi6C977UMIfulSk8r6uL0BU46ZE2+fUj/zbSNSfVxeaj9ElmnSni5OMwF9np+b+w==
+"@vue/component-compiler-utils@^2.0.0", "@vue/component-compiler-utils@^2.4.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@vue/component-compiler-utils/-/component-compiler-utils-2.6.0.tgz#aa46d2a6f7647440b0b8932434d22f12371e543b"
+ integrity sha512-IHjxt7LsOFYc0DkTncB7OXJL7UzwOLPPQCfEUNyxL2qt+tF12THV+EO33O1G2Uk4feMSWua3iD39Itszx0f0bw==
dependencies:
consolidate "^0.15.1"
hash-sum "^1.0.2"
lru-cache "^4.1.2"
merge-source-map "^1.1.0"
- postcss "^6.0.20"
- postcss-selector-parser "^3.1.1"
- prettier "1.13.7"
- source-map "^0.5.6"
- vue-template-es2015-compiler "^1.6.0"
+ postcss "^7.0.14"
+ postcss-selector-parser "^5.0.0"
+ prettier "1.16.3"
+ source-map "~0.6.1"
+ vue-template-es2015-compiler "^1.9.0"
"@vue/test-utils@^1.0.0-beta.25":
version "1.0.0-beta.25"
@@ -1012,10 +1032,10 @@ ajv-keywords@^3.1.0:
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a"
integrity sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=
-ajv@^6.1.0, ajv@^6.5.3, ajv@^6.5.5, ajv@^6.6.1:
- version "6.6.1"
- resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.6.1.tgz#6360f5ed0d80f232cc2b294c362d5dc2e538dd61"
- integrity sha512-ZoJjft5B+EJBjUyu9C9Hc0OZyPZSSlOF+plzouTrg6UlA8f+e/n8NIgBFG/9tppJtpPWfthHakK7juJdNDODww==
+ajv@^6.1.0, ajv@^6.5.3, ajv@^6.5.5, ajv@^6.9.1:
+ version "6.9.1"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.9.1.tgz#a4d3683d74abc5670e75f0b16520f70a20ea8dc1"
+ integrity sha512-XDN92U311aINL77ieWHmqCcNlwjoP5cHXDxIxbf2MaPYuCXOHS7gHH8jktxeK5omgd52XbSTX6a4Piwd1pQmzA==
dependencies:
fast-deep-equal "^2.0.1"
fast-json-stable-stringify "^2.0.0"
@@ -1064,6 +1084,11 @@ ansi-regex@^3.0.0:
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
+ansi-regex@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.0.0.tgz#70de791edf021404c3fd615aa89118ae0432e5a9"
+ integrity sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==
+
ansi-styles@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
@@ -1084,50 +1109,54 @@ anymatch@^2.0.0:
micromatch "^3.1.4"
normalize-path "^2.1.1"
-apollo-boost@^0.1.20:
- version "0.1.20"
- resolved "https://registry.yarnpkg.com/apollo-boost/-/apollo-boost-0.1.20.tgz#cc3e418ebd2bea857656685d32a7a20443493363"
- integrity sha512-n2MiEY5IGpD/cy0RH+pM9vbmobM/JZ5qz38XQAUA41FxxMPlLFQxf0IUMm0tijLOJvJJBub3pDt+Of4TVPBCqA==
+apollo-boost@^0.3.1:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/apollo-boost/-/apollo-boost-0.3.1.tgz#b6a896e020a0eab7e415032fe565734a955c65f8"
+ integrity sha512-VdXcTMxLBeNvANW/FtiarEkrRr/cepYKG6wTAURdy8CS33WYpEHtIg9S8tAjxwVzIECpE4lWyDKyPLoESJ072Q==
dependencies:
- apollo-cache "^1.1.20"
- apollo-cache-inmemory "^1.3.9"
- apollo-client "^2.4.5"
+ apollo-cache "^1.2.1"
+ apollo-cache-inmemory "^1.5.1"
+ apollo-client "^2.5.1"
apollo-link "^1.0.6"
apollo-link-error "^1.0.3"
apollo-link-http "^1.3.1"
- apollo-link-state "^0.4.0"
graphql-tag "^2.4.2"
+ ts-invariant "^0.2.1"
+ tslib "^1.9.3"
-apollo-cache-inmemory@^1.3.9:
- version "1.3.9"
- resolved "https://registry.yarnpkg.com/apollo-cache-inmemory/-/apollo-cache-inmemory-1.3.9.tgz#10738ba6a04faaeeb0da21bbcc1f7c0b5902910c"
- integrity sha512-Q2k84p/OqIuMUyeWGc6XbVXXZu0erYOO+wTx9p+CnQUspnNvf7zmvFNgFnmudXzfuG1m1CSzePk6fC/M1ehOqQ==
+apollo-cache-inmemory@^1.5.1:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/apollo-cache-inmemory/-/apollo-cache-inmemory-1.5.1.tgz#265d1ee67b0bf0aca9c37629d410bfae44e62953"
+ integrity sha512-D3bdpPmWfaKQkWy8lfwUg+K8OBITo3sx0BHLs1B/9vIdOIZ7JNCKq3EUcAgAfInomJUdN0QG1yOfi8M8hxkN1g==
dependencies:
- apollo-cache "^1.1.20"
- apollo-utilities "^1.0.25"
- optimism "^0.6.6"
+ apollo-cache "^1.2.1"
+ apollo-utilities "^1.2.1"
+ optimism "^0.6.9"
+ ts-invariant "^0.2.1"
+ tslib "^1.9.3"
-apollo-cache@1.1.20, apollo-cache@^1.1.20:
- version "1.1.20"
- resolved "https://registry.yarnpkg.com/apollo-cache/-/apollo-cache-1.1.20.tgz#6152cc4baf6a63e376efee79f75de4f5c84bf90e"
- integrity sha512-+Du0/4kUSuf5PjPx0+pvgMGV12ezbHA8/hubYuqRQoy/4AWb4faa61CgJNI6cKz2mhDd9m94VTNKTX11NntwkQ==
+apollo-cache@1.2.1, apollo-cache@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/apollo-cache/-/apollo-cache-1.2.1.tgz#aae71eb4a11f1f7322adc343f84b1a39b0693644"
+ integrity sha512-nzFmep/oKlbzUuDyz6fS6aYhRmfpcHWqNkkA9Bbxwk18RD6LXC4eZkuE0gXRX0IibVBHNjYVK+Szi0Yied4SpQ==
dependencies:
- apollo-utilities "^1.0.25"
+ apollo-utilities "^1.2.1"
+ tslib "^1.9.3"
-apollo-client@^2.4.5:
- version "2.4.5"
- resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.4.5.tgz#545beda1ef60814943b5622f0feabc9f29ee9822"
- integrity sha512-nUm06EGa4TP/IY68OzmC3lTD32TqkjLOQdb69uYo+lHl8NnwebtrAw3qFtsQtTEz6ueBp/Z/HasNZng4jwafVQ==
+apollo-client@^2.5.1:
+ version "2.5.1"
+ resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.5.1.tgz#36126ed1d32edd79c3713c6684546a3bea80e6d1"
+ integrity sha512-MNcQKiqLHdGmNJ0rZ0NXaHrToXapJgS/5kPk0FygXt+/FmDCdzqcujI7OPxEC6e9Yw5S/8dIvOXcRNuOMElHkA==
dependencies:
"@types/zen-observable" "^0.8.0"
- apollo-cache "1.1.20"
+ apollo-cache "1.2.1"
apollo-link "^1.0.0"
apollo-link-dedup "^1.0.0"
- apollo-utilities "1.0.25"
+ apollo-utilities "1.2.1"
symbol-observable "^1.0.2"
+ ts-invariant "^0.2.1"
+ tslib "^1.9.3"
zen-observable "^0.8.0"
- optionalDependencies:
- "@types/async" "2.0.50"
apollo-link-dedup@^1.0.0:
version "1.0.10"
@@ -1158,14 +1187,6 @@ apollo-link-http@^1.3.1:
apollo-link "^1.2.3"
apollo-link-http-common "^0.2.5"
-apollo-link-state@^0.4.0:
- version "0.4.2"
- resolved "https://registry.yarnpkg.com/apollo-link-state/-/apollo-link-state-0.4.2.tgz#ac00e9be9b0ca89eae0be6ba31fe904b80bbe2e8"
- integrity sha512-xMPcAfuiPVYXaLwC6oJFIZrKgV3GmdO31Ag2eufRoXpvT0AfJZjdaPB4450Nu9TslHRePN9A3quxNueILlQxlw==
- dependencies:
- apollo-utilities "^1.0.8"
- graphql-anywhere "^4.1.0-alpha.0"
-
apollo-link@^1.0.0, apollo-link@^1.0.6, apollo-link@^1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.3.tgz#9bd8d5fe1d88d31dc91dae9ecc22474d451fb70d"
@@ -1174,19 +1195,14 @@ apollo-link@^1.0.0, apollo-link@^1.0.6, apollo-link@^1.2.3:
apollo-utilities "^1.0.0"
zen-observable-ts "^0.8.10"
-apollo-utilities@1.0.25, apollo-utilities@^1.0.0, apollo-utilities@^1.0.25, apollo-utilities@^1.0.8:
- version "1.0.25"
- resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.0.25.tgz#899b00f5f990fb451675adf84cb3de82eb6372ea"
- integrity sha512-AXvqkhni3Ir1ffm4SA1QzXn8k8I5BBl4PVKEyak734i4jFdp+xgfUyi2VCqF64TJlFTA/B73TRDUvO2D+tKtZg==
+apollo-utilities@1.2.1, apollo-utilities@^1.0.0, apollo-utilities@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.2.1.tgz#1c3a1ebf5607d7c8efe7636daaf58e7463b41b3c"
+ integrity sha512-Zv8Udp9XTSFiN8oyXOjf6PMHepD4yxxReLsl6dPUy5Ths7jti3nmlBzZUOxuTWRwZn0MoclqL7RQ5UEJN8MAxg==
dependencies:
fast-json-stable-stringify "^2.0.0"
-
-append-transform@^0.4.0:
- version "0.4.0"
- resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-0.4.0.tgz#d76ebf8ca94d276e247a36bad44a4b74ab611991"
- integrity sha1-126/jKlNJ24keja61EpLdKthGZE=
- dependencies:
- default-require-extensions "^1.0.0"
+ ts-invariant "^0.2.1"
+ tslib "^1.9.3"
append-transform@^1.0.0:
version "1.0.0"
@@ -1215,19 +1231,12 @@ argparse@^1.0.7:
dependencies:
sprintf-js "~1.0.2"
-arr-diff@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf"
- integrity sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=
- dependencies:
- arr-flatten "^1.0.1"
-
arr-diff@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520"
integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=
-arr-flatten@^1.0.1, arr-flatten@^1.1.0:
+arr-flatten@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1"
integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==
@@ -1242,6 +1251,11 @@ array-equal@^1.0.0:
resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93"
integrity sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=
+array-find-index@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1"
+ integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=
+
array-find@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/array-find/-/array-find-1.0.0.tgz#6c8e286d11ed768327f8e62ecee87353ca3e78b8"
@@ -1262,7 +1276,7 @@ array-slice@^0.2.3:
resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-0.2.3.tgz#dd3cfb80ed7973a75117cdac69b0b99ec86186f5"
integrity sha1-3Tz7gO15c6dRF82sabC5nshhhvU=
-array-union@^1.0.1:
+array-union@^1.0.1, array-union@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39"
integrity sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=
@@ -1347,7 +1361,7 @@ async@1.x, async@^1.5.2:
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=
-async@^2.0.0, async@^2.1.4, async@^2.5.0, async@^2.6.1:
+async@^2.0.0, async@^2.5.0, async@^2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610"
integrity sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==
@@ -1364,6 +1378,18 @@ atob@^2.1.1:
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
+autoprefixer@^9.0.0:
+ version "9.4.7"
+ resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.4.7.tgz#f997994f9a810eae47b38fa6d8a119772051c4ff"
+ integrity sha512-qS5wW6aXHkm53Y4z73tFGsUhmZu4aMPV9iHXYlF0c/wxjknXNHuj/1cIQb+6YH692DbJGGWcckAXX+VxKvahMA==
+ dependencies:
+ browserslist "^4.4.1"
+ caniuse-lite "^1.0.30000932"
+ normalize-range "^0.1.2"
+ num2fraction "^1.2.2"
+ postcss "^7.0.14"
+ postcss-value-parser "^3.3.1"
+
autosize@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/autosize/-/autosize-4.0.0.tgz#7a0599b1ba84d73bd7589b0d9da3870152c69237"
@@ -1403,36 +1429,6 @@ babel-code-frame@^6.26.0:
esutils "^2.0.2"
js-tokens "^3.0.2"
-babel-core@^6.0.0, babel-core@^6.26.0:
- version "6.26.3"
- resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.3.tgz#b2e2f09e342d0f0c88e2f02e067794125e75c207"
- integrity sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==
- dependencies:
- babel-code-frame "^6.26.0"
- babel-generator "^6.26.0"
- babel-helpers "^6.24.1"
- babel-messages "^6.23.0"
- babel-register "^6.26.0"
- babel-runtime "^6.26.0"
- babel-template "^6.26.0"
- babel-traverse "^6.26.0"
- babel-types "^6.26.0"
- babylon "^6.18.0"
- convert-source-map "^1.5.1"
- debug "^2.6.9"
- json5 "^0.5.1"
- lodash "^4.17.4"
- minimatch "^3.0.4"
- path-is-absolute "^1.0.1"
- private "^0.1.8"
- slash "^1.0.0"
- source-map "^0.5.7"
-
-babel-core@^7.0.0-bridge:
- version "7.0.0-bridge.0"
- resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece"
- integrity sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==
-
babel-eslint@^10.0.1:
version "10.0.1"
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.1.tgz#919681dc099614cd7d31d45c8908695092a1faed"
@@ -1445,35 +1441,15 @@ babel-eslint@^10.0.1:
eslint-scope "3.7.1"
eslint-visitor-keys "^1.0.0"
-babel-generator@^6.18.0, babel-generator@^6.26.0:
- version "6.26.1"
- resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90"
- integrity sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==
- dependencies:
- babel-messages "^6.23.0"
- babel-runtime "^6.26.0"
- babel-types "^6.26.0"
- detect-indent "^4.0.0"
- jsesc "^1.3.0"
- lodash "^4.17.4"
- source-map "^0.5.7"
- trim-right "^1.0.1"
-
-babel-helpers@^6.24.1:
- version "6.24.1"
- resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2"
- integrity sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=
+babel-jest@^24.1.0:
+ version "24.1.0"
+ resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-24.1.0.tgz#441e23ef75ded3bd547e300ac3194cef87b55190"
+ integrity sha512-MLcagnVrO9ybQGLEfZUqnOzv36iQzU7Bj4elm39vCukumLVSfoX+tRy3/jW7lUKc7XdpRmB/jech6L/UCsSZjw==
dependencies:
- babel-runtime "^6.22.0"
- babel-template "^6.24.1"
-
-babel-jest@^23.6.0:
- version "23.6.0"
- resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-23.6.0.tgz#a644232366557a2240a0c083da6b25786185a2f1"
- integrity sha512-lqKGG6LYXYu+DQh/slrQ8nxXQkEkhugdXsU6St7GmhVS7Ilc/22ArwqXNJrf0QaOBjZB0360qZMwXqDYQHXaew==
- dependencies:
- babel-plugin-istanbul "^4.1.6"
- babel-preset-jest "^23.2.0"
+ babel-plugin-istanbul "^5.1.0"
+ babel-preset-jest "^24.1.0"
+ chalk "^2.4.2"
+ slash "^2.0.0"
babel-loader@^8.0.5:
version "8.0.5"
@@ -1485,30 +1461,6 @@ babel-loader@^8.0.5:
mkdirp "^0.5.1"
util.promisify "^1.0.0"
-babel-messages@^6.23.0:
- version "6.23.0"
- resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e"
- integrity sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=
- dependencies:
- babel-runtime "^6.22.0"
-
-babel-plugin-dynamic-import-node@^2.2.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.2.0.tgz#c0adfb07d95f4a4495e9aaac6ec386c4d7c2524e"
- integrity sha512-fP899ELUnTaBcIzmrW7nniyqqdYWrWuJUyPWHxFa/c7r7hS6KC8FscNfLlBNIoPSc55kYMGEEKjPjJGCLbE1qA==
- dependencies:
- object.assign "^4.1.0"
-
-babel-plugin-istanbul@^4.1.6:
- version "4.1.6"
- resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz#36c59b2192efce81c5b378321b74175add1c9a45"
- integrity sha512-PWP9FQ1AhZhS01T/4qLSKoHGY/xvkZdVBGlKM/HuxxS3+sC66HhTNR7+MpbO/so/cz/wY94MeSWJuP1hXIPfwQ==
- dependencies:
- babel-plugin-syntax-object-rest-spread "^6.13.0"
- find-up "^2.1.0"
- istanbul-lib-instrument "^1.10.1"
- test-exclude "^4.2.1"
-
babel-plugin-istanbul@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-5.1.0.tgz#6892f529eff65a3e2d33d87dc5888ffa2ecd4a30"
@@ -1518,39 +1470,16 @@ babel-plugin-istanbul@^5.1.0:
istanbul-lib-instrument "^3.0.0"
test-exclude "^5.0.0"
-babel-plugin-jest-hoist@^23.2.0:
- version "23.2.0"
- resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-23.2.0.tgz#e61fae05a1ca8801aadee57a6d66b8cefaf44167"
- integrity sha1-5h+uBaHKiAGq3uV6bWa4zvr0QWc=
+babel-plugin-jest-hoist@^24.1.0:
+ version "24.1.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.1.0.tgz#dfecc491fb15e2668abbd690a697a8fd1411a7f8"
+ integrity sha512-gljYrZz8w1b6fJzKcsfKsipSru2DU2DmQ39aB6nV3xQ0DDv3zpIzKGortA5gknrhNnPN8DweaEgrnZdmbGmhnw==
babel-plugin-rewire@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/babel-plugin-rewire/-/babel-plugin-rewire-1.2.0.tgz#822562d72ed2c84e47c0f95ee232c920853e9d89"
integrity sha512-JBZxczHw3tScS+djy6JPLMjblchGhLI89ep15H3SyjujIzlxo5nr6Yjo7AXotdeVczeBmWs0tF8PgJWDdgzAkQ==
-babel-plugin-syntax-object-rest-spread@^6.13.0:
- version "6.13.0"
- resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5"
- integrity sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=
-
-babel-plugin-transform-es2015-modules-commonjs@^6.26.0, babel-plugin-transform-es2015-modules-commonjs@^6.26.2:
- version "6.26.2"
- resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz#58a793863a9e7ca870bdc5a881117ffac27db6f3"
- integrity sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q==
- dependencies:
- babel-plugin-transform-strict-mode "^6.24.1"
- babel-runtime "^6.26.0"
- babel-template "^6.26.0"
- babel-types "^6.26.0"
-
-babel-plugin-transform-strict-mode@^6.24.1:
- version "6.24.1"
- resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758"
- integrity sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=
- dependencies:
- babel-runtime "^6.22.0"
- babel-types "^6.24.1"
-
babel-polyfill@6.23.0:
version "6.23.0"
resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.23.0.tgz#8364ca62df8eafb830499f699177466c3b03499d"
@@ -1560,28 +1489,15 @@ babel-polyfill@6.23.0:
core-js "^2.4.0"
regenerator-runtime "^0.10.0"
-babel-preset-jest@^23.2.0:
- version "23.2.0"
- resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-23.2.0.tgz#8ec7a03a138f001a1a8fb1e8113652bf1a55da46"
- integrity sha1-jsegOhOPABoaj7HoETZSvxpV2kY=
- dependencies:
- babel-plugin-jest-hoist "^23.2.0"
- babel-plugin-syntax-object-rest-spread "^6.13.0"
-
-babel-register@^6.26.0:
- version "6.26.0"
- resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071"
- integrity sha1-btAhFz4vy0htestFxgCahW9kcHE=
+babel-preset-jest@^24.1.0:
+ version "24.1.0"
+ resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-24.1.0.tgz#83bc564fdcd4903641af65ec63f2f5de6b04132e"
+ integrity sha512-FfNLDxFWsNX9lUmtwY7NheGlANnagvxq8LZdl5PKnVG3umP+S/g0XbVBfwtA4Ai3Ri/IMkWabBz3Tyk9wdspcw==
dependencies:
- babel-core "^6.26.0"
- babel-runtime "^6.26.0"
- core-js "^2.5.0"
- home-or-tmp "^2.0.0"
- lodash "^4.17.4"
- mkdirp "^0.5.1"
- source-map-support "^0.4.15"
+ "@babel/plugin-syntax-object-rest-spread" "^7.0.0"
+ babel-plugin-jest-hoist "^24.1.0"
-babel-runtime@^6.22.0, babel-runtime@^6.26.0:
+babel-runtime@^6.22.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4=
@@ -1594,57 +1510,21 @@ babel-standalone@^6.26.0:
resolved "https://registry.yarnpkg.com/babel-standalone/-/babel-standalone-6.26.0.tgz#15fb3d35f2c456695815ebf1ed96fe7f015b6886"
integrity sha1-Ffs9NfLEVmlYFevx7Zb+fwFbaIY=
-babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.26.0:
- version "6.26.0"
- resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02"
- integrity sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=
- dependencies:
- babel-runtime "^6.26.0"
- babel-traverse "^6.26.0"
- babel-types "^6.26.0"
- babylon "^6.18.0"
- lodash "^4.17.4"
-
-babel-traverse@^6.0.0, babel-traverse@^6.18.0, babel-traverse@^6.26.0:
- version "6.26.0"
- resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee"
- integrity sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=
- dependencies:
- babel-code-frame "^6.26.0"
- babel-messages "^6.23.0"
- babel-runtime "^6.26.0"
- babel-types "^6.26.0"
- babylon "^6.18.0"
- debug "^2.6.8"
- globals "^9.18.0"
- invariant "^2.2.2"
- lodash "^4.17.4"
-
-babel-types@^6.0.0, babel-types@^6.18.0, babel-types@^6.24.1, babel-types@^6.26.0:
- version "6.26.0"
- resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497"
- integrity sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=
- dependencies:
- babel-runtime "^6.26.0"
- esutils "^2.0.2"
- lodash "^4.17.4"
- to-fast-properties "^1.0.3"
-
babylon@7.0.0-beta.19:
version "7.0.0-beta.19"
resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.19.tgz#e928c7e807e970e0536b078ab3e0c48f9e052503"
integrity sha512-Vg0C9s/REX6/WIXN37UKpv5ZhRi6A4pjHlpkE34+8/a6c2W1Q692n3hmc+SZG5lKRnaExLUbxtJ1SVT+KaCQ/A==
-babylon@^6.18.0:
- version "6.18.0"
- resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3"
- integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==
-
backo2@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947"
integrity sha1-MasayLEpNjRj41s+u2n038+6eUc=
+bail@^1.0.0:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.3.tgz#63cfb9ddbac829b02a3128cd53224be78e6c21a3"
+ integrity sha512-1X8CnjFVQ+a+KW36uBNMTU5s8+v5FzeqrP7hTG5aTb4aPreSbZJlhwPon9VKMuEVgV++JM+SQrALY3kr7eswdg==
+
balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
@@ -1810,15 +1690,6 @@ braces@^0.1.2:
dependencies:
expand-range "^0.1.0"
-braces@^1.8.2:
- version "1.8.5"
- resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7"
- integrity sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=
- dependencies:
- expand-range "^1.8.1"
- preserve "^0.2.0"
- repeat-element "^1.1.2"
-
braces@^2.3.0, braces@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.1.tgz#7086c913b4e5a08dbe37ac0ee6a2500c4ba691bb"
@@ -1912,15 +1783,22 @@ browserify-zlib@^0.2.0:
dependencies:
pako "~1.0.5"
-browserslist@^4.3.4:
- version "4.3.7"
- resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.3.7.tgz#f1de479a6466ea47a0a26dcc725e7504817e624a"
- integrity sha512-pWQv51Ynb0MNk9JGMCZ8VkM785/4MQNXiFYtPqI7EEP0TJO+/d/NqRVn1uiAN0DNbnlUSpL2sh16Kspasv3pUQ==
+browserslist@^4.3.4, browserslist@^4.4.1:
+ version "4.4.1"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.4.1.tgz#42e828954b6b29a7a53e352277be429478a69062"
+ integrity sha512-pEBxEXg7JwaakBXjATYw/D1YZh4QUSCX/Mnd/wnqSRPPSi1U39iDhDoKGoBUcraKdxDlrYqJxSI5nNvD+dWP2A==
dependencies:
- caniuse-lite "^1.0.30000925"
- electron-to-chromium "^1.3.96"
+ caniuse-lite "^1.0.30000929"
+ electron-to-chromium "^1.3.103"
node-releases "^1.1.3"
+bs-logger@0.x:
+ version "0.2.6"
+ resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8"
+ integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==
+ dependencies:
+ fast-json-stable-stringify "2.x"
+
bser@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/bser/-/bser-2.0.0.tgz#9ac78d3ed5d915804fd87acb158bc797147a1719"
@@ -1928,10 +1806,10 @@ bser@^2.0.0:
dependencies:
node-int64 "^0.4.0"
-buffer-from@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.0.0.tgz#4cb8832d23612589b0406e9e2956c17f06fdf531"
- integrity sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA==
+buffer-from@1.x, buffer-from@^1.0.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
+ integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
buffer-indexof@^1.0.0:
version "1.1.0"
@@ -1952,11 +1830,6 @@ buffer@^4.3.0:
ieee754 "^1.1.4"
isarray "^1.0.0"
-builtin-modules@^1.0.0:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
- integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=
-
builtin-status-codes@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
@@ -2026,6 +1899,18 @@ cacheable-request@^2.1.1:
normalize-url "2.0.1"
responselike "1.0.2"
+call-me-maybe@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b"
+ integrity sha1-JtII6onje1y95gJQoV8DHBak1ms=
+
+caller-callsite@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134"
+ integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=
+ dependencies:
+ callsites "^2.0.0"
+
caller-path@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f"
@@ -2033,6 +1918,13 @@ caller-path@^0.1.0:
dependencies:
callsites "^0.2.0"
+caller-path@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4"
+ integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=
+ dependencies:
+ caller-callsite "^2.0.0"
+
callsite@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20"
@@ -2048,6 +1940,20 @@ callsites@^2.0.0:
resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50"
integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=
+callsites@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.0.0.tgz#fb7eb569b72ad7a45812f93fd9430a3e410b3dd3"
+ integrity sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw==
+
+camelcase-keys@^4.0.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-4.2.0.tgz#a2aa5fb1af688758259c32c141426d78923b9b77"
+ integrity sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=
+ dependencies:
+ camelcase "^4.1.0"
+ map-obj "^2.0.0"
+ quick-lru "^1.0.0"
+
camelcase@^4.0.0, camelcase@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
@@ -2058,10 +1964,10 @@ camelcase@^5.0.0:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.0.0.tgz#03295527d58bd3cd4aa75363f35b2e8d97be2f42"
integrity sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==
-caniuse-lite@^1.0.30000925:
- version "1.0.30000927"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000927.tgz#114a9de4ff1e01f5790fe578ecd93421c7524665"
- integrity sha512-ogq4NbUWf1uG/j66k0AmiO3GjqJAlQyF8n4w8a954cbCyFKmYGvRtgz6qkq2fWuduTXHibX7GyYL5Pg58Aks2g==
+caniuse-lite@^1.0.30000929, caniuse-lite@^1.0.30000932:
+ version "1.0.30000936"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000936.tgz#5d33b118763988bf721b9b8ad436d0400e4a116b"
+ integrity sha512-orX4IdpbFhdNO7bTBhSbahp1EBpqzBc+qrvTRVUFfZgA4zta7TdM6PN5ZxkEUgDnz36m+PfWGcdX7AVfFWItJw==
capture-exit@^1.2.0:
version "1.2.0"
@@ -2087,6 +1993,11 @@ catharsis@~0.8.9:
dependencies:
underscore-contrib "~0.3.0"
+ccount@^1.0.0:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.0.3.tgz#f1cec43f332e2ea5a569fd46f9f5bde4e6102aff"
+ integrity sha512-Jt9tIBkRc9POUof7QA/VwWd+58fKkEEfI+/t1/eOlxKM7ZhrczNzMFefge7Ai+39y1pR/pP6cI19guHy3FSLmw==
+
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"
@@ -2098,15 +2009,35 @@ chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.3:
strip-ansi "^3.0.0"
supports-color "^2.0.0"
-chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1:
- version "2.4.1"
- resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e"
- integrity sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==
+chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2:
+ version "2.4.2"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
+ integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
dependencies:
ansi-styles "^3.2.1"
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
+character-entities-html4@^1.0.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-1.1.2.tgz#c44fdde3ce66b52e8d321d6c1bf46101f0150610"
+ integrity sha512-sIrXwyna2+5b0eB9W149izTPJk/KkJTg6mEzDGibwBUkyH1SbDa+nf515Ppdi3MaH35lW0JFJDWeq9Luzes1Iw==
+
+character-entities-legacy@^1.0.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.2.tgz#7c6defb81648498222c9855309953d05f4d63a9c"
+ integrity sha512-9NB2VbXtXYWdXzqrvAHykE/f0QJxzaKIpZ5QzNZrrgQ7Iyxr2vnfS8fCBNVW9nUEZE0lo57nxKRqnzY/dKrwlA==
+
+character-entities@^1.0.0:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.2.tgz#58c8f371c0774ef0ba9b2aca5f00d8f100e6e363"
+ integrity sha512-sMoHX6/nBiy3KKfC78dnEalnpn0Az0oSNvqUWYTtYrhRI5iUIYsROU48G+E+kMFQzqXaJ8kHJZ85n7y6/PHgwQ==
+
+character-reference-invalid@^1.0.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.2.tgz#21e421ad3d84055952dab4a43a04e73cd425d3ed"
+ integrity sha512-7I/xceXfKyUJmSAn/jw8ve/9DyOP7XxufNYLI9Px7CmsKgEUaZLUTax6nZxGQtaoiZCjpu6cHPj20xC/vqRReQ==
+
chardet@^0.4.0:
version "0.4.2"
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2"
@@ -2187,6 +2118,11 @@ ci-info@^1.5.0:
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497"
integrity sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==
+ci-info@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46"
+ integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==
+
cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de"
@@ -2265,6 +2201,14 @@ cliui@^4.0.0:
strip-ansi "^4.0.0"
wrap-ansi "^2.0.0"
+clone-regexp@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/clone-regexp/-/clone-regexp-1.0.1.tgz#051805cd33173375d82118fc0918606da39fd60f"
+ integrity sha512-Fcij9IwRW27XedRIJnSOEupS7RVcXtObJXbcUOX93UCLqqOdRpkvzKywOOSizmEK/Is3S/RHX9dLdfo6R1Q1mw==
+ dependencies:
+ is-regexp "^1.0.0"
+ is-supported-regexp-flag "^1.0.0"
+
clone-response@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b"
@@ -2272,11 +2216,6 @@ clone-response@1.0.2:
dependencies:
mimic-response "^1.0.0"
-clone@2.x:
- version "2.1.2"
- resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
- integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=
-
co@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
@@ -2306,6 +2245,11 @@ codesandbox-import-utils@^1.2.3:
istextorbinary "^2.2.1"
lz-string "^1.4.4"
+collapse-white-space@^1.0.2:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.4.tgz#ce05cf49e54c3277ae573036a26851ba430a0091"
+ integrity sha512-YfQ1tAUZm561vpYD+5eyWN8+UsceQbSrqqlc/6zDY2gtAE+uZLSdkkovhnGpmCThsvKBFakq4EdY/FF93E8XIw==
+
collection-visit@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0"
@@ -2506,7 +2450,7 @@ content-type@~1.0.4:
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
-convert-source-map@^1.1.0, convert-source-map@^1.4.0, convert-source-map@^1.5.1:
+convert-source-map@^1.1.0, convert-source-map@^1.4.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20"
integrity sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==
@@ -2547,7 +2491,7 @@ copy-to-clipboard@^3.0.8:
dependencies:
toggle-selection "^1.0.3"
-core-js@^2.2.0, core-js@^2.4.0, core-js@^2.4.1, core-js@^2.5.0:
+core-js@^2.2.0, core-js@^2.4.0, core-js@^2.4.1:
version "2.5.7"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e"
integrity sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==
@@ -2562,6 +2506,16 @@ core-util-is@1.0.2, core-util-is@~1.0.0:
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
+cosmiconfig@^5.0.0:
+ version "5.0.7"
+ resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.0.7.tgz#39826b292ee0d78eda137dfa3173bd1c21a43b04"
+ integrity sha512-PcLqxTKiDmNT6pSpy4N6KtuPwb53W+2tzNvwOZw0WH9N6O0vLIBq0x8aj8Oj75ere4YcGi48bDFCL+3fRJdlNA==
+ dependencies:
+ import-fresh "^2.0.0"
+ is-directory "^0.3.1"
+ js-yaml "^3.9.0"
+ parse-json "^4.0.0"
+
create-ecdh@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.0.tgz#888c723596cdf7612f6498233eebd7a35301737d"
@@ -2700,6 +2654,11 @@ cssesc@^0.1.0:
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4"
integrity sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=
+cssesc@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-2.0.0.tgz#3b13bd1bb1cb36e1bcb5a4dcd27f54c5dcb35703"
+ integrity sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==
+
cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0":
version "0.3.4"
resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.4.tgz#8cd52e8a3acfd68d3aed38ee0a640177d2f9d797"
@@ -2712,6 +2671,13 @@ cssstyle@^1.0.0:
dependencies:
cssom "0.3.x"
+currently-unhandled@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea"
+ integrity sha1-mI3zP+qxke95mmE2nddsF635V+o=
+ dependencies:
+ array-find-index "^1.0.1"
+
custom-event@~1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425"
@@ -3029,10 +2995,10 @@ debug@^3.1.0, debug@^3.2.5:
dependencies:
ms "^2.1.1"
-debug@^4.0.1, debug@^4.1.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.0.tgz#373687bffa678b38b1cd91f861b63850035ddc87"
- integrity sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==
+debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
+ integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
dependencies:
ms "^2.1.1"
@@ -3043,7 +3009,15 @@ debug@~3.1.0:
dependencies:
ms "2.0.0"
-decamelize@^1.1.1, decamelize@^1.2.0:
+decamelize-keys@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9"
+ integrity sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=
+ dependencies:
+ decamelize "^1.1.0"
+ map-obj "^1.0.0"
+
+decamelize@^1.1.0, decamelize@^1.1.1, decamelize@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
@@ -3095,13 +3069,6 @@ default-gateway@^2.6.0:
execa "^0.10.0"
ip-regex "^2.1.0"
-default-require-extensions@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-1.0.0.tgz#f37ea15d3e13ffd9b437d33e1a75b5fb97874cb8"
- integrity sha1-836hXT4T/9m0N9M+GnW1+5eHTLg=
- dependencies:
- strip-bom "^2.0.0"
-
default-require-extensions@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-2.0.0.tgz#f5f8fbb18a7d6d50b21f641f649ebb522cfe24f7"
@@ -3206,13 +3173,6 @@ detect-file@^1.0.0:
resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7"
integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=
-detect-indent@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208"
- integrity sha1-920GQ1LN9Docts5hnE7jqUdd4gg=
- dependencies:
- repeating "^2.0.0"
-
detect-libc@^1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
@@ -3233,6 +3193,11 @@ di@^0.0.1:
resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c"
integrity sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=
+diff-sequences@^24.0.0:
+ version "24.0.0"
+ resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.0.0.tgz#cdf8e27ed20d8b8d3caccb4e0c0d8fe31a173013"
+ integrity sha512-46OkIuVGBBnrC0soO/4LHu5LHGHx0uhP65OVz8XOrAJpqiCB2aVIuESvjI1F9oqebuvY8lekS1pt6TN7vt7qsw==
+
diff@^3.2.0, diff@^3.4.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
@@ -3247,6 +3212,13 @@ diffie-hellman@^5.0.0:
miller-rabin "^4.0.0"
randombytes "^2.0.0"
+dir-glob@^2.2.1:
+ version "2.2.2"
+ resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.2.2.tgz#fa09f0694153c8918b18ba0deafae94769fc50c4"
+ integrity sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==
+ dependencies:
+ path-type "^3.0.0"
+
dns-equal@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d"
@@ -3423,10 +3395,10 @@ ejs@^2.6.1:
resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0"
integrity sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==
-electron-to-chromium@^1.3.96:
- version "1.3.100"
- resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.100.tgz#899fb088def210aee6b838a47655bbb299190e13"
- integrity sha512-cEUzis2g/RatrVf8x26L8lK5VEls1AGnLHk6msluBUg/NTB4wcXzExTsGscFq+Vs4WBBU2zbLLySvD4C0C3hwg==
+electron-to-chromium@^1.3.103:
+ version "1.3.113"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.113.tgz#b1ccf619df7295aea17bc6951dc689632629e4a9"
+ integrity sha512-De+lPAxEcpxvqPTyZAXELNpRZXABRxf+uL/rSykstQhzj/B0l1150G/ExIIxKc16lI89Hgz81J0BHAcbTqK49g==
elliptic@^6.0.0:
version "6.4.0"
@@ -3441,7 +3413,7 @@ elliptic@^6.0.0:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.0"
-emoji-regex@^7.0.3:
+emoji-regex@^7.0.1, emoji-regex@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==
@@ -3716,10 +3688,10 @@ eslint-plugin-jasmine@^2.10.1:
resolved "https://registry.yarnpkg.com/eslint-plugin-jasmine/-/eslint-plugin-jasmine-2.10.1.tgz#5733b709e751f4bc40e31e1c16989bd2cdfbec97"
integrity sha1-VzO3CedR9LxA4x4cFpib0s377Jc=
-eslint-plugin-jest@^22.1.0:
- version "22.1.0"
- resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.1.0.tgz#9a4dfa3367563e8301560a7fb92ec309096dbca3"
- integrity sha512-WcQd5LxEoAS20zuWEAd8CX0pVC+gGInZPcsoYvK5w7BrEJNmltyTxYYh1r0ct4GsahD2GvNySlcTcLtK2amFZA==
+eslint-plugin-jest@^22.3.0:
+ version "22.3.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.3.0.tgz#a10f10dedfc92def774ec9bb5bfbd2fb8e1c96d2"
+ integrity sha512-P1mYVRNlOEoO5T9yTqOfucjOYf1ktmJ26NjwjH8sxpCFQa6IhBGr5TpKl3hcAAT29hOsRJVuMWmTsHoUVo9FoA==
eslint-plugin-promise@^4.0.1:
version "4.0.1"
@@ -3929,6 +3901,26 @@ execa@^0.7.0:
signal-exit "^3.0.0"
strip-eof "^1.0.0"
+execa@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8"
+ integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==
+ dependencies:
+ cross-spawn "^6.0.0"
+ get-stream "^4.0.0"
+ is-stream "^1.1.0"
+ npm-run-path "^2.0.0"
+ p-finally "^1.0.0"
+ signal-exit "^3.0.0"
+ strip-eof "^1.0.0"
+
+execall@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/execall/-/execall-1.0.0.tgz#73d0904e395b3cab0658b08d09ec25307f29bb73"
+ integrity sha1-c9CQTjlbPKsGWLCNCewlMH8pu3M=
+ dependencies:
+ clone-regexp "^1.0.0"
+
exit@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
@@ -3943,13 +3935,6 @@ expand-braces@^0.1.1:
array-unique "^0.2.1"
braces "^0.1.2"
-expand-brackets@^0.1.4:
- version "0.1.5"
- resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b"
- integrity sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=
- dependencies:
- is-posix-bracket "^0.1.0"
-
expand-brackets@^2.1.4:
version "2.1.4"
resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622"
@@ -3971,13 +3956,6 @@ expand-range@^0.1.0:
is-number "^0.1.1"
repeat-string "^0.2.2"
-expand-range@^1.8.1:
- version "1.8.2"
- resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337"
- integrity sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=
- dependencies:
- fill-range "^2.1.0"
-
expand-tilde@^2.0.0, expand-tilde@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502"
@@ -3985,17 +3963,16 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2:
dependencies:
homedir-polyfill "^1.0.1"
-expect@^23.6.0:
- version "23.6.0"
- resolved "https://registry.yarnpkg.com/expect/-/expect-23.6.0.tgz#1e0c8d3ba9a581c87bd71fb9bc8862d443425f98"
- integrity sha512-dgSoOHgmtn/aDGRVFWclQyPDKl2CQRq0hmIEoUAuQs/2rn2NcvCWcSCovm6BLeuB/7EZuLGu2QfnR+qRt5OM4w==
+expect@^24.1.0:
+ version "24.1.0"
+ resolved "https://registry.yarnpkg.com/expect/-/expect-24.1.0.tgz#88e73301c4c785cde5f16da130ab407bdaf8c0f2"
+ integrity sha512-lVcAPhaYkQcIyMS+F8RVwzbm1jro20IG8OkvxQ6f1JfqhVZyyudCwYogQ7wnktlf14iF3ii7ArIUO/mqvrW9Gw==
dependencies:
ansi-styles "^3.2.0"
- jest-diff "^23.6.0"
- jest-get-type "^22.1.0"
- jest-matcher-utils "^23.6.0"
- jest-message-util "^23.4.0"
- jest-regex-util "^23.3.0"
+ jest-get-type "^24.0.0"
+ jest-matcher-utils "^24.0.0"
+ jest-message-util "^24.0.0"
+ jest-regex-util "^24.0.0"
exports-loader@^0.7.0:
version "0.7.0"
@@ -4079,13 +4056,6 @@ external-editor@^3.0.0:
iconv-lite "^0.4.22"
tmp "^0.0.33"
-extglob@^0.3.1:
- version "0.3.2"
- resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1"
- integrity sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=
- dependencies:
- is-extglob "^1.0.0"
-
extglob@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543"
@@ -4117,7 +4087,19 @@ fast-deep-equal@^2.0.1:
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=
-fast-json-stable-stringify@^2.0.0:
+fast-glob@^2.2.6:
+ version "2.2.6"
+ resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.6.tgz#a5d5b697ec8deda468d85a74035290a025a95295"
+ integrity sha512-0BvMaZc1k9F+MeWWMe8pL6YltFzZYcJsYU7D4JyDA6PAczaXvxqQQ/z+mDF7/4Mw01DeUc+i3CTKajnkANkV4w==
+ dependencies:
+ "@mrmlnc/readdir-enhanced" "^2.2.1"
+ "@nodelib/fs.stat" "^1.1.2"
+ glob-parent "^3.1.0"
+ is-glob "^4.0.0"
+ merge2 "^1.2.3"
+ micromatch "^3.1.10"
+
+fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I=
@@ -4180,6 +4162,13 @@ file-entry-cache@^2.0.0:
flat-cache "^1.2.1"
object-assign "^4.0.1"
+file-entry-cache@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-4.0.0.tgz#633567d15364aefe0b299e1e217735e8f3a9f6e8"
+ integrity sha512-AVSwsnbV8vH/UVbvgEhf3saVQXORNv0ZzSkvkhQIaia5Tia+JhGTaa/ePUSVoPHQyGayQNmYfkzFi3WZV5zcpA==
+ dependencies:
+ flat-cache "^2.0.1"
+
file-loader@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-3.0.1.tgz#f8e0ba0b599918b51adfe45d66d1e771ad560faa"
@@ -4188,12 +4177,7 @@ file-loader@^3.0.1:
loader-utils "^1.0.2"
schema-utils "^1.0.0"
-filename-regex@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26"
- integrity sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=
-
-fileset@^2.0.2, fileset@^2.0.3:
+fileset@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/fileset/-/fileset-2.0.3.tgz#8e7548a96d3cc2327ee5e674168723a333bba2a0"
integrity sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=
@@ -4206,17 +4190,6 @@ filesize@^3.6.1:
resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.6.1.tgz#090bb3ee01b6f801a8a8be99d31710b3422bb317"
integrity sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==
-fill-range@^2.1.0:
- version "2.2.4"
- resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.4.tgz#eb1e773abb056dcd8df2bfdf6af59b8b3a936565"
- integrity sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==
- dependencies:
- is-number "^2.1.0"
- isobject "^2.0.0"
- randomatic "^3.0.0"
- repeat-element "^1.1.2"
- repeat-string "^1.5.2"
-
fill-range@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7"
@@ -4253,14 +4226,6 @@ finalhandler@1.1.1:
statuses "~1.4.0"
unpipe "~1.0.0"
-find-babel-config@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/find-babel-config/-/find-babel-config-1.1.0.tgz#acc01043a6749fec34429be6b64f542ebb5d6355"
- integrity sha1-rMAQQ6Z0n+w0Qpvmtk9ULrtdY1U=
- dependencies:
- json5 "^0.5.1"
- path-exists "^3.0.0"
-
find-cache-dir@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.0.0.tgz#4c1faed59f45184530fb9d7fa123a4d04a98472d"
@@ -4317,6 +4282,20 @@ flat-cache@^1.2.1:
graceful-fs "^4.1.2"
write "^0.2.1"
+flat-cache@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0"
+ integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==
+ dependencies:
+ flatted "^2.0.0"
+ rimraf "2.6.3"
+ write "1.0.3"
+
+flatted@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.0.tgz#55122b6536ea496b4b44893ee2608141d10d9916"
+ integrity sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg==
+
flush-write-stream@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.2.tgz#c81b90d8746766f1a609a46809946c45dd8ae417"
@@ -4332,18 +4311,11 @@ follow-redirects@^1.2.5:
dependencies:
debug "^3.1.0"
-for-in@^1.0.1, for-in@^1.0.2:
+for-in@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=
-for-own@^0.1.4:
- version "0.1.5"
- resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce"
- integrity sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=
- dependencies:
- for-in "^1.0.1"
-
forever-agent@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
@@ -4474,6 +4446,13 @@ get-stream@3.0.0, get-stream@^3.0.0:
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=
+get-stream@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5"
+ integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==
+ dependencies:
+ pump "^3.0.0"
+
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"
@@ -4508,21 +4487,6 @@ gettext-extractor@^3.3.2:
pofile "^1"
typescript "^2"
-glob-base@^0.3.0:
- version "0.3.0"
- resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4"
- integrity sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=
- dependencies:
- glob-parent "^2.0.0"
- is-glob "^2.0.0"
-
-glob-parent@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28"
- integrity sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=
- dependencies:
- is-glob "^2.0.0"
-
glob-parent@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae"
@@ -4531,7 +4495,12 @@ glob-parent@^3.1.0:
is-glob "^3.1.0"
path-dirname "^1.0.0"
-"glob@5 - 7", glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3:
+glob-to-regexp@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
+ integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=
+
+"glob@5 - 7", glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3:
version "7.1.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1"
integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==
@@ -4575,6 +4544,13 @@ global-modules@^1.0.0:
is-windows "^1.0.1"
resolve-dir "^1.0.0"
+global-modules@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780"
+ integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==
+ dependencies:
+ global-prefix "^3.0.0"
+
global-prefix@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe"
@@ -4586,16 +4562,20 @@ global-prefix@^1.0.1:
is-windows "^1.0.1"
which "^1.2.14"
+global-prefix@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97"
+ integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==
+ dependencies:
+ ini "^1.3.5"
+ kind-of "^6.0.2"
+ which "^1.3.1"
+
globals@^11.1.0, globals@^11.7.0:
version "11.7.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-11.7.0.tgz#a583faa43055b1aca771914bf68258e2fc125673"
integrity sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==
-globals@^9.18.0:
- version "9.18.0"
- resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
- integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==
-
globby@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d"
@@ -4619,6 +4599,31 @@ globby@^6.1.0:
pify "^2.0.0"
pinkie-promise "^2.0.0"
+globby@^9.0.0:
+ version "9.0.0"
+ resolved "https://registry.yarnpkg.com/globby/-/globby-9.0.0.tgz#3800df736dc711266df39b4ce33fe0d481f94c23"
+ integrity sha512-q0qiO/p1w/yJ0hk8V9x1UXlgsXUxlGd0AHUOXZVXBO6aznDtpx7M8D1kBrCAItoPm+4l8r6ATXV1JpjY2SBQOw==
+ dependencies:
+ array-union "^1.0.2"
+ dir-glob "^2.2.1"
+ fast-glob "^2.2.6"
+ glob "^7.1.3"
+ ignore "^4.0.3"
+ pify "^4.0.1"
+ slash "^2.0.0"
+
+globjoin@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/globjoin/-/globjoin-0.1.4.tgz#2f4494ac8919e3767c5cbb691e9f463324285d43"
+ integrity sha1-L0SUrIkZ43Z8XLtpHp9GMyQoXUM=
+
+gonzales-pe@^4.2.3:
+ version "4.2.3"
+ resolved "https://registry.yarnpkg.com/gonzales-pe/-/gonzales-pe-4.2.3.tgz#41091703625433285e0aee3aa47829fc1fbeb6f2"
+ integrity sha512-Kjhohco0esHQnOiqqdJeNz/5fyPkOMD/d6XVjwTAoPGUFh0mCollPUTUTa2OZy4dYNAqlPIQdTiNzJTWdd9Htw==
+ dependencies:
+ minimist "1.1.x"
+
good-listener@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50"
@@ -4678,13 +4683,6 @@ graphlibrary@^2.2.0:
dependencies:
lodash "^4.17.5"
-graphql-anywhere@^4.1.0-alpha.0:
- version "4.1.22"
- resolved "https://registry.yarnpkg.com/graphql-anywhere/-/graphql-anywhere-4.1.22.tgz#1c831ba3c9e5664a0dd24d10d23a9e9512d92056"
- integrity sha512-qm2/1cKM8nfotxDhm4J0r1znVlK0Yge/yEKt26EVVBgpIhvxjXYFALCGbr7cvfDlvzal1iSPpaYa+8YTtjsxQA==
- dependencies:
- apollo-utilities "^1.0.25"
-
graphql-tag@^2.10.0, graphql-tag@^2.4.2:
version "2.10.0"
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.10.0.tgz#87da024be863e357551b2b8700e496ee2d4353ae"
@@ -4715,10 +4713,10 @@ handle-thing@^2.0.0:
resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.0.tgz#0e039695ff50c93fc288557d696f3c1dc6776754"
integrity sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==
-handlebars@^4.0.1, handlebars@^4.0.11, handlebars@^4.0.3:
- version "4.0.12"
- resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.12.tgz#2c15c8a96d46da5e266700518ba8cb8d919d5bc5"
- integrity sha512-RhmTekP+FZL+XNhwS1Wf+bTTZpdLougwt5pcgA1tuz6Jcx0fpH/7z0qd71RKnZHBCxIRBHfBOnio4gViPemNzA==
+handlebars@^4.0.1, handlebars@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.0.tgz#0d6a6f34ff1f63cecec8423aa4169827bf787c3a"
+ integrity sha512-l2jRuU1NAWK6AW5qqcTATWQJvNPEwkM7NEKSiv/gqOsoSQbVoWyqVEY5GS+XPQ88zLNmqASRpzfdm8d79hJS+w==
dependencies:
async "^2.5.0"
optimist "^0.6.1"
@@ -4875,14 +4873,6 @@ hmac-drbg@^1.0.0:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"
-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"
- integrity sha1-42w/LSyufXRqhX440Y1fMqeILbg=
- dependencies:
- os-homedir "^1.0.0"
- os-tmpdir "^1.0.1"
-
homedir-polyfill@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz#4c2bbc8a758998feebf5ed68580f76d46768b4bc"
@@ -4922,6 +4912,11 @@ html-entities@^1.2.0:
resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.0.tgz#41948caf85ce82fed36e4e6a0ed371a6664379e2"
integrity sha1-QZSMr4XOgv7Tbk5qDtNxpmZDeeI=
+html-tags@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-2.0.0.tgz#10b30a386085f43cede353cc8fa7cb0deeea668b"
+ integrity sha1-ELMKOGCF9Dzt41PMj6fLDe7qZos=
+
htmlparser2@^3.10.0, htmlparser2@^3.9.0:
version "3.10.0"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.0.tgz#5f5e422dcf6119c0d983ed36260ce9ded0bee464"
@@ -5032,11 +5027,16 @@ ignore-walk@^3.0.1:
dependencies:
minimatch "^3.0.4"
-ignore@^4.0.6:
+ignore@^4.0.3, ignore@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
+ignore@^5.0.4:
+ version "5.0.5"
+ resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.0.5.tgz#c663c548d6ce186fb33616a8ccb5d46e56bdbbf9"
+ integrity sha512-kOC8IUb8HSDMVcYrDVezCxpJkzSQWTAzf3olpKM6o9rM5zpojx23O0Fl8Wr4+qJ6ZbPEHqf1fdwev/DS7v7pmA==
+
immediate@~3.0.5:
version "3.0.6"
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
@@ -5047,18 +5047,23 @@ immutable-tuple@^0.4.9:
resolved "https://registry.yarnpkg.com/immutable-tuple/-/immutable-tuple-0.4.9.tgz#473ebdd6c169c461913a454bf87ef8f601a20ff0"
integrity sha512-LWbJPZnidF8eczu7XmcnLBsumuyRBkpwIRPCZxlojouhBo5jEBO4toj6n7hMy6IxHU/c+MqDSWkvaTpPlMQcyA==
+import-fresh@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546"
+ integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY=
+ dependencies:
+ caller-path "^2.0.0"
+ resolve-from "^3.0.0"
+
import-lazy@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43"
integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=
-import-local@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/import-local/-/import-local-1.0.0.tgz#5e4ffdc03f4fe6c009c6729beb29631c2f8227bc"
- integrity sha512-vAaZHieK9qjGo58agRBg+bhHX3hoTZU/Oa3GESWLz7t1U62fk63aHuDJJEteXoDeTCcPmUT+z38gkHPZkkmpmQ==
- dependencies:
- pkg-dir "^2.0.0"
- resolve-cwd "^2.0.0"
+import-lazy@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-3.1.0.tgz#891279202c8a2280fdbd6674dbd8da1a1dfc67cc"
+ integrity sha512-8/gvXvX2JMn0F+CDlSC4l6kOmVaLOO3XLkksI7CI3Ud95KDYJuYur2b9P/PUt/i/pDAMd/DulQsNbbbmRRsDIQ==
import-local@^2.0.0:
version "2.0.0"
@@ -5081,6 +5086,11 @@ imurmurhash@^0.1.4:
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
+indent-string@^3.0.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289"
+ integrity sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=
+
indexes-of@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607"
@@ -5109,7 +5119,7 @@ inherits@2.0.1:
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=
-ini@^1.3.4, ini@~1.3.0:
+ini@^1.3.4, ini@^1.3.5, ini@~1.3.0:
version "1.3.5"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
@@ -5219,6 +5229,24 @@ is-accessor-descriptor@^1.0.0:
dependencies:
kind-of "^6.0.0"
+is-alphabetical@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.2.tgz#1fa6e49213cb7885b75d15862fb3f3d96c884f41"
+ integrity sha512-V0xN4BYezDHcBSKb1QHUFMlR4as/XEuCZBzMJUU4n7+Cbt33SmUnSol+pnXFvLxSHNq2CemUXNdaXV6Flg7+xg==
+
+is-alphanumeric@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-alphanumeric/-/is-alphanumeric-1.0.0.tgz#4a9cef71daf4c001c1d81d63d140cf53fd6889f4"
+ integrity sha1-Spzvcdr0wAHB2B1j0UDPU/1oifQ=
+
+is-alphanumerical@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.2.tgz#1138e9ae5040158dc6ff76b820acd6b7a181fd40"
+ integrity sha512-pyfU/0kHdISIgslFfZN9nfY1Gk3MquQgUm1mJTjdkEPpkAKNWuBTSqFwewOpR7N351VkErCiyV71zX7mlQQqsg==
+ dependencies:
+ is-alphabetical "^1.0.0"
+ is-decimal "^1.0.0"
+
is-arrayish@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
@@ -5236,12 +5264,10 @@ is-buffer@^1.1.5:
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
-is-builtin-module@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe"
- integrity sha1-VAVy0096wxGfj3bDDLwbHgN6/74=
- dependencies:
- builtin-modules "^1.0.0"
+is-buffer@^2.0.0:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725"
+ integrity sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==
is-callable@^1.1.1, is-callable@^1.1.3:
version "1.1.4"
@@ -5255,6 +5281,13 @@ is-ci@^1.0.10:
dependencies:
ci-info "^1.5.0"
+is-ci@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c"
+ integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==
+ dependencies:
+ ci-info "^2.0.0"
+
is-data-descriptor@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56"
@@ -5274,6 +5307,11 @@ is-date-object@^1.0.1:
resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16"
integrity sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=
+is-decimal@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.2.tgz#894662d6a8709d307f3a276ca4339c8fa5dff0ff"
+ integrity sha512-TRzl7mOCchnhchN+f3ICUCzYvL9ul7R+TYOsZ8xia++knyZAJfv/uA1FvQXsAnYIl1T3B2X5E/J7Wb1QXiIBXg==
+
is-descriptor@^0.1.0:
version "0.1.6"
resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca"
@@ -5292,17 +5330,10 @@ is-descriptor@^1.0.0, is-descriptor@^1.0.2:
is-data-descriptor "^1.0.0"
kind-of "^6.0.2"
-is-dotfile@^1.0.0:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1"
- integrity sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=
-
-is-equal-shallow@^0.1.3:
- version "0.1.3"
- resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534"
- integrity sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=
- dependencies:
- is-primitive "^2.0.0"
+is-directory@^0.3.1:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1"
+ integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=
is-extendable@^0.1.0, is-extendable@^0.1.1:
version "0.1.1"
@@ -5316,23 +5347,11 @@ is-extendable@^1.0.1:
dependencies:
is-plain-object "^2.0.4"
-is-extglob@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0"
- integrity sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=
-
is-extglob@^2.1.0, is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
-is-finite@^1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa"
- integrity sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=
- dependencies:
- number-is-nan "^1.0.0"
-
is-fullwidth-code-point@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb"
@@ -5345,17 +5364,10 @@ is-fullwidth-code-point@^2.0.0:
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
-is-generator-fn@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-1.0.0.tgz#969d49e1bb3329f6bb7f09089be26578b2ddd46a"
- integrity sha1-lp1J4bszKfa7fwkIm+JleLLd1Go=
-
-is-glob@^2.0.0, is-glob@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863"
- integrity sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=
- dependencies:
- is-extglob "^1.0.0"
+is-generator-fn@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.0.0.tgz#038c31b774709641bda678b1f06a4e3227c10b3e"
+ integrity sha512-elzyIdM7iKoFHzcrndIqjYomImhxrFRnGP3galODoII4TB9gI7mZ+FnlLQmmjf27SxHS2gKEeyhX5/+YRS6H9g==
is-glob@^3.1.0:
version "3.1.0"
@@ -5371,6 +5383,11 @@ is-glob@^4.0.0:
dependencies:
is-extglob "^2.1.1"
+is-hexadecimal@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.2.tgz#b6e710d7d07bb66b98cb8cece5c9b4921deeb835"
+ integrity sha512-but/G3sapV3MNyqiDBLrOi4x8uCIw0RY3o/Vb5GT0sMFHrVV7731wFSVy41T5FO1og7G0gXLJh0MkgPRouko/A==
+
is-installed-globally@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80"
@@ -5389,13 +5406,6 @@ is-number@^0.1.1:
resolved "https://registry.yarnpkg.com/is-number/-/is-number-0.1.1.tgz#69a7af116963d47206ec9bd9b48a14216f1e3806"
integrity sha1-aaevEWlj1HIG7JvZtIoUIW8eOAY=
-is-number@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f"
- integrity sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=
- dependencies:
- kind-of "^3.0.2"
-
is-number@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195"
@@ -5444,7 +5454,7 @@ is-path-inside@^1.0.0:
dependencies:
path-is-inside "^1.0.1"
-is-plain-obj@^1.0.0:
+is-plain-obj@^1.0.0, is-plain-obj@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4=
@@ -5456,16 +5466,6 @@ is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4:
dependencies:
isobject "^3.0.1"
-is-posix-bracket@^0.1.0:
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4"
- integrity sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=
-
-is-primitive@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575"
- integrity sha1-IHurkWOEmcB7Kt8kCkGochADRXU=
-
is-promise@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa"
@@ -5503,6 +5503,11 @@ is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0:
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
+is-supported-regexp-flag@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-supported-regexp-flag/-/is-supported-regexp-flag-1.0.1.tgz#21ee16518d2c1dd3edd3e9a0d57e50207ac364ca"
+ integrity sha512-3vcJecUUrpgCqc/ca0aWeNu64UGgxcvO60K/Fkr1N6RSvfGCTU60UKN68JDmKokgba0rFFJs12EnzOQa14ubKQ==
+
is-symbol@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38"
@@ -5515,16 +5520,21 @@ is-typedarray@~1.0.0:
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
-is-utf8@^0.2.0:
- version "0.2.1"
- resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
- integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=
+is-whitespace-character@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.2.tgz#ede53b4c6f6fb3874533751ec9280d01928d03ed"
+ integrity sha512-SzM+T5GKUCtLhlHFKt2SDAX2RFzfS6joT91F2/WSi9LxgFdsnhfPK/UIA+JhRR2xuyLdrCys2PiFDrtn1fU5hQ==
is-windows@^1.0.1, is-windows@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
+is-word-character@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/is-word-character/-/is-word-character-1.0.2.tgz#46a5dac3f2a1840898b91e576cd40d493f3ae553"
+ integrity sha512-T3FlsX8rCHAH8e7RE7PfOPZVFQlcV3XRF9eOOBQ1uf70OxO7CjjSOjeImMPCADBdYWcStAbVbYvJ1m2D3tb+EA==
+
is-wsl@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
@@ -5567,145 +5577,76 @@ isstream@~0.1.2:
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
-istanbul-api@^1.3.1:
- version "1.3.7"
- resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.3.7.tgz#a86c770d2b03e11e3f778cd7aedd82d2722092aa"
- integrity sha512-4/ApBnMVeEPG3EkSzcw25wDe4N66wxwn+KKn6b47vyek8Xb3NBAcg4xfuQbS7BqcZuTX4wxfD5lVagdggR3gyA==
- dependencies:
- async "^2.1.4"
- fileset "^2.0.2"
- istanbul-lib-coverage "^1.2.1"
- istanbul-lib-hook "^1.2.2"
- istanbul-lib-instrument "^1.10.2"
- istanbul-lib-report "^1.1.5"
- istanbul-lib-source-maps "^1.2.6"
- istanbul-reports "^1.5.1"
- js-yaml "^3.7.0"
- mkdirp "^0.5.1"
- once "^1.4.0"
-
-istanbul-api@^2.0.5:
- version "2.0.6"
- resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-2.0.6.tgz#cd7b33ee678f6c01531d05f5e567ebbcd25f8ecc"
- integrity sha512-8W5oeAGWXhtTJjAyVfvavOLVyZCTNCKsyF6GON/INKlBdO7uJ/bv3qnPj5M6ERKzmMCJS1kntnjjGuJ86fn3rQ==
+istanbul-api@^2.0.5, istanbul-api@^2.0.8:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-2.1.1.tgz#194b773f6d9cbc99a9258446848b0f988951c4d0"
+ integrity sha512-kVmYrehiwyeBAk/wE71tW6emzLiHGjYIiDrc8sfyty4F8M02/lrgXSm+R1kXysmF20zArvmZXjlE/mg24TVPJw==
dependencies:
async "^2.6.1"
compare-versions "^3.2.1"
fileset "^2.0.3"
- istanbul-lib-coverage "^2.0.1"
- istanbul-lib-hook "^2.0.1"
- istanbul-lib-instrument "^3.0.0"
- istanbul-lib-report "^2.0.2"
- istanbul-lib-source-maps "^2.0.1"
- istanbul-reports "^2.0.1"
+ istanbul-lib-coverage "^2.0.3"
+ istanbul-lib-hook "^2.0.3"
+ istanbul-lib-instrument "^3.1.0"
+ istanbul-lib-report "^2.0.4"
+ istanbul-lib-source-maps "^3.0.2"
+ istanbul-reports "^2.1.1"
js-yaml "^3.12.0"
make-dir "^1.3.0"
+ minimatch "^3.0.4"
once "^1.4.0"
-istanbul-lib-coverage@^1.2.0, istanbul-lib-coverage@^1.2.1:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz#ccf7edcd0a0bb9b8f729feeb0930470f9af664f0"
- integrity sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==
-
-istanbul-lib-coverage@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#2aee0e073ad8c5f6a0b00e0dfbf52b4667472eda"
- integrity sha512-nPvSZsVlbG9aLhZYaC3Oi1gT/tpyo3Yt5fNyf6NmcKIayz4VV/txxJFFKAK/gU4dcNn8ehsanBbVHVl0+amOLA==
-
-istanbul-lib-hook@^1.2.2:
- version "1.2.2"
- resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.2.2.tgz#bc6bf07f12a641fbf1c85391d0daa8f0aea6bf86"
- integrity sha512-/Jmq7Y1VeHnZEQ3TL10VHyb564mn6VrQXHchON9Jf/AEcmQ3ZIiyD1BVzNOKTZf/G3gE+kiGK6SmpF9y3qGPLw==
- dependencies:
- append-transform "^0.4.0"
+istanbul-lib-coverage@^2.0.2, istanbul-lib-coverage@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#0b891e5ad42312c2b9488554f603795f9a2211ba"
+ integrity sha512-dKWuzRGCs4G+67VfW9pBFFz2Jpi4vSp/k7zBcJ888ofV5Mi1g5CUML5GvMvV6u9Cjybftu+E8Cgp+k0dI1E5lw==
-istanbul-lib-hook@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-2.0.1.tgz#918a57b75a0f951d552a08487ca1fa5336433d72"
- integrity sha512-ufiZoiJ8CxY577JJWEeFuxXZoMqiKpq/RqZtOAYuQLvlkbJWscq9n3gc4xrCGH9n4pW0qnTxOz1oyMmVtk8E1w==
+istanbul-lib-hook@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-2.0.3.tgz#e0e581e461c611be5d0e5ef31c5f0109759916fb"
+ integrity sha512-CLmEqwEhuCYtGcpNVJjLV1DQyVnIqavMLFHV/DP+np/g3qvdxu3gsPqYoJMXm15sN84xOlckFB3VNvRbf5yEgA==
dependencies:
append-transform "^1.0.0"
-istanbul-lib-instrument@^1.10.1, istanbul-lib-instrument@^1.10.2:
- version "1.10.2"
- resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz#1f55ed10ac3c47f2bdddd5307935126754d0a9ca"
- integrity sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A==
- dependencies:
- babel-generator "^6.18.0"
- babel-template "^6.16.0"
- babel-traverse "^6.18.0"
- babel-types "^6.18.0"
- babylon "^6.18.0"
- istanbul-lib-coverage "^1.2.1"
- semver "^5.3.0"
-
-istanbul-lib-instrument@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-3.0.0.tgz#b5f066b2a161f75788be17a9d556f40a0cf2afc9"
- integrity sha512-eQY9vN9elYjdgN9Iv6NS/00bptm02EBBk70lRMaVjeA6QYocQgenVrSgC28TJurdnZa80AGO3ASdFN+w/njGiQ==
+istanbul-lib-instrument@^3.0.0, istanbul-lib-instrument@^3.0.1, istanbul-lib-instrument@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-3.1.0.tgz#a2b5484a7d445f1f311e93190813fa56dfb62971"
+ integrity sha512-ooVllVGT38HIk8MxDj/OIHXSYvH+1tq/Vb38s8ixt9GoJadXska4WkGY+0wkmtYCZNYtaARniH/DixUGGLZ0uA==
dependencies:
"@babel/generator" "^7.0.0"
"@babel/parser" "^7.0.0"
"@babel/template" "^7.0.0"
"@babel/traverse" "^7.0.0"
"@babel/types" "^7.0.0"
- istanbul-lib-coverage "^2.0.1"
+ istanbul-lib-coverage "^2.0.3"
semver "^5.5.0"
-istanbul-lib-report@^1.1.5:
- version "1.1.5"
- resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.5.tgz#f2a657fc6282f96170aaf281eb30a458f7f4170c"
- integrity sha512-UsYfRMoi6QO/doUshYNqcKJqVmFe9w51GZz8BS3WB0lYxAllQYklka2wP9+dGZeHYaWIdcXUx8JGdbqaoXRXzw==
- dependencies:
- istanbul-lib-coverage "^1.2.1"
- mkdirp "^0.5.1"
- path-parse "^1.0.5"
- supports-color "^3.1.2"
-
-istanbul-lib-report@^2.0.2:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-2.0.2.tgz#430a2598519113e1da7af274ba861bd42dd97535"
- integrity sha512-rJ8uR3peeIrwAxoDEbK4dJ7cqqtxBisZKCuwkMtMv0xYzaAnsAi3AHrHPAAtNXzG/bcCgZZ3OJVqm1DTi9ap2Q==
+istanbul-lib-report@^2.0.4:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-2.0.4.tgz#bfd324ee0c04f59119cb4f07dab157d09f24d7e4"
+ integrity sha512-sOiLZLAWpA0+3b5w5/dq0cjm2rrNdAfHWaGhmn7XEFW6X++IV9Ohn+pnELAl9K3rfpaeBfbmH9JU5sejacdLeA==
dependencies:
- istanbul-lib-coverage "^2.0.1"
+ istanbul-lib-coverage "^2.0.3"
make-dir "^1.3.0"
- supports-color "^5.4.0"
-
-istanbul-lib-source-maps@^1.2.4, istanbul-lib-source-maps@^1.2.6:
- version "1.2.6"
- resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.6.tgz#37b9ff661580f8fca11232752ee42e08c6675d8f"
- integrity sha512-TtbsY5GIHgbMsMiRw35YBHGpZ1DVFEO19vxxeiDMYaeOFOCzfnYVxvl6pOUIZR4dtPhAGpSMup8OyF8ubsaqEg==
- dependencies:
- debug "^3.1.0"
- istanbul-lib-coverage "^1.2.1"
- mkdirp "^0.5.1"
- rimraf "^2.6.1"
- source-map "^0.5.3"
+ supports-color "^6.0.0"
-istanbul-lib-source-maps@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-2.0.1.tgz#ce8b45131d8293fdeaa732f4faf1852d13d0a97e"
- integrity sha512-30l40ySg+gvBLcxTrLzR4Z2XTRj3HgRCA/p2rnbs/3OiTaoj054gAbuP5DcLOtwqmy4XW8qXBHzrmP2/bQ9i3A==
+istanbul-lib-source-maps@^3.0.1, istanbul-lib-source-maps@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.2.tgz#f1e817229a9146e8424a28e5d69ba220fda34156"
+ integrity sha512-JX4v0CiKTGp9fZPmoxpu9YEkPbEqCqBbO3403VabKjH+NRXo72HafD5UgnjTEqHL2SAjaZK1XDuDOkn6I5QVfQ==
dependencies:
- debug "^3.1.0"
- istanbul-lib-coverage "^2.0.1"
+ debug "^4.1.1"
+ istanbul-lib-coverage "^2.0.3"
make-dir "^1.3.0"
rimraf "^2.6.2"
source-map "^0.6.1"
-istanbul-reports@^1.5.1:
- version "1.5.1"
- resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.5.1.tgz#97e4dbf3b515e8c484caea15d6524eebd3ff4e1a"
- integrity sha512-+cfoZ0UXzWjhAdzosCPP3AN8vvef8XDkWtTfgaN+7L3YTpNYITnCaEkceo5SEYy644VkHka/P1FvkWvrG/rrJw==
- dependencies:
- handlebars "^4.0.3"
-
-istanbul-reports@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-2.0.1.tgz#fb8d6ea850701a3984350b977a969e9a556116a7"
- integrity sha512-CT0QgMBJqs6NJLF678ZHcquUAZIoBIUNzdJrRJfpkI9OnzG6MkUfHxbJC3ln981dMswC7/B1mfX3LNkhgJxsuw==
+istanbul-reports@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-2.1.1.tgz#72ef16b4ecb9a4a7bd0e2001e00f95d1eec8afa9"
+ integrity sha512-FzNahnidyEPBCI0HcufJoSEoKykesRlFcSzQqjH9x0+LC8tnnE/p/90PBLu8iZTxr8yYZNyTtiAujUqyN+CIxw==
dependencies:
- handlebars "^4.0.11"
+ handlebars "^4.1.0"
istanbul@^0.4.5:
version "0.4.5"
@@ -5771,333 +5712,349 @@ jed@^1.1.1:
resolved "https://registry.yarnpkg.com/jed/-/jed-1.1.1.tgz#7a549bbd9ffe1585b0cd0a191e203055bee574b4"
integrity sha1-elSbvZ/+FYWwzQoZHiAwVb7ldLQ=
-jest-changed-files@^23.4.2:
- version "23.4.2"
- resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-23.4.2.tgz#1eed688370cd5eebafe4ae93d34bb3b64968fe83"
- integrity sha512-EyNhTAUWEfwnK0Is/09LxoqNDOn7mU7S3EHskG52djOFS/z+IT0jT3h3Ql61+dklcG7bJJitIWEMB4Sp1piHmA==
+jest-changed-files@^24.0.0:
+ version "24.0.0"
+ resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-24.0.0.tgz#c02c09a8cc9ca93f513166bc773741bd39898ff7"
+ integrity sha512-nnuU510R9U+UX0WNb5XFEcsrMqriSiRLeO9KWDFgPrpToaQm60prfQYpxsXigdClpvNot5bekDY440x9dNGnsQ==
dependencies:
+ execa "^1.0.0"
throat "^4.0.0"
-jest-cli@^23.6.0:
- version "23.6.0"
- resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-23.6.0.tgz#61ab917744338f443ef2baa282ddffdd658a5da4"
- integrity sha512-hgeD1zRUp1E1zsiyOXjEn4LzRLWdJBV//ukAHGlx6s5mfCNJTbhbHjgxnDUXA8fsKWN/HqFFF6X5XcCwC/IvYQ==
+jest-cli@^24.1.0:
+ version "24.1.0"
+ resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-24.1.0.tgz#f7cc98995f36e7210cce3cbb12974cbf60940843"
+ integrity sha512-U/iyWPwOI0T1CIxVLtk/2uviOTJ/OiSWJSe8qt6X1VkbbgP+nrtLJlmT9lPBe4lK78VNFJtrJ7pttcNv/s7yCw==
dependencies:
ansi-escapes "^3.0.0"
chalk "^2.0.1"
exit "^0.1.2"
glob "^7.1.2"
- graceful-fs "^4.1.11"
- import-local "^1.0.0"
- is-ci "^1.0.10"
- istanbul-api "^1.3.1"
- istanbul-lib-coverage "^1.2.0"
- istanbul-lib-instrument "^1.10.1"
- istanbul-lib-source-maps "^1.2.4"
- jest-changed-files "^23.4.2"
- jest-config "^23.6.0"
- jest-environment-jsdom "^23.4.0"
- jest-get-type "^22.1.0"
- jest-haste-map "^23.6.0"
- jest-message-util "^23.4.0"
- jest-regex-util "^23.3.0"
- jest-resolve-dependencies "^23.6.0"
- jest-runner "^23.6.0"
- jest-runtime "^23.6.0"
- jest-snapshot "^23.6.0"
- jest-util "^23.4.0"
- jest-validate "^23.6.0"
- jest-watcher "^23.4.0"
- jest-worker "^23.2.0"
- micromatch "^2.3.11"
+ graceful-fs "^4.1.15"
+ import-local "^2.0.0"
+ is-ci "^2.0.0"
+ istanbul-api "^2.0.8"
+ istanbul-lib-coverage "^2.0.2"
+ istanbul-lib-instrument "^3.0.1"
+ istanbul-lib-source-maps "^3.0.1"
+ jest-changed-files "^24.0.0"
+ jest-config "^24.1.0"
+ jest-environment-jsdom "^24.0.0"
+ jest-get-type "^24.0.0"
+ jest-haste-map "^24.0.0"
+ jest-message-util "^24.0.0"
+ jest-regex-util "^24.0.0"
+ jest-resolve-dependencies "^24.1.0"
+ jest-runner "^24.1.0"
+ jest-runtime "^24.1.0"
+ jest-snapshot "^24.1.0"
+ jest-util "^24.0.0"
+ jest-validate "^24.0.0"
+ jest-watcher "^24.0.0"
+ jest-worker "^24.0.0"
+ micromatch "^3.1.10"
node-notifier "^5.2.1"
- prompts "^0.1.9"
+ p-each-series "^1.0.0"
+ pirates "^4.0.0"
+ prompts "^2.0.1"
realpath-native "^1.0.0"
rimraf "^2.5.4"
- slash "^1.0.0"
+ slash "^2.0.0"
string-length "^2.0.0"
- strip-ansi "^4.0.0"
+ strip-ansi "^5.0.0"
which "^1.2.12"
- yargs "^11.0.0"
+ yargs "^12.0.2"
-jest-config@^23.6.0:
- version "23.6.0"
- resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-23.6.0.tgz#f82546a90ade2d8c7026fbf6ac5207fc22f8eb1d"
- integrity sha512-i8V7z9BeDXab1+VNo78WM0AtWpBRXJLnkT+lyT+Slx/cbP5sZJ0+NDuLcmBE5hXAoK0aUp7vI+MOxR+R4d8SRQ==
+jest-config@^24.1.0:
+ version "24.1.0"
+ resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-24.1.0.tgz#6ea6881cfdd299bc86cc144ee36d937c97c3850c"
+ integrity sha512-FbbRzRqtFC6eGjG5VwsbW4E5dW3zqJKLWYiZWhB0/4E5fgsMw8GODLbGSrY5t17kKOtCWb/Z7nsIThRoDpuVyg==
dependencies:
- babel-core "^6.0.0"
- babel-jest "^23.6.0"
+ "@babel/core" "^7.1.0"
+ babel-jest "^24.1.0"
chalk "^2.0.1"
glob "^7.1.1"
- jest-environment-jsdom "^23.4.0"
- jest-environment-node "^23.4.0"
- jest-get-type "^22.1.0"
- jest-jasmine2 "^23.6.0"
- jest-regex-util "^23.3.0"
- jest-resolve "^23.6.0"
- jest-util "^23.4.0"
- jest-validate "^23.6.0"
- micromatch "^2.3.11"
- pretty-format "^23.6.0"
-
-jest-diff@^23.6.0:
- version "23.6.0"
- resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-23.6.0.tgz#1500f3f16e850bb3d71233408089be099f610c7d"
- integrity sha512-Gz9l5Ov+X3aL5L37IT+8hoCUsof1CVYBb2QEkOupK64XyRR3h+uRpYIm97K7sY8diFxowR8pIGEdyfMKTixo3g==
+ jest-environment-jsdom "^24.0.0"
+ jest-environment-node "^24.0.0"
+ jest-get-type "^24.0.0"
+ jest-jasmine2 "^24.1.0"
+ jest-regex-util "^24.0.0"
+ jest-resolve "^24.1.0"
+ jest-util "^24.0.0"
+ jest-validate "^24.0.0"
+ micromatch "^3.1.10"
+ pretty-format "^24.0.0"
+ realpath-native "^1.0.2"
+
+jest-diff@^24.0.0:
+ version "24.0.0"
+ resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.0.0.tgz#a3e5f573dbac482f7d9513ac9cfa21644d3d6b34"
+ integrity sha512-XY5wMpRaTsuMoU+1/B2zQSKQ9RdE9gsLkGydx3nvApeyPijLA8GtEvIcPwISRCer+VDf9W1mStTYYq6fPt8ryA==
dependencies:
chalk "^2.0.1"
- diff "^3.2.0"
- jest-get-type "^22.1.0"
- pretty-format "^23.6.0"
+ diff-sequences "^24.0.0"
+ jest-get-type "^24.0.0"
+ pretty-format "^24.0.0"
-jest-docblock@^23.2.0:
- version "23.2.0"
- resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-23.2.0.tgz#f085e1f18548d99fdd69b20207e6fd55d91383a7"
- integrity sha1-8IXh8YVI2Z/dabICB+b9VdkTg6c=
+jest-docblock@^24.0.0:
+ version "24.0.0"
+ resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-24.0.0.tgz#54d77a188743e37f62181a91a01eb9222289f94e"
+ integrity sha512-KfAKZ4SN7CFOZpWg4i7g7MSlY0M+mq7K0aMqENaG2vHuhC9fc3vkpU/iNN9sOus7v3h3Y48uEjqz3+Gdn2iptA==
dependencies:
detect-newline "^2.1.0"
-jest-each@^23.6.0:
- version "23.6.0"
- resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-23.6.0.tgz#ba0c3a82a8054387016139c733a05242d3d71575"
- integrity sha512-x7V6M/WGJo6/kLoissORuvLIeAoyo2YqLOoCDkohgJ4XOXSqOtyvr8FbInlAWS77ojBsZrafbozWoKVRdtxFCg==
+jest-each@^24.0.0:
+ version "24.0.0"
+ resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-24.0.0.tgz#10987a06b21c7ffbfb7706c89d24c52ed864be55"
+ integrity sha512-gFcbY4Cu55yxExXMkjrnLXov3bWO3dbPAW7HXb31h/DNWdNc/6X8MtxGff8nh3/MjkF9DpVqnj0KsPKuPK0cpA==
dependencies:
chalk "^2.0.1"
- pretty-format "^23.6.0"
+ jest-get-type "^24.0.0"
+ jest-util "^24.0.0"
+ pretty-format "^24.0.0"
-jest-environment-jsdom@^23.4.0:
- version "23.4.0"
- resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-23.4.0.tgz#056a7952b3fea513ac62a140a2c368c79d9e6023"
- integrity sha1-BWp5UrP+pROsYqFAosNox52eYCM=
+jest-environment-jsdom@^24.0.0:
+ version "24.0.0"
+ resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-24.0.0.tgz#5affa0654d6e44cd798003daa1a8701dbd6e4d11"
+ integrity sha512-1YNp7xtxajTRaxbylDc2pWvFnfDTH5BJJGyVzyGAKNt/lEULohwEV9zFqTgG4bXRcq7xzdd+sGFws+LxThXXOw==
dependencies:
- jest-mock "^23.2.0"
- jest-util "^23.4.0"
+ jest-mock "^24.0.0"
+ jest-util "^24.0.0"
jsdom "^11.5.1"
-jest-environment-node@^23.4.0:
- version "23.4.0"
- resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-23.4.0.tgz#57e80ed0841dea303167cce8cd79521debafde10"
- integrity sha1-V+gO0IQd6jAxZ8zozXlSHeuv3hA=
+jest-environment-node@^24.0.0:
+ version "24.0.0"
+ resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-24.0.0.tgz#330948980656ed8773ce2e04eb597ed91e3c7190"
+ integrity sha512-62fOFcaEdU0VLaq8JL90TqwI7hLn0cOKOl8vY2n477vRkCJRojiRRtJVRzzCcgFvs6gqU97DNqX5R0BrBP6Rxg==
dependencies:
- jest-mock "^23.2.0"
- jest-util "^23.4.0"
+ jest-mock "^24.0.0"
+ jest-util "^24.0.0"
-jest-get-type@^22.1.0:
- version "22.4.3"
- resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-22.4.3.tgz#e3a8504d8479342dd4420236b322869f18900ce4"
- integrity sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==
+jest-get-type@^24.0.0:
+ version "24.0.0"
+ resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.0.0.tgz#36e72930b78e33da59a4f63d44d332188278940b"
+ integrity sha512-z6/Eyf6s9ZDGz7eOvl+fzpuJmN9i0KyTt1no37/dHu8galssxz5ZEgnc1KaV8R31q1khxyhB4ui/X5ZjjPk77w==
-jest-haste-map@^23.6.0:
- version "23.6.0"
- resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-23.6.0.tgz#2e3eb997814ca696d62afdb3f2529f5bbc935e16"
- integrity sha512-uyNhMyl6dr6HaXGHp8VF7cK6KpC6G9z9LiMNsst+rJIZ8l7wY0tk8qwjPmEghczojZ2/ZhtEdIabZ0OQRJSGGg==
+jest-haste-map@^24.0.0:
+ version "24.0.0"
+ resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.0.0.tgz#e9ef51b2c9257384b4d6beb83bd48c65b37b5e6e"
+ integrity sha512-CcViJyUo41IQqttLxXVdI41YErkzBKbE6cS6dRAploCeutePYfUimWd3C9rQEWhX0YBOQzvNsC0O9nYxK2nnxQ==
dependencies:
fb-watchman "^2.0.0"
- graceful-fs "^4.1.11"
+ graceful-fs "^4.1.15"
invariant "^2.2.4"
- jest-docblock "^23.2.0"
- jest-serializer "^23.0.1"
- jest-worker "^23.2.0"
- micromatch "^2.3.11"
- sane "^2.0.0"
-
-jest-jasmine2@^23.6.0:
- version "23.6.0"
- resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-23.6.0.tgz#840e937f848a6c8638df24360ab869cc718592e0"
- integrity sha512-pe2Ytgs1nyCs8IvsEJRiRTPC0eVYd8L/dXJGU08GFuBwZ4sYH/lmFDdOL3ZmvJR8QKqV9MFuwlsAi/EWkFUbsQ==
- dependencies:
- babel-traverse "^6.0.0"
+ jest-serializer "^24.0.0"
+ jest-util "^24.0.0"
+ jest-worker "^24.0.0"
+ micromatch "^3.1.10"
+ sane "^3.0.0"
+
+jest-jasmine2@^24.1.0:
+ version "24.1.0"
+ resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-24.1.0.tgz#8377324b967037c440f0a549ee0bbd9912055db6"
+ integrity sha512-H+o76SdSNyCh9fM5K8upK45YTo/DiFx5w2YAzblQebSQmukDcoVBVeXynyr7DDnxh+0NTHYRCLwJVf3tC518wg==
+ dependencies:
+ "@babel/traverse" "^7.1.0"
chalk "^2.0.1"
co "^4.6.0"
- expect "^23.6.0"
- is-generator-fn "^1.0.0"
- jest-diff "^23.6.0"
- jest-each "^23.6.0"
- jest-matcher-utils "^23.6.0"
- jest-message-util "^23.4.0"
- jest-snapshot "^23.6.0"
- jest-util "^23.4.0"
- pretty-format "^23.6.0"
-
-jest-junit@^5.2.0:
- version "5.2.0"
- resolved "https://registry.yarnpkg.com/jest-junit/-/jest-junit-5.2.0.tgz#980401db7aa69999cf117c6d740a8135c22ae379"
- integrity sha512-Mdg0Qpdh1Xm/FA1B/mcLlmEmlr3XzH5pZg7MvcAwZhjHijPRd1z/UwYwkwNHmCV7o4ZOWCf77nLu7ZkhHHrtJg==
+ expect "^24.1.0"
+ is-generator-fn "^2.0.0"
+ jest-each "^24.0.0"
+ jest-matcher-utils "^24.0.0"
+ jest-message-util "^24.0.0"
+ jest-snapshot "^24.1.0"
+ jest-util "^24.0.0"
+ pretty-format "^24.0.0"
+ throat "^4.0.0"
+
+jest-junit@^6.3.0:
+ version "6.3.0"
+ resolved "https://registry.yarnpkg.com/jest-junit/-/jest-junit-6.3.0.tgz#99e64ebc54eddcb21238f0cc49f5820c89a8c785"
+ integrity sha512-3PH9UkpaomX6CUzqjlnk0m4yBCW/eroxV6v61OM6LkCQFO848P3YUhfIzu8ypZSBKB3vvCbB4WaLTKT0BrIf8A==
dependencies:
- jest-config "^23.6.0"
- jest-validate "^23.0.1"
+ jest-validate "^24.0.0"
mkdirp "^0.5.1"
strip-ansi "^4.0.0"
xml "^1.0.1"
-jest-leak-detector@^23.6.0:
- version "23.6.0"
- resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-23.6.0.tgz#e4230fd42cf381a1a1971237ad56897de7e171de"
- integrity sha512-f/8zA04rsl1Nzj10HIyEsXvYlMpMPcy0QkQilVZDFOaPbv2ur71X5u2+C4ZQJGyV/xvVXtCCZ3wQ99IgQxftCg==
+jest-leak-detector@^24.0.0:
+ version "24.0.0"
+ resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-24.0.0.tgz#78280119fd05ee98317daee62cddb3aa537a31c6"
+ integrity sha512-ZYHJYFeibxfsDSKowjDP332pStuiFT2xfc5R67Rjm/l+HFJWJgNIOCOlQGeXLCtyUn3A23+VVDdiCcnB6dTTrg==
dependencies:
- pretty-format "^23.6.0"
+ pretty-format "^24.0.0"
-jest-matcher-utils@^23.6.0:
- version "23.6.0"
- resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-23.6.0.tgz#726bcea0c5294261a7417afb6da3186b4b8cac80"
- integrity sha512-rosyCHQfBcol4NsckTn01cdelzWLU9Cq7aaigDf8VwwpIRvWE/9zLgX2bON+FkEW69/0UuYslUe22SOdEf2nog==
+jest-matcher-utils@^24.0.0:
+ version "24.0.0"
+ resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.0.0.tgz#fc9c41cfc49b2c3ec14e576f53d519c37729d579"
+ integrity sha512-LQTDmO+aWRz1Tf9HJg+HlPHhDh1E1c65kVwRFo5mwCVp5aQDzlkz4+vCvXhOKFjitV2f0kMdHxnODrXVoi+rlA==
dependencies:
chalk "^2.0.1"
- jest-get-type "^22.1.0"
- pretty-format "^23.6.0"
+ jest-diff "^24.0.0"
+ jest-get-type "^24.0.0"
+ pretty-format "^24.0.0"
-jest-message-util@^23.4.0:
- version "23.4.0"
- resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-23.4.0.tgz#17610c50942349508d01a3d1e0bda2c079086a9f"
- integrity sha1-F2EMUJQjSVCNAaPR4L2iwHkIap8=
+jest-message-util@^24.0.0:
+ version "24.0.0"
+ resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-24.0.0.tgz#a07a141433b2c992dbaec68d4cbfe470ba289619"
+ integrity sha512-J9ROJIwz/IeC+eV1XSwnRK4oAwPuhmxEyYx1+K5UI+pIYwFZDSrfZaiWTdq0d2xYFw4Xiu+0KQWsdsQpgJMf3Q==
dependencies:
- "@babel/code-frame" "^7.0.0-beta.35"
+ "@babel/code-frame" "^7.0.0"
chalk "^2.0.1"
- micromatch "^2.3.11"
- slash "^1.0.0"
+ micromatch "^3.1.10"
+ slash "^2.0.0"
stack-utils "^1.0.1"
-jest-mock@^23.2.0:
- version "23.2.0"
- resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-23.2.0.tgz#ad1c60f29e8719d47c26e1138098b6d18b261134"
- integrity sha1-rRxg8p6HGdR8JuETgJi20YsmETQ=
+jest-mock@^24.0.0:
+ version "24.0.0"
+ resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.0.0.tgz#9a4b53e01d66a0e780f7d857462d063e024c617d"
+ integrity sha512-sQp0Hu5fcf5NZEh1U9eIW2qD0BwJZjb63Yqd98PQJFvf/zzUTBoUAwv/Dc/HFeNHIw1f3hl/48vNn+j3STaI7A==
-jest-regex-util@^23.3.0:
- version "23.3.0"
- resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-23.3.0.tgz#5f86729547c2785c4002ceaa8f849fe8ca471bc5"
- integrity sha1-X4ZylUfCeFxAAs6qj4Sf6MpHG8U=
+jest-regex-util@^24.0.0:
+ version "24.0.0"
+ resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-24.0.0.tgz#4feee8ec4a358f5bee0a654e94eb26163cb9089a"
+ integrity sha512-Jv/uOTCuC+PY7WpJl2mpoI+WbY2ut73qwwO9ByJJNwOCwr1qWhEW2Lyi2S9ZewUdJqeVpEBisdEVZSI+Zxo58Q==
-jest-resolve-dependencies@^23.6.0:
- version "23.6.0"
- resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-23.6.0.tgz#b4526af24c8540d9a3fab102c15081cf509b723d"
- integrity sha512-EkQWkFWjGKwRtRyIwRwI6rtPAEyPWlUC2MpzHissYnzJeHcyCn1Hc8j7Nn1xUVrS5C6W5+ZL37XTem4D4pLZdA==
+jest-resolve-dependencies@^24.1.0:
+ version "24.1.0"
+ resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-24.1.0.tgz#78f738a2ec59ff4d00751d9da56f176e3f589f6c"
+ integrity sha512-2VwPsjd3kRPu7qe2cpytAgowCObk5AKeizfXuuiwgm1a9sijJDZe8Kh1sFj6FKvSaNEfCPlBVkZEJa2482m/Uw==
dependencies:
- jest-regex-util "^23.3.0"
- jest-snapshot "^23.6.0"
+ jest-regex-util "^24.0.0"
+ jest-snapshot "^24.1.0"
-jest-resolve@^23.6.0:
- version "23.6.0"
- resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-23.6.0.tgz#cf1d1a24ce7ee7b23d661c33ba2150f3aebfa0ae"
- integrity sha512-XyoRxNtO7YGpQDmtQCmZjum1MljDqUCob7XlZ6jy9gsMugHdN2hY4+Acz9Qvjz2mSsOnPSH7skBmDYCHXVZqkA==
+jest-resolve@^24.1.0:
+ version "24.1.0"
+ resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-24.1.0.tgz#42ff0169b0ea47bfdbd0c52a0067ca7d022c7688"
+ integrity sha512-TPiAIVp3TG6zAxH28u/6eogbwrvZjBMWroSLBDkwkHKrqxB/RIdwkWDye4uqPlZIXWIaHtifY3L0/eO5Z0f2wg==
dependencies:
browser-resolve "^1.11.3"
chalk "^2.0.1"
realpath-native "^1.0.0"
-jest-runner@^23.6.0:
- version "23.6.0"
- resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-23.6.0.tgz#3894bd219ffc3f3cb94dc48a4170a2e6f23a5a38"
- integrity sha512-kw0+uj710dzSJKU6ygri851CObtCD9cN8aNkg8jWJf4ewFyEa6kwmiH/r/M1Ec5IL/6VFa0wnAk6w+gzUtjJzA==
+jest-runner@^24.1.0:
+ version "24.1.0"
+ resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-24.1.0.tgz#3686a2bb89ce62800da23d7fdc3da2c32792943b"
+ integrity sha512-CDGOkT3AIFl16BLL/OdbtYgYvbAprwJ+ExKuLZmGSCSldwsuU2dEGauqkpvd9nphVdAnJUcP12e/EIlnTX0QXg==
dependencies:
+ chalk "^2.4.2"
exit "^0.1.2"
- graceful-fs "^4.1.11"
- jest-config "^23.6.0"
- jest-docblock "^23.2.0"
- jest-haste-map "^23.6.0"
- jest-jasmine2 "^23.6.0"
- jest-leak-detector "^23.6.0"
- jest-message-util "^23.4.0"
- jest-runtime "^23.6.0"
- jest-util "^23.4.0"
- jest-worker "^23.2.0"
+ graceful-fs "^4.1.15"
+ jest-config "^24.1.0"
+ jest-docblock "^24.0.0"
+ jest-haste-map "^24.0.0"
+ jest-jasmine2 "^24.1.0"
+ jest-leak-detector "^24.0.0"
+ jest-message-util "^24.0.0"
+ jest-runtime "^24.1.0"
+ jest-util "^24.0.0"
+ jest-worker "^24.0.0"
source-map-support "^0.5.6"
throat "^4.0.0"
-jest-runtime@^23.6.0:
- version "23.6.0"
- resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-23.6.0.tgz#059e58c8ab445917cd0e0d84ac2ba68de8f23082"
- integrity sha512-ycnLTNPT2Gv+TRhnAYAQ0B3SryEXhhRj1kA6hBPSeZaNQkJ7GbZsxOLUkwg6YmvWGdX3BB3PYKFLDQCAE1zNOw==
+jest-runtime@^24.1.0:
+ version "24.1.0"
+ resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-24.1.0.tgz#7c157a2e776609e8cf552f956a5a19ec9c985214"
+ integrity sha512-59/BY6OCuTXxGeDhEMU7+N33dpMQyXq7MLK07cNSIY/QYt2QZgJ7Tjx+rykBI0skAoigFl0A5tmT8UdwX92YuQ==
dependencies:
- babel-core "^6.0.0"
- babel-plugin-istanbul "^4.1.6"
+ "@babel/core" "^7.1.0"
+ babel-plugin-istanbul "^5.1.0"
chalk "^2.0.1"
convert-source-map "^1.4.0"
exit "^0.1.2"
fast-json-stable-stringify "^2.0.0"
- graceful-fs "^4.1.11"
- jest-config "^23.6.0"
- jest-haste-map "^23.6.0"
- jest-message-util "^23.4.0"
- jest-regex-util "^23.3.0"
- jest-resolve "^23.6.0"
- jest-snapshot "^23.6.0"
- jest-util "^23.4.0"
- jest-validate "^23.6.0"
- micromatch "^2.3.11"
+ glob "^7.1.3"
+ graceful-fs "^4.1.15"
+ jest-config "^24.1.0"
+ jest-haste-map "^24.0.0"
+ jest-message-util "^24.0.0"
+ jest-regex-util "^24.0.0"
+ jest-resolve "^24.1.0"
+ jest-snapshot "^24.1.0"
+ jest-util "^24.0.0"
+ jest-validate "^24.0.0"
+ micromatch "^3.1.10"
realpath-native "^1.0.0"
- slash "^1.0.0"
- strip-bom "3.0.0"
- write-file-atomic "^2.1.0"
- yargs "^11.0.0"
-
-jest-serializer@^23.0.1:
- version "23.0.1"
- resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-23.0.1.tgz#a3776aeb311e90fe83fab9e533e85102bd164165"
- integrity sha1-o3dq6zEekP6D+rnlM+hRAr0WQWU=
-
-jest-snapshot@^23.6.0:
- version "23.6.0"
- resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-23.6.0.tgz#f9c2625d1b18acda01ec2d2b826c0ce58a5aa17a"
- integrity sha512-tM7/Bprftun6Cvj2Awh/ikS7zV3pVwjRYU2qNYS51VZHgaAMBs5l4o/69AiDHhQrj5+LA2Lq4VIvK7zYk/bswg==
- dependencies:
- babel-types "^6.0.0"
+ slash "^2.0.0"
+ strip-bom "^3.0.0"
+ write-file-atomic "2.4.1"
+ yargs "^12.0.2"
+
+jest-serializer@^24.0.0:
+ version "24.0.0"
+ resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-24.0.0.tgz#522c44a332cdd194d8c0531eb06a1ee5afb4256b"
+ integrity sha512-9FKxQyrFgHtx3ozU+1a8v938ILBE7S8Ko3uiAVjT8Yfi2o91j/fj81jacCQZ/Ihjiff/VsUCXVgQ+iF1XdImOw==
+
+jest-snapshot@^24.1.0:
+ version "24.1.0"
+ resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-24.1.0.tgz#85e22f810357aa5994ab61f236617dc2205f2f5b"
+ integrity sha512-th6TDfFqEmXvuViacU1ikD7xFb7lQsPn2rJl7OEmnfIVpnrx3QNY2t3PE88meeg0u/mQ0nkyvmC05PBqO4USFA==
+ dependencies:
+ "@babel/types" "^7.0.0"
chalk "^2.0.1"
- jest-diff "^23.6.0"
- jest-matcher-utils "^23.6.0"
- jest-message-util "^23.4.0"
- jest-resolve "^23.6.0"
+ jest-diff "^24.0.0"
+ jest-matcher-utils "^24.0.0"
+ jest-message-util "^24.0.0"
+ jest-resolve "^24.1.0"
mkdirp "^0.5.1"
natural-compare "^1.4.0"
- pretty-format "^23.6.0"
+ pretty-format "^24.0.0"
semver "^5.5.0"
-jest-util@^23.4.0:
- version "23.4.0"
- resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-23.4.0.tgz#4d063cb927baf0a23831ff61bec2cbbf49793561"
- integrity sha1-TQY8uSe68KI4Mf9hvsLLv0l5NWE=
+jest-transform-graphql@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/jest-transform-graphql/-/jest-transform-graphql-2.1.0.tgz#903cb66bb27bc2772fd3e5dd4f7e9b57230f5829"
+ integrity sha1-kDy2a7J7wncv0+XdT36bVyMPWCk=
+
+jest-util@^24.0.0:
+ version "24.0.0"
+ resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-24.0.0.tgz#fd38fcafd6dedbd0af2944d7a227c0d91b68f7d6"
+ integrity sha512-QxsALc4wguYS7cfjdQSOr5HTkmjzkHgmZvIDkcmPfl1ib8PNV8QUWLwbKefCudWS0PRKioV+VbQ0oCUPC691fQ==
dependencies:
- callsites "^2.0.0"
+ callsites "^3.0.0"
chalk "^2.0.1"
- graceful-fs "^4.1.11"
- is-ci "^1.0.10"
- jest-message-util "^23.4.0"
+ graceful-fs "^4.1.15"
+ is-ci "^2.0.0"
+ jest-message-util "^24.0.0"
mkdirp "^0.5.1"
- slash "^1.0.0"
+ slash "^2.0.0"
source-map "^0.6.0"
-jest-validate@^23.0.1, jest-validate@^23.6.0:
- version "23.6.0"
- resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-23.6.0.tgz#36761f99d1ed33fcd425b4e4c5595d62b6597474"
- integrity sha512-OFKapYxe72yz7agrDAWi8v2WL8GIfVqcbKRCLbRG9PAxtzF9b1SEDdTpytNDN12z2fJynoBwpMpvj2R39plI2A==
+jest-validate@^24.0.0:
+ version "24.0.0"
+ resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-24.0.0.tgz#aa8571a46983a6538328fef20406b4a496b6c020"
+ integrity sha512-vMrKrTOP4BBFIeOWsjpsDgVXATxCspC9S1gqvbJ3Tnn/b9ACsJmteYeVx9830UMV28Cob1RX55x96Qq3Tfad4g==
dependencies:
+ camelcase "^5.0.0"
chalk "^2.0.1"
- jest-get-type "^22.1.0"
+ jest-get-type "^24.0.0"
leven "^2.1.0"
- pretty-format "^23.6.0"
+ pretty-format "^24.0.0"
-jest-watcher@^23.4.0:
- version "23.4.0"
- resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-23.4.0.tgz#d2e28ce74f8dad6c6afc922b92cabef6ed05c91c"
- integrity sha1-0uKM50+NrWxq/JIrksq+9u0FyRw=
+jest-watcher@^24.0.0:
+ version "24.0.0"
+ resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-24.0.0.tgz#20d44244d10b0b7312410aefd256c1c1eef68890"
+ integrity sha512-GxkW2QrZ4YxmW1GUWER05McjVDunBlKMFfExu+VsGmXJmpej1saTEKvONdx5RJBlVdpPI5x6E3+EDQSIGgl53g==
dependencies:
ansi-escapes "^3.0.0"
chalk "^2.0.1"
+ jest-util "^24.0.0"
string-length "^2.0.0"
-jest-worker@^23.2.0:
- version "23.2.0"
- resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-23.2.0.tgz#faf706a8da36fae60eb26957257fa7b5d8ea02b9"
- integrity sha1-+vcGqNo2+uYOsmlXJX+ntdjqArk=
+jest-worker@^24.0.0:
+ version "24.0.0"
+ resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.0.0.tgz#3d3483b077bf04f412f47654a27bba7e947f8b6d"
+ integrity sha512-s64/OThpfQvoCeHG963MiEZOAAxu8kHsaL/rCMF7lpdzo7vgF0CtPml9hfguOMgykgH/eOm4jFP4ibfHLruytg==
dependencies:
merge-stream "^1.0.1"
+ supports-color "^6.1.0"
-jest@^23.6.0:
- version "23.6.0"
- resolved "https://registry.yarnpkg.com/jest/-/jest-23.6.0.tgz#ad5835e923ebf6e19e7a1d7529a432edfee7813d"
- integrity sha512-lWzcd+HSiqeuxyhG+EnZds6iO3Y3ZEnMrfZq/OTGvF/C+Z4fPMCdhWTGSAiO2Oym9rbEXfwddHhh6jqrTF3+Lw==
+jest@^24.1.0:
+ version "24.1.0"
+ resolved "https://registry.yarnpkg.com/jest/-/jest-24.1.0.tgz#b1e1135caefcf2397950ecf7f90e395fde866fd2"
+ integrity sha512-+q91L65kypqklvlRFfXfdzUKyngQLOcwGhXQaLmVHv+d09LkNXuBuGxlofTFW42XMzu3giIcChchTsCNUjQ78A==
dependencies:
- import-local "^1.0.0"
- jest-cli "^23.6.0"
+ import-local "^2.0.0"
+ jest-cli "^24.1.0"
jquery-ujs@1.2.2:
version "1.2.2"
@@ -6116,7 +6073,7 @@ jquery.waitforimages@^2.2.0:
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca"
integrity sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==
-js-beautify@^1.6.14, js-beautify@^1.8.8:
+js-beautify@^1.8.8:
version "1.8.9"
resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.8.9.tgz#08e3c05ead3ecfbd4f512c3895b1cda76c87d523"
integrity sha512-MwPmLywK9RSX0SPsUJjN7i+RQY9w/yC17Lbrq9ViEefpLRgqAR2BgrMN2AbifkUuhDV8tRauLhLda/9+bE0YQA==
@@ -6147,10 +6104,10 @@ js-tokens@^3.0.2:
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls=
-js-yaml@3.x, js-yaml@^3.12.0, js-yaml@^3.7.0:
- version "3.12.0"
- resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1"
- integrity sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==
+js-yaml@3.x, js-yaml@^3.12.0, js-yaml@^3.9.0:
+ version "3.12.1"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.1.tgz#295c8632a18a23e054cf5c9d3cecafe678167600"
+ integrity sha512-um46hB9wNOKlwkHgiuyEVAybXBjwFUV0Z/RaHJblRd9DXltue9FTYvzCr9ErQrK9Adz5MU4gHWVaNUfdmrC8qA==
dependencies:
argparse "^1.0.7"
esprima "^4.0.0"
@@ -6222,11 +6179,6 @@ jsdom@^11.5.1:
ws "^5.2.0"
xml-name-validator "^3.0.0"
-jsesc@^1.3.0:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b"
- integrity sha1-RsP+yMGJKxKwgz25vHYiF226s0s=
-
jsesc@^2.5.1:
version "2.5.1"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.1.tgz#e421a2a8e20d6b0819df28908f782526b96dd1fe"
@@ -6272,18 +6224,18 @@ json3@^3.3.2:
resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1"
integrity sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=
-json5@^0.5.0, json5@^0.5.1:
- version "0.5.1"
- resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
- integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=
-
-json5@^2.1.0:
+json5@2.x, json5@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.0.tgz#e7a0c62c48285c628d20a10b85c89bb807c32850"
integrity sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==
dependencies:
minimist "^1.2.0"
+json5@^0.5.0:
+ version "0.5.1"
+ resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
+ integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=
+
jsprim@^1.2.2:
version "1.4.1"
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
@@ -6449,10 +6401,15 @@ klaw@~2.0.0:
dependencies:
graceful-fs "^4.1.9"
-kleur@^2.0.1:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/kleur/-/kleur-2.0.2.tgz#b704f4944d95e255d038f0cb05fb8a602c55a300"
- integrity sha512-77XF9iTllATmG9lSlIv0qdQ2BQ/h9t0bJllHlbvsQ0zUWfU7Yi0S8L5JXzPZgkefIiajLmBJJ4BsMJmqcf7oxQ==
+kleur@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.2.tgz#83c7ec858a41098b613d5998a7b653962b504f68"
+ integrity sha512-3h7B2WRT5LNXOtQiAaWonilegHcPSf9nLVXlSTci8lu1dZUuui61+EsPEZqSVxY7rXYmB2DVKMQILxaO5WL61Q==
+
+known-css-properties@^0.11.0:
+ version "0.11.0"
+ resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.11.0.tgz#0da784f115ea77c76b81536d7052e90ee6c86a8a"
+ integrity sha512-bEZlJzXo5V/ApNNa5z375mJC6Nrz4vG43UgcSCrg2OHC+yuB6j0iDSrY7RQ/+PRofFB03wNIIt9iXIVLr4wc7w==
latest-version@^3.0.0:
version "3.1.0"
@@ -6519,17 +6476,6 @@ linkify-it@^2.0.0:
dependencies:
uc.micro "^1.0.1"
-load-json-file@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
- integrity sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=
- dependencies:
- graceful-fs "^4.1.2"
- parse-json "^2.2.0"
- pify "^2.0.0"
- pinkie-promise "^2.0.0"
- strip-bom "^2.0.0"
-
load-json-file@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8"
@@ -6640,12 +6586,12 @@ lodash.upperfirst@4.3.1:
resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce"
integrity sha1-E2Xt9DFIBIHvDRxolXpe2Z1J984=
-lodash@4.x, lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0, lodash@^4.5.0:
+lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0, lodash@^4.5.0:
version "4.17.11"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
-log-symbols@^2.1.0:
+log-symbols@^2.0.0, log-symbols@^2.1.0, log-symbols@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a"
integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==
@@ -6668,6 +6614,11 @@ loglevel@^1.4.1:
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.4.1.tgz#95b383f91a3c2756fd4ab093667e4309161f2bcd"
integrity sha1-lbOD+Ro8J1b9SrCTZn5DCRYfK80=
+longest-streak@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.2.tgz#2421b6ba939a443bb9ffebf596585a50b4c38e2e"
+ integrity sha512-TmYTeEYxiAmSVdpbnQDXGtvYOIRsCMg89CVZzwzc2o7GFL1CjoiRPjH5ec0NFAVlAx3fVof9dX/t6KKRAo2OWA==
+
loose-envify@^1.0.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
@@ -6675,6 +6626,14 @@ loose-envify@^1.0.0:
dependencies:
js-tokens "^3.0.0 || ^4.0.0"
+loud-rejection@^1.0.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f"
+ integrity sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=
+ dependencies:
+ currently-unhandled "^0.4.1"
+ signal-exit "^3.0.0"
+
lowercase-keys@1.0.0, lowercase-keys@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306"
@@ -6720,6 +6679,11 @@ make-dir@^1.0.0, make-dir@^1.3.0:
dependencies:
pify "^3.0.0"
+make-error@1.x:
+ version "1.3.5"
+ resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.5.tgz#efe4e81f6db28cadd605c70f29c831b58ef776c8"
+ integrity sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==
+
makeerror@1.0.x:
version "1.0.11"
resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c"
@@ -6739,6 +6703,16 @@ map-cache@^0.2.2:
resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf"
integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=
+map-obj@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d"
+ integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=
+
+map-obj@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-2.0.0.tgz#a65cd29087a92598b8791257a523e021222ac1f9"
+ integrity sha1-plzSkIepJZi4eRJXpSPgISIqwfk=
+
map-visit@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f"
@@ -6746,6 +6720,11 @@ map-visit@^1.0.0:
dependencies:
object-visit "^1.0.0"
+markdown-escapes@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.2.tgz#e639cbde7b99c841c0bacc8a07982873b46d2122"
+ integrity sha512-lbRZ2mE3Q9RtLjxZBZ9+IMl68DKIXaVAhwvwn9pmjnPLS0h/6kyBMgNhqi1xFJ/2yv6cSyv0jbiZavZv93JkkA==
+
markdown-it@^8.4.2:
version "8.4.2"
resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-8.4.2.tgz#386f98998dc15a37722aa7722084f4020bdd9b54"
@@ -6757,15 +6736,20 @@ markdown-it@^8.4.2:
mdurl "^1.0.1"
uc.micro "^1.0.5"
+markdown-table@^1.1.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-1.1.2.tgz#c78db948fa879903a41bce522e3b96f801c63786"
+ integrity sha512-NcWuJFHDA8V3wkDgR/j4+gZx+YQwstPgfQDV8ndUeWWzta3dnDTBxpVzqS9lkmJAuV5YX35lmyojl6HO5JXAgw==
+
marked@^0.3.12, marked@~0.3.6:
version "0.3.19"
resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.19.tgz#5d47f709c4c9fc3c216b6d46127280f40b39d790"
integrity sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==
-math-random@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.1.tgz#8b3aac588b8a66e4975e3cdea67f7bb329601fac"
- integrity sha1-izqsWIuKZuSXXjzepn97sylgH6w=
+mathml-tag-names@^2.0.1:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.0.tgz#490b70e062ee24636536e3d9481e333733d00f2c"
+ integrity sha512-3Zs9P/0zzwTob2pdgT0CHZuMbnSUSp8MB1bddfm+HDmnFWHGT4jvEZRf+2RuPoa+cjdn/z25SEt5gFTqdhvJAg==
md5.js@^1.3.4:
version "1.3.4"
@@ -6775,6 +6759,13 @@ md5.js@^1.3.4:
hash-base "^3.0.0"
inherits "^2.0.1"
+mdast-util-compact@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/mdast-util-compact/-/mdast-util-compact-1.0.2.tgz#c12ebe16fffc84573d3e19767726de226e95f649"
+ integrity sha512-d2WS98JSDVbpSsBfVvD9TaDMlqPRz7ohM/11G0rp5jOBb5q96RJ6YLszQ/09AAixyzh23FeIpCGqfaamEADtWg==
+ dependencies:
+ unist-util-visit "^1.1.0"
+
mdurl@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
@@ -6814,6 +6805,21 @@ memory-fs@^0.4.0, memory-fs@~0.4.1:
errno "^0.1.3"
readable-stream "^2.0.1"
+meow@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/meow/-/meow-5.0.0.tgz#dfc73d63a9afc714a5e371760eb5c88b91078aa4"
+ integrity sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig==
+ dependencies:
+ camelcase-keys "^4.0.0"
+ decamelize-keys "^1.0.0"
+ loud-rejection "^1.0.0"
+ minimist-options "^3.0.1"
+ normalize-package-data "^2.3.4"
+ read-pkg-up "^3.0.0"
+ redent "^2.0.0"
+ trim-newlines "^2.0.0"
+ yargs-parser "^10.0.0"
+
merge-descriptors@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
@@ -6833,6 +6839,11 @@ merge-stream@^1.0.1:
dependencies:
readable-stream "^2.0.1"
+merge2@^1.2.3:
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.2.3.tgz#7ee99dbd69bb6481689253f018488a1b902b0ed5"
+ integrity sha512-gdUU1Fwj5ep4kplwcmftruWofEFt6lfpkkr3h860CXbAB9c3hGb55EOL2ali0Td5oebvW0E1+3Sr+Ur7XfKpRA==
+
merge@^1.2.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145"
@@ -6857,26 +6868,7 @@ methods@~1.1.2:
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
-micromatch@^2.3.11:
- version "2.3.11"
- resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565"
- integrity sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=
- dependencies:
- arr-diff "^2.0.0"
- array-unique "^0.2.1"
- braces "^1.8.2"
- expand-brackets "^0.1.4"
- extglob "^0.3.1"
- filename-regex "^2.0.0"
- is-extglob "^1.0.0"
- is-glob "^2.0.1"
- kind-of "^3.0.2"
- normalize-path "^2.0.1"
- object.omit "^2.0.0"
- parse-glob "^3.0.4"
- regex-cache "^0.4.2"
-
-micromatch@^3.0.4, micromatch@^3.1.4, micromatch@^3.1.6, micromatch@^3.1.8, micromatch@^3.1.9:
+micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.6, micromatch@^3.1.8, micromatch@^3.1.9:
version "3.1.10"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==
@@ -6952,11 +6944,24 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
dependencies:
brace-expansion "^1.1.7"
+minimist-options@^3.0.1:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-3.0.2.tgz#fba4c8191339e13ecf4d61beb03f070103f3d954"
+ integrity sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==
+ dependencies:
+ arrify "^1.0.1"
+ is-plain-obj "^1.1.0"
+
minimist@0.0.8, minimist@~0.0.1:
version "0.0.8"
resolved "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
+minimist@1.1.x:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.1.3.tgz#3bedfd91a92d39016fcfaa1c681e8faa1a1efda8"
+ integrity sha1-O+39kaktOQFvz6ocaB6Pqhoe/ag=
+
minimist@1.2.0, minimist@^1.1.1, minimist@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
@@ -7001,7 +7006,7 @@ mixin-deep@^1.2.0:
for-in "^1.0.2"
is-extendable "^1.0.1"
-mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
+mkdirp@0.5.x, mkdirp@0.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
version "0.5.1"
resolved "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
@@ -7122,14 +7127,6 @@ nice-try@^1.0.4:
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.4.tgz#d93962f6c52f2c1558c0fbda6d512819f1efe1c4"
integrity sha512-2NpiFHqC87y/zFke0fC0spBXL3bBsoh/p5H1EFhshxjCR5+0g2d6BiXbUFz9v1sAcxsk2htp2eQnNIci2dIYcA==
-node-cache@^4.1.1:
- version "4.2.0"
- resolved "https://registry.yarnpkg.com/node-cache/-/node-cache-4.2.0.tgz#48ac796a874e762582692004a376d26dfa875811"
- integrity sha512-obRu6/f7S024ysheAjoYFEEBqqDWv4LOMNJEuO8vMeEw2AT4z+NCzO4hlc2lhI4vATzbCQv6kke9FVdx0RbCOw==
- dependencies:
- clone "2.x"
- lodash "4.x"
-
node-fetch@1.6.3:
version "1.6.3"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.6.3.tgz#dc234edd6489982d58e8f0db4f695029abcd8c04"
@@ -7177,6 +7174,11 @@ node-int64@^0.4.0:
util "^0.10.3"
vm-browserify "0.0.4"
+node-modules-regexp@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40"
+ integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=
+
node-notifier@^5.2.1:
version "5.3.0"
resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.3.0.tgz#c77a4a7b84038733d5fb351aafd8a268bfe19a01"
@@ -7248,17 +7250,17 @@ nopt@~1.0.10:
dependencies:
abbrev "1"
-normalize-package-data@^2.3.2:
- version "2.4.0"
- resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f"
- integrity sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==
+normalize-package-data@^2.3.2, normalize-package-data@^2.3.4:
+ version "2.5.0"
+ resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
+ integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==
dependencies:
hosted-git-info "^2.1.4"
- is-builtin-module "^1.0.0"
+ resolve "^1.10.0"
semver "2 || 3 || 4 || 5"
validate-npm-package-license "^3.0.1"
-normalize-path@^2.0.1, normalize-path@^2.1.1:
+normalize-path@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9"
integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=
@@ -7270,6 +7272,16 @@ normalize-path@^3.0.0:
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
+normalize-range@^0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
+ integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=
+
+normalize-selector@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/normalize-selector/-/normalize-selector-0.2.0.tgz#d0b145eb691189c63a78d201dc4fdb1293ef0c03"
+ integrity sha1-0LFF62kRicY6eNIB3E/bEpPvDAM=
+
normalize-url@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-2.0.1.tgz#835a9da1551fa26f70e92329069a23aa6574d7e6"
@@ -7314,6 +7326,11 @@ null-check@^1.0.0:
resolved "https://registry.yarnpkg.com/null-check/-/null-check-1.0.0.tgz#977dffd7176012b9ec30d2a39db5cf72a0439edd"
integrity sha1-l33/1xdgErnsMNKjnbXPcqBDnt0=
+num2fraction@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede"
+ integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=
+
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"
@@ -7329,7 +7346,7 @@ oauth-sign@~0.9.0:
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
-object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
+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"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
@@ -7388,14 +7405,6 @@ object.getownpropertydescriptors@^2.0.3:
define-properties "^1.1.2"
es-abstract "^1.5.1"
-object.omit@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa"
- integrity sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=
- dependencies:
- for-own "^0.1.4"
- is-extendable "^0.1.1"
-
object.pick@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747"
@@ -7466,10 +7475,10 @@ opn@^5.1.0:
dependencies:
is-wsl "^1.1.0"
-optimism@^0.6.6:
- version "0.6.8"
- resolved "https://registry.yarnpkg.com/optimism/-/optimism-0.6.8.tgz#0780b546da8cd0a72e5207e0c3706c990c8673a6"
- integrity sha512-bN5n1KCxSqwBDnmgDnzMtQTHdL+uea2HYFx1smvtE+w2AMl0Uy31g0aXnP/Nt85OINnMJPRpJyfRQLTCqn5Weg==
+optimism@^0.6.9:
+ version "0.6.9"
+ resolved "https://registry.yarnpkg.com/optimism/-/optimism-0.6.9.tgz#19258ff8b3be0cea29ac35f06bff818e026e30bb"
+ integrity sha512-xoQm2lvXbCA9Kd7SCx6y713Y7sZ6fUc5R6VYpoL5M6svKJbTuvtNopexK8sO8K4s0EOUYHuPN2+yAEsNyRggkQ==
dependencies:
immutable-tuple "^0.4.9"
@@ -7533,7 +7542,7 @@ os-locale@^3.0.0:
lcid "^2.0.0"
mem "^4.0.0"
-os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.2:
+os-tmpdir@^1.0.0, os-tmpdir@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
@@ -7556,6 +7565,13 @@ p-defer@^1.0.0:
resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c"
integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=
+p-each-series@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-1.0.0.tgz#930f3d12dd1f50e7434457a22cd6f04ac6ad7f71"
+ integrity sha1-kw89Et0fUOdDRFeiLNbwSsatf3E=
+ dependencies:
+ p-reduce "^1.0.0"
+
p-finally@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
@@ -7599,6 +7615,11 @@ p-map@^1.1.1:
resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.1.1.tgz#05f5e4ae97a068371bc2a5cc86bfbdbc19c4ae7a"
integrity sha1-BfXkrpegaDcbwqXMhr+9vBnErno=
+p-reduce@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-1.0.0.tgz#18c2b0dd936a4690a529f8231f58a0fdb6a47dfa"
+ integrity sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=
+
p-timeout@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-2.0.1.tgz#d8dd1979595d2dc0139e1fe46b8b646cb3cdf038"
@@ -7651,15 +7672,17 @@ parse-asn1@^5.0.0:
evp_bytestokey "^1.0.0"
pbkdf2 "^3.0.3"
-parse-glob@^3.0.4:
- version "3.0.4"
- resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c"
- integrity sha1-ssN2z7EfNVE7rdFz7wu246OIORw=
+parse-entities@^1.0.2, parse-entities@^1.1.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-1.2.0.tgz#9deac087661b2e36814153cb78d7e54a4c5fd6f4"
+ integrity sha512-XXtDdOPLSB0sHecbEapQi6/58U/ODj/KWfIXmmMCJF/eRn8laX6LZbOyioMoETOOJoWRW8/qTSl5VQkUIfKM5g==
dependencies:
- glob-base "^0.3.0"
- is-dotfile "^1.0.0"
- is-extglob "^1.0.0"
- is-glob "^2.0.0"
+ character-entities "^1.0.0"
+ character-entities-legacy "^1.0.0"
+ character-reference-invalid "^1.0.0"
+ is-alphanumerical "^1.0.0"
+ is-decimal "^1.0.0"
+ is-hexadecimal "^1.0.0"
parse-json@^2.2.0:
version "2.2.0"
@@ -7737,7 +7760,7 @@ path-exists@^3.0.0:
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=
-path-is-absolute@^1.0.0, path-is-absolute@^1.0.1:
+path-is-absolute@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
@@ -7752,7 +7775,7 @@ path-key@^2.0.0, path-key@^2.0.1:
resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=
-path-parse@^1.0.5:
+path-parse@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
@@ -7762,15 +7785,6 @@ path-to-regexp@0.1.7:
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
-path-type@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
- integrity sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=
- dependencies:
- graceful-fs "^4.1.2"
- pify "^2.0.0"
- pinkie-promise "^2.0.0"
-
path-type@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73"
@@ -7811,6 +7825,11 @@ pify@^3.0.0:
resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=
+pify@^4.0.0, pify@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231"
+ integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==
+
pikaday@^1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/pikaday/-/pikaday-1.6.1.tgz#b91bcb9b8539cedd8d6d08e4e7465e12095671b0"
@@ -7830,6 +7849,13 @@ pinkie@^2.0.0:
resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA=
+pirates@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87"
+ integrity sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==
+ dependencies:
+ node-modules-regexp "^1.0.0"
+
pixelmatch@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/pixelmatch/-/pixelmatch-4.0.2.tgz#8f47dcec5011b477b67db03c243bc1f3085e8854"
@@ -7844,13 +7870,6 @@ pkg-dir@^1.0.0:
dependencies:
find-up "^1.0.0"
-pkg-dir@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b"
- integrity sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=
- dependencies:
- find-up "^2.1.0"
-
pkg-dir@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3"
@@ -7897,6 +7916,40 @@ posix-character-classes@^0.1.0:
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=
+postcss-html@^0.36.0:
+ version "0.36.0"
+ resolved "https://registry.yarnpkg.com/postcss-html/-/postcss-html-0.36.0.tgz#b40913f94eaacc2453fd30a1327ad6ee1f88b204"
+ integrity sha512-HeiOxGcuwID0AFsNAL0ox3mW6MHH5cstWN1Z3Y+n6H+g12ih7LHdYxWwEA/QmrebctLjo79xz9ouK3MroHwOJw==
+ dependencies:
+ htmlparser2 "^3.10.0"
+
+postcss-jsx@^0.36.0:
+ version "0.36.0"
+ resolved "https://registry.yarnpkg.com/postcss-jsx/-/postcss-jsx-0.36.0.tgz#b7685ed3d070a175ef0aa48f83d9015bd772c82d"
+ integrity sha512-/lWOSXSX5jlITCKFkuYU2WLFdrncZmjSVyNpHAunEgirZXLwI8RjU556e3Uz4mv0WVHnJA9d3JWb36lK9Yx99g==
+ dependencies:
+ "@babel/core" ">=7.1.0"
+
+postcss-less@^3.1.0:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/postcss-less/-/postcss-less-3.1.2.tgz#fb67e7ba351dbdf69de3c52eebd1184c52bfaea6"
+ integrity sha512-66ZBVo1JGkQ7r13M97xcHcyarWpgg21RaqIZWZXHE3XOtb5+ywK1uZWeY1DYkYRkIX/l8Hvxnx9iSKB68nFr+w==
+ dependencies:
+ postcss "^7.0.14"
+
+postcss-markdown@^0.36.0:
+ version "0.36.0"
+ resolved "https://registry.yarnpkg.com/postcss-markdown/-/postcss-markdown-0.36.0.tgz#7f22849ae0e3db18820b7b0d5e7833f13a447560"
+ integrity sha512-rl7fs1r/LNSB2bWRhyZ+lM/0bwKv9fhl38/06gF6mKMo/NPnp55+K1dSTosSVjFZc0e1ppBlu+WT91ba0PMBfQ==
+ dependencies:
+ remark "^10.0.1"
+ unist-util-find-all-after "^1.0.2"
+
+postcss-media-query-parser@^0.2.3:
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz#27b39c6f4d94f81b1a73b8f76351c609e5cef244"
+ integrity sha1-J7Ocb02U+Bsac7j3Y1HGCeXO8kQ=
+
postcss-modules-extract-imports@^1.2.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.1.tgz#dc87e34148ec7eab5f791f7cd5849833375b741a"
@@ -7928,7 +7981,44 @@ postcss-modules-values@^1.3.0:
icss-replace-symbols "^1.1.0"
postcss "^6.0.1"
-postcss-selector-parser@^3.1.1:
+postcss-reporter@^6.0.0:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/postcss-reporter/-/postcss-reporter-6.0.1.tgz#7c055120060a97c8837b4e48215661aafb74245f"
+ integrity sha512-LpmQjfRWyabc+fRygxZjpRxfhRf9u/fdlKf4VHG4TSPbV2XNsuISzYW1KL+1aQzx53CAppa1bKG4APIB/DOXXw==
+ dependencies:
+ chalk "^2.4.1"
+ lodash "^4.17.11"
+ log-symbols "^2.2.0"
+ postcss "^7.0.7"
+
+postcss-resolve-nested-selector@^0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz#29ccbc7c37dedfac304e9fff0bf1596b3f6a0e4e"
+ integrity sha1-Kcy8fDfe36wwTp//C/FZaz9qDk4=
+
+postcss-safe-parser@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/postcss-safe-parser/-/postcss-safe-parser-4.0.1.tgz#8756d9e4c36fdce2c72b091bbc8ca176ab1fcdea"
+ integrity sha512-xZsFA3uX8MO3yAda03QrG3/Eg1LN3EPfjjf07vke/46HERLZyHrTsQ9E1r1w1W//fWEhtYNndo2hQplN2cVpCQ==
+ dependencies:
+ postcss "^7.0.0"
+
+postcss-sass@^0.3.5:
+ version "0.3.5"
+ resolved "https://registry.yarnpkg.com/postcss-sass/-/postcss-sass-0.3.5.tgz#6d3e39f101a53d2efa091f953493116d32beb68c"
+ integrity sha512-B5z2Kob4xBxFjcufFnhQ2HqJQ2y/Zs/ic5EZbCywCkxKd756Q40cIQ/veRDwSrw1BF6+4wUgmpm0sBASqVi65A==
+ dependencies:
+ gonzales-pe "^4.2.3"
+ postcss "^7.0.1"
+
+postcss-scss@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-2.0.0.tgz#248b0a28af77ea7b32b1011aba0f738bda27dea1"
+ integrity sha512-um9zdGKaDZirMm+kZFKKVsnKPF7zF7qBAtIfTSnZXD1jZ0JNZIxdB6TxQOjCnlSzLRInVl2v3YdBh/M881C4ug==
+ dependencies:
+ postcss "^7.0.0"
+
+postcss-selector-parser@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz#4f875f4afb0c96573d5cf4d74011aee250a7e865"
integrity sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=
@@ -7937,12 +8027,26 @@ postcss-selector-parser@^3.1.1:
indexes-of "^1.0.1"
uniq "^1.0.1"
-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"
- integrity sha1-h/OPnxj3dKSrTIojL1xc6IcqnRU=
+postcss-selector-parser@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz#249044356697b33b64f1a8f7c80922dddee7195c"
+ integrity sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==
+ dependencies:
+ cssesc "^2.0.0"
+ indexes-of "^1.0.1"
+ uniq "^1.0.1"
+
+postcss-syntax@^0.36.2:
+ version "0.36.2"
+ resolved "https://registry.yarnpkg.com/postcss-syntax/-/postcss-syntax-0.36.2.tgz#f08578c7d95834574e5593a82dfbfa8afae3b51c"
+ integrity sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w==
-postcss@^6.0.1, postcss@^6.0.14, postcss@^6.0.20, postcss@^6.0.23:
+postcss-value-parser@^3.3.0, postcss-value-parser@^3.3.1:
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281"
+ integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==
+
+postcss@^6.0.1, postcss@^6.0.14, postcss@^6.0.23:
version "6.0.23"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324"
integrity sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==
@@ -7951,6 +8055,15 @@ postcss@^6.0.1, postcss@^6.0.14, postcss@^6.0.20, postcss@^6.0.23:
source-map "^0.6.1"
supports-color "^5.4.0"
+postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.13, postcss@^7.0.14, postcss@^7.0.2, postcss@^7.0.7:
+ version "7.0.14"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.14.tgz#4527ed6b1ca0d82c53ce5ec1a2041c2346bbd6e5"
+ integrity sha512-NsbD6XUUMZvBxtQAJuWDJeeC4QFsmWsfozWxCJPWf3M55K9iu2iMDaKqyoOdTJ1R4usBXuxlVFAIo8rZPQD4Bg==
+ dependencies:
+ chalk "^2.4.2"
+ source-map "^0.6.1"
+ supports-color "^6.1.0"
+
prelude-ls@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
@@ -7966,27 +8079,22 @@ prepend-http@^2.0.0:
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
-preserve@^0.2.0:
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
- integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=
-
-prettier@1.13.7:
- version "1.13.7"
- resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.13.7.tgz#850f3b8af784a49a6ea2d2eaa7ed1428a34b7281"
- integrity sha512-KIU72UmYPGk4MujZGYMFwinB7lOf2LsDNGSOC8ufevsrPLISrZbNJlWstRi3m0AMuszbH+EFSQ/r6w56RSPK6w==
-
prettier@1.16.1:
version "1.16.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.1.tgz#534c2c9d7853f8845e5e078384e71973bd74089f"
integrity sha512-XXUITwIkGb3CPJ2hforHah/zTINRyie5006Jd2HKy2qz7snEJXl0KLfsJZW/wst9g6R2rFvqba3VpNYdu1hDcA==
-pretty-format@^23.6.0:
- version "23.6.0"
- resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-23.6.0.tgz#5eaac8eeb6b33b987b7fe6097ea6a8a146ab5760"
- integrity sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==
+prettier@1.16.3:
+ version "1.16.3"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.3.tgz#8c62168453badef702f34b45b6ee899574a6a65d"
+ integrity sha512-kn/GU6SMRYPxUakNXhpP0EedT/KmaPzr0H5lIsDogrykbaxOpOfAFfk5XA7DZrJyMAv1wlMV3CPcZruGXVVUZw==
+
+pretty-format@^24.0.0:
+ version "24.0.0"
+ resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.0.0.tgz#cb6599fd73ac088e37ed682f61291e4678f48591"
+ integrity sha512-LszZaKG665djUcqg5ZQq+XzezHLKrxsA86ZABTozp+oNhkdqa+tG2dX4qa6ERl5c/sRDrAa3lHmwnvKoP+OG/g==
dependencies:
- ansi-regex "^3.0.0"
+ ansi-regex "^4.0.0"
ansi-styles "^3.2.0"
prismjs@^1.6.0:
@@ -7996,7 +8104,7 @@ prismjs@^1.6.0:
optionalDependencies:
clipboard "^1.5.5"
-private@^0.1.6, private@^0.1.8:
+private@^0.1.6:
version "0.1.8"
resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==
@@ -8026,13 +8134,13 @@ promise-inflight@^1.0.1:
resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM=
-prompts@^0.1.9:
- version "0.1.14"
- resolved "https://registry.yarnpkg.com/prompts/-/prompts-0.1.14.tgz#a8e15c612c5c9ec8f8111847df3337c9cbd443b2"
- integrity sha512-rxkyiE9YH6zAz/rZpywySLKkpaj0NMVyNw1qhsubdbjjSgcayjTShDreZGlFMcGSu5sab3bAKPfFk78PB90+8w==
+prompts@^2.0.1:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.0.3.tgz#c5ccb324010b2e8f74752aadceeb57134c1d2522"
+ integrity sha512-H8oWEoRZpybm6NV4to9/1limhttEo13xK62pNvn2JzY0MA03p7s0OjtmhXyon3uJmxiJJVSuUwEJFFssI3eBiQ==
dependencies:
- kleur "^2.0.1"
- sisteransi "^0.1.1"
+ kleur "^3.0.2"
+ sisteransi "^1.0.0"
prosemirror-commands@^1.0.7:
version "1.0.7"
@@ -8273,14 +8381,10 @@ querystringify@^2.0.0:
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.0.tgz#7ded8dfbf7879dcc60d0a644ac6754b283ad17ef"
integrity sha512-sluvZZ1YiTLD5jsqZcDmFyV2EwToyXZBfpoVOmktMmW+VEnhgakFHnasVph65fOjGPTWN0Nw3+XQaSeMayr0kg==
-randomatic@^3.0.0:
- version "3.1.1"
- resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.1.tgz#b776efc59375984e36c537b2f51a1f0aff0da1ed"
- integrity sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==
- dependencies:
- is-number "^4.0.0"
- kind-of "^6.0.0"
- math-random "^1.0.1"
+quick-lru@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-1.1.0.tgz#4360b17c61136ad38078397ff11416e186dcfbb8"
+ integrity sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
version "2.0.6"
@@ -8342,14 +8446,6 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.2.7:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
-read-pkg-up@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02"
- integrity sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=
- dependencies:
- find-up "^1.0.0"
- read-pkg "^1.0.0"
-
read-pkg-up@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be"
@@ -8358,6 +8454,14 @@ read-pkg-up@^2.0.0:
find-up "^2.0.0"
read-pkg "^2.0.0"
+read-pkg-up@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07"
+ integrity sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=
+ dependencies:
+ find-up "^2.0.0"
+ read-pkg "^3.0.0"
+
read-pkg-up@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-4.0.0.tgz#1b221c6088ba7799601c808f91161c66e58f8978"
@@ -8366,15 +8470,6 @@ read-pkg-up@^4.0.0:
find-up "^3.0.0"
read-pkg "^3.0.0"
-read-pkg@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28"
- integrity sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=
- dependencies:
- load-json-file "^1.0.0"
- normalize-package-data "^2.3.2"
- path-type "^1.0.0"
-
read-pkg@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8"
@@ -8437,13 +8532,21 @@ readdirp@^2.0.0:
readable-stream "^2.0.2"
set-immediate-shim "^1.0.1"
-realpath-native@^1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.0.2.tgz#cd51ce089b513b45cf9b1516c82989b51ccc6560"
- integrity sha512-+S3zTvVt9yTntFrBpm7TQmQ3tzpCrnA1a/y+3cUHAc9ZR6aIjG0WNLR+Rj79QpJktY+VeW/TQtFlQ1bzsehI8g==
+realpath-native@^1.0.0, realpath-native@^1.0.2:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.1.0.tgz#2003294fea23fb0672f2476ebe22fcf498a2d65c"
+ integrity sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==
dependencies:
util.promisify "^1.0.0"
+redent@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/redent/-/redent-2.0.0.tgz#c1b2007b42d57eb1389079b3c8333639d5e1ccaa"
+ integrity sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=
+ dependencies:
+ indent-string "^3.0.0"
+ strip-indent "^2.0.0"
+
regenerate-unicode-properties@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-7.0.0.tgz#107405afcc4a190ec5ed450ecaa00ed0cafa7a4c"
@@ -8473,13 +8576,6 @@ regenerator-transform@^0.13.3:
dependencies:
private "^0.1.6"
-regex-cache@^0.4.2:
- version "0.4.4"
- resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd"
- integrity sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==
- dependencies:
- is-equal-shallow "^0.1.3"
-
regex-not@^1.0.0, regex-not@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c"
@@ -8562,6 +8658,56 @@ regjsparser@^0.3.0:
dependencies:
jsesc "~0.5.0"
+remark-parse@^6.0.0:
+ version "6.0.3"
+ resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-6.0.3.tgz#c99131052809da482108413f87b0ee7f52180a3a"
+ integrity sha512-QbDXWN4HfKTUC0hHa4teU463KclLAnwpn/FBn87j9cKYJWWawbiLgMfP2Q4XwhxxuuuOxHlw+pSN0OKuJwyVvg==
+ dependencies:
+ collapse-white-space "^1.0.2"
+ is-alphabetical "^1.0.0"
+ is-decimal "^1.0.0"
+ is-whitespace-character "^1.0.0"
+ is-word-character "^1.0.0"
+ markdown-escapes "^1.0.0"
+ parse-entities "^1.1.0"
+ repeat-string "^1.5.4"
+ state-toggle "^1.0.0"
+ trim "0.0.1"
+ trim-trailing-lines "^1.0.0"
+ unherit "^1.0.4"
+ unist-util-remove-position "^1.0.0"
+ vfile-location "^2.0.0"
+ xtend "^4.0.1"
+
+remark-stringify@^6.0.0:
+ version "6.0.4"
+ resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-6.0.4.tgz#16ac229d4d1593249018663c7bddf28aafc4e088"
+ integrity sha512-eRWGdEPMVudijE/psbIDNcnJLRVx3xhfuEsTDGgH4GsFF91dVhw5nhmnBppafJ7+NWINW6C7ZwWbi30ImJzqWg==
+ dependencies:
+ ccount "^1.0.0"
+ is-alphanumeric "^1.0.0"
+ is-decimal "^1.0.0"
+ is-whitespace-character "^1.0.0"
+ longest-streak "^2.0.1"
+ markdown-escapes "^1.0.0"
+ markdown-table "^1.1.0"
+ mdast-util-compact "^1.0.0"
+ parse-entities "^1.0.2"
+ repeat-string "^1.5.4"
+ state-toggle "^1.0.0"
+ stringify-entities "^1.0.1"
+ unherit "^1.0.4"
+ xtend "^4.0.1"
+
+remark@^10.0.1:
+ version "10.0.1"
+ resolved "https://registry.yarnpkg.com/remark/-/remark-10.0.1.tgz#3058076dc41781bf505d8978c291485fe47667df"
+ integrity sha512-E6lMuoLIy2TyiokHprMjcWNJ5UxfGQjaMSMhV+f4idM625UjjK4j798+gPs5mfjzDE6vL0oFKVeZM6gZVSVrzQ==
+ dependencies:
+ remark-parse "^6.0.0"
+ remark-stringify "^6.0.0"
+ unified "^7.0.0"
+
remove-trailing-separator@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
@@ -8577,17 +8723,15 @@ repeat-string@^0.2.2:
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-0.2.2.tgz#c7a8d3236068362059a7e4651fc6884e8b1fb4ae"
integrity sha1-x6jTI2BoNiBZp+RlH8aITosftK4=
-repeat-string@^1.5.2, repeat-string@^1.6.1:
+repeat-string@^1.5.4, repeat-string@^1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc=
-repeating@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda"
- integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=
- dependencies:
- is-finite "^1.0.0"
+replace-ext@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb"
+ integrity sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=
request-promise-core@1.1.1:
version "1.1.1"
@@ -8686,6 +8830,11 @@ resolve-from@^3.0.0:
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748"
integrity sha1-six699nWiBvItuZTM17rywoYh0g=
+resolve-from@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
+ integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
+
resolve-url@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
@@ -8696,12 +8845,12 @@ resolve@1.1.7, resolve@1.1.x:
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=
-resolve@^1.3.2, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.6.0:
- version "1.8.1"
- resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26"
- integrity sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==
+resolve@1.x, resolve@^1.10.0, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.6.0:
+ version "1.10.0"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.0.tgz#3bdaaeaf45cc07f375656dfd2e54ed0810b101ba"
+ integrity sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==
dependencies:
- path-parse "^1.0.5"
+ path-parse "^1.0.6"
responselike@1.0.2:
version "1.0.2"
@@ -8728,12 +8877,12 @@ rfdc@^1.1.2:
resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.1.2.tgz#e6e72d74f5dc39de8f538f65e00c36c18018e349"
integrity sha512-92ktAgvZhBzYTIK0Mja9uen5q5J3NRVMoDkJL2VMwq6SXjVCgqvQeVP2XAaUY6HT+XpQYeLSjb3UoitBryKmdA==
-rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.2:
- version "2.6.2"
- resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36"
- integrity sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==
+rimraf@2.6.3, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.2:
+ version "2.6.3"
+ resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
+ integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==
dependencies:
- glob "^7.0.5"
+ glob "^7.1.3"
ripemd160@^2.0.0, ripemd160@^2.0.1:
version "2.0.1"
@@ -8806,14 +8955,15 @@ safe-regex@^1.1.0:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
-sane@^2.0.0:
- version "2.5.2"
- resolved "https://registry.yarnpkg.com/sane/-/sane-2.5.2.tgz#b4dc1861c21b427e929507a3e751e2a2cb8ab3fa"
- integrity sha1-tNwYYcIbQn6SlQej51HiosuKs/o=
+sane@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/sane/-/sane-3.1.0.tgz#995193b7dc1445ef1fe41ddfca2faf9f111854c6"
+ integrity sha512-G5GClRRxT1cELXfdAq7UKtUsv8q/ZC5k8lQGmjEm4HcAl3HzBy68iglyNCmw4+0tiXPCBZntslHlRhbnsSws+Q==
dependencies:
anymatch "^2.0.0"
capture-exit "^1.2.0"
exec-sh "^0.2.0"
+ execa "^1.0.0"
fb-watchman "^2.0.0"
micromatch "^3.1.4"
minimist "^1.1.1"
@@ -8895,7 +9045,7 @@ semver-diff@^2.0.0:
dependencies:
semver "^5.0.3"
-"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0:
+"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0:
version "5.6.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==
@@ -9042,20 +9192,20 @@ signal-exit@^3.0.0, signal-exit@^3.0.2:
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=
-sisteransi@^0.1.1:
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-0.1.1.tgz#5431447d5f7d1675aac667ccd0b865a4994cb3ce"
- integrity sha512-PmGOd02bM9YO5ifxpw36nrNMBTptEtfRl4qUYl9SndkolplkrZZOW7PGHjrZL53QvMVj9nQ+TKqUnRsw4tJa4g==
-
-slash@^1.0.0:
+sisteransi@^1.0.0:
version "1.0.0"
- resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
- integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=
+ resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.0.tgz#77d9622ff909080f1c19e5f4a1df0c1b0a27b88c"
+ integrity sha512-N+z4pHB4AmUv0SjveWRd6q1Nj5w62m5jodv+GD8lvmbY/83T/rpbJGZOnK5T149OldDj4Db07BSv9xY4K6NTPQ==
-slice-ansi@2.0.0:
+slash@^2.0.0:
version "2.0.0"
- resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.0.0.tgz#5373bdb8559b45676e8541c66916cdd6251612e7"
- integrity sha512-4j2WTWjp3GsZ+AOagyzVbzp4vWGtZ0hEZ/gDY/uTvm6MTxUfTUIsnMIFb1bn8o0RuXiqUw15H1bue8f22Vw2oQ==
+ resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44"
+ integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==
+
+slice-ansi@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636"
+ integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==
dependencies:
ansi-styles "^3.2.0"
astral-regex "^1.0.0"
@@ -9199,13 +9349,6 @@ source-map-resolve@^0.5.0, source-map-resolve@^0.5.2:
source-map-url "^0.4.0"
urix "^0.1.0"
-source-map-support@^0.4.15:
- version "0.4.18"
- resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f"
- integrity sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==
- dependencies:
- source-map "^0.5.6"
-
source-map-support@^0.5.6, source-map-support@~0.5.6:
version "0.5.9"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.9.tgz#41bc953b2534267ea2d605bccfa7bfa3111ced5f"
@@ -9224,7 +9367,7 @@ source-map@0.5.0:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.0.tgz#0fe96503ac86a5adb5de63f4e412ae4872cdbe86"
integrity sha1-D+llA6yGpa213mP05BKuSHLNvoY=
-source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7:
+source-map@^0.5.0, source-map@^0.5.6:
version "0.5.7"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
@@ -9281,6 +9424,11 @@ spdy@^4.0.0:
select-hose "^2.0.0"
spdy-transport "^3.0.0"
+specificity@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/specificity/-/specificity-0.4.1.tgz#aab5e645012db08ba182e151165738d00887b019"
+ integrity sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg==
+
split-string@^3.0.1, split-string@^3.0.2:
version "3.1.0"
resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2"
@@ -9333,6 +9481,11 @@ stack-utils@^1.0.1:
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8"
integrity sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==
+state-toggle@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.1.tgz#c3cb0974f40a6a0f8e905b96789eb41afa1cde3a"
+ integrity sha512-Qe8QntFrrpWTnHwvwj2FZTgv+PKIsp0B9VxLzLLbSpPXWOgRgc5LVj/aTiSfK1RqIeF9jeC1UeOH8Q8y60A7og==
+
static-extend@^0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
@@ -9433,6 +9586,15 @@ string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^4.0.0"
+string-width@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.0.0.tgz#5a1690a57cc78211fffd9bf24bbe24d090604eb1"
+ integrity sha512-rr8CUxBbvOZDUvc5lNIJ+OC1nPVpz+Siw9VBtUjB9b6jZehZLFt0JMCZzShFHIsI8cbhm0EsNIfWJMFV3cu3Ew==
+ dependencies:
+ emoji-regex "^7.0.1"
+ is-fullwidth-code-point "^2.0.0"
+ strip-ansi "^5.0.0"
+
string_decoder@^1.0.0, string_decoder@^1.1.1, string_decoder@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
@@ -9445,6 +9607,16 @@ string_decoder@~0.10.x:
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=
+stringify-entities@^1.0.1:
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-1.3.2.tgz#a98417e5471fd227b3e45d3db1861c11caf668f7"
+ integrity sha512-nrBAQClJAPN2p+uGCVJRPIPakKeKWZ9GtBCmormE7pWOSlHat7+x5A8gx85M7HM5Dt0BP3pP5RhVW77WdbJJ3A==
+ dependencies:
+ character-entities-html4 "^1.0.0"
+ character-entities-legacy "^1.0.0"
+ is-alphanumerical "^1.0.0"
+ is-hexadecimal "^1.0.0"
+
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"
@@ -9459,18 +9631,18 @@ strip-ansi@^4.0.0:
dependencies:
ansi-regex "^3.0.0"
-strip-bom@3.0.0, strip-bom@^3.0.0:
+strip-ansi@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.0.0.tgz#f78f68b5d0866c20b2c9b8c61b5298508dc8756f"
+ integrity sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow==
+ dependencies:
+ ansi-regex "^4.0.0"
+
+strip-bom@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=
-strip-bom@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e"
- integrity sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=
- dependencies:
- is-utf8 "^0.2.0"
-
strip-css-comments@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/strip-css-comments/-/strip-css-comments-3.0.0.tgz#7a5625eff8a2b226cf8947a11254da96e13dae89"
@@ -9483,7 +9655,12 @@ strip-eof@^1.0.0:
resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=
-strip-json-comments@^2.0.0, strip-json-comments@^2.0.1, strip-json-comments@~2.0.1:
+strip-indent@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68"
+ integrity sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=
+
+strip-json-comments@^2.0.1, strip-json-comments@~2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
@@ -9496,12 +9673,93 @@ style-loader@^0.23.1:
loader-utils "^1.1.0"
schema-utils "^1.0.0"
+style-search@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/style-search/-/style-search-0.1.0.tgz#7958c793e47e32e07d2b5cafe5c0bf8e12e77902"
+ integrity sha1-eVjHk+R+MuB9K1yv5cC/jhLneQI=
+
+stylelint-config-recommended@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-2.1.0.tgz#f526d5c771c6811186d9eaedbed02195fee30858"
+ integrity sha512-ajMbivOD7JxdsnlS5945KYhvt7L/HwN6YeYF2BH6kE4UCLJR0YvXMf+2j7nQpJyYLZx9uZzU5G1ZOSBiWAc6yA==
+
+stylelint-scss@^3.5.3:
+ version "3.5.3"
+ resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-3.5.3.tgz#e158b3061eeec26d7f6088f346998a797432f3c8"
+ integrity sha512-QESQUOY1ldU5tlJTTM3Megz/QtJ39S58ByjZ7dZobGDq9qMjy5jbC7PDUasrv/T7pB1UbpPojpxX9K1OR7IPEg==
+ dependencies:
+ lodash "^4.17.11"
+ postcss-media-query-parser "^0.2.3"
+ postcss-resolve-nested-selector "^0.1.1"
+ postcss-selector-parser "^5.0.0"
+ postcss-value-parser "^3.3.1"
+
+stylelint@^9.10.1:
+ version "9.10.1"
+ resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-9.10.1.tgz#5f0ee3701461dff1d68284e1386efe8f0677a75d"
+ integrity sha512-9UiHxZhOAHEgeQ7oLGwrwoDR8vclBKlSX7r4fH0iuu0SfPwFaLkb1c7Q2j1cqg9P7IDXeAV2TvQML/fRQzGBBQ==
+ dependencies:
+ autoprefixer "^9.0.0"
+ balanced-match "^1.0.0"
+ chalk "^2.4.1"
+ cosmiconfig "^5.0.0"
+ debug "^4.0.0"
+ execall "^1.0.0"
+ file-entry-cache "^4.0.0"
+ get-stdin "^6.0.0"
+ global-modules "^2.0.0"
+ globby "^9.0.0"
+ globjoin "^0.1.4"
+ html-tags "^2.0.0"
+ ignore "^5.0.4"
+ import-lazy "^3.1.0"
+ imurmurhash "^0.1.4"
+ known-css-properties "^0.11.0"
+ leven "^2.1.0"
+ lodash "^4.17.4"
+ log-symbols "^2.0.0"
+ mathml-tag-names "^2.0.1"
+ meow "^5.0.0"
+ micromatch "^3.1.10"
+ normalize-selector "^0.2.0"
+ pify "^4.0.0"
+ postcss "^7.0.13"
+ postcss-html "^0.36.0"
+ postcss-jsx "^0.36.0"
+ postcss-less "^3.1.0"
+ postcss-markdown "^0.36.0"
+ postcss-media-query-parser "^0.2.3"
+ postcss-reporter "^6.0.0"
+ postcss-resolve-nested-selector "^0.1.1"
+ postcss-safe-parser "^4.0.0"
+ postcss-sass "^0.3.5"
+ postcss-scss "^2.0.0"
+ postcss-selector-parser "^3.1.0"
+ postcss-syntax "^0.36.2"
+ postcss-value-parser "^3.3.0"
+ resolve-from "^4.0.0"
+ signal-exit "^3.0.2"
+ slash "^2.0.0"
+ specificity "^0.4.1"
+ string-width "^3.0.0"
+ style-search "^0.1.0"
+ sugarss "^2.0.0"
+ svg-tags "^1.0.0"
+ table "^5.0.0"
+
+sugarss@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/sugarss/-/sugarss-2.0.0.tgz#ddd76e0124b297d40bf3cca31c8b22ecb43bc61d"
+ integrity sha512-WfxjozUk0UVA4jm+U1d736AUpzSrNsQcIbyOkoE364GrtWmIrFdk5lksEupgWMD4VaT/0kVx1dobpiDumSgmJQ==
+ dependencies:
+ postcss "^7.0.2"
+
supports-color@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=
-supports-color@^3.1.0, supports-color@^3.1.2:
+supports-color@^3.1.0:
version "3.2.3"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6"
integrity sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=
@@ -9515,6 +9773,18 @@ supports-color@^5.1.0, supports-color@^5.2.0, supports-color@^5.3.0, supports-co
dependencies:
has-flag "^3.0.0"
+supports-color@^6.0.0, supports-color@^6.1.0:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
+ integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==
+ dependencies:
+ has-flag "^3.0.0"
+
+svg-tags@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764"
+ integrity sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q=
+
svg4everybody@2.1.9:
version "2.1.9"
resolved "https://registry.yarnpkg.com/svg4everybody/-/svg4everybody-2.1.9.tgz#5bd9f6defc133859a044646d4743fabc28db7e2d"
@@ -9530,15 +9800,15 @@ symbol-tree@^3.2.2:
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6"
integrity sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=
-table@^5.0.2:
- version "5.1.1"
- resolved "https://registry.yarnpkg.com/table/-/table-5.1.1.tgz#92030192f1b7b51b6eeab23ed416862e47b70837"
- integrity sha512-NUjapYb/qd4PeFW03HnAuOJ7OMcBkJlqeClWxeNlQ0lXGSb52oZXGzkO0/I0ARegQ2eUT1g2VDJH0eUxDRcHmw==
+table@^5.0.0, table@^5.0.2:
+ version "5.2.3"
+ resolved "https://registry.yarnpkg.com/table/-/table-5.2.3.tgz#cde0cc6eb06751c009efab27e8c820ca5b67b7f2"
+ integrity sha512-N2RsDAMvDLvYwFcwbPyF3VmVSSkuF+G1e+8inhBLtHpvwXGw4QRPEZhihQNeEN0i1up6/f6ObCJXNdlRG3YVyQ==
dependencies:
- ajv "^6.6.1"
+ ajv "^6.9.1"
lodash "^4.17.11"
- slice-ansi "2.0.0"
- string-width "^2.1.1"
+ slice-ansi "^2.1.0"
+ string-width "^3.0.0"
taffydb@2.6.2:
version "2.6.2"
@@ -9598,17 +9868,6 @@ terser@^3.8.1:
source-map "~0.6.1"
source-map-support "~0.5.6"
-test-exclude@^4.2.1:
- version "4.2.3"
- resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.2.3.tgz#a9a5e64474e4398339245a0a769ad7c2f4a97c20"
- integrity sha512-SYbXgY64PT+4GAL2ocI3HwPa4Q4TBKm0cwAVeKOt/Aoc0gSpNRjJX8w0pA1LMKZ3LBmd8pYBqApFNQLII9kavA==
- dependencies:
- arrify "^1.0.1"
- micromatch "^2.3.11"
- object-assign "^4.1.0"
- read-pkg-up "^1.0.1"
- require-main-filename "^1.0.1"
-
test-exclude@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-5.0.0.tgz#cdce7cece785e0e829cd5c2b27baf18bc583cfb7"
@@ -9769,11 +10028,6 @@ to-arraybuffer@^1.0.0:
resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=
-to-fast-properties@^1.0.3:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47"
- integrity sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=
-
to-fast-properties@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
@@ -9831,27 +10085,59 @@ tr46@^1.0.1:
dependencies:
punycode "^2.1.0"
+trim-newlines@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-2.0.0.tgz#b403d0b91be50c331dfc4b82eeceb22c3de16d20"
+ integrity sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=
+
trim-right@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=
+trim-trailing-lines@^1.0.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-1.1.1.tgz#e0ec0810fd3c3f1730516b45f49083caaf2774d9"
+ integrity sha512-bWLv9BbWbbd7mlqqs2oQYnLD/U/ZqeJeJwbO0FG2zA1aTq+HTvxfHNKFa/HGCVyJpDiioUYaBhfiT6rgk+l4mg==
+
+trim@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd"
+ integrity sha1-WFhUf2spB1fulczMZm+1AITEYN0=
+
+trough@^1.0.0:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.3.tgz#e29bd1614c6458d44869fc28b255ab7857ef7c24"
+ integrity sha512-fwkLWH+DimvA4YCy+/nvJd61nWQQ2liO/nF/RjkTpiOGi+zxZzVkhb1mvbHIIW4b/8nDsYI8uTmAlc0nNkRMOw==
+
tryer@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.0.tgz#027b69fa823225e551cace3ef03b11f6ab37c1d7"
integrity sha1-Antp+oIyJeVRys4+8DsR9qs3wdc=
-tsconfig@^7.0.0:
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/tsconfig/-/tsconfig-7.0.0.tgz#84538875a4dc216e5c4a5432b3a4dec3d54e91b7"
- integrity sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==
- dependencies:
- "@types/strip-bom" "^3.0.0"
- "@types/strip-json-comments" "0.0.30"
- strip-bom "^3.0.0"
- strip-json-comments "^2.0.0"
-
-tslib@^1.9.0:
+ts-invariant@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.2.1.tgz#3d587f9d6e3bded97bf9ec17951dd9814d5a9d3f"
+ integrity sha512-Z/JSxzVmhTo50I+LKagEISFJW3pvPCqsMWLamCTX8Kr3N5aMrnGOqcflbe5hLUzwjvgPfnLzQtHZv0yWQ+FIHg==
+ dependencies:
+ tslib "^1.9.3"
+
+ts-jest@24.0.0, ts-jest@^23.10.5:
+ version "24.0.0"
+ resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-24.0.0.tgz#3f26bf2ec1fa584863a5a9c29bd8717d549efbf6"
+ integrity sha512-o8BO3TkMREpAATaFTrXkovMsCpBl2z4NDBoLJuWZcJJj1ijI49UnvDMfVpj+iogn/Jl8Pbhuei5nc/Ti+frEHw==
+ dependencies:
+ bs-logger "0.x"
+ buffer-from "1.x"
+ fast-json-stable-stringify "2.x"
+ json5 "2.x"
+ make-error "1.x"
+ mkdirp "0.x"
+ resolve "1.x"
+ semver "^5.5"
+ yargs-parser "10.x"
+
+tslib@^1.9.0, tslib@^1.9.3:
version "1.9.3"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==
@@ -9945,6 +10231,14 @@ underscore@~1.8.3:
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022"
integrity sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=
+unherit@^1.0.4:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.1.tgz#132748da3e88eab767e08fabfbb89c5e9d28628c"
+ integrity sha512-+XZuV691Cn4zHsK0vkKYwBEwB74T3IZIcxrgn2E4rKwTfFyI1zCh7X7grwh9Re08fdPlarIdyWgI8aVB3F5A5g==
+ dependencies:
+ inherits "^2.0.1"
+ xtend "^4.0.1"
+
unicode-canonical-property-names-ecmascript@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818"
@@ -9968,6 +10262,20 @@ unicode-property-aliases-ecmascript@^1.0.4:
resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.4.tgz#5a533f31b4317ea76f17d807fa0d116546111dd0"
integrity sha512-2WSLa6OdYd2ng8oqiGIWnJqyFArvhn+5vgx5GTxMbUYjCYKUcuKS62YLFF0R/BDGlB1yzXjQOLtPAfHsgirEpg==
+unified@^7.0.0:
+ version "7.1.0"
+ resolved "https://registry.yarnpkg.com/unified/-/unified-7.1.0.tgz#5032f1c1ee3364bd09da12e27fdd4a7553c7be13"
+ integrity sha512-lbk82UOIGuCEsZhPj8rNAkXSDXd6p0QLzIuSsCdxrqnqU56St4eyOB+AlXsVgVeRmetPTYydIuvFfpDIed8mqw==
+ dependencies:
+ "@types/unist" "^2.0.0"
+ "@types/vfile" "^3.0.0"
+ bail "^1.0.0"
+ extend "^3.0.0"
+ is-plain-obj "^1.1.0"
+ trough "^1.0.0"
+ vfile "^3.0.0"
+ x-is-string "^0.1.0"
+
union-value@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4"
@@ -10004,6 +10312,44 @@ unique-string@^1.0.0:
dependencies:
crypto-random-string "^1.0.0"
+unist-util-find-all-after@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/unist-util-find-all-after/-/unist-util-find-all-after-1.0.2.tgz#9be49cfbae5ca1566b27536670a92836bf2f8d6d"
+ integrity sha512-nDl79mKpffXojLpCimVXnxhlH/jjaTnDuScznU9J4jjsaUtBdDbxmlc109XtcqxY4SDO0SwzngsxxW8DIISt1w==
+ dependencies:
+ unist-util-is "^2.0.0"
+
+unist-util-is@^2.0.0, unist-util-is@^2.1.2:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-2.1.2.tgz#1193fa8f2bfbbb82150633f3a8d2eb9a1c1d55db"
+ integrity sha512-YkXBK/H9raAmG7KXck+UUpnKiNmUdB+aBGrknfQ4EreE1banuzrKABx3jP6Z5Z3fMSPMQQmeXBlKpCbMwBkxVw==
+
+unist-util-remove-position@^1.0.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-1.1.2.tgz#86b5dad104d0bbfbeb1db5f5c92f3570575c12cb"
+ integrity sha512-XxoNOBvq1WXRKXxgnSYbtCF76TJrRoe5++pD4cCBsssSiWSnPEktyFrFLE8LTk3JW5mt9hB0Sk5zn4x/JeWY7Q==
+ dependencies:
+ unist-util-visit "^1.1.0"
+
+unist-util-stringify-position@^1.0.0, unist-util-stringify-position@^1.1.1:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-1.1.2.tgz#3f37fcf351279dcbca7480ab5889bb8a832ee1c6"
+ integrity sha512-pNCVrk64LZv1kElr0N1wPiHEUoXNVFERp+mlTg/s9R5Lwg87f9bM/3sQB99w+N9D/qnM9ar3+AKDBwo/gm/iQQ==
+
+unist-util-visit-parents@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-2.0.1.tgz#63fffc8929027bee04bfef7d2cce474f71cb6217"
+ integrity sha512-6B0UTiMfdWql4cQ03gDTCSns+64Zkfo2OCbK31Ov0uMizEz+CJeAp0cgZVb5Fhmcd7Bct2iRNywejT0orpbqUA==
+ dependencies:
+ unist-util-is "^2.1.2"
+
+unist-util-visit@^1.1.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-1.4.0.tgz#1cb763647186dc26f5e1df5db6bd1e48b3cc2fb1"
+ integrity sha512-FiGu34ziNsZA3ZUteZxSFaczIjGmksfSgdKqBfOejrrfzyUy5b7YrlzT1Bcvi+djkYDituJDy2XB7tGTeBieKw==
+ dependencies:
+ unist-util-visit-parents "^2.0.0"
+
unpipe@1.0.0, unpipe@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
@@ -10178,6 +10524,28 @@ verror@1.10.0:
core-util-is "1.0.2"
extsprintf "^1.2.0"
+vfile-location@^2.0.0:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-2.0.4.tgz#2a5e7297dd0d9e2da4381464d04acc6b834d3e55"
+ integrity sha512-KRL5uXQPoUKu+NGvQVL4XLORw45W62v4U4gxJ3vRlDfI9QsT4ZN1PNXn/zQpKUulqGDpYuT0XDfp5q9O87/y/w==
+
+vfile-message@^1.0.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-1.1.1.tgz#5833ae078a1dfa2d96e9647886cd32993ab313e1"
+ integrity sha512-1WmsopSGhWt5laNir+633LszXvZ+Z/lxveBf6yhGsqnQIhlhzooZae7zV6YVM1Sdkw68dtAW3ow0pOdPANugvA==
+ dependencies:
+ unist-util-stringify-position "^1.1.1"
+
+vfile@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/vfile/-/vfile-3.0.1.tgz#47331d2abe3282424f4a4bb6acd20a44c4121803"
+ integrity sha512-y7Y3gH9BsUSdD4KzHsuMaCzRjglXN0W2EcMf0gpvu6+SbsGhMje7xDc8AEoeXy6mIwCKMI6BkjMsRjzQbhMEjQ==
+ dependencies:
+ is-buffer "^2.0.0"
+ replace-ext "1.0.0"
+ unist-util-stringify-position "^1.0.0"
+ vfile-message "^1.0.0"
+
visibilityjs@^1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/visibilityjs/-/visibilityjs-1.2.4.tgz#bff8663da62c8c10ad4ee5ae6a1ae6fac4259d63"
@@ -10195,10 +10563,10 @@ void-elements@^2.0.0:
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"
integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=
-vue-apollo@^3.0.0-beta.25:
- version "3.0.0-beta.25"
- resolved "https://registry.yarnpkg.com/vue-apollo/-/vue-apollo-3.0.0-beta.25.tgz#05a9a699b2ba6103639e9bd6c3bb88ca04c4b637"
- integrity sha512-M7/l3h0NlFvaZ/s/wrtRiOt3xXMbaNNuteGaCY+U5D0ABrQqvCgy5mayIZHurQxbloluNkbCt18wRKAgJTAuKA==
+vue-apollo@^3.0.0-beta.28:
+ version "3.0.0-beta.28"
+ resolved "https://registry.yarnpkg.com/vue-apollo/-/vue-apollo-3.0.0-beta.28.tgz#be6a3a1504be2096cbfb23996537e2fc95c8c239"
+ integrity sha512-ceCc1xTyxpNtiSeJMQgSkfgJue6pnv+TIvp75CwZlwMxRtNZjITj4MGvBWFwnoIEhVrUAw45ff/5udgJ8z9sdQ==
dependencies:
chalk "^2.4.1"
throttle-debounce "^2.0.0"
@@ -10225,21 +10593,17 @@ vue-hot-reload-api@^2.3.0:
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.0.tgz#97976142405d13d8efae154749e88c4e358cf926"
integrity sha512-2j/t+wIbyVMP5NvctQoSUvLkYKoWAAk2QlQiilrM2a6/ulzFgdcLUJfTvs4XQ/3eZhHiBmmEojbjmM4AzZj8JA==
-vue-jest@^3.0.2:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/vue-jest/-/vue-jest-3.0.2.tgz#c64bf5da9abd0d3ee16071217037696d14b0689c"
- integrity sha512-5XIQ1xQFW0ZnWxHWM7adVA2IqbDsdw1vhgZfGFX4oWd75J38KIS3YT41PtiE7lpMLmNM6+VJ0uprT2mhHjUgkA==
+vue-jest@^4.0.0-beta.2:
+ version "4.0.0-beta.2"
+ resolved "https://registry.yarnpkg.com/vue-jest/-/vue-jest-4.0.0-beta.2.tgz#f2120ea9d24224aad3a100c2010b0760d47ee6fe"
+ integrity sha512-SywBIciuIfqsCb8Eb9UQ02s06+NV8Ry8KnbyhAfnvnyFFulIuh7ujtga9eJYq720nCS4Hz4TpVtS4pD1ZbUILQ==
dependencies:
- babel-plugin-transform-es2015-modules-commonjs "^6.26.0"
+ "@babel/plugin-transform-modules-commonjs" "^7.2.0"
+ "@vue/component-compiler-utils" "^2.4.0"
chalk "^2.1.0"
extract-from-css "^0.4.4"
- find-babel-config "^1.1.0"
- js-beautify "^1.6.14"
- node-cache "^4.1.1"
- object-assign "^4.1.1"
source-map "^0.5.6"
- tsconfig "^7.0.0"
- vue-template-es2015-compiler "^1.6.0"
+ ts-jest "^23.10.5"
vue-loader@^15.4.2:
version "15.4.2"
@@ -10280,10 +10644,10 @@ vue-template-compiler@^2.5.0, vue-template-compiler@^2.5.21:
de-indent "^1.0.2"
he "^1.1.0"
-vue-template-es2015-compiler@^1.6.0:
- version "1.6.0"
- resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.6.0.tgz#dc42697133302ce3017524356a6c61b7b69b4a18"
- integrity sha512-x3LV3wdmmERhVCYy3quqA57NJW7F3i6faas++pJQWtknWT+n7k30F4TVdHvCLn48peTJFRvCpxs3UuFPqgeELg==
+vue-template-es2015-compiler@^1.9.0:
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
+ integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
vue-virtual-scroll-list@^1.2.5:
version "1.2.5"
@@ -10529,7 +10893,7 @@ which-module@^2.0.0:
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
-which@^1.1.1, which@^1.2.1, which@^1.2.12, which@^1.2.14, which@^1.2.9, which@^1.3.0:
+which@^1.1.1, which@^1.2.1, which@^1.2.12, which@^1.2.14, which@^1.2.9, which@^1.3.0, which@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
@@ -10589,15 +10953,22 @@ wrappy@1:
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
-write-file-atomic@^2.0.0, write-file-atomic@^2.1.0:
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.3.0.tgz#1ff61575c2e2a4e8e510d6fa4e243cce183999ab"
- integrity sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA==
+write-file-atomic@2.4.1, write-file-atomic@^2.0.0:
+ version "2.4.1"
+ resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.1.tgz#d0b05463c188ae804396fd5ab2a370062af87529"
+ integrity sha512-TGHFeZEZMnv+gBFRfjAcxL5bPHrsGKtnb4qsFAws7/vlh+QfwAaySIw4AXP9ZskTTh5GWu3FLuJhsWVdiJPGvg==
dependencies:
graceful-fs "^4.1.11"
imurmurhash "^0.1.4"
signal-exit "^3.0.2"
+write@1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3"
+ integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==
+ dependencies:
+ mkdirp "^0.5.1"
+
write@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757"
@@ -10628,6 +10999,11 @@ ws@~3.3.1:
safe-buffer "~5.1.0"
ultron "~1.1.0"
+x-is-string@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/x-is-string/-/x-is-string-0.1.0.tgz#474b50865af3a49a9c4657f05acd145458f77d82"
+ integrity sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI=
+
xdg-basedir@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"
@@ -10698,7 +11074,7 @@ yallist@^3.0.0, yallist@^3.0.2:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9"
integrity sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=
-yargs-parser@^10.1.0:
+yargs-parser@10.x, yargs-parser@^10.0.0, yargs-parser@^10.1.0:
version "10.1.0"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-10.1.0.tgz#7202265b89f7e9e9f2e5765e0fe735a905edbaa8"
integrity sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==
@@ -10720,13 +11096,6 @@ yargs-parser@^8.1.0:
dependencies:
camelcase "^4.1.0"
-yargs-parser@^9.0.2:
- version "9.0.2"
- resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-9.0.2.tgz#9ccf6a43460fe4ed40a9bb68f48d43b8a68cc077"
- integrity sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=
- dependencies:
- camelcase "^4.1.0"
-
yargs@12.0.2:
version "12.0.2"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.2.tgz#fe58234369392af33ecbef53819171eff0f5aadc"
@@ -10763,25 +11132,7 @@ yargs@^10.0.3:
y18n "^3.2.1"
yargs-parser "^8.1.0"
-yargs@^11.0.0:
- version "11.1.0"
- resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.1.0.tgz#90b869934ed6e871115ea2ff58b03f4724ed2d77"
- integrity sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==
- dependencies:
- cliui "^4.0.0"
- decamelize "^1.1.1"
- find-up "^2.1.0"
- get-caller-file "^1.0.1"
- os-locale "^2.0.0"
- require-directory "^2.1.1"
- require-main-filename "^1.0.1"
- set-blocking "^2.0.0"
- string-width "^2.0.0"
- which-module "^2.0.0"
- y18n "^3.2.1"
- yargs-parser "^9.0.2"
-
-yargs@^12.0.4:
+yargs@^12.0.2, yargs@^12.0.4:
version "12.0.5"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13"
integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==