summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Knox <psimyn@gmail.com>2018-03-19 19:18:58 +1100
committerSimon Knox <psimyn@gmail.com>2018-03-19 19:18:58 +1100
commitfaf3110228c96b25760cf810afd5bc88405a26d4 (patch)
tree5d0d27d9eb4bc3b80a7ede343ae61e55d5743b30
parente42fbe41528704d8f9dc0493582889c9e691d9e6 (diff)
parent6cfea81e6dc51e9627e2b2f64e29834e0e9bfc60 (diff)
downloadgitlab-ce-43928-merge-request-comment-gets-cleared-once-the-previous-comment-is-submitted.tar.gz
Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into 43928-merge-request-comment-gets-cleared-once-the-previous-comment-is-submitted43928-merge-request-comment-gets-cleared-once-the-previous-comment-is-submitted
-rw-r--r--.eslintrc28
-rw-r--r--.gitignore4
-rw-r--r--.gitlab-ci.yml2
-rw-r--r--.prettierrc4
-rw-r--r--.rubocop.yml72
-rw-r--r--CHANGELOG.md35
-rw-r--r--CONTRIBUTING.md18
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_PAGES_VERSION2
-rw-r--r--Gemfile27
-rw-r--r--Gemfile.lock115
-rw-r--r--PROCESS.md5
-rw-r--r--app/assets/images/icons.json1
-rw-r--r--app/assets/images/icons.svg1
-rw-r--r--app/assets/images/illustrations/cluster_popover.svg1
-rw-r--r--app/assets/images/illustrations/clusters_empty.svg1
-rw-r--r--app/assets/images/illustrations/convdev/convdev_no_data.svg1
-rw-r--r--app/assets/images/illustrations/convdev/convdev_no_index.svg1
-rw-r--r--app/assets/images/illustrations/convdev/convdev_overview.svg1
-rw-r--r--app/assets/images/illustrations/convdev/i2p_step_1.svg1
-rw-r--r--app/assets/images/illustrations/convdev/i2p_step_10.svg1
-rw-r--r--app/assets/images/illustrations/convdev/i2p_step_2.svg1
-rw-r--r--app/assets/images/illustrations/convdev/i2p_step_3.svg1
-rw-r--r--app/assets/images/illustrations/convdev/i2p_step_4.svg1
-rw-r--r--app/assets/images/illustrations/convdev/i2p_step_5.svg1
-rw-r--r--app/assets/images/illustrations/convdev/i2p_step_6.svg1
-rw-r--r--app/assets/images/illustrations/convdev/i2p_step_7.svg1
-rw-r--r--app/assets/images/illustrations/convdev/i2p_step_8.svg1
-rw-r--r--app/assets/images/illustrations/convdev/i2p_step_9.svg1
-rw-r--r--app/assets/images/illustrations/epics.svg1
-rw-r--r--app/assets/images/illustrations/gitlab_logo.svg1
-rw-r--r--app/assets/images/illustrations/image_comment_light_cursor.svg1
-rw-r--r--app/assets/images/illustrations/image_comment_light_cursor@2x.svg1
-rw-r--r--app/assets/images/illustrations/issues.svg1
-rw-r--r--app/assets/images/illustrations/job_not_triggered.svg1
-rw-r--r--app/assets/images/illustrations/labels.svg1
-rw-r--r--app/assets/images/illustrations/logos/go_logo.svg1
-rw-r--r--app/assets/images/illustrations/logos/mattermost_logo.svg1
-rw-r--r--app/assets/images/illustrations/manual_action.svg1
-rw-r--r--app/assets/images/illustrations/merge_request_changes_empty.svg1
-rw-r--r--app/assets/images/illustrations/merge_requests.svg1
-rw-r--r--app/assets/images/illustrations/monitoring/getting_started.svg1
-rw-r--r--app/assets/images/illustrations/monitoring/loading.svg1
-rw-r--r--app/assets/images/illustrations/monitoring/unable_to_connect.svg1
-rw-r--r--app/assets/images/illustrations/multi-editor_all_changes_committed_empty.svg1
-rw-r--r--app/assets/images/illustrations/multi-editor_no_changes_empty.svg1
-rw-r--r--app/assets/images/illustrations/multi-editor_no_staged_files_empty.svg1
-rw-r--r--app/assets/images/illustrations/multi_file_editor_empty.svg1
-rw-r--r--app/assets/images/illustrations/no_commits.svg1
-rw-r--r--app/assets/images/illustrations/pending_job_empty.svg1
-rw-r--r--app/assets/images/illustrations/pipelines_empty.svg1
-rw-r--r--app/assets/images/illustrations/pipelines_failed.svg1
-rw-r--r--app/assets/images/illustrations/pipelines_pending.svg1
-rw-r--r--app/assets/images/illustrations/priority_labels.svg1
-rw-r--r--app/assets/images/illustrations/service_desk_callout.svg1
-rw-r--r--app/assets/images/illustrations/service_desk_empty.svg1
-rw-r--r--app/assets/images/illustrations/slack_logo.svg1
-rw-r--r--app/assets/images/illustrations/todos_all_done.svg1
-rw-r--r--app/assets/images/illustrations/todos_empty.svg1
-rw-r--r--app/assets/images/illustrations/welcome/add_new_group.svg1
-rw-r--r--app/assets/images/illustrations/welcome/add_new_project.svg1
-rw-r--r--app/assets/images/illustrations/welcome/add_new_user.svg1
-rw-r--r--app/assets/images/illustrations/welcome/configure_server.svg1
-rw-r--r--app/assets/images/illustrations/welcome/ee_trial.svg1
-rw-r--r--app/assets/images/illustrations/welcome/globe.svg1
-rw-r--r--app/assets/images/illustrations/welcome/lightbulb.svg1
-rw-r--r--app/assets/images/illustrations/wiki-fro-logged-out-users.svg1
-rw-r--r--app/assets/images/illustrations/wiki_login_empty.svg1
-rw-r--r--app/assets/images/illustrations/wiki_logout_empty.svg1
-rw-r--r--app/assets/javascripts/activities.js1
-rw-r--r--app/assets/javascripts/ajax_loading_spinner.js2
-rw-r--r--app/assets/javascripts/api.js1
-rw-r--r--app/assets/javascripts/awards_handler.js2
-rw-r--r--app/assets/javascripts/behaviors/copy_as_gfm.js1
-rw-r--r--app/assets/javascripts/behaviors/copy_to_clipboard.js1
-rw-r--r--app/assets/javascripts/behaviors/details_behavior.js1
-rw-r--r--app/assets/javascripts/behaviors/quick_submit.js3
-rw-r--r--app/assets/javascripts/behaviors/requires_input.js1
-rw-r--r--app/assets/javascripts/behaviors/toggler_behavior.js4
-rw-r--r--app/assets/javascripts/blob/blob_file_dropzone.js2
-rw-r--r--app/assets/javascripts/blob/blob_fork_suggestion.js2
-rw-r--r--app/assets/javascripts/blob/file_template_mediator.js2
-rw-r--r--app/assets/javascripts/blob/file_template_selector.js2
-rw-r--r--app/assets/javascripts/blob/template_selector.js4
-rw-r--r--app/assets/javascripts/blob/viewer/index.js1
-rw-r--r--app/assets/javascripts/blob_edit/blob_bundle.js2
-rw-r--r--app/assets/javascripts/blob_edit/edit_blob.js1
-rw-r--r--app/assets/javascripts/boards/components/board.js2
-rw-r--r--app/assets/javascripts/boards/components/board_delete.js1
-rw-r--r--app/assets/javascripts/boards/components/board_new_issue.vue1
-rw-r--r--app/assets/javascripts/boards/components/board_sidebar.js3
-rw-r--r--app/assets/javascripts/boards/components/issue_card_inner.js1
-rw-r--r--app/assets/javascripts/boards/components/new_list_dropdown.js5
-rw-r--r--app/assets/javascripts/boards/components/project_select.vue2
-rw-r--r--app/assets/javascripts/boards/index.js1
-rw-r--r--app/assets/javascripts/boards/mixins/sortable_default_options.js1
-rw-r--r--app/assets/javascripts/boards/stores/boards_store.js2
-rw-r--r--app/assets/javascripts/branches/branches_delete_modal.js2
-rw-r--r--app/assets/javascripts/breadcrumb.js2
-rw-r--r--app/assets/javascripts/build_artifacts.js2
-rw-r--r--app/assets/javascripts/build_variables.js4
-rw-r--r--app/assets/javascripts/ci_variable_list/native_form_variable_list.js1
-rw-r--r--app/assets/javascripts/commit/image_file.js2
-rw-r--r--app/assets/javascripts/commit_merge_requests.js1
-rw-r--r--app/assets/javascripts/commits.js1
-rw-r--r--app/assets/javascripts/compare.js2
-rw-r--r--app/assets/javascripts/compare_autocomplete.js2
-rw-r--r--app/assets/javascripts/confirm_danger_modal.js2
-rw-r--r--app/assets/javascripts/contextual_sidebar.js1
-rw-r--r--app/assets/javascripts/create_label.js2
-rw-r--r--app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js1
-rw-r--r--app/assets/javascripts/diff.js1
-rw-r--r--app/assets/javascripts/diff_notes/components/comment_resolve_btn.js1
-rw-r--r--app/assets/javascripts/diff_notes/components/diff_note_avatars.js1
-rw-r--r--app/assets/javascripts/diff_notes/components/jump_to_discussion.js1
-rw-r--r--app/assets/javascripts/diff_notes/components/resolve_btn.js1
-rw-r--r--app/assets/javascripts/diff_notes/diff_notes_bundle.js1
-rw-r--r--app/assets/javascripts/diff_notes/models/discussion.js1
-rw-r--r--app/assets/javascripts/dispatcher.js2
-rw-r--r--app/assets/javascripts/dropzone_input.js1
-rw-r--r--app/assets/javascripts/due_date_select.js1
-rw-r--r--app/assets/javascripts/environments/components/environment_stop.vue2
-rw-r--r--app/assets/javascripts/experimental_flags.js1
-rw-r--r--app/assets/javascripts/feature_highlight/feature_highlight.js1
-rw-r--r--app/assets/javascripts/feature_highlight/feature_highlight_helper.js1
-rw-r--r--app/assets/javascripts/filterable_list.js1
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js4
-rw-r--r--app/assets/javascripts/gl_dropdown.js4
-rw-r--r--app/assets/javascripts/gl_field_error.js2
-rw-r--r--app/assets/javascripts/gl_field_errors.js1
-rw-r--r--app/assets/javascripts/gl_form.js1
-rw-r--r--app/assets/javascripts/gpg_badges.js1
-rw-r--r--app/assets/javascripts/group.js2
-rw-r--r--app/assets/javascripts/group_avatar.js2
-rw-r--r--app/assets/javascripts/group_label_subscription.js1
-rw-r--r--app/assets/javascripts/groups/components/app.vue1
-rw-r--r--app/assets/javascripts/groups/groups_filterable_list.js1
-rw-r--r--app/assets/javascripts/groups/transfer_dropdown.js2
-rw-r--r--app/assets/javascripts/groups_select.js1
-rw-r--r--app/assets/javascripts/header.js1
-rw-r--r--app/assets/javascripts/help/help.js3
-rw-r--r--app/assets/javascripts/how_to_merge.js2
-rw-r--r--app/assets/javascripts/image_diff/image_diff.js1
-rw-r--r--app/assets/javascripts/importer_status.js1
-rw-r--r--app/assets/javascripts/init_changes_dropdown.js1
-rw-r--r--app/assets/javascripts/init_labels.js1
-rw-r--r--app/assets/javascripts/integrations/integration_settings_form.js1
-rw-r--r--app/assets/javascripts/issuable/auto_width_dropdown_select.js2
-rw-r--r--app/assets/javascripts/issuable_bulk_update_actions.js2
-rw-r--r--app/assets/javascripts/issuable_bulk_update_sidebar.js1
-rw-r--r--app/assets/javascripts/issuable_context.js1
-rw-r--r--app/assets/javascripts/issuable_form.js1
-rw-r--r--app/assets/javascripts/issuable_index.js1
-rw-r--r--app/assets/javascripts/issue.js2
-rw-r--r--app/assets/javascripts/issue_show/components/description.vue1
-rw-r--r--app/assets/javascripts/issue_show/components/fields/description_template.vue1
-rw-r--r--app/assets/javascripts/issue_status_select.js2
-rw-r--r--app/assets/javascripts/job.js1
-rw-r--r--app/assets/javascripts/label_manager.js2
-rw-r--r--app/assets/javascripts/labels.js2
-rw-r--r--app/assets/javascripts/labels_select.js2
-rw-r--r--app/assets/javascripts/layout_nav.js1
-rw-r--r--app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js2
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js4
-rw-r--r--app/assets/javascripts/lib/utils/csrf.js2
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js1
-rw-r--r--app/assets/javascripts/lib/utils/text_markdown.js2
-rw-r--r--app/assets/javascripts/lib/utils/text_utility.js14
-rw-r--r--app/assets/javascripts/line_highlighter.js2
-rw-r--r--app/assets/javascripts/logo.js2
-rw-r--r--app/assets/javascripts/main.js1
-rw-r--r--app/assets/javascripts/member_expiration_date.js1
-rw-r--r--app/assets/javascripts/members.js2
-rw-r--r--app/assets/javascripts/merge_conflicts/merge_conflict_store.js1
-rw-r--r--app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js1
-rw-r--r--app/assets/javascripts/merge_request.js2
-rw-r--r--app/assets/javascripts/merge_request_tabs.js6
-rw-r--r--app/assets/javascripts/milestone.js1
-rw-r--r--app/assets/javascripts/milestone_select.js2
-rw-r--r--app/assets/javascripts/mini_pipeline_graph_dropdown.js2
-rw-r--r--app/assets/javascripts/monitoring/components/graph.vue1
-rw-r--r--app/assets/javascripts/mr_notes/index.js9
-rw-r--r--app/assets/javascripts/namespace_select.js2
-rw-r--r--app/assets/javascripts/network/branch_graph.js1
-rw-r--r--app/assets/javascripts/new_branch_form.js2
-rw-r--r--app/assets/javascripts/notes.js726
-rw-r--r--app/assets/javascripts/notes/components/comment_form.vue550
-rw-r--r--app/assets/javascripts/notes/components/diff_file_header.vue34
-rw-r--r--app/assets/javascripts/notes/components/diff_with_note.vue95
-rw-r--r--app/assets/javascripts/notes/components/discussion_counter.vue114
-rw-r--r--app/assets/javascripts/notes/components/discussion_locked_widget.vue18
-rw-r--r--app/assets/javascripts/notes/components/note_actions.vue226
-rw-r--r--app/assets/javascripts/notes/components/note_attachment.vue16
-rw-r--r--app/assets/javascripts/notes/components/note_awards_list.vue349
-rw-r--r--app/assets/javascripts/notes/components/note_body.vue133
-rw-r--r--app/assets/javascripts/notes/components/note_edited_text.vue50
-rw-r--r--app/assets/javascripts/notes/components/note_form.vue224
-rw-r--r--app/assets/javascripts/notes/components/note_header.vue110
-rw-r--r--app/assets/javascripts/notes/components/note_signed_out_widget.vue24
-rw-r--r--app/assets/javascripts/notes/components/noteable_discussion.vue360
-rw-r--r--app/assets/javascripts/notes/components/noteable_note.vue259
-rw-r--r--app/assets/javascripts/notes/components/notes_app.vue281
-rw-r--r--app/assets/javascripts/notes/index.js68
-rw-r--r--app/assets/javascripts/notes/mixins/autosave.js7
-rw-r--r--app/assets/javascripts/notes/mixins/resolvable.js11
-rw-r--r--app/assets/javascripts/notes/services/notes_service.js9
-rw-r--r--app/assets/javascripts/notes/stores/actions.js275
-rw-r--r--app/assets/javascripts/notes/stores/getters.js34
-rw-r--r--app/assets/javascripts/notes/stores/index.js3
-rw-r--r--app/assets/javascripts/notes/stores/mutation_types.js1
-rw-r--r--app/assets/javascripts/notes/stores/mutations.js44
-rw-r--r--app/assets/javascripts/notes/stores/utils.js13
-rw-r--r--app/assets/javascripts/notifications_dropdown.js1
-rw-r--r--app/assets/javascripts/notifications_form.js1
-rw-r--r--app/assets/javascripts/pager.js1
-rw-r--r--app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js1
-rw-r--r--app/assets/javascripts/pages/admin/admin.js1
-rw-r--r--app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js1
-rw-r--r--app/assets/javascripts/pages/admin/projects/index/index.js1
-rw-r--r--app/assets/javascripts/pages/admin/users/index.js1
-rw-r--r--app/assets/javascripts/pages/ci/lints/new/index.js (renamed from app/assets/javascripts/pages/ci/lints/create/index.js)0
-rw-r--r--app/assets/javascripts/pages/dashboard/todos/index/todos.js2
-rw-r--r--app/assets/javascripts/pages/help/index/index.js1
-rw-r--r--app/assets/javascripts/pages/profiles/index.js1
-rw-r--r--app/assets/javascripts/pages/profiles/notifications/show/index.js7
-rw-r--r--app/assets/javascripts/pages/profiles/two_factor_auths/index.js1
-rw-r--r--app/assets/javascripts/pages/projects/blob/show/index.js22
-rw-r--r--app/assets/javascripts/pages/projects/branches/new/index.js1
-rw-r--r--app/assets/javascripts/pages/projects/commit/pipelines/index.js1
-rw-r--r--app/assets/javascripts/pages/projects/commit/show/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/find_file/show/index.js1
-rw-r--r--app/assets/javascripts/pages/projects/graphs/charts/index.js1
-rw-r--r--app/assets/javascripts/pages/projects/graphs/show/index.js1
-rw-r--r--app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js1
-rw-r--r--app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js2
-rw-r--r--app/assets/javascripts/pages/projects/issues/form.js2
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js1
-rw-r--r--app/assets/javascripts/pages/projects/network/network.js1
-rw-r--r--app/assets/javascripts/pages/projects/network/show/index.js1
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/target_branch_dropdown.js2
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js2
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js1
-rw-r--r--app/assets/javascripts/pages/projects/pipelines/charts/index.js1
-rw-r--r--app/assets/javascripts/pages/projects/pipelines/new/index.js1
-rw-r--r--app/assets/javascripts/pages/projects/project.js1
-rw-r--r--app/assets/javascripts/pages/projects/releases/edit/index.js1
-rw-r--r--app/assets/javascripts/pages/projects/shared/project_avatar.js2
-rw-r--r--app/assets/javascripts/pages/projects/shared/project_new.js1
-rw-r--r--app/assets/javascripts/pages/projects/show/index.js22
-rw-r--r--app/assets/javascripts/pages/projects/snippets/edit/index.js1
-rw-r--r--app/assets/javascripts/pages/projects/snippets/new/index.js1
-rw-r--r--app/assets/javascripts/pages/projects/tags/new/index.js1
-rw-r--r--app/assets/javascripts/pages/projects/tree/show/index.js1
-rw-r--r--app/assets/javascripts/pages/projects/wikis/index.js1
-rw-r--r--app/assets/javascripts/pages/search/show/search.js1
-rw-r--r--app/assets/javascripts/pages/sessions/new/index.js1
-rw-r--r--app/assets/javascripts/pages/sessions/new/oauth_remember_me.js2
-rw-r--r--app/assets/javascripts/pages/sessions/new/username_validator.js1
-rw-r--r--app/assets/javascripts/pages/snippets/form.js1
-rw-r--r--app/assets/javascripts/pages/users/activity_calendar.js1
-rw-r--r--app/assets/javascripts/pages/users/index.js1
-rw-r--r--app/assets/javascripts/pages/users/user_tabs.js1
-rw-r--r--app/assets/javascripts/performance_bar.js8
-rw-r--r--app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue1
-rw-r--r--app/assets/javascripts/pipelines/components/nav_controls.vue27
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines.vue21
-rw-r--r--app/assets/javascripts/pipelines/components/stage.vue1
-rw-r--r--app/assets/javascripts/pipelines/mixins/pipelines.js6
-rw-r--r--app/assets/javascripts/preview_markdown.js9
-rw-r--r--app/assets/javascripts/profile/gl_crop.js1
-rw-r--r--app/assets/javascripts/profile/profile.js2
-rw-r--r--app/assets/javascripts/project_edit.js2
-rw-r--r--app/assets/javascripts/project_find_file.js1
-rw-r--r--app/assets/javascripts/project_fork.js2
-rw-r--r--app/assets/javascripts/project_label_subscription.js1
-rw-r--r--app/assets/javascripts/project_select.js2
-rw-r--r--app/assets/javascripts/project_select_combo_button.js1
-rw-r--r--app/assets/javascripts/project_visibility.js2
-rw-r--r--app/assets/javascripts/projects/project_import_gitlab_project.js1
-rw-r--r--app/assets/javascripts/projects/project_new.js1
-rw-r--r--app/assets/javascripts/projects_dropdown/index.js1
-rw-r--r--app/assets/javascripts/prometheus_metrics/prometheus_metrics.js60
-rw-r--r--app/assets/javascripts/protected_branches/protected_branch_create.js1
-rw-r--r--app/assets/javascripts/protected_branches/protected_branch_edit_list.js1
-rw-r--r--app/assets/javascripts/protected_tags/protected_tag_create.js1
-rw-r--r--app/assets/javascripts/protected_tags/protected_tag_edit_list.js1
-rw-r--r--app/assets/javascripts/ref_select_dropdown.js2
-rw-r--r--app/assets/javascripts/render_gfm.js1
-rw-r--r--app/assets/javascripts/render_math.js1
-rw-r--r--app/assets/javascripts/right_sidebar.js1
-rw-r--r--app/assets/javascripts/search_autocomplete.js2
-rw-r--r--app/assets/javascripts/settings_panels.js2
-rw-r--r--app/assets/javascripts/shared/milestones/form.js1
-rw-r--r--app/assets/javascripts/shared/sessions/u2f.js1
-rw-r--r--app/assets/javascripts/shortcuts.js1
-rw-r--r--app/assets/javascripts/shortcuts_issuable.js1
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/assignee_title.js59
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/assignee_title.vue64
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue2
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.js1
-rw-r--r--app/assets/javascripts/sidebar/lib/sidebar_move_issue.js2
-rw-r--r--app/assets/javascripts/sidebar/mount_sidebar.js1
-rw-r--r--app/assets/javascripts/single_file_diff.js1
-rw-r--r--app/assets/javascripts/smart_interval.js2
-rw-r--r--app/assets/javascripts/snippet/snippet_bundle.js2
-rw-r--r--app/assets/javascripts/star.js1
-rw-r--r--app/assets/javascripts/subscription_select.js2
-rw-r--r--app/assets/javascripts/syntax_highlight.js2
-rw-r--r--app/assets/javascripts/task_list.js1
-rw-r--r--app/assets/javascripts/templates/issuable_template_selector.js1
-rw-r--r--app/assets/javascripts/templates/issuable_template_selectors.js2
-rw-r--r--app/assets/javascripts/terminal/terminal.js2
-rw-r--r--app/assets/javascripts/tree.js2
-rw-r--r--app/assets/javascripts/u2f/authenticate.js1
-rw-r--r--app/assets/javascripts/u2f/register.js1
-rw-r--r--app/assets/javascripts/ui_development_kit.js1
-rw-r--r--app/assets/javascripts/user_callout.js1
-rw-r--r--app/assets/javascripts/users_select.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/deployment.vue144
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/memory_usage.vue (renamed from app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js)87
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js113
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_maintainer_edit.vue3
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue34
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge.js44
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions.js27
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js1
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue47
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/unresolved_discussions.vue33
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/dependencies.js10
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js23
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js1
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/field.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/header.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/memory_graph.vue (renamed from app/assets/javascripts/vue_shared/components/memory_graph.js)38
-rw-r--r--app/assets/javascripts/vue_shared/components/navigation_tabs.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue24
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue11
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer.vue16
-rw-r--r--app/assets/javascripts/vue_shared/directives/popover.js2
-rw-r--r--app/assets/javascripts/vue_shared/directives/tooltip.js2
-rw-r--r--app/assets/javascripts/zen_mode.js1
-rw-r--r--app/assets/stylesheets/framework/common.scss4
-rw-r--r--app/assets/stylesheets/framework/flash.scss6
-rw-r--r--app/assets/stylesheets/framework/mixins.scss2
-rw-r--r--app/assets/stylesheets/pages/commits.scss6
-rw-r--r--app/assets/stylesheets/pages/issuable.scss40
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss38
-rw-r--r--app/assets/stylesheets/pages/notes.scss2
-rw-r--r--app/assets/stylesheets/pages/settings.scss3
-rw-r--r--app/assets/stylesheets/pages/wiki.scss5
-rw-r--r--app/controllers/projects/discussions_controller.rb6
-rw-r--r--app/controllers/projects/services_controller.rb33
-rw-r--r--app/controllers/projects/settings/ci_cd_controller.rb10
-rw-r--r--app/finders/admin/projects_finder.rb3
-rw-r--r--app/finders/user_recent_events_finder.rb45
-rw-r--r--app/helpers/import_helper.rb13
-rw-r--r--app/helpers/javascript_helper.rb5
-rw-r--r--app/helpers/labels_helper.rb33
-rw-r--r--app/helpers/notes_helper.rb2
-rw-r--r--app/models/application_setting.rb6
-rw-r--r--app/models/ci/build.rb145
-rw-r--r--app/models/ci/pipeline.rb11
-rw-r--r--app/models/ci/runner.rb9
-rw-r--r--app/models/clusters/platforms/kubernetes.rb26
-rw-r--r--app/models/compare.rb39
-rw-r--r--app/models/concerns/storage/legacy_namespace.rb51
-rw-r--r--app/models/cycle_analytics/summary.rb0
-rw-r--r--app/models/environment.rb7
-rw-r--r--app/models/member.rb7
-rw-r--r--app/models/members/project_member.rb6
-rw-r--r--app/models/merge_request.rb5
-rw-r--r--app/models/merge_request_diff.rb4
-rw-r--r--app/models/project.rb51
-rw-r--r--app/models/project_auto_devops.rb11
-rw-r--r--app/models/project_services/jira_service.rb5
-rw-r--r--app/models/project_services/kubernetes_service.rb26
-rw-r--r--app/models/project_services/pipelines_email_service.rb4
-rw-r--r--app/models/project_wiki.rb2
-rw-r--r--app/models/repository.rb82
-rw-r--r--app/models/service.rb16
-rw-r--r--app/models/user.rb40
-rw-r--r--app/services/ci/fetch_kubernetes_token_service.rb2
-rw-r--r--app/services/clusters/applications/check_installation_progress_service.rb2
-rw-r--r--app/services/clusters/applications/install_service.rb2
-rw-r--r--app/services/compare_service.rb9
-rw-r--r--app/services/files/create_service.rb8
-rw-r--r--app/services/files/multi_service.rb26
-rw-r--r--app/services/lfs/file_modification_handler.rb42
-rw-r--r--app/services/lfs/file_transformer.rb66
-rw-r--r--app/services/members/destroy_service.rb40
-rw-r--r--app/services/merge_requests/conflicts/list_service.rb10
-rw-r--r--app/services/merge_requests/merge_request_diff_cache_service.rb11
-rw-r--r--app/services/notification_service.rb4
-rw-r--r--app/services/projects/import_export/export_service.rb2
-rw-r--r--app/services/projects/update_service.rb2
-rw-r--r--app/views/admin/runners/show.html.haml4
-rw-r--r--app/views/discussions/_diff_with_notes.html.haml31
-rw-r--r--app/views/discussions/_discussion.html.haml2
-rw-r--r--app/views/import/github/new.html.haml2
-rw-r--r--app/views/peek/views/_gc.html.haml7
-rw-r--r--app/views/peek/views/_gitaly.html.haml18
-rw-r--r--app/views/peek/views/_redis.html.haml7
-rw-r--r--app/views/peek/views/_sidekiq.html.haml7
-rw-r--r--app/views/peek/views/_sql.html.haml13
-rw-r--r--app/views/projects/services/_form.html.haml8
-rw-r--r--app/views/projects/services/prometheus/_show.html.haml18
-rw-r--r--app/views/shared/_service_settings.html.haml4
-rw-r--r--app/views/shared/boards/_show.html.haml2
-rw-r--r--app/views/shared/issuable/_label_page_create.html.haml3
-rw-r--r--app/views/shared/issuable/_label_page_default.html.haml7
-rw-r--r--app/views/shared/issuable/form/_contribution.html.haml2
-rw-r--r--app/views/shared/milestones/_milestone.html.haml20
-rw-r--r--app/workers/project_export_worker.rb5
-rw-r--r--changelogs/unreleased/34604-fix-generated-url-for-external-repository.yml5
-rw-r--r--changelogs/unreleased/35475-lazy-diff.yml5
-rw-r--r--changelogs/unreleased/40525-listing-user-activity-timeouts.yml5
-rw-r--r--changelogs/unreleased/41902-add-api-option-to-overwrite-project-description-on-project-export.yml5
-rw-r--r--changelogs/unreleased/42579-fix-sidebar-dropdown-hover-style.yml5
-rw-r--r--changelogs/unreleased/42814-fix-remove-source-branch-when-mwps.yml6
-rw-r--r--changelogs/unreleased/43261-fix-prometheus-installation.yml5
-rw-r--r--changelogs/unreleased/43510-merge-requests-and-issues-don-t-show-for-all-subgroups.yml6
-rw-r--r--changelogs/unreleased/43531-500-error-searching-wiki-incompatible-character-encodings-utf-8-and-ascii-8bit.yml5
-rw-r--r--changelogs/unreleased/43532-error-on-admin-applications-prometheus-template.yml5
-rw-r--r--changelogs/unreleased/43702-update-label-dropdown-wording.yml5
-rw-r--r--changelogs/unreleased/43717-breadcrumb-on-admin-runner-page.yml5
-rw-r--r--changelogs/unreleased/43720-update-fe-webpack-docs.yml6
-rw-r--r--changelogs/unreleased/43802-ensure-foreign-keys-on-clusters-applications.yml5
-rw-r--r--changelogs/unreleased/43805-list-gitaly-calls-and-arguments-in-the-performance-bar.yml5
-rw-r--r--changelogs/unreleased/43806-update-ruby-saml-to-1-7-2.yml5
-rw-r--r--changelogs/unreleased/43949-verify-job-artifacts.yml5
-rw-r--r--changelogs/unreleased/44024-fix-table-extra-column.yml5
-rw-r--r--changelogs/unreleased/44139-fix-issue-boards-dup-keys.yml6
-rw-r--r--changelogs/unreleased/44149-issue-comment-buttons.yml5
-rw-r--r--changelogs/unreleased/44160-update-foreman-to-0-84-0.yml5
-rw-r--r--changelogs/unreleased/44191-reduce-redis-usage-from-merge-request-diffs-caching.yml5
-rw-r--r--changelogs/unreleased/44235-update-knapsack-to-1-16-0.yml5
-rw-r--r--changelogs/unreleased/adamco-gitlab-ce-move-issue-command.yml5
-rw-r--r--changelogs/unreleased/add-indexes-to-todos-for-heavy-users-like-sean.yml5
-rw-r--r--changelogs/unreleased/bvl-import-zip-multiple-assignees.yml5
-rw-r--r--changelogs/unreleased/cache-refactor.yml5
-rw-r--r--changelogs/unreleased/ce-jej-github-project-service-for-ci.yml5
-rw-r--r--changelogs/unreleased/ce-jej-integrations-can-hide-trigger-checkboxes.yml6
-rw-r--r--changelogs/unreleased/fix-42459---in-branch.yml5
-rw-r--r--changelogs/unreleased/fix-dropzone-project-show.yml5
-rw-r--r--changelogs/unreleased/fix-emoji-popup.yml5
-rw-r--r--changelogs/unreleased/fix-mattermost-delete-team.yml5
-rw-r--r--changelogs/unreleased/fix-sm-fix_pages_worker.yml5
-rw-r--r--changelogs/unreleased/grpc-unavailable-restart.yml5
-rw-r--r--changelogs/unreleased/issue-edit-shortcut.yml5
-rw-r--r--changelogs/unreleased/jej-commit-api-tracks-lfs.yml5
-rw-r--r--changelogs/unreleased/jivl-realtime-update-adding-file.yml5
-rw-r--r--changelogs/unreleased/merge-request-widget-source-branch-improvements.yml6
-rw-r--r--changelogs/unreleased/minimal-fix-for-artifacts-service.yml5
-rw-r--r--changelogs/unreleased/mk-fix-error-code-for-repo-does-not-exist.yml5
-rw-r--r--changelogs/unreleased/osw-43951-single-batch-blob-request-to-gitaly.yml5
-rw-r--r--changelogs/unreleased/osw-stop-recalculating-merge-base-on-mr-loading.yml5
-rw-r--r--changelogs/unreleased/refactor-move-assignee-title-vue-component.yml5
-rw-r--r--changelogs/unreleased/refactor-move-mr-widget-memory-usage-and-graph-components.yml5
-rw-r--r--changelogs/unreleased/refactor-move-mr-widget-nothing-to-merge-vue-component.yml5
-rw-r--r--changelogs/unreleased/refactor-move-mr-widget-unresolved-discussions-vue-component.yml5
-rw-r--r--changelogs/unreleased/remove-projects-finder-from-todos-finder.yml5
-rw-r--r--changelogs/unreleased/revert-project-visibility-changes.yml5
-rw-r--r--changelogs/unreleased/sh-cache-column-exists.yml5
-rw-r--r--changelogs/unreleased/sh-cache-table-exists.yml5
-rw-r--r--changelogs/unreleased/sh-optimize-admin-projects-page.yml5
-rw-r--r--changelogs/unreleased/tc-api-fix-expose_url.yml5
-rw-r--r--changelogs/unreleased/unassign-when-leaving.yml5
-rw-r--r--changelogs/unreleased/zj-gitaly-encoding-issue.yml5
-rw-r--r--config/application.rb14
-rw-r--r--config/initializers/backtrace_silencers.rb9
-rw-r--r--config/initializers/peek.rb4
-rw-r--r--config/routes/git_http.rb2
-rw-r--r--config/routes/group.rb4
-rw-r--r--config/routes/project.rb8
-rw-r--r--config/routes/user.rb4
-rw-r--r--config/svg.config.js48
-rw-r--r--db/fixtures/development/22_labeled_issues_seed.rb103
-rw-r--r--db/migrate/20180223120443_create_user_interacted_projects_table.rb4
-rw-r--r--db/migrate/20180302152117_ensure_foreign_keys_on_clusters_applications.rb50
-rw-r--r--db/migrate/20180304204842_clean_commits_count_migration.rb14
-rw-r--r--db/migrate/20180306134842_add_missing_indexes_acts_as_taggable_on_engine.rb21
-rw-r--r--db/migrate/20180308052825_add_section_name_id_index_on_ci_build_trace_sections.rb23
-rw-r--r--db/migrate/20180309121820_reschedule_commits_count_for_merge_request_diff.rb30
-rw-r--r--db/migrate/20180309160427_add_partial_indexes_on_todos.rb28
-rw-r--r--db/post_migrate/20180223124427_build_user_interacted_projects_table.rb142
-rw-r--r--db/schema.rb9
-rw-r--r--doc/administration/incoming_email.md1
-rw-r--r--doc/administration/monitoring/performance/img/performance_bar.pngbin170256 -> 344274 bytes
-rw-r--r--doc/administration/monitoring/performance/img/performance_bar_gitaly_calls.pngbin0 -> 278693 bytes
-rw-r--r--doc/administration/monitoring/performance/performance_bar.md8
-rw-r--r--doc/administration/raketasks/check.md8
-rw-r--r--doc/administration/raketasks/maintenance.md28
-rw-r--r--doc/api/project_import_export.md3
-rw-r--r--doc/ci/examples/README.md23
-rw-r--r--doc/ci/examples/code_climate.md22
-rw-r--r--doc/ci/examples/container_scanning.md55
-rw-r--r--doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/img/aws_config_window.pngbin0 -> 22046 bytes
-rw-r--r--doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/img/gitlab_config.pngbin0 -> 27620 bytes
-rw-r--r--doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/img/test_pipeline_pass.pngbin0 -> 18789 bytes
-rw-r--r--doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md526
-rw-r--r--doc/ci/examples/sast_docker.md56
-rw-r--r--doc/ci/variables/README.md2
-rw-r--r--doc/ci/yaml/README.md974
-rw-r--r--doc/development/doc_styleguide.md337
-rw-r--r--doc/development/ee_features.md18
-rw-r--r--doc/development/fe_guide/index.md4
-rw-r--r--doc/development/fe_guide/performance.md130
-rw-r--r--doc/development/fe_guide/style_guide_js.md51
-rw-r--r--doc/development/fe_guide/vue.md12
-rw-r--r--doc/development/i18n/externalization.md2
-rw-r--r--doc/development/i18n/proofreader.md3
-rw-r--r--doc/development/new_fe_guide/development/security.md13
-rw-r--r--doc/development/new_fe_guide/index.md2
-rw-r--r--doc/development/new_fe_guide/principles.md34
-rw-r--r--doc/development/new_fe_guide/style/html.md52
-rw-r--r--doc/development/new_fe_guide/style/index.md8
-rw-r--r--doc/development/new_fe_guide/style/prettier.md45
-rw-r--r--doc/development/writing_documentation.md275
-rw-r--r--doc/downgrade_ee_to_ce/README.md2
-rw-r--r--doc/install/installation.md19
-rw-r--r--doc/install/openshift_and_gitlab/index.md11
-rw-r--r--doc/integration/slash_commands.md3
-rw-r--r--doc/topics/autodevops/index.md18
-rw-r--r--doc/update/10.5-to-10.6.md361
-rw-r--r--doc/user/permissions.md3
-rw-r--r--doc/user/project/issue_board.md11
-rw-r--r--doc/user/project/members/share_project_with_groups.md24
-rw-r--r--doc/user/project/merge_requests/img/allow_maintainer_push.pngbin502758 -> 49216 bytes
-rw-r--r--doc/user/project/merge_requests/maintainer_access.md2
-rw-r--r--doc/user/project/repository/img/jupyter_notebook.pngbin0 -> 63326 bytes
-rw-r--r--doc/user/project/repository/index.md18
-rw-r--r--doc/user/project/settings/import_export.md3
-rw-r--r--features/project/active_tab.feature138
-rw-r--r--features/project/fork.feature49
-rw-r--r--features/project/graph.feature33
-rw-r--r--features/project/redirects.feature38
-rw-r--r--features/steps/project/active_tab.rb127
-rw-r--r--features/steps/project/fork.rb85
-rw-r--r--features/steps/project/graph.rb50
-rw-r--r--features/steps/project/redirects.rb67
-rw-r--r--lib/api/helpers/related_resources_helpers.rb2
-rw-r--r--lib/api/project_export.rb7
-rw-r--r--lib/banzai/filter/relative_link_filter.rb2
-rw-r--r--lib/constraints/group_url_constrainer.rb12
-rw-r--r--lib/constraints/project_url_constrainer.rb20
-rw-r--r--lib/constraints/user_url_constrainer.rb12
-rw-r--r--lib/container_registry/client.rb5
-rw-r--r--lib/declarative_policy.rb4
-rw-r--r--lib/declarative_policy/delegate_dsl.rb16
-rw-r--r--lib/declarative_policy/dsl.rb103
-rw-r--r--lib/declarative_policy/policy_dsl.rb44
-rw-r--r--lib/declarative_policy/preferred_scope.rb2
-rw-r--r--lib/declarative_policy/rule_dsl.rb45
-rw-r--r--lib/generators/rails/post_deployment_migration/post_deployment_migration_generator.rb2
-rw-r--r--lib/gitlab.rb1
-rw-r--r--lib/gitlab/auth/result.rb2
-rw-r--r--lib/gitlab/auth/saml/config.rb2
-rw-r--r--lib/gitlab/background_migration/add_merge_request_diff_commits_count.rb2
-rw-r--r--lib/gitlab/ci/pipeline/chain/command.rb2
-rw-r--r--lib/gitlab/ci/variables/collection.rb38
-rw-r--r--lib/gitlab/ci/variables/collection/item.rb50
-rw-r--r--lib/gitlab/conflict/file_collection.rb32
-rw-r--r--lib/gitlab/current_settings.rb2
-rw-r--r--lib/gitlab/data_builder/pipeline.rb1
-rw-r--r--lib/gitlab/database.rb9
-rw-r--r--lib/gitlab/database/migration_helpers.rb13
-rw-r--r--lib/gitlab/diff/diff_refs.rb6
-rw-r--r--lib/gitlab/diff/file_collection/merge_request_diff.rb30
-rw-r--r--lib/gitlab/exclusive_lease.rb10
-rw-r--r--lib/gitlab/git/gitlab_projects.rb12
-rw-r--r--lib/gitlab/git/lfs_pointer_file.rb9
-rw-r--r--lib/gitlab/git/repository.rb41
-rw-r--r--lib/gitlab/gitaly_client.rb27
-rw-r--r--lib/gitlab/github_import/client.rb5
-rw-r--r--lib/gitlab/gitlab_import/client.rb2
-rw-r--r--lib/gitlab/health_checks/metric.rb2
-rw-r--r--lib/gitlab/health_checks/result.rb2
-rw-r--r--lib/gitlab/import_export.rb2
-rw-r--r--lib/gitlab/import_export/project_tree_restorer.rb10
-rw-r--r--lib/gitlab/import_export/project_tree_saver.rb7
-rw-r--r--lib/gitlab/import_export/relation_factory.rb9
-rw-r--r--lib/gitlab/kubernetes/namespace.rb2
-rw-r--r--lib/gitlab/legacy_github_import/client.rb2
-rw-r--r--lib/gitlab/metrics/methods.rb2
-rw-r--r--lib/gitlab/middleware/release_env.rb2
-rw-r--r--lib/gitlab/project_transfer.rb16
-rw-r--r--lib/gitlab/repository_cache.rb33
-rw-r--r--lib/gitlab/repository_cache_adapter.rb84
-rw-r--r--lib/gitlab/search_results.rb4
-rw-r--r--lib/gitlab/shell.rb13
-rw-r--r--lib/gitlab/slash_commands/command.rb1
-rw-r--r--lib/gitlab/slash_commands/issue_move.rb45
-rw-r--r--lib/gitlab/slash_commands/presenters/issue_move.rb53
-rw-r--r--lib/gitlab/slash_commands/result.rb2
-rw-r--r--lib/gitlab/user_access.rb9
-rw-r--r--lib/gitlab/verify/job_artifacts.rb27
-rw-r--r--lib/google_api/auth.rb2
-rw-r--r--lib/haml_lint/inline_javascript.rb2
-rw-r--r--lib/mattermost/session.rb6
-rw-r--r--lib/mattermost/team.rb5
-rw-r--r--lib/peek/views/gitaly.rb20
-rw-r--r--lib/repository_cache.rb30
-rw-r--r--lib/tasks/gitlab/artifacts/check.rake8
-rw-r--r--lib/tasks/gitlab/exclusive_lease.rake9
-rw-r--r--lib/tasks/gitlab/shell.rake2
-rw-r--r--locale/gitlab.pot108
-rw-r--r--package.json9
-rw-r--r--qa/qa/page/project/settings/ci_cd.rb2
-rw-r--r--rubocop/rubocop.rb1
-rw-r--r--scripts/frontend/frontend_script_utils.js30
-rw-r--r--scripts/frontend/prettier.js103
-rwxr-xr-xscripts/trigger-build-docs19
-rw-r--r--spec/controllers/projects/services_controller_spec.rb12
-rw-r--r--spec/controllers/projects/settings/ci_cd_controller_spec.rb19
-rw-r--r--spec/factories/ci/job_artifacts.rb6
-rw-r--r--spec/features/boards/boards_spec.rb2
-rw-r--r--spec/features/boards/sidebar_spec.rb4
-rw-r--r--spec/features/boards/sub_group_project_spec.rb2
-rw-r--r--spec/features/ci_lint_spec.rb1
-rw-r--r--spec/features/issues/form_spec.rb4
-rw-r--r--spec/features/issues/issue_sidebar_spec.rb10
-rw-r--r--spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb6
-rw-r--r--spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb1
-rw-r--r--spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb16
-rw-r--r--spec/features/merge_request/user_sees_deployment_widget_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_merge_widget_spec.rb26
-rw-r--r--spec/features/milestone_spec.rb11
-rw-r--r--spec/features/profiles/user_visits_notifications_tab_spec.rb2
-rw-r--r--spec/features/projects/actve_tabs_spec.rb137
-rw-r--r--spec/features/projects/blobs/blob_show_spec.rb25
-rw-r--r--spec/features/projects/fork_spec.rb104
-rw-r--r--spec/features/projects/graph_spec.rb75
-rw-r--r--spec/features/projects/import_export/import_file_spec.rb1
-rw-r--r--spec/features/projects/import_export/test_project_export.tar.gzbin343087 -> 341299 bytes
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb20
-rw-r--r--spec/features/projects/redirects_spec.rb74
-rw-r--r--spec/features/projects/services/disable_triggers_spec.rb35
-rw-r--r--spec/features/projects/show_project_spec.rb22
-rw-r--r--spec/helpers/import_helper_spec.rb31
-rw-r--r--spec/helpers/labels_helper_spec.rb72
-rw-r--r--spec/javascripts/activities_spec.js1
-rw-r--r--spec/javascripts/ajax_loading_spinner_spec.js5
-rw-r--r--spec/javascripts/autosave_spec.js1
-rw-r--r--spec/javascripts/awards_handler_spec.js1
-rw-r--r--spec/javascripts/behaviors/autosize_spec.js1
-rw-r--r--spec/javascripts/behaviors/quick_submit_spec.js1
-rw-r--r--spec/javascripts/behaviors/requires_input_spec.js1
-rw-r--r--spec/javascripts/blob/blob_file_dropzone_spec.js3
-rw-r--r--spec/javascripts/blob/viewer/index_spec.js2
-rw-r--r--spec/javascripts/bootstrap_jquery_spec.js1
-rw-r--r--spec/javascripts/ci_variable_list/ci_variable_list_spec.js1
-rw-r--r--spec/javascripts/ci_variable_list/native_form_variable_list_spec.js1
-rw-r--r--spec/javascripts/commits_spec.js1
-rw-r--r--spec/javascripts/create_item_dropdown_spec.js1
-rw-r--r--spec/javascripts/feature_highlight/feature_highlight_helper_spec.js1
-rw-r--r--spec/javascripts/feature_highlight/feature_highlight_spec.js1
-rw-r--r--spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js3
-rw-r--r--spec/javascripts/gfm_auto_complete_spec.js1
-rw-r--r--spec/javascripts/gl_dropdown_spec.js1
-rw-r--r--spec/javascripts/gl_field_errors_spec.js1
-rw-r--r--spec/javascripts/gl_form_spec.js1
-rw-r--r--spec/javascripts/groups/components/app_spec.js1
-rw-r--r--spec/javascripts/header_spec.js1
-rw-r--r--spec/javascripts/integrations/integration_settings_form_spec.js1
-rw-r--r--spec/javascripts/issuable_spec.js1
-rw-r--r--spec/javascripts/issuable_time_tracker_spec.js1
-rw-r--r--spec/javascripts/issue_show/components/description_spec.js1
-rw-r--r--spec/javascripts/issue_spec.js2
-rw-r--r--spec/javascripts/job_spec.js1
-rw-r--r--spec/javascripts/labels_issue_sidebar_spec.js2
-rw-r--r--spec/javascripts/labels_select_spec.js1
-rw-r--r--spec/javascripts/line_highlighter_spec.js1
-rw-r--r--spec/javascripts/merge_request_notes_spec.js1
-rw-r--r--spec/javascripts/merge_request_spec.js6
-rw-r--r--spec/javascripts/merge_request_tabs_spec.js2
-rw-r--r--spec/javascripts/mini_pipeline_graph_dropdown_spec.js1
-rw-r--r--spec/javascripts/namespace_select_spec.js1
-rw-r--r--spec/javascripts/new_branch_spec.js1
-rw-r--r--spec/javascripts/notes/components/comment_form_spec.js15
-rw-r--r--spec/javascripts/notes/components/note_app_spec.js1
-rw-r--r--spec/javascripts/notes/components/noteable_note_spec.js1
-rw-r--r--spec/javascripts/notes/mock_data.js2
-rw-r--r--spec/javascripts/notes/stores/actions_spec.js81
-rw-r--r--spec/javascripts/notes/stores/mutation_spec.js81
-rw-r--r--spec/javascripts/oauth_remember_me_spec.js1
-rw-r--r--spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js1
-rw-r--r--spec/javascripts/pipelines/nav_controls_spec.js37
-rw-r--r--spec/javascripts/pipelines/pipelines_spec.js14
-rw-r--r--spec/javascripts/project_select_combo_button_spec.js1
-rw-r--r--spec/javascripts/projects/project_new_spec.js1
-rw-r--r--spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js2
-rw-r--r--spec/javascripts/right_sidebar_spec.js1
-rw-r--r--spec/javascripts/search_autocomplete_spec.js1
-rw-r--r--spec/javascripts/search_spec.js1
-rw-r--r--spec/javascripts/shortcuts_issuable_spec.js1
-rw-r--r--spec/javascripts/shortcuts_spec.js1
-rw-r--r--spec/javascripts/sidebar/assignee_title_spec.js2
-rw-r--r--spec/javascripts/sidebar/sidebar_move_issue_spec.js1
-rw-r--r--spec/javascripts/smart_interval_spec.js1
-rw-r--r--spec/javascripts/syntax_highlight_spec.js1
-rw-r--r--spec/javascripts/todos_spec.js1
-rw-r--r--spec/javascripts/toggle_buttons_spec.js1
-rw-r--r--spec/javascripts/u2f/authenticate_spec.js1
-rw-r--r--spec/javascripts/u2f/register_spec.js1
-rw-r--r--spec/javascripts/version_check_image_spec.js1
-rw-r--r--spec/javascripts/vue_mr_widget/components/deployment_spec.js172
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js179
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_maintainer_edit_spec.js33
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js18
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js20
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js6
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js12
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js8
-rw-r--r--spec/javascripts/vue_mr_widget/mr_widget_options_spec.js140
-rw-r--r--spec/javascripts/vue_shared/components/gl_modal_spec.js1
-rw-r--r--spec/javascripts/vue_shared/components/markdown/field_spec.js1
-rw-r--r--spec/javascripts/vue_shared/components/memory_graph_spec.js26
-rw-r--r--spec/javascripts/vue_shared/components/modal_spec.js1
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/labels_select/base_spec.js26
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js16
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js32
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js1
-rw-r--r--spec/javascripts/vue_shared/directives/tooltip_spec.js1
-rw-r--r--spec/javascripts/zen_mode_spec.js1
-rw-r--r--spec/lib/api/helpers/related_resources_helpers_spec.rb41
-rw-r--r--spec/lib/banzai/filter/relative_link_filter_spec.rb17
-rw-r--r--spec/lib/constraints/group_url_constrainer_spec.rb2
-rw-r--r--spec/lib/constraints/project_url_constrainer_spec.rb2
-rw-r--r--spec/lib/constraints/user_url_constrainer_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb12
-rw-r--r--spec/lib/gitlab/bare_repository_import/repository_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/variables/collection/item_spec.rb54
-rw-r--r--spec/lib/gitlab/ci/variables/collection_spec.rb99
-rw-r--r--spec/lib/gitlab/conflict/file_collection_spec.rb32
-rw-r--r--spec/lib/gitlab/data_builder/pipeline_spec.rb1
-rw-r--r--spec/lib/gitlab/database_spec.rb23
-rw-r--r--spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb2
-rw-r--r--spec/lib/gitlab/exclusive_lease_spec.rb12
-rw-r--r--spec/lib/gitlab/git/gitlab_projects_spec.rb14
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/project.json31
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb16
-rw-r--r--spec/lib/gitlab/import_export/project_tree_saver_spec.rb12
-rw-r--r--spec/lib/gitlab/kubernetes/namespace_spec.rb2
-rw-r--r--spec/lib/gitlab/profiler_spec.rb4
-rw-r--r--spec/lib/gitlab/project_search_results_spec.rb16
-rw-r--r--spec/lib/gitlab/project_transfer_spec.rb59
-rw-r--r--spec/lib/gitlab/repository_cache_adapter_spec.rb76
-rw-r--r--spec/lib/gitlab/repository_cache_spec.rb50
-rw-r--r--spec/lib/gitlab/shell_spec.rb16
-rw-r--r--spec/lib/gitlab/slash_commands/command_spec.rb5
-rw-r--r--spec/lib/gitlab/slash_commands/issue_move_spec.rb117
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb26
-rw-r--r--spec/lib/gitlab/verify/job_artifacts_spec.rb35
-rw-r--r--spec/lib/mattermost/team_spec.rb104
-rw-r--r--spec/lib/repository_cache_spec.rb34
-rw-r--r--spec/migrations/migrate_old_artifacts_spec.rb2
-rw-r--r--spec/migrations/reschedule_commits_count_for_merge_request_diff_spec.rb37
-rw-r--r--spec/models/ci/build_spec.rb177
-rw-r--r--spec/models/ci/pipeline_spec.rb8
-rw-r--r--spec/models/clusters/platforms/kubernetes_spec.rb2
-rw-r--r--spec/models/compare_spec.rb40
-rw-r--r--spec/models/members/project_member_spec.rb23
-rw-r--r--spec/models/merge_request_spec.rb2
-rw-r--r--spec/models/namespace_spec.rb57
-rw-r--r--spec/models/project_auto_devops_spec.rb8
-rw-r--r--spec/models/project_services/kubernetes_service_spec.rb2
-rw-r--r--spec/models/project_spec.rb10
-rw-r--r--spec/models/project_wiki_spec.rb2
-rw-r--r--spec/models/repository_spec.rb71
-rw-r--r--spec/models/user_spec.rb1
-rw-r--r--spec/requests/api/project_export_spec.rb11
-rw-r--r--spec/routing/routing_spec.rb4
-rw-r--r--spec/services/clusters/applications/install_service_spec.rb2
-rw-r--r--spec/services/files/create_service_spec.rb4
-rw-r--r--spec/services/files/multi_service_spec.rb72
-rw-r--r--spec/services/lfs/file_transformer_spec.rb97
-rw-r--r--spec/services/members/destroy_service_spec.rb96
-rw-r--r--spec/services/merge_requests/merge_request_diff_cache_service_spec.rb36
-rw-r--r--spec/services/projects/create_service_spec.rb4
-rw-r--r--spec/services/projects/fork_service_spec.rb2
-rw-r--r--spec/services/projects/transfer_service_spec.rb2
-rw-r--r--spec/services/projects/update_service_spec.rb11
-rw-r--r--spec/tasks/gitlab/artifacts/check_rake_spec.rb34
-rw-r--r--vendor/assets/javascripts/peek.js70
-rw-r--r--vendor/gitignore/Dart.gitignore2
-rw-r--r--vendor/gitignore/Qt.gitignore8
-rw-r--r--vendor/gitignore/R.gitignore3
-rw-r--r--vendor/gitignore/TeX.gitignore8
-rw-r--r--vendor/gitignore/VisualStudio.gitignore3
-rw-r--r--vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml20
-rw-r--r--vendor/licenses.csv514
-rw-r--r--vendor/project_templates/express.tar.gzbin5608 -> 5614 bytes
-rw-r--r--vendor/project_templates/rails.tar.gzbin25004 -> 25007 bytes
-rw-r--r--vendor/project_templates/spring.tar.gzbin50938 -> 50945 bytes
-rw-r--r--yarn.lock12
799 files changed, 10989 insertions, 5830 deletions
diff --git a/.eslintrc b/.eslintrc
index 8f9cdfb14ac..3f187db0c07 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -1,6 +1,5 @@
{
"env": {
- "jquery": true,
"browser": true,
"es6": true
},
@@ -36,15 +35,22 @@
"import/no-commonjs": "error",
"no-multiple-empty-lines": ["error", { "max": 1 }],
"promise/catch-or-return": "error",
- "no-underscore-dangle": ["error", { "allow": ["__", "_links"]}],
- "vue/html-self-closing": ["error", {
- "html": {
- "void": "always",
- "normal": "never",
- "component": "always"
- },
- "svg": "always",
- "math": "always"
- }]
+ "no-underscore-dangle": ["error", { "allow": ["__", "_links"] }],
+ "no-mixed-operators": 0,
+ "space-before-function-paren": 0,
+ "curly": 0,
+ "arrow-parens": 0,
+ "vue/html-self-closing": [
+ "error",
+ {
+ "html": {
+ "void": "always",
+ "normal": "never",
+ "component": "always"
+ },
+ "svg": "always",
+ "math": "always"
+ }
+ ]
}
}
diff --git a/.gitignore b/.gitignore
index fa39ae01ff0..447fb71bd64 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,7 @@
eslint-report.html
/.gitlab_shell_secret
.idea
+/.vscode/*
/.rbenv-version
.rbx/
/.ruby-gemset
@@ -22,6 +23,9 @@ eslint-report.html
/.yarn-cache
/.byebug_history
/Vagrantfile
+/app/assets/images/icons.json
+/app/assets/images/icons.svg
+/app/assets/images/illustrations/
/app/assets/javascripts/locale/**/app.js
/backups/*
/config/aws.yml
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 5556bf5bc0b..724e37141d6 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -257,7 +257,7 @@ stages:
##
# Trigger a package build in omnibus-gitlab repository
#
-package-qa:
+package-and-qa:
<<: *dedicated-runner
image: ruby:2.4-alpine
before_script: []
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 00000000000..a20502b7f06
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,4 @@
+{
+ "singleQuote": true,
+ "trailingComma": "all"
+}
diff --git a/.rubocop.yml b/.rubocop.yml
index 293f61fb725..14840ddd262 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -31,6 +31,78 @@ Style/MutableConstant:
- 'ee/db/post_migrate/**/*'
- 'ee/db/geo/migrate/**/*'
+Naming/FileName:
+ ExpectMatchingDefinition: true
+ Exclude:
+ - 'spec/**/*'
+ - 'features/**/*'
+ - 'ee/spec/**/*'
+ - 'qa/spec/**/*'
+ - 'qa/qa/specs/**/*'
+ - 'qa/bin/*'
+ - 'config/**/*'
+ - 'lib/generators/**/*'
+ - 'ee/lib/generators/**/*'
+ IgnoreExecutableScripts: true
+ AllowedAcronyms:
+ - EE
+ - JSON
+ - LDAP
+ - IO
+ - HMAC
+ - QA
+ - ENV
+ - STL
+ - PDF
+ - SVG
+ - CTE
+ - DN
+ - RSA
+ - CI
+ - CD
+ - OAuth
+ # default ones:
+ - CLI
+ - DSL
+ - ACL
+ - API
+ - ASCII
+ - CPU
+ - CSS
+ - DNS
+ - EOF
+ - GUID
+ - HTML
+ - HTTP
+ - HTTPS
+ - ID
+ - IP
+ - JSON
+ - LHS
+ - QPS
+ - RAM
+ - RHS
+ - RPC
+ - SLA
+ - SMTP
+ - SQL
+ - SSH
+ - TCP
+ - TLS
+ - TTL
+ - UDP
+ - UI
+ - UID
+ - UUID
+ - URI
+ - URL
+ - UTF8
+ - VM
+ - XML
+ - XMPP
+ - XSRF
+ - XSS
+
# Gitlab ###################################################################
Gitlab/ModuleWithInstanceVariables:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 246a0fbc5f6..8c64e68967e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,41 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 10.5.5 (2018-03-15)
+
+### Fixed (3 changes)
+
+- Fix missing uploads after group transfer. !17658
+- Fix code and wiki search results when filename is non-ASCII.
+- Remove double caching of Repository#empty?.
+
+### Performance (2 changes)
+
+- Adding missing indexes on taggings table.
+- Add index on section_name_id on ci_build_trace_sections table.
+
+
+## 10.5.4 (2018-03-08)
+
+### Fixed (11 changes)
+
+- Encode branch name as binary before creating a RPC request to copy attributes. !17291
+- Restart Unicorn and Sidekiq when GRPC throws 14:Endpoint read failed. !17293
+- Ensure group issues and merge requests pages show results from subgroups when there are no results from the current group. !17312
+- Prevent trace artifact migration to incur data loss. !17313
+- Return a 404 instead of 403 if the repository does not exist on disk. !17341
+- Allow Prometheus application to be installed from Cluster applications. !17372
+- Fixes Prometheus admin configuration page. !17377
+- Fix code and wiki search results pages when non-ASCII text is displayed. !17413
+- Fix pages flaky failure by reloading stale object. !17522
+- Fixed issue edit shortcut not opening edit form.
+- Revert Project.public_or_visible_to_user changes and only apply to snippets.
+
+### Performance (1 change)
+
+- Don't use ProjectsFinder in TodosFinder.
+
+
## 10.5.3 (2018-03-01)
### Security (1 change)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 76ee6265c5c..9c8fdc1275b 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -26,7 +26,7 @@ _This notice should stay as the first item in the CONTRIBUTING.md file._
- [Type labels (~"feature proposal", ~bug, ~customer, etc.)](#type-labels-feature-proposal-bug-customer-etc)
- [Subject labels (~wiki, ~"container registry", ~ldap, ~api, etc.)](#subject-labels-wiki-container-registry-ldap-api-etc)
- [Team labels (~"CI/CD", ~Discussion, ~Edge, ~Platform, etc.)](#team-labels-cicd-discussion-edge-platform-etc)
- - [Priority labels (~Deliverable and ~Stretch)](#priority-labels-deliverable-and-stretch)
+ - [Priority labels (~Deliverable, ~Stretch, ~"Next Patch Release")](#priority-labels-deliverable-stretch-next-patch-release)
- [Label for community contributors (~"Accepting Merge Requests")](#label-for-community-contributors-accepting-merge-requests)
- [Implement design & UI elements](#implement-design-ui-elements)
- [Issue tracker](#issue-tracker)
@@ -98,8 +98,8 @@ coach is going to finish the merge request we assign the
## Helping others
-Please help other GitLab users when you can. The channels people will reach out
-on can be found on the [getting help page][getting-help].
+Please help other GitLab users when you can.
+The methods people will use to seek help can be found on the [getting help page][getting-help].
Sign up for the mailing list, answer GitLab questions on StackOverflow or
respond in the IRC channel. You can also sign up on [CodeTriage][codetriage] to help with
@@ -126,7 +126,7 @@ Most issues will have labels for at least one of the following:
- Type: ~"feature proposal", ~bug, ~customer, etc.
- Subject: ~wiki, ~"container registry", ~ldap, ~api, ~frontend, etc.
- Team: ~"CI/CD", ~Discussion, ~Edge, ~Platform, etc.
-- Priority: ~Deliverable, ~Stretch
+- Priority: ~Deliverable, ~Stretch, ~"Next Patch Release"
All labels, their meaning and priority are defined on the
[labels page][labels-page].
@@ -185,7 +185,7 @@ indicate if an issue needs backend work, frontend work, or both.
Team labels are always capitalized so that they show up as the first label for
any issue.
-### Priority labels (~Deliverable and ~Stretch)
+### Priority labels (~Deliverable, ~Stretch, ~"Next Patch Release")
Priority labels help us clearly communicate expectations of the work for the
release. There are two levels of priority labels:
@@ -195,6 +195,13 @@ release. There are two levels of priority labels:
- ~Stretch: Issues that are a stretch goal for delivering in the current
milestone. If these issues are not done in the current release, they will
strongly be considered for the next release.
+- ~"Next Patch Release": Issues to put in the next patch release. Work on these
+ first, and add the "Pick Into X" label to the merge request, along with the
+ appropriate milestone.
+
+Each issue scheduled for the current milestone should be labeled ~Deliverable
+or ~"Stretch". Any open issue for a previous milestone should be labeled
+~"Next Patch Release", or otherwise rescheduled to a different milestone.
### Severity labels (~S1, ~S2, etc.)
@@ -686,3 +693,4 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
[^1]: Please note that specs other than JavaScript specs are considered backend
code.
+ \ No newline at end of file
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index fe6d01c1a45..8f63f4f9a10 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.88.0
+0.91.0
diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION
index faef31a4357..39e898a4f95 100644
--- a/GITLAB_PAGES_VERSION
+++ b/GITLAB_PAGES_VERSION
@@ -1 +1 @@
-0.7.0
+0.7.1
diff --git a/Gemfile b/Gemfile
index 2793463fd81..dcd5f5ee049 100644
--- a/Gemfile
+++ b/Gemfile
@@ -34,7 +34,7 @@ gem 'omniauth-gitlab', '~> 1.0.2'
gem 'omniauth-google-oauth2', '~> 0.5.2'
gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
gem 'omniauth-oauth2-generic', '~> 0.2.2'
-gem 'omniauth-saml', '~> 1.7.0'
+gem 'omniauth-saml', '~> 1.10.0'
gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-twitter', '~> 1.2.0'
gem 'omniauth_crowd', '~> 2.2.0'
@@ -104,7 +104,7 @@ gem 'carrierwave', '~> 1.2'
gem 'dropzonejs-rails', '~> 0.7.1'
# for backups
-gem 'fog-aws', '~> 1.4'
+gem 'fog-aws', '~> 2.0'
gem 'fog-core', '~> 1.44'
gem 'fog-google', '~> 0.5'
gem 'fog-local', '~> 0.3'
@@ -152,7 +152,7 @@ end
gem 'state_machines-activerecord', '~> 0.4.0'
# Issue tags
-gem 'acts-as-taggable-on', '~> 4.0'
+gem 'acts-as-taggable-on', '~> 5.0'
# Background jobs
gem 'sidekiq', '~> 5.0'
@@ -208,7 +208,7 @@ gem 'asana', '~> 0.6.0'
gem 'ruby-fogbugz', '~> 0.2.1'
# Kubernetes integration
-gem 'kubeclient', '~> 2.2.0'
+gem 'kubeclient', '~> 3.0'
# d3
gem 'd3_rails', '~> 3.5.0'
@@ -257,14 +257,13 @@ gem 'font-awesome-rails', '~> 4.7'
gem 'gemojione', '~> 3.3'
gem 'gon', '~> 6.1.0'
gem 'jquery-atwho-rails', '~> 1.3.2'
-gem 'jquery-rails', '~> 4.3.1'
gem 'request_store', '~> 1.3'
gem 'select2-rails', '~> 3.5.9'
gem 'virtus', '~> 1.0.1'
gem 'base32', '~> 0.3.0'
# Sentry integration
-gem 'sentry-raven', '~> 2.5.3'
+gem 'sentry-raven', '~> 2.7'
gem 'premailer-rails', '~> 1.9.7'
@@ -300,7 +299,7 @@ group :metrics do
end
group :development do
- gem 'foreman', '~> 0.78.0'
+ gem 'foreman', '~> 0.84.0'
gem 'brakeman', '~> 3.6.0', require: false
gem 'letter_opener_web', '~> 1.3.0'
@@ -360,7 +359,7 @@ group :development, :test do
gem 'benchmark-ips', '~> 2.3.0', require: false
gem 'license_finder', '~> 3.1', require: false
- gem 'knapsack', '~> 1.11.0'
+ gem 'knapsack', '~> 1.16'
gem 'activerecord_sane_schema_dumper', '0.2'
@@ -381,14 +380,14 @@ group :test do
gem 'test-prof', '~> 0.2.5'
end
-gem 'octokit', '~> 4.6.2'
+gem 'octokit', '~> 4.8'
gem 'mail_room', '~> 0.9.1'
gem 'email_reply_trimmer', '~> 0.1'
gem 'html2text'
-gem 'ruby-prof', '~> 0.16.2'
+gem 'ruby-prof', '~> 0.17.0'
# OAuth
gem 'oauth2', '~> 1.4'
@@ -401,7 +400,7 @@ gem 'vmstat', '~> 2.3.0'
gem 'sys-filesystem', '~> 1.1.6'
# SSH host key support
-gem 'net-ssh', '~> 4.1.0'
+gem 'net-ssh', '~> 4.2.0'
gem 'sshkey', '~> 1.9.0'
# Required for ED25519 SSH host key support
@@ -421,9 +420,9 @@ gem 'google-protobuf', '= 3.5.1'
gem 'toml-rb', '~> 1.0.0', require: false
# Feature toggles
-gem 'flipper', '~> 0.11.0'
-gem 'flipper-active_record', '~> 0.11.0'
-gem 'flipper-active_support_cache_store', '~> 0.11.0'
+gem 'flipper', '~> 0.13.0'
+gem 'flipper-active_record', '~> 0.13.0'
+gem 'flipper-active_support_cache_store', '~> 0.13.0'
# Structured logging
gem 'lograge', '~> 0.5'
diff --git a/Gemfile.lock b/Gemfile.lock
index fa99ec3febe..618fbf4b3e7 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -40,8 +40,8 @@ GEM
minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
- acts-as-taggable-on (4.0.0)
- activerecord (>= 4.0)
+ acts-as-taggable-on (5.0.0)
+ activerecord (>= 4.2.8)
adamantium (0.2.0)
ice_nine (~> 0.11.0)
memoizable (~> 0.4.0)
@@ -63,7 +63,7 @@ GEM
fog-core
mime-types (>= 2.99)
unf
- ast (2.3.0)
+ ast (2.4.0)
atomic (1.1.99)
attr_encrypted (3.0.3)
encryptor (~> 3.0.0)
@@ -174,7 +174,7 @@ GEM
diff-lcs (1.3)
diffy (3.1.0)
docile (1.1.5)
- domain_name (0.5.20161021)
+ domain_name (0.5.20170404)
unf (>= 0.0.5, < 1.0.0)
doorkeeper (4.2.6)
railties (>= 4.2)
@@ -194,7 +194,7 @@ GEM
et-orbi (1.0.3)
tzinfo
eventmachine (1.0.8)
- excon (0.57.1)
+ excon (0.60.0)
execjs (2.6.0)
expression_parser (0.9.0)
factory_bot (4.8.2)
@@ -218,13 +218,13 @@ GEM
path_expander (~> 1.0)
ruby_parser (~> 3.0)
sexp_processor (~> 4.0)
- flipper (0.11.0)
- flipper-active_record (0.11.0)
+ flipper (0.13.0)
+ flipper-active_record (0.13.0)
activerecord (>= 3.2, < 6)
- flipper (~> 0.11.0)
- flipper-active_support_cache_store (0.11.0)
+ flipper (~> 0.13.0)
+ flipper-active_support_cache_store (0.13.0)
activesupport (>= 3.2, < 6)
- flipper (~> 0.11.0)
+ flipper (~> 0.13.0)
flowdock (0.7.1)
httparty (~> 0.7)
multi_json
@@ -233,14 +233,14 @@ GEM
fog-json (~> 1.0)
ipaddress (~> 0.8)
xml-simple (~> 1.1)
- fog-aws (1.4.0)
+ fog-aws (2.0.1)
fog-core (~> 1.38)
fog-json (~> 1.0)
fog-xml (~> 0.1)
ipaddress (~> 0.8)
- fog-core (1.44.3)
+ fog-core (1.45.0)
builder
- excon (~> 0.49)
+ excon (~> 0.58)
formatador (~> 0.2)
fog-google (0.5.3)
fog-core
@@ -265,7 +265,7 @@ GEM
nokogiri (>= 1.5.11, < 2.0.0)
font-awesome-rails (4.7.0.1)
railties (>= 3.2, < 5.1)
- foreman (0.78.0)
+ foreman (0.84.0)
thor (~> 0.19.1)
formatador (0.2.5)
fuubar (2.2.0)
@@ -388,7 +388,7 @@ GEM
thor
tilt
hashdiff (0.3.4)
- hashie (3.5.6)
+ hashie (3.5.7)
hashie-forbidden_attributes (0.1.1)
hashie (>= 3.0)
health_check (2.6.0)
@@ -402,20 +402,20 @@ GEM
html2text (0.2.0)
nokogiri (~> 1.6)
htmlentities (4.3.4)
- http (0.9.8)
+ http (2.2.2)
addressable (~> 2.3)
http-cookie (~> 1.0)
http-form_data (~> 1.0.1)
http_parser.rb (~> 0.6.0)
http-cookie (1.0.3)
domain_name (~> 0.5)
- http-form_data (1.0.1)
+ http-form_data (1.0.3)
http_parser.rb (0.6.0)
httparty (0.13.7)
json (~> 1.8)
multi_xml (>= 0.5.2)
httpclient (2.8.2)
- i18n (0.9.1)
+ i18n (0.9.5)
concurrent-ruby (~> 1.0)
ice_nine (0.11.2)
influxdb (0.2.3)
@@ -427,10 +427,6 @@ GEM
multipart-post
oauth (~> 0.5, >= 0.5.0)
jquery-atwho-rails (1.3.2)
- jquery-rails (4.3.1)
- rails-dom-testing (>= 1, < 3)
- railties (>= 4.2.0)
- thor (>= 0.14, < 2.0)
json (1.8.6)
json-jwt (1.7.2)
activesupport
@@ -454,13 +450,13 @@ GEM
kaminari-core (= 1.0.1)
kaminari-core (1.0.1)
kgio (2.10.0)
- knapsack (1.11.0)
+ knapsack (1.16.0)
rake
timecop (>= 0.1.0)
- kubeclient (2.2.0)
- http (= 0.9.8)
- recursive-open-struct (= 1.0.0)
- rest-client
+ kubeclient (3.0.0)
+ http (~> 2.2.2)
+ recursive-open-struct (~> 1.0.4)
+ rest-client (~> 2.0)
launchy (2.4.3)
addressable (~> 2.3)
letter_opener (1.4.1)
@@ -513,7 +509,7 @@ GEM
mustermann (~> 1.0.0)
mysql2 (0.4.10)
net-ldap (0.16.0)
- net-ssh (4.1.0)
+ net-ssh (4.2.0)
netrc (0.11.0)
nokogiri (1.8.2)
mini_portile2 (~> 2.3.0)
@@ -525,12 +521,12 @@ GEM
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (>= 1.2, < 3)
- octokit (4.6.2)
+ octokit (4.8.0)
sawyer (~> 0.8.0, >= 0.5.3)
oj (2.17.5)
- omniauth (1.4.2)
+ omniauth (1.4.3)
hashie (>= 1.2, < 4)
- rack (>= 1.0, < 3)
+ rack (>= 1.6.2, < 3)
omniauth-auth0 (1.4.1)
omniauth-oauth2 (~> 1.1)
omniauth-authentiq (0.3.1)
@@ -569,9 +565,9 @@ GEM
omniauth (~> 1.2)
omniauth-oauth2-generic (0.2.2)
omniauth-oauth2 (~> 1.0)
- omniauth-saml (1.7.0)
- omniauth (~> 1.3)
- ruby-saml (~> 1.4)
+ omniauth-saml (1.10.0)
+ omniauth (~> 1.3, >= 1.3.2)
+ ruby-saml (~> 1.7)
omniauth-shibboleth (1.2.1)
omniauth (>= 1.0.0)
omniauth-twitter (1.2.1)
@@ -586,8 +582,8 @@ GEM
orm_adapter (0.5.0)
os (0.9.6)
parallel (1.12.1)
- parser (2.4.0.2)
- ast (~> 2.3)
+ parser (2.5.0.3)
+ ast (~> 2.4.0)
parslet (1.5.0)
blankslate (~> 2.0)
path_expander (1.0.2)
@@ -650,7 +646,7 @@ GEM
pry (>= 0.9.10)
public_suffix (3.0.2)
pyu-ruby-sasl (0.0.3.3)
- rack (1.6.8)
+ rack (1.6.9)
rack-accept (0.4.5)
rack (>= 0.4)
rack-attack (4.4.1)
@@ -713,7 +709,7 @@ GEM
re2 (1.1.1)
recaptcha (3.0.0)
json
- recursive-open-struct (1.0.0)
+ recursive-open-struct (1.0.5)
redcarpet (3.4.0)
redis (3.3.5)
redis-actionpack (5.0.2)
@@ -741,7 +737,7 @@ GEM
request_store (1.3.1)
responders (2.3.0)
railties (>= 4.2.0, < 5.1)
- rest-client (2.0.0)
+ rest-client (2.0.2)
http-cookie (>= 1.0.2, < 2.0)
mime-types (>= 1.16, < 4.0)
netrc (~> 0.8)
@@ -803,9 +799,9 @@ GEM
i18n
ruby-fogbugz (0.2.1)
crack (~> 0.4)
- ruby-prof (0.16.2)
+ ruby-prof (0.17.0)
ruby-progressbar (1.9.0)
- ruby-saml (1.4.1)
+ ruby-saml (1.7.2)
nokogiri (>= 1.5.10)
ruby_parser (3.9.0)
sexp_processor (~> 4.1)
@@ -844,7 +840,7 @@ GEM
selenium-webdriver (3.5.0)
childprocess (~> 0.5)
rubyzip (~> 1.0)
- sentry-raven (2.5.3)
+ sentry-raven (2.7.2)
faraday (>= 0.7.6, < 1.0)
settingslogic (2.0.9)
sexp_processor (4.9.0)
@@ -933,7 +929,7 @@ GEM
truncato (0.7.10)
htmlentities (~> 4.3.1)
nokogiri (~> 1.8.0, >= 1.7.0)
- tzinfo (1.2.4)
+ tzinfo (1.2.5)
thread_safe (~> 0.1)
u2f (0.2.1)
uber (0.1.0)
@@ -942,7 +938,7 @@ GEM
json (>= 1.8.0)
unf (0.1.4)
unf_ext
- unf_ext (0.0.7.4)
+ unf_ext (0.0.7.5)
unicode-display_width (1.3.0)
unicorn (5.1.0)
kgio (~> 2.6)
@@ -951,13 +947,13 @@ GEM
get_process_mem (~> 0)
unicorn (>= 4, < 6)
uniform_notifier (1.10.0)
- unparser (0.2.6)
+ unparser (0.2.7)
abstract_type (~> 0.0.7)
adamantium (~> 0.2.0)
concord (~> 0.1.5)
diff-lcs (~> 1.3)
equalizer (~> 0.0.9)
- parser (>= 2.3.1.2, < 2.5)
+ parser (>= 2.3.1.2, < 2.6)
procto (~> 0.0.2)
url_safe_base64 (0.2.2)
validates_hostname (1.0.6)
@@ -994,7 +990,7 @@ DEPENDENCIES
RedCloth (~> 4.3.2)
ace-rails-ap (~> 4.1.0)
activerecord_sane_schema_dumper (= 0.2)
- acts-as-taggable-on (~> 4.0)
+ acts-as-taggable-on (~> 5.0)
addressable (~> 2.5.2)
akismet (~> 2.0)
allocations (~> 1.0)
@@ -1044,18 +1040,18 @@ DEPENDENCIES
fast_blank
ffaker (~> 2.4)
flay (~> 2.10.0)
- flipper (~> 0.11.0)
- flipper-active_record (~> 0.11.0)
- flipper-active_support_cache_store (~> 0.11.0)
+ flipper (~> 0.13.0)
+ flipper-active_record (~> 0.13.0)
+ flipper-active_support_cache_store (~> 0.13.0)
fog-aliyun (~> 0.2.0)
- fog-aws (~> 1.4)
+ fog-aws (~> 2.0)
fog-core (~> 1.44)
fog-google (~> 0.5)
fog-local (~> 0.3)
fog-openstack (~> 0.1)
fog-rackspace (~> 0.1.1)
font-awesome-rails (~> 4.7)
- foreman (~> 0.78.0)
+ foreman (~> 0.84.0)
fuubar (~> 2.2.0)
gemnasium-gitlab-service (~> 0.2)
gemojione (~> 3.3)
@@ -1090,12 +1086,11 @@ DEPENDENCIES
influxdb (~> 0.2)
jira-ruby (~> 1.4)
jquery-atwho-rails (~> 1.3.2)
- jquery-rails (~> 4.3.1)
json-schema (~> 2.8.0)
jwt (~> 1.5.6)
kaminari (~> 1.0)
- knapsack (~> 1.11.0)
- kubeclient (~> 2.2.0)
+ knapsack (~> 1.16)
+ kubeclient (~> 3.0)
letter_opener_web (~> 1.3.0)
license_finder (~> 3.1)
licensee (~> 8.7.0)
@@ -1107,10 +1102,10 @@ DEPENDENCIES
mousetrap-rails (~> 1.4.6)
mysql2 (~> 0.4.10)
net-ldap
- net-ssh (~> 4.1.0)
+ net-ssh (~> 4.2.0)
nokogiri (~> 1.8.2)
oauth2 (~> 1.4)
- octokit (~> 4.6.2)
+ octokit (~> 4.8)
oj (~> 2.17.4)
omniauth (~> 1.4.2)
omniauth-auth0 (~> 1.4.1)
@@ -1123,7 +1118,7 @@ DEPENDENCIES
omniauth-google-oauth2 (~> 0.5.2)
omniauth-kerberos (~> 0.3.0)
omniauth-oauth2-generic (~> 0.2.2)
- omniauth-saml (~> 1.7.0)
+ omniauth-saml (~> 1.10.0)
omniauth-shibboleth (~> 1.2.0)
omniauth-twitter (~> 1.2.0)
omniauth_crowd (~> 2.2.0)
@@ -1173,7 +1168,7 @@ DEPENDENCIES
rubocop (~> 0.52.1)
rubocop-rspec (~> 1.22.1)
ruby-fogbugz (~> 0.2.1)
- ruby-prof (~> 0.16.2)
+ ruby-prof (~> 0.17.0)
ruby_parser (~> 3.8)
rufus-scheduler (~> 3.4)
rugged (~> 0.26.0)
@@ -1183,7 +1178,7 @@ DEPENDENCIES
seed-fu (~> 2.3.7)
select2-rails (~> 3.5.9)
selenium-webdriver (~> 3.5)
- sentry-raven (~> 2.5.3)
+ sentry-raven (~> 2.7)
settingslogic (~> 2.0.9)
sham_rack (~> 1.3.6)
shoulda-matchers (~> 3.1.2)
diff --git a/PROCESS.md b/PROCESS.md
index 5ae191840ff..f206506f7c5 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -53,7 +53,7 @@ Below we describe the contributing process to GitLab for two reasons:
Several people from the [GitLab team][team] are helping community members to get
their contributions accepted by meeting our [Definition of done][done].
-What you can expect from them is described at https://about.gitlab.com/jobs/merge-request-coach/.
+What you can expect from them is described at https://about.gitlab.com/roles/merge-request-coach/.
## Assigning issues
@@ -202,6 +202,9 @@ you can ask for an exception to be made.
Go to [Release tasks issue tracker](https://gitlab.com/gitlab-org/release/tasks/issues/new) and create an issue
using the `Exception-request` issue template.
+**Do not** set the relevant `Pick into X.Y` label (see above) before request an
+exception; this should be done after the exception is approved.
+
You can find who is who on the [team page](https://about.gitlab.com/team/).
Whether an exception is made is determined by weighing the benefit and urgency of the change
diff --git a/app/assets/images/icons.json b/app/assets/images/icons.json
deleted file mode 100644
index 19843d24e22..00000000000
--- a/app/assets/images/icons.json
+++ /dev/null
@@ -1 +0,0 @@
-{"iconCount":191,"spriteSize":86607,"icons":["abuse","account","admin","angle-double-left","angle-double-right","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-down","arrow-right","assignee","bold","book","bookmark","branch","bullhorn","calendar","cancel","chart","chevron-down","chevron-left","chevron-right","chevron-up","clock","close","code","collapse","comment-dots","comment-next","comment","comments","commit","credit-card","cut","dashboard","disk","doc_code","doc_image","doc_text","double-headed-arrow","download","duplicate","earth","ellipsis_v","emoji_slightly_smiling_face","emoji_smile","emoji_smiley","epic","external-link","eye-slash","eye","file-addition","file-deletion","file-modified","filter","folder-o","folder-open","folder","fork","geo-nodes","git-merge","group","history","home","hook","hourglass","image-comment-dark","image-comment-light","import","issue-block","issue-child","issue-close","issue-duplicate","issue-external","issue-new","issue-open-m","issue-open","issue-parent","issues","italic","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","menu","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil-square","pencil","pipeline","play","plus-square-o","plus-square","plus","podcast","preferences","profile","project","push-rules","question-o","question","quote","redo","remove","repeat","retry","scale","screen-full","screen-normal","scroll_down","scroll_up","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","soft-unwrap","soft-wrap","spam","spinner","staged","star-o","star","status_canceled_borderless","status_canceled","status_closed","status_created_borderless","status_created","status_failed_borderless","status_failed","status_manual_borderless","status_manual","status_notfound_borderless","status_notfound","status_open","status_pending_borderless","status_pending","status_running_borderless","status_running","status_skipped_borderless","status_skipped","status_success_borderless","status_success_solid","status_success","status_warning_borderless","status_warning","stop","task-done","template","terminal","thumb-down","thumb-up","thumbtack","timer","todo-add","todo-done","token","unapproval","unassignee","unlink","unstaged","user","users","volume-up","warning","work"]} \ No newline at end of file
diff --git a/app/assets/images/icons.svg b/app/assets/images/icons.svg
deleted file mode 100644
index 6aec54d0543..00000000000
--- a/app/assets/images/icons.svg
+++ /dev/null
@@ -1 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><symbol viewBox="0 0 16 16" id="abuse" xmlns="http://www.w3.org/2000/svg"><path d="M11.408.328l4.029 3.222A1.5 1.5 0 0 1 16 4.72v6.555a1.5 1.5 0 0 1-.563 1.171l-4.026 3.224a1.5 1.5 0 0 1-.937.329H5.529a1.5 1.5 0 0 1-.937-.328L.563 12.45A1.5 1.5 0 0 1 0 11.28V4.724a1.5 1.5 0 0 1 .563-1.171L4.589.329A1.5 1.5 0 0 1 5.526 0h4.945c.34 0 .67.116.937.328zM10.296 2H5.702L2 4.964v6.074L5.704 14h4.594L14 11.036V4.962L10.296 2zM8 4a1 1 0 0 1 1 1v3a1 1 0 1 1-2 0V5a1 1 0 0 1 1-1zm0 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="account" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M9.195 9.965l-.568-.875a.25.25 0 0 1 .015-.294l.405-.5a.25.25 0 0 1 .283-.075l.938.36c.257-.183.543-.325.851-.42l.322-.988A.25.25 0 0 1 11.679 7h.642a.25.25 0 0 1 .238.173l.322.988c.308.095.594.237.851.42l.938-.36a.25.25 0 0 1 .283.076l.405.5a.25.25 0 0 1 .015.293l-.568.875c.113.297.18.616.193.95l.898.54a.25.25 0 0 1 .115.27l-.144.626a.25.25 0 0 1-.222.193l-1.115.098a3.015 3.015 0 0 1-.512.608l.165 1.18a.25.25 0 0 1-.138.259l-.577.281a.25.25 0 0 1-.29-.05l-.874-.905a3.035 3.035 0 0 1-.608 0l-.875.904a.25.25 0 0 1-.289.051l-.577-.281a.25.25 0 0 1-.138-.26l.165-1.18a3.015 3.015 0 0 1-.512-.607l-1.115-.098a.25.25 0 0 1-.222-.193l-.144-.626a.25.25 0 0 1 .115-.27l.898-.54c.013-.334.08-.653.193-.95zM6.789 8.023A12.845 12.845 0 0 0 6 8c-5.036 0-6 2.74-6 4.48C0 14.22.076 15 6 15c.553 0 1.055-.006 1.51-.02A5.977 5.977 0 0 1 6 11c0-1.083.287-2.1.79-2.977zM5.976 7a3 3 0 1 1 0-6 3 3 0 0 1 0 6zM12 12a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="admin" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M13.162 2.5a3.5 3.5 0 0 1-3.163 5.479L6.08 14.766a1.5 1.5 0 0 1-2.598-1.5L7.4 6.479A3.5 3.5 0 0 1 10.564 1L8.9 3.88l2.599 1.5 1.663-2.88zm-8.63 11.949a.5.5 0 1 0 .5-.866.5.5 0 0 0-.5.866z"/></symbol><symbol viewBox="0 0 16 16" id="angle-double-left" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10.414 7.95l4.243-4.243a1 1 0 0 0-1.414-1.414l-4.95 4.95a.997.997 0 0 0 0 1.414l4.95 4.95a1 1 0 1 0 1.414-1.415L10.414 7.95zm-7 0l4.243-4.243a1 1 0 0 0-1.414-1.414l-4.95 4.95a.997.997 0 0 0 0 1.414l4.95 4.95a1 1 0 0 0 1.414-1.415L3.414 7.95z"/></symbol><symbol viewBox="0 0 16 16" id="angle-double-right" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5.536 7.95L1.293 3.707a1 1 0 0 1 1.414-1.414l4.95 4.95a.997.997 0 0 1 0 1.414l-4.95 4.95a1 1 0 1 1-1.414-1.415L5.536 7.95zm7 0L8.293 3.707a1 1 0 0 1 1.414-1.414l4.95 4.95a.997.997 0 0 1 0 1.414l-4.95 4.95a1 1 0 0 1-1.414-1.415l4.243-4.242z"/></symbol><symbol viewBox="0 0 16 16" id="angle-down" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8 10.243l-4.95-4.95a1 1 0 0 0-1.414 1.414l5.657 5.657a.997.997 0 0 0 1.414 0l5.657-5.657a1 1 0 0 0-1.414-1.414L8 10.243z"/></symbol><symbol viewBox="0 0 16 16" id="angle-left" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5.757 8l4.95-4.95a1 1 0 1 0-1.414-1.414L3.636 7.293a.997.997 0 0 0 0 1.414l5.657 5.657a1 1 0 0 0 1.414-1.414L5.757 8z"/></symbol><symbol viewBox="0 0 16 16" id="angle-right" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10.243 8l-4.95-4.95a1 1 0 0 1 1.414-1.414l5.657 5.657a.997.997 0 0 1 0 1.414l-5.657 5.657a1 1 0 0 1-1.414-1.414L10.243 8z"/></symbol><symbol viewBox="0 0 16 16" id="angle-up" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8 6.757l-4.95 4.95a1 1 0 1 1-1.414-1.414l5.657-5.657a.997.997 0 0 1 1.414 0l5.657 5.657a1 1 0 0 1-1.414 1.414L8 6.757z"/></symbol><symbol viewBox="0 0 16 16" id="appearance" xmlns="http://www.w3.org/2000/svg"><path d="M11.161 12.456l.232.121c.1.053.175.094.249.137.53.318.844.75.857 1.402.012 1.397-1.116 1.756-3.12 1.858a23.85 23.85 0 0 1-1.38.026A8 8 0 0 1 0 8a8 8 0 0 1 8-8c4.417 0 7.998 3.582 7.998 7.977.06 2.621-1.312 3.586-4.48 3.648-.602.008-1.068.043-1.4.104.228.192.598.47 1.043.727zm-3.287-.943c-.019-1.495 1.228-1.856 3.611-1.888C13.67 9.582 14.028 9.33 13.998 8A6 6 0 1 0 8 14c.603 0 .91-.004 1.277-.023a9.7 9.7 0 0 0 .478-.035c-1.172-.738-1.868-1.47-1.88-2.43zM6 5a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm6 3a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm-2-3a1 1 0 1 1 0-2 1 1 0 0 1 0 2zM4 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="applications" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M1 0h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1zm0 6h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1zm6-6h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1zm0 1v2h2V1H7zm0 5h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1zm6-6h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1zm0 6h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1zm0 1v2h2V7h-2zM1 12h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1zm0 1v2h2v-2H1zm6-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1zm6 0h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1z"/></symbol><symbol viewBox="0 0 16 16" id="approval" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10.536 10.657l2.828-2.829a1 1 0 0 1 1.414 1.415l-3.535 3.535a.997.997 0 0 1-1.415 0l-2.12-2.121A1 1 0 1 1 9.12 9.243l1.415 1.414zM7.632 8.109A2 2 0 0 0 7 11.364l2.121 2.121a1.996 1.996 0 0 0 2.807.021C11.686 14.554 10.627 15 6 15c-5.924 0-6-.78-6-2.52S.964 8 6 8c.6 0 1.142.038 1.632.109zM5.976 7a3 3 0 1 1 0-6 3 3 0 0 1 0 6z"/></symbol><symbol viewBox="0 0 16 16" id="arrow-down" xmlns="http://www.w3.org/2000/svg"><path d="M10.472 7.282a.862.862 0 0 1 1.26-.006c.357.364.357.958 0 1.285L8.627 11.73A.886.886 0 0 1 8 12a.849.849 0 0 1-.627-.27L4.275 8.561a.904.904 0 0 1-.013-1.285.861.861 0 0 1 1.26-.007l2.486 2.527z"/></symbol><symbol viewBox="0 0 16 16" id="arrow-right" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M9 6H2a2 2 0 1 0 0 4h7v2.586a1 1 0 0 0 1.707.707l4.586-4.586a1 1 0 0 0 0-1.414l-4.586-4.586A1 1 0 0 0 9 3.414V6z"/></symbol><symbol viewBox="0 0 16 16" id="assignee" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M12 5V4a1 1 0 0 1 2 0v1h1a1 1 0 0 1 0 2h-1v1a1 1 0 0 1-2 0V7h-1a1 1 0 0 1 0-2h1zM5.976 7a3 3 0 1 1 0-6 3 3 0 0 1 0 6zM6 15c-5.924 0-6-.78-6-2.52S.964 8 6 8s6 2.692 6 4.48c0 1.788-.076 2.52-6 2.52z"/></symbol><symbol viewBox="0 0 16 16" id="bold" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M4 12.5v-9A1.5 1.5 0 0 1 5.5 2h2.104c2.182 0 3.879.681 3.879 2.982 0 1.067-.517 2.227-1.374 2.595v.073C11.176 7.963 12 8.865 12 10.466 12 12.914 10.19 14 7.911 14H5.5A1.5 1.5 0 0 1 4 12.5zm2.376-5.696H7.49c1.164 0 1.665-.552 1.665-1.417 0-.94-.534-1.289-1.649-1.289h-1.13v2.706zm0 5.098h1.341c1.293 0 1.956-.515 1.956-1.62 0-1.049-.647-1.472-1.956-1.472H6.376v3.092z"/></symbol><symbol viewBox="0 0 16 16" id="book" xmlns="http://www.w3.org/2000/svg"><path d="M7 2H5a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2v4.191a.5.5 0 0 1-.724.447l-1.052-.526a.5.5 0 0 0-.448 0l-1.052.526A.5.5 0 0 1 7 6.191V2zM5 0h6a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H5a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4z"/></symbol><symbol viewBox="0 0 16 16" id="bookmark" xmlns="http://www.w3.org/2000/svg"><path d="M6.746 10.505a2 2 0 0 1 2.508 0L11 11.911V3H5v8.91l1.746-1.405zM5 1h6a2 2 0 0 1 2 2v10.999a1 1 0 0 1-1.627.779L8 12.064l-3.373 2.714A1 1 0 0 1 3 13.998V3a2 2 0 0 1 2-2z"/></symbol><symbol viewBox="0 0 16 16" id="branch" xmlns="http://www.w3.org/2000/svg"><path d="M6 11.978v.29a2 2 0 1 1-2 0V3.732a2 2 0 1 1 2 0v3.849c.592-.491 1.31-.854 2.15-1.081 1.308-.353 1.875-.882 1.893-1.743a2 2 0 1 1 2.002-.051C12.053 6.54 10.857 7.84 8.67 8.43 7.056 8.867 6.195 9.98 6 11.978zM5 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm6 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2zM5 15a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="bullhorn" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M6.143 10H7V4H3a3 3 0 1 0 0 6h.143l.734 5.141a1 1 0 0 0 .99.859h1.556a.5.5 0 0 0 .495-.57L6.143 10zM8 4c1.034.02 2.039-.274 3.014-.883.727-.455 1.836-1.334 3.328-2.637A1 1 0 0 1 16 1.233v10.764a1 1 0 0 1-1.595.803c-1.658-1.227-2.788-1.992-3.392-2.294-.781-.39-1.785-.559-3.013-.506V4z"/></symbol><symbol viewBox="0 0 16 16" id="calendar" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M12 2h2a2 2 0 0 1 2 2H0a2 2 0 0 1 2-2h2V1a1 1 0 1 1 2 0v1h4V1a1 1 0 1 1 2 0v1zM0 4h16v9a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V4zm2 2.5V13a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V6.5a.5.5 0 0 0-.5-.5h-11a.5.5 0 0 0-.5.5zM5 8h2a1 1 0 1 1 0 2H5a1 1 0 1 1 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="cancel" xmlns="http://www.w3.org/2000/svg"><path d="M3.11 4.523a6 6 0 0 0 8.367 8.367L3.109 4.524zM4.522 3.11l8.368 8.368A6 6 0 0 0 4.524 3.11zM8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16z"/></symbol><symbol viewBox="0 0 16 16" id="chart" xmlns="http://www.w3.org/2000/svg"><path d="M15 14a1 1 0 0 1 0 2H2a2 2 0 0 1-2-2V1a1 1 0 1 1 2 0v13h13zM3.142 8.735l2.502-2.561a.5.5 0 0 1 .714-.003L8 7.833l3.592-4.553a.5.5 0 0 1 .796.015l2.516 3.454a.5.5 0 0 1 .096.295V12.5a.5.5 0 0 1-.5.5h-11a.5.5 0 0 1-.5-.5V9.085a.5.5 0 0 1 .142-.35z"/></symbol><symbol viewBox="0 0 16 16" id="chevron-down" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8.078 8.2l3.535-3.536a2 2 0 0 1 2.828 2.828l-4.949 4.95c-.39.39-.902.586-1.414.586a1.994 1.994 0 0 1-1.414-.586l-4.95-4.95a2 2 0 1 1 2.828-2.828l3.536 3.535z"/></symbol><symbol viewBox="0 0 16 16" id="chevron-left" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7.977 7.998l3.535-3.535a2 2 0 1 0-2.828-2.828l-4.95 4.949c-.39.39-.586.902-.586 1.414 0 .512.196 1.024.586 1.414l4.95 4.95a2 2 0 1 0 2.828-2.828L7.977 7.998z"/></symbol><symbol viewBox="0 0 16 16" id="chevron-right" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8.22 7.998L4.683 4.463a2 2 0 0 1 2.828-2.828l4.95 4.949c.39.39.586.902.586 1.414a1.99 1.99 0 0 1-.586 1.414l-4.95 4.95a2 2 0 0 1-2.828-2.828l3.535-3.536z"/></symbol><symbol viewBox="0 0 16 16" id="chevron-up" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7.778 8.957l3.535 3.535a2 2 0 1 0 2.828-2.828l-4.949-4.95a1.994 1.994 0 0 0-1.414-.586c-.512 0-1.024.196-1.414.586l-4.95 4.95a2 2 0 1 0 2.828 2.828l3.536-3.535z"/></symbol><symbol viewBox="0 0 16 16" id="clock" xmlns="http://www.w3.org/2000/svg"><path d="M9 7h1a1 1 0 0 1 0 2H8a.997.997 0 0 1-1-1V5a1 1 0 1 1 2 0v2zm-1 9A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12z"/></symbol><symbol viewBox="0 0 16 16" id="close" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M9.414 8l4.95-4.95a1 1 0 0 0-1.414-1.414L8 6.586l-4.95-4.95A1 1 0 0 0 1.636 3.05L6.586 8l-4.95 4.95a1 1 0 1 0 1.414 1.414L8 9.414l4.95 4.95a1 1 0 1 0 1.414-1.414L9.414 8z"/></symbol><symbol viewBox="0 0 16 16" id="code" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M15.871 8.243a.997.997 0 0 0-.293-.707L12.75 4.707a1 1 0 0 0-1.414 1.414l2.12 2.122-2.12 2.121a1 1 0 0 0 1.414 1.414l2.828-2.828a.997.997 0 0 0 .293-.707zm-13.243 0L4.75 6.12a1 1 0 1 0-1.414-1.414L.507 7.536a.997.997 0 0 0 0 1.414l2.829 2.828a1 1 0 1 0 1.414-1.414L2.628 8.243zm6.407-4.107a1 1 0 0 1 .707 1.225L8.19 11.157a1 1 0 1 1-1.931-.518L7.81 4.843a1 1 0 0 1 1.224-.707z"/></symbol><symbol viewBox="0 0 9 13" id="collapse"><path d="M.084.25C.01.18-.015.12.008.071.031.024.093 0 .194 0h8.521c.1 0 .162.024.185.072.023.048-.002.107-.075.177l-4.11 3.935a.372.372 0 0 1-.11.072h-.301a.508.508 0 0 1-.11-.072L.084.249zM.377 6.88a.364.364 0 0 1-.26-.105.334.334 0 0 1-.11-.25v-.709c0-.096.036-.179.11-.249a.364.364 0 0 1 .26-.105h8.15c.101 0 .188.035.261.105.074.07.11.153.11.25v.709c0 .096-.036.179-.11.249a.364.364 0 0 1-.26.105H.377zM.084 12.132c-.074.07-.099.129-.076.177.023.048.085.072.186.072h8.521c.1 0 .162-.024.185-.072.023-.048-.002-.107-.075-.177l-4.11-3.935a.372.372 0 0 0-.11-.072h-.301a.508.508 0 0 0-.11.072l-4.11 3.935z"/></symbol><symbol viewBox="0 0 16 16" id="comment" xmlns="http://www.w3.org/2000/svg"><path d="M1.707 15.707C1.077 16.337 0 15.891 0 15V3a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H5.414l-3.707 3.707zM2 12.586l2.293-2.293A1 1 0 0 1 5 10h8a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v9.586z"/></symbol><symbol viewBox="0 0 16 16" id="comment-dots" xmlns="http://www.w3.org/2000/svg"><path d="M1.707 15.707C1.077 16.337 0 15.891 0 15V3a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H5.414l-3.707 3.707zM2 12.586l2.293-2.293A1 1 0 0 1 5 10h8a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v9.586zM5 7a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm3 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm3 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="comment-next" xmlns="http://www.w3.org/2000/svg"><path d="M8 5V4a.5.5 0 0 1 .8-.4l2.667 2a.5.5 0 0 1 0 .8L8.8 8.4A.5.5 0 0 1 8 8V7H6a1 1 0 1 1 0-2h2zM1.707 15.707C1.077 16.337 0 15.891 0 15V3a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H5.414l-3.707 3.707zM2 12.586l2.293-2.293A1 1 0 0 1 5 10h8a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v9.586z"/></symbol><symbol viewBox="0 0 16 16" id="comments" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M3.75 10L0 13V3a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2H3.75zM13 5h1a2 2 0 0 1 2 2v8l-2.667-2H8a2 2 0 0 1-2-2h4a3 3 0 0 0 3-3V5z"/></symbol><symbol viewBox="0 0 16 16" id="commit" xmlns="http://www.w3.org/2000/svg"><path d="M8 10a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm3.876-1.008a4.002 4.002 0 0 1-7.752 0A1.01 1.01 0 0 1 4 9H1a1 1 0 1 1 0-2h3c.042 0 .083.003.124.008a4.002 4.002 0 0 1 7.752 0A1.01 1.01 0 0 1 12 7h3a1 1 0 0 1 0 2h-3a1.01 1.01 0 0 1-.124-.008z"/></symbol><symbol viewBox="0 0 16 16" id="credit-card" xmlns="http://www.w3.org/2000/svg"><path d="M14 5a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1h12zm0 3H2v3a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V8zM3 2h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3zm6.5 8h3a.5.5 0 1 1 0 1h-3a.5.5 0 1 1 0-1z"/></symbol><symbol viewBox="0 0 16 16" id="cut" xmlns="http://www.w3.org/2000/svg"><rect width="16" height="2" y="7" fill-rule="evenodd" rx="1"/></symbol><symbol viewBox="0 0 16 16" id="dashboard" xmlns="http://www.w3.org/2000/svg"><path d="M7.709 10.021l.696-2.6a.5.5 0 0 1 .966.26l-.657 2.45A2 2 0 0 1 10 12H6a2 2 0 0 1 1.709-1.979zM0 8.9a8 8 0 0 1 15.998 0H16v3.6a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5V8.9zM14 9A6 6 0 1 0 2 9v3.5a.5.5 0 0 0 .5.5h11a.5.5 0 0 0 .5-.5V9zM3.5 9a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm9 0a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm-7-3a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm5 0a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1z"/></symbol><symbol viewBox="0 0 16 16" id="disk" xmlns="http://www.w3.org/2000/svg"><path d="M16 11.764V3a3 3 0 0 0-3-3H3a3 3 0 0 0-3 3v8.764A2.989 2.989 0 0 1 2 11V3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v8c.768 0 1.47.289 2 .764zM2 12h12a2 2 0 1 1 0 4H2a2 2 0 1 1 0-4zm10 1a1 1 0 1 0 0 2 1 1 0 0 0 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="doc_code" xmlns="http://www.w3.org/2000/svg"><path d="M8 2H5a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V7h-3a2 2 0 0 1-2-2V2zm2 .414V5h2.586L10 2.414zm1.036 7.607a.498.498 0 0 1-.147.354l-1.414 1.414a.5.5 0 0 1-.707-.707l1.06-1.06-1.06-1.061a.5.5 0 0 1 .707-.707l1.414 1.414a.498.498 0 0 1 .147.353zm-4.822 0l1.06 1.061a.5.5 0 0 1-.706.707l-1.414-1.414a.498.498 0 0 1 0-.707l1.414-1.414a.5.5 0 1 1 .707.707l-1.06 1.06zM5 0h4.586A2 2 0 0 1 11 .586L14.414 4A2 2 0 0 1 15 5.414V12a4 4 0 0 1-4 4H5a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4z"/></symbol><symbol viewBox="0 0 16 16" id="doc_image" xmlns="http://www.w3.org/2000/svg"><path d="M8 2H5a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V7h-3a2 2 0 0 1-2-2V2zm2 .414V5h2.586L10 2.414zM7.333 9.667l1.313-1.313a.5.5 0 0 1 .708 0L12 11H4l2.188-1.75a.5.5 0 0 1 .624 0l.521.417zM5 0h4.586A2 2 0 0 1 11 .586L14.414 4A2 2 0 0 1 15 5.414V12a4 4 0 0 1-4 4H5a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm.5 8a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zM4 11h8v.7a.3.3 0 0 1-.3.3H4.3a.3.3 0 0 1-.3-.3V11z"/></symbol><symbol viewBox="0 0 16 16" id="doc_text" xmlns="http://www.w3.org/2000/svg"><path d="M8 2H5a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V7h-3a2 2 0 0 1-2-2V2zm2 .414V5h2.586L10 2.414zM5 0h4.586A2 2 0 0 1 11 .586L14.414 4A2 2 0 0 1 15 5.414V12a4 4 0 0 1-4 4H5a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm.5 11h5a.5.5 0 1 1 0 1h-5a.5.5 0 1 1 0-1zm0-2h5a.5.5 0 1 1 0 1h-5a.5.5 0 0 1 0-1zm0-2h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1z"/></symbol><symbol viewBox="0 0 105 26" id="double-headed-arrow" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M1.018 11.089L15.138.614c1.23-.911 3.086-.795 4.147.26.461.46.715 1.045.715 1.651v20.95C20 24.869 18.684 26 17.06 26a3.238 3.238 0 0 1-1.921-.614L1.019 14.911C-.212 14-.347 12.405.714 11.35c.094-.094.195-.18.303-.261zm102.964 0c.108.08.21.167.303.26 1.061 1.056.925 2.65-.303 3.562l-14.12 10.475A3.238 3.238 0 0 1 87.94 26C86.316 26 85 24.87 85 23.475V2.525c0-.606.254-1.192.715-1.65 1.061-1.056 2.917-1.172 4.146-.26l14.12 10.474zM35 17a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm18 0a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm18 0a4 4 0 1 1 0-8 4 4 0 0 1 0 8z"/></symbol><symbol viewBox="0 0 16 16" id="download" xmlns="http://www.w3.org/2000/svg"><path d="M9 12h1a.5.5 0 0 1 .4.8l-2 2.667a.5.5 0 0 1-.8 0l-2-2.667A.5.5 0 0 1 6 12h1V8a1 1 0 1 1 2 0v4zM4 9a1 1 0 1 1 0 2 4 4 0 0 1-1.971-7.481 4 4 0 0 1 6.633-2.505 3.999 3.999 0 0 1 3.82 2.014A4 4 0 0 1 12 11a1 1 0 0 1 0-2 2 2 0 1 0 0-4h-1a2 2 0 0 0-3.112-1.662A2 2 0 1 0 4.268 5H4a2 2 0 1 0 0 4z"/></symbol><symbol viewBox="0 0 16 16" id="duplicate" xmlns="http://www.w3.org/2000/svg"><path d="M14 10h-3a1 1 0 0 1-1-1V6H8.527A.527.527 0 0 0 8 6.527V13a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1v-3zm-4-7H8.527c-.18 0-.355.013-.527.04V3a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2v2H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h4a3 3 0 0 1 3 3zM8.527 4h2.323a.5.5 0 0 1 .35.143l4.65 4.551a.5.5 0 0 1 .15.357V13a3 3 0 0 1-3 3H9a3 3 0 0 1-3-3V6.527A2.527 2.527 0 0 1 8.527 4z"/></symbol><symbol viewBox="0 0 16 16" id="earth" xmlns="http://www.w3.org/2000/svg"><path d="M8.7 2.04l-.082.177c.283.223.422.413.417.571-.008.237-.311.057-.444.274-.133.218.038.542-.112.637-.15.096-.398-.386-.479-.46-.054-.049-.166-.257-.336-.625l-.216-.225a.844.844 0 0 0-.418-.035c-.177.038-.075.1-.035.132.04.032.32.037.452.2.132.164.03.224-.05.298-.054.05-.157.062-.31.035H5.952l-.402.398.03.325.229.455.324-.463c.008-.206.058-.342.15-.41.14-.1.342-.15.534-.085.191.066-.057.218.011.271.068.053.204-.098.313-.02.11.08.07.155.104.322.036.167.254.114.398.328.144.215.19.29.147.483-.043.195-.168.26-.305.232-.138-.028-.107-.246-.275-.348-.168-.102-.266-.114-.386-.054-.12.06-.016.129.023.235.04.106.274.321.224.43-.05.107-.108.116-.42 0-.21-.077-.414-.007-.615.212l-.76.722c-.153.715-.3 1.13-.44 1.243-.211.17-.177-.483-.483-.656-.306-.174-.494-.047-.8-.07-.307-.023-.42.65-.38.873a.434.434 0 0 0 .221.321c.236-.141.39-.184.465-.128.11.084-.144.267-.074.425.07.158.314.069.386.283.073.213.084.48-.05.706-.135.227-.275.178-.4.053-.127-.126-.033-.375-.255-.704-.223-.329-.381-.337-.63-.787-.158-.287-.35-.743-.575-1.366a6 6 0 0 0 3.21 7.198l.001-.075c0-.577-.004-.944-.012-1.102-.011-.236-.95-.945-1.104-1.2-.154-.256-.34-.595-.355-.746-.016-.151.185-.232.344-.325.16-.093-.11-.367.028-.626.137-.258.395-.438.496-.356.101.081.058.228.267.333.209.104.077-.213.456-.178.38.035.143.201.252.216.11.016.113-.127.299-.143.186-.015.282.445.471.622.19.178.452.008.611.043.159.034.267.09.402.255.136.166-.03.352.073.557.103.205 1.07.22 1.433.255.364.034.371.011.371.324s-.166.314-.453.507c-.286.193-.166.462-.38.762-.212.3-.316.062-.622.14-.306.077-.413.382-.452.568-.039.186-.386.094-.877.232-.29.082-.429.144-.569.204a6.002 6.002 0 0 0 7.682-4.3c-.094-.384-.18-.63-.258-.74-.213-.297-.36.21-.924.49-.564.278-.57-.288-.81-.49-.16-.133-.212-.44-.158-.92-.005-.478.02-.828.077-1.049.057-.221.126-.543.207-.965.351-.373.606-.572.764-.595.237-.034.336.374.658.3a.315.315 0 0 0 .035-.01 5.993 5.993 0 0 0-.475-.824l-.309-.043a.646.646 0 0 0-.332-.117c-.205-.02-.025.128-.089.24-.064.112-.235.724-.437.685-.201-.039-.204-.374-.17-.668.036-.294-.077-.35-.2-.412-.124-.062-.325-.213-.556-.295-.232-.082-.123-.175-.093-.274.03-.1.208-.015.193-.058-.014-.044-.313-.135-.266-.167.03-.02.2-.02.506.003l.216-.012.293-.163a.58.58 0 0 0-.376-.22c-.233-.036-.513-.034-.73-.142-.205-.103-.458-.36-.643-.638A5.965 5.965 0 0 0 8.7 2.04zM8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16z"/></symbol><symbol viewBox="0 0 1600 1600" id="ellipsis_v" xmlns="http://www.w3.org/2000/svg"><path d="M1088 1248v192q0 40-28 68t-68 28H800q-40 0-68-28t-28-68v-192q0-40 28-68t68-28h192q40 0 68 28t28 68zm0-512v192q0 40-28 68t-68 28H800q-40 0-68-28t-28-68V736q0-40 28-68t68-28h192q40 0 68 28t28 68zm0-512v192q0 40-28 68t-68 28H800q-40 0-68-28t-28-68V224q0-40 28-68t68-28h192q40 0 68 28t28 68z"/></symbol><symbol viewBox="0 0 18 18" id="emoji_slightly_smiling_face" xmlns="http://www.w3.org/2000/svg"><path d="M13.29 11.098a4.328 4.328 0 0 1-1.618 2.285c-.79.578-1.68.867-2.672.867-.992 0-1.883-.29-2.672-.867a4.328 4.328 0 0 1-1.617-2.285.721.721 0 0 1 .047-.569.715.715 0 0 1 .445-.369.721.721 0 0 1 .568.047.715.715 0 0 1 .37.445 2.91 2.91 0 0 0 1.084 1.518A2.93 2.93 0 0 0 9 12.75a2.93 2.93 0 0 0 1.775-.58 2.913 2.913 0 0 0 1.084-1.518.711.711 0 0 1 .375-.445.737.737 0 0 1 .575-.047c.195.063.34.186.433.37.094.183.11.372.047.568zM7.5 6c0 .414-.146.768-.44 1.06A1.44 1.44 0 0 1 6 7.5a1.44 1.44 0 0 1-1.06-.44A1.445 1.445 0 0 1 4.5 6c0-.414.146-.768.44-1.06A1.44 1.44 0 0 1 6 4.5c.414 0 .768.146 1.06.44.294.292.44.646.44 1.06zm6 0c0 .414-.146.768-.44 1.06A1.44 1.44 0 0 1 12 7.5a1.44 1.44 0 0 1-1.06-.44A1.445 1.445 0 0 1 10.5 6c0-.414.146-.768.44-1.06A1.44 1.44 0 0 1 12 4.5c.414 0 .768.146 1.06.44.294.292.44.646.44 1.06zm3 3a7.29 7.29 0 0 0-.598-2.912 7.574 7.574 0 0 0-1.6-2.39 7.574 7.574 0 0 0-2.39-1.6A7.29 7.29 0 0 0 9 1.5a7.29 7.29 0 0 0-2.912.598 7.574 7.574 0 0 0-2.39 1.6 7.574 7.574 0 0 0-1.6 2.39A7.29 7.29 0 0 0 1.5 9c0 1.016.2 1.986.598 2.912a7.574 7.574 0 0 0 1.6 2.39 7.574 7.574 0 0 0 2.39 1.6A7.29 7.29 0 0 0 9 16.5a7.29 7.29 0 0 0 2.912-.598 7.574 7.574 0 0 0 2.39-1.6 7.574 7.574 0 0 0 1.6-2.39A7.29 7.29 0 0 0 16.5 9zM18 9a8.804 8.804 0 0 1-1.207 4.518 8.96 8.96 0 0 1-3.275 3.275A8.804 8.804 0 0 1 9 18a8.804 8.804 0 0 1-4.518-1.207 8.96 8.96 0 0 1-3.275-3.275A8.804 8.804 0 0 1 0 9c0-1.633.402-3.139 1.207-4.518a8.96 8.96 0 0 1 3.275-3.275A8.804 8.804 0 0 1 9 0c1.633 0 3.139.402 4.518 1.207a8.96 8.96 0 0 1 3.275 3.275A8.804 8.804 0 0 1 18 9z" fill-rule="evenodd"/></symbol><symbol viewBox="0 0 18 18" id="emoji_smile" xmlns="http://www.w3.org/2000/svg"><path d="M13.29 11.098a4.328 4.328 0 0 1-1.618 2.285c-.79.578-1.68.867-2.672.867-.992 0-1.883-.29-2.672-.867a4.328 4.328 0 0 1-1.617-2.285.721.721 0 0 1 .047-.569.715.715 0 0 1 .445-.369c.195-.062 7.41-.062 7.606 0 .195.063.34.186.433.37.094.183.11.372.047.568zM14 6.37c0 .398-.04.755-.513.755-.473 0-.498-.272-1.237-.272-.74 0-.74.215-1.165.215-.425 0-.585-.3-.585-.698 0-.397.17-.736.513-1.017.341-.281.754-.422 1.237-.422.483 0 .896.14 1.237.422.342.28.513.62.513 1.017zm-6.5 0c0 .398-.04.755-.513.755-.473 0-.498-.272-1.237-.272-.74 0-.74.215-1.165.215-.425 0-.585-.3-.585-.698 0-.397.17-.736.513-1.017.341-.281.754-.422 1.237-.422.483 0 .896.14 1.237.422.342.28.513.62.513 1.017zm9 2.63a7.29 7.29 0 0 0-.598-2.912 7.574 7.574 0 0 0-1.6-2.39 7.574 7.574 0 0 0-2.39-1.6A7.29 7.29 0 0 0 9 1.5a7.29 7.29 0 0 0-2.912.598 7.574 7.574 0 0 0-2.39 1.6 7.574 7.574 0 0 0-1.6 2.39A7.29 7.29 0 0 0 1.5 9c0 1.016.2 1.986.598 2.912a7.574 7.574 0 0 0 1.6 2.39 7.574 7.574 0 0 0 2.39 1.6A7.29 7.29 0 0 0 9 16.5a7.29 7.29 0 0 0 2.912-.598 7.574 7.574 0 0 0 2.39-1.6 7.574 7.574 0 0 0 1.6-2.39A7.29 7.29 0 0 0 16.5 9zM18 9a8.804 8.804 0 0 1-1.207 4.518 8.96 8.96 0 0 1-3.275 3.275A8.804 8.804 0 0 1 9 18a8.804 8.804 0 0 1-4.518-1.207 8.96 8.96 0 0 1-3.275-3.275A8.804 8.804 0 0 1 0 9c0-1.633.402-3.139 1.207-4.518a8.96 8.96 0 0 1 3.275-3.275A8.804 8.804 0 0 1 9 0c1.633 0 3.139.402 4.518 1.207a8.96 8.96 0 0 1 3.275 3.275A8.804 8.804 0 0 1 18 9z" fill-rule="evenodd"/></symbol><symbol viewBox="0 0 18 18" id="emoji_smiley" xmlns="http://www.w3.org/2000/svg"><path d="M13.29 11.098a4.328 4.328 0 0 1-1.618 2.285c-.79.578-1.68.867-2.672.867-.992 0-1.883-.29-2.672-.867a4.328 4.328 0 0 1-1.617-2.285.721.721 0 0 1 .047-.569.715.715 0 0 1 .445-.369c.195-.062 7.41-.062 7.606 0 .195.063.34.186.433.37.094.183.11.372.047.568h.001zM7.5 6c0 .414-.146.768-.44 1.06A1.44 1.44 0 0 1 6 7.5a1.44 1.44 0 0 1-1.06-.44A1.445 1.445 0 0 1 4.5 6c0-.414.146-.768.44-1.06A1.44 1.44 0 0 1 6 4.5c.414 0 .768.146 1.06.44.294.292.44.646.44 1.06zm6 0c0 .414-.146.768-.44 1.06A1.44 1.44 0 0 1 12 7.5a1.44 1.44 0 0 1-1.06-.44A1.445 1.445 0 0 1 10.5 6c0-.414.146-.768.44-1.06A1.44 1.44 0 0 1 12 4.5c.414 0 .768.146 1.06.44.294.292.44.646.44 1.06zm3 3a7.29 7.29 0 0 0-.598-2.912 7.574 7.574 0 0 0-1.6-2.39 7.574 7.574 0 0 0-2.39-1.6A7.29 7.29 0 0 0 9 1.5a7.29 7.29 0 0 0-2.912.598 7.574 7.574 0 0 0-2.39 1.6 7.574 7.574 0 0 0-1.6 2.39A7.29 7.29 0 0 0 1.5 9c0 1.016.2 1.986.598 2.912a7.574 7.574 0 0 0 1.6 2.39 7.574 7.574 0 0 0 2.39 1.6c.92.397 1.91.6 2.912.598a7.29 7.29 0 0 0 2.912-.598 7.574 7.574 0 0 0 2.39-1.6 7.574 7.574 0 0 0 1.6-2.39c.397-.92.6-1.91.598-2.912zM18 9a8.804 8.804 0 0 1-1.207 4.518 8.96 8.96 0 0 1-3.275 3.275A8.804 8.804 0 0 1 9 18a8.804 8.804 0 0 1-4.518-1.207 8.96 8.96 0 0 1-3.275-3.275A8.804 8.804 0 0 1 0 9c0-1.633.402-3.139 1.207-4.518a8.96 8.96 0 0 1 3.275-3.275A8.804 8.804 0 0 1 9 0c1.633 0 3.139.402 4.518 1.207a8.96 8.96 0 0 1 3.275 3.275A8.804 8.804 0 0 1 18 9z"/></symbol><symbol viewBox="0 0 16 16" id="epic" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M14.985 8.044l-.757 2.272a1 1 0 0 1-.949.684H1.618a1 1 0 0 1-.894-1.447l.318-.637A2 2 0 0 0 1.618 9h11.661a2 2 0 0 0 1.706-.956zm0 3l-.757 2.272a1 1 0 0 1-.949.684H1.618a1 1 0 0 1-.894-1.447l.318-.637a2 2 0 0 0 .576.084h11.661a2 2 0 0 0 1.706-.956zM3.618 2h10.995a1 1 0 0 1 .948 1.316l-1.333 4a1 1 0 0 1-.949.684H1.618a1 1 0 0 1-.894-1.447l2-4A1 1 0 0 1 3.618 2zm-.382 4h9.322l.667-2H4.236l-1 2z"/></symbol><symbol viewBox="0 0 16 16" id="external-link" xmlns="http://www.w3.org/2000/svg"><path d="M13.121 4.177l-4.95 4.95a1 1 0 1 1-1.414-1.414l4.95-4.95-1.386-1.386a.5.5 0 0 1 .299-.85l4.709-.524a.5.5 0 0 1 .552.552l-.523 4.71a.5.5 0 0 1-.851.297l-1.386-1.385zM12 8.884a1 1 0 0 1 2 0v4a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3v-8a3 3 0 0 1 3-3h4a1 1 0 1 1 0 2H3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-4z"/></symbol><symbol viewBox="0 0 16 16" id="eye" xmlns="http://www.w3.org/2000/svg"><path d="M8 14C4.816 14 2.253 12.284.393 8.981a2 2 0 0 1 0-1.962C2.253 3.716 4.816 2 8 2s5.747 1.716 7.607 5.019a2 2 0 0 1 0 1.962C13.747 12.284 11.184 14 8 14zm0-2c2.41 0 4.338-1.29 5.864-4C12.338 5.29 10.411 4 8 4 5.59 4 3.662 5.29 2.136 8 3.662 10.71 5.589 12 8 12zm0-1a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm1-3a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="eye-slash" xmlns="http://www.w3.org/2000/svg"><path d="M13.618 2.62L1.62 14.619a1 1 0 0 1-.985-1.668l1.525-1.526C1.516 10.742.926 9.927.393 8.981a2 2 0 0 1 0-1.962C2.253 3.716 4.816 2 8 2c1.074 0 2.076.195 3.006.58l.944-.944a1 1 0 0 1 1.668.985zM8.068 11a3 3 0 0 0 2.931-2.932l-2.931 2.931zm-3.02-2.462a3 3 0 0 1 3.49-3.49l.884-.884A6.044 6.044 0 0 0 8 4C5.59 4 3.662 5.29 2.136 8c.445.79.924 1.46 1.439 2.011l1.473-1.473zm.421 5.06l1.658-1.658c.283.04.575.06.873.06 2.41 0 4.338-1.29 5.864-4a11.023 11.023 0 0 0-1.133-1.664l1.418-1.418a12.799 12.799 0 0 1 1.458 2.1 2 2 0 0 1 0 1.963C13.747 12.284 11.184 14 8 14a7.883 7.883 0 0 1-2.53-.402z"/></symbol><symbol viewBox="0 0 16 16" id="file-addition" xmlns="http://www.w3.org/2000/svg"><path d="M7 7V5a1 1 0 1 1 2 0v2h2a1 1 0 0 1 0 2H9v2a1 1 0 0 1-2 0V9H5a1 1 0 1 1 0-2h2zM3 0h10a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2H3z"/></symbol><symbol viewBox="0 0 16 16" id="file-deletion" xmlns="http://www.w3.org/2000/svg"><path d="M3 0h10a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2H3zm2 6h6a1 1 0 0 1 0 2H5a1 1 0 1 1 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="file-modified" xmlns="http://www.w3.org/2000/svg"><path d="M3 0h10a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2H3zm5 4a3 3 0 1 1 0 6 3 3 0 0 1 0-6z"/></symbol><symbol viewBox="0 0 16 16" id="filter" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 6v9l-3.724-1.862A.5.5 0 0 1 6 12.691V6L1.854 1.854A.5.5 0 0 1 2.207 1h11.586a.5.5 0 0 1 .353.854L10 6z"/></symbol><symbol viewBox="0 0 16 16" id="folder" xmlns="http://www.w3.org/2000/svg"><path d="M13 3a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a2 2 0 0 1 2-2h3.81a3 3 0 0 1 2.827 1.995L13 3z"/></symbol><symbol viewBox="0 0 16 16" id="folder-o" xmlns="http://www.w3.org/2000/svg"><path d="M13 5l-4.365-.005a2 2 0 0 1-1.882-1.33A1 1 0 0 0 5.81 3H2v9a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1zm0-2a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a2 2 0 0 1 2-2h3.81a3 3 0 0 1 2.827 1.995L13 3z"/></symbol><symbol viewBox="0 0 16 16" id="folder-open" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M14.59 5.464a2.998 2.998 0 0 1 1.096 3.845l-1.666 3.436A4 4 0 0 1 10.46 15H3a3 3 0 0 1-3-3V3a2 2 0 0 1 2-2h3.558a2 2 0 0 1 1.898 1.368l.21.632h4.973a2 2 0 0 1 2 2 2 2 0 0 1-.027.329l-.023.135zM5.285 7a1 1 0 0 0-.9.564l-1.939 4a1 1 0 0 0 .9 1.436h7.074a2 2 0 0 0 1.8-1.128l1.665-3.436a1 1 0 0 0-.9-1.436h-7.7z"/></symbol><symbol viewBox="0 0 16 16" id="fork" xmlns="http://www.w3.org/2000/svg"><path d="M9 12.268a2 2 0 1 1-2 0V8.874A4.002 4.002 0 0 1 4 5V3.732a2 2 0 1 1 2 0V5a2 2 0 1 0 4 0V3.732a2 2 0 1 1 2 0V5a4.002 4.002 0 0 1-3 3.874v3.394zM11 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2zM5 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 12a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="geo-nodes" xmlns="http://www.w3.org/2000/svg"><path d="M9.7 13.1l-.2.2c-.7.8-2 .9-2.8.1-.1 0-.1-.1-.1-.1l-.2-.2c-2 .2-3.4.7-3.4 1.4 0 .8 2.2 1.5 5 1.5s5-.7 5-1.5c0-.7-1.4-1.2-3.3-1.4M7.3 12.7c.4.4 1 .3 1.4-.1C11.6 9.5 13 7 13 5.3 13 2.4 10.8 0 8 0S3 2.4 3 5.3C3 7 4.4 9.5 7.3 12.7M8 2c1.6 0 3 1.4 3 3.3 0 1-1 2.8-3 5.2-2-2.4-3-4.2-3-5.2C5 3.4 6.4 2 8 2"/><circle cx="8" cy="5" r="1"/></symbol><symbol viewBox="0 0 16 16" id="git-merge" xmlns="http://www.w3.org/2000/svg"><path d="M11 12.268V5a1 1 0 0 0-1-1v1a.5.5 0 0 1-.8.4l-2.667-2a.5.5 0 0 1 0-.8L9.2.6a.5.5 0 0 1 .8.4v1a3 3 0 0 1 3 3v7.268a2 2 0 1 1-2 0zm-6 0a2 2 0 1 1-2 0V4.732a2 2 0 1 1 2 0v7.536zM4 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 11a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm8 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="group" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M3.048 11.997C-.377 11.975.013 11.782.013 10.56.013 9.235.653 8 4 8c.444 0 .84.022 1.194.062.164.435.426.82.76 1.132-1.786.389-2.721 1.353-2.906 2.803zm2.94-7.222a2.993 2.993 0 0 0-.976 1.95 2 2 0 1 1 .975-1.95zm6.964 7.222c-.185-1.45-1.12-2.414-2.906-2.803.334-.311.596-.697.76-1.132C11.16 8.022 11.556 8 12 8c3.346 0 3.987 1.235 3.987 2.56 0 1.222.39 1.415-3.035 1.437zm-1.964-5.272a2.993 2.993 0 0 0-.976-1.95 2 2 0 1 1 .976 1.95zM8 9a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm0 5c-2.177 0-3.987-.115-3.987-1.44S4.653 10 8 10c3.346 0 3.987 1.235 3.987 2.56S10.177 14 8 14z"/></symbol><symbol viewBox="0 0 16 16" id="history" xmlns="http://www.w3.org/2000/svg"><path d="M2.868 3.24a7 7 0 1 1-.043 9.475 1 1 0 0 1 1.478-1.348 5 5 0 1 0 .124-6.865l.796.645a.5.5 0 0 1-.193.873l-3.232.814a.5.5 0 0 1-.622-.504L1.3 3a.5.5 0 0 1 .814-.37l.754.61zM9 8h1a1 1 0 0 1 0 2H8a.997.997 0 0 1-1-1V6a1 1 0 1 1 2 0v2z"/></symbol><symbol viewBox="0 0 16 16" id="home" xmlns="http://www.w3.org/2000/svg"><path d="M9 13h3v-3H4v3h3v-1a1 1 0 0 1 2 0v1zm5-3v3.659c0 .729-.657 1.341-1.5 1.341h-9c-.843 0-1.5-.612-1.5-1.341V10h-.88C.502 10 0 9.486 0 8.853c0-.307.12-.601.333-.816l6.405-6.463a1.56 1.56 0 0 1 2.374-.052L15.66 8.03c.444.441.455 1.167.024 1.622a1.108 1.108 0 0 1-.804.348H14zM7.95 3.273l-4.595 4.64h9.264l-4.67-4.64z"/></symbol><symbol viewBox="0 0 16 16" id="hook" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 3a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1h4zm0 1H6v1a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1V4zM7 8a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h2a3 3 0 0 1 3 3v2a3 3 0 0 1-3 3v4a2 2 0 1 0 4 0h-.44a.3.3 0 0 1-.25-.466l1.44-2.16a.3.3 0 0 1 .5 0l1.44 2.16a.3.3 0 0 1-.25.466H15a4 4 0 0 1-7 2.646A4 4 0 0 1 1 12H.56a.3.3 0 0 1-.25-.466l1.44-2.16a.3.3 0 0 1 .5 0l1.44 2.16a.3.3 0 0 1-.25.466H3a2 2 0 1 0 4 0V8z"/></symbol><symbol viewBox="0 0 16 16" id="hourglass" xmlns="http://www.w3.org/2000/svg"><path d="M10.331 4.889A2.988 2.988 0 0 0 11 3V2H5v1c0 .362.064.709.182 1.03l5.15.859zM3 14v-1c0-1.78.93-3.342 2.33-4.228.447-.327.67-.582.67-.764 0-.19-.242-.46-.725-.815A4.996 4.996 0 0 1 3 3V2H2a1 1 0 1 1 0-2h12a1 1 0 0 1 0 2h-1v1a4.997 4.997 0 0 1-2.39 4.266c-.407.3-.61.545-.61.734 0 .19.203.434.61.734A4.997 4.997 0 0 1 13 13v1h1a1 1 0 0 1 0 2H2a1 1 0 0 1 0-2h1zm8 0v-1a3 3 0 0 0-6 0v1h6z"/></symbol><symbol viewBox="0 0 38 38" id="image-comment-dark" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><circle cx="19" cy="19" r="18" fill="#1F78D1"/><path fill="#FFF" fill-rule="nonzero" d="M19 38C8.507 38 0 29.493 0 19S8.507 0 19 0s19 8.507 19 19-8.507 19-19 19zm0-2c9.389 0 17-7.611 17-17S28.389 2 19 2 2 9.611 2 19s7.611 17 17 17zm-6.293-8.293c-.63.63-1.707.184-1.707-.707V15a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3h-7.586l-3.707 3.707zM13 24.586l2.293-2.293A1 1 0 0 1 16 22h8a1 1 0 0 0 1-1v-6a1 1 0 0 0-1-1H14a1 1 0 0 0-1 1v9.586z"/></g></symbol><symbol viewBox="0 0 38 38" id="image-comment-light" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><circle cx="19" cy="19" r="18" fill="#FFF"/><path fill="#1F78D1" fill-rule="nonzero" d="M19 38C8.507 38 0 29.493 0 19S8.507 0 19 0s19 8.507 19 19-8.507 19-19 19zm0-2c9.389 0 17-7.611 17-17S28.389 2 19 2 2 9.611 2 19s7.611 17 17 17zm-6.293-8.293c-.63.63-1.707.184-1.707-.707V15a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3h-7.586l-3.707 3.707zM13 24.586l2.293-2.293A1 1 0 0 1 16 22h8a1 1 0 0 0 1-1v-6a1 1 0 0 0-1-1H14a1 1 0 0 0-1 1v9.586z"/></g></symbol><symbol viewBox="0 0 16 16" id="import" xmlns="http://www.w3.org/2000/svg"><path d="M9 8h1a.5.5 0 0 1 .4.8l-2 2.667a.5.5 0 0 1-.8 0L5.6 8.8A.5.5 0 0 1 6 8h1V1a1 1 0 1 1 2 0v7zM0 8a1 1 0 1 1 2 0 6 6 0 1 0 12 0 1 1 0 0 1 2 0A8 8 0 1 1 0 8z"/></symbol><symbol viewBox="0 0 16 16" id="issue-block" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5.803 8a5.97 5.97 0 0 0-.462 1H4.5a.5.5 0 0 1 0-1h1.303zM4.5 5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1 0-1zm7.5.083a6.04 6.04 0 0 0-2 0V3a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h2.083a5.96 5.96 0 0 0 .72 2H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h6a3 3 0 0 1 3 3v2.083zm1.121 3.796zM11 16a5 5 0 1 1 0-10 5 5 0 0 1 0 10zm-1.293-2.292a3 3 0 0 0 4.001-4.001l-4.001 4zm-1.415-1.415l4.001-4a3 3 0 0 0-4.001 4.001z"/></symbol><symbol viewBox="0 0 16 16" id="issue-child" xmlns="http://www.w3.org/2000/svg"><path d="M11 8H5v1h1a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h2V7a.997.997 0 0 1 1-1h3V4H4.5a.5.5 0 0 1-.5-.5v-2a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.5.5H9v2h3a.997.997 0 0 1 1 1v2h2a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-5a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h1V8zm-9 3v2h3v-2H2zm9 0v2h3v-2h-3z"/></symbol><symbol viewBox="0 0 16 16" id="issue-close" xmlns="http://www.w3.org/2000/svg"><path d="M7.536 8.657l2.828-2.829a1 1 0 0 1 1.414 1.415l-3.535 3.535a.997.997 0 0 1-1.415 0l-2.12-2.121A1 1 0 0 1 6.12 7.243l1.415 1.414zM8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12z"/></symbol><symbol viewBox="0 0 16 16" id="issue-duplicate" xmlns="http://www.w3.org/2000/svg"><path d="M10.874 2H12a3 3 0 0 1 3 3v8a3 3 0 0 1-3 3h-2c-.918 0-1.74-.413-2.29-1.063a3.987 3.987 0 0 0 1.988-.984A1 1 0 0 0 10 14h2a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1h-1V3c0-.345-.044-.68-.126-1zM4 0h3a3 3 0 0 1 3 3v8a3 3 0 0 1-3 3H4a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H4z"/></symbol><symbol viewBox="0 0 16 16" id="issue-external" xmlns="http://www.w3.org/2000/svg"><path d="M11 4a5.99 5.99 0 0 0-2 .341V3a1 1 0 0 0-1-1H4a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h2.528a6.003 6.003 0 0 0 2.705 1.736A2.99 2.99 0 0 1 8 16H4a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h4a3 3 0 0 1 3 3v1zM8.212 8.97l-.568-.876A.25.25 0 0 1 7.66 7.8l.404-.5a.25.25 0 0 1 .284-.076l.938.36c.256-.182.543-.325.85-.42l.323-.988a.25.25 0 0 1 .237-.173h.643a.25.25 0 0 1 .238.173l.321.989c.308.094.595.237.852.418l.937-.359a.25.25 0 0 1 .284.076l.404.5a.25.25 0 0 1 .016.293l-.568.875c.113.297.18.616.192.95l.9.54a.25.25 0 0 1 .114.27l-.145.627a.25.25 0 0 1-.221.192l-1.115.098a3.015 3.015 0 0 1-.512.608l.165 1.18a.25.25 0 0 1-.138.259l-.577.282a.25.25 0 0 1-.29-.051l-.874-.905a3.035 3.035 0 0 1-.608 0l-.875.905a.25.25 0 0 1-.29.05l-.577-.281a.25.25 0 0 1-.138-.26L9 12.254a3.015 3.015 0 0 1-.512-.607l-1.114-.098a.25.25 0 0 1-.222-.192l-.145-.627a.25.25 0 0 1 .115-.27l.899-.54c.012-.334.08-.653.192-.95zm2.806 2.034a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="issue-new" xmlns="http://www.w3.org/2000/svg"><path d="M10 2V1a1 1 0 0 1 2 0v1h1a1 1 0 0 1 0 2h-1v1a1 1 0 0 1-2 0V4H9a1 1 0 1 1 0-2h1zm0 6a1 1 0 0 1 2 0v5a3 3 0 0 1-3 3H5a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3h1a1 1 0 1 1 0 2H5a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1V8z"/></symbol><symbol viewBox="0 0 16 16" id="issue-open" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zm0-2a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm0-2a2 2 0 1 0 0-4 2 2 0 0 0 0 4z"/></symbol><symbol viewBox="0 0 16 16" id="issue-open-m" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12z"/></symbol><symbol viewBox="0 0 16 16" id="issue-parent" xmlns="http://www.w3.org/2000/svg"><path d="M11 11H5v1h1.5a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.5.5h-6a.5.5 0 0 1-.5-.5v-2a.5.5 0 0 1 .5-.5H3v-2a.997.997 0 0 1 1-1h3V7H5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1H9v2h3a.997.997 0 0 1 1 1v2h2.5a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.5.5h-6a.5.5 0 0 1-.5-.5v-2a.5.5 0 0 1 .5-.5H11v-1zM6 3v2h4V3H6z"/></symbol><symbol viewBox="0 0 16 16" id="issues" xmlns="http://www.w3.org/2000/svg"><path d="M10.458 15.012l.311.055a3 3 0 0 0 3.476-2.433l1.389-7.879A3 3 0 0 0 13.2 1.28L11.23.933a3.002 3.002 0 0 0-.824-.031c.364.59.58 1.28.593 2.02l1.854.328a1 1 0 0 1 .811 1.158l-1.389 7.879a1 1 0 0 1-1.158.81l-.118-.02a3.98 3.98 0 0 1-.541 1.935zM3 0h4a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3z"/></symbol><symbol viewBox="0 0 16 16" id="italic" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5.5 12l2-8H6a1 1 0 1 1 0-2h6a1 1 0 0 1 0 2h-1.5l-2 8H10a1 1 0 0 1 0 2H4a1 1 0 0 1 0-2h1.5z"/></symbol><symbol viewBox="0 0 16 16" id="key" xmlns="http://www.w3.org/2000/svg"><path d="M7.575 6.689a4.002 4.002 0 0 1 6.274-4.86 4 4 0 0 1-4.86 6.274l-2.21 2.21.706.708a1 1 0 1 1-1.414 1.414l-.707-.707-.707.707.707.707a1 1 0 1 1-1.414 1.414l-.707-.707a1 1 0 0 1-1.414-1.414l5.746-5.746zm2.032-.618a2 2 0 1 0 2.828-2.828A2 2 0 0 0 9.607 6.07z"/></symbol><symbol viewBox="0 0 16 16" id="key-2" xmlns="http://www.w3.org/2000/svg"><path d="M5.172 14.157l-.344.344-2.485.133a.462.462 0 0 1-.497-.503l.14-2.24a.599.599 0 0 1 .177-.382l5.155-5.155a4 4 0 1 1 2.828 2.828l-1.439 1.44-1.06-.354-.708.707.354 1.06-.707.708-1.06-.354-.708.707.354 1.06zm6.01-8.839a1 1 0 1 0 1.414-1.414 1 1 0 0 0-1.414 1.414z"/></symbol><symbol viewBox="0 0 16 16" id="label" xmlns="http://www.w3.org/2000/svg"><path d="M11.782 14.718a3 3 0 0 1-4.242 0L1.652 8.829a2 2 0 0 1-.565-1.702l.54-3.703a2 2 0 0 1 1.69-1.69l3.703-.54a2 2 0 0 1 1.703.564l5.888 5.888a3 3 0 0 1 0 4.243l-2.829 2.829zm1.415-5.657L7.309 3.173l-3.703.54-.54 3.702 5.888 5.888a1 1 0 0 0 1.414 0l2.829-2.828a1 1 0 0 0 0-1.414zM5.732 5.525A1 1 0 1 1 7.146 6.94a1 1 0 0 1-1.414-1.414z"/></symbol><symbol viewBox="0 0 16 16" id="labels" xmlns="http://www.w3.org/2000/svg"><path d="M9.424 2.254l2.08-.905a1 1 0 0 1 1.206.326l3.013 4.12a1 1 0 0 1 .16.849l-1.947 7.264a3 3 0 0 1-3.675 2.122l-.5-.135a3.999 3.999 0 0 0 1.082-1.782 1 1 0 0 0 1.16-.722l1.823-6.802-2.258-3.087-.687.299a2 2 0 0 0-.628-.88l-.829-.667zM.377 3.7L4.4.498a1 1 0 0 1 1.25.003L9.627 3.7a1 1 0 0 1 .373.78V13a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V4.482A1 1 0 0 1 .377 3.7zM2 13a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1V4.958L5.02 2.561 2 4.964V13zm3-6a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="leave" xmlns="http://www.w3.org/2000/svg"><path d="M11 7V5.883a.5.5 0 0 1 .757-.429l3.528 2.117a.5.5 0 0 1 0 .858l-3.528 2.117a.5.5 0 0 1-.757-.43V9H7a1 1 0 1 1 0-2h4zm-2 6.256a1 1 0 0 1 2 0A2.744 2.744 0 0 1 8.256 16H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h5.19A2.81 2.81 0 0 1 11 2.81a1 1 0 0 1-2 0A.81.81 0 0 0 8.19 2H3a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h5.256c.41 0 .744-.333.744-.744z"/></symbol><symbol viewBox="0 0 16 16" id="level-up" xmlns="http://www.w3.org/2000/svg"><path fill="#2E2E2E" fill-rule="evenodd" d="M7 6h3.489a.5.5 0 0 0 .373-.832L6.374.117a.5.5 0 0 0-.748 0l-4.488 5.05A.5.5 0 0 0 1.51 6H5v7a3 3 0 0 0 3 3h6a1 1 0 0 0 0-2H8a1 1 0 0 1-1-1V6z"/></symbol><symbol viewBox="0 0 16 16" id="license" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M12.56 8.9l2.66 4.606a.3.3 0 0 1-.243.45l-1.678.094a.1.1 0 0 0-.078.044l-.953 1.432a.3.3 0 0 1-.51-.016L9.097 10.9a5.994 5.994 0 0 0 3.464-2zm-5.23 2.063L4.707 15.51a.3.3 0 0 1-.51.016l-.953-1.432a.1.1 0 0 0-.078-.044l-1.678-.094a.3.3 0 0 1-.243-.45l2.48-4.297a5.983 5.983 0 0 0 3.607 1.754zM8 10A5 5 0 1 1 8 0a5 5 0 0 1 0 10zm0-2a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0-1a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/></symbol><symbol viewBox="0 0 16 16" id="link" xmlns="http://www.w3.org/2000/svg"><path d="M6.986 3.35l2.12-2.122a4 4 0 0 1 5.657 5.657l-2.828 2.829a4 4 0 0 1-5.657 0 1 1 0 0 1 1.414-1.415 2 2 0 0 0 2.829 0l2.828-2.828a2 2 0 1 0-2.828-2.828l-1.001 1a5.018 5.018 0 0 0-2.534-.294zm2.12 9.192l-2.12 2.121a4 4 0 1 1-5.658-5.656l2.829-2.829a4 4 0 0 1 5.657 0 1 1 0 1 1-1.415 1.414 2 2 0 0 0-2.828 0l-2.828 2.829a2 2 0 1 0 2.828 2.828l1.001-1.001a5.018 5.018 0 0 0 2.534.294z"/></symbol><symbol viewBox="0 0 16 16" id="list-bulleted" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M1 4a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm0 5a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4-7h10a1 1 0 0 1 0 2H5a1 1 0 1 1 0-2zm0 5h10a1 1 0 0 1 0 2H5a1 1 0 1 1 0-2zm-4 7a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4-2h10a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="list-numbered" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M6 2h8a1 1 0 0 1 0 2H6a1 1 0 1 1 0-2zm0 5h8a1 1 0 0 1 0 2H6a1 1 0 1 1 0-2zm0 5h8a1 1 0 0 1 0 2H6a1 1 0 0 1 0-2zM1.156 5v-.828h.816V2.204h-.72v-.636c.432-.084.708-.192.996-.372h.756v2.976h.684V5H1.156zm-.18 5v-.588c.9-.828 1.596-1.464 1.596-1.98 0-.342-.192-.504-.468-.504-.252 0-.444.18-.624.36l-.552-.552c.396-.42.756-.612 1.32-.612.768 0 1.308.492 1.308 1.248 0 .612-.576 1.284-1.092 1.812.192-.024.468-.048.636-.048h.636V10H.976zm1.26 5.072c-.618 0-1.068-.204-1.356-.54l.468-.648c.234.216.51.36.78.36.336 0 .552-.12.552-.36 0-.288-.15-.456-.948-.456v-.72c.636 0 .828-.168.828-.432 0-.228-.138-.348-.396-.348-.252 0-.432.108-.672.312l-.516-.624c.372-.312.768-.492 1.236-.492.84 0 1.38.384 1.38 1.074 0 .366-.204.642-.612.822v.024c.432.132.732.432.732.912 0 .72-.684 1.116-1.476 1.116z"/></symbol><symbol viewBox="0 0 16 16" id="location" xmlns="http://www.w3.org/2000/svg"><path d="M8.755 15.144a1 1 0 0 1-1.51 0C3.748 11.114 2 8.065 2 6a6 6 0 1 1 12 0c0 2.065-1.748 5.113-5.245 9.144zM12 6a4 4 0 1 0-8 0c0 1.314 1.312 3.71 4 6.944C10.688 9.71 12 7.314 12 6zM8 8a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/></symbol><symbol viewBox="0 0 16 16" id="location-dot" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M6.314 13.087C4.382 13.295 3 13.85 3 14.5c0 .828 2.239 1.5 5 1.5s5-.672 5-1.5c0-.65-1.382-1.205-3.314-1.413l-.202.225a2 2 0 0 1-2.968 0l-.202-.225zm2.428-.445a1 1 0 0 1-1.484 0C4.419 9.5 3 7.037 3 5.252 3 2.353 5.239 0 8 0s5 2.352 5 5.253c0 1.784-1.42 4.247-4.258 7.389zM11 5.252C11 3.436 9.634 2 8 2S5 3.435 5 5.253c0 1.027.974 2.824 3 5.203 2.026-2.38 3-4.176 3-5.203zM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="lock" xmlns="http://www.w3.org/2000/svg"><path d="M10 5V4h2v1a3 3 0 0 1 3 3v5a3 3 0 0 1-3 3H4a3 3 0 0 1-3-3V8a3 3 0 0 1 3-3V4h2v1h4zM4 7a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V8a1 1 0 0 0-1-1H4zm0-3a4 4 0 1 1 8 0h-2a2 2 0 1 0-4 0H4z"/></symbol><symbol viewBox="0 0 16 16" id="lock-open" xmlns="http://www.w3.org/2000/svg"><path d="M4.044 4a4 4 0 0 1 6.99-2.658 1 1 0 1 1-1.495 1.33A2 2 0 0 0 6.044 4a.998.998 0 0 1-.07.367v.701H12a3 3 0 0 1 3 3v5a3 3 0 0 1-3 3H4a3 3 0 0 1-3-3v-5a3 3 0 0 1 2.974-3V4h.07zM4 7.07a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-5a1 1 0 0 0-1-1H4z"/></symbol><symbol viewBox="0 0 16 16" id="log" xmlns="http://www.w3.org/2000/svg"><path d="M4 0h8a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H4a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H4zm1 4a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm0 3a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm3-5h3a1 1 0 0 1 0 2H8a1 1 0 1 1 0-2zm0 3h3a1 1 0 0 1 0 2H8a1 1 0 1 1 0-2zm-3 5a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm3-2h3a1 1 0 0 1 0 2H8a1 1 0 0 1 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="mail" xmlns="http://www.w3.org/2000/svg"><path d="M14 5.6L9.338 9.796a2 2 0 0 1-2.676 0L2 5.6V11a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V5.6zM3 2h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3zm.212 2L8 8.31 12.788 4H3.212z"/></symbol><symbol viewBox="0 0 16 16" id="menu" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M1.143 2h13.714C15.488 2 16 2.448 16 3s-.512 1-1.143 1H1.143C.512 4 0 3.552 0 3s.512-1 1.143-1zm0 5h13.714C15.488 7 16 7.448 16 8s-.512 1-1.143 1H1.143C.512 9 0 8.552 0 8s.512-1 1.143-1zm0 5h13.714c.631 0 1.143.448 1.143 1s-.512 1-1.143 1H1.143C.512 14 0 13.552 0 13s.512-1 1.143-1z"/></symbol><symbol viewBox="0 0 16 16" id="merge-request-close" xmlns="http://www.w3.org/2000/svg"><path d="M9.414 8l1.414 1.414a1 1 0 1 1-1.414 1.414L8 9.414l-1.414 1.414a1 1 0 1 1-1.414-1.414L6.586 8 5.172 6.586a1 1 0 1 1 1.414-1.414L8 6.586l1.414-1.414a1 1 0 1 1 1.414 1.414L9.414 8zM8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12z"/></symbol><symbol viewBox="0 0 16 16" id="messages" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8.588 8.942l1.173 5.862A1 1 0 0 1 8.78 16H7.22a1 1 0 0 1-.98-1.196l1.172-5.862a3.014 3.014 0 0 0 1.176 0zM8 8a2 2 0 1 1 0-4 2 2 0 0 1 0 4zM4.464 2.464L5.88 3.88a3 3 0 0 0 0 4.242L4.464 9.536a5 5 0 0 1 0-7.072zm7.072 7.072L10.12 8.12a3 3 0 0 0 0-4.242l1.415-1.415a5 5 0 0 1 0 7.072zM2.343.343l1.414 1.414a6 6 0 0 0 0 8.486l-1.414 1.414a8 8 0 0 1 0-11.314zm11.314 11.314l-1.414-1.414a6 6 0 0 0 0-8.486L13.657.343a8 8 0 0 1 0 11.314z"/></symbol><symbol viewBox="0 0 16 16" id="mobile-issue-close" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5.657 10.728L2.12 7.192A1 1 0 1 0 .707 8.607l4.243 4.242a.997.997 0 0 0 1.414 0l8.485-8.485a1 1 0 1 0-1.414-1.414l-7.778 7.778z"/></symbol><symbol viewBox="0 0 16 16" id="monitor" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 13v1h3a1 1 0 0 1 0 2H3a1 1 0 0 1 0-2h3v-1H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v7a3 3 0 0 1-3 3h-3zM3 2a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3zm5.723 6.416l-2.66-1.773-1.71 1.71a.5.5 0 1 1-.707-.707l2-2a.5.5 0 0 1 .631-.062l2.66 1.773 2.71-2.71a.5.5 0 0 1 .707.707l-3 3a.5.5 0 0 1-.631.062z"/></symbol><symbol viewBox="0 0 16 16" id="more" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8 4a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm0 6a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm0 6a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/></symbol><symbol viewBox="0 0 16 16" id="notifications" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M6 14H2.435a2 2 0 0 1-1.761-2.947c.962-1.788 1.521-3.065 1.68-3.832.322-1.566.947-5.501 4.65-6.134a1 1 0 1 1 1.994-.024c3.755.528 4.375 4.27 4.761 6.043.188.86.742 2.188 1.661 3.982A2 2 0 0 1 13.64 14H10a2 2 0 1 1-4 0zm5.805-6.468c-.325-1.492-.37-1.674-.61-2.288C10.6 3.716 9.742 3 8.07 3c-1.608 0-2.49.718-3.103 2.197-.28.676-.356.982-.654 2.428-.208 1.012-.827 2.424-1.877 4.375H13.64c-.993-1.937-1.6-3.396-1.835-4.468z"/></symbol><symbol viewBox="0 0 16 16" id="notifications-off" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M13.26 5.089c.243.757.382 1.478.5 2.017.187.86.74 2.188 1.66 3.982A2 2 0 0 1 13.64 14H10a2 2 0 1 1-4 0H4.35l2-2h7.29c-.993-1.937-1.6-3.396-1.835-4.468-.07-.326-.129-.59-.178-.81l1.634-1.633zM10.943 1.75l-1.48 1.48C9.07 3.076 8.612 3 8.069 3c-1.608 0-2.49.718-3.103 2.197-.28.676-.356.982-.654 2.428-.065.317-.17.673-.317 1.073L.45 12.242a1.99 1.99 0 0 1 .224-1.19c.962-1.787 1.521-3.064 1.68-3.831.322-1.566.947-5.501 4.65-6.134a1 1 0 1 1 1.994-.024 4.867 4.867 0 0 1 1.944.688zm2.932-.105a1 1 0 0 1 0 1.415L2.561 14.374a1 1 0 1 1-1.415-1.414L12.46 1.646a1 1 0 0 1 1.414 0z"/></symbol><symbol viewBox="0 0 16 16" id="overview" xmlns="http://www.w3.org/2000/svg"><path d="M2 0h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2zm0 2v3h3V2H2zm9-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2h-3a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2zm0 2v3h3V2h-3zM2 9h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2zm0 2v3h3v-3H2zm9-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2h-3a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2zm0 2v3h3v-3h-3z"/></symbol><symbol viewBox="0 0 16 16" id="pencil" xmlns="http://www.w3.org/2000/svg"><path d="M13.02 1.293l1.414 1.414a1 1 0 0 1 0 1.414L4.119 14.436a1 1 0 0 1-.704.293l-2.407.008L1 12.316a1 1 0 0 1 .293-.71L11.605 1.292a1 1 0 0 1 1.414 0zm-1.416 1.415l-.707.707L12.31 4.83l.707-.707-1.414-1.415zM3.411 13.73l1.123-1.122H3.12v-1.415L2 12.312l.005 1.422 1.406-.005z"/></symbol><symbol viewBox="0 0 16 16" id="pencil-square" xmlns="http://www.w3.org/2000/svg"><path d="M12 9a1 1 0 0 1 2 0v4a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3h4a1 1 0 1 1 0 2H3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V9zm.778-7.179l1.414 1.415-6.476 6.476a1 1 0 0 1-.498.27l-1.51.325.323-1.512a1 1 0 0 1 .27-.497l6.477-6.477zM15.607.407a1 1 0 0 1 0 1.414l-.708.707-1.414-1.414.707-.707a1 1 0 0 1 1.415 0z"/></symbol><symbol viewBox="0 0 16 16" id="pipeline" xmlns="http://www.w3.org/2000/svg"><path d="M8.969 7.25a2 2 0 1 1-1.938 0A1.002 1.002 0 0 1 7 7V5.083a.2.2 0 0 1 .06-.142l.877-.87a.1.1 0 0 1 .141 0l.864.87A.2.2 0 0 1 9 5.083V7c0 .086-.01.17-.031.25zM8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zm4.5-4a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm0-3a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm-2 6a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm0-9a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm-5 9a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm0-9a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm-2 6a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zm0-3a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1zM8 10a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="play" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M2.765 15.835c-.545.321-1.258.159-1.593-.363A1.075 1.075 0 0 1 1 14.89V1.11C1 .496 1.518 0 2.158 0c.214 0 .424.057.607.165l11.684 6.89c.544.321.714 1.005.38 1.526a1.135 1.135 0 0 1-.38.364l-11.684 6.89z"/></symbol><symbol viewBox="0 0 16 16" id="plus" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7 7H2a1 1 0 1 0 0 2h5v5a1 1 0 0 0 2 0V9h5a1 1 0 0 0 0-2H9V2a1 1 0 1 0-2 0v5z"/></symbol><symbol viewBox="0 0 16 16" id="plus-square" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M9 7V4a1 1 0 1 0-2 0v3H4a1 1 0 1 0 0 2h3v3a1 1 0 0 0 2 0V9h3a1 1 0 0 0 0-2H9zM3 0h10a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3z"/></symbol><symbol viewBox="0 0 16 16" id="plus-square-o" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7 7V5a1 1 0 1 1 2 0v2h2a1 1 0 0 1 0 2H9v2a1 1 0 0 1-2 0V9H5a1 1 0 1 1 0-2h2zM3 0h10a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3z"/></symbol><symbol viewBox="0 0 16 16" id="podcast" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8.588 8.942l1.173 5.862a1 1 0 0 1-.785 1.177A1 1 0 0 1 8.78 16H7.22a1 1 0 0 1-1-1 1 1 0 0 1 .02-.196l1.172-5.862a3.014 3.014 0 0 0 1.176 0zM8 7.5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zM4.464 2.464A1 1 0 0 1 5.88 3.88a3 3 0 0 0 0 4.242 1 1 0 0 1-1.415 1.415 5 5 0 0 1 0-7.072zm7.072 7.072A1 1 0 0 1 10.12 8.12a3 3 0 0 0 0-4.242 1 1 0 0 1 1.415-1.415 5 5 0 0 1 0 7.072zM2.343.343a1 1 0 1 1 1.414 1.414 6 6 0 0 0 0 8.486 1 1 0 1 1-1.414 1.414 8 8 0 0 1 0-11.314zm11.314 11.314a1 1 0 1 1-1.414-1.414 6 6 0 0 0 0-8.486A1 1 0 0 1 13.657.343a8 8 0 0 1 0 11.314z"/></symbol><symbol viewBox="0 0 16 16" id="preferences" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5 12h10a1 1 0 0 1 0 2H5a1 1 0 0 1-2 0v-2a1 1 0 0 1 2 0zm-3 0H1a1 1 0 0 0 0 2h1v-2zm11-5h2a1 1 0 0 1 0 2h-2a1 1 0 0 1-2 0V7a1 1 0 0 1 2 0zm-3 0H1a1 1 0 1 0 0 2h9V7zM6 2h9a1 1 0 0 1 0 2H6a1 1 0 1 1-2 0V2a1 1 0 1 1 2 0zM3 2H1a1 1 0 1 0 0 2h2V2z"/></symbol><symbol viewBox="0 0 16 16" id="profile" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zm-4.274-3.404C4.412 9.709 5.694 9 8 9c2.313 0 3.595.7 4.28 1.586A4.997 4.997 0 0 1 8 13a4.997 4.997 0 0 1-4.274-2.404zM8 8a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/></symbol><symbol viewBox="0 0 16 16" id="project" xmlns="http://www.w3.org/2000/svg"><path d="M8.462 2.177l-.038.044a.505.505 0 0 0 .038-.044zm-.787 0a.5.5 0 0 0 .038.043l-.038-.043zM3.706 7h8.725L8.069 2.585 3.706 7zM7 13.369V12a1 1 0 0 1 2 0v1.369h3V9H4v4.369h3zM14 9v4.836c0 .833-.657 1.533-1.5 1.533h-9c-.843 0-1.5-.7-1.5-1.533V9h-.448a1.1 1.1 0 0 1-.783-1.873L6.934.887a1.5 1.5 0 0 1 2.269 0l6.165 6.24A1.1 1.1 0 0 1 14.585 9H14z"/></symbol><symbol viewBox="0 0 16 16" id="push-rules" xmlns="http://www.w3.org/2000/svg"><path d="M6.268 9a2 2 0 0 1 3.464 0H11a1 1 0 0 1 0 2H9.732a2 2 0 0 1-3.464 0H5a1 1 0 0 1 0-2h1.268zM7 2H4a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1h-1v3.515a.3.3 0 0 1-.434.268l-1.432-.716a.3.3 0 0 0-.268 0l-1.432.716A.3.3 0 0 1 7 5.515V2zM4 0h8a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H4a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm4 11a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="question" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm-1.46-5.602h2.233a3.97 3.97 0 0 1 .051-.558c.029-.17.073-.326.133-.469.06-.143.14-.28.242-.41.102-.13.228-.263.38-.399.26-.24.504-.467.733-.683a5.03 5.03 0 0 0 .598-.668c.17-.23.302-.477.399-.742a2.66 2.66 0 0 0 .144-.907c0-.505-.083-.95-.25-1.335a2.55 2.55 0 0 0-.723-.97 3.2 3.2 0 0 0-1.152-.589 5.441 5.441 0 0 0-1.531-.2c-.516 0-.998.063-1.445.188a3.19 3.19 0 0 0-1.168.59c-.331.268-.594.61-.79 1.027-.195.417-.295.917-.3 1.5h2.64c.006-.224.04-.416.102-.578.062-.161.142-.293.238-.394a.921.921 0 0 1 .332-.227 1.04 1.04 0 0 1 .39-.074c.34 0 .593.095.763.285.169.19.254.488.254.895 0 .328-.106.63-.317.906-.21.276-.499.565-.863.867-.214.182-.39.374-.531.574-.141.2-.253.42-.336.657a3.656 3.656 0 0 0-.176.777 7.89 7.89 0 0 0-.05.937zm-.321 2.375c0 .188.035.362.105.524.07.161.17.3.301.418.13.117.284.21.46.277.178.068.376.102.595.102.218 0 .416-.034.593-.102.178-.068.331-.16.461-.277a1.2 1.2 0 0 0 .301-.418c.07-.162.106-.336.106-.524a1.3 1.3 0 0 0-.106-.523 1.2 1.2 0 0 0-.3-.418 1.461 1.461 0 0 0-.462-.277 1.651 1.651 0 0 0-.593-.102c-.22 0-.417.034-.594.102a1.46 1.46 0 0 0-.461.277 1.2 1.2 0 0 0-.3.418 1.284 1.284 0 0 0-.106.523z"/></symbol><symbol viewBox="0 0 16 16" id="question-o" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zm-.778-4.151c0-.301.014-.575.044-.82a3.2 3.2 0 0 1 .154-.68c.073-.208.17-.4.294-.575.123-.176.278-.343.465-.503a4.81 4.81 0 0 0 .755-.758c.185-.242.277-.506.277-.793 0-.356-.074-.617-.222-.783-.148-.166-.37-.25-.667-.25a.92.92 0 0 0-.342.065.806.806 0 0 0-.29.199 1.04 1.04 0 0 0-.209.345 1.5 1.5 0 0 0-.088.506H5.082c.005-.51.092-.948.263-1.313.171-.364.401-.664.69-.899.29-.234.63-.406 1.023-.516a4.66 4.66 0 0 1 1.264-.164c.497 0 .944.058 1.34.174.397.117.733.289 1.008.517.276.227.487.51.633.847.146.337.218.727.218 1.17 0 .295-.042.56-.126.792a2.52 2.52 0 0 1-.349.65 4.4 4.4 0 0 1-.523.584c-.2.19-.414.389-.642.598a2.73 2.73 0 0 0-.332.349c-.089.114-.16.233-.212.359a1.868 1.868 0 0 0-.116.41 3.39 3.39 0 0 0-.044.489H7.222zm-.28 2.078c0-.164.03-.317.092-.458a1.05 1.05 0 0 1 .263-.366c.114-.103.248-.183.403-.243a1.45 1.45 0 0 1 .52-.089c.191 0 .364.03.52.09.154.059.289.14.403.242.114.103.201.224.263.366.061.141.092.294.092.458 0 .164-.03.316-.092.458a1.05 1.05 0 0 1-.263.365 1.278 1.278 0 0 1-.404.243 1.43 1.43 0 0 1-.52.089c-.19 0-.364-.03-.519-.089-.155-.06-.29-.14-.403-.243a1.05 1.05 0 0 1-.263-.365 1.135 1.135 0 0 1-.093-.458z"/></symbol><symbol viewBox="0 0 16 16" id="quote" xmlns="http://www.w3.org/2000/svg"><path d="M15 3v8a3 3 0 0 1-3 3 1 1 0 0 1 0-2 1 1 0 0 0 1-1V9h-2a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h3a1 1 0 0 1 1 1zM7 3v8a3 3 0 0 1-3 3 1 1 0 0 1 0-2 1 1 0 0 0 1-1V9H3a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h3a1 1 0 0 1 1 1z"/></symbol><symbol viewBox="0 0 16 16" id="redo" xmlns="http://www.w3.org/2000/svg"><path d="M4.625 4.423A4.897 4.897 0 0 1 8.079 3c2.73 0 4.944 2.239 4.944 5s-2.214 5-4.944 5c-1.41 0-2.723-.6-3.655-1.633a.98.98 0 0 0-1.397-.066 1.008 1.008 0 0 0-.064 1.413A6.87 6.87 0 0 0 8.079 15C11.9 15 15 11.866 15 8s-3.099-7-6.921-7A6.866 6.866 0 0 0 3.08 3.158L1.833 2.137a.49.49 0 0 0-.695.074.504.504 0 0 0-.11.311L1 7.26a.497.497 0 0 0 .6.492l4.576-1.013a.5.5 0 0 0 .206-.877L4.625 4.423z"/></symbol><symbol viewBox="0 0 16 16" id="remove" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M2 3a1 1 0 1 1 0-2h12a1 1 0 0 1 0 2v10a3 3 0 0 1-3 3H5a3 3 0 0 1-3-3V3zm3-2a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1H5zM4 3v10a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V3H4zm2.5 2a.5.5 0 0 1 .5.5v6a.5.5 0 1 1-1 0v-6a.5.5 0 0 1 .5-.5zm3 0a.5.5 0 0 1 .5.5v6a.5.5 0 1 1-1 0v-6a.5.5 0 0 1 .5-.5z"/></symbol><symbol viewBox="0 0 16 16" id="repeat" xmlns="http://www.w3.org/2000/svg"><path d="M11.375 4.423A4.897 4.897 0 0 0 7.921 3c-2.73 0-4.944 2.239-4.944 5s2.214 5 4.944 5c1.41 0 2.723-.6 3.655-1.633a.98.98 0 0 1 1.397-.066c.403.373.432 1.005.064 1.413A6.87 6.87 0 0 1 7.921 15C4.1 15 1 11.866 1 8s3.099-7 6.921-7c1.915 0 3.706.792 4.999 2.158l1.247-1.021a.49.49 0 0 1 .695.074c.07.088.11.198.11.311L15 7.26a.497.497 0 0 1-.6.492L9.824 6.739a.5.5 0 0 1-.206-.877l1.757-1.439z"/></symbol><symbol viewBox="0 0 16 16" id="retry" xmlns="http://www.w3.org/2000/svg"><path d="M4.114 6.958a4 4 0 0 0 5.283 4.775 1 1 0 1 1 .712 1.87A6 6 0 0 1 2.182 6.44l-.741-.2a.5.5 0 0 1-.12-.915l2.195-1.268a.5.5 0 0 1 .683.183l1.268 2.196a.5.5 0 0 1-.563.733l-.79-.212zm7.777 2.084a4 4 0 0 0-5.284-4.775 1 1 0 0 1-.712-1.87 6 6 0 0 1 7.927 7.162l.742.2a.5.5 0 0 1 .12.915l-2.196 1.268a.5.5 0 0 1-.683-.183l-1.267-2.196a.5.5 0 0 1 .562-.733l.79.212z"/></symbol><symbol viewBox="0 0 16 16" id="scale" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M13.99 9a.792.792 0 0 0-.078-.231L13 7l-.912 1.769a.791.791 0 0 0-.077.231h1.978zm-10 0a.792.792 0 0 0-.078-.231L3 7l-.912 1.769A.791.791 0 0 0 2.011 9h1.978zM2 0h12a1 1 0 0 1 0 2H2a1 1 0 1 1 0-2zm3 14h6a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2zM8 4a1 1 0 0 1 1 1v9H7V5a1 1 0 0 1 1-1zm-4.53-.714l2.265 4.735c.68 1.42.006 3.091-1.504 3.73A3.161 3.161 0 0 1 3 12c-1.657 0-3-1.263-3-2.821 0-.4.09-.794.264-1.158L2.53 3.286a.53.53 0 0 1 .94 0zm10 0l2.265 4.735c.68 1.42.006 3.091-1.504 3.73A3.161 3.161 0 0 1 13 12c-1.657 0-3-1.263-3-2.821 0-.4.09-.794.264-1.158l2.266-4.735a.53.53 0 0 1 .94 0z"/></symbol><symbol viewBox="0 0 16 16" id="screen-full" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M14 14v-2a1 1 0 0 1 2 0v3a.997.997 0 0 1-1 1h-3a1 1 0 0 1 0-2h2zM2 14v-2a1 1 0 0 0-2 0v3a1 1 0 0 0 1 1h3a1 1 0 0 0 0-2H2zM15.707.293A.997.997 0 0 1 16 1v3a1 1 0 0 1-2 0V2h-2a1 1 0 0 1 0-2h3c.276 0 .526.112.707.293zM2 2v2a1 1 0 1 1-2 0V1a.997.997 0 0 1 1-1h3a1 1 0 1 1 0 2H2zm4 4h4a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1z"/></symbol><symbol viewBox="0 0 16 16" id="screen-normal" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M3 3V1a1 1 0 1 1 2 0v3a.997.997 0 0 1-1 1H1a1 1 0 1 1 0-2h2zm10 0h2a1 1 0 0 1 0 2h-3a.997.997 0 0 1-1-1V1a1 1 0 0 1 2 0v2zM3 13H1a1 1 0 0 1 0-2h3a.997.997 0 0 1 1 1v3a1 1 0 0 1-2 0v-2zm10 0v2a1 1 0 0 1-2 0v-3a.997.997 0 0 1 1-1h3a1 1 0 0 1 0 2h-2zM6.5 7h3a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5z"/></symbol><symbol viewBox="0 0 12 16" id="scroll_down" xmlns="http://www.w3.org/2000/svg"><path class="fbfirst-triangle" d="M1.048 14.155a.508.508 0 0 0-.32.105c-.091.07-.136.154-.136.25v.71c0 .095.045.178.135.249.09.07.197.105.321.105h10.043a.51.51 0 0 0 .321-.105c.09-.07.136-.154.136-.25v-.71c0-.095-.045-.178-.136-.249a.508.508 0 0 0-.32-.105"/><path class="fbsecond-triangle" d="M.687 8.027c-.09-.087-.122-.16-.093-.22.028-.06.104-.09.228-.09h10.5c.123 0 .2.03.228.09.029.06-.002.133-.093.22L6.393 12.91a.458.458 0 0 1-.136.089h-.37a.626.626 0 0 1-.136-.09"/><path class="fbthird-triangle" d="M.687 1.027C.597.94.565.867.594.807c.028-.06.104-.09.228-.09h10.5c.123 0 .2.03.228.09.029.06-.002.133-.093.22L6.393 5.91a.458.458 0 0 1-.136.09h-.37a.626.626 0 0 1-.136-.09"/></symbol><symbol viewBox="0 0 12 16" id="scroll_up" xmlns="http://www.w3.org/2000/svg"><path d="M1.048 1.845a.508.508 0 0 1-.32-.105c-.091-.07-.136-.154-.136-.25V.78c0-.095.045-.178.135-.249a.508.508 0 0 1 .321-.105h10.043a.51.51 0 0 1 .321.105c.09.07.136.154.136.25v.71c0 .095-.045.178-.136.249a.508.508 0 0 1-.32.105M.687 7.973c-.09.087-.122.16-.093.22.028.06.104.09.228.09h10.5c.123 0 .2-.03.228-.09.029-.06-.002-.133-.093-.22L6.393 3.09A.458.458 0 0 0 6.257 3h-.37a.626.626 0 0 0-.136.09M.687 14.973c-.09.087-.122.16-.093.22.028.06.104.09.228.09h10.5c.123 0 .2-.03.228-.09.029-.06-.002-.133-.093-.22L6.393 10.09a.458.458 0 0 0-.136-.09h-.37a.626.626 0 0 0-.136.09"/></symbol><symbol viewBox="0 0 16 16" id="search" xmlns="http://www.w3.org/2000/svg"><path d="M8.853 8.854a3.5 3.5 0 1 0-4.95-4.95 3.5 3.5 0 0 0 4.95 4.95zm.207 2.328a5.5 5.5 0 1 1 2.121-2.121l3.329 3.328a1.5 1.5 0 0 1-2.121 2.121L9.06 11.182z"/></symbol><symbol viewBox="0 0 16 16" id="settings" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M2.415 5.803L1.317 4.084A.5.5 0 0 1 1.35 3.5l.805-.994a.5.5 0 0 1 .564-.153l1.878.704a5.975 5.975 0 0 1 1.65-.797L6.885.342A.5.5 0 0 1 7.36 0h1.28a.5.5 0 0 1 .474.342l.639 1.918a5.97 5.97 0 0 1 1.65.797l1.877-.704a.5.5 0 0 1 .565.153l.805.994a.5.5 0 0 1 .032.584l-1.097 1.719c.217.551.354 1.143.399 1.76l1.731 1.058a.5.5 0 0 1 .227.54l-.288 1.246a.5.5 0 0 1-.44.385l-2.008.19a6.026 6.026 0 0 1-1.142 1.431l.265 1.995a.5.5 0 0 1-.277.516l-1.15.56a.5.5 0 0 1-.576-.1l-1.424-1.452a6.047 6.047 0 0 1-1.804 0l-1.425 1.453a.5.5 0 0 1-.576.1l-1.15-.561a.5.5 0 0 1-.276-.516l.265-1.995a6.026 6.026 0 0 1-1.143-1.43l-2.008-.191a.5.5 0 0 1-.44-.385L.058 9.16a.5.5 0 0 1 .226-.539l1.732-1.058a5.968 5.968 0 0 1 .399-1.76zM8 11a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/></symbol><symbol viewBox="0 0 16 16" id="shield" xmlns="http://www.w3.org/2000/svg"><path d="M4 0h8c1.657 0 3 1.373 3 3.067v7.346c0 1.065-.54 2.053-1.426 2.611l-4 2.52a2.944 2.944 0 0 1-3.148 0l-4-2.52A3.083 3.083 0 0 1 1 10.414V3.066C1 1.373 2.343 0 4 0zm0 2.045c-.552 0-1 .457-1 1.022v7.346c0 .355.18.685.475.87l4 2.52a.981.981 0 0 0 1.05 0l4-2.52c.295-.185.475-.515.475-.87V3.067c0-.565-.448-1.022-1-1.022H4zm0 1.533c0-.282.224-.511.5-.511h4V12.1a.52.52 0 0 1-.069.258.494.494 0 0 1-.684.183l-3.5-2.098a.513.513 0 0 1-.247-.44V3.577z"/></symbol><symbol viewBox="0 0 16 16" id="slight-frown" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zm-2.163-3.275a2.499 2.499 0 0 1 4.343.03.5.5 0 0 1-.871.49 1.5 1.5 0 0 0-2.607-.018.5.5 0 1 1-.865-.502zM5 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm6 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="slight-smile" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zM5 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm6 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm-5.163 2.254a.5.5 0 1 1 .865-.502 1.499 1.499 0 0 0 2.607-.018.5.5 0 1 1 .871.49 2.499 2.499 0 0 1-4.343.03z"/></symbol><symbol viewBox="0 0 16 16" id="smile" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zM6.18 6.27a.5.5 0 0 1-.873.487.5.5 0 0 0-.872-.003.5.5 0 1 1-.87-.495 1.5 1.5 0 0 1 2.616.012zm6 0a.5.5 0 1 1-.873.487.5.5 0 0 0-.872-.003.5.5 0 1 1-.87-.495 1.5 1.5 0 0 1 2.616.012zM5 9a3 3 0 0 0 6 0H5z"/></symbol><symbol viewBox="0 0 16 16" id="smiley" xmlns="http://www.w3.org/2000/svg"><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12zM5 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm6 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2zM5 9h6a3 3 0 0 1-6 0z"/></symbol><symbol viewBox="0 0 16 16" id="snippet" xmlns="http://www.w3.org/2000/svg"><path d="M10.67 9.31a3.001 3.001 0 0 1 2.062 5.546 3 3 0 0 1-3.771-4.559 1.007 1.007 0 0 1-.095-.137l-4.5-7.794a1 1 0 0 1 1.732-1l4.5 7.794c.028.05.052.1.071.15zm-3.283.35l-.289.5c-.028.05-.06.095-.095.137a3.001 3.001 0 0 1-3.77 4.56A3 3 0 0 1 5.294 9.31c.02-.051.043-.102.071-.15l.866-1.5 1.155 2zm2.31-4l-1.156-2 1.325-2.294a1 1 0 0 1 1.732 1L9.696 5.66zm-5.465 7.464a1 1 0 1 0 1-1.732 1 1 0 0 0-1 1.732zm7.5 0a1 1 0 1 0-1-1.732 1 1 0 0 0 1 1.732z"/></symbol><symbol viewBox="0 0 16 16" id="soft-unwrap" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M6.5 11v-.598a.5.5 0 0 1 .765-.424l2.557 1.598a.5.5 0 0 1 0 .848l-2.557 1.598a.5.5 0 0 1-.765-.424V13H2a1 1 0 0 1 0-2h4.5zM2 3h12a1 1 0 0 1 0 2H2a1 1 0 1 1 0-2zm0 4h12a1 1 0 0 1 0 2H2a1 1 0 1 1 0-2zm10 4h2a1 1 0 0 1 0 2h-2a1 1 0 0 1 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="soft-wrap" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10.5 13v.598a.5.5 0 0 1-.765.424l-2.557-1.598a.5.5 0 0 1 0-.848l2.557-1.598a.5.5 0 0 1 .765.424V11H12a1 1 0 0 0 0-2H2a1 1 0 1 1 0-2h10a3 3 0 0 1 0 6h-1.5zM2 3h12a1 1 0 0 1 0 2H2a1 1 0 1 1 0-2zm0 8h3a1 1 0 0 1 0 2H2a1 1 0 0 1 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="spam" xmlns="http://www.w3.org/2000/svg"><path d="M8.75.433l5.428 3.134a1.5 1.5 0 0 1 .75 1.299v6.268a1.5 1.5 0 0 1-.75 1.299L8.75 15.567a1.5 1.5 0 0 1-1.5 0l-5.428-3.134a1.5 1.5 0 0 1-.75-1.299V4.866a1.5 1.5 0 0 1 .75-1.299L7.25.433a1.5 1.5 0 0 1 1.5 0zM3.072 5.155v5.69L8 13.691l4.928-2.846v-5.69L8 2.309 3.072 5.155zM8 4a1 1 0 0 1 1 1v3a1 1 0 1 1-2 0V5a1 1 0 0 1 1-1zm0 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol><symbol viewBox="0 0 14 14" id="spinner" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><circle cx="7" cy="7" r="6" stroke="#000" stroke-opacity=".1" stroke-width="2"/><path fill="#000" fill-opacity=".1" fill-rule="nonzero" d="M7 0a7 7 0 0 1 7 7h-2a5 5 0 0 0-5-5V0z"/></g></symbol><symbol viewBox="0 0 16 16" id="staged" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M2 3h4a1 1 0 1 1 0 2H2a1 1 0 1 1 0-2zm9 6a3 3 0 1 1 0-6 3 3 0 0 1 0 6zM2 7h4a1 1 0 1 1 0 2H2a1 1 0 1 1 0-2zm0 4h12a1 1 0 0 1 0 2H2a1 1 0 0 1 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="star" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7.609 14.394l-3.465 1.473a1 1 0 0 1-1.39-.989l.276-4.024a1 1 0 0 0-.219-.694L.303 7.037A1 1 0 0 1 .83 5.443l3.715-.964a1 1 0 0 0 .609-.457L7.14.682a1 1 0 0 1 1.72 0l1.985 3.34a1 1 0 0 0 .609.457l3.715.964a1 1 0 0 1 .528 1.594L13.19 10.16a1 1 0 0 0-.219.694l.275 4.024a1 1 0 0 1-1.389.989l-3.465-1.473a1 1 0 0 0-.782 0z"/></symbol><symbol viewBox="0 0 16 16" id="star-o" xmlns="http://www.w3.org/2000/svg"><path d="M10.975 10.99a3 3 0 0 1 .655-2.083l1.54-1.916-2.219-.576a3 3 0 0 1-1.825-1.37L8 3.15 6.874 5.044a3 3 0 0 1-1.825 1.371l-2.218.576 1.54 1.916a3 3 0 0 1 .654 2.083l-.165 2.4 1.965-.836a3 3 0 0 1 2.348 0l1.965.836-.164-2.399zM7.61 14.394l-3.465 1.473a1 1 0 0 1-1.39-.989l.276-4.024a1 1 0 0 0-.219-.694L.303 7.037A1 1 0 0 1 .83 5.443l3.715-.964a1 1 0 0 0 .609-.457L7.14.682a1 1 0 0 1 1.72 0l1.985 3.34a1 1 0 0 0 .609.457l3.715.964a1 1 0 0 1 .528 1.594L13.19 10.16a1 1 0 0 0-.219.694l.275 4.024a1 1 0 0 1-1.389.989l-3.465-1.473a1 1 0 0 0-.782 0z"/></symbol><symbol viewBox="0 0 14 14" id="status_canceled" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><path d="M5.2 3.8l4.9 4.9c.2.2.2.5 0 .7l-.7.7c-.2.2-.5.2-.7 0L3.8 5.2c-.2-.2-.2-.5 0-.7l.7-.7c.2-.2.5-.2.7 0"/></g></symbol><symbol viewBox="0 0 22 22" id="status_canceled_borderless" xmlns="http://www.w3.org/2000/svg"><path d="M8.171 5.971l7.7 7.7a.76.76 0 0 1 0 1.1l-1.1 1.1a.76.76 0 0 1-1.1 0l-7.7-7.7a.76.76 0 0 1 0-1.1l1.1-1.1a.76.76 0 0 1 1.1 0"/></symbol><symbol viewBox="0 0 16 16" id="status_closed" xmlns="http://www.w3.org/2000/svg"><path d="M7.536 8.657l2.828-2.83a1 1 0 0 1 1.414 1.416l-3.535 3.535a1 1 0 0 1-1.415.001l-2.12-2.12a1 1 0 1 1 1.413-1.415zM8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16zm0-2A6 6 0 1 0 8 2a6 6 0 0 0 0 12z"/></symbol><symbol viewBox="0 0 14 14" id="status_created" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><circle cx="7" cy="7" r="3.25"/></g></symbol><symbol viewBox="0 0 22 22" id="status_created_borderless" xmlns="http://www.w3.org/2000/svg"><circle cx="11" cy="11" r="5.107"/></symbol><symbol viewBox="0 0 14 14" id="status_failed" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><path d="M7 5.969L5.599 4.568a.29.29 0 0 0-.413.004l-.614.614a.294.294 0 0 0-.004.413L5.968 7l-1.4 1.401a.29.29 0 0 0 .004.413l.614.614c.113.114.3.117.413.004L7 8.032l1.401 1.4a.29.29 0 0 0 .413-.004l.614-.614a.294.294 0 0 0 .004-.413L8.032 7l1.4-1.401a.29.29 0 0 0-.004-.413l-.614-.614a.294.294 0 0 0-.413-.004L7 5.968z"/></g></symbol><symbol viewBox="0 0 22 22" id="status_failed_borderless" xmlns="http://www.w3.org/2000/svg"><path d="M11 9.38L8.798 7.178a.455.455 0 0 0-.65.006l-.964.965a.462.462 0 0 0-.006.65L9.38 11l-2.202 2.202a.455.455 0 0 0 .006.65l.965.964a.462.462 0 0 0 .65.006L11 12.62l2.202 2.202a.455.455 0 0 0 .65-.006l.964-.965a.462.462 0 0 0 .006-.65L12.62 11l2.202-2.202a.455.455 0 0 0-.006-.65l-.965-.964a.462.462 0 0 0-.65-.006L11 9.38z"/></symbol><symbol viewBox="0 0 14 14" id="status_manual" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><path d="M10.5 7.63V6.37l-.787-.13c-.044-.175-.132-.349-.263-.61l.481-.652-.918-.913-.657.478a2.346 2.346 0 0 0-.612-.26L7.656 3.5H6.388l-.132.783c-.219.043-.394.13-.612.26l-.657-.478-.918.913.437.652c-.131.218-.175.392-.262.61l-.744.086v1.261l.787.13c.044.218.132.392.263.61l-.438.651.92.913.655-.434c.175.086.394.173.613.26l.131.783h1.313l.131-.783c.219-.043.394-.13.613-.26l.656.478.918-.913-.48-.652c.13-.218.218-.435.262-.61l.656-.13zM7 8.283a1.285 1.285 0 0 1-1.313-1.305c0-.739.57-1.304 1.313-1.304.744 0 1.313.565 1.313 1.304 0 .74-.57 1.305-1.313 1.305z"/></g></symbol><symbol viewBox="0 0 22 22" id="status_manual_borderless" xmlns="http://www.w3.org/2000/svg"><path d="M16.5 11.99v-1.98l-1.238-.206c-.068-.273-.206-.546-.412-.956l.756-1.025-1.444-1.435-1.03.752a3.686 3.686 0 0 0-.963-.41L12.03 5.5h-1.994l-.206 1.23c-.343.068-.618.205-.962.41l-1.031-.752-1.444 1.435.687 1.025c-.206.341-.275.615-.412.956L5.5 9.941v1.981l1.237.205c.07.342.207.615.413.957l-.688 1.025 1.444 1.434 1.032-.683c.274.137.618.274.962.41l.206 1.23h2.063l.206-1.23c.344-.068.619-.205.963-.41l1.03.752 1.444-1.435-.756-1.025c.207-.341.344-.683.413-.956l1.031-.205zM11 13.017c-1.169 0-2.063-.889-2.063-2.05 0-1.162.894-2.05 2.063-2.05s2.063.888 2.063 2.05c0 1.161-.894 2.05-2.063 2.05z"/></symbol><symbol viewBox="0 0 14 14" id="status_notfound" xmlns="http://www.w3.org/2000/svg"><path d="M7 14A7 7 0 1 1 7 0a7 7 0 0 1 0 14z"/><path d="M7 13A6 6 0 1 0 7 1a6 6 0 0 0 0 12z" fill="#FFF"/><path d="M8.16 7.184c.519-.37.904-.857 1.07-1.477.384-1.427-.619-2.897-2.246-2.897-.732 0-1.327.26-1.766.692a2.163 2.163 0 0 0-.509.743.75.75 0 0 0 1.4.54.78.78 0 0 1 .16-.213c.168-.165.39-.262.715-.262.597 0 .936.496.798 1.007-.067.249-.235.462-.492.644-.231.165-.47.264-.601.3a.75.75 0 0 0-.556.724v1.421a.75.75 0 0 0 1.5 0v-.909a3.74 3.74 0 0 0 .526-.313z"/><ellipse cx="6.889" cy="10.634" rx="1" ry="1"/></symbol><symbol viewBox="0 0 22 22" id="status_notfound_borderless" xmlns="http://www.w3.org/2000/svg"><path d="M12.822 11.29c.816-.581 1.421-1.348 1.683-2.322.603-2.243-.973-4.553-3.53-4.553-1.15 0-2.085.41-2.775 1.089-.42.413-.672.835-.8 1.167a1.179 1.179 0 0 0 2.2.847c.016-.043.1-.184.252-.334.264-.259.613-.412 1.123-.412.938 0 1.47.78 1.254 1.584-.105.39-.37.726-.773 1.012a3.25 3.25 0 0 1-.945.47 1.179 1.179 0 0 0-.874 1.138v2.234a1.179 1.179 0 1 0 2.358 0v-1.43a5.9 5.9 0 0 0 .827-.492z"/><ellipse cx="10.825" cy="16.711" rx="1.275" ry="1.322"/></symbol><symbol viewBox="0 0 14 14" id="status_open" xmlns="http://www.w3.org/2000/svg"><path d="M0 7c0-3.866 3.142-7 7-7 3.866 0 7 3.142 7 7 0 3.866-3.142 7-7 7-3.866 0-7-3.142-7-7z"/><path d="M1 7c0 3.309 2.69 6 6 6 3.309 0 6-2.69 6-6 0-3.309-2.69-6-6-6-3.309 0-6 2.69-6 6z" fill="#FFF"/><path d="M7 9.219a2.218 2.218 0 1 0 0-4.436A2.218 2.218 0 0 0 7 9.22zm0 1.12a3.338 3.338 0 1 1 0-6.676 3.338 3.338 0 0 1 0 6.676z"/></symbol><symbol viewBox="0 0 14 14" id="status_pending" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><path d="M4.7 5.3c0-.2.1-.3.3-.3h.9c.2 0 .3.1.3.3v3.4c0 .2-.1.3-.3.3H5c-.2 0-.3-.1-.3-.3V5.3m3 0c0-.2.1-.3.3-.3h.9c.2 0 .3.1.3.3v3.4c0 .2-.1.3-.3.3H8c-.2 0-.3-.1-.3-.3V5.3"/></g></symbol><symbol viewBox="0 0 22 22" id="status_pending_borderless" xmlns="http://www.w3.org/2000/svg"><path d="M7.386 8.329c0-.315.157-.472.471-.472h1.414c.315 0 .472.157.472.472v5.342c0 .315-.157.472-.472.472H7.857c-.314 0-.471-.157-.471-.472V8.33m4.714 0c0-.315.157-.472.471-.472h1.415c.314 0 .471.157.471.472v5.342c0 .315-.157.472-.471.472H12.57c-.314 0-.471-.157-.471-.472V8.33"/></symbol><symbol viewBox="0 0 14 14" id="status_running" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><path d="M7 3c2.2 0 4 1.8 4 4s-1.8 4-4 4c-1.3 0-2.5-.7-3.3-1.7L7 7V3"/></g></symbol><symbol viewBox="0 0 22 22" id="status_running_borderless" xmlns="http://www.w3.org/2000/svg"><path d="M11 4.714c3.457 0 6.286 2.829 6.286 6.286 0 3.457-2.829 6.286-6.286 6.286-2.043 0-3.929-1.1-5.186-2.672L11 11V4.714"/></symbol><symbol viewBox="0 0 14 14" id="status_skipped" xmlns="http://www.w3.org/2000/svg"><path d="M7 14A7 7 0 1 1 7 0a7 7 0 0 1 0 14z"/><path d="M7 13A6 6 0 1 0 7 1a6 6 0 0 0 0 12z" fill="#FFF"/><path d="M6.415 7.04L4.579 5.203a.295.295 0 0 1 .004-.416l.349-.349a.29.29 0 0 1 .416-.004l2.214 2.214a.289.289 0 0 1 .019.021l.132.133c.11.11.108.291 0 .398L5.341 9.573a.282.282 0 0 1-.398 0l-.331-.331a.285.285 0 0 1 0-.399L6.415 7.04zm2.54 0L7.119 5.203a.295.295 0 0 1 .004-.416l.349-.349a.29.29 0 0 1 .416-.004l2.214 2.214a.289.289 0 0 1 .019.021l.132.133c.11.11.108.291 0 .398L7.881 9.573a.282.282 0 0 1-.398 0l-.331-.331a.285.285 0 0 1 0-.399L8.955 7.04z"/></symbol><symbol viewBox="0 0 22 22" id="status_skipped_borderless" xmlns="http://www.w3.org/2000/svg"><path d="M14.072 11.063l-2.82 2.82a.46.46 0 0 0-.001.652l.495.495a.457.457 0 0 0 .653-.001l3.7-3.7a.46.46 0 0 0 .001-.653l-.196-.196a.453.453 0 0 0-.03-.033l-3.479-3.479a.464.464 0 0 0-.654.007l-.548.548a.463.463 0 0 0-.007.654l2.886 2.886z"/><path d="M10.08 11.063l-2.819 2.82a.46.46 0 0 0-.002.652l.496.495a.457.457 0 0 0 .652-.001l3.7-3.7a.46.46 0 0 0 .002-.653l-.196-.196a.453.453 0 0 0-.03-.033l-3.48-3.479a.464.464 0 0 0-.653.007l-.548.548a.463.463 0 0 0-.007.654l2.886 2.886z"/></symbol><symbol viewBox="0 0 14 14" id="status_success" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><path d="M6.278 7.697L5.045 6.464a.296.296 0 0 0-.42-.002l-.613.614a.298.298 0 0 0 .002.42l1.91 1.909a.5.5 0 0 0 .703.005l.265-.265L9.997 6.04a.291.291 0 0 0-.009-.408l-.614-.614a.29.29 0 0 0-.408-.009L6.278 7.697z"/></g></symbol><symbol viewBox="0 0 22 22" id="status_success_borderless" xmlns="http://www.w3.org/2000/svg"><path d="M9.866 12.095l-1.95-1.95a.462.462 0 0 0-.647.01l-.964.964a.46.46 0 0 0-.01.646l3.013 3.014a.787.787 0 0 0 1.106.008l.425-.425 4.854-4.853a.462.462 0 0 0 .002-.659l-.964-.964a.468.468 0 0 0-.658.002l-4.207 4.207z"/></symbol><symbol viewBox="0 0 14 14" id="status_success_solid" xmlns="http://www.w3.org/2000/svg"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7zm6.278.697L5.045 6.464a.296.296 0 0 0-.42-.002l-.613.614a.298.298 0 0 0 .002.42l1.91 1.909a.5.5 0 0 0 .703.005l.265-.265L9.997 6.04a.291.291 0 0 0-.009-.408l-.614-.614a.29.29 0 0 0-.408-.009L6.278 7.697z" fill-rule="evenodd"/></symbol><symbol viewBox="0 0 14 14" id="status_warning" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><path d="M6 3.5c0-.3.2-.5.5-.5h1c.3 0 .5.2.5.5v4c0 .3-.2.5-.5.5h-1c-.3 0-.5-.2-.5-.5v-4m0 6c0-.3.2-.5.5-.5h1c.3 0 .5.2.5.5v1c0 .3-.2.5-.5.5h-1c-.3 0-.5-.2-.5-.5v-1"/></g></symbol><symbol viewBox="0 0 22 22" id="status_warning_borderless" xmlns="http://www.w3.org/2000/svg"><path d="M9.429 5.5c0-.471.314-.786.785-.786h1.572c.471 0 .785.315.785.786v6.286c0 .471-.314.785-.785.785h-1.572c-.471 0-.785-.314-.785-.785V5.5m0 9.429c0-.472.314-.786.785-.786h1.572c.471 0 .785.314.785.786V16.5c0 .471-.314.786-.785.786h-1.572c-.471 0-.785-.315-.785-.786v-1.571"/></symbol><symbol viewBox="0 0 16 16" id="stop" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M2 0h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2z"/></symbol><symbol viewBox="0 0 16 16" id="task-done" xmlns="http://www.w3.org/2000/svg"><path d="M7.536 8.657l2.828-2.829a1 1 0 0 1 1.414 1.415l-3.535 3.535a.997.997 0 0 1-1.415 0l-2.12-2.121A1 1 0 0 1 6.12 7.243l1.415 1.414zM3 0h10a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3z"/></symbol><symbol viewBox="0 0 16 16" id="template" xmlns="http://www.w3.org/2000/svg"><path d="M3 0h10a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3zm.8 2h2.4a.8.8 0 0 1 .8.8v1.4a.8.8 0 0 1-.8.8H3.8a.8.8 0 0 1-.8-.8V4.8a.8.8 0 0 1 .8-.8zm4.7 0h4a.5.5 0 1 1 0 1h-4a.5.5 0 0 1 0-1zm0 2h4a.5.5 0 1 1 0 1h-4a.5.5 0 0 1 0-1zm-5 3h9a.5.5 0 1 1 0 1h-9a.5.5 0 0 1 0-1zm0 2h9a.5.5 0 1 1 0 1h-9a.5.5 0 1 1 0-1z"/></symbol><symbol viewBox="0 0 16 16" id="terminal" xmlns="http://www.w3.org/2000/svg"><path d="M7 8a.997.997 0 0 1-.293.707l-1.414 1.414a1 1 0 1 1-1.414-1.414L4.586 8l-.707-.707a1 1 0 1 1 1.414-1.414l1.414 1.414A.997.997 0 0 1 7 8zM4 0h8a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm0 2a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H4zm5 7h2a1 1 0 0 1 0 2H9a1 1 0 0 1 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="thumb-down" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8.33 11h5.282a2 2 0 0 0 1.963-2.38l-.563-2.905a3 3 0 0 0-.243-.732l-1.103-2.286A3 3 0 0 0 10.964 1H7a3 3 0 0 0-3 3v6.3a2 2 0 0 0 .436 1.247l3.11 3.9a.632.632 0 0 0 .941.053l.137-.137a1 1 0 0 0 .28-.87L8.329 11zM1 10h2V3H1a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1z"/></symbol><symbol viewBox="0 0 16 16" id="thumb-up" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8.33 5h5.282a2 2 0 0 1 1.963 2.38l-.563 2.905a3 3 0 0 1-.243.732l-1.103 2.286A3 3 0 0 1 10.964 15H7a3 3 0 0 1-3-3V5.7a2 2 0 0 1 .436-1.247l3.11-3.9A.632.632 0 0 1 8.487.5l.137.137a1 1 0 0 1 .28.87L8.329 5zM1 6h2v7H1a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1z"/></symbol><symbol viewBox="0 0 16 16" id="thumbtack" xmlns="http://www.w3.org/2000/svg"><path d="M7.125 9h-2.19a.5.5 0 0 1-.417-.777L6 6V2L5.362.724A.5.5 0 0 1 5.809 0h4.382a.5.5 0 0 1 .447.724L10 2v4l1.482 2.223a.5.5 0 0 1-.416.777H8.875L8 16l-.875-7z" fill-rule="evenodd"/></symbol><symbol viewBox="0 0 16 16" id="timer" xmlns="http://www.w3.org/2000/svg"><path d="M12.022 3.27l.77-.77a1 1 0 0 1 1.415 1.414l-.728.729a7 7 0 1 1-1.456-1.372zM8 14A5 5 0 1 0 8 4a5 5 0 0 0 0 10zm0-9a1 1 0 0 1 1 1v2a1 1 0 1 1-2 0V6a1 1 0 0 1 1-1zM6 0h4a1 1 0 0 1 0 2H6a1 1 0 1 1 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="todo-add" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 4V2a1 1 0 0 1 2 0v2h2a1 1 0 0 1 0 2h-2v2a1 1 0 0 1-2 0V6H8a1 1 0 1 1 0-2h2zm2 7a1 1 0 0 1 2 0v2a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3h2a1 1 0 1 1 0 2H3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-2z"/></symbol><symbol viewBox="0 0 16 16" id="todo-done" xmlns="http://www.w3.org/2000/svg"><path d="M8.243 7.485l4.95-4.95a1 1 0 1 1 1.414 1.415L8.95 9.607a.997.997 0 0 1-1.414 0L4.707 6.778a1 1 0 0 1 1.414-1.414l2.122 2.121zM12 11a1 1 0 0 1 2 0v2a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3h2a1 1 0 1 1 0 2H3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-2z"/></symbol><symbol viewBox="0 0 16 16" id="token" xmlns="http://www.w3.org/2000/svg"><path d="M3 2h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H3zm1 5a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="unapproval" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M11.95 8.536l1.06-1.061a1 1 0 0 1 1.415 1.414l-1.061 1.06 1.06 1.061a1 1 0 0 1-1.414 1.415l-1.06-1.061-1.06 1.06a1 1 0 1 1-1.415-1.414l1.06-1.06-1.06-1.06a1 1 0 0 1 1.414-1.415l1.06 1.06zm-3.768-.33c.006.503.201 1.006.586 1.39l.353.354-.353.353a2 2 0 1 0 2.828 2.829l.354-.354.047.048C11.964 14.363 11.527 15 6 15c-5.924 0-6-.78-6-2.52S.964 8 6 8c.834 0 1.557.074 2.182.205zM5.976 7a3 3 0 1 1 0-6 3 3 0 0 1 0 6z"/></symbol><symbol viewBox="0 0 16 16" id="unassignee" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M11 5h4a1 1 0 0 1 0 2h-4a1 1 0 0 1 0-2zM5.976 7a3 3 0 1 1 0-6 3 3 0 0 1 0 6zM6 15c-5.924 0-6-.78-6-2.52S.964 8 6 8s6 2.692 6 4.48c0 1.788-.076 2.52-6 2.52z"/></symbol><symbol viewBox="0 0 16 16" id="unlink" xmlns="http://www.w3.org/2000/svg"><path d="M11.295 8.845l-.659-1.664a1.78 1.78 0 0 0 .04-.04l1.415-1.414c.586-.586.654-1.468.152-1.97s-1.384-.434-1.97.152L8.859 5.323a1.781 1.781 0 0 0-.04.04l-1.664-.658c.141-.208.305-.408.491-.594l1.415-1.414c1.366-1.367 3.424-1.525 4.596-.354 1.171 1.172 1.013 3.23-.354 4.596L11.89 8.354c-.186.186-.386.35-.594.491zm-2.45 2.45a4.075 4.075 0 0 1-.491.594l-1.415 1.414c-1.366 1.367-3.424 1.525-4.596.354-1.171-1.172-1.013-3.23.354-4.596L4.11 7.646c.186-.186.386-.35.594-.491l.659 1.664a1.781 1.781 0 0 0-.04.04l-1.415 1.414c-.586.586-.654 1.468-.152 1.97s1.384.434 1.97-.152l1.414-1.414a1.78 1.78 0 0 0 .04-.04l1.664.658zm3.812-2.088h2a.5.5 0 0 1 .5.5v.05a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1-.5-.5v-.05a.5.5 0 0 1 .5-.5zm-.384 2.116l1.415 1.414a.5.5 0 0 1 0 .708l-.037.036a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 0-.707l.036-.037a.5.5 0 0 1 .707 0zm-2.823 1.09a.5.5 0 0 1 .5-.5h.052a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.5.5H9.95a.5.5 0 0 1-.5-.5v-2zm-2.748-9.16a.5.5 0 0 1-.5.5h-.05a.5.5 0 0 1-.5-.5v-2a.5.5 0 0 1 .5-.5h.05a.5.5 0 0 1 .5.5v2zm-2.116.383a.5.5 0 0 1 0 .707l-.036.036a.5.5 0 0 1-.707 0L2.428 2.965a.5.5 0 0 1 0-.707l.037-.036a.5.5 0 0 1 .707 0l1.414 1.414zm-1.09 2.823h-2a.5.5 0 0 1-.5-.5v-.051a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 .5.5v.05a.5.5 0 0 1-.5.5z"/></symbol><symbol viewBox="0 0 16 16" id="unstaged" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M2 3h12a1 1 0 0 1 0 2H2a1 1 0 1 1 0-2zm0 4h12a1 1 0 0 1 0 2H2a1 1 0 1 1 0-2zm0 4h12a1 1 0 0 1 0 2H2a1 1 0 0 1 0-2z"/></symbol><symbol viewBox="0 0 16 16" id="user" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8 7a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0 8c-6.888 0-6.976-.78-6.976-2.52S2.144 8 8 8s6.976 2.692 6.976 4.48c0 1.788-.088 2.52-6.976 2.52z"/></symbol><symbol viewBox="0 0 16 16" id="users" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10.521 8.01C15.103 8.19 16 10.755 16 12.48c0 1.533-.056 2.29-3.808 2.475.609-.54.808-1.331.808-2.475 0-1.911-.804-3.503-2.479-4.47zm-1.67-1.228A3.987 3.987 0 0 0 9.976 4a3.987 3.987 0 0 0-1.125-2.782 3 3 0 1 1 0 5.563zM5.976 7a3 3 0 1 1 0-6 3 3 0 0 1 0 6zM6 15c-5.924 0-6-.78-6-2.52S.964 8 6 8s6 2.692 6 4.48c0 1.788-.076 2.52-6 2.52z"/></symbol><symbol viewBox="0 0 16 16" id="volume-up" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M1 5h1v6H1a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1zm2 0l4.445-2.964A1 1 0 0 1 9 2.87v10.26a1 1 0 0 1-1.555.833L3 11V5zm10.283 7.89a.5.5 0 0 1-.66-.752A5.485 5.485 0 0 0 14.5 8c0-1.601-.687-3.09-1.865-4.128a.5.5 0 0 1 .661-.75A6.484 6.484 0 0 1 15.5 8a6.485 6.485 0 0 1-2.217 4.89zm-2.002-2.236a.5.5 0 1 1-.652-.758c.55-.472.871-1.157.871-1.896 0-.732-.315-1.411-.856-1.883a.5.5 0 0 1 .658-.753A3.492 3.492 0 0 1 12.5 8c0 1.033-.45 1.994-1.219 2.654z"/></symbol><symbol viewBox="0 0 16 16" id="warning" xmlns="http://www.w3.org/2000/svg"><path d="M15.572 10.506c.867 1.42.375 3.247-1.098 4.082a3.184 3.184 0 0 1-1.57.412h-9.81C1.387 15 0 13.665 0 12.018a2.9 2.9 0 0 1 .427-1.512L5.332 2.47C6.2 1.05 8.096.577 9.57 1.412c.453.257.831.622 1.098 1.059l4.905 8.035zM8.89 3.479a1.014 1.014 0 0 0-.366-.353 1.053 1.053 0 0 0-1.412.353l-4.905 8.035a.967.967 0 0 0-.143.504c0 .549.462.994 1.032.994h9.81c.184 0 .364-.048.523-.137a.974.974 0 0 0 .366-1.361L8.889 3.479zM8 5a1 1 0 0 1 1 1v2a1 1 0 1 1-2 0V6a1 1 0 0 1 1-1zm0 7a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></symbol><symbol viewBox="0 0 16 16" id="work" xmlns="http://www.w3.org/2000/svg"><path d="M12 3h1a3 3 0 0 1 3 3v7a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V6a3 3 0 0 1 3-3h1V2a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1zM6 2v1h4V2H6zM3 5a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1H3zm1.5 1a.5.5 0 0 1 .5.5v6a.5.5 0 1 1-1 0v-6a.5.5 0 0 1 .5-.5zm7 0a.5.5 0 0 1 .5.5v6a.5.5 0 1 1-1 0v-6a.5.5 0 0 1 .5-.5z"/></symbol></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/cluster_popover.svg b/app/assets/images/illustrations/cluster_popover.svg
deleted file mode 100644
index 202231373f1..00000000000
--- a/app/assets/images/illustrations/cluster_popover.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="142" height="104" viewBox="0 0 142 104"><g fill="none" fill-rule="evenodd"><g transform="translate(112 4)"><path fill="#FFF" d="M8 4a4 4 0 0 0-4 4v14a4 4 0 0 0 4 4h14a4 4 0 0 0 4-4V8a4 4 0 0 0-4-4H8z"/><path fill="#FDC4A8" fill-rule="nonzero" d="M8 4a4 4 0 0 0-4 4v14a4 4 0 0 0 4 4h14a4 4 0 0 0 4-4V8a4 4 0 0 0-4-4H8zm0-4h14a8 8 0 0 1 8 8v14a8 8 0 0 1-8 8H8a8 8 0 0 1-8-8V8a8 8 0 0 1 8-8z"/><rect width="10" height="10" x="10" y="10" fill="#FC6D26" rx="5"/></g><g transform="translate(5 74)"><rect width="30" height="30" fill="#FFF" rx="8"/><path fill="#E1DBF1" fill-rule="nonzero" d="M8 4a4 4 0 0 0-4 4v14a4 4 0 0 0 4 4h14a4 4 0 0 0 4-4V8a4 4 0 0 0-4-4H8zm0-4h14a8 8 0 0 1 8 8v14a8 8 0 0 1-8 8H8a8 8 0 0 1-8-8V8a8 8 0 0 1 8-8z"/><rect width="10" height="10" x="10" y="10" fill="#6B4FBB" rx="5"/></g><path fill="#FFF" d="M6 4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H6z"/><path fill="#FDC4A8" fill-rule="nonzero" d="M6 4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H6zm0-4h12a6 6 0 0 1 6 6v12a6 6 0 0 1-6 6H6a6 6 0 0 1-6-6V6a6 6 0 0 1 6-6z"/><rect width="8" height="8" x="8" y="8" fill="#FC6D26" rx="4"/><g transform="translate(112 77)"><rect width="24" height="24" fill="#FFF" rx="6"/><path fill="#E1DBF1" fill-rule="nonzero" d="M6 4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H6zm0-4h12a6 6 0 0 1 6 6v12a6 6 0 0 1-6 6H6a6 6 0 0 1-6-6V6a6 6 0 0 1 6-6z"/><rect width="8" height="8" x="8" y="8" fill="#6B4FBB" rx="4"/></g><g transform="translate(46 29)"><rect width="46" height="46" y="2" fill="#E1DBF1" rx="10"/><rect width="46" height="46" fill="#E1DBF1" rx="10"/><path fill="#C3B8E3" fill-rule="nonzero" d="M10 4a6 6 0 0 0-6 6v26a6 6 0 0 0 6 6h26a6 6 0 0 0 6-6V10a6 6 0 0 0-6-6H10zm0-4h26c5.523 0 10 4.477 10 10v26c0 5.523-4.477 10-10 10H10C4.477 46 0 41.523 0 36V10C0 4.477 4.477 0 10 0z"/><rect width="14" height="14" x="16" y="16" fill="#6B4FBB" rx="2"/></g><path fill="#E1DBF1" fill-rule="nonzero" d="M98.413 35.682a2 2 0 1 1-2.826-2.83l2.122-2.12a2 2 0 1 1 2.827 2.83l-2.123 2.12z"/><path fill="#C3B8E3" d="M104.78 29.32a2 2 0 0 1-2.826-2.829l2.122-2.12a2 2 0 0 1 2.827 2.83l-2.122 2.12z"/><path fill="#C3B8E3" fill-rule="nonzero" d="M42.413 89.682a2 2 0 1 1-2.826-2.83l2.122-2.12a2 2 0 1 1 2.827 2.83l-2.123 2.12z"/><path fill="#E1DBF1" d="M48.78 83.32a2 2 0 1 1-2.826-2.829l2.122-2.12a2 2 0 1 1 2.827 2.83l-2.122 2.12z"/><path fill="#E1DBF1" fill-rule="nonzero" d="M27.713 26.531a2 2 0 1 1 2.574-3.062l2.296 1.93a2 2 0 1 1-2.573 3.062l-2.297-1.93z"/><path fill="#C3B8E3" d="M34.604 32.321a2 2 0 1 1 2.573-3.062l2.297 1.93A2 2 0 0 1 36.9 34.25l-2.297-1.93z"/><path fill="#C3B8E3" fill-rule="nonzero" d="M93.74 74.553a2 2 0 0 1 2.52-3.106l2.33 1.891a2 2 0 1 1-2.521 3.106l-2.33-1.891z"/><path fill="#E1DBF1" d="M100.727 80.225a2 2 0 1 1 2.521-3.105l2.33 1.89a2 2 0 1 1-2.522 3.106l-2.33-1.89z"/></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/clusters_empty.svg b/app/assets/images/illustrations/clusters_empty.svg
deleted file mode 100644
index 39627a1c314..00000000000
--- a/app/assets/images/illustrations/clusters_empty.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg height="128" viewBox="0 0 142 128" width="142" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M94 62h20v4H94z" fill="#f0edf8"/><path d="M84.828 84l17.678 17.678-2.828 2.828L82 86.828z" fill="#fee1d3"/><path d="M42.828 24l17.678 17.678-2.828 2.828L40 26.828zM40 101.678L57.678 84l2.828 2.828-17.678 17.678z" fill="#f0edf8"/><path d="M82 41.678L99.678 24l2.828 2.828-17.678 17.678zM28 62h20v4H28zM3 52h24v24H3z" fill="#fee1d3"/><path d="M31 3h24v24H31z" fill="#f0edf8"/><path d="M87 3h24v24H87z" fill="#fef0e8"/><path d="M115 52h24v24h-24z" fill="#f0edf8"/><path d="M87 101h24v24H87z" fill="#fee1d3"/><path d="M31 101h24v24H31z" fill="#f0edf8"/><path d="M49 42h44v44H49z" fill="#c3b8e3"/><g fill-rule="nonzero"><path d="M5 53a1 1 0 0 0-1 1v20a1 1 0 0 0 1 1h20a1 1 0 0 0 1-1V54a1 1 0 0 0-1-1zm0-4h20a5 5 0 0 1 5 5v20a5 5 0 0 1-5 5H5a5 5 0 0 1-5-5V54a5 5 0 0 1 5-5z" fill="#fdc4a8"/><path d="M56 43a6 6 0 0 0-6 6v30a6 6 0 0 0 6 6h30a6 6 0 0 0 6-6V49a6 6 0 0 0-6-6zm0-4h30c5.523 0 10 4.477 10 10v30c0 5.523-4.477 10-10 10H56c-5.523 0-10-4.477-10-10V49c0-5.523 4.477-10 10-10z" fill="#6b4fbb"/><path d="M89 4a1 1 0 0 0-1 1v20a1 1 0 0 0 1 1h20a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1zm0-4h20a5 5 0 0 1 5 5v20a5 5 0 0 1-5 5H89a5 5 0 0 1-5-5V5a5 5 0 0 1 5-5z" fill="#fee1d3"/><path d="M89 102a1 1 0 0 0-1 1v20a1 1 0 0 0 1 1h20a1 1 0 0 0 1-1v-20a1 1 0 0 0-1-1zm0-4h20a5 5 0 0 1 5 5v20a5 5 0 0 1-5 5H89a5 5 0 0 1-5-5v-20a5 5 0 0 1 5-5z" fill="#fdc4a8"/><path d="M117 53a1 1 0 0 0-1 1v20a1 1 0 0 0 1 1h20a1 1 0 0 0 1-1V54a1 1 0 0 0-1-1zm0-4h20a5 5 0 0 1 5 5v20a5 5 0 0 1-5 5h-20a5 5 0 0 1-5-5V54a5 5 0 0 1 5-5zM33 102a1 1 0 0 0-1 1v20a1 1 0 0 0 1 1h20a1 1 0 0 0 1-1v-20a1 1 0 0 0-1-1zm0-4h20a5 5 0 0 1 5 5v20a5 5 0 0 1-5 5H33a5 5 0 0 1-5-5v-20a5 5 0 0 1 5-5zM33 4a1 1 0 0 0-1 1v20a1 1 0 0 0 1 1h20a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1zm0-4h20a5 5 0 0 1 5 5v20a5 5 0 0 1-5 5H33a5 5 0 0 1-5-5V5a5 5 0 0 1 5-5z" fill="#e1dbf1"/></g></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/convdev/convdev_no_data.svg b/app/assets/images/illustrations/convdev/convdev_no_data.svg
deleted file mode 100644
index b90eddcccfa..00000000000
--- a/app/assets/images/illustrations/convdev/convdev_no_data.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="360" height="220" viewBox="0 0 360 220"><g fill="none" fill-rule="evenodd"><path fill="#000" fill-opacity=".02" d="M125 44V24.003C125 18.48 129.483 14 135.005 14h89.99C230.52 14 235 18.477 235 24.003V43h84.992C326.624 43 332 48.372 332 55.002v144.996c0 6.63-5.38 12.002-12.008 12.002h-85.984c-6.632 0-12.008-5.372-12.008-12.002V183h-78v17.002c0 6.626-5.38 11.998-12.008 11.998H46.008C39.376 212 34 206.624 34 200.002V55.998C34 49.372 39.38 44 46.008 44H125z"/><g transform="translate(214 36)"><rect width="110" height="168" x="2" y="2" fill="#FFF" rx="10"/><path fill="#EEE" fill-rule="nonzero" d="M4 12.006c0-2.208.896-4.27 2.457-5.77a2 2 0 0 0-2.773-2.883A11.974 11.974 0 0 0 0 12.006a2 2 0 1 0 4 0zM14.388 4h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm17.51.227a8.015 8.015 0 0 1 5.022 3.756 2 2 0 1 0 3.458-2.011A12.01 12.01 0 0 0 104.844.34a2 2 0 0 0-.946 3.887zM110 16.78v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm-.024 17.844a7.99 7.99 0 0 1-2.903 5.558 2 2 0 0 0 2.54 3.09 11.977 11.977 0 0 0 4.35-8.338 2.002 2.002 0 0 0-1.838-2.15 2.003 2.003 0 0 0-2.15 1.84zM98.826 168h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-17.334-.4a8.032 8.032 0 0 1-4.71-4.143 1.998 1.998 0 0 0-2.667-.938 1.997 1.997 0 0 0-.938 2.667 12.022 12.022 0 0 0 7.063 6.21 1.998 1.998 0 1 0 1.252-3.798zM4 154.434v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0z"/><path fill="#F0EDF8" fill-rule="nonzero" d="M57 111c-11.598 0-21-9.402-21-21s9.402-21 21-21 21 9.402 21 21-9.402 21-21 21zm0-4c9.39 0 17-7.61 17-17s-7.61-17-17-17-17 7.61-17 17 7.61 17 17 17z"/><path fill="#6B4FBB" d="M58 88v-6.997c0-1.11-.895-2.003-2-2.003-1.112 0-2 .897-2 2.003v8.994a1.999 1.999 0 0 0 2.503 1.94c.162.04.33.063.506.063h7.98a2 2 0 0 0 .001-4H58z"/><rect width="8" height="4" x="8" y="14" fill="#EEE" rx="2"/><path fill="#EEE" d="M21 16c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 21 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 34 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 47 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 60 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 73 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 86 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 99 16z"/></g><g transform="translate(118 7)"><rect width="110" height="168" x="2" y="2" fill="#FFF" rx="10"/><path fill="#EEE" fill-rule="nonzero" d="M4 12.006c0-2.208.896-4.27 2.457-5.77a2 2 0 0 0-2.773-2.883A11.974 11.974 0 0 0 0 12.006a2 2 0 1 0 4 0zM14.388 4h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm17.51.227a8.015 8.015 0 0 1 5.022 3.756 2 2 0 1 0 3.458-2.011A12.01 12.01 0 0 0 104.844.34a2 2 0 0 0-.946 3.887zM110 16.78v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm-.024 17.844a7.99 7.99 0 0 1-2.903 5.558 2 2 0 0 0 2.54 3.09 11.977 11.977 0 0 0 4.35-8.338 2.002 2.002 0 0 0-1.838-2.15 2.003 2.003 0 0 0-2.15 1.84zM98.826 168h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-17.334-.4a8.032 8.032 0 0 1-4.71-4.143 1.998 1.998 0 0 0-2.667-.938 1.997 1.997 0 0 0-.938 2.667 12.022 12.022 0 0 0 7.063 6.21 1.998 1.998 0 1 0 1.252-3.798zM4 154.434v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0z"/><g fill-rule="nonzero"><path fill="#F0EDF8" d="M57 112c-12.15 0-22-9.85-22-22s9.85-22 22-22 22 9.85 22 22-9.85 22-22 22zm0-6c8.837 0 16-7.163 16-16s-7.163-16-16-16-16 7.163-16 16 7.163 16 16 16z"/><path fill="#6B4FBB" d="M41.692 105.8A21.93 21.93 0 0 0 57 112c12.15 0 22-9.85 22-22s-9.85-22-22-22v6c8.837 0 16 7.163 16 16s-7.163 16-16 16a15.935 15.935 0 0 1-11.133-4.508l-4.175 4.31z"/></g><path fill="#EEE" d="M8 16c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2H9.998A1.995 1.995 0 0 1 8 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 21 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 34 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 47 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 60 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 73 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 86 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 99 16z"/></g><g transform="translate(26 36)"><rect width="110" height="168" x="2" y="2" fill="#FFF" rx="10"/><path fill="#EEE" fill-rule="nonzero" d="M4 12.006v147.988A8 8 0 0 0 12.005 168h89.99a8.007 8.007 0 0 0 8.005-8.006V12.006A8 8 0 0 0 101.995 4h-89.99A8.007 8.007 0 0 0 4 12.006zm-4 0C0 5.376 5.377 0 12.005 0h89.99C108.628 0 114 5.37 114 12.006v147.988c0 6.63-5.377 12.006-12.005 12.006h-89.99C5.372 172 0 166.63 0 159.994V12.006z"/><g transform="translate(21 82)"><rect width="24" height="4" y="10" fill="#F0EDF8" rx="2"/><rect width="14" height="4" x="5" fill="#6B4FBB" rx="2"/></g><g transform="translate(69 82)"><rect width="24" height="4" y="10" fill="#F0EDF8" rx="2"/><rect width="14" height="4" x="5" fill="#6B4FBB" rx="2"/></g><g transform="translate(38 42)"><rect width="22" height="4" x="8" fill="#FEE1D3" rx="2"/><rect width="38" height="4" y="12" fill="#FB722E" rx="2"/></g><path fill="#EEE" d="M4 14h106v4H4z"/><path fill="#333" d="M35.724 138h9.696v-2.856h-2.856V122.76h-2.592c-1.08.648-2.136 1.08-3.792 1.392v2.184h2.856v8.808h-3.312V138zm17.736.288c-2.952 0-5.76-2.208-5.76-7.56 0-5.688 2.952-8.256 6.168-8.256 2.016 0 3.48.84 4.44 1.824l-1.848 2.112c-.528-.576-1.488-1.08-2.376-1.08-1.68 0-3.024 1.2-3.144 4.752.792-1.008 2.112-1.608 3.048-1.608 2.616 0 4.536 1.488 4.536 4.704 0 3.168-2.304 5.112-5.064 5.112zm-.072-2.64c1.056 0 1.92-.744 1.92-2.472 0-1.608-.84-2.208-1.992-2.208-.792 0-1.68.432-2.304 1.512.312 2.4 1.32 3.168 2.376 3.168zM63.9 132c-2.256 0-3.888-1.848-3.888-4.992 0-3.12 1.632-4.944 3.888-4.944 2.256 0 3.912 1.824 3.912 4.944 0 3.144-1.656 4.992-3.912 4.992zm0-1.968c.792 0 1.44-.792 1.44-3.024s-.648-2.976-1.44-2.976c-.792 0-1.44.744-1.44 2.976s.648 3.024 1.44 3.024zm.528 8.256l8.448-16.224h2.04l-8.448 16.224h-2.04zm11.016 0c-2.256 0-3.888-1.848-3.888-4.992 0-3.12 1.632-4.944 3.888-4.944 2.256 0 3.912 1.824 3.912 4.944 0 3.144-1.656 4.992-3.912 4.992zm0-1.968c.792 0 1.44-.792 1.44-3.024s-.648-2.976-1.44-2.976c-.792 0-1.44.744-1.44 2.976s.648 3.024 1.44 3.024z"/></g></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/convdev/convdev_no_index.svg b/app/assets/images/illustrations/convdev/convdev_no_index.svg
deleted file mode 100644
index 4aaf505e0b8..00000000000
--- a/app/assets/images/illustrations/convdev/convdev_no_index.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="360" height="200" viewBox="0 0 360 200"><g fill="none" fill-rule="evenodd" transform="translate(3 11)"><rect width="110" height="168" x="6" y="8" fill="#000" fill-opacity=".02" rx="10"/><g transform="translate(0 2)"><rect width="110" height="168" fill="#FFF" rx="10"/><path fill="#EEE" fill-rule="nonzero" d="M2 10.006v147.988A8 8 0 0 0 10.005 166h89.99a8.007 8.007 0 0 0 8.005-8.006V10.006A8 8 0 0 0 99.995 2h-89.99A8.007 8.007 0 0 0 2 10.006zm-4 0C-2 3.376 3.377-2 10.005-2h89.99C106.628-2 112 3.37 112 10.006v147.988c0 6.63-5.377 12.006-12.005 12.006h-89.99C3.372 170-2 164.63-2 157.994V10.006z"/><g transform="translate(19 80)"><rect width="24" height="4" y="10" fill="#F0EDF8" rx="2"/><rect width="14" height="4" x="5" fill="#6B4FBB" rx="2"/></g><g transform="translate(67 80)"><rect width="24" height="4" y="10" fill="#F0EDF8" rx="2"/><rect width="14" height="4" x="5" fill="#6B4FBB" rx="2"/></g><g transform="translate(36 40)"><rect width="22" height="4" x="8" fill="#FEE1D3" rx="2"/><rect width="38" height="4" y="12" fill="#FB722E" rx="2"/></g><path fill="#EEE" d="M2 12h106v4H2z"/><path fill="#333" d="M38.048 127.792c.792 0 1.68-.432 2.28-1.512-.312-2.4-1.296-3.168-2.376-3.168-1.032 0-1.92.744-1.92 2.472 0 1.608.864 2.208 2.016 2.208zm-.552 8.496c-2.016 0-3.504-.864-4.464-1.824l1.872-2.112c.504.576 1.464 1.08 2.352 1.08 1.704 0 3.024-1.2 3.144-4.752-.792 1.008-2.112 1.608-3.048 1.608-2.592 0-4.536-1.488-4.536-4.704 0-3.168 2.304-5.112 5.064-5.112 2.952 0 5.784 2.208 5.784 7.56 0 5.688-2.976 8.256-6.168 8.256zm13.488 0c-3.048 0-5.304-1.704-5.304-4.176 0-1.848 1.152-2.976 2.592-3.744v-.096c-1.176-.888-2.04-1.992-2.04-3.6 0-2.592 2.04-4.2 4.872-4.2 2.784 0 4.632 1.656 4.632 4.176 0 1.464-.936 2.64-1.992 3.336v.096c1.464.792 2.64 1.968 2.64 3.984 0 2.4-2.16 4.224-5.4 4.224zm.96-9.168c.6-.696.936-1.44.936-2.232 0-1.176-.696-1.968-1.848-1.968-.936 0-1.704.576-1.704 1.752 0 1.248 1.056 1.848 2.616 2.448zm-.888 6.72c1.176 0 2.04-.624 2.04-1.896 0-1.344-1.296-1.848-3.216-2.664-.672.624-1.176 1.488-1.176 2.424 0 1.344 1.08 2.136 2.352 2.136zm10.8-3.84c-2.256 0-3.888-1.848-3.888-4.992 0-3.12 1.632-4.944 3.888-4.944 2.256 0 3.912 1.824 3.912 4.944 0 3.144-1.656 4.992-3.912 4.992zm0-1.968c.792 0 1.44-.792 1.44-3.024s-.648-2.976-1.44-2.976c-.792 0-1.44.744-1.44 2.976s.648 3.024 1.44 3.024zm.528 8.256l8.448-16.224h2.04l-8.448 16.224h-2.04zm11.016 0c-2.256 0-3.888-1.848-3.888-4.992 0-3.12 1.632-4.944 3.888-4.944 2.256 0 3.912 1.824 3.912 4.944 0 3.144-1.656 4.992-3.912 4.992zm0-1.968c.792 0 1.44-.792 1.44-3.024s-.648-2.976-1.44-2.976c-.792 0-1.44.744-1.44 2.976s.648 3.024 1.44 3.024z"/></g><g transform="translate(122)"><rect width="110" height="168" x="2" y="2" fill="#FFF" rx="10"/><path fill="#EEE" fill-rule="nonzero" d="M4 12.006c0-2.208.896-4.27 2.457-5.77a2 2 0 0 0-2.773-2.883A11.974 11.974 0 0 0 0 12.006a2 2 0 1 0 4 0zM14.388 4h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm17.51.227a8.015 8.015 0 0 1 5.022 3.756 2 2 0 1 0 3.458-2.011A12.01 12.01 0 0 0 104.844.34a2 2 0 0 0-.946 3.887zM110 16.78v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm-.024 17.844a7.99 7.99 0 0 1-2.903 5.558 2 2 0 0 0 2.54 3.09 11.977 11.977 0 0 0 4.35-8.338 2.002 2.002 0 0 0-1.838-2.15 2.003 2.003 0 0 0-2.15 1.84zM98.826 168h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-17.334-.4a8.032 8.032 0 0 1-4.71-4.143 1.998 1.998 0 0 0-2.667-.938 1.997 1.997 0 0 0-.938 2.667 12.022 12.022 0 0 0 7.063 6.21 1.998 1.998 0 1 0 1.252-3.798zM4 154.434v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0z"/><g transform="translate(21 82)"><rect width="24" height="4" y="10" fill="#F0EDF8" rx="2"/><rect width="14" height="4" x="5" fill="#C3B8E3" rx="2"/></g><g transform="translate(69 82)"><rect width="24" height="4" y="10" fill="#F0EDF8" rx="2"/><rect width="14" height="4" x="5" fill="#C3B8E3" rx="2"/></g><path fill="#FEE1D3" d="M44 44a2 2 0 0 1 1.998-2h2.004a2 2 0 0 1 0 4h-2.004A1.994 1.994 0 0 1 44 44zm10 0a2 2 0 0 1 1.998-2h2.004a2 2 0 0 1 0 4h-2.004A1.994 1.994 0 0 1 54 44zm10 0a2 2 0 0 1 1.998-2h2.004a2 2 0 0 1 0 4h-2.004A1.994 1.994 0 0 1 64 44zM34 56a2 2 0 0 1 1.998-2h2.004a2 2 0 0 1 0 4h-2.004A1.994 1.994 0 0 1 34 56zm10 0a2 2 0 0 1 1.998-2h2.004a2 2 0 0 1 0 4h-2.004A1.994 1.994 0 0 1 44 56zm10 0a2 2 0 0 1 1.998-2h2.004a2 2 0 0 1 0 4h-2.004A1.994 1.994 0 0 1 54 56zm10 0a2 2 0 0 1 1.998-2h2.004a2 2 0 0 1 0 4h-2.004A1.994 1.994 0 0 1 64 56zm10 0a2 2 0 0 1 1.998-2h2.004a2 2 0 0 1 0 4h-2.004A1.994 1.994 0 0 1 74 56z"/><rect width="8" height="4" x="8" y="14" fill="#EEE" rx="2"/><rect width="8" height="4" x="21" y="14" fill="#EEE" rx="2"/><rect width="8" height="4" x="34" y="14" fill="#EEE" rx="2"/><rect width="8" height="4" x="47" y="14" fill="#EEE" rx="2"/><rect width="8" height="4" x="60" y="14" fill="#EEE" rx="2"/><rect width="8" height="4" x="73" y="14" fill="#EEE" rx="2"/><rect width="8" height="4" x="86" y="14" fill="#EEE" rx="2"/><rect width="8" height="4" x="99" y="14" fill="#EEE" rx="2"/><path fill="#EEE" d="M46.716 138.288c-3.264 0-5.448-2.784-5.448-7.968s2.184-7.848 5.448-7.848c3.264 0 5.448 2.664 5.448 7.848 0 5.184-2.184 7.968-5.448 7.968zm0-2.736c1.2 0 2.112-1.08 2.112-5.232 0-4.176-.912-5.112-2.112-5.112-1.176 0-2.112.936-2.112 5.112 0 4.152.936 5.232 2.112 5.232zM57.564 132c-2.256 0-3.888-1.848-3.888-4.992 0-3.12 1.632-4.944 3.888-4.944 2.256 0 3.912 1.824 3.912 4.944 0 3.144-1.656 4.992-3.912 4.992zm0-1.968c.792 0 1.44-.792 1.44-3.024s-.648-2.976-1.44-2.976c-.792 0-1.44.744-1.44 2.976s.648 3.024 1.44 3.024zm.528 8.256l8.448-16.224h2.04l-8.448 16.224h-2.04zm11.016 0c-2.256 0-3.888-1.848-3.888-4.992 0-3.12 1.632-4.944 3.888-4.944 2.256 0 3.912 1.824 3.912 4.944 0 3.144-1.656 4.992-3.912 4.992zm0-1.968c.792 0 1.44-.792 1.44-3.024s-.648-2.976-1.44-2.976c-.792 0-1.44.744-1.44 2.976s.648 3.024 1.44 3.024z"/></g><g transform="translate(243)"><rect width="110" height="168" x="2" y="2" fill="#FFF" rx="10"/><path fill="#EEE" fill-rule="nonzero" d="M4 12.006c0-2.208.896-4.27 2.457-5.77a2 2 0 0 0-2.773-2.883A11.974 11.974 0 0 0 0 12.006a2 2 0 1 0 4 0zM14.388 4h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm17.51.227a8.015 8.015 0 0 1 5.022 3.756 2 2 0 1 0 3.458-2.011A12.01 12.01 0 0 0 104.844.34a2 2 0 0 0-.946 3.887zM110 16.78v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm-.024 17.844a7.99 7.99 0 0 1-2.903 5.558 2 2 0 0 0 2.54 3.09 11.977 11.977 0 0 0 4.35-8.338 2.002 2.002 0 0 0-1.838-2.15 2.003 2.003 0 0 0-2.15 1.84zM98.826 168h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-17.334-.4a8.032 8.032 0 0 1-4.71-4.143 1.998 1.998 0 0 0-2.667-.938 1.997 1.997 0 0 0-.938 2.667 12.022 12.022 0 0 0 7.063 6.21 1.998 1.998 0 1 0 1.252-3.798zM4 154.434v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0z"/><path fill="#FEE1D3" d="M44 44a2 2 0 0 1 1.998-2h2.004a2 2 0 0 1 0 4h-2.004A1.994 1.994 0 0 1 44 44zm10 0a2 2 0 0 1 1.998-2h2.004a2 2 0 0 1 0 4h-2.004A1.994 1.994 0 0 1 54 44zm10 0a2 2 0 0 1 1.998-2h2.004a2 2 0 0 1 0 4h-2.004A1.994 1.994 0 0 1 64 44zM34 56a2 2 0 0 1 1.998-2h2.004a2 2 0 0 1 0 4h-2.004A1.994 1.994 0 0 1 34 56zm10 0a2 2 0 0 1 1.998-2h2.004a2 2 0 0 1 0 4h-2.004A1.994 1.994 0 0 1 44 56zm10 0a2 2 0 0 1 1.998-2h2.004a2 2 0 0 1 0 4h-2.004A1.994 1.994 0 0 1 54 56zm10 0a2 2 0 0 1 1.998-2h2.004a2 2 0 0 1 0 4h-2.004A1.994 1.994 0 0 1 64 56zm10 0a2 2 0 0 1 1.998-2h2.004a2 2 0 0 1 0 4h-2.004A1.994 1.994 0 0 1 74 56z"/><g transform="translate(21 82)"><rect width="24" height="4" y="10" fill="#F0EDF8" rx="2"/><rect width="14" height="4" x="5" fill="#C3B8E3" rx="2"/></g><g transform="translate(69 82)"><rect width="24" height="4" y="10" fill="#F0EDF8" rx="2"/><rect width="14" height="4" x="5" fill="#C3B8E3" rx="2"/></g><rect width="8" height="4" x="8" y="14" fill="#EEE" rx="2"/><rect width="8" height="4" x="21" y="14" fill="#EEE" rx="2"/><rect width="8" height="4" x="34" y="14" fill="#EEE" rx="2"/><rect width="8" height="4" x="47" y="14" fill="#EEE" rx="2"/><rect width="8" height="4" x="60" y="14" fill="#EEE" rx="2"/><rect width="8" height="4" x="73" y="14" fill="#EEE" rx="2"/><rect width="8" height="4" x="86" y="14" fill="#EEE" rx="2"/><rect width="8" height="4" x="99" y="14" fill="#EEE" rx="2"/><path fill="#EEE" d="M46.716 138.288c-3.264 0-5.448-2.784-5.448-7.968s2.184-7.848 5.448-7.848c3.264 0 5.448 2.664 5.448 7.848 0 5.184-2.184 7.968-5.448 7.968zm0-2.736c1.2 0 2.112-1.08 2.112-5.232 0-4.176-.912-5.112-2.112-5.112-1.176 0-2.112.936-2.112 5.112 0 4.152.936 5.232 2.112 5.232zM57.564 132c-2.256 0-3.888-1.848-3.888-4.992 0-3.12 1.632-4.944 3.888-4.944 2.256 0 3.912 1.824 3.912 4.944 0 3.144-1.656 4.992-3.912 4.992zm0-1.968c.792 0 1.44-.792 1.44-3.024s-.648-2.976-1.44-2.976c-.792 0-1.44.744-1.44 2.976s.648 3.024 1.44 3.024zm.528 8.256l8.448-16.224h2.04l-8.448 16.224h-2.04zm11.016 0c-2.256 0-3.888-1.848-3.888-4.992 0-3.12 1.632-4.944 3.888-4.944 2.256 0 3.912 1.824 3.912 4.944 0 3.144-1.656 4.992-3.912 4.992zm0-1.968c.792 0 1.44-.792 1.44-3.024s-.648-2.976-1.44-2.976c-.792 0-1.44.744-1.44 2.976s.648 3.024 1.44 3.024z"/></g></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/convdev/convdev_overview.svg b/app/assets/images/illustrations/convdev/convdev_overview.svg
deleted file mode 100644
index a06d70812ca..00000000000
--- a/app/assets/images/illustrations/convdev/convdev_overview.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="208" height="127" viewBox="0 0 208 127" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><rect id="a" width="58" height="98" y="17" rx="6"/><rect id="b" width="58" height="98" x="3.5" y="17" rx="6"/><rect id="c" width="58" height="98.394" rx="6"/></defs><g fill="none" fill-rule="evenodd" transform="translate(1)"><path fill="#000" fill-opacity=".06" fill-rule="nonzero" d="M16 11.06c0-1.39.56-2.69 1.534-3.635.398-.386.41-1.025.027-1.426a.993.993 0 0 0-1.413-.028A7.075 7.075 0 0 0 14 11.062c0 .556.448 1.007 1 1.007s1-.452 1-1.01zm6.432-5.043h4.8c.552 0 1-.452 1-1.01 0-.556-.448-1.007-1-1.007h-4.8c-.552 0-1 .45-1 1.008 0 .557.448 1.01 1 1.01zm10.8 0h4.8c.552 0 1-.452 1-1.01 0-.556-.448-1.007-1-1.007h-4.8c-.552 0-1 .45-1 1.008 0 .557.448 1.01 1 1.01zm10.8 0h4.8c.552 0 1-.452 1-1.01 0-.556-.448-1.007-1-1.007h-4.8c-.552 0-1 .45-1 1.008 0 .557.448 1.01 1 1.01zm10.8 0h4.8c.552 0 1-.452 1-1.01 0-.556-.448-1.007-1-1.007h-4.8c-.552 0-1 .45-1 1.008 0 .557.448 1.01 1 1.01zm10.8 0h4.8c.552 0 1-.452 1-1.01 0-.556-.448-1.007-1-1.007h-4.8c-.552 0-1 .45-1 1.008 0 .557.448 1.01 1 1.01zm10.8 0h4.8c.552 0 1-.452 1-1.01 0-.556-.448-1.007-1-1.007h-4.8c-.552 0-1 .45-1 1.008 0 .557.448 1.01 1 1.01zm10.8 0h4.8c.552 0 1-.452 1-1.01 0-.556-.448-1.007-1-1.007h-4.8c-.552 0-1 .45-1 1.008 0 .557.448 1.01 1 1.01zm10.8 0h4.8c.552 0 1-.452 1-1.01 0-.556-.448-1.007-1-1.007h-4.8c-.552 0-1 .45-1 1.008 0 .557.448 1.01 1 1.01zm10.8 0h4.8c.552 0 1-.452 1-1.01 0-.556-.448-1.007-1-1.007h-4.8c-.552 0-1 .45-1 1.008 0 .557.448 1.01 1 1.01zm10.8 0h4.8c.552 0 1-.452 1-1.01 0-.556-.448-1.007-1-1.007h-4.8c-.552 0-1 .45-1 1.008 0 .557.448 1.01 1 1.01zm10.8 0h4.8c.552 0 1-.452 1-1.01 0-.556-.448-1.007-1-1.007h-4.8c-.552 0-1 .45-1 1.008 0 .557.448 1.01 1 1.01zm10.8 0h4.8c.552 0 1-.452 1-1.01 0-.556-.448-1.007-1-1.007h-4.8c-.552 0-1 .45-1 1.008 0 .557.448 1.01 1 1.01zm10.8 0h4.8c.552 0 1-.452 1-1.01 0-.556-.448-1.007-1-1.007h-4.8c-.552 0-1 .45-1 1.008 0 .557.448 1.01 1 1.01zm10.8 0h4.8c.552 0 1-.452 1-1.01 0-.556-.448-1.007-1-1.007h-4.8c-.552 0-1 .45-1 1.008 0 .557.448 1.01 1 1.01zm10.8 0h4.8c.552 0 1-.452 1-1.01 0-.556-.448-1.007-1-1.007h-4.8c-.552 0-1 .45-1 1.008 0 .557.448 1.01 1 1.01zm10.8 0H185a4.95 4.95 0 0 1 3.254 1.215.995.995 0 0 0 1.41-.108c.36-.423.312-1.06-.107-1.422A6.944 6.944 0 0 0 185 4h-.568c-.552 0-1 .45-1 1.008 0 .557.448 1.01 1 1.01zM190 11.932v4.84c0 .557.448 1.008 1 1.008s1-.45 1-1.008v-4.84c0-.556-.448-1.008-1-1.008s-1 .452-1 1.008zm0 10.89v4.84c0 .556.448 1.008 1 1.008s1-.452 1-1.01v-4.838c0-.557-.448-1.01-1-1.01s-1 .453-1 1.01zm0 10.89v4.84c0 .555.448 1.007 1 1.007s1-.453 1-1.01v-4.84c0-.556-.448-1.007-1-1.007s-1 .45-1 1.008zm0 10.888v4.84c0 .557.448 1.008 1 1.008s1-.45 1-1.008V44.6c0-.557-.448-1.008-1-1.008s-1 .45-1 1.008zm0 10.89v4.84c0 .556.448 1.007 1 1.007s1-.45 1-1.008v-4.84c0-.557-.448-1.01-1-1.01s-1 .453-1 1.01zm0 10.89v4.838c0 .557.448 1.01 1 1.01s1-.453 1-1.01v-4.84c0-.556-.448-1.008-1-1.008s-1 .452-1 1.01zm0 10.888v4.84c0 .556.448 1.008 1 1.008s1-.452 1-1.008v-4.84c0-.557-.448-1.008-1-1.008s-1 .45-1 1.008zm0 10.89v4.84c0 .556.448 1.007 1 1.007s1-.45 1-1.008v-4.84c0-.557-.448-1.008-1-1.008s-1 .45-1 1.007zm0 10.888v4.84c0 .557.448 1.008 1 1.008s1-.45 1-1.008v-4.84c0-.556-.448-1.008-1-1.008s-1 .452-1 1.008zm-.24 21.446a5.06 5.06 0 0 1-2.572 2.985 1.01 1.01 0 0 0-.46 1.348c.24.5.84.708 1.336.464a7.06 7.06 0 0 0 3.598-4.178c.17-.53-.12-1.098-.644-1.27a1 1 0 0 0-1.26.65zm-8.063 3.49h-4.8c-.552 0-1 .453-1 1.01 0 .557.448 1.008 1 1.008h4.8c.553 0 1-.45 1-1.008 0-.557-.447-1.01-1-1.01zm-10.8 0h-4.8c-.552 0-1 .453-1 1.01 0 .557.448 1.008 1 1.008h4.8c.553 0 1-.45 1-1.008 0-.557-.447-1.01-1-1.01zm-10.8 0h-4.8c-.552 0-1 .453-1 1.01 0 .557.448 1.008 1 1.008h4.8c.553 0 1-.45 1-1.008 0-.557-.447-1.01-1-1.01zm-10.8 0h-4.8c-.552 0-1 .453-1 1.01 0 .557.448 1.008 1 1.008h4.8c.553 0 1-.45 1-1.008 0-.557-.447-1.01-1-1.01zm-10.8 0h-4.8c-.552 0-1 .453-1 1.01 0 .557.448 1.008 1 1.008h4.8c.553 0 1-.45 1-1.008 0-.557-.447-1.01-1-1.01zm-10.8 0h-4.8c-.552 0-1 .453-1 1.01 0 .557.448 1.008 1 1.008h4.8c.553 0 1-.45 1-1.008 0-.557-.447-1.01-1-1.01zm-10.8 0h-4.8c-.552 0-1 .453-1 1.01 0 .557.448 1.008 1 1.008h4.8c.553 0 1-.45 1-1.008 0-.557-.447-1.01-1-1.01zm-10.8 0h-4.8c-.552 0-1 .453-1 1.01 0 .557.448 1.008 1 1.008h4.8c.553 0 1-.45 1-1.008 0-.557-.447-1.01-1-1.01zm-10.8 0h-4.8c-.552 0-1 .453-1 1.01 0 .557.448 1.008 1 1.008h4.8c.553 0 1-.45 1-1.008 0-.557-.447-1.01-1-1.01zm-10.8 0h-4.8c-.552 0-1 .453-1 1.01 0 .557.448 1.008 1 1.008h4.8c.553 0 1-.45 1-1.008 0-.557-.447-1.01-1-1.01zm-10.8 0h-4.8c-.552 0-1 .453-1 1.01 0 .557.448 1.008 1 1.008h4.8c.553 0 1-.45 1-1.008 0-.557-.447-1.01-1-1.01zm-10.8 0h-4.8c-.552 0-1 .453-1 1.01 0 .557.448 1.008 1 1.008h4.8c.553 0 1-.45 1-1.008 0-.557-.447-1.01-1-1.01zm-10.8 0h-4.8c-.552 0-1 .453-1 1.01 0 .557.448 1.008 1 1.008h4.8c.553 0 1-.45 1-1.008 0-.557-.447-1.01-1-1.01zm-10.8 0h-4.8c-.552 0-1 .453-1 1.01 0 .557.448 1.008 1 1.008h4.8c.553 0 1-.45 1-1.008 0-.557-.447-1.01-1-1.01zm-10.8 0h-4.8c-.552 0-1 .453-1 1.01 0 .557.448 1.008 1 1.008h4.8c.553 0 1-.45 1-1.008 0-.557-.447-1.01-1-1.01zm-10.577-.116a5.009 5.009 0 0 1-3.19-2.3.994.994 0 0 0-1.373-.333c-.472.29-.62.91-.332 1.386.99 1.632 2.6 2.8 4.465 3.215a1 1 0 0 0 1.192-.768 1.005 1.005 0 0 0-.762-1.2zM16 105.292v-4.84c0-.556-.448-1.008-1-1.008s-1 .452-1 1.01v4.838c0 .557.448 1.01 1 1.01s1-.453 1-1.01zm0-10.89v-4.84c0-.555-.448-1.007-1-1.007s-1 .452-1 1.008v4.84c0 .557.448 1.008 1 1.008s1-.45 1-1.007zm0-10.888v-4.84c0-.557-.448-1.008-1-1.008s-1 .45-1 1.008v4.84c0 .557.448 1.008 1 1.008s1-.45 1-1.008zm0-10.89v-4.84c0-.556-.448-1.007-1-1.007s-1 .45-1 1.008v4.84c0 .556.448 1.008 1 1.008s1-.452 1-1.008zm0-10.89v-4.838c0-.557-.448-1.01-1-1.01s-1 .453-1 1.01v4.84c0 .556.448 1.008 1 1.008s1-.452 1-1.01zm0-11.888v-4.84c0-.556-.448-1.008-1-1.008s-1 .452-1 1.008v4.84c0 .557.448 1.008 1 1.008s1-.45 1-1.008zm0-9.89v-4.84c0-.556-.448-1.007-1-1.007s-1 .45-1 1.007v4.84c0 .557.448 1.008 1 1.008s1-.45 1-1.008zm0-10.888v-4.84c0-.557-.448-1.008-1-1.008s-1 .45-1 1.008v4.84c0 .556.448 1.008 1 1.008s1-.452 1-1.008zm0-10.89v-4.84c0-.556-.448-1.008-1-1.008s-1 .452-1 1.01v4.838c0 .557.448 1.01 1 1.01s1-.453 1-1.01z"/><g transform="translate(74)"><rect width="58" height="98" y="20" fill="#000" fill-opacity=".02" rx="6"/><use fill="#FFF" xlink:href="#a"/><rect width="56" height="96" x="1" y="18" stroke="#EEE" stroke-width="2" rx="6"/><g transform="translate(16 45.185)"><path fill="#333" d="M.59 33.815h5.655V32.15H4.58v-7.225H3.066c-.63.378-1.246.63-2.212.812v1.274H2.52v5.14H.59v1.665zm10.093.168c-1.778 0-3.094-.994-3.094-2.436 0-1.078.67-1.736 1.51-2.184v-.056c-.685-.518-1.19-1.162-1.19-2.1 0-1.512 1.19-2.45 2.843-2.45 1.624 0 2.702.966 2.702 2.436 0 .854-.546 1.54-1.162 1.946v.055c.854.462 1.54 1.148 1.54 2.324 0 1.4-1.26 2.463-3.15 2.463zm.56-5.348c.35-.406.546-.84.546-1.302 0-.686-.407-1.148-1.08-1.148-.545 0-.993.336-.993 1.022 0 .728.616 1.078 1.526 1.428zm-.518 3.92c.686 0 1.19-.364 1.19-1.106 0-.785-.756-1.08-1.876-1.555-.393.364-.687.868-.687 1.414 0 .783.63 1.245 1.372 1.245zm6.3-2.24c-1.316 0-2.268-1.078-2.268-2.912 0-1.82.952-2.884 2.268-2.884 1.316 0 2.282 1.063 2.282 2.883 0 1.834-.966 2.912-2.282 2.912zm0-1.148c.462 0 .84-.462.84-1.764s-.378-1.736-.84-1.736c-.462 0-.84.434-.84 1.736s.378 1.764.84 1.764zm.308 4.816l4.928-9.464h1.19l-4.927 9.463h-1.19zm6.426 0c-1.317 0-2.27-1.078-2.27-2.912 0-1.82.953-2.883 2.27-2.883 1.315 0 2.28 1.064 2.28 2.884 0 1.835-.965 2.913-2.28 2.913zm0-1.148c.46 0 .84-.462.84-1.764 0-1.3-.38-1.735-.84-1.735-.463 0-.84.434-.84 1.736 0 1.303.377 1.765.84 1.765z"/><rect width="13" height="2" x="6" y=".815" fill="#FB722E" rx="1"/><path fill="#F0EDF8" d="M3 47.815c0-.552.455-1 .992-1h18.016c.548 0 .992.444.992 1 0 .553-.455 1-.992 1H3.992a.994.994 0 0 1-.992-1zm0 6c0-.552.455-1 .992-1h18.016c.548 0 .992.444.992 1 0 .553-.455 1-.992 1H3.992a.994.994 0 0 1-.992-1z"/><rect width="20" height="2" x="3" y="6.815" fill="#FEE1D3" rx="1"/></g><g transform="translate(10.81)"><circle cx="18.19" cy="18" r="18" fill="#FFF"/><path fill="#F0EDF8" fill-rule="nonzero" d="M18.19 34c8.837 0 16-7.163 16-16s-7.163-16-16-16-16 7.163-16 16 7.163 16 16 16zm0 2c-9.94 0-18-8.06-18-18s8.06-18 18-18 18 8.06 18 18-8.06 18-18 18z"/><g transform="translate(10 11)"><path fill="#C3B8E3" fill-rule="nonzero" d="M2.19 13.32L5.397 11h7.783a.998.998 0 0 0 1.01-1V3c0-.55-.45-1-1.01-1H3.2a.998.998 0 0 0-1.01 1v10.32zM6.045 13l-3.422 2.476C1.28 16.45.19 15.892.19 14.23V3c0-1.657 1.337-3 3.01-3h9.98a3.004 3.004 0 0 1 3.01 3v7c0 1.657-1.337 3-3.01 3H6.045z"/><rect width="4" height="2" x="5.19" y="4" fill="#6B4FBB" rx="1"/><rect width="6" height="2" x="5.19" y="7" fill="#6B4FBB" rx="1"/></g></g></g><g transform="translate(144.5)"><rect width="58" height="98" x=".5" y="20" fill="#000" fill-opacity=".02" rx="6"/><use fill="#FFF" xlink:href="#b"/><rect width="56" height="96" x="4.5" y="18" stroke="#EEE" stroke-width="2" rx="6"/><g transform="translate(19 46.185)"><path fill="#333" d="M4.01 33.746c1.793 0 3.305-.938 3.305-2.59 0-1.148-.742-1.876-1.764-2.17v-.056c.953-.406 1.485-1.05 1.485-1.974 0-1.554-1.232-2.436-3.066-2.436-1.093 0-1.99.434-2.8 1.134l1.035 1.26c.56-.49 1.036-.784 1.666-.784.7 0 1.093.364 1.093.98 0 .714-.504 1.19-2.1 1.19v1.456c1.932 0 2.394.49 2.394 1.274 0 .672-.574 1.05-1.442 1.05-.756 0-1.414-.378-1.946-.896l-.953 1.302c.644.756 1.652 1.26 3.094 1.26zm4.51-.168h6.257v-1.736h-1.792c-.42 0-1.036.056-1.484.112 1.443-1.512 2.843-3.108 2.843-4.606 0-1.708-1.19-2.828-2.94-2.828-1.274 0-2.1.476-2.982 1.414l1.12 1.106c.45-.476.94-.91 1.583-.91.77 0 1.26.476 1.26 1.344 0 1.26-1.596 2.786-3.864 4.928v1.176zm9.505-3.5c-1.316 0-2.268-1.078-2.268-2.912 0-1.82.952-2.884 2.268-2.884 1.316 0 2.282 1.064 2.282 2.884 0 1.834-.966 2.912-2.282 2.912zm0-1.148c.462 0 .84-.462.84-1.764s-.378-1.736-.84-1.736c-.462 0-.84.434-.84 1.736s.378 1.764.84 1.764zm.308 4.816l4.928-9.464h1.19l-4.927 9.464h-1.19zm6.426 0c-1.317 0-2.27-1.078-2.27-2.912 0-1.82.953-2.884 2.27-2.884 1.315 0 2.28 1.064 2.28 2.884 0 1.834-.965 2.912-2.28 2.912zm0-1.148c.46 0 .84-.462.84-1.764s-.38-1.736-.84-1.736c-.463 0-.84.434-.84 1.736s.377 1.764.84 1.764z"/><rect width="13" height="2.008" x="7.5" fill="#FB722E" rx="1.004"/><path fill="#F0EDF8" d="M3.5 47.19c0-.556.455-1.005 1.006-1.005h17.988c.556 0 1.006.445 1.006 1.004 0 .553-.455 1.003-1.006 1.003H4.506A1.003 1.003 0 0 1 3.5 47.188zm0 6.023c0-.555.455-1.004 1.006-1.004h17.988c.556 0 1.006.444 1.006 1.003 0 .554-.455 1.004-1.006 1.004H4.506A1.003 1.003 0 0 1 3.5 53.212z"/><rect width="20" height="2.008" x="4" y="6.024" fill="#FEE1D3" rx="1.004"/></g><g transform="translate(14.413)"><circle cx="18.087" cy="18" r="18" fill="#FFF"/><path fill="#F0EDF8" fill-rule="nonzero" d="M18.087 34c8.836 0 16-7.163 16-16s-7.164-16-16-16c-8.837 0-16 7.163-16 16s7.163 16 16 16zm0 2c-9.942 0-18-8.06-18-18s8.058-18 18-18c9.94 0 18 8.06 18 18s-8.06 18-18 18z"/><path fill="#C3B8E3" fill-rule="nonzero" d="M18.087 24a6 6 0 1 0 0-12 6 6 0 0 0 0 12zm0 2c-4.42 0-8-3.582-8-8s3.58-8 8-8a8 8 0 0 1 0 16z"/><path fill="#6B4FBB" d="M19.087 17v-2c0-.556-.448-1-1-1-.557 0-1 .448-1 1v3a.997.997 0 0 0 .998 1h3c.557 0 1-.448 1-1 0-.556-.447-1-1-1h-2z"/></g></g><rect width="58" height="98" x="3" y="20" fill="#000" fill-opacity=".02" rx="6"/><g transform="translate(0 16.754)"><use fill="#FFF" xlink:href="#c"/><rect width="56" height="96.394" x="1" y="1" stroke="#EEE" stroke-width="2" rx="6"/><g transform="translate(16 29.618)"><path fill="#333" d="M3.137 27.84c.462 0 .98-.253 1.33-.883-.182-1.4-.756-1.848-1.386-1.848-.6 0-1.12.433-1.12 1.44 0 .94.505 1.29 1.177 1.29zm-.322 4.955A3.626 3.626 0 0 1 .21 31.73l1.093-1.23c.294.335.854.63 1.372.63.994 0 1.764-.7 1.834-2.773-.463.588-1.233.938-1.78.938-1.51 0-2.645-.868-2.645-2.744 0-1.847 1.344-2.98 2.954-2.98 1.72 0 3.373 1.287 3.373 4.41 0 3.317-1.736 4.815-3.598 4.815zm8.12 0c-1.722 0-3.36-1.288-3.36-4.41 0-3.318 1.722-4.816 3.598-4.816 1.176 0 2.03.49 2.59 1.063l-1.078 1.232c-.308-.336-.868-.63-1.386-.63-.98 0-1.765.7-1.835 2.772.462-.588 1.232-.938 1.778-.938 1.526 0 2.646.867 2.646 2.743 0 1.848-1.345 2.982-2.955 2.982zm-.042-1.54c.616 0 1.12-.434 1.12-1.442 0-.938-.49-1.288-1.162-1.288-.46 0-.98.252-1.343.882.182 1.4.77 1.848 1.386 1.848zm6.132-2.128c-1.316 0-2.268-1.078-2.268-2.912 0-1.82.952-2.884 2.268-2.884 1.316 0 2.282 1.065 2.282 2.885 0 1.834-.966 2.912-2.282 2.912zm0-1.148c.462 0 .84-.463.84-1.765 0-1.302-.378-1.736-.84-1.736-.462 0-.84.433-.84 1.735s.378 1.764.84 1.764zm.308 4.815l4.928-9.464h1.19l-4.927 9.465h-1.19zm6.426 0c-1.317 0-2.27-1.078-2.27-2.912 0-1.82.953-2.884 2.27-2.884 1.315 0 2.28 1.063 2.28 2.883 0 1.834-.965 2.912-2.28 2.912zm0-1.148c.46 0 .84-.462.84-1.764s-.38-1.736-.84-1.736c-.463 0-.84.434-.84 1.736s.377 1.764.84 1.764z"/><rect width="13" height="2.008" x="6.5" y=".314" fill="#FEE1D3" rx="1.004"/><path fill="#F0EDF8" d="M3 46.627c0-.552.455-1 .992-1h18.016c.548 0 .992.444.992 1 0 .553-.455 1-.992 1H3.992a.994.994 0 0 1-.992-1zm0 6c0-.552.455-1 .992-1h18.016c.548 0 .992.444.992 1 0 .553-.455 1-.992 1H3.992a.994.994 0 0 1-.992-1z"/><rect width="20" height="2" x="3" y="5.627" fill="#FB722E" rx="1"/></g></g><g transform="translate(10.41)"><circle cx="18.589" cy="18" r="18" fill="#FFF"/><path fill="#F0EDF8" fill-rule="nonzero" d="M18.59 34c8.836 0 16-7.163 16-16s-7.164-16-16-16c-8.837 0-16 7.163-16 16s7.163 16 16 16zm0 2c-9.942 0-18-8.06-18-18s8.058-18 18-18c9.94 0 18 8.06 18 18s-8.06 18-18 18z"/><path fill="#C3B8E3" d="M17.05 19.262h3.367l.248-2.808H17.3l-.25 2.808zm-.177 2.008l-.144 1.627c-.06.662-.646 1.2-1.3 1.2h.25c-.658 0-1.144-.534-1.085-1.2l.144-1.627H13.59a1.001 1.001 0 0 1-1.003-1.004c0-.555.455-1.004 1.002-1.004h1.325l.248-2.808h-1.15a1 1 0 0 1-1.004-1.004 1.01 1.01 0 0 1 1.004-1.004h1.33l.106-1.2c.058-.66.644-1.198 1.298-1.198h-.25c.66 0 1.145.533 1.086 1.2l-.106 1.198h3.365l.107-1.2c.058-.66.644-1.198 1.298-1.198h-.25c.66 0 1.145.533 1.086 1.2l-.106 1.198h1.03c.554 0 1.003.446 1.003 1.004 0 .555-.455 1.004-1 1.004H22.8l-.25 2.808h1.037a1 1 0 0 1 1.002 1.004c0 .554-.456 1.004-1.003 1.004h-1.214l-.144 1.627c-.06.662-.646 1.2-1.3 1.2h.25c-.658 0-1.144-.534-1.085-1.2l.144-1.627H16.87z"/><path fill="#6B4FBB" d="M17.05 19.262l-.177 2.008H14.74l.177-2.008h2.134zm-1.707-4.816h2.135l-.178 2.008h-2.135l.178-2.008zm5.5 0h2.135l-.178 2.008h-2.135l.178-2.008zm1.708 4.816l-.177 2.008H20.24l.177-2.008h2.134z"/></g></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/convdev/i2p_step_1.svg b/app/assets/images/illustrations/convdev/i2p_step_1.svg
deleted file mode 100644
index 67467b1513d..00000000000
--- a/app/assets/images/illustrations/convdev/i2p_step_1.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76"><path d="M45.688 18.854c-4.869-1.989-10.488-1.975-15.29-.001a20.014 20.014 0 0 0-6.493 4.268 19.798 19.798 0 0 0-4.346 6.381 19.135 19.135 0 0 0-1.525 7.537c0 2.066.33 4.118.983 6.104a20.142 20.142 0 0 0 1.83 3.937 5.983 5.983 0 0 0-2.086 4.538c0 3.309 2.691 6 6 6s6-2.691 6-6-2.691-6-6-6c-.779 0-1.522.154-2.205.425a18.13 18.13 0 0 1-1.642-3.533 17.467 17.467 0 0 1-.881-5.472c0-2.351.459-4.623 1.391-6.814a17.721 17.721 0 0 1 3.88-5.675 18.057 18.057 0 0 1 5.85-3.845c4.329-1.778 9.392-1.79 13.78.002a18.077 18.077 0 0 1 5.843 3.84c3.39 3.34 5.257 7.776 5.257 12.493a17.463 17.463 0 0 1-.878 5.481 17.451 17.451 0 0 1-2.569 4.923c-2.134 2.866-3.818 4.698-5.174 6.173-2.424 2.643-3.98 4.599-4.383 8.384H32.215a1 1 0 1 0 0 2h11.739a1 1 0 0 0 .999-.947c.19-3.645 1.345-5.263 3.934-8.09 1.385-1.506 3.107-3.381 5.304-6.331a19.422 19.422 0 0 0 2.864-5.489c.651-1.98.98-4.04.979-6.109 0-5.256-2.078-10.198-5.856-13.92a20.079 20.079 0 0 0-6.49-4.265M28.761 51.612c0 2.206-1.794 4-4 4s-4-1.794-4-4 1.794-4 4-4 4 1.794 4 4M40 74h-4a1 1 0 1 0 0 2h4a1 1 0 1 0 0-2M42 70h-8a1 1 0 1 0 0 2h8a1 1 0 1 0 0-2M38 10a1 1 0 0 0 1-1V1a1 1 0 1 0-2 0v8a1 1 0 0 0 1 1M20.828 15.828a.999.999 0 0 0 .707-1.707l-5.656-5.656a.999.999 0 1 0-1.414 1.414l5.656 5.656a.997.997 0 0 0 .707.293M10 33H2a1 1 0 1 0 0 2h8a1 1 0 1 0 0-2M60.12 8.465l-5.656 5.656a.999.999 0 1 0 1.414 1.414l5.656-5.656a.999.999 0 1 0-1.414-1.414M74 33h-8a1 1 0 1 0 0 2h8a1 1 0 1 0 0-2M43 66H33a1 1 0 1 0 0 2h10a1 1 0 1 0 0-2"/></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/convdev/i2p_step_10.svg b/app/assets/images/illustrations/convdev/i2p_step_10.svg
deleted file mode 100644
index 588ecd81414..00000000000
--- a/app/assets/images/illustrations/convdev/i2p_step_10.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76"><path d="M5 43a1 1 0 1 0 2 0v-4h4a1 1 0 1 0 0-2H7v-4a1 1 0 1 0-2 0v4H1a1 1 0 1 0 0 2h4v4M75 37h-4v-4a1 1 0 1 0-2 0v4h-4a1 1 0 1 0 0 2h4v4a1 1 0 1 0 2 0v-4h4a1 1 0 1 0 0-2M21 38a1 1 0 0 0 .47.848l8 5a.999.999 0 0 0 1.061-1.696L23.887 38l6.644-4.152a1 1 0 1 0-1.061-1.695l-8 5A.998.998 0 0 0 21 38M55 38a1 1 0 0 0-.47-.848l-8-5a.999.999 0 1 0-1.061 1.695L52.113 38l-6.644 4.152a1 1 0 1 0 1.061 1.696l8-5A1 1 0 0 0 55 38M41.803 26.05a1 1 0 0 0-1.256.65l-7 22a1.001 1.001 0 0 0 .953 1.303 1 1 0 0 0 .953-.697l7-22a1.001 1.001 0 0 0-.65-1.256M62 7c3.859 0 7 3.141 7 7v11a1 1 0 1 0 2 0V14c0-4.963-4.04-9-9-9H45.91c-.479-2.833-2.943-5-5.91-5-3.309 0-6 2.691-6 6s2.691 6 6 6c2.967 0 5.431-2.167 5.91-5H62m-22 3c-2.206 0-4-1.794-4-4s1.794-4 4-4 4 1.794 4 4-1.794 4-4 4M6 26a1 1 0 0 0 1-1V14c0-3.859 3.141-7 7-7h11.09l-3.293 3.293a.999.999 0 1 0 1.414 1.414l5-5a.999.999 0 0 0 0-1.414l-5-5a.999.999 0 1 0-1.414 1.414L25.09 5H14c-4.963 0-9 4.04-9 9v11a1 1 0 0 0 1 1M36 64c-2.967 0-5.431 2.167-5.91 5H14c-3.859 0-7-3.141-7-7V51a1 1 0 1 0-2 0v11c0 4.963 4.04 9 9 9h16.09c.478 2.833 2.942 5 5.91 5 3.309 0 6-2.691 6-6s-2.691-6-6-6m0 10c-2.206 0-4-1.794-4-4s1.794-4 4-4 4 1.794 4 4-1.794 4-4 4M70 50a1 1 0 0 0-1 1v11c0 3.859-3.141 7-7 7H50.91l3.293-3.293a.999.999 0 1 0-1.414-1.414l-5 5a.999.999 0 0 0 0 1.414l5 5a.997.997 0 0 0 1.414 0 .999.999 0 0 0 0-1.414L50.91 71H62c4.963 0 9-4.04 9-9V51a1 1 0 0 0-1-1"/></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/convdev/i2p_step_2.svg b/app/assets/images/illustrations/convdev/i2p_step_2.svg
deleted file mode 100644
index 4280024c23c..00000000000
--- a/app/assets/images/illustrations/convdev/i2p_step_2.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76"><path d="M42.26 40.44a.989.989 0 0 0 1.109-.877l2.625-22.444a.997.997 0 0 0-.993-1.117h-14a1 1 0 0 0-.994 1.108l3.454 31.575a6.981 6.981 0 0 0-2.46 5.317c0 3.859 3.141 7 7 7s7-3.141 7-7-3.141-7-7-7c-.94 0-1.835.189-2.655.527l-3.23-29.527h11.761L41.383 39.33a1 1 0 0 0 .877 1.11m.741 13.562c0 2.757-2.243 5-5 5s-5-2.243-5-5 2.243-5 5-5 5 2.243 5 5"/><path d="M73.236 23.749a1 1 0 0 0-1.854.75A35.788 35.788 0 0 1 74 38c0 19.851-16.149 36-36 36S2 57.851 2 38 18.149 2 38 2c7.6 0 14.83 2.332 20.965 6.74A5.955 5.955 0 0 0 58 12c0 1.603.624 3.109 1.758 4.242A5.956 5.956 0 0 0 64 18a5.956 5.956 0 0 0 4.242-1.758C69.376 15.109 70 13.603 70 12s-.624-3.109-1.758-4.242A5.956 5.956 0 0 0 64 6a5.943 5.943 0 0 0-3.668 1.259C53.812 2.512 46.104 0 38 0 17.047 0 0 17.047 0 38s17.047 38 38 38 38-17.047 38-38c0-4.93-.93-9.725-2.764-14.251zM64 8c1.068 0 2.072.416 2.828 1.172S68 10.932 68 12s-.416 2.072-1.172 2.828c-1.512 1.512-4.145 1.512-5.656 0C60.416 14.072 60 13.068 60 12s.416-2.072 1.172-2.828S62.932 8 64 8z"/></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/convdev/i2p_step_3.svg b/app/assets/images/illustrations/convdev/i2p_step_3.svg
deleted file mode 100644
index 7690f91b420..00000000000
--- a/app/assets/images/illustrations/convdev/i2p_step_3.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76"><path d="M12 8c0-3.309-2.691-6-6-6S0 4.691 0 8c0 2.967 2.167 5.431 5 5.91v8.181c-2.833.478-5 2.942-5 5.909s2.167 5.431 5 5.91v8.181c-2.833.478-5 2.942-5 5.909s2.167 5.431 5 5.91v8.181c-2.833.478-5 2.942-5 5.909 0 3.309 2.691 6 6 6s6-2.691 6-6c0-2.967-2.167-5.431-5-5.91v-8.18c2.833-.478 5-2.942 5-5.91s-2.167-5.431-5-5.91v-8.18c2.833-.478 5-2.942 5-5.91s-2.167-5.431-5-5.91v-8.18c2.833-.479 5-2.943 5-5.91M2 8c0-2.206 1.794-4 4-4s4 1.794 4 4-1.794 4-4 4-4-1.794-4-4m8 60c0 2.206-1.794 4-4 4s-4-1.794-4-4 1.794-4 4-4 4 1.794 4 4m0-20c0 2.206-1.794 4-4 4s-4-1.794-4-4 1.794-4 4-4 4 1.794 4 4m0-20c0 2.206-1.794 4-4 4s-4-1.794-4-4 1.794-4 4-4 4 1.794 4 4M21 6h54a1 1 0 1 0 0-2H21a1 1 0 1 0 0 2M21 12h35a1 1 0 1 0 0-2H21a1 1 0 1 0 0 2M75 24H21a1 1 0 1 0 0 2h54a1 1 0 1 0 0-2M21 32h34a1 1 0 1 0 0-2H21a1 1 0 1 0 0 2M75 44H21a1 1 0 1 0 0 2h54a1 1 0 1 0 0-2M21 52h34a1 1 0 1 0 0-2H21a1 1 0 1 0 0 2M75 64H21a1 1 0 1 0 0 2h54a1 1 0 1 0 0-2M55 70H21a1 1 0 1 0 0 2h34a1 1 0 1 0 0-2"/></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/convdev/i2p_step_4.svg b/app/assets/images/illustrations/convdev/i2p_step_4.svg
deleted file mode 100644
index ba21b9e2c3a..00000000000
--- a/app/assets/images/illustrations/convdev/i2p_step_4.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76"><path d="M67.7 10h-6.751C60.442 4.402 55.728 0 50 0c-6.06 0-11 4.935-11 11s4.935 11 11 11c5.728 0 10.442-4.402 10.949-10H67.7c1.269 0 2.3.987 2.3 2.2v57.6c0 1.213-1.031 2.2-2.3 2.2H8.3C7.031 74 6 73.013 6 71.8V14.2C6 12.987 7.031 12 8.3 12h15.15a1 1 0 1 0 0-2H8.3C5.929 10 4 11.884 4 14.2v57.6C4 74.116 5.929 76 8.3 76h59.4c2.371 0 4.3-1.884 4.3-4.2V14.2c0-2.316-1.929-4.2-4.3-4.2M50 20c-4.963 0-9-4.04-9-9s4.04-9 9-9 9 4.04 9 9-4.04 9-9 9"/><path d="M21.293 29.29a.999.999 0 0 0 0 1.414l12.975 12.975-12.975 12.974a.999.999 0 1 0 1.414 1.414l13.682-13.682a.999.999 0 0 0 0-1.414L22.707 29.29a.999.999 0 0 0-1.414 0M54 59a1 1 0 1 0 0-2H42a1 1 0 1 0 0 2h12"/></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/convdev/i2p_step_5.svg b/app/assets/images/illustrations/convdev/i2p_step_5.svg
deleted file mode 100644
index 3c8f8422a97..00000000000
--- a/app/assets/images/illustrations/convdev/i2p_step_5.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76"><path d="M48.949 37C48.442 31.402 43.728 27 38 27s-10.442 4.402-10.949 10h-13.05a1 1 0 1 0 0 2h13.05c.507 5.598 5.221 10 10.949 10s10.442-4.402 10.949-10h12.24a1 1 0 1 0 0-2h-12.24M38 47c-4.963 0-9-4.04-9-9s4.04-9 9-9 9 4.04 9 9-4.04 9-9 9"/><path d="M73.236 23.749a1 1 0 0 0-1.854.75A35.788 35.788 0 0 1 74 38c0 19.851-16.149 36-36 36S2 57.851 2 38 18.149 2 38 2c7.6 0 14.83 2.332 20.965 6.74A5.955 5.955 0 0 0 58 12c0 1.603.624 3.109 1.758 4.242A5.956 5.956 0 0 0 64 18a5.956 5.956 0 0 0 4.242-1.758C69.376 15.109 70 13.603 70 12s-.624-3.109-1.758-4.242A5.956 5.956 0 0 0 64 6a5.943 5.943 0 0 0-3.668 1.259C53.812 2.512 46.104 0 38 0 17.047 0 0 17.047 0 38s17.047 38 38 38 38-17.047 38-38c0-4.93-.93-9.725-2.764-14.251zM64 8c1.068 0 2.072.416 2.828 1.172S68 10.932 68 12s-.416 2.072-1.172 2.828c-1.512 1.512-4.145 1.512-5.656 0C60.416 14.072 60 13.068 60 12s.416-2.072 1.172-2.828S62.932 8 64 8z"/></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/convdev/i2p_step_6.svg b/app/assets/images/illustrations/convdev/i2p_step_6.svg
deleted file mode 100644
index 933860798ad..00000000000
--- a/app/assets/images/illustrations/convdev/i2p_step_6.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76"><path d="M14.267 7.32l-4.896 5.277-1.702-1.533a.999.999 0 1 0-1.338 1.486l2.434 2.192c.064.058.139.091.212.13.035.018.065.048.101.062a.99.99 0 0 0 .752-.016c.044-.019.077-.058.118-.084.076-.047.155-.086.219-.154l5.566-6a1 1 0 0 0-1.466-1.36M31 9h44a1 1 0 1 0 0-2H31a1 1 0 1 0 0 2M31 15h24a1 1 0 1 0 0-2H31a1 1 0 1 0 0 2"/><path d="M11 0C4.93 0 0 4.935 0 11s4.935 11 11 11 11-4.935 11-11S17.065 0 11 0m0 20c-4.963 0-9-4.04-9-9s4.04-9 9-9 9 4.04 9 9-4.04 9-9 9M14.267 34.32l-4.896 5.277-1.702-1.533a1 1 0 1 0-1.338 1.486l2.434 2.192c.064.058.139.091.212.13.035.018.065.048.101.062a.99.99 0 0 0 .752-.016c.044-.019.077-.058.118-.084.076-.047.155-.086.219-.154l5.566-6a1 1 0 0 0-1.466-1.36M75 34H31a1 1 0 1 0 0 2h44a1 1 0 1 0 0-2M31 42h24a1 1 0 1 0 0-2H31a1 1 0 1 0 0 2"/><path d="M11 27C4.93 27 0 31.935 0 38s4.935 11 11 11 11-4.935 11-11-4.935-11-11-11m0 20c-4.963 0-9-4.04-9-9s4.04-9 9-9 9 4.04 9 9-4.04 9-9 9M14.267 61.32l-4.896 5.277-1.702-1.533a1 1 0 1 0-1.338 1.486l2.434 2.192c.064.058.139.091.212.13.035.018.065.048.101.062a.99.99 0 0 0 .752-.016c.044-.019.077-.058.118-.084.076-.047.155-.086.219-.154l5.566-6a1 1 0 0 0-1.466-1.36"/><path d="M11 54C4.93 54 0 58.935 0 65s4.935 11 11 11 11-4.935 11-11-4.935-11-11-11m0 20c-4.963 0-9-4.04-9-9s4.04-9 9-9 9 4.04 9 9-4.04 9-9 9M75 61H31a1 1 0 1 0 0 2h44a1 1 0 1 0 0-2M55 67H31a1 1 0 1 0 0 2h24a1 1 0 1 0 0-2"/></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/convdev/i2p_step_7.svg b/app/assets/images/illustrations/convdev/i2p_step_7.svg
deleted file mode 100644
index d97c8f7c2d4..00000000000
--- a/app/assets/images/illustrations/convdev/i2p_step_7.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76"><path d="M73.236 23.749a1 1 0 1 0-1.854.75A35.788 35.788 0 0 1 74 38c0 19.851-16.149 36-36 36S2 57.851 2 38 18.149 2 38 2c7.6 0 14.83 2.332 20.965 6.74A5.955 5.955 0 0 0 58 12c0 1.603.624 3.109 1.758 4.242A5.956 5.956 0 0 0 64 18a5.956 5.956 0 0 0 4.242-1.758C69.376 15.109 70 13.603 70 12s-.624-3.109-1.758-4.242A5.956 5.956 0 0 0 64 6a5.943 5.943 0 0 0-3.668 1.259C53.812 2.512 46.104 0 38 0 17.047 0 0 17.047 0 38s17.047 38 38 38 38-17.047 38-38c0-4.93-.93-9.725-2.764-14.251zM64 8c1.068 0 2.072.416 2.828 1.172S68 10.932 68 12s-.416 2.072-1.172 2.828c-1.512 1.512-4.145 1.512-5.656 0C60.416 14.072 60 13.068 60 12s.416-2.072 1.172-2.828S62.932 8 64 8z"/><path d="M27.19 32.17a.997.997 0 0 0-1.366-.364L13.17 39.132a1 1 0 0 0 0 1.73l12.654 7.326a1 1 0 0 0 1.002-1.73l-11.159-6.461 11.159-6.461a.998.998 0 0 0 .364-1.366M48.808 47.827a1 1 0 0 0 1.366.364l12.654-7.326a1 1 0 0 0 0-1.73l-12.654-7.326a1 1 0 0 0-1.002 1.73L60.331 40l-11.159 6.461a.998.998 0 0 0-.364 1.366M42.71 23.06L31.398 56.29a1 1 0 0 0 1.892.645l11.312-33.23a1 1 0 0 0-1.892-.645"/></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/convdev/i2p_step_8.svg b/app/assets/images/illustrations/convdev/i2p_step_8.svg
deleted file mode 100644
index 919bbeff319..00000000000
--- a/app/assets/images/illustrations/convdev/i2p_step_8.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76"><path d="M62.44 54.765l-9.912-11.09c.315-3.881.481-7.241.508-10.271-.029-13.871-3.789-23.05-13.413-32.746-.855-.859-2.411-.828-3.294.059-7.594 7.65-11.139 13.934-12.575 22.3a6.94 6.94 0 0 0-4.699 2.039c-1.321 1.321-2.05 3.079-2.05 4.949s.729 3.628 2.051 4.949c1.321 1.322 3.079 2.051 4.949 2.051s3.628-.729 4.949-2.051a6.951 6.951 0 0 0 2.051-4.949 6.955 6.955 0 0 0-2.051-4.949c-.9-.9-2-1.517-3.205-1.824 1.373-7.859 4.764-13.818 11.999-21.11.128-.13.356-.158.456-.059 9.207 9.274 12.805 18.06 12.832 31.33-.026 3.079-.202 6.527-.536 10.54a.997.997 0 0 0 .25.749l10.166 11.379c.062.076.109.23.093.32l-4.547 17.407c-.004.015-.009.036-.079.106a.403.403 0 0 1-.2.106l-3.577.002c-.144-.009-.265-.077-.309-.153l-5.425-10.328a1.002 1.002 0 0 0-.886-.535H30.024c-.371 0-.713.206-.886.535l-5.407 10.303-.069.072a.366.366 0 0 1-.199.105l-3.588.001c-.179-.009-.304-.123-.33-.227l-4.531-17.338a.525.525 0 0 1 .049-.34L25.26 44.682a1 1 0 0 0-1.492-1.332L13.539 54.803c-.448.554-.63 1.312-.474 2.084l4.544 17.396c.253.963 1.146 1.669 2.218 1.719h3.636c.581 0 1.187-.261 1.615-.693.114-.114.286-.286.406-.528l5.144-9.793h14.754l5.16 9.822c.396.697 1.124 1.143 2.01 1.192l3.712-.003a2.396 2.396 0 0 0 1.544-.694c.313-.316.504-.646.598-1.022l4.557-17.451a2.502 2.502 0 0 0-.518-2.066M29.01 30.001c0 1.335-.521 2.591-1.465 3.535s-2.2 1.465-3.535 1.465-2.591-.521-3.535-1.465-1.465-2.2-1.465-3.535.521-2.591 1.465-3.535 2.2-1.465 3.535-1.465 2.591.521 3.535 1.465 1.465 2.2 1.465 3.535"/></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/convdev/i2p_step_9.svg b/app/assets/images/illustrations/convdev/i2p_step_9.svg
deleted file mode 100644
index 2d1b10d430d..00000000000
--- a/app/assets/images/illustrations/convdev/i2p_step_9.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76"><path d="M68 67c-1.725 0-3.36.541-4.723 1.545A12.998 12.998 0 0 0 52 62c-2.734 0-5.359.853-7.555 2.43L42.159 49h1.228l3.829 7.645c.339.598.962.979 1.724 1.022l2.812-.003a2.07 2.07 0 0 0 1.316-.595c.264-.266.433-.559.514-.882l3.433-13.145a2.138 2.138 0 0 0-.449-1.763l-7.385-8.268c.231-2.875.354-5.376.374-7.641C49.532 14.863 46.684 7.908 39.393.564c-.737-.742-2.072-.715-2.829.044-5.617 5.659-8.309 10.336-9.446 16.463a5.95 5.95 0 0 0-3.36 1.686C22.624 19.891 22 21.397 22 23s.624 3.109 1.758 4.242C24.891 28.376 26.397 29 28 29s3.109-.624 4.242-1.758C33.376 26.109 34 24.603 34 23s-.624-3.109-1.758-4.242a5.952 5.952 0 0 0-3.098-1.648c1.095-5.538 3.637-9.855 8.83-15.14 6.874 6.924 9.561 13.485 9.581 23.392-.021 2.316-.151 4.903-.402 7.91a.999.999 0 0 0 .25.749l7.663 8.572-3.391 13.07-2.695.036-4.081-8.15a1.001 1.001 0 0 0-.895-.553h-12.01c-.379 0-.725.214-.895.553l-4.04 8.114-2.707.015-3.427-13.07 7.671-8.588a1 1 0 0 0-1.492-1.332l-7.7 8.623c-.383.47-.54 1.116-.406 1.787l3.419 13.08c.216.829.98 1.438 1.907 1.48h2.735c.508 0 1.016-.218 1.391-.595.091-.09.242-.241.358-.475l3.804-7.597h1.228l-2.286 15.43a12.914 12.914 0 0 0-7.555-2.43c-4.685 0-8.979 2.53-11.277 6.545a7.943 7.943 0 0 0-4.723-1.545c-4.411 0-8 3.589-8 8a1 1 0 0 0 1 1h74a1 1 0 0 0 1-1c0-4.411-3.589-8-8-8m-36-44a3.973 3.973 0 0 1-1.172 2.828c-1.512 1.512-4.145 1.512-5.656 0-.756-.756-1.172-1.76-1.172-2.828s.416-2.072 1.172-2.828 1.76-1.172 2.828-1.172 2.072.416 2.828 1.172 1.172 1.76 1.172 2.828m-29.917 51a6.01 6.01 0 0 1 5.917-5c1.638 0 3.17.652 4.313 1.836a.998.998 0 0 0 1.634-.289 11.011 11.011 0 0 1 10.05-6.547c2.836 0 5.532 1.085 7.593 3.055a1.001 1.001 0 0 0 1.681-.576l2.588-17.479h4.275l2.589 17.479a.999.999 0 1 0 1.681.576 10.945 10.945 0 0 1 7.593-3.055c4.343 0 8.288 2.57 10.05 6.547a.998.998 0 0 0 1.634.289 5.948 5.948 0 0 1 4.313-1.836 6.01 6.01 0 0 1 5.917 5H2.076"/></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/epics.svg b/app/assets/images/illustrations/epics.svg
deleted file mode 100644
index 1a37e6bba5f..00000000000
--- a/app/assets/images/illustrations/epics.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="430" height="300" viewBox="0 0 430 300"><g fill="none" fill-rule="evenodd"><g transform="translate(75 53)"><rect width="284" height="208" y="5" fill="#F9F9F9" rx="10"/><rect width="284" height="208" fill="#FFF" rx="10"/><path fill="#EEE" fill-rule="nonzero" d="M10 4a6 6 0 0 0-6 6v188a6 6 0 0 0 6 6h264a6 6 0 0 0 6-6V10a6 6 0 0 0-6-6H10zm0-4h264c5.523 0 10 4.477 10 10v188c0 5.523-4.477 10-10 10H10c-5.523 0-10-4.477-10-10V10C0 4.477 4.477 0 10 0z"/><path fill="#EEE" fill-rule="nonzero" d="M25.168 153.995c3.837-.215 7.173.028 10.119.691a3 3 0 1 0 1.318-5.853c-3.509-.79-7.4-1.074-11.773-.828a3 3 0 1 0 .336 5.99zm19.043 4.66c2.401 1.704 4.388 3.61 7.569 7.083a3 3 0 0 0 4.424-4.054c-3.448-3.763-5.686-5.911-8.522-7.923a3 3 0 1 0-3.471 4.894zm15.575 15.173c3.181 2.675 6.52 4.665 10.397 6.039a3 3 0 0 0 2.004-5.655c-3.162-1.121-5.884-2.743-8.54-4.976a3 3 0 1 0-3.861 4.592zm22.133 8.148c1.02.037 2.067.045 3.143.023a72.664 72.664 0 0 0 8.346-.638 3 3 0 1 0-.812-5.945c-2.442.334-4.996.53-7.658.585a48.55 48.55 0 0 1-2.796-.021 3 3 0 0 0-.223 5.996zm22.778-3.286c3.9-1.37 7.427-3.15 10.54-5.305a3 3 0 0 0-3.415-4.933c-2.665 1.845-5.712 3.382-9.114 4.578a3 3 0 0 0 1.989 5.66zm19.156-13.62a33.752 33.752 0 0 0 5.276-10.817 3 3 0 1 0-5.773-1.633 27.753 27.753 0 0 1-4.341 8.9 3 3 0 1 0 4.838 3.55zm6.577-22.657c-.187-3.817-.926-7.71-2.204-11.596a3 3 0 0 0-5.7 1.874c1.113 3.384 1.75 6.745 1.91 10.016a3 3 0 1 0 5.994-.294zm-7.097-22.26c-1.897-3.2-4.152-6.325-6.748-9.344a3 3 0 0 0-4.55 3.913c2.372 2.756 4.421 5.597 6.136 8.49a3 3 0 0 0 5.162-3.06zm-11.546-17.793c-.938-3.025-1.402-6.42-1.365-9.976a3 3 0 0 0-6-.063c-.043 4.163.506 8.177 1.634 11.816a3 3 0 1 0 5.731-1.777zm.053-20.107c.905-3.341 2.22-6.538 3.904-9.448a3 3 0 0 0-5.194-3.004c-1.948 3.368-3.463 7.048-4.501 10.884a3 3 0 1 0 5.791 1.568zm10.134-17.305c2.475-2.28 5.265-4.09 8.335-5.374a3 3 0 1 0-2.314-5.536c-3.725 1.558-7.105 3.75-10.086 6.497a3 3 0 1 0 4.065 4.413zm18.177-7.586c3.202-.18 6.599.092 10.18.843a3 3 0 0 0 1.23-5.872c-4.086-.857-8.009-1.172-11.747-.962a3 3 0 1 0 .337 5.99zm20.047 3.95c3.068 1.268 6.232 2.842 9.487 4.728a3 3 0 0 0 3.009-5.191c-3.48-2.017-6.883-3.71-10.204-5.083a3 3 0 1 0-2.292 5.545zm19.578 9.955c3.711 1.586 7.376 2.77 10.997 3.565a3 3 0 0 0 1.286-5.86c-3.248-.713-6.555-1.782-9.925-3.222a3 3 0 1 0-2.358 5.517zm22.591 4.789c3.94-.04 7.808-.553 11.61-1.513a3 3 0 1 0-1.468-5.817 43.358 43.358 0 0 1-10.203 1.33 3 3 0 0 0 .061 6zm22.52-5.558c3.335-1.637 6.607-3.613 9.845-5.916a3 3 0 1 0-3.477-4.89c-2.984 2.122-5.98 3.931-9.011 5.42a3 3 0 1 0 2.643 5.386zm18.678-13.054a3 3 0 0 1-4.02-4.454 130.547 130.547 0 0 0 5.31-5.088 3 3 0 1 1 4.265 4.22 136.507 136.507 0 0 1-5.555 5.322zm-48.722 25.641a3 3 0 1 1 4.314-4.17c3.056 3.16 5.075 6.744 6.172 10.754a3 3 0 0 1-5.787 1.584c-.834-3.047-2.35-5.739-4.699-8.168zm5.347 18.049a3 3 0 1 1 5.978.52c-.282 3.232-.805 6.273-1.832 11.206a3 3 0 0 1-5.874-1.222c.981-4.717 1.473-7.572 1.728-10.504zm-3.777 21.555a3 3 0 0 1 5.953.747c-.5 3.988-.397 7.09.399 9.67a3 3 0 1 1-5.733 1.769c-1.087-3.52-1.217-7.426-.62-12.186zm7.393 22.444a3 3 0 0 1 4.461-4.013c2.703 3.005 5.224 5.296 7.594 6.947a3 3 0 0 1-3.429 4.924c-2.775-1.932-5.632-4.53-8.626-7.858zm20.352 12.28a3 3 0 1 1 .334-5.99c2.77.154 5.453-.554 9.224-2.254a3 3 0 0 1 2.466 5.47c-4.57 2.06-8.103 2.993-12.024 2.775zm21.784-7.058a3 3 0 0 1-1.815-5.719c4.227-1.342 8.24-1.61 12.496-.572a3 3 0 0 1-1.421 5.83c-3.116-.76-6.025-.566-9.26.46zM106.53 56.038a3 3 0 1 1-3.45 4.909c-1.074-.755-6.723-6.044-8.083-7.204a68.019 68.019 0 0 0-.332-.281 3 3 0 1 1 3.865-4.59l.362.306c1.643 1.402 6.971 6.391 7.638 6.86zM88.536 42.422a3 3 0 0 1-2.285 5.548c-3.14-1.293-5.78-1.34-8.105-.05a3 3 0 0 1-2.91-5.247c4.087-2.266 8.597-2.187 13.3-.25zM66.698 48.73a3 3 0 0 1 2.029 5.647c-4.432 1.592-8.786.835-13.166-1.88a3 3 0 1 1 3.16-5.1c2.93 1.816 5.425 2.25 7.977 1.333zm-15.636-8.038a3 3 0 0 1-4.352 4.13c-.911-.96-1.85-1.98-3.061-3.32-.295-.325-2.437-2.703-3.07-3.4-.47-.518-.9-.988-1.313-1.436a3 3 0 0 1 4.41-4.068c.425.46.866.942 1.346 1.47.642.709 2.79 3.092 3.076 3.41a180.865 180.865 0 0 0 2.964 3.214z"/><path fill="#E1DBF1" d="M254.66 72.196l2-3.464a2 2 0 1 0-3.464-2l-2 3.464-3.464-2a2 2 0 0 0-2 3.464l3.464 2-2 3.464a2 2 0 0 0 3.464 2l2-3.464 3.464 2a2 2 0 1 0 2-3.464l-3.464-2zm-151.904 78.732l2.829-2.828a2 2 0 0 0-2.829-2.829l-2.828 2.829-2.828-2.829a2 2 0 0 0-2.829 2.829l2.829 2.828-2.829 2.829a2 2 0 1 0 2.829 2.828l2.828-2.828 2.828 2.828a2 2 0 1 0 2.829-2.828l-2.829-2.829z"/><path fill="#6B4FBB" d="M210.66 173.66l3.464-2a2 2 0 1 0-2-3.464l-3.464 2-2-3.464a2 2 0 0 0-3.464 2l2 3.464-3.464 2a2 2 0 1 0 2 3.464l3.464-2 2 3.464a2 2 0 1 0 3.464-2l-2-3.464z"/><path fill="#FDC4A8" fill-rule="nonzero" d="M27 181a8 8 0 1 1 0-16 8 8 0 0 1 0 16zm0-4a4 4 0 1 0 0-8 4 4 0 0 0 0 8z"/><path fill="#C3B8E3" fill-rule="nonzero" d="M138 85a7 7 0 1 1 0-14 7 7 0 0 1 0 14zm0-4a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/><path fill="#6B4FBB" fill-rule="nonzero" d="M200 57a7 7 0 1 1 0-14 7 7 0 0 1 0 14zm0-4a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/><path fill="#FC6D26" fill-rule="nonzero" d="M222.647 121.647v5h5v-5h-5zm0-4h5a4 4 0 0 1 4 4v5a4 4 0 0 1-4 4h-5a4 4 0 0 1-4-4v-5a4 4 0 0 1 4-4z"/><path fill="#FEE1D3" fill-rule="nonzero" d="M103.647 28.647v5h5v-5h-5zm0-4h5a4 4 0 0 1 4 4v5a4 4 0 0 1-4 4h-5a4 4 0 0 1-4-4v-5a4 4 0 0 1 4-4z"/><path fill="#FC6D26" fill-rule="nonzero" d="M85 103.488L81.841 108h6.318L85 103.488zm6.436 2.218A4 4 0 0 1 88.159 112H81.84a4 4 0 0 1-3.277-6.294l3.16-4.512a4 4 0 0 1 6.553 0l3.159 4.512z"/></g><path fill="#F9F9F9" d="M334.376 99.43A48.805 48.805 0 0 0 366 111c27.062 0 49-21.938 49-49s-21.938-49-49-49-49 21.938-49 49c0 9.454 2.677 18.283 7.315 25.77l-3.05 11.306a2.5 2.5 0 0 0 3.064 3.065l10.047-2.71z"/><path fill="#FFF" d="M339.376 94.43A48.805 48.805 0 0 0 371 106c27.062 0 49-21.938 49-49S398.062 8 371 8s-49 21.938-49 49c0 9.454 2.677 18.283 7.315 25.77l-3.05 11.306a2.5 2.5 0 0 0 3.064 3.065l10.047-2.71z"/><path fill="#EEE" fill-rule="nonzero" d="M329.85 99.072a4.5 4.5 0 0 1-5.516-5.517l2.827-10.48C322.501 75.258 320 66.31 320 57c0-28.167 22.833-51 51-51s51 22.833 51 51-22.833 51-51 51c-11.859 0-23.096-4.064-32.102-11.37l-9.048 2.442zm10.817-6.169C349.091 100.027 359.737 104 371 104c25.957 0 47-21.043 47-47s-21.043-47-47-47-47 21.043-47 47c0 8.859 2.453 17.351 7.016 24.716l.456.737-3.277 12.144c.072.527.347.685.613.613l11.059-2.984.8.677z"/><g transform="translate(354 34)"><path fill="#E1DBF1" fill-rule="nonzero" d="M13 4a1 1 0 0 0-1 1v1a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1h-8zm0-4h8a5 5 0 0 1 5 5v1a5 5 0 0 1-5 5h-8a5 5 0 0 1-5-5V5a5 5 0 0 1 5-5z"/><path fill="#6B4FBB" fill-rule="nonzero" d="M5 11a1 1 0 0 0 0 2h24a1 1 0 0 0 0-2H5zm0-4h24a5 5 0 0 1 0 10H5A5 5 0 0 1 5 7z"/><rect width="12" height="4" x="11" y="31" fill="#C3B8E3" rx="2"/><rect width="12" height="4" x="11" y="19" fill="#C3B8E3" rx="2"/><rect width="12" height="4" x="11" y="37" fill="#E1DBF1" rx="2"/><rect width="12" height="4" x="11" y="43" fill="#C3B8E3" rx="2"/><rect width="12" height="4" x="11" y="25" fill="#E1DBF1" rx="2"/></g><path fill="#F9F9F9" d="M344.238 225.072A38.83 38.83 0 0 1 368 217c21.54 0 39 17.46 39 39s-17.46 39-39 39-39-17.46-39-39a38.84 38.84 0 0 1 4.001-17.227l-3.737-13.85a2.5 2.5 0 0 1 3.065-3.064l11.91 3.213z"/><path fill="#FFF" d="M348.238 221.072A38.83 38.83 0 0 1 372 213c21.54 0 39 17.46 39 39s-17.46 39-39 39-39-17.46-39-39a38.84 38.84 0 0 1 4.001-17.227l-3.737-13.85a2.5 2.5 0 0 1 3.065-3.064l11.91 3.213z"/><path fill="#EEE" fill-rule="nonzero" d="M336.85 215.928a4.5 4.5 0 0 0-5.516 5.517l3.543 13.13A40.848 40.848 0 0 0 331 252c0 22.644 18.356 41 41 41s41-18.356 41-41-18.356-41-41-41a40.82 40.82 0 0 0-24.182 7.887l-10.968-2.96zm12.608 6.73A36.824 36.824 0 0 1 372 215c20.435 0 37 16.565 37 37s-16.565 37-37 37-37-16.565-37-37c0-5.747 1.31-11.304 3.795-16.343l.334-.677-3.934-14.577a.5.5 0 0 1 .613-.613l12.865 3.471.785-.604z"/><path fill="#FEE1D3" fill-rule="nonzero" d="M356.097 255.962a7 7 0 0 0 8.81 10.88l1.093-.885v1.454a7 7 0 1 0 14 0v-1.454l1.092.885a7 7 0 1 0 8.81-10.88l-1.185-.96 1.455-.337a7 7 0 1 0-3.15-13.64l-1.4.323.623-1.278a7 7 0 0 0-12.583-6.137l-.662 1.356-.662-1.356a7 7 0 0 0-12.583 6.137l.623 1.278-1.4-.324a7 7 0 1 0-3.15 13.641l1.455.336-1.186.96zm5.464-.913a11.914 11.914 0 0 1-.444-1.95l-.19-1.362-4.2-.97a3 3 0 0 1 1.35-5.845l4.178.964.768-1.145c.373-.557.793-1.082 1.254-1.57l.95-1.006-1.877-3.849a3 3 0 0 1 5.393-2.63l1.892 3.879 1.363-.113a12.188 12.188 0 0 1 2.004 0l1.363.113 1.892-3.879a3 3 0 0 1 5.393 2.63l-1.877 3.849.95 1.006c.461.488.88 1.013 1.254 1.57l.768 1.145 4.178-.964a3 3 0 1 1 1.35 5.846l-4.2.97-.19 1.36a11.914 11.914 0 0 1-.444 1.95l-.413 1.302 3.36 2.72a3 3 0 1 1-3.776 4.663l-3.32-2.688-1.196.706a11.94 11.94 0 0 1-1.808.873l-1.286.492v4.295a3 3 0 1 1-6 0v-4.295l-1.286-.492a11.94 11.94 0 0 1-1.808-.873l-1.196-.706-3.32 2.688a3 3 0 1 1-3.776-4.663l3.36-2.72-.413-1.301z"/><path fill="#FC6D26" fill-rule="nonzero" d="M373 245.411a6 6 0 1 0 0 12 6 6 0 0 0 0-12zm0 4a2 2 0 1 1 0 4 2 2 0 0 1 0-4z"/><g><path fill="#F9F9F9" d="M94.624 162.43A48.805 48.805 0 0 1 63 174c-27.062 0-49-21.938-49-49s21.938-49 49-49 49 21.938 49 49c0 9.454-2.677 18.283-7.315 25.77l3.05 11.306a2.5 2.5 0 0 1-3.064 3.065l-10.047-2.71z"/><path fill="#FFF" stroke="#EEE" stroke-width="4" d="M89.624 157.43A48.805 48.805 0 0 1 58 169c-27.062 0-49-21.938-49-49s21.938-49 49-49 49 21.938 49 49c0 9.454-2.677 18.283-7.315 25.77l3.05 11.306a2.5 2.5 0 0 1-3.064 3.065l-10.047-2.71z"/><path fill="#EEE" fill-rule="nonzero" d="M99.15 162.072a4.5 4.5 0 0 0 5.516-5.517l-2.827-10.48C106.499 138.258 109 129.31 109 120c0-28.167-22.833-51-51-51S7 91.833 7 120s22.833 51 51 51c11.859 0 23.096-4.064 32.102-11.37l9.048 2.442zm-10.817-6.169C79.909 163.027 69.263 167 58 167c-25.957 0-47-21.043-47-47s21.043-47 47-47 47 21.043 47 47c0 8.859-2.453 17.351-7.016 24.716l-.456.737 3.277 12.144c-.072.527-.347.685-.613.613l-11.059-2.984-.8.677z"/><g fill-rule="nonzero"><path fill="#FEE1D3" d="M55.47 94.47l-16.148 6.688a4 4 0 0 0-2.164 2.164l-6.689 16.147a4 4 0 0 0 0 3.062l6.689 16.147a4 4 0 0 0 2.164 2.164l16.147 6.689a4 4 0 0 0 3.062 0l16.147-6.689a4 4 0 0 0 2.164-2.164l6.689-16.147a4 4 0 0 0 0-3.062l-6.689-16.147a4 4 0 0 0-2.164-2.164L58.53 94.469a4 4 0 0 0-3.062 0zM57 98.164l16.147 6.688L79.835 121l-6.688 16.147L57 143.835l-16.147-6.688L34.165 121l6.688-16.147L57 98.165zM57 107a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm12 4a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm4 12a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm-4 11a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm-12 6a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm-12-6a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm-4-11a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm4-11a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm12 20c6.075 0 11-4.925 11-11s-4.925-11-11-11-11 4.925-11 11 4.925 11 11 11zm0-4a7 7 0 1 1 0-14 7 7 0 0 1 0 14z"/><path fill="#FC6D26" d="M57 126.5a5.5 5.5 0 1 0 0-11 5.5 5.5 0 0 0 0 11zm0-3a2.5 2.5 0 1 1 0-5 2.5 2.5 0 0 1 0 5z"/></g></g></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/gitlab_logo.svg b/app/assets/images/illustrations/gitlab_logo.svg
deleted file mode 100644
index 8dbd75a340e..00000000000
--- a/app/assets/images/illustrations/gitlab_logo.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="492.509" height="453.68" viewBox="0 0 492.50943 453.67966"><g fill="none" fill-rule="evenodd"><path d="M491.589 259.398l-27.559-84.814L409.413 6.486c-2.81-8.648-15.045-8.648-17.856 0l-54.619 168.098H155.572L100.952 6.486c-2.81-8.648-15.046-8.648-17.856 0L28.478 174.584.921 259.398a18.775 18.775 0 0 0 6.82 20.992l238.513 173.29L484.77 280.39a18.777 18.777 0 0 0 6.82-20.992" fill="#fc6d26"/><path d="M246.255 453.68l90.684-279.096H155.57z" fill="#e24329"/><path d="M246.255 453.68L155.57 174.583H28.479z" fill="#fc6d26"/><path d="M28.479 174.584L.92 259.4a18.773 18.773 0 0 0 6.821 20.99l238.514 173.29z" fill="#fca326"/><path d="M28.479 174.584H155.57L100.952 6.487c-2.81-8.65-15.047-8.65-17.856 0z" fill="#e24329"/><path d="M246.255 453.68l90.684-279.096H464.03z" fill="#fc6d26"/><path d="M464.03 174.584l27.56 84.815a18.773 18.773 0 0 1-6.822 20.99L246.255 453.68z" fill="#fca326"/><path d="M464.03 174.584H336.94L391.557 6.487c2.811-8.65 15.047-8.65 17.856 0z" fill="#e24329"/></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/image_comment_light_cursor.svg b/app/assets/images/illustrations/image_comment_light_cursor.svg
deleted file mode 100644
index ac712ea0c96..00000000000
--- a/app/assets/images/illustrations/image_comment_light_cursor.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 38 38"><g fill="none" fill-rule="evenodd"><circle cx="19" cy="19" r="18" fill="#FFF"/><path fill="#1F78D1" fill-rule="nonzero" d="M19 38C8.507 38 0 29.493 0 19S8.507 0 19 0s19 8.507 19 19-8.507 19-19 19zm0-2c9.389 0 17-7.611 17-17S28.389 2 19 2 2 9.611 2 19s7.611 17 17 17zm-6.293-8.293c-.63.63-1.707.184-1.707-.707V15a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3h-7.586l-3.707 3.707zM13 24.586l2.293-2.293A1 1 0 0 1 16 22h8a1 1 0 0 0 1-1v-6a1 1 0 0 0-1-1H14a1 1 0 0 0-1 1v9.586z"/></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/image_comment_light_cursor@2x.svg b/app/assets/images/illustrations/image_comment_light_cursor@2x.svg
deleted file mode 100644
index 02943acd9d7..00000000000
--- a/app/assets/images/illustrations/image_comment_light_cursor@2x.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 38 38"><g fill="none" fill-rule="evenodd"><circle cx="19" cy="19" r="18" fill="#FFF"/><path fill="#1F78D1" fill-rule="nonzero" d="M19 38C8.507 38 0 29.493 0 19S8.507 0 19 0s19 8.507 19 19-8.507 19-19 19zm0-2c9.389 0 17-7.611 17-17S28.389 2 19 2 2 9.611 2 19s7.611 17 17 17zm-6.293-8.293c-.63.63-1.707.184-1.707-.707V15a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3h-7.586l-3.707 3.707zM13 24.586l2.293-2.293A1 1 0 0 1 16 22h8a1 1 0 0 0 1-1v-6a1 1 0 0 0-1-1H14a1 1 0 0 0-1 1v9.586z"/></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/issues.svg b/app/assets/images/illustrations/issues.svg
deleted file mode 100644
index c8e0504732d..00000000000
--- a/app/assets/images/illustrations/issues.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="790 253 425 254" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><rect id="a" width="25" height="8.942" x="25" y="88.423" rx="2"/><mask id="h" width="25" height="8.942" x="0" y="0" fill="#fff"><use xlink:href="#a"/></mask><path id="b" d="M16 29.801h43v61.603H16z"/><mask id="i" width="43" height="61.603" x="0" y="0" fill="#fff"><use xlink:href="#b"/></mask><path id="c" d="M57 60.603l13.187 9.358c.449.32.876 1.015.955 1.568l3.575 24.863c.157 1.086-.253 1.257-.912.384L66 86.436l-9-6.955"/><mask id="j" width="17.75" height="36.731" x="0" y="0" fill="#fff"><use xlink:href="#c"/></mask><path id="d" d="M.25 60.603l13.186 9.358c.45.32.876 1.015.956 1.568l3.575 24.863c.156 1.086-.253 1.257-.912.384l-7.806-10.34-9-6.955"/><mask id="k" width="17.75" height="36.731" x="0" y="0" fill="#fff"><use xlink:href="#d"/></mask><path id="e" d="M16 29.801L35.786 1.456c.947-1.357 2.48-1.36 3.428 0L59 29.8"/><mask id="l" width="43" height="29.364" x="0" y="0" fill="#fff"><use xlink:href="#e"/></mask><rect id="f" width="26.265" height="35.509" x="6.367" rx="13.133"/><mask id="m" width="26.265" height="35.509" x="0" y="0" fill="#fff"><use xlink:href="#f"/></mask><rect id="g" width="16.837" height="22.386" x="4.082" rx="8.418"/><mask id="n" width="16.837" height="22.386" x="0" y="0" fill="#fff"><use xlink:href="#g"/></mask></defs><g fill="none" fill-rule="evenodd" transform="translate(792 255)"><path d="M225.437 59.587c-.059.59-.132 1.27-.22 2.03a178.367 178.367 0 0 1-.965 7.07 1.5 1.5 0 1 0 2.963.465c.4-2.553.726-4.975.982-7.19a137.446 137.446 0 0 0 .297-2.832 1.5 1.5 0 1 0-2.989-.26c-.01.123-.033.365-.068.717zm-5.563 28.354a1.5 1.5 0 0 0 2.853.929c.975-2.997 1.849-6.283 2.628-9.797a1.5 1.5 0 1 0-2.928-.65c-.76 3.426-1.61 6.62-2.553 9.518zm-9.947 15.225a1.5 1.5 0 1 0 1.001 2.828c2.98-1.055 5.542-3.68 7.78-7.627a1.5 1.5 0 0 0-2.61-1.48c-1.915 3.378-3.995 5.508-6.171 6.279zm-19.488 4.417a1.5 1.5 0 1 0 1.164 2.765c3.12-1.314 6.272-2.324 9.258-2.981a1.5 1.5 0 1 0-.645-2.93c-3.167.697-6.491 1.763-9.777 3.146zm-17.208 11.043a1.5 1.5 0 0 0 2.066 2.175c2.282-2.169 4.866-4.162 7.676-5.946a1.5 1.5 0 0 0-1.608-2.533c-2.97 1.885-5.707 3.998-8.134 6.304zm-10.777 17.623a1.5 1.5 0 1 0 2.91.732c.768-3.054 2.041-5.977 3.78-8.748a1.5 1.5 0 0 0-2.54-1.595c-1.903 3.032-3.302 6.244-4.15 9.611zm-.265 20.444a1.5 1.5 0 1 0 2.977-.375c-.367-2.91-.58-6.137-.645-9.817a1.5 1.5 0 0 0-3 .053c.067 3.783.287 7.116.668 10.139zm6.219 19.472a1.5 1.5 0 0 0 2.652-1.403c-1.674-3.162-2.903-5.995-3.848-8.943a1.5 1.5 0 1 0-2.857.916c1.003 3.127 2.302 6.12 4.053 9.43zm7.566 12.77a595.837 595.837 0 0 1 2.73 4.475 1.5 1.5 0 0 0 2.569-1.551 626.463 626.463 0 0 0-2.744-4.495c.08.13-1.954-3.173-2.486-4.04a1.5 1.5 0 1 0-2.558 1.567c.534.87 2.571 4.178 2.489 4.045zm8.856 22.447a1.5 1.5 0 0 0 3-.039 32.214 32.214 0 0 0-1.837-10.326 1.5 1.5 0 0 0-2.828.999 29.212 29.212 0 0 1 1.665 9.366zm-5.483 18.028a1.5 1.5 0 0 0 2.497 1.662 36.203 36.203 0 0 0 4.488-9.416 1.5 1.5 0 0 0-2.868-.882 33.197 33.197 0 0 1-4.117 8.636z" fill="#FDE5D8"/><g transform="rotate(60 126.799 371.622)"><path stroke="#FDE5D8" stroke-width="3" d="M19 154l10-52.66m16 0L55 154" stroke-linecap="round"/><rect width="3" height="38.75" x="35" y="99.353" fill="#FDE5D8" rx="1.5"/><use fill="#FFF" stroke="#FDE5D8" stroke-width="6" mask="url(#h)" xlink:href="#a"/><use stroke="#FDE5D8" stroke-width="6" mask="url(#i)" xlink:href="#b"/><use stroke="#FDE5D8" stroke-width="6" mask="url(#j)" xlink:href="#c"/><use stroke="#FDE5D8" stroke-width="6" mask="url(#k)" transform="matrix(-1 0 0 1 18.25 0)" xlink:href="#d"/><use stroke="#FDE5D8" stroke-width="6" mask="url(#l)" xlink:href="#e"/><ellipse cx="28.5" cy="82.958" fill="#FC8A51" rx="1.5" ry="1.49"/><ellipse cx="34.5" cy="82.958" fill="#FC8A51" rx="1.5" ry="1.49"/><ellipse cx="40.5" cy="82.958" fill="#FC8A51" rx="1.5" ry="1.49"/><ellipse cx="46.5" cy="82.958" fill="#FC8A51" rx="1.5" ry="1.49"/><ellipse cx="37.5" cy="55.138" stroke="#FDE5D8" stroke-width="3" rx="10.5" ry="10.433"/><ellipse cx="37.5" cy="55.138" stroke="#FDE5D8" stroke-width="3" rx="5.5" ry="5.465"/></g><path fill="#EEE" d="M96.043 37.21c-.152 1.688.081 3.816.997 6.147a1.016 1.016 0 0 0 1.89-.74c-.791-2.014-.99-3.832-.865-5.226.01-.114.02-.186.024-.211a1.015 1.015 0 1 0-2.002-.333 5.06 5.06 0 0 0-.044.363zm11.487 15.683c.491.24 1.098.063 1.355-.394.257-.456.068-1.02-.424-1.26-1.866-.907-3.458-1.914-4.794-3.007a1.058 1.058 0 0 0-1.417.085.888.888 0 0 0 .091 1.317c1.458 1.192 3.183 2.283 5.19 3.26zm13.131 6.06a1.032 1.032 0 0 0 1.293-.7 1.06 1.06 0 0 0-.686-1.32 376.355 376.355 0 0 1-5.915-1.882 1.031 1.031 0 0 0-1.303.681 1.06 1.06 0 0 0 .668 1.33c1.729.569 2.905.94 5.943 1.891zm11.934 3.928c.45.246 1.022.098 1.28-.33a.872.872 0 0 0-.346-1.221c-1.494-.819-3.192-1.545-5.267-2.275-.486-.17-1.025.067-1.204.53-.18.464.07.978.555 1.149 1.984.697 3.59 1.384 4.982 2.147zm9.382 10.502c.205.494.81.742 1.349.554.54-.188.81-.74.605-1.234-.85-2.048-1.853-3.796-3.037-5.305-.337-.429-.99-.527-1.459-.218-.469.308-.575.906-.238 1.335 1.074 1.368 1.992 2.97 2.78 4.868zm2.632 13.642c.018.553.568.99 1.228.975.66-.016 1.18-.477 1.163-1.03-.073-2.204-.27-4.206-.622-6.12-.101-.547-.712-.923-1.365-.838-.652.084-1.1.597-.999 1.144.336 1.825.525 3.745.595 5.869z"/><path fill="#E5E5E5" d="M144.142 95.73a244.285 244.285 0 0 0-.142 5.254c-.007.553.396 1.008.902 1.016.506.008.923-.433.93-.985.02-1.467.056-2.681.142-5.211l.026-.767c.018-.552-.377-1.016-.882-1.036-.506-.02-.931.41-.95.963l-.026.766zm.797 19.471c.12.545.673.892 1.236.777.562-.116.921-.651.802-1.196-.417-1.9-.71-3.84-.897-5.864-.052-.554-.558-.964-1.131-.914-.573.05-.996.54-.945 1.094.195 2.102.5 4.121.935 6.103zm5.056 12.324c.296.454.953.61 1.467.348.514-.261.69-.841.395-1.295a40.725 40.725 0 0 1-2.79-4.991c-.227-.485-.855-.715-1.403-.515-.548.2-.81.755-.582 1.239a42.56 42.56 0 0 0 2.913 5.214zm4.814 7.701a33.475 33.475 0 0 0 3.543 3.531 1.021 1.021 0 0 0 1.393-.066.908.908 0 0 0-.07-1.326 31.562 31.562 0 0 1-3.34-3.328 59.092 59.092 0 0 1-.576-.682 1.02 1.02 0 0 0-1.386-.152.909.909 0 0 0-.16 1.32c.196.234.394.469.596.703zm15.825 11.677c.48.242 1.052.017 1.276-.501.224-.52.016-1.136-.464-1.378a49.756 49.756 0 0 1-4.986-2.872c-.453-.298-1.044-.144-1.32.345-.276.488-.133 1.126.32 1.424a51.568 51.568 0 0 0 5.174 2.982z"/><path fill="#EEE" d="M184.733 151.97c.553.141 1.108-.226 1.239-.82.131-.595-.21-1.192-.763-1.333a72.17 72.17 0 0 1-5.863-1.763c-.54-.188-1.12.13-1.296.712-.175.581.121 1.205.662 1.393a74.018 74.018 0 0 0 6.021 1.81zm13.2 2.028c.554.04 1.03-.445 1.065-1.083.035-.639-.386-1.188-.939-1.228a71.842 71.842 0 0 1-5.92-.676c-.55-.086-1.055.358-1.13.991-.074.634.31 1.217.86 1.303a73.28 73.28 0 0 0 6.065.693zm14.188-1.392c.55-.055.94-.457.871-.9-.068-.441-.569-.755-1.118-.7-1.917.192-3.893.32-5.91.382-.554.017-.985.392-.963.837.021.445.487.792 1.04.774a88.939 88.939 0 0 0 6.08-.393zm14.245-2.657c.53-.22.776-.816.55-1.332a1.053 1.053 0 0 0-1.367-.535 44.421 44.421 0 0 1-5.777 1.923 1.012 1.012 0 0 0-.736 1.243c.15.542.721.863 1.277.717a46.532 46.532 0 0 0 6.054-2.016zm11.483-9.532c.292-.435.148-1.006-.32-1.277-.47-.27-1.087-.138-1.379.297-.957 1.424-2.225 2.734-3.784 3.92a.88.88 0 0 0-.138 1.304c.35.396.98.453 1.408.128 1.723-1.31 3.136-2.771 4.213-4.372zm7.824-9.73a.965.965 0 0 0 .09-1.358.958.958 0 0 0-1.355-.09 44.935 44.935 0 0 0-4.17 4.163.965.965 0 0 0 .089 1.359.957.957 0 0 0 1.354-.089 43.05 43.05 0 0 1 3.991-3.985zm11.808-7.817c.476-.257.657-.858.405-1.342a.967.967 0 0 0-1.319-.412 67.097 67.097 0 0 0-5.123 3.059c-.451.298-.58.913-.287 1.373.294.46.898.59 1.35.292a65.257 65.257 0 0 1 4.974-2.97zm12.795-5.948c.55-.169.851-.724.672-1.241-.179-.518-.77-.8-1.32-.632a92.308 92.308 0 0 0-5.975 2.054c-.536.205-.794.78-.576 1.283.218.504.83.746 1.366.541a90.115 90.115 0 0 1 5.833-2.005z"/><circle cx="145" cy="90" r="5" fill="#FFF" stroke="#EEE" stroke-width="2"/><circle cx="238" cy="138" r="5" fill="#FFF" stroke="#EEE" stroke-width="2"/><path stroke="#B5A7DD" stroke-width="3" d="M20.06 56s-17.47 33-12 53c5.47 20 17 32 38 44s32.44-5 60.94 6 29 43 29 43" stroke-linecap="round" stroke-dasharray="8 10"/><g stroke="#EEE" stroke-width="3" transform="translate(108 173)"><path fill="#FFF" d="M154 77c0-42.526-34.474-77-77-77S0 34.474 0 77" stroke-linecap="round"/><circle cx="108" cy="41" r="16"/><circle cx="42.5" cy="30.5" r="8.5"/><circle cx="22" cy="58" r="5"/></g><g fill="#FC8A51" transform="rotate(15 101.633 923.121)"><path d="M.398 11.298h2.388c0-4.234 3.385-7.666 7.56-7.666V1.21C4.853 1.21.399 5.727.399 11.298z"/><ellipse cx="10.745" cy="2.018" rx="1.99" ry="2.018"/></g><g fill="#FC8A51" transform="scale(-1 1) rotate(-15 -102.031 920.099)"><path d="M.398 11.298h2.388c0-4.234 3.385-7.666 7.56-7.666V1.21C4.853 1.21.399 5.727.399 11.298z"/><ellipse cx="10.745" cy="2.018" rx="1.99" ry="2.018"/></g><g transform="rotate(15 71.738 842.306)"><g fill="#FC8A51" transform="translate(29.449 11.298)"><rect width="7.959" height="2" x=".796" y="8.877" rx="1"/><rect width="7.959" height="2" x=".796" y="16.14" transform="rotate(15 4.776 17.14)" rx="1"/><rect width="7.959" height="2" x=".915" y="1.807" transform="rotate(-15 4.895 2.807)" rx="1"/></g><g fill="#FC8A51" transform="matrix(-1 0 0 1 9.551 11.298)"><rect width="7.959" height="2" x=".796" y="8.877" rx="1"/><rect width="7.959" height="2" x=".796" y="16.14" transform="rotate(15 4.776 17.14)" rx="1"/><rect width="7.959" height="2" x=".915" y="1.807" transform="rotate(-15 4.895 2.807)" rx="1"/></g><use stroke="#FC8A51" stroke-width="6" mask="url(#m)" xlink:href="#f"/><path fill="#FC8A51" d="M7.163 12.912h23.878v3H7.163z"/></g><g fill="#EEE" transform="scale(-1 1) rotate(15 -60.75 -335.206)"><path d="M.255 7.123h1.53a4.84 4.84 0 0 1 4.848-4.834V.763C3.11.763.255 3.611.255 7.123z"/><ellipse cx="6.888" cy="1.272" rx="1.276" ry="1.272"/></g><g fill="#EEE" transform="rotate(-15 60.494 -337.144)"><path d="M.255 7.123h1.53a4.84 4.84 0 0 1 4.848-4.834V.763C3.11.763.255 3.611.255 7.123z"/><ellipse cx="6.888" cy="1.272" rx="1.276" ry="1.272"/></g><g transform="scale(-1 1) rotate(15 -79.491 -386.955)"><g fill="#EEE" transform="translate(18.878 7.123)"><rect width="5.102" height="2" x=".51" y="5.596" rx="1"/><rect width="5.102" height="2" x=".51" y="10.175" transform="rotate(15 3.061 11.175)" rx="1"/><rect width="5.102" height="2" x=".587" y="1.139" transform="rotate(-15 3.138 2.14)" rx="1"/></g><g fill="#EEE" transform="matrix(-1 0 0 1 6.122 7.123)"><rect width="5.102" height="2" x=".51" y="5.596" rx="1"/><rect width="5.102" height="2" x=".51" y="10.175" transform="rotate(15 3.061 11.175)" rx="1"/><rect width="5.102" height="2" x=".587" y="1.139" transform="rotate(-15 3.138 2.14)" rx="1"/></g><use stroke="#EEE" stroke-width="4" mask="url(#n)" xlink:href="#g"/><path fill="#EEE" d="M4.592 8.14h15.306v2H4.592z"/></g><g fill="#FFF" transform="translate(0 103)"><circle cx="8.5" cy="8.5" r="8.5" stroke="#B5A7DD" stroke-width="4"/><circle cx="171.5" cy="20.5" r="6.5"/></g><g transform="translate(39 142)"><ellipse cx="12.5" cy="12.5" fill="#FFF" stroke="#6B4FBB" stroke-width="4" rx="12.5" ry="12.5"/><path fill="#FC8A51" d="M10.732 13.475l-1.766-1.767a1.5 1.5 0 1 0-2.122 2.122l2.826 2.826h.001v.001c.59.59 1.535.587 2.119.003l6.37-6.37a1.504 1.504 0 0 0-.003-2.118 1.494 1.494 0 0 0-2.118-.004l-5.307 5.307z"/></g><circle cx="171.5" cy="122.5" r="6.5" fill="#FFF" stroke="#FC8A51" stroke-width="3"/><circle cx="22" cy="52" r="6" fill="#FFF" stroke="#B5A7DD" stroke-width="3"/><path fill="#FFF" stroke="#B5A7DD" stroke-width="3.6" d="M188.151 141.596c8.704-7.746 11.013-20.925 4.862-31.578-7.02-12.16-22.405-16.422-34.362-9.518-11.958 6.904-15.96 22.358-8.939 34.518 6.236 10.8 19.068 15.37 30.238 11.42l10.899 18.879a4.765 4.765 0 0 0 6.508 1.748 4.768 4.768 0 0 0 1.74-6.51l-10.946-18.959zm-8.434-4.609c7.857-4.536 10.487-14.692 5.873-22.683-4.613-7.991-14.723-10.791-22.58-6.255-7.858 4.537-10.488 14.693-5.875 22.684 4.614 7.99 14.724 10.791 22.582 6.254z"/></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/job_not_triggered.svg b/app/assets/images/illustrations/job_not_triggered.svg
deleted file mode 100644
index e13c1cb0a7d..00000000000
--- a/app/assets/images/illustrations/job_not_triggered.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 310 141" xmlns:xlink="http://www.w3.org/1999/xlink"><g fill="none" fill-rule="evenodd"><g fill-rule="nonzero"><path fill="#e5e5e5" d="M48 69c0-1.105.887-2 1.998-2h4c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4A1.992 1.992 0 0 1 48 69m14 0c0-1.105.887-2 1.998-2h4c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4A1.992 1.992 0 0 1 62 69"/><g fill="#31af64"><path d="M19 88C8.507 88 0 79.493 0 69s8.507-19 19-19 19 8.507 19 19-8.507 19-19 19m0-4c8.284 0 15-6.716 15-15 0-8.284-6.716-15-15-15-8.284 0-15 6.716-15 15 0 8.284 6.716 15 15 15"/><path d="M17.07 71.02l-2.829-2.828a1.995 1.995 0 0 0-2.828 0 1.997 1.997 0 0 0 0 2.83l4.243 4.243a1.993 1.993 0 0 0 2.823.005l7.79-7.79a1.998 1.998 0 0 0-.007-2.822 1.99 1.99 0 0 0-2.822-.006l-6.37 6.37v-.001"/></g></g><g transform="translate(187)"><rect width="116" height="134" y="7" fill="#f9f9f9" rx="10"/><rect width="116" height="134" x="5" y="2" fill="#fff" rx="10"/><path fill="#eee" fill-rule="nonzero" d="M15 4a8 8 0 0 0-8 8v114a8 8 0 0 0 8 8h96a8 8 0 0 0 8-8V12a8 8 0 0 0-8-8H15m0-4h96c6.627 0 12 5.373 12 12v114c0 6.627-5.373 12-12 12H15c-6.627 0-12-5.373-12-12V12C3 5.373 8.373 0 15 0"/><g transform="translate(23 25)"><g fill="#e1dbf1"><rect width="16" height="4" rx="2"/><rect width="16" height="4" x="32" y="12" rx="2"/></g><rect width="16" height="4" x="44" fill="#eee" rx="2"/><rect width="16" height="4" x="12" y="24" fill="#e1dbf1" rx="2"/><rect width="16" height="4" x="64" y="36" fill="#fef0e8" rx="2"/><rect width="8" height="4" x="20" fill="#fee1d3" rx="2" id="a"/><rect width="8" height="4" x="32" y="36" fill="#fc6d26" rx="2"/><rect width="8" height="4" x="52" y="12" fill="#fef0e8" rx="2"/><rect width="8" height="4" x="64" fill="#fef0e8" rx="2" id="b"/><rect width="12" height="4" x="16" y="48" fill="#e1dbf1" rx="2"/><rect width="8" height="4" x="44" y="36" fill="#fc6d26" rx="2"/><g fill="#e1dbf1"><rect width="4" height="4" x="56" y="36" rx="2"/><rect width="4" height="4" x="64" y="60" rx="2"/></g><rect width="4" height="4" x="72" y="60" fill="#fc6d26" rx="2"/><rect width="8" height="4" x="32" fill="#fc6d26" rx="2" id="c"/><g fill="#eee"><rect width="28" height="4" y="36" rx="2"/><rect width="28" height="4" x="44" y="48" rx="2"/></g><rect width="28" height="4" x="32" y="60" fill="#efedf8" rx="2"/><rect width="28" height="4" y="12" fill="#6b4fbb" rx="2"/><rect width="28" height="4" x="32" y="24" fill="#c3b8e3" rx="2"/><rect width="8" height="4" y="24" fill="#fef0e8" rx="2"/><rect width="8" height="4" x="32" y="48" fill="#6b4fbb" rx="2"/><rect width="12" height="4" y="48" fill="#fc6d26" rx="2"/><g fill="#fef0e8"><rect width="12" height="4" y="60" rx="2"/><rect width="12" height="4" x="16" y="60" rx="2"/></g></g><g transform="translate(23 97)"><rect width="16" height="4" fill="#efedf8" rx="2"/><rect width="16" height="4" x="18" y="12" fill="#fc6d26" rx="2"/><rect width="16" height="4" x="44" fill="#6b4fbb" rx="2"/><use xlink:href="#a"/><rect width="8" height="4" x="38" y="12" fill="#fef0e8" rx="2"/><use xlink:href="#b"/><use xlink:href="#c"/><rect width="14" height="4" y="12" fill="#eee" rx="2"/></g></g><g fill-rule="nonzero"><path fill="#eee" d="M109 101a2 2 0 1 1 0-4c2.524 0 5-.346 7.379-1.02a2 2 0 0 1 1.091 3.849 31.007 31.007 0 0 1-8.47 1.172m18.09-5.825a31.174 31.174 0 0 0 6.187-5.899 2 2 0 1 0-3.131-2.489 27.133 27.133 0 0 1-5.393 5.142 2.001 2.001 0 0 0 2.337 3.247m11.297-15.288a30.923 30.923 0 0 0 1.576-8.407 2 2 0 1 0-3.996-.188 26.875 26.875 0 0 1-1.372 7.32 2 2 0 1 0 3.791 1.275m.283-18.89a30.855 30.855 0 0 0-3.593-7.763 2 2 0 1 0-3.362 2.166 26.905 26.905 0 0 1 3.128 6.757 2 2 0 0 0 3.828-1.16M127.875 45.41a30.973 30.973 0 0 0-7.435-4.228 2 2 0 0 0-1.477 3.717 26.936 26.936 0 0 1 6.474 3.682 2 2 0 0 0 2.438-3.172m-17.834-6.391a31.09 31.09 0 0 0-8.5.886 2 2 0 0 0 .959 3.883 27.06 27.06 0 0 1 7.408-.771 2 2 0 1 0 .132-3.998m-18.272 5.207a31.139 31.139 0 0 0-6.383 5.688 2 2 0 1 0 3.045 2.593 27.152 27.152 0 0 1 5.564-4.957 2 2 0 1 0-2.226-3.324M79.96 59.121a30.864 30.864 0 0 0-1.862 8.349 2 2 0 1 0 3.987.323c.203-2.506.75-4.946 1.62-7.268a2 2 0 1 0-3.746-1.404m-.923 18.873a30.827 30.827 0 0 0 3.327 7.881 2.001 2.001 0 0 0 3.435-2.051 26.785 26.785 0 0 1-2.895-6.859 2 2 0 0 0-3.865 1.029M89.301 93.94a31.008 31.008 0 0 0 7.286 4.476 2 2 0 1 0 1.603-3.665 26.983 26.983 0 0 1-6.346-3.899 2 2 0 0 0-2.543 3.087m17.61 6.991a2 2 0 0 1 .265-3.991c.601.04 1.205.06 1.812.06a1.999 1.999 0 1 1-.001 3.999c-.695 0-1.387-.023-2.076-.069"/><path fill="#fc0" d="M117.78 63.798c.241.268.288.563.14.884l-10.848 23.24c-.174.334-.455.502-.843.502-.054 0-.148-.014-.282-.04a.855.855 0 0 1-.512-.382.761.761 0 0 1-.09-.603l3.957-16.232-8.156 2.03a1.08 1.08 0 0 1-.241.02.93.93 0 0 1-.623-.222c-.24-.2-.328-.462-.26-.783l4.04-16.574a.858.858 0 0 1 .321-.462.917.917 0 0 1 .563-.18h6.59c.254 0 .468.083.642.25a.797.797 0 0 1 .261.593.818.818 0 0 1-.1.362l-3.435 9.301 7.955-1.969c.107-.027.187-.04.241-.04.254 0 .482.1.683.301"/><path fill="#e5e5e5" d="M148 69c0-1.105.887-2 1.998-2h4c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4A1.992 1.992 0 0 1 148 69m14 0c0-1.105.887-2 1.998-2h4c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4A1.992 1.992 0 0 1 162 69"/></g></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/labels.svg b/app/assets/images/illustrations/labels.svg
deleted file mode 100644
index 3a2d521323b..00000000000
--- a/app/assets/images/illustrations/labels.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="787 240 386 274" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><circle id="a" cx="37" cy="107" r="8"/><mask id="e" width="16" height="16" x="0" y="0" fill="#fff"><use xlink:href="#a"/></mask><circle id="b" cx="37" cy="75" r="8"/><mask id="f" width="16" height="16" x="0" y="0" fill="#fff"><use xlink:href="#b"/></mask><circle id="c" cx="42" cy="93" r="8"/><mask id="g" width="16" height="16" x="0" y="0" fill="#fff"><use xlink:href="#c"/></mask><circle id="d" cx="43" cy="75" r="8"/><mask id="h" width="16" height="16" x="0" y="0" fill="#fff"><use xlink:href="#d"/></mask></defs><g fill="none" fill-rule="evenodd" transform="translate(791 244)"><g transform="rotate(30 49.554 229.722)"><rect width="74" height="124" x="8.6" y="95.9" fill="#FAFAFA" rx="8"/><rect width="74" height="124" y="87" fill="#FFF" stroke="#EEE" stroke-width="4" stroke-linecap="round" rx="8"/><circle cx="26.5" cy="178.5" r="3.5" fill="#FC8A51"/><circle cx="47.5" cy="178.5" r="3.5" fill="#FC8A51"/><rect width="50" height="4" x="12" y="127" fill="#E5E5E5" rx="2"/><rect width="38" height="4" x="18" y="139" fill="#E5E5E5" rx="2"/><use stroke="#E5E5E5" stroke-width="8" mask="url(#e)" stroke-linecap="round" xlink:href="#a"/><path stroke="#EEE" stroke-width="4" d="M37.3 107S10.5 18.3 81 .6" stroke-linecap="round"/><path fill="#FDE5D8" d="M31 189c0 3.3 2.7 6 6 6s6-2.7 6-6"/></g><g transform="translate(105 47)"><rect width="74" height="124" y="64" fill="#FAFAFA" rx="8"/><rect width="74" height="124" y="55" fill="#FFF" stroke="#EEE" stroke-width="4" stroke-linecap="round" rx="8"/><rect width="50" height="4" x="12" y="95" fill="#E5E5E5" rx="2"/><rect width="38" height="4" x="18" y="107" fill="#E5E5E5" rx="2"/><use stroke="#E5E5E5" stroke-width="8" mask="url(#f)" stroke-linecap="round" xlink:href="#b"/><path fill="#B5A7DD" d="M56 149.7c-.6-1-.2-2 .7-2.7l1.8-1c1-.6 2-.2 2.7.7.5 1 .2 2.2-.7 2.8l-1.8 1c-1 .5-2 .2-2.7-.8zm-37.8 0c.5-1 .2-2-.7-2.7l-1.8-1c-1-.6-2-.2-2.7.7-.6 1-.2 2.2.7 2.8l1.8 1c1 .5 2 .2 2.7-.8zM33 151h9v4h-9v-4z"/><path fill="#6B4FBB" d="M59 153c0-5.5-4.6-10-10-10-5.7 0-10 4.5-10 10s4.3 10 10 10c5.4 0 10-4.5 10-10zm-16 0c0-3.3 2.6-6 6-6 3.2 0 6 2.7 6 6s-2.8 6-6 6c-3.4 0-6-2.7-6-6zm-8 0c0-5.5-4.6-10-10-10-5.7 0-10 4.5-10 10s4.3 10 10 10c5.4 0 10-4.5 10-10zm-16 0c0-3.3 2.6-6 6-6 3.2 0 6 2.7 6 6s-2.8 6-6 6c-3.4 0-6-2.7-6-6z"/><path stroke="#EEE" stroke-width="4" d="M37 75S30 0 80 0" stroke-linecap="round"/></g><g transform="rotate(15 -82.507 752.644)"><rect width="74" height="124" x="14.6" y="81.8" fill="#FAFAFA" rx="8"/><rect width="74" height="124" x="5" y="73" fill="#FFF" stroke="#EEE" stroke-width="4" stroke-linecap="round" rx="8"/><path fill="#FDE5D8" d="M41 147c0-1 1-2 2-2s2 1 2 2v3c0 1-1 2-2 2s-2-1-2-2v-3zm16.8 6.2c.8-.7 2-.6 2.8.3.7.8.5 2-.3 2.8L58 158c-1 .8-2.2.7-3 0-.6-1-.4-2.3.4-3l2.4-1.8zm-32 3c-1-.6-1-2-.4-2.7.7-1 2-1 2.8-.3l2.4 1.8c.8.7 1 2 .3 3-.8.7-2 1-3 0l-2.3-1.7z"/><rect width="2" height="7" x="39" y="168" fill="#FC8A51" rx="1"/><rect width="2" height="7" x="45" y="168" fill="#FC8A51" rx="1"/><circle cx="40" cy="169" r="2" fill="#FC8A51"/><circle cx="46" cy="169" r="2" fill="#FC8A51"/><rect width="22" height="18" x="32" y="158" stroke="#FC8A51" stroke-width="4" rx="8"/><rect width="34" height="5" x="26" y="174" fill="#FC8A51" rx="2.5"/><rect width="50" height="4" x="17" y="113" fill="#E5E5E5" rx="2"/><rect width="38" height="4" x="23" y="125" fill="#E5E5E5" rx="2"/><use stroke="#E5E5E5" stroke-width="8" mask="url(#g)" stroke-linecap="round" xlink:href="#c"/><path stroke="#EEE" stroke-width="4" d="M42 93S50 0 0 0" stroke-linecap="round"/></g><g transform="rotate(-15 276.18 -697.744)"><rect width="74" height="124" x="18.7" y="65.6" fill="#FAFAFA" rx="8"/><rect width="74" height="124" x="6" y="55" fill="#FFF" stroke="#EEE" stroke-width="4" stroke-linecap="round" rx="8"/><g transform="translate(25 129)"><path stroke="#B5A7DD" stroke-width="4" d="M32 14c0-7.7-6.3-14-14-14S4 6.3 4 14" stroke-linecap="round"/><path stroke="#B5A7DD" stroke-width="2" d="M33 15v13c0 4.4-3.6 8-8 8" stroke-linecap="round"/><rect width="7" height="4" x="20" y="34" fill="#6B4FBB" rx="2"/><rect width="7" height="13" y="15" fill="#FFF" stroke="#6B4FBB" stroke-width="3" stroke-linejoin="round" rx="3.5"/><rect width="7" height="13" x="29" y="15" fill="#FFF" stroke="#6B4FBB" stroke-width="3" stroke-linejoin="round" transform="matrix(-1 0 0 1 65 0)" rx="3.5"/></g><rect width="50" height="4" x="18" y="95" fill="#E5E5E5" rx="2"/><rect width="38" height="4" x="24" y="107" fill="#E5E5E5" rx="2"/><use stroke="#E5E5E5" stroke-width="8" mask="url(#h)" stroke-linecap="round" xlink:href="#d"/><path stroke="#EEE" stroke-width="4" d="M43 75S50 0 0 0" stroke-linecap="round"/></g><circle cx="193" cy="47" r="12" fill="#FFF" stroke="#FDE5D8" stroke-width="4"/><circle cx="193" cy="47" r="5" fill="#FFF" stroke="#FDE5D8" stroke-width="4"/><g opacity=".2"><path fill="#FC8A51" d="M30.7 254.8l-2.6 1c-1 .5-1.7 0-1.7-1v-3l-1-2.7c-.4-1 .2-1.7 1.2-1.7h3l2.6-1c1.2-.4 2 .2 2 1.2l-.2 3 1 2.6c.5 1.2 0 2-1 2l-3-.2zm344-121l-2.6 1c-1 .5-1.7 0-1.7-1v-3l-1-2.7c-.4-1 .2-1.7 1.2-1.7h3l2.6-1c1.2-.4 2 .2 2 1.2l-.2 3 1 2.6c.5 1.2 0 2-1 2l-3-.2zM5.6 95H1.8c-1.3.2-2-.8-1.4-2l1.4-3.4-.2-3.8c0-1.3 1-2 2-1.4l3.6 1.4 3.7-.2c1.2 0 2 1 1.4 2L11 91.3V95c.2 1.2-.8 2-2 1.4L5.6 95z"/><path fill="#6B4FBB" d="M308.8 62l-2-2.3c-.7-.8-.5-1.7.6-2l2.8-1 2-2c1-.6 1.8-.4 2.2.7l.8 2.8 2 2c.8 1 .5 1.8-.5 2.2l-2.8.8-2.3 2c-.8.8-1.7.5-2-.5l-1-2.8zm9.2 164.6h-3c-1-.2-1.4-1-1-2l1.4-2.5v-3c.2-1 1-1.4 2-1l2.6 1.4h3c1 .2 1.5 1 1 2l-1.4 2.6v3c-.2 1-1 1.5-2 1l-2.5-1.4zM121.8 8l-2-2.3c-.7-.8-.5-1.7.6-2l2.8-1 2-2c1-.6 1.8-.4 2.2.7l.8 2.8 2 2c.8 1 .5 1.8-.5 2.2l-2.8.8-2.3 2c-.8.8-1.7.5-2-.5l-1-2.8z"/></g></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/logos/go_logo.svg b/app/assets/images/illustrations/logos/go_logo.svg
deleted file mode 100644
index 7fd49118006..00000000000
--- a/app/assets/images/illustrations/logos/go_logo.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 16 16"><g fill-rule="evenodd"><path d="M14 16.01h1V7.99C15 4.128 11.866.999 8 .999c-3.858 0-7 3.13-7 6.991v8.02h1V7.99c0-3.306 2.691-5.991 6-5.991 3.314 0 6 2.682 6 5.991v8.02M3.48 2.656a2 2 0 1 0-2.155 3.228c.102-.321.226-.631.371-.93a1.001 1.001 0 1 1 1.069-1.599 6.96 6.96 0 0 1 .717-.699m9.04-.002a2 2 0 1 1 2.155 3.23 6.835 6.835 0 0 0-.37-.931 1 1 0 1 0-1.068-1.599 6.96 6.96 0 0 0-.717-.699"/><path d="M5.726 8.04h1.557v.124c0 .283-.033.534-.1.752a1.583 1.583 0 0 1-.33.566c-.35.394-.795.591-1.335.591-.527 0-.979-.19-1.355-.571a1.893 1.893 0 0 1-.564-1.377c0-.547.191-1.01.574-1.391a1.902 1.902 0 0 1 1.396-.574c.295 0 .57.06.825.181.244.12.484.316.72.586l-.405.388c-.309-.412-.686-.618-1.13-.618-.399 0-.733.138-1 .413-.27.27-.405.609-.405 1.015 0 .42.151.766.452 1.037.282.252.587.378.915.378.28 0 .531-.094.754-.283.223-.19.347-.418.373-.683h-.94v-.535m2.884.061c0-.53.194-.986.583-1.367a1.919 1.919 0 0 1 1.396-.571c.537 0 .998.192 1.382.576.386.384.578.845.578 1.384 0 .542-.194 1-.581 1.379a1.944 1.944 0 0 1-1.408.569c-.487 0-.923-.168-1.311-.505-.426-.373-.64-.861-.64-1.465m.574.007c0 .417.14.759.42 1.028.278.269.6.403.964.403.395 0 .729-.137 1-.41.272-.277.408-.613.408-1.01 0-.402-.134-.739-.403-1.01a1.33 1.33 0 0 0-.991-.41c-.392 0-.723.137-.993.41a1.36 1.36 0 0 0-.405 1m-.184 3.918c.525.026.812.063.812.063.271.025.324-.096.116-.273 0 0-.775-.813-1.933-.813-1.159 0-1.923.813-1.923.813-.211.174-.153.3.12.273 0 0 .286-.037.81-.063v.477c0 .268.224.5.5.5.268 0 .5-.223.5-.498v-.252.25c0 .268.224.5.5.5.268 0 .5-.223.5-.498v-.478m-1-1.023c.552 0 1-.224 1-.5s-.448-.5-1-.5-1 .224-1 .5.448.5 1 .5"/></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/logos/mattermost_logo.svg b/app/assets/images/illustrations/logos/mattermost_logo.svg
deleted file mode 100644
index b577c0599aa..00000000000
--- a/app/assets/images/illustrations/logos/mattermost_logo.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" version="1" viewBox="0 0 501 501"><path d="M236 .7C137.7 7.5 54 68.2 18.2 158.5c-32 81-19.6 172.8 33 242.5 39.8 53 97.2 87 164.3 97 16.5 2.7 48 3.2 63.5 1.2 48.7-6.3 92.2-24.6 129-54.2 13-10.5 33-31.2 42.2-43.7 26.4-35.5 42.8-75.8 49-120.3 1.6-12.3 1.6-48.7 0-61-4-28.3-12-54.8-24.2-79.5-12.8-26-26.5-45.3-46.8-65.8C417.8 64 400.2 49 398.4 49c-.6 0-.4 10.5.3 26l1.3 26 7 8.7c19 23.7 32.8 53.5 38.2 83 2.5 14 3 43 1 55.8-4.5 27.8-15.2 54-31 76.5-8.6 12.2-28 31.6-40.2 40.2-24 17-50 27.6-80 33-10 1.8-49 1.8-59 0-43-7.7-78.8-26-107.2-54.8-29.3-29.7-46.5-64-52.4-104.4-2-14-1.5-42 1-55C90 121.4 132 72 192 49.7c8-3 18.4-5.8 29.5-8.2 1.7-.4 34.4-38 35.3-40.6.3-1-10.2-1-20.8-.4z"/><path d="M322.2 24.6c-1.3.8-8.4 9.3-16 18.7-7.4 9.5-22.4 28-33.2 41.2-51 62.2-66 81.6-70.6 91-6 12-8.4 21-9 33-1.2 19.8 5 36 19 50C222 268 230 273 243 277.2c9 3 10.4 3.2 24 3.2 13.8 0 15 0 22.6-3 23.2-9 39-28.4 45-55.7 2-8.2 2-28.7.4-79.7l-2-72c-1-36.8-1.4-41.8-3-44-2-3-4.8-3.6-7.8-1.4z"/></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/manual_action.svg b/app/assets/images/illustrations/manual_action.svg
deleted file mode 100644
index 85735855b46..00000000000
--- a/app/assets/images/illustrations/manual_action.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 398 151" xmlns:xlink="http://www.w3.org/1999/xlink"><g fill="none" fill-rule="evenodd"><path fill="#fef0e8" stroke="#fc6d26" stroke-width="4" d="M57.7 106.5h21.6a4.2 4.2 0 0 1 4.2 4.2v5.6a4.2 4.2 0 0 1-4.2 4.2H57.7a4.2 4.2 0 0 1-4.2-4.2v-5.6a4.2 4.2 0 0 1 4.2-4.2"/><g transform="translate(42 117)"><rect width="52" height="23" x=".5" y=".5" fill="#fff" stroke="#eee" stroke-width="4" rx="4.2"/><g fill="#fdc4a8"><rect width="11" height="2" x="8" y="8" rx="1"/><rect width="11" height="2" x="8" y="14" rx="1"/></g></g><g fill-rule="nonzero"><path fill="#e1dbf1" d="M96.31 132.32c1.048 0 1.648.007 4.319.042 11.523.153 18.377-.12 26.32-1.533 24.23-4.309 38.521-18.02 38.521-45.03 0-31.02 21.885-44.487 66.903-40.522l.351-3.985c-47.09-4.147-71.25 10.727-71.25 44.507 0 24.868-12.746 37.1-35.22 41.09-7.623 1.356-14.284 1.621-25.567 1.471a287.717 287.717 0 0 0-4.372-.042v4"/><path fill="#eee" d="M242 57.678c-6.29-1.373-11-6.976-11-13.678 0-6.702 4.71-12.304 11-13.678v4.136c-4.057 1.274-7 5.065-7 9.542 0 4.478 2.943 8.268 7 9.542v4.136"/></g><g transform="translate(242)"><rect width="116" height="134" y="7" fill="#f9f9f9" rx="10"/><rect width="116" height="134" x="5" y="2" fill="#fff" rx="10"/><path fill="#eee" fill-rule="nonzero" d="M15 4a8 8 0 0 0-8 8v114a8 8 0 0 0 8 8h96a8 8 0 0 0 8-8V12a8 8 0 0 0-8-8H15m0-4h96c6.627 0 12 5.373 12 12v114c0 6.627-5.373 12-12 12H15c-6.627 0-12-5.373-12-12V12C3 5.373 8.373 0 15 0"/><g transform="translate(23 25)"><g fill="#e1dbf1"><rect width="16" height="4" rx="2"/><rect width="16" height="4" x="32" y="12" rx="2"/></g><rect width="16" height="4" x="44" fill="#eee" rx="2"/><rect width="16" height="4" x="12" y="24" fill="#e1dbf1" rx="2"/><rect width="16" height="4" x="64" y="36" fill="#fef0e8" rx="2"/><rect width="8" height="4" x="20" fill="#fee1d3" rx="2" id="a"/><rect width="8" height="4" x="32" y="36" fill="#fc6d26" rx="2"/><rect width="8" height="4" x="52" y="12" fill="#fef0e8" rx="2"/><rect width="8" height="4" x="64" fill="#fef0e8" rx="2" id="b"/><rect width="12" height="4" x="16" y="48" fill="#e1dbf1" rx="2"/><rect width="8" height="4" x="44" y="36" fill="#fc6d26" rx="2"/><g fill="#e1dbf1"><rect width="4" height="4" x="56" y="36" rx="2"/><rect width="4" height="4" x="64" y="60" rx="2"/></g><rect width="4" height="4" x="72" y="60" fill="#fc6d26" rx="2"/><rect width="8" height="4" x="32" fill="#fc6d26" rx="2" id="c"/><g fill="#eee"><rect width="28" height="4" y="36" rx="2"/><rect width="28" height="4" x="44" y="48" rx="2"/></g><rect width="28" height="4" x="32" y="60" fill="#efedf8" rx="2"/><rect width="28" height="4" y="12" fill="#6b4fbb" rx="2"/><rect width="28" height="4" x="32" y="24" fill="#c3b8e3" rx="2"/><rect width="8" height="4" y="24" fill="#fef0e8" rx="2"/><rect width="8" height="4" x="32" y="48" fill="#6b4fbb" rx="2"/><rect width="12" height="4" y="48" fill="#fc6d26" rx="2"/><g fill="#fef0e8"><rect width="12" height="4" y="60" rx="2"/><rect width="12" height="4" x="16" y="60" rx="2"/></g><g transform="translate(0 72)"><rect width="16" height="4" fill="#efedf8" rx="2"/><rect width="16" height="4" x="18" y="12" fill="#fc6d26" rx="2"/><rect width="16" height="4" x="44" fill="#6b4fbb" rx="2"/><use xlink:href="#a"/><rect width="8" height="4" x="38" y="12" fill="#fef0e8" rx="2"/><use xlink:href="#b"/><use xlink:href="#c"/><rect width="14" height="4" y="12" fill="#eee" rx="2"/></g></g></g><g transform="translate(330 83)"><circle cx="33" cy="33" r="33" fill="#fff"/><g fill-rule="nonzero"><path fill="#eee" d="M33 68C13.67 68-2 52.33-2 33S13.67-2 33-2s35 15.67 35 35-15.67 35-35 35m0-4c17.12 0 31-13.879 31-31C64 15.88 50.121 2 33 2 15.88 2 2 15.879 2 33c0 17.12 13.879 31 31 31"/><path fill="#6b4fbb" stroke="#6b4fbb" stroke-width=".968" d="M42.383 34.655v-3.308l-2.112-.343c-.116-.456-.351-.913-.703-1.598l1.29-1.711-2.463-2.398-1.76 1.256a6.347 6.347 0 0 0-1.642-.684l-.233-2.055h-3.401l-.352 2.055c-.586.114-1.055.342-1.642.684l-1.76-1.255-2.463 2.397 1.173 1.711c-.352.57-.469 1.027-.704 1.598l-1.995.228v3.31l2.112.342c.116.57.351 1.027.703 1.598l-1.172 1.712 2.463 2.397 1.759-1.141c.469.227 1.056.456 1.642.684l.352 2.055h3.518l.352-2.055c.586-.114 1.055-.342 1.642-.684l1.76 1.255 2.463-2.397-1.29-1.712a6.03 6.03 0 0 0 .703-1.598l1.76-.344M33 36.367c-1.994 0-3.519-1.484-3.519-3.424 0-1.941 1.525-3.424 3.519-3.424 1.994 0 3.519 1.483 3.519 3.424 0 1.94-1.525 3.424-3.519 3.424" stroke-linecap="round" stroke-linejoin="bevel"/><path fill="#e1dbf1" d="M33 53.563c-11.598 0-21-9.206-21-20.563s9.402-20.563 21-20.563S54 21.643 54 33s-9.402 20.563-21 20.563m0-4.375c9.13 0 16.532-7.248 16.532-16.188 0-8.94-7.402-16.188-16.532-16.188-9.13 0-16.532 7.248-16.532 16.188 0 8.94 7.402 16.188 16.532 16.188"/></g></g><path fill="#fff" d="M164 114c14.912 0 27-12.09 27-27 0-14.912-12.09-27-27-27-14.912 0-27 12.09-27 27 0 14.912 12.09 27 27 27"/><g fill-rule="nonzero"><path fill="#eee" d="M164 118c-17.12 0-31-13.879-31-31 0-17.12 13.879-31 31-31 17.12 0 31 13.879 31 31 0 17.12-13.879 31-31 31m0-4c14.912 0 27-12.09 27-27 0-14.912-12.09-27-27-27-14.912 0-27 12.09-27 27 0 14.912 12.09 27 27 27"/><path fill="#fc0" d="M172.78 80.798c.241.268.288.563.14.884l-10.848 23.24c-.174.334-.455.502-.843.502-.054 0-.148-.014-.282-.04a.855.855 0 0 1-.512-.382.761.761 0 0 1-.09-.603l3.957-16.232-8.156 2.03a1.08 1.08 0 0 1-.241.02.93.93 0 0 1-.623-.222c-.24-.2-.328-.462-.26-.783l4.04-16.574a.858.858 0 0 1 .321-.462.917.917 0 0 1 .563-.18h6.59c.254 0 .468.083.642.25a.797.797 0 0 1 .261.593.818.818 0 0 1-.1.362l-3.435 9.301 7.955-1.969c.107-.027.187-.04.241-.04.254 0 .482.1.683.301"/></g><g><path fill="#eee" fill-rule="nonzero" d="M37.801 99.01l5.355 2.648c2.271 1.122 4.643-.252 4.809-2.778l.487-7.546a27.675 27.675 0 0 0 2.87-4.076c7.594-13.152 3.088-29.972-10.07-37.565-13.153-7.594-29.971-3.087-37.566 10.07-7.594 13.154-3.087 29.973 10.07 37.565a27.46 27.46 0 0 0 24.05 1.687m.952-3.992a2.002 2.002 0 0 0-1.698-.035 23.454 23.454 0 0 1-21.299-1.124c-11.24-6.488-15.09-20.86-8.602-32.1 6.49-11.239 20.862-15.09 32.1-8.601 11.239 6.489 15.09 20.862 8.6 32.1a23.519 23.519 0 0 1-2.849 3.939 1.995 1.995 0 0 0-.504 1.204l-.466 7.229-5.285-2.613"/><path fill="#fdc4a8" d="M21.137 70.471A7.495 7.495 0 0 0 27.5 74c2.684 0 5.04-1.41 6.363-3.529C36.377 71.869 38 74.267 38 77.674c0 5.799-2.739 9.587-10.5 9.587S17 83.473 17 77.674c0-3.407 1.622-5.804 4.137-7.203M27.5 72a5.5 5.5 0 1 1 0-11 5.5 5.5 0 1 1 0 11"/></g></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/merge_request_changes_empty.svg b/app/assets/images/illustrations/merge_request_changes_empty.svg
deleted file mode 100644
index 40efeb2de57..00000000000
--- a/app/assets/images/illustrations/merge_request_changes_empty.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="374" height="268" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><circle id="a" cx="44" cy="44" r="44"/><circle id="b" cx="31" cy="31" r="31"/><circle id="c" cx="35" cy="35" r="35"/><rect id="d" width="230" height="176" rx="10"/><circle id="e" cx="31" cy="31" r="31"/></defs><g fill="none" fill-rule="evenodd"><g transform="translate(4 98)"><circle cx="53" cy="53" r="44" fill="#F9F9F9"/><g transform="translate(6 6)"><use fill="#FFF" xlink:href="#a"/><circle cx="44" cy="44" r="42" stroke="#EEE" stroke-width="4"/><path fill="#FEE1D3" fill-rule="nonzero" d="M34.394 55.736A4 4 0 0 1 36.706 55H56a6 6 0 0 0 6-6V35a6 6 0 0 0-6-6H34a6 6 0 0 0-6 6v25.265l6.394-4.53zM36.706 59l-7.972 5.647A3 3 0 0 1 24 62.199V35c0-5.523 4.477-10 10-10h22c5.523 0 10 4.477 10 10v14c0 5.523-4.477 10-10 10H36.706z"/><path fill="#FC6D26" d="M38 40a2 2 0 1 1 0 4 2 2 0 0 1 0-4zm7 0a2 2 0 1 1 0 4 2 2 0 0 1 0-4zm7 0a2 2 0 1 1 0 4 2 2 0 0 1 0-4z"/></g></g><g transform="translate(50 2)"><circle cx="39" cy="39" r="31" fill="#F9F9F9"/><g transform="translate(5 5)"><use fill="#FFF" xlink:href="#b"/><circle cx="31" cy="31" r="29" stroke="#EEE" stroke-width="4"/><rect width="20" height="4" x="21" y="29" fill="#6B4FBB" rx="2"/></g></g><path fill="#F9F9F9" d="M235.58 229H102c-6.627 0-12-5.373-12-12V65c0-6.627 5.373-12 12-12h206c6.627 0 12 5.373 12 12v18.399A34.842 34.842 0 0 1 337 79c19.33 0 35 15.67 35 35s-15.67 35-35 35a34.842 34.842 0 0 1-17-4.399V217c0 6.627-5.373 12-12 12h-11.58c.38 1.941.58 3.947.58 6 0 17.12-13.88 31-31 31s-31-13.88-31-31c0-2.053.2-4.059.58-6z"/><g transform="translate(87 50)"><g transform="translate(212 26)"><use fill="#FFF" xlink:href="#c"/><circle cx="35" cy="35" r="33" stroke="#EEE" stroke-width="4"/><g transform="translate(20 19)"><circle cx="15" cy="16" r="15" fill="#F4F1FA" stroke="#6B4FBB" stroke-width="3"/><path fill="#6B4FBB" d="M19.419 6.996h-.007L16.959 4l-2.454 2.997h-.006L12.045 4 9.59 6.998h-.003L7.132 4 4.676 7H2c2.605-4.204 7.23-7 12.502-7C19.771 0 24.394 2.793 27 6.994h-2.676L21.872 4l-2.453 2.996z"/><circle cx="9.5" cy="17.5" r="1.5" fill="#6B4FBB"/><circle cx="20.5" cy="17.5" r="1.5" fill="#6B4FBB"/></g></g><use fill="#FFF" xlink:href="#d"/><rect width="226" height="172" x="2" y="2" stroke="#EEE" stroke-width="4" rx="10"/><rect width="4" height="122" x="33" y="42" fill="#EEE" rx="2"/><g transform="translate(13 59)"><rect width="10" height="4" fill="#FEE1D3" rx="2"/><rect width="10" height="4" y="12" fill="#F0EDF8" rx="2"/><rect width="10" height="4" y="24" fill="#FEF0E9" rx="2"/><rect width="10" height="4" y="36" fill="#FEE1D3" rx="2"/><rect width="10" height="4" y="48" fill="#E1DBF1" rx="2"/><rect width="10" height="4" y="60" fill="#F0EDF8" rx="2"/><rect width="10" height="4" y="72" fill="#FEF0E9" rx="2"/><rect width="10" height="4" y="84" fill="#FEE1D3" rx="2"/></g><g transform="translate(55 59)"><rect width="14" height="4" fill="#6B4FBB" rx="2"/><rect width="14" height="4" x="20" fill="#F0EDF8" rx="2"/><rect width="14" height="4" x="40" fill="#FEF0E9" rx="2"/><rect width="14" height="4" y="12" fill="#F0EDF8" rx="2"/><rect width="14" height="4" x="40" y="24" fill="#FEF0E9" rx="2"/><rect width="14" height="4" y="48" fill="#E1DBF1" rx="2"/><rect width="14" height="4" x="40" y="36" fill="#FEF0E9" rx="2"/><rect width="7" height="4" x="20" y="12" fill="#FEE1D3" rx="2"/><rect width="7" height="4" x="27" y="36" fill="#6B4FBB" rx="2"/><rect width="7" height="4" x="20" y="48" fill="#FEE1D3" rx="2"/><rect width="7" height="4" y="24" fill="#FC6D26" rx="2"/><rect width="21" height="4" x="13" y="24" fill="#E1DBF1" rx="2"/><rect width="21" height="4" y="36" fill="#EEE" rx="2"/><rect width="7" height="4" x="33" y="12" fill="#6B4FBB" rx="2"/><g transform="translate(98)"><rect width="14" height="4" fill="#FEE1D3" rx="2"/><rect width="14" height="4" x="20" fill="#F0EDF8" rx="2"/><rect width="14" height="4" x="40" fill="#FC6D26" rx="2"/><rect width="14" height="4" y="12" fill="#FEF0E9" rx="2"/><rect width="14" height="4" x="40" y="24" fill="#E1DBF1" rx="2"/><rect width="14" height="4" y="48" fill="#F0EDF8" rx="2"/><rect width="14" height="4" x="40" y="36" fill="#FEE1D3" rx="2"/><rect width="7" height="4" x="20" y="12" fill="#FC6D26" rx="2"/><rect width="7" height="4" x="27" y="36" fill="#6B4FBB" rx="2"/><rect width="7" height="4" x="20" y="48" fill="#FC6D26" rx="2"/><rect width="7" height="4" y="24" fill="#6B4FBB" rx="2"/><rect width="21" height="4" x="13" y="24" fill="#FEE1D3" rx="2"/><rect width="21" height="4" y="36" fill="#FEF0E9" rx="2"/><rect width="7" height="4" x="33" y="12" fill="#6B4FBB" rx="2"/></g><g transform="translate(0 60)"><rect width="14" height="4" fill="#F0EDF8" rx="2"/><rect width="14" height="4" x="20" fill="#6B4FBB" rx="2"/><rect width="14" height="4" x="40" fill="#E1DBF1" rx="2"/><rect width="14" height="4" y="12" fill="#FEF0E9" rx="2"/><rect width="14" height="4" x="40" y="24" fill="#FEE1D3" rx="2"/><rect width="7" height="4" x="20" y="12" fill="#EEE" rx="2"/><rect width="7" height="4" y="24" fill="#6B4FBB" rx="2"/><rect width="21" height="4" x="13" y="24" fill="#FEF0E9" rx="2"/><rect width="7" height="4" x="33" y="12" fill="#FC6D26" rx="2"/></g><rect width="4" height="63" x="74" y="13" fill="#EEE" rx="2"/></g><rect width="230" height="4" y="27" fill="#EEE" rx="2"/></g><g transform="translate(233 201)"><use fill="#FFF" xlink:href="#e"/><circle cx="31" cy="31" r="29" stroke="#EEE" stroke-width="4"/><path fill="#FC6D26" d="M29 29v-6a2 2 0 1 1 4 0v6h6a2 2 0 1 1 0 4h-6v6a2 2 0 1 1-4 0v-6h-6a2 2 0 1 1 0-4h6z"/></g></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/merge_requests.svg b/app/assets/images/illustrations/merge_requests.svg
deleted file mode 100644
index b9b8f0058e6..00000000000
--- a/app/assets/images/illustrations/merge_requests.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="755 221 385 225" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><rect id="a" width="278" height="179" rx="10"/><mask id="d" width="278" height="179" x="0" y="0" fill="#fff"><use xlink:href="#a"/></mask><path id="b" d="M13.6 49H57c5.5 0 10-4.5 10-10V10c0-5.5-4.5-10-10-10H10C4.5 0 0 4.5 0 10v42c0 5.5 3.2 7 7.2 3l6.4-6z"/><mask id="e" width="67" height="57.2" x="0" y="0" fill="#fff"><use xlink:href="#b"/></mask><path id="c" d="M13.6 49H57c5.5 0 10-4.5 10-10V10c0-5.5-4.5-10-10-10H10C4.5 0 0 4.5 0 10v42c0 5.5 3.2 7 7.2 3l6.4-6z"/><mask id="f" width="67" height="57.2" x="0" y="0" fill="#fff"><use xlink:href="#c"/></mask></defs><g fill="none" fill-rule="evenodd"><g fill="#F9F9F9" transform="translate(752 227)"><rect width="120" height="22" x="30" rx="11"/><rect width="132" height="22" y="44" rx="11"/><rect width="190" height="22" x="208" y="66" rx="11"/><rect width="158" height="22" x="129" y="197" rx="11"/><rect width="158" height="22" x="66" y="154" rx="11"/><rect width="350" height="22" x="31" y="110" rx="11"/><path d="M153 22H21h21.5c6 0 11 5 11 11s-5 11-11 11H21h132-36.5c-6 0-11-5-11-11s5-11 11-11H153zm252 66H288h36.5c6 0 11 5 11 11s-5 11-11 11H288h117-36.5c-6 0-11-5-11-11s5-11 11-11H405zm-244 44H44h36.5c6 0 11 5 11 11s-5 11-11 11H44h117-36.5c-6 0-11-5-11-11s5-11 11-11H161zm75 44H119h21.5c6 0 11 5 11 11s-5 11-11 11H119h117-51.5c-6 0-11-5-11-11s5-11 11-11H236z"/></g><g transform="translate(812 240)"><use fill="#FFF" stroke="#EEE" stroke-width="8" mask="url(#d)" xlink:href="#a"/><path fill="#EEE" d="M4 29h271v4H4z"/><g transform="translate(34 60)"><rect width="6" height="2" y="1" fill="#B5A7DD" rx="1"/><rect width="15" height="4" x="15" fill="#EEE" rx="2"/><rect width="15" height="4" x="72" fill="#EEE" rx="2"/><rect width="15" height="4" x="39" y="22" fill="#EEE" rx="2"/><rect width="15" height="4" x="53" y="11" fill="#FC6D26" rx="2"/><rect width="20" height="4" x="48" fill="#FC6D26" opacity=".5" rx="2"/><rect width="20" height="4" x="15" y="22" fill="#EEE" rx="2"/><rect width="20" height="4" x="29" y="11" fill="#EEE" rx="2"/><rect width="10" height="4" x="34" fill="#FC6D26" rx="2"/><rect width="10" height="4" x="15" y="11" fill="#EEE" rx="2"/><rect width="6" height="2" y="12" fill="#B5A7DD" rx="1"/><rect width="6" height="2" y="23" fill="#B5A7DD" rx="1"/></g><g transform="translate(34 93)"><rect width="6" height="2" y="1" fill="#B5A7DD" rx="1"/><rect width="15" height="4" x="15" fill="#FC6D26" rx="2"/><rect width="15" height="4" x="72" fill="#EEE" rx="2"/><rect width="15" height="4" x="39" y="22" fill="#FC6D26" opacity=".5" rx="2"/><rect width="15" height="4" x="53" y="11" fill="#EEE" rx="2"/><rect width="20" height="4" x="48" fill="#FC6D26" rx="2"/><rect width="20" height="4" x="15" y="22" fill="#FC6D26" rx="2"/><rect width="20" height="4" x="29" y="11" fill="#EEE" rx="2"/><rect width="10" height="4" x="34" fill="#FC6D26" opacity=".5" rx="2"/><rect width="10" height="4" x="15" y="11" fill="#EEE" rx="2"/><rect width="6" height="2" y="12" fill="#B5A7DD" rx="1"/><rect width="6" height="2" y="23" fill="#B5A7DD" rx="1"/></g><g transform="translate(34 126)"><rect width="6" height="2" y="1" fill="#B5A7DD" rx="1"/><rect width="15" height="4" x="15" fill="#EEE" rx="2"/><rect width="15" height="4" x="72" fill="#EEE" rx="2"/><rect width="15" height="4" x="39" y="22" fill="#EEE" rx="2"/><rect width="15" height="4" x="53" y="11" fill="#EEE" rx="2"/><rect width="20" height="4" x="48" fill="#FC6D26" rx="2"/><rect width="20" height="4" x="15" y="22" fill="#EEE" rx="2"/><rect width="20" height="4" x="29" y="11" fill="#EEE" rx="2"/><rect width="10" height="4" x="34" fill="#FC6D26" opacity=".5" rx="2"/><rect width="10" height="4" x="15" y="11" fill="#EEE" rx="2"/><rect width="6" height="2" y="12" fill="#B5A7DD" rx="1"/><rect width="6" height="2" y="23" fill="#B5A7DD" rx="1"/></g><g transform="translate(157 59)"><rect width="6" height="2" y="1" fill="#FDE5D8" rx="1"/><rect width="15" height="4" x="15" fill="#EEE" rx="2"/><rect width="15" height="4" x="72" fill="#EEE" rx="2"/><rect width="15" height="4" x="39" y="22" fill="#6B4FBB" opacity=".5" rx="2"/><rect width="15" height="4" x="53" y="11" fill="#6B4FBB" rx="2"/><rect width="20" height="4" x="48" fill="#6B4FBB" opacity=".5" rx="2"/><rect width="20" height="4" x="15" y="22" fill="#6B4FBB" rx="2"/><rect width="20" height="4" x="29" y="11" fill="#EEE" rx="2"/><rect width="10" height="4" x="34" fill="#6B4FBB" rx="2"/><rect width="10" height="4" x="15" y="11" fill="#EEE" rx="2"/><rect width="6" height="2" y="12" fill="#FDE5D8" rx="1"/><rect width="6" height="2" y="23" fill="#FDE5D8" rx="1"/><rect width="6" height="2" y="34" fill="#FDE5D8" rx="1"/><rect width="15" height="4" x="15" y="33" fill="#EEE" rx="2"/><rect width="15" height="4" x="58" y="22" fill="#EEE" rx="2"/><rect width="15" height="4" x="39" y="55" fill="#6B4FBB" opacity=".5" rx="2"/><rect width="15" height="4" x="29" y="44" fill="#6B4FBB" rx="2"/><rect width="20" height="4" x="48" y="33" fill="#6B4FBB" rx="2"/><rect width="20" height="4" x="15" y="55" fill="#EEE" rx="2"/><rect width="10" height="4" x="34" y="33" fill="#EEE" rx="2"/><rect width="10" height="4" x="15" y="44" fill="#EEE" rx="2"/><rect width="10" height="4" x="48" y="44" fill="#EEE" rx="2"/><rect width="10" height="4" x="62" y="44" fill="#EEE" rx="2"/><rect width="10" height="4" x="77" y="22" fill="#EEE" rx="2"/><rect width="6" height="2" y="45" fill="#FDE5D8" rx="1"/><rect width="6" height="2" y="56" fill="#FDE5D8" rx="1"/><rect width="6" height="2" y="67" fill="#FDE5D8" rx="1"/><rect width="15" height="4" x="15" y="66" fill="#6B4FBB" rx="2"/><rect width="15" height="4" x="39" y="88" fill="#EEE" rx="2"/><rect width="15" height="4" x="53" y="77" fill="#6B4FBB" opacity=".5" rx="2"/><rect width="20" height="4" x="15" y="88" fill="#EEE" rx="2"/><rect width="20" height="4" x="29" y="77" fill="#6B4FBB" rx="2"/><rect width="10" height="4" x="34" y="66" fill="#EEE" rx="2"/><rect width="10" height="4" x="72" y="77" fill="#EEE" rx="2"/><rect width="10" height="4" x="15" y="77" fill="#EEE" rx="2"/><rect width="6" height="2" y="78" fill="#FDE5D8" rx="1"/><rect width="6" height="2" y="89" fill="#FDE5D8" rx="1"/></g></g><g transform="translate(1057 221)"><use fill="#FFF" stroke="#FDE5D8" stroke-width="8" mask="url(#e)" xlink:href="#b"/><rect width="29" height="3" x="14" y="14" fill="#FDB692" rx="1.5"/><rect width="39" height="3" x="14" y="23" fill="#FDB692" rx="1.5"/><rect width="29" height="3" x="14" y="32" fill="#FDB692" rx="1.5"/></g><g transform="translate(1046 285)"><circle cx="16" cy="15" r="15" fill="#FFF7F4" stroke="#FC6D26" stroke-width="3"/><path stroke="#FC6D26" stroke-width="2" d="M0 14h1c5 0 9.2-2.7 11.4-6.7M14 1V0"/><path stroke="#FC6D26" stroke-width="2" d="M7.8 3c3 4.3 7.8 7 13.2 7 3.3 0 6.3-1 9-2.7"/><circle cx="10.5" cy="17.5" r="1.5" fill="#FC6D26"/><circle cx="21.5" cy="17.5" r="1.5" fill="#FC6D26"/></g><g transform="translate(825 370)"><circle cx="15" cy="16" r="15" fill="#F4F1FA" stroke="#6B4FBB" stroke-width="3"/><path fill="#6B4FBB" d="M25 7h2.7C25 2.8 20.4 0 15 0 9.6 0 5 2.8 2.3 7H5l2.5-3L10 7l2.5-3L15 7l2.5-3L20 7l2.5-3L25 7z"/><circle cx="9.5" cy="17.5" r="1.5" fill="#6B4FBB"/><circle cx="20.5" cy="17.5" r="1.5" fill="#6B4FBB"/></g><g transform="matrix(-1 0 0 1 840 306)"><use fill="#FFF" stroke="#E2DCF2" stroke-width="8" mask="url(#f)" xlink:href="#c"/><rect width="29" height="3" x="24" y="14" fill="#6B4FBB" opacity=".5" rx="1.5"/><rect width="19" height="3" x="34" y="23" fill="#6B4FBB" opacity=".5" rx="1.5"/><rect width="19" height="3" x="34" y="32" fill="#6B4FBB" opacity=".5" rx="1.5"/></g></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/monitoring/getting_started.svg b/app/assets/images/illustrations/monitoring/getting_started.svg
deleted file mode 100644
index ff783bdd388..00000000000
--- a/app/assets/images/illustrations/monitoring/getting_started.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 406 305" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><rect id="a" width="159.8" height="127.81" x=".196" y="5" rx="10"/><rect id="b" width="160" height="128" x=".666" y=".41" rx="10"/><rect id="c" width="160.19" height="128.19" x=".339" y=".59" rx="10"/><mask id="d" width="159.8" height="127.81" x="0" y="0" fill="#fff"><use xlink:href="#a"/></mask><mask id="e" width="160" height="128" x="0" y="0" fill="#fff"><use xlink:href="#b"/></mask><mask id="f" width="160.19" height="128.19" x="0" y="0" fill="#fff"><use xlink:href="#c"/></mask></defs><g fill="none" fill-rule="evenodd" transform="translate(12 3)"><rect width="160" height="128" x="122.08" y="146.08" fill="#f9f9f9" transform="rotate(5 202.071 210.085)" rx="10"/><g transform="rotate(15 -104.714 891.23)"><rect width="159.8" height="127.81" x="1.64" y="10.06" fill="#f9f9f9" rx="8"/><use fill="#fff" stroke="#eee" stroke-width="8" mask="url(#d)" xlink:href="#a"/><path fill="#d2caea" fill-rule="nonzero" d="M96.153 81.151a2.001 2.001 0 0 0 2.184-.496l35.956-38.34a2 2 0 1 0-2.918-2.736l-35.03 37.36-41.888-16.285a2 2 0 0 0-2.16.471l-26.368 27.16a2 2 0 1 0 2.87 2.786l25.444-26.21 41.911 16.294"/><g fill="#fff" transform="translate(24.368 36.951)"><circle cx="5.716" cy="5.104" r="5" stroke="#6b4fbb" stroke-width="4" transform="translate(65.917 34.945)"/><g stroke="#fb722e"><ellipse cx="4.632" cy="50.05" stroke-width="3.2" rx="4" ry="3.999"/><g stroke-width="4"><ellipse cx="29.632" cy="27.05" rx="4" ry="3.999"/><ellipse cx="107.63" cy="4.048" rx="4" ry="3.999"/></g></g></g></g><rect width="160.19" height="128.19" x="36.28" y="86.74" fill="#f9f9f9" transform="rotate(-5 116.372 150.825)" rx="10"/><g transform="rotate(5 -1514.687 1518.752)"><use fill="#fff" stroke="#eee" stroke-width="8" mask="url(#e)" xlink:href="#b"/><path fill="#6b4fbb" stroke="#6b4fbb" stroke-width="3.2" d="M84.67 28.41c18.225 0 33 15.07 33 33.651h-33V28.41" stroke-linecap="round" stroke-linejoin="round"/><path fill="#d2caea" fill-rule="nonzero" d="M78.67 66.41h30a2 2 0 0 1 2 2c0 18.778-15.222 34-34 34s-34-15.222-34-34 15.222-34 34-34a2 2 0 0 1 2 2v30m-32 2c0 16.569 13.431 30 30 30 15.896 0 28.905-12.364 29.934-28H76.67a2 2 0 0 1-2-2V38.476c-15.636 1.029-28 14.04-28 29.934"/></g><g transform="rotate(-5 1023.06 -299.524)"><use fill="#fff" stroke="#eee" stroke-width="8" mask="url(#f)" xlink:href="#c"/><path fill="#fef0ea" d="M42 47.391c0-.768.628-1.391 1.4-1.391h9.2c.773 0 1.4.626 1.4 1.391V97H42V47.391"/><path fill="#fb722e" d="M108 55.406c0-.777.628-1.406 1.4-1.406h9.2a1.4 1.4 0 0 1 1.4 1.406V97h-12V55.406"/><path fill="#6b4fbb" d="M64 35.404c0-.776.628-1.404 1.4-1.404h9.2a1.4 1.4 0 0 1 1.4 1.404v61.6H64v-61.6"/><path fill="#d2caea" d="M86 73.4a1.4 1.4 0 0 1 1.4-1.398h9.2c.773 0 1.4.618 1.4 1.398v23.602H86V73.4"/></g><g fill="#fee8dc"><path d="M3.592 93.86l-2.454-1.562c-.93-.592-.924-1.554 0-2.143l2.454-1.562 1.562-2.454c.592-.93 1.554-.925 2.143 0l1.562 2.454 2.454 1.562c.93.591.924 1.554 0 2.143L8.86 93.86l-1.562 2.454c-.591.93-1.554.924-2.143 0L3.592 93.86M309.489 52.07l-3.14-1.998c-1.12-.713-1.128-1.863 0-2.581l3.14-2 1.999-3.14c.713-1.12 1.863-1.127 2.58 0l2 3.14 3.14 2c1.12.713 1.128 1.863 0 2.58l-3.14 2-2 3.14c-.712 1.12-1.862 1.128-2.58 0l-1.999-3.14"/></g><path fill="#e1dcf1" d="M128.073 11.066l-1.99 3.126c-.718 1.129-1.88 1.131-2.6 0l-1.99-3.126-3.126-1.989c-1.128-.718-1.13-1.88 0-2.6l3.127-1.99 1.989-3.126c.718-1.128 1.88-1.13 2.6 0l1.99 3.126 3.126 1.99c1.128.718 1.13 1.88 0 2.6l-3.126 1.99"/><path fill="#d2caea" d="M378.07 243.068l-1.989 3.126c-.718 1.129-1.88 1.131-2.6 0l-1.99-3.126-3.126-1.989c-1.128-.718-1.13-1.88 0-2.6l3.127-1.99 1.989-3.126c.718-1.128 1.88-1.13 2.6 0l1.99 3.126 3.126 1.99c1.128.718 1.13 1.88 0 2.6l-3.126 1.99"/></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/monitoring/loading.svg b/app/assets/images/illustrations/monitoring/loading.svg
deleted file mode 100644
index 1e196fc8ad1..00000000000
--- a/app/assets/images/illustrations/monitoring/loading.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 406 305" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><rect id="c" width="161" height="100" x="92" y="181" rx="10"/><rect id="d" width="151" height="32" x="20" rx="10"/><rect id="a" width="191" height="62" y="10" rx="10"/><circle id="b" cx="23" cy="41" r="9"/><circle id="k" cx="36.5" cy="36.5" r="36.5"/><circle id="e" cx="262.5" cy="169.5" r="15.5"/><circle id="g" cx="79.5" cy="169.5" r="15.5"/><circle id="j" cx="45" cy="41" r="9"/><circle id="f" cx="30.5" cy="30.5" r="30.5"/><circle id="h" cx="18" cy="34" r="3"/><ellipse id="i" cx="43.5" cy="43.5" rx="43.5" ry="43.5"/><mask id="t" width="191" height="62" x="0" y="0" fill="#fff"><use xlink:href="#a"/></mask><mask id="u" width="18" height="18" x="0" y="0" fill="#fff"><use xlink:href="#b"/></mask><mask id="r" width="161" height="100" x="0" y="0" fill="#fff"><use xlink:href="#c"/></mask><mask id="s" width="151" height="32" x="0" y="0" fill="#fff"><use xlink:href="#d"/></mask><mask id="p" width="31" height="31" x="0" y="0" fill="#fff"><use xlink:href="#e"/></mask><mask id="l" width="61" height="61" x="0" y="0" fill="#fff"><use xlink:href="#f"/></mask><mask id="q" width="31" height="31" x="0" y="0" fill="#fff"><use xlink:href="#g"/></mask><mask id="m" width="6" height="6" x="0" y="0" fill="#fff"><use xlink:href="#h"/></mask><mask id="o" width="87" height="87" x="0" y="0" fill="#fff"><use xlink:href="#i"/></mask><mask id="v" width="18" height="18" x="0" y="0" fill="#fff"><use xlink:href="#j"/></mask><mask id="n" width="73" height="73" x="0" y="0" fill="#fff"><use xlink:href="#k"/></mask></defs><g fill="none" fill-rule="evenodd" transform="translate(28 2)"><g transform="translate(133 87)"><use fill="#fff" stroke="#eee" stroke-width="8" mask="url(#l)" xlink:href="#f"/><path stroke="#d2caea" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" d="M19 32l2-9 5 17 4-12 4 5 6-10 3 5"/><g fill="#fff" stroke="#fb722e"><use stroke-width="4" mask="url(#m)" xlink:href="#h"/><circle cx="44" cy="30" r="2" stroke-width="2"/></g></g><g transform="translate(188 29)"><circle cx="36.5" cy="41.5" r="36.5" fill="#f9f9f9"/><use fill="#fff" stroke="#eee" stroke-width="8" mask="url(#n)" xlink:href="#k"/><rect width="27" height="4" x="23" y="27" fill="#d2caea" rx="2"/><rect width="10.5" height="4" x="23" y="27" fill="#6b4fbb" rx="2"/><rect width="27" height="4" x="23" y="36" fill="#d2caea" rx="2"/><rect width="19" height="4" x="23" y="36" fill="#6b4fbb" rx="2"/><rect width="27" height="4" x="23" y="45" fill="#d2caea" rx="2"/><rect width="7" height="4" x="23" y="45" fill="#6b4fbb" rx="2"/></g><path fill="#eee" fill-rule="nonzero" d="M247 292v1c0 5.519-4.469 9.993-10.01 9.993H111c-5.177 0-9.436-3.927-9.954-8.96a9.96 9.96 0 0 0 4.705 1.883 6.008 6.008 0 0 0 5.248 3.077h125.99a6 6 0 0 0 5.526-3.637 10.027 10.027 0 0 0 4.48-3.359m1.947-8.962a10.001 10.001 0 0 1-9.95 8.958h-131.99a10 10 0 0 1-9.851-8.25 9.942 9.942 0 0 0 4.649 1.248 6 6 0 0 0 5.202 3h131.99a6.002 6.002 0 0 0 5.245-3.076 9.943 9.943 0 0 0 4.705-1.882"/><g transform="translate(79)"><ellipse cx="43.5" cy="47.5" fill="#f9f9f9" rx="43.5" ry="43.5"/><g fill="#fff"><g stroke="#eee"><use stroke-width="8" mask="url(#o)" xlink:href="#i"/><path stroke-width="4" d="M18.595 49C21.11 60.44 31.305 69 43.5 69 57.58 69 69 57.583 69 43.5c0-12.195-8.56-22.391-20-24.905v15.959c3 1.848 5 5.164 5 8.946C54 49.299 49.299 54 43.5 54c-3.782 0-7.098-2-8.946-5H18.595" stroke-linejoin="round"/></g><path stroke="#d2caea" stroke-width="4" d="M18 44a27.69 27.69 0 0 1-.005-.5c0-14.08 11.417-25.5 25.5-25.5.167 0 .334.002.5.005v15.01a10.365 10.365 0 0 0-.5-.012c-5.799 0-10.5 4.701-10.5 10.5 0 .168.004.334.012.5h-15.01" stroke-linejoin="round"/></g></g><g fill="#fff" stroke="#eee" stroke-width="8"><use mask="url(#p)" xlink:href="#e"/><use mask="url(#q)" xlink:href="#g"/><use mask="url(#r)" xlink:href="#c"/></g><g fill="#eee"><rect width="15" height="2" x="226" y="247" rx="1"/><rect width="15" height="2" x="226" y="242" rx="1"/><rect width="15" height="2" x="226" y="252" rx="1"/></g><rect width="10" height="52" x="118" y="196" fill="#d2caea" rx="2"/><rect width="10" height="47" x="154" y="196" fill="#6b4fbb" rx="2"/><rect width="10" height="37" x="190" y="196" fill="#d2caea" rx="2"/><g fill="#fee8dc"><rect width="10" height="52" x="132" y="185" rx="2"/><rect width="10" height="38" x="168" y="185" rx="2"/></g><rect width="10" height="58" x="204" y="185" fill="#fb722e" rx="2"/><g fill="#fff" stroke="#eee" stroke-width="8" transform="translate(76 128)"><use mask="url(#s)" xlink:href="#d"/><use mask="url(#t)" xlink:href="#a"/></g><g fill="#d2caea" transform="translate(76 128)"><rect width="16" height="4" x="156" y="35" rx="2"/><rect width="16" height="4" x="156" y="43" rx="2"/></g><g fill="#fff" stroke-width="8" transform="translate(76 128)"><use stroke="#fee8dc" mask="url(#u)" xlink:href="#b"/><use stroke="#fb722e" mask="url(#v)" xlink:href="#j"/></g><g fill="#fb722e"><path d="M3.597 219.858l-2.455-1.562c-.929-.59-.924-1.553 0-2.142l2.455-1.562 1.562-2.455c.59-.929 1.553-.924 2.142 0l1.562 2.455 2.454 1.562c.93.591.925 1.553 0 2.142l-2.454 1.562-1.562 2.455c-.591.929-1.553.924-2.142 0l-1.562-2.455M253.597 8.859l-2.454-1.562c-.93-.592-.925-1.554 0-2.143l2.454-1.562 1.562-2.454c.591-.93 1.554-.925 2.143 0l1.562 2.454 2.454 1.562c.93.591.924 1.554 0 2.143l-2.454 1.562-1.562 2.454c-.592.93-1.554.924-2.143 0l-1.562-2.454" opacity=".2"/></g><path fill="#fee8dc" d="M309.49 149.07l-3.141-1.999c-1.12-.712-1.128-1.863 0-2.58l3.14-2 2-3.14c.712-1.12 1.863-1.128 2.58 0l2 3.14 3.14 2c1.12.712 1.127 1.863 0 2.58l-3.14 2-2 3.14c-.713 1.12-1.863 1.128-2.58 0l-2-3.14"/><path fill="#6b4fbb" d="M47.068 79.067l-1.99 3.126c-.718 1.129-1.88 1.13-2.6 0l-1.99-3.126-3.125-1.99c-1.129-.718-1.131-1.88 0-2.6l3.126-1.989 1.989-3.126c.718-1.129 1.88-1.13 2.6 0l1.99 3.126 3.126 1.99c1.128.718 1.13 1.88 0 2.6l-3.126 1.989" opacity=".2"/></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/monitoring/unable_to_connect.svg b/app/assets/images/illustrations/monitoring/unable_to_connect.svg
deleted file mode 100644
index 314c052f931..00000000000
--- a/app/assets/images/illustrations/monitoring/unable_to_connect.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 406 305" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><use id="g" xlink:href="#a"/><use id="f" xlink:href="#a"/><use id="h" xlink:href="#a"/><path id="e" d="M74 93h26v47H74z"/><path id="c" d="M74 93h26v47H74z"/><rect id="b" width="65" height="14" x="55" y="135" rx="4"/><rect id="d" width="175" height="118" rx="10"/><rect id="a" width="159" rx="10" height="56"/><rect id="i" width="160" y="2" rx="10" height="56" fill="#f9f9f9"/><mask id="q" width="65" height="14" x="0" y="0" fill="#fff"><use xlink:href="#b"/></mask><mask id="p" width="26" height="47" x="0" y="0" fill="#fff"><use xlink:href="#c"/></mask><mask id="r" width="175" height="118" x="0" y="0" fill="#fff"><use xlink:href="#d"/></mask><mask id="o" width="26" height="47" x="0" y="0" fill="#fff"><use xlink:href="#e"/></mask><mask id="k" width="159" height="56" x="0" y="0" fill="#fff"><use xlink:href="#f"/></mask><mask id="j" width="159" height="56" x="0" y="0" fill="#fff"><use xlink:href="#g"/></mask><mask id="l" width="159" height="56" x="0" y="0" fill="#fff"><use xlink:href="#h"/></mask></defs><g fill="none" fill-rule="evenodd"><g transform="translate(245 65)"><use xlink:href="#i"/><use fill="#fff" stroke="#eee" stroke-width="8" mask="url(#j)" xlink:href="#g"/><g fill-rule="nonzero"><path fill="#fb722e" d="M134 31a2 2 0 1 0 .001-3.999A2 2 0 0 0 134 31m0 4a6 6 0 1 1 0-12 6 6 0 0 1 0 12"/><path fill="#fee8dc" d="M117 31a2 2 0 1 0 .001-3.999A2 2 0 0 0 117 31m0 4a6 6 0 1 1 0-12 6 6 0 0 1 0 12m-17-4a2 2 0 1 0 .001-3.999A2 2 0 0 0 100 31m0 4a6 6 0 1 1 0-12 6 6 0 0 1 0 12"/></g><g fill="#d2caea"><rect width="50" height="4" x="19" y="20" rx="2"/><rect width="50" height="4" x="19" y="34" rx="2"/></g><g transform="translate(0 59)"><use xlink:href="#i"/><use fill="#fff" stroke="#eee" stroke-width="8" mask="url(#k)" xlink:href="#f"/><g fill-rule="nonzero"><path fill="#fee8dc" d="M134 30a2 2 0 1 0 .001-3.999A2 2 0 0 0 134 30m0 4a6 6 0 1 1 0-12 6 6 0 0 1 0 12"/><path fill="#fb722e" d="M117 30a2 2 0 1 0 .001-3.999A2 2 0 0 0 117 30m0 4a6 6 0 1 1 0-12 6 6 0 0 1 0 12"/><path fill="#fee8dc" d="M100 30a2 2 0 1 0 .001-3.999A2 2 0 0 0 100 30m0 4a6 6 0 1 1 0-12 6 6 0 0 1 0 12"/></g><rect width="50" height="4" x="19" y="19" fill="#d2caea" rx="2" id="m"/><rect width="50" height="4" x="19" y="33" fill="#d2caea" rx="2" id="n"/></g><g transform="translate(0 118)"><use xlink:href="#i"/><use fill="#fff" stroke="#eee" stroke-width="8" mask="url(#l)" xlink:href="#h"/><g fill-rule="nonzero"><path fill="#fb722e" d="M134 30a2 2 0 1 0 .001-3.999A2 2 0 0 0 134 30m0 4a6 6 0 1 1 0-12 6 6 0 0 1 0 12"/><path fill="#fee8dc" d="M117 30a2 2 0 1 0 .001-3.999A2 2 0 0 0 117 30m0 4a6 6 0 1 1 0-12 6 6 0 0 1 0 12m-17-4a2 2 0 1 0 .001-3.999A2 2 0 0 0 100 30m0 4a6 6 0 1 1 0-12 6 6 0 0 1 0 12"/></g><use xlink:href="#m"/><use xlink:href="#n"/></g></g><g fill="#eee" transform="translate(164 120)"><rect width="29" height="4" y="29" rx="2"/><rect width="28" height="4" x="55" y="29" rx="2"/></g><g transform="translate(180 120)"><circle cx="30" cy="30" r="24" fill="#fef0ea"/><g fill="#fb722e"><circle cx="30.5" cy="30.5" r="30.5" opacity=".1"/><circle cx="30.5" cy="30.5" r="19.5" opacity=".1"/></g><circle cx="30.5" cy="30.5" r="13.5" fill="#fff"/><path fill="#fb722e" d="M32.621 30.5l2.481-2.481a1.492 1.492 0 0 0-.006-2.115 1.491 1.491 0 0 0-2.115-.006L30.5 28.379l-2.481-2.481a1.492 1.492 0 0 0-2.115.006 1.491 1.491 0 0 0-.006 2.115l2.481 2.481-2.481 2.481a1.492 1.492 0 0 0 .006 2.115c.59.59 1.533.589 2.115.006l2.481-2.481 2.481 2.481c.586.586 1.529.58 2.115-.006.59-.59.589-1.533.006-2.115L32.621 30.5"/></g><g transform="translate(1 78)"><rect width="65" height="14" x="55" y="137" fill="#f9f9f9" rx="4"/><use fill="#fff" stroke="#eee" stroke-width="8" mask="url(#o)" xlink:href="#e"/><rect width="175" height="118" y="3" fill="#f9f9f9" rx="10"/><g fill="#fff" stroke="#eee" stroke-width="8"><use mask="url(#p)" xlink:href="#c"/><use mask="url(#q)" xlink:href="#b"/><use mask="url(#r)" xlink:href="#d"/></g><g fill-rule="nonzero"><path fill="#eee" d="M163 105V12H11v93h152M7 11.99A3.998 3.998 0 0 1 10.995 8h152.01A3.999 3.999 0 0 1 167 11.99v93.02a3.998 3.998 0 0 1-3.995 3.99H10.995A3.999 3.999 0 0 1 7 105.01V11.99"/><path fill="#d2caea" d="M86 92c-11.598 0-21-9.402-21-21s9.402-21 21-21 21 9.402 21 21-9.402 21-21 21m0-4c9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17 7.611 17 17 17"/></g><path fill="#6b4fbb" d="M83 63a3.001 3.001 0 0 1 6 0v7.993a3.001 3.001 0 0 1-6 0V63m3 18.997a3 3 0 1 1 0-6 3 3 0 0 1 0 6"/><g fill="#eee"><rect width="134" height="4" x="20" y="30" rx="2"/><rect width="14" height="4" x="20" y="20" rx="2"/><circle cx="87" cy="21" r="5"/></g></g></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/multi-editor_all_changes_committed_empty.svg b/app/assets/images/illustrations/multi-editor_all_changes_committed_empty.svg
deleted file mode 100644
index 06d73941c33..00000000000
--- a/app/assets/images/illustrations/multi-editor_all_changes_committed_empty.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="80" height="80" viewBox="0 0 80 80"><g fill="none" fill-rule="evenodd"><path fill="#EEE" d="M44.242 59.348c-3.7 1.576-7.3 1.994-10.902.84a7.002 7.002 0 0 1-9.085-.699l-4.243-4.243a7 7 0 0 1-.238-9.649c-.701-3.024-.419-6.083.646-9.206l-6.287-2.426a5.6 5.6 0 0 1-2.274-8.824l8.233-9.811a5.6 5.6 0 0 1 6.306-1.625l8.045 3.105c.772-.797 1.564-1.6 2.374-2.41C44.841 6.376 55.265 2.135 68.09 1.677a10 10 0 0 1 1.119.023c5.507.42 9.63 5.226 9.209 10.733-.935 12.225-5.373 22.309-13.315 30.25a410.76 410.76 0 0 1-1.661 1.653l3.247 8.412a5.6 5.6 0 0 1-1.625 6.306l-9.81 8.233a5.6 5.6 0 0 1-8.825-2.274l-2.186-5.665zm-22.92-26.923l10.406-12.402-6.822-2.633a1.6 1.6 0 0 0-1.801.464l-8.233 9.811a1.6 1.6 0 0 0 .65 2.521l5.8 2.239zm26.646 25.4l2.239 5.8a1.6 1.6 0 0 0 2.521.649l9.81-8.232a1.6 1.6 0 0 0 .465-1.802l-2.633-6.822-12.402 10.406zm-19.69-5.627c8.751 8.752 16.065 5.587 33.995-12.343 7.25-7.25 11.292-16.433 12.155-27.727a6 6 0 0 0-6.196-6.454c-11.846.423-21.303 4.271-28.586 11.554-17.03 17.03-20.414 25.924-11.368 34.97z"/><path fill="#FDC4A8" fill-rule="nonzero" d="M52.54 28.376a4 4 0 1 0 5.656-5.657 4 4 0 0 0-5.657 5.657zm-2.83 2.829A8 8 0 1 1 61.025 19.89a8 8 0 0 1-11.313 11.314z"/><path fill="#FEE1D3" d="M15.063 54.54a2 2 0 0 1 0 2.828L3.749 68.68A2 2 0 1 1 .92 65.853l11.314-11.314a2 2 0 0 1 2.829 0zm9.899 9.899a2 2 0 0 1 0 2.828l-8.485 8.485a2 2 0 1 1-2.829-2.828l8.486-8.485a2 2 0 0 1 2.828 0z"/><path fill="#FDC4A8" d="M20.012 59.489a2 2 0 0 1 0 2.828L4.456 77.874a2 2 0 0 1-2.829-2.829L17.184 59.49a2 2 0 0 1 2.828 0z"/></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/multi-editor_no_changes_empty.svg b/app/assets/images/illustrations/multi-editor_no_changes_empty.svg
deleted file mode 100644
index ebeea1f3dd9..00000000000
--- a/app/assets/images/illustrations/multi-editor_no_changes_empty.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="80" height="80" viewBox="0 0 80 80"><g fill="none" fill-rule="evenodd" transform="translate(7 3)"><path fill="#EEE" fill-rule="nonzero" d="M54 18a2 2 0 1 1 0-4h4c.843 0 1.675.105 2.48.31a2 2 0 1 1-.99 3.876A6.015 6.015 0 0 0 58 18h-4zm9.735 4.228a2 2 0 0 1 3.822-1.18A10 10 0 0 1 68 24v3.513a2 2 0 1 1-4 0V24c0-.61-.09-1.204-.265-1.772zM64 35.513a2 2 0 1 1 4 0v6a2 2 0 1 1-4 0v-6zm0 14a2 2 0 1 1 4 0v6a2 2 0 1 1-4 0v-6zm0 14a2 2 0 1 1 4 0V66a9.97 9.97 0 0 1-.963 4.286 2 2 0 1 1-3.613-1.716A5.969 5.969 0 0 0 64 66v-2.487zm-5.255 8.441a2 2 0 1 1 .49 3.97c-.401.05-.806.075-1.218.076h-5.042a2 2 0 1 1 0-4h5.038c.246 0 .49-.016.732-.046zM44.975 72a2 2 0 1 1 0 4h-6a2 2 0 1 1 0-4h6zm-14 0a2 2 0 1 1 0 4H26c-.429 0-.855-.027-1.276-.08a2 2 0 0 1 .506-3.969c.254.033.51.049.77.049h4.975zm-10.438-3.514a2 2 0 1 1-3.64 1.66A9.97 9.97 0 0 1 16 66v-2.538a2 2 0 1 1 4 0V66c0 .871.185 1.713.537 2.486zM8 2a6 6 0 0 0-6 6v42a6 6 0 0 0 6 6h32a6 6 0 0 0 6-6V8a6 6 0 0 0-6-6H8zm0-4h32c5.523 0 10 4.477 10 10v42c0 5.523-4.477 10-10 10H8C2.477 60-2 55.523-2 50V8C-2 2.477 2.477-2 8-2z"/><rect width="10" height="4" x="8" y="16" fill="#EFEDF8" rx="2"/><rect width="10" height="4" x="21" y="16" fill="#6B4FBB" rx="2"/><rect width="10" height="4" x="8" y="32" fill="#E1DBF1" rx="2"/><rect width="6" height="4" x="34" y="16" fill="#EFEDF8" rx="2"/><rect width="6" height="4" x="8" y="24" fill="#6B4FBB" rx="2"/><rect width="6" height="4" x="17" y="24" fill="#EFEDF8" rx="2"/><rect width="6" height="4" x="21" y="32" fill="#6B4FBB" rx="2"/><rect width="6" height="4" x="8" y="40" fill="#6B4FBB" rx="2"/><rect width="6" height="4" x="17" y="40" fill="#EFEDF8" rx="2"/><rect width="6" height="4" x="26" y="40" fill="#C3B8E3" rx="2"/><rect width="10" height="4" x="26" y="24" fill="#C3B8E3" rx="2"/></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/multi-editor_no_staged_files_empty.svg b/app/assets/images/illustrations/multi-editor_no_staged_files_empty.svg
deleted file mode 100644
index 08321ef526b..00000000000
--- a/app/assets/images/illustrations/multi-editor_no_staged_files_empty.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="80" height="80" viewBox="0 0 80 80"><g fill="none" fill-rule="evenodd" transform="translate(0 3)"><path fill="#EEE" fill-rule="nonzero" d="M40.843 5.864a2 2 0 1 1 .348-3.985l5.977.523a2 2 0 1 1-.348 3.985l-5.977-.523zm13.946 1.22a2 2 0 1 1 .349-3.985l5.977.523a2 2 0 1 1-.348 3.985l-5.978-.523zm13.947 1.22a2 2 0 1 1 .349-3.984 11.952 11.952 0 0 1 6.655 2.75 2 2 0 1 1-2.569 3.066 7.953 7.953 0 0 0-4.435-1.832zm7.28 7.357a2 2 0 1 1 3.99-.301c.048.639.045 1.283-.01 1.934l-.385 4.4a2 2 0 1 1-3.985-.349l.384-4.395c.037-.433.039-.863.007-1.29zm-1.088 13.654a2 2 0 0 1 3.985.348l-.523 5.978a2 2 0 1 1-3.984-.349l.522-5.977zm-1.22 13.947a2 2 0 1 1 3.985.348l-.523 5.977a2 2 0 1 1-3.985-.348l.523-5.977zM72.305 56.7a2 2 0 0 1 3.79 1.282 11.995 11.995 0 0 1-4.253 5.81 2 2 0 0 1-2.373-3.22 7.996 7.996 0 0 0 2.836-3.872zm-9.054 5.33a2 2 0 1 1-.349 3.985l-5.977-.522a2 2 0 1 1 .349-3.985l5.977.523zM32.793 10.675a2 2 0 1 1-3.675-1.579 12.02 12.02 0 0 1 4.696-5.456 2 2 0 0 1 2.112 3.397 8.02 8.02 0 0 0-3.133 3.638z"/><rect width="48" height="58" x="2" y="14" fill="#FAFAFA" rx="10"/><path fill="#EEE" fill-rule="nonzero" d="M12 16a8 8 0 0 0-8 8v38a8 8 0 0 0 8 8h28a8 8 0 0 0 8-8V24a8 8 0 0 0-8-8H12zm0-4h28c6.627 0 12 5.373 12 12v38c0 6.627-5.373 12-12 12H12C5.373 74 0 68.627 0 62V24c0-6.627 5.373-12 12-12z"/><rect width="24" height="4" x="11" y="30" fill="#E5E5E5" rx="2"/><rect width="30" height="4" x="11" y="41" fill="#E5E5E5" rx="2"/><rect width="20" height="4" x="11" y="52" fill="#E5E5E5" rx="2"/></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/multi_file_editor_empty.svg b/app/assets/images/illustrations/multi_file_editor_empty.svg
deleted file mode 100644
index bd376f0a050..00000000000
--- a/app/assets/images/illustrations/multi_file_editor_empty.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="430" height="300"><g fill="none" fill-rule="evenodd" transform="translate(35 29)"><path fill="#EEE" fill-rule="nonzero" d="M90 23a2 2 0 1 1 0-4h10a2 2 0 0 1 0 4H90zm20 0a2 2 0 0 1 0-4h10a2 2 0 0 1 0 4h-10zm20 0a2 2 0 0 1 0-4h10a2 2 0 0 1 0 4h-10zm20 0a2 2 0 0 1 0-4h10a2 2 0 0 1 0 4h-10zm20 0a2 2 0 0 1 0-4h10a2 2 0 0 1 0 4h-10zm20 0a2 2 0 0 1 0-4h10a2 2 0 0 1 0 4h-10zm20 0a2 2 0 0 1 0-4h10a2 2 0 0 1 0 4h-10zm20 0a2 2 0 0 1 0-4h10a2 2 0 0 1 0 4h-10zm20 0a2 2 0 0 1 0-4h10a2 2 0 0 1 0 4h-10zm20 0a2 2 0 0 1 0-4h10a2 2 0 0 1 0 4h-10zm20 0a2 2 0 0 1 0-4h10a2 2 0 0 1 0 4h-10zm20 0a2 2 0 0 1 0-4h10a2 2 0 0 1 0 4h-10zm20 0a2 2 0 0 1 0-4h1a11.98 11.98 0 0 1 9.457 4.612 2 2 0 0 1-3.151 2.464A7.981 7.981 0 0 0 331 23h-1zm9 11.39a2 2 0 0 1 4 0v10a2 2 0 0 1-4 0v-10zm0 180a2 2 0 1 1 4 0V223c0 .56-.038 1.114-.114 1.662a2 2 0 0 1-3.962-.55A8.21 8.21 0 0 0 339 223v-8.61zm-4.769 15.931a2 2 0 0 1 1.618 3.658A11.967 11.967 0 0 1 331 235h-5.782a2 2 0 0 1 0-4H331c1.13 0 2.224-.233 3.231-.679zm-19.013.679a2 2 0 1 1 0 4h-10a2 2 0 0 1 0-4h10zm-20 0a2 2 0 1 1 0 4h-10a2 2 0 0 1 0-4h10zm-20 0a2 2 0 1 1 0 4h-10a2 2 0 0 1 0-4h10zm-20 0a2 2 0 1 1 0 4h-10a2 2 0 0 1 0-4h10zm-20 0a2 2 0 1 1 0 4h-10a2 2 0 0 1 0-4h10zm-20 0a2 2 0 1 1 0 4h-10a2 2 0 0 1 0-4h10zm-20 0a2 2 0 1 1 0 4h-10a2 2 0 0 1 0-4h10zm-20 0a2 2 0 1 1 0 4h-10a2 2 0 0 1 0-4h10zm-20 0a2 2 0 1 1 0 4h-10a2 2 0 0 1 0-4h10zm-20 0a2 2 0 1 1 0 4h-10a2 2 0 0 1 0-4h10zM115 231a2 2 0 0 1 0 4h-10a2 2 0 0 1 0-4h10zm-26.2 4c.131-.646.2-1.315.2-2v-2h4a2 2 0 0 1 0 4h-4.2z"/><path fill="#EEE" fill-rule="nonzero" d="M103 211h258a6 6 0 0 0 6-6V63a6 6 0 0 0-6-6H166a5 5 0 0 1-5-5v-8.5a5.5 5.5 0 0 0-5.5-5.5H109a6 6 0 0 0-6 6v167zm62-167.5V52a1 1 0 0 0 1 1h195c5.523 0 10 4.477 10 10v142c0 5.523-4.477 10-10 10H99V44c0-5.523 4.477-10 10-10h46.5a9.5 9.5 0 0 1 9.5 9.5z"/><rect width="40" height="4" x="118" y="78" fill="#6B4FBB" rx="2"/><rect width="30" height="4" x="118" y="90" fill="#EFEDF8" rx="2"/><rect width="30" height="4" x="153" y="90" fill="#E1DBF1" rx="2"/><rect width="150" height="4" x="118" y="102" fill="#EFEDF8" rx="2"/><rect width="90" height="4" x="118" y="114" fill="#E1DBF1" rx="2"/><rect width="60" height="4" x="118" y="138" fill="#EFEDF8" rx="2"/><rect width="20" height="4" x="118" y="150" fill="#6B4FBB" rx="2"/><rect width="20" height="4" x="144" y="150" fill="#C3B8E3" rx="2"/><rect width="20" height="4" x="170" y="150" fill="#E1DBF1" rx="2"/><rect width="130" height="4" x="118" y="162" fill="#EFEDF8" rx="2"/><rect width="30" height="4" x="118" y="174" fill="#C3B8E3" rx="2"/><rect width="30" height="4" x="154" y="174" fill="#EFEDF8" rx="2"/><rect width="30" height="4" x="190" y="174" fill="#EFEDF8" rx="2"/><rect width="40" height="4" x="118" y="186" fill="#E1DBF1" rx="2"/><path fill="#F9F9F9" d="M89 24.292l11.434 19.326v170.326L89 226.336V24.292z"/><path fill="#EEE" fill-rule="nonzero" d="M89 229.286v-5.9l9.434-10.223V44.165L89 28.22v-7.856l13.434 22.707v171.655L89 229.286zM10 4a6 6 0 0 0-6 6v223a6 6 0 0 0 6 6h69a6 6 0 0 0 6-6V10a6 6 0 0 0-6-6H10zm0-4h69c5.523 0 10 4.477 10 10v223c0 5.523-4.477 10-10 10H10c-5.523 0-10-4.477-10-10V10C0 4.477 4.477 0 10 0z"/><circle cx="25" cy="23" r="11" fill="#FEF0E8"/><path fill="#FEE1D3" d="M46 17h16a2 2 0 1 1 0 4H46a2 2 0 1 1 0-4zm0 8h27a2 2 0 1 1 0 4H46a2 2 0 1 1 0-4z"/><path fill="#EEE" d="M16 50h4a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2zm14 2h24a2 2 0 1 1 0 4H30a2 2 0 1 1 0-4zm-4 12h4a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2zm14 2h24a2 2 0 1 1 0 4H40a2 2 0 1 1 0-4zM26 78h4a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2zm14 2h24a2 2 0 1 1 0 4H40a2 2 0 1 1 0-4z"/><g transform="translate(14 110)"><rect width="8" height="8" fill="#FEE1D3" rx="2"/><rect width="28" height="4" x="14" y="2" fill="#FEF0E8" rx="2"/></g><path fill="#EEE" d="M16 140h4a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2zm14 2h24a2 2 0 1 1 0 4H30a2 2 0 1 1 0-4zm-14 14h4a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2zm14 2h24a2 2 0 1 1 0 4H30a2 2 0 1 1 0-4zm-14 14h4a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2zm14 2h24a2 2 0 1 1 0 4H30a2 2 0 1 1 0-4zm-14 14h4a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2zm14 2h24a2 2 0 1 1 0 4H30a2 2 0 1 1 0-4z"/><g transform="translate(24 124)"><rect width="8" height="8" fill="#FEE1D3" rx="2"/><rect width="28" height="4" x="14" y="2" fill="#FEF0E8" rx="2"/></g><g fill="#FC6D26" transform="translate(24 92)"><rect width="8" height="8" rx="2"/><rect width="28" height="4" x="14" y="2" rx="2"/></g><path fill="#FDC4A8" fill-rule="nonzero" d="M152 50.5a4.5 4.5 0 1 1 0-9 4.5 4.5 0 0 1 0 9zm0-3a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z"/></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/no_commits.svg b/app/assets/images/illustrations/no_commits.svg
deleted file mode 100644
index 76fa25156dd..00000000000
--- a/app/assets/images/illustrations/no_commits.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 168 107" xmlns:xlink="http://www.w3.org/1999/xlink"><g fill="#eee" fill-rule="evenodd"><path d="M4.01 2h1.102a1 1 0 0 0 0-2H4.01A4.001 4.001 0 0 0 0 4a1 1 0 0 0 2 0c0-1.108.892-2 2.01-2m12.702 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7m11.6 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7m11.6 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7m11.6 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7m11.6 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7m11.6 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7m11.6 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7m11.6 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7m11.6 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7m11.6 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7m11.6 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7m11.6 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7m11.6 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7M164 2c.822 0 1.554.503 1.86 1.254a1 1 0 1 0 1.853-.753 4.01 4.01 0 0 0-3.712-2.5h-2.188a1 1 0 0 0 0 2h2.188m2.01 12.518a1 1 0 0 0 2 0v-5.7a1 1 0 0 0-2 0v5.7m0 11.6a1 1 0 0 0 2 0v-5.7a1 1 0 0 0-2 0v5.7m0 11.6a1 1 0 0 0 2 0v-5.7a1 1 0 0 0-2 0v5.7m0 6.282c0 1.108-.892 2-2.01 2h-.72a1 1 0 0 0 0 2h.72a4.001 4.001 0 0 0 4.01-4v-.382a1 1 0 0 0-2 0v.382m-14.325 2a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-11.6 0a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-11.6 0a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-11.6 0a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-11.6 0a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-11.6 0a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-11.6 0a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-11.6 0a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-11.6 0a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-11.6 0a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-11.6 0a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-11.6 0a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-11.6 0a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-8.47 0a2.01 2.01 0 0 1-1.782-1.085 1 1 0 0 0-1.775.923 4.007 4.007 0 0 0 3.556 2.162h2.57a1 1 0 0 0 0-2h-2.57m-2.01-12.136a1 1 0 0 0-2 0v5.7a1 1 0 0 0 2 0v-5.7m0-11.6a1 1 0 0 0-2 0v5.7a1 1 0 0 0 2 0v-5.7m0-11.6a1 1 0 0 0-2 0v5.7a1 1 0 0 0 2 0v-5.7m0-6.664a1 1 0 0 0-2 0v.764a1 1 0 0 0 2 0v-.764" id="a"/><circle cx="21" cy="24" r="10"/><rect width="33" height="3" x="37" y="18" rx="1.5" id="b"/><rect width="53" height="3" x="37" y="27" rx="1.5" id="c"/><path d="M131 29c0 .552.447.999.996.999h22.01c.545 0 .996-.451.996-.999v-9a.998.998 0 0 0-.996-.999h-22.01c-.545 0-.996.451-.996.999v9m.996-12h22.01a2.998 2.998 0 0 1 2.996 2.999v9a3.003 3.003 0 0 1-2.996 2.999h-22.01A2.998 2.998 0 0 1 129 28.999v-9A3.003 3.003 0 0 1 131.996 17" id="d"/><g transform="translate(0 59)"><use xlink:href="#a"/><circle cx="21" cy="24" r="10"/><use xlink:href="#b"/><use xlink:href="#c"/><use xlink:href="#d"/></g></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/pending_job_empty.svg b/app/assets/images/illustrations/pending_job_empty.svg
deleted file mode 100644
index 8de695afa18..00000000000
--- a/app/assets/images/illustrations/pending_job_empty.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="430" height="200" viewBox="0 0 430 200"><g fill="none" fill-rule="evenodd"><g transform="translate(138 65)"><path fill="#E5E5E5" fill-rule="nonzero" d="M35 70a2 2 0 1 1 0-4c2.542 0 5.042-.305 7.463-.904a2 2 0 1 1 .96 3.884A35.075 35.075 0 0 1 35 70zm18.21-5.105a2 2 0 1 1-2.083-3.414 31.143 31.143 0 0 0 5.896-4.664 2 2 0 1 1 2.842 2.815 35.143 35.143 0 0 1-6.654 5.263zM66.106 51.06a2 2 0 0 1-3.552-1.838 30.77 30.77 0 0 0 2.612-7.042 2 2 0 1 1 3.892.922 34.77 34.77 0 0 1-2.952 7.958zm3.816-18.433a2 2 0 1 1-3.991.268 30.873 30.873 0 0 0-1.407-7.38 2 2 0 0 1 3.808-1.223 34.873 34.873 0 0 1 1.59 8.335zm-6.346-17.842a2 2 0 0 1-3.264 2.312 31.188 31.188 0 0 0-5.054-5.564 2 2 0 0 1 2.615-3.027 35.188 35.188 0 0 1 5.703 6.279zM48.895 2.867a2 2 0 0 1-1.59 3.67 30.758 30.758 0 0 0-7.206-2.12 2 2 0 1 1 .653-3.946 34.758 34.758 0 0 1 8.143 2.396zM30.263.318a2 2 0 0 1 .537 3.964c-2.505.339-4.94.98-7.266 1.907a2 2 0 1 1-1.48-3.716A34.774 34.774 0 0 1 30.263.318zM12.907 7.853a2 2 0 0 1 2.527 3.1 31.196 31.196 0 0 0-5.213 5.416 2 2 0 0 1-3.196-2.406 35.196 35.196 0 0 1 5.882-6.11zM1.99 23.343a2 2 0 0 1 3.772 1.331 30.82 30.82 0 0 0-1.619 7.337 2 2 0 1 1-3.982-.38 34.82 34.82 0 0 1 1.829-8.289zM.719 42.086a2 2 0 1 1 3.917-.806 30.757 30.757 0 0 0 2.4 7.118 2 2 0 1 1-3.605 1.73 34.757 34.757 0 0 1-2.713-8.042zM9.393 58.86a2 2 0 0 1 2.926-2.728 31.167 31.167 0 0 0 5.751 4.841 2 2 0 1 1-2.187 3.349 35.167 35.167 0 0 1-6.49-5.462zm16.245 9.873a2 2 0 1 1 1.067-3.855 30.979 30.979 0 0 0 7.434 1.11 2 2 0 1 1-.11 3.998 34.979 34.979 0 0 1-8.391-1.253z"/><circle cx="35" cy="35" r="16" stroke="#E1DBF1" stroke-width="4"/><path fill="#6B4FBB" d="M37 33h5a2 2 0 1 1 0 4h-7a2 2 0 0 1-2-2v-8a2 2 0 1 1 4 0v6z"/></g><g transform="translate(247 30)"><rect width="116" height="135" y="5" fill="#F9F9F9" rx="10"/><rect width="116" height="134" x="5" fill="#FFF" stroke="#EEE" stroke-width="4" stroke-linecap="round" rx="10"/><g transform="translate(23 23)"><rect width="16" height="4" fill="#E1DBF1" rx="2"/><rect width="16" height="4" x="32" y="12" fill="#E1DBF1" rx="2"/><rect width="16" height="4" x="44" fill="#EEE" rx="2"/><rect width="16" height="4" x="12" y="24" fill="#E1DBF1" rx="2"/><rect width="16" height="4" x="64" y="36" fill="#FEF0E8" rx="2"/><rect width="8" height="4" x="20" fill="#FEE1D3" rx="2"/><rect width="8" height="4" x="32" y="36" fill="#FC6D26" rx="2"/><rect width="8" height="4" x="52" y="12" fill="#FEF0E8" rx="2"/><rect width="8" height="4" x="64" fill="#FEF0E8" rx="2"/><rect width="12" height="4" x="16" y="48" fill="#E1DBF1" rx="2"/><rect width="8" height="4" x="44" y="36" fill="#FC6D26" rx="2"/><rect width="4" height="4" x="56" y="36" fill="#E1DBF1" rx="2"/><rect width="4" height="4" x="64" y="60" fill="#E1DBF1" rx="2"/><rect width="4" height="4" x="72" y="60" fill="#FC6D26" rx="2"/><rect width="8" height="4" x="32" fill="#FC6D26" rx="2"/><rect width="28" height="4" y="36" fill="#EEE" rx="2"/><rect width="28" height="4" x="44" y="48" fill="#EEE" rx="2"/><rect width="28" height="4" x="32" y="60" fill="#EFEDF8" rx="2"/><rect width="28" height="4" y="12" fill="#6B4FBB" rx="2"/><rect width="28" height="4" x="32" y="24" fill="#C3B8E3" rx="2"/><rect width="8" height="4" y="24" fill="#FEF0E8" rx="2"/><rect width="8" height="4" x="32" y="48" fill="#6B4FBB" rx="2"/><rect width="12" height="4" y="48" fill="#FC6D26" rx="2"/><rect width="12" height="4" y="60" fill="#FEF0E8" rx="2"/><rect width="12" height="4" x="16" y="60" fill="#FEF0E8" rx="2"/></g><g transform="translate(23 95)"><rect width="16" height="4" fill="#EFEDF8" rx="2"/><rect width="16" height="4" x="18" y="12" fill="#FC6D26" rx="2"/><rect width="16" height="4" x="44" fill="#6B4FBB" rx="2"/><rect width="8" height="4" x="20" fill="#FEE1D3" rx="2"/><rect width="8" height="4" x="38" y="12" fill="#FEF0E8" rx="2"/><rect width="8" height="4" x="64" fill="#FEF0E8" rx="2"/><rect width="8" height="4" x="32" fill="#FC6D26" rx="2"/><rect width="14" height="4" y="12" fill="#EEE" rx="2"/></g></g><path fill="#FC6D26" fill-rule="nonzero" d="M81 119c-10.493 0-19-8.507-19-19s8.507-19 19-19 19 8.507 19 19-8.507 19-19 19zm0-4c8.284 0 15-6.716 15-15 0-8.284-6.716-15-15-15-8.284 0-15 6.716-15 15 0 8.284 6.716 15 15 15zm-5-20a2 2 0 0 1 2 2v6a2 2 0 1 1-4 0v-6a2 2 0 0 1 2-2zm10 0a2 2 0 0 1 2 2v6a2 2 0 1 1-4 0v-6a2 2 0 0 1 2-2z"/><path fill="#E5E5E5" fill-rule="nonzero" d="M108 102c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004a1.994 1.994 0 0 1-1.998-2zm14 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004a1.994 1.994 0 0 1-1.998-2zm93 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004a1.994 1.994 0 0 1-1.998-2zm14 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004a1.994 1.994 0 0 1-1.998-2z"/></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/pipelines_empty.svg b/app/assets/images/illustrations/pipelines_empty.svg
deleted file mode 100644
index f3107c8f062..00000000000
--- a/app/assets/images/illustrations/pipelines_empty.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 150"><g fill="none" fill-rule="evenodd"><g fill="#e5e5e5" transform="translate(0 102)"><rect width="74" height="4" x="34" y="21" opacity=".5" rx="2"/><path d="M152 23c0-1.105.887-2 1.998-2h4c1.104 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4A1.994 1.994 0 0 1 152 23m14 0c0-1.105.887-2 1.998-2h4c1.104 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4A1.994 1.994 0 0 1 166 23m14 0c0-1.105.887-2 1.998-2h4c1.104 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4A1.994 1.994 0 0 1 180 23m14 0c0-1.105.887-2 1.998-2h4c1.104 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4A1.994 1.994 0 0 1 194 23m14 0c0-1.105.887-2 1.998-2h4c1.104 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4A1.994 1.994 0 0 1 208 23"/></g><g fill="#31af64"><path fill-rule="nonzero" d="M19 144c-10.493 0-19-8.507-19-19s8.507-19 19-19 19 8.507 19 19-8.507 19-19 19m0-4c8.284 0 15-6.716 15-15 0-8.284-6.716-15-15-15-8.284 0-15 6.716-15 15 0 8.284 6.716 15 15 15"/><path d="M17.07 127.02l-2.829-2.829a1.995 1.995 0 0 0-2.828 0 1.995 1.995 0 0 0 0 2.828l4.243 4.243a1.995 1.995 0 0 0 2.822.006l7.79-7.79a1.997 1.997 0 0 0-.006-2.823 1.992 1.992 0 0 0-2.823-.006l-6.37 6.37"/></g><g fill="#e52c5a"><path fill-rule="nonzero" d="M126 149.5c-12.979 0-23.5-10.521-23.5-23.5s10.521-23.5 23.5-23.5 23.5 10.521 23.5 23.5-10.521 23.5-23.5 23.5m0-5c10.217 0 18.5-8.283 18.5-18.5s-8.283-18.5-18.5-18.5-18.5 8.283-18.5 18.5 8.283 18.5 18.5 18.5"/><path d="M130.24 126l2.833-2.833a3 3 0 0 0-4.243-4.243l-2.833 2.833-2.833-2.833a3 3 0 0 0-4.243 4.243l2.833 2.833-2.833 2.833a3 3 0 0 0 4.243 4.243l2.833-2.833 2.833 2.833a3 3 0 0 0 4.243-4.243L130.24 126"/></g><path fill="#e5e5e5" fill-rule="nonzero" d="M236 139c-7.732 0-14-6.268-14-14s6.268-14 14-14 14 6.268 14 14-6.268 14-14 14m0-4c5.523 0 10-4.477 10-10s-4.477-10-10-10-10 4.477-10 10 4.477 10 10 10"/><g transform="translate(73 4)"><path stroke="#e5e5e5" stroke-width="4" d="M64.82 76H98c4.419 0 8-3.579 8-7.99V7.99C106 3.577 102.417 0 98 0H8.009c-4.419 0-8 3.579-8 7.99v60.02c0 4.413 3.583 7.99 8 7.99h31.935l9.263 9.855a4.357 4.357 0 0 0 6.354 0L64.824 76"/><rect width="18" height="6" x="11" y="19" fill="#fc8a51" rx="3"/><rect width="18" height="6" x="35" y="35" fill="#e52c5a" rx="3"/><rect width="18" height="6" x="29" y="51" fill="#e5e5e5" rx="3"/><rect width="12" height="6" x="35" y="19" fill="#fde5d8" rx="3"/><rect width="12" height="6" x="53" y="51" fill="#e52c5a" rx="3"/><rect width="12" height="6" x="11" y="51" fill="#b5a7dd" rx="3"/><rect width="18" height="6" x="77" y="19" fill="#fc8a51" rx="3"/><rect width="18" height="6" x="11" y="35" fill="#fde5d8" rx="3"/><rect width="6" height="6" x="53" y="19" fill="#e52c5a" rx="3"/><g fill="#fde5d8"><rect width="6" height="6" x="65" y="19" rx="3"/><rect width="6" height="6" x="71" y="35" rx="3"/></g><rect width="6" height="6" x="59" y="35" fill="#e52c5a" rx="3"/></g><path fill="#6b4fbb" fill-rule="nonzero" d="M151.869 77.403c-13.26 9.264-31.649 7.977-43.484-3.858-13.279-13.279-13.279-34.806 0-48.084 13.278-13.278 34.805-13.278 48.083 0 11.836 11.836 13.118 30.23 3.858 43.485.133.111.262.229.387.354l15.556 15.555a6.004 6.004 0 0 1 0 8.486 5.997 5.997 0 0 1-8.486 0l-15.555-15.556a6.051 6.051 0 0 1-.355-.387m-1.06-9.512c10.154-10.154 10.154-26.617 0-36.77-10.153-10.154-26.616-10.154-36.77 0-10.153 10.153-10.153 26.616 0 36.77 10.154 10.153 26.617 10.153 36.77 0"/></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/pipelines_failed.svg b/app/assets/images/illustrations/pipelines_failed.svg
deleted file mode 100644
index 8daf7da86ed..00000000000
--- a/app/assets/images/illustrations/pipelines_failed.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 446 249" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M260.03 114h23.972v-.013c19.972-.53 36-16.887 36-36.987 0-20.435-16.565-37-37-37-.993 0-1.977.039-2.95.116-4.95-14.605-18.773-25.12-35.05-25.12a36.87 36.87 0 0 0-15.32 3.311c-6.649-9.841-17.909-16.311-30.68-16.311-20.435 0-37 16.565-37 37 0 .701.019 1.397.058 2.088C145.95 45.083 134 59.645 134 76.996c0 20.435 16.565 37 37 37 .324 0 .646-.004.968-.012"/><ellipse id="b" cx="41" cy="41" rx="41" ry="41"/><mask id="c" width="186" height="112" x="0" y="0" fill="#fff"><use xlink:href="#a"/></mask><mask id="d" width="82" height="82" x="0" y="0" fill="#fff"><use xlink:href="#b"/></mask></defs><g fill="none" fill-rule="evenodd"><path stroke="#b5a7dd" stroke-width="4" d="M228.415 137.792c8.443 17.156 21.89 32.082 39.688 42.358"/><path fill="#fb722e" d="M284.464 183.822a2.006 2.006 0 0 1 2.74-.727l6.914 3.992a2.001 2.001 0 0 1 .741 2.737 2.006 2.006 0 0 1-2.74.727l-6.914-3.992a2.001 2.001 0 0 1-.74-2.737m-5 8.66a2.006 2.006 0 0 1 2.74-.726l6.913 3.991a2.001 2.001 0 0 1 .741 2.737 2.006 2.006 0 0 1-2.74.727l-6.914-3.991a2.001 2.001 0 0 1-.74-2.737"/><path fill="#fde5d8" fill-rule="nonzero" d="M267.072 189.947l5.196 3a5.998 5.998 0 0 0 8.195-2.194l3.005-5.205a5.995 5.995 0 0 0-2.198-8.193l-5.196-3-9 15.588m6.032-18.447a3.005 3.005 0 0 1 4.098-1.11l6.07 3.505c4.784 2.761 6.426 8.871 3.662 13.658l-3.005 5.204c-2.76 4.782-8.875 6.42-13.659 3.658l-6.07-3.505a2.999 2.999 0 0 1-1.088-4.104l9.992-17.306"/><g fill-rule="nonzero"><path fill="#e5e5e5" d="M260.597 18.747C266.208 9.657 276.116 4 287 4c17.12 0 31 13.879 31 31 0 7.02-2.34 13.685-6.58 19.1l3.149 2.466A34.855 34.855 0 0 0 322 35.001c0-19.33-15.67-35-35-35-12.286 0-23.476 6.384-29.808 16.647l3.404 2.1"/><path fill="#b5a7dd" d="M281.982 23.991l-2.526 1.154-2.992-2.993a.4.4 0 0 0-.564.009l-1.738 1.738a.392.392 0 0 0-.009.564l2.987 2.987-1.147 2.524a12.26 12.26 0 0 0-1.04 3.883l-.269 2.76-4.08 1.093a.399.399 0 0 0-.275.492l.636 2.375c.06.223.273.346.485.29l4.087-1.096 1.611 2.262a12.017 12.017 0 0 0 2.827 2.828l2.26 1.612-1.094 4.08a.399.399 0 0 0 .29.485l2.374.636a.393.393 0 0 0 .493-.275l1.093-4.08 2.763-.267a12.14 12.14 0 0 0 3.862-1.035l2.526-1.154 2.992 2.992a.4.4 0 0 0 .564-.008l1.738-1.738a.392.392 0 0 0 .009-.564l-2.987-2.987 1.147-2.524a12.26 12.26 0 0 0 1.04-3.883l.27-2.76 4.08-1.093a.399.399 0 0 0 .274-.493l-.636-2.374a.393.393 0 0 0-.485-.29l-4.087 1.096-1.611-2.262a12.017 12.017 0 0 0-2.826-2.828l-2.26-1.612 1.093-4.08a.399.399 0 0 0-.29-.485l-2.373-.636a.393.393 0 0 0-.493.274l-1.094 4.081-2.763.266c-1.336.129-2.64.48-3.862 1.036m3.48-5.02l.375-1.4a4.393 4.393 0 0 1 5.392-3.103l2.375.636a4.399 4.399 0 0 1 3.117 5.383l-.375 1.401a16.077 16.077 0 0 1 3.761 3.767l1.405-.376a4.397 4.397 0 0 1 5.386 3.118l.636 2.375a4.398 4.398 0 0 1-3.103 5.39l-1.402.376a16.217 16.217 0 0 1-1.378 5.143l1.027 1.026a4.392 4.392 0 0 1-.008 6.22l-1.739 1.738a4.4 4.4 0 0 1-6.224.008l-1.028-1.028a16.09 16.09 0 0 1-5.14 1.381l-.376 1.4a4.393 4.393 0 0 1-5.392 3.104l-2.374-.636a4.399 4.399 0 0 1-3.118-5.383l.376-1.401a16.077 16.077 0 0 1-3.762-3.767l-1.404.376a4.397 4.397 0 0 1-5.386-3.118l-.637-2.374a4.398 4.398 0 0 1 3.103-5.391l1.402-.376a16.217 16.217 0 0 1 1.378-5.143l-1.026-1.026a4.392 4.392 0 0 1 .008-6.22l1.738-1.738a4.4 4.4 0 0 1 6.224-.008l1.028 1.028a16.09 16.09 0 0 1 5.141-1.381"/><path fill="#6b4fbb" d="M286.367 37.355a2.439 2.439 0 1 0 1.262-4.711 2.439 2.439 0 0 0-1.262 4.711m-1.035 3.864a6.44 6.44 0 1 1 3.333-12.44 6.44 6.44 0 0 1-3.333 12.44"/></g><use fill="#fff" stroke="#e5e5e5" stroke-width="8" mask="url(#c)" stroke-linejoin="round" xlink:href="#a"/><g transform="translate(175 58)"><use fill="#fff" stroke="#e5e5e5" stroke-width="8" mask="url(#d)" xlink:href="#b"/><g fill-rule="nonzero"><path fill="#e5e5e5" d="M41 78c20.435 0 37-16.565 37-37S61.435 4 41 4 4 20.565 4 41s16.565 37 37 37m0 4C18.356 82 0 63.644 0 41S18.356 0 41 0s41 18.356 41 41-18.356 41-41 41"/><path fill="#b5a7dd" d="M34.363 26.44l-2.527 1.154-3.211-3.211a1.495 1.495 0 0 0-2.117-.005l-2.131 2.13a1.504 1.504 0 0 0 .005 2.117l3.206 3.206-1.147 2.524a16.09 16.09 0 0 0-.897 2.503 16.08 16.08 0 0 0-.475 2.616l-.269 2.76-4.379 1.174a1.495 1.495 0 0 0-1.063 1.83l.78 2.911a1.504 1.504 0 0 0 1.836 1.054l4.387-1.176 1.612 2.263a15.954 15.954 0 0 0 3.737 3.742l2.26 1.612-1.173 4.38a1.495 1.495 0 0 0 1.053 1.835l2.908.78a1.504 1.504 0 0 0 1.83-1.063l1.174-4.38 2.763-.266a15.977 15.977 0 0 0 5.108-1.372l2.527-1.154 3.211 3.212a1.495 1.495 0 0 0 2.117.005l2.131-2.131a1.504 1.504 0 0 0-.005-2.117l-3.206-3.206 1.147-2.524a16.09 16.09 0 0 0 .897-2.503 16.1 16.1 0 0 0 .475-2.616l.269-2.76 4.379-1.173a1.495 1.495 0 0 0 1.063-1.83l-.78-2.912a1.504 1.504 0 0 0-1.836-1.054l-4.387 1.176-1.612-2.262a15.954 15.954 0 0 0-3.737-3.743l-2.26-1.612 1.173-4.38a1.495 1.495 0 0 0-1.053-1.835l-2.908-.779a1.504 1.504 0 0 0-1.83 1.063l-1.174 4.38-2.763.265c-1.767.17-3.493.636-5.108 1.373m4.726-5.355l.455-1.699a5.504 5.504 0 0 1 6.73-3.89l2.907.778a5.495 5.495 0 0 1 3.882 6.735l-.455 1.699a19.95 19.95 0 0 1 4.673 4.68l1.704-.457a5.503 5.503 0 0 1 6.734 3.886l.78 2.91a5.493 5.493 0 0 1-3.894 6.73l-1.701.455a20.134 20.134 0 0 1-.593 3.265 20.134 20.134 0 0 1-1.119 3.124l1.245 1.246a5.507 5.507 0 0 1 .008 7.774l-2.13 2.13a5.5 5.5 0 0 1-7.775-.001l-1.248-1.248c-2 .914-4.157 1.502-6.387 1.717l-.455 1.699a5.504 5.504 0 0 1-6.73 3.89l-2.907-.778a5.495 5.495 0 0 1-3.882-6.735l.455-1.699a19.95 19.95 0 0 1-4.673-4.68l-1.704.457a5.503 5.503 0 0 1-6.734-3.886l-.78-2.91a5.493 5.493 0 0 1 3.894-6.73l1.701-.455a20.258 20.258 0 0 1 1.712-6.389l-1.245-1.246a5.507 5.507 0 0 1-.008-7.774l2.13-2.13a5.5 5.5 0 0 1 7.775.001l1.248 1.248c2-.914 4.157-1.502 6.387-1.717"/><path fill="#6b4fbb" d="M39.965 44.863a4 4 0 1 0 2.07-7.727 4 4 0 0 0-2.07 7.727m-1.036 3.864a8 8 0 1 1 4.142-15.455 8 8 0 0 1-4.142 15.455"/></g></g><path fill="#e5e5e5" fill-rule="nonzero" d="M144 169.541v30.01a4.002 4.002 0 0 0 4 3.995h20c2.209 0 4-1.789 4-3.995v-30.01a4.002 4.002 0 0 0-4-3.995h-20c-2.209 0-4 1.789-4 3.995m-4 0c0-4.416 3.583-7.995 8-7.995h20c4.416 0 8 3.584 8 7.995v30.01c0 4.416-3.583 7.995-8 7.995h-20c-4.416 0-8-3.584-8-7.995v-30.01"/><g fill="#fb722e" transform="translate(140 161)"><rect width="4" height="11" x="10" y="18.545" rx="2"/><rect width="4" height="11" x="21" y="18.545" rx="2"/></g><path fill="#e5e5e5" fill-rule="nonzero" d="M445.16 245.34c-16.874-11.778-110.62-20.336-222.14-20.336-111.61 0-205.4 8.571-222.18 20.364a2 2 0 1 0 2.3 3.272c15.756-11.07 109.46-19.636 219.88-19.636 110.34 0 203.99 8.55 219.85 19.617a2.001 2.001 0 0 0 2.29-3.28"/></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/pipelines_pending.svg b/app/assets/images/illustrations/pipelines_pending.svg
deleted file mode 100644
index 25038366e92..00000000000
--- a/app/assets/images/illustrations/pipelines_pending.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="430" height="220" viewBox="0 0 430 220"><g fill="none" fill-rule="evenodd"><path fill="#EEE" fill-rule="nonzero" d="M189.8 182l2.4-12H114c-5.523 0-10-4.477-10-10V34c0-5.523 4.477-10 10-10h200c5.523 0 10 4.477 10 10v126c0 5.523-4.477 10-10 10h-78.2l2.4 12h22.52a9.651 9.651 0 0 1 9.28 7 5.491 5.491 0 0 1-5.28 7H164.159a5.787 5.787 0 0 1-5.659-7 8.855 8.855 0 0 1 8.659-7H189.8zM114 28a6 6 0 0 0-6 6v126a6 6 0 0 0 6 6h200a6 6 0 0 0 6-6V34a6 6 0 0 0-6-6H114zm5 6h190a5 5 0 0 1 5 5v116a5 5 0 0 1-5 5H119a5 5 0 0 1-5-5V39a5 5 0 0 1 5-5zm0 4a1 1 0 0 0-1 1v116a1 1 0 0 0 1 1h190a1 1 0 0 0 1-1V39a1 1 0 0 0-1-1H119zm112.72 132h-35.44l-2.4 12h40.24l-2.4-12zm-64.561 16c-2.29 0-4.268 1.6-4.748 3.838A1.787 1.787 0 0 0 164.16 192h100.56a1.491 1.491 0 0 0 1.435-1.901A5.651 5.651 0 0 0 260.72 186h-93.561z"/><path fill="#FEF0E8" d="M177.965 99H194a2 2 0 1 1 0 4h-16.322c-1.374 6.29-6.976 11-13.678 11-6.702 0-12.304-4.71-13.678-11h-3.365l-7.395 9.249a2 2 0 0 1-3.049.089L128.11 103h-5.844a2 2 0 1 1 0-4H129a2 2 0 0 1 1.487.662l7.423 8.248 6.523-8.159a2 2 0 0 1 1.562-.751h4.04c.513-7.265 6.57-13 13.965-13 7.396 0 13.452 5.735 13.965 13zM164 110c5.523 0 10-4.477 10-10s-4.477-10-10-10-10 4.477-10 10 4.477 10 10 10z"/><path fill="#EFEDF8" d="M273.847 103c-.962 6.23-6.347 11-12.847 11-6.5 0-11.885-4.77-12.847-11H232a2 2 0 0 1 0-4h16.153c.962-6.23 6.347-11 12.847-11 6.5 0 11.885 4.77 12.847 11h3.998l8.404-9.338a2 2 0 0 1 3.048.09L296.692 99H305a2 2 0 0 1 0 4h-9.27a2 2 0 0 1-1.562-.751l-6.523-8.16-7.423 8.249a2 2 0 0 1-1.487.662h-4.888zM261 110a9 9 0 1 0 0-18 9 9 0 0 0 0 18z"/><path fill="#FEE1D3" fill-rule="nonzero" d="M213 119c-10.493 0-19-8.507-19-19s8.507-19 19-19 19 8.507 19 19-8.507 19-19 19zm0-4c8.284 0 15-6.716 15-15 0-8.284-6.716-15-15-15-8.284 0-15 6.716-15 15 0 8.284 6.716 15 15 15z"/><path fill="#FC6D26" d="M211.586 101.828L208.757 99a2 2 0 1 0-2.828 2.828l4.243 4.243c.39.39.902.586 1.414.586.512 0 1.023-.195 1.414-.586L220.071 99a2 2 0 1 0-2.828-2.828l-5.657 5.656z"/><path fill="#FDC4A8" d="M162.95 101.07l-1.768-1.767a1.5 1.5 0 0 0-2.121 2.121l2.828 2.829c.293.293.677.439 1.06.439.385 0 .769-.146 1.062-.44l4.242-4.242a1.5 1.5 0 1 0-2.121-2.121l-3.182 3.182z"/><path fill="#6B4FBB" d="M256.39 104.841A6 6 0 1 0 261 95v6l-4.61 3.841z"/><path fill="#FEF0E8" fill-rule="nonzero" d="M99 99h-5a2 2 0 1 0 0 4h5a2 2 0 1 0 0-4zm-16 0h-5a2 2 0 1 0 0 4h5a2 2 0 1 0 0-4zm-14.384-.078l-3.643-3.425a2 2 0 1 0-2.74 2.914l3.643 3.425a2 2 0 1 0 2.74-2.914zm-11.657-10.96l-3.642-3.425a2 2 0 1 0-2.74 2.914l3.642 3.425a2 2 0 0 0 2.74-2.914zm-11.656-10.96l-3.643-3.425a2 2 0 0 0-2.74 2.914l3.643 3.425a2 2 0 1 0 2.74-2.914zm-14.367-3.885l-3.593 3.477a2 2 0 0 0 2.782 2.875l3.593-3.477a2 2 0 0 0-2.782-2.875zM19.44 84.244l-3.593 3.477a2 2 0 1 0 2.781 2.874l3.593-3.477a2 2 0 0 0-2.781-2.874zM7.94 95.371l-3.593 3.477a2 2 0 1 0 2.782 2.874l3.593-3.477a2 2 0 1 0-2.782-2.874z"/><path fill="#E1DBF1" fill-rule="nonzero" d="M423.611 99.56l-3.598 3.472a2 2 0 0 0 2.777 2.879l3.599-3.472a2 2 0 0 0-2.778-2.878zm-11.514 11.11l-3.598 3.472a2 2 0 0 0 2.777 2.878l3.598-3.471a2 2 0 0 0-2.777-2.879zm-11.514 11.11l-3.599 3.471a2 2 0 1 0 2.778 2.879l3.598-3.472a2 2 0 1 0-2.777-2.879zm-8.799 4.48l-3.642-3.426a2 2 0 0 0-2.74 2.915l3.642 3.425a2 2 0 0 0 2.74-2.915zm-11.656-10.96l-3.643-3.426a2 2 0 1 0-2.74 2.914l3.643 3.426a2 2 0 1 0 2.74-2.915zm-11.657-10.96l-3.643-3.426a2 2 0 1 0-2.74 2.914l3.643 3.425a2 2 0 1 0 2.74-2.914zM353.001 99h-5a2 2 0 1 0 0 4h5a2 2 0 0 0 0-4zm-16 0h-5a2 2 0 1 0 0 4h5a2 2 0 0 0 0-4z"/></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/priority_labels.svg b/app/assets/images/illustrations/priority_labels.svg
deleted file mode 100644
index b79c551d3d7..00000000000
--- a/app/assets/images/illustrations/priority_labels.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="116" height="68" viewBox="181 0 116 68"><g fill="none" fill-rule="evenodd" transform="translate(182)"><rect width="78" height="34" x="37" y="34" fill="#FAFAFA" rx="3"/><rect width="78" height="34" x="31" y="28" fill="#FFF" stroke="#EEE" stroke-width="4" stroke-linecap="round" rx="3"/><path fill="#FFF" stroke="#FC6D26" stroke-width="3" d="M34 35.8c-.6 0-1.4 0-1.8.4L29 38.8c-1 .7-1.7.4-2-.7l-.6-4c0-.5-.5-1.2-1-1.5L22 30.2c-1-.6-1-1.5 0-2l3.7-2c.5-.2 1-.8 1.2-1.3l1-4.2c.3-1 1-1.3 2-.5l3 3c.3.3 1 .6 1.6.6l4.2-.3c1 0 1.5.7 1 1.7L38 29c-.3.6-.3 1.4 0 2l1.3 3.8c.4 1 0 1.8-1.2 1.6l-4-.6z" stroke-linecap="round"/><path fill="#FDE5D8" d="M51.6 14.3c-.2-.2-.8-.4-1-.3l-2.8.5c-.7 0-1-.4-.8-1l1-2.8V9.5L46.6 7c-.3-.7 0-1.2.8-1h2.7c.3 0 .8-.2 1-.5l2-2c.6-.5 1-.4 1.3.3l.7 2.8c0 .3.4.8.7 1l2.3 1.2c.7.3.7 1 0 1.3l-2.2 1.7-.6 1-.4 3c-.2.6-.7.8-1.3.4l-2-1.7zM5.4 43.2c-.2-.2-.5-.2-.7-.2l-1.8.3c-.6 0-1-.2-.7-.7l.7-1.8V40l-1-1.7c0-.4 0-.7.6-.7h1.8c.3 0 .6 0 .8-.2L6.5 36c.3-.3.7-.2.8.2l.5 2 .5.5 1.6.8c.3.2.3.7 0 1l-1.6 1c-.2 0-.4.4-.4.7l-.4 2c0 .3-.4.5-.8.2l-1.4-1.2zm5-34C10.2 9 10 9 9.7 9L8 9.3c-.6 0-1-.2-.7-.7L8 6.8V6L7 4.3c0-.4 0-.7.6-.7h1.8c.3 0 .6 0 .8-.2L11.5 2c.3-.3.7-.2.8.2l.5 2 .5.5 1.6.8c.3.2.3.7 0 1l-1.6 1c-.2 0-.4.4-.4.7l-.4 2c0 .3-.4.5-.8.2l-1.4-1.2z"/><rect width="52" height="4" x="43" y="38" fill="#EEE" rx="2"/><rect width="36" height="4" x="43" y="48" fill="#EEE" rx="2"/></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/service_desk_callout.svg b/app/assets/images/illustrations/service_desk_callout.svg
deleted file mode 100644
index 2886388279e..00000000000
--- a/app/assets/images/illustrations/service_desk_callout.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82"><g fill="none" fill-rule="evenodd"><path fill="#F9F9F9" d="M2.12 42c-.08.99-.12 1.99-.12 3 0 20.435 16.565 37 37 37s37-16.565 37-37c0-1.01-.04-2.01-.12-3C74.353 61.032 58.425 76 39 76 19.575 76 3.647 61.032 2.12 42z"/><path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/><rect width="7" height="1" x="59" y="38" fill="#E1DBF2" rx=".5"/><path fill="#6B4FBB" d="M60.5 42a3.5 3.5 0 0 0 0-7v7z"/><rect width="7" height="1" x="12" y="38" fill="#E1DBF2" transform="matrix(-1 0 0 1 31 0)" rx=".5"/><path fill="#6B4FBB" d="M17.5 42a3.5 3.5 0 0 1 0-7v7z"/><path fill="#E1DBF1" fill-rule="nonzero" d="M39 58c10.493 0 19-8.507 19-19s-8.507-19-19-19-19 8.507-19 19 8.507 19 19 19zm0 4c-12.703 0-23-10.297-23-23s10.297-23 23-23 23 10.297 23 23-10.297 23-23 23z"/><path fill="#6B4FBB" d="M35 56a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/><path fill="#E1DBF1" fill-rule="nonzero" d="M26.5 40c0 4.143 3.355 7.5 7.494 7.5h10.012A7.497 7.497 0 0 0 51.5 40c0-4.143-3.355-7.5-7.494-7.5H33.994A7.497 7.497 0 0 0 26.5 40zm-3 0c0-5.799 4.698-10.5 10.494-10.5h10.012C49.802 29.5 54.5 34.2 54.5 40c0 5.799-4.698 10.5-10.494 10.5H33.994C28.198 50.5 23.5 45.8 23.5 40z"/><path fill="#6B4FBB" fill-rule="nonzero" d="M35.255 42.406a1 1 0 1 1 1.872-.703 2.001 2.001 0 0 0 3.76-.038 1 1 0 1 1 1.886.665 4 4 0 0 1-7.518.076zM31.5 40a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm15 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z"/><path fill="#6B4FBB" d="M38 22h2a1 1 0 0 1 0 2h-2a1 1 0 0 1 0-2zm0 3h2a1 1 0 0 1 0 2h-2a1 1 0 0 1 0-2z" style="mix-blend-mode:multiply"/></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/service_desk_empty.svg b/app/assets/images/illustrations/service_desk_empty.svg
deleted file mode 100644
index daaaeae6a17..00000000000
--- a/app/assets/images/illustrations/service_desk_empty.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="226" height="178" viewBox="0 0 226 178"><g fill="none" fill-rule="evenodd"><path fill="#EEE" fill-rule="nonzero" d="M109.496 165.895a78.17 78.17 0 0 0 6.158.08 2 2 0 0 0-.11-4c-1.94.053-3.886.028-5.84-.074a2 2 0 0 0-2.1 1.893 1.996 1.996 0 0 0 1.89 2.102zm18.408-1.245a76 76 0 0 0 6-1.4 2 2 0 1 0-1.064-3.856c-1.875.52-3.772.96-5.686 1.327a2.001 2.001 0 0 0 .75 3.93zm17.572-5.636a76.28 76.28 0 0 0 5.486-2.803 2 2 0 1 0-1.962-3.485 72.42 72.42 0 0 1-5.2 2.656 2.003 2.003 0 0 0 1.676 3.635zm44.342-74.897a75.786 75.786 0 0 0-.674-6.127 2.002 2.002 0 0 0-3.956.598c.29 1.92.505 3.857.64 5.805a1.998 1.998 0 0 0 2.133 1.857 2 2 0 0 0 1.858-2.133zm-3.505-18.144a76.141 76.141 0 0 0-2.13-5.78 2.001 2.001 0 0 0-3.695 1.534 72.381 72.381 0 0 1 2.02 5.476 1.999 1.999 0 1 0 3.805-1.229zm-7.754-16.73a77.053 77.053 0 0 0-3.454-5.1 1.998 1.998 0 0 0-2.797-.423 1.998 1.998 0 0 0-.424 2.796 73.06 73.06 0 0 1 3.273 4.835c.58.94 1.814 1.23 2.753.647a2.001 2.001 0 0 0 .646-2.754zm-11.582-14.446a76.37 76.37 0 0 0-4.572-4.128 1.999 1.999 0 1 0-2.559 3.073 72.633 72.633 0 0 1 4.334 3.913 2.001 2.001 0 1 0 2.798-2.86zm-101.422-4.91a77.634 77.634 0 0 0-4.64 4.05 2.001 2.001 0 0 0 2.749 2.906 72.611 72.611 0 0 1 4.4-3.84 2 2 0 1 0-2.509-3.115zM52.7 43.062a75.962 75.962 0 0 0-3.546 5.04 2 2 0 1 0 3.363 2.168 72.314 72.314 0 0 1 3.36-4.777 2 2 0 0 0-3.177-2.432zm-9.373 15.924c-.82 1.882-1.56 3.8-2.226 5.745a2 2 0 1 0 3.787 1.294 72.253 72.253 0 0 1 2.108-5.443 1.998 1.998 0 0 0-1.036-2.63 2.001 2.001 0 0 0-2.633 1.036zm-5.26 17.74a76.33 76.33 0 0 0-.777 6.11 2 2 0 0 0 3.985.347c.17-1.947.415-3.88.737-5.793a2 2 0 0 0-3.945-.664zM74.87 155.55a76.028 76.028 0 0 0 5.437 2.897 2 2 0 1 0 1.737-3.603 71.34 71.34 0 0 1-5.152-2.745 1.998 1.998 0 0 0-2.737.714 2.002 2.002 0 0 0 .715 2.738zm16.97 7.34a76.606 76.606 0 0 0 5.975 1.498 2 2 0 1 0 .816-3.916 72.52 72.52 0 0 1-5.662-1.42 1.999 1.999 0 1 0-1.129 3.837z"/><path fill="#F9F9F9" d="M2.12 130c-.08.99-.12 1.99-.12 3 0 20.435 16.565 37 37 37s37-16.565 37-37c0-1.01-.04-2.01-.12-3-1.527 19.032-17.455 34-36.88 34-19.425 0-35.353-14.968-36.88-34z"/><path fill="#EEE" fill-rule="nonzero" d="M39 166c-21.54 0-39-17.46-39-39s17.46-39 39-39 39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 92 39 92 4 107.67 4 127s15.67 35 35 35z"/><path fill="#FDC4A8" fill-rule="nonzero" d="M53.925 116.226A1.995 1.995 0 0 0 53 116H25a1.99 1.99 0 0 0-.898.212l14.663 13.406c.39.357.99.348 1.37-.02l13.79-13.372zm1.075 4.53L42.92 132.47a5 5 0 0 1-6.854.1L23 120.624V138a2 2 0 0 0 2 2h28a2 2 0 0 0 2-2v-17.244zM25 112h28a6 6 0 0 1 6 6v20a6 6 0 0 1-6 6H25a6 6 0 0 1-6-6v-20a6 6 0 0 1 6-6z"/><path fill="#F9F9F9" d="M150.12 131c-.08.99-.12 1.99-.12 3 0 20.435 16.565 37 37 37s37-16.565 37-37c0-1.01-.04-2.01-.12-3-1.527 19.032-17.455 34-36.88 34-19.425 0-35.353-14.968-36.88-34z"/><path fill="#EEE" fill-rule="nonzero" d="M187 167c-21.54 0-39-17.46-39-39s17.46-39 39-39 39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35s-15.67-35-35-35-35 15.67-35 35 15.67 35 35 35z"/><path fill="#E1DBF1" fill-rule="nonzero" d="M180.51 137H199a2 2 0 0 0 2-2v-16a2 2 0 0 0-2-2h-24a2 2 0 0 0-2 2v22.743l7.51-4.743zm1.157 4l-9.6 6.062a2 2 0 0 1-3.067-1.69V119a6 6 0 0 1 6-6h24a6 6 0 0 1 6 6v16a6 6 0 0 1-6 6h-17.333z"/><path fill="#6B4FBB" d="M180 129a2 2 0 1 1-.001-3.999A2 2 0 0 1 180 129zm7 0a2 2 0 1 1-.001-3.999A2 2 0 0 1 187 129zm7 0a2 2 0 1 1-.001-3.999A2 2 0 0 1 194 129z"/><g><path fill="#F9F9F9" d="M76.12 42c-.08.99-.12 1.99-.12 3 0 20.435 16.565 37 37 37s37-16.565 37-37c0-1.01-.04-2.01-.12-3-1.527 19.032-17.455 34-36.88 34-19.425 0-35.353-14.968-36.88-34z"/><path fill="#EEE" fill-rule="nonzero" d="M113 78c-21.54 0-39-17.46-39-39S91.46 0 113 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S132.33 4 113 4 78 19.67 78 39s15.67 35 35 35z"/><g transform="translate(133 35)"><rect width="7" height="1" y="3" fill="#E1DBF2" rx=".5"/><path fill="#6B4FBB" d="M1.5 7a3.5 3.5 0 1 0 0-7v7z"/></g><g transform="matrix(-1 0 0 1 93 35)"><rect width="7" height="1" y="3" fill="#E1DBF2" rx=".5"/><path fill="#6B4FBB" d="M1.5 7a3.5 3.5 0 1 0 0-7v7z"/></g><path fill="#E1DBF1" fill-rule="nonzero" d="M113 58c10.493 0 19-8.507 19-19s-8.507-19-19-19-19 8.507-19 19 8.507 19 19 19zm0 4c-12.703 0-23-10.297-23-23s10.297-23 23-23 23 10.297 23 23-10.297 23-23 23z"/><path fill="#6B4FBB" d="M109 56a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/><path fill="#E1DBF1" fill-rule="nonzero" d="M97.5 40c0-5.8 4.698-10.5 10.494-10.5h10.012c5.796 0 10.494 4.7 10.494 10.5s-4.698 10.5-10.494 10.5h-10.012C102.198 50.5 97.5 45.8 97.5 40zm3 0c0 4.143 3.355 7.5 7.494 7.5h10.012A7.496 7.496 0 0 0 125.5 40c0-4.143-3.355-7.5-7.494-7.5h-10.012A7.496 7.496 0 0 0 100.5 40z"/><path fill="#6B4FBB" fill-rule="nonzero" d="M109.255 42.406a.998.998 0 0 1 .584-1.287.997.997 0 0 1 1.287.583 2 2 0 0 0 3.76-.038 1 1 0 0 1 1.886.665 4.001 4.001 0 0 1-7.518.076zM105.5 40a1.5 1.5 0 1 1 .001-3.001A1.5 1.5 0 0 1 105.5 40zm15 0a1.5 1.5 0 1 1 .001-3.001A1.5 1.5 0 0 1 120.5 40z"/><path fill="#6B4FBB" d="M112 22h2a1 1 0 0 1 0 2h-2a1 1 0 0 1 0-2zm0 3h2a1 1 0 0 1 0 2h-2a1 1 0 0 1 0-2z" style="mix-blend-mode:multiply"/></g></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/slack_logo.svg b/app/assets/images/illustrations/slack_logo.svg
deleted file mode 100644
index b8d7906c2e1..00000000000
--- a/app/assets/images/illustrations/slack_logo.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" viewBox="0 0 121.94154 121.84154" width="121.942" height="121.842"><style id="style200">.st0{fill:#ecb32d}.st1{fill:#63c1a0}.st2{fill:#e01a59}.st3{fill:#331433}.st4{fill:#d62027}.st5{fill:#89d3df}.st6{fill:#258b74}.st7{fill:#819c3c}</style><path class="st0" d="M79.03 7.511c-1.9-5.7-8-8.8-13.7-7-5.7 1.9-8.8 8-7 13.7l28.1 86.4c1.9 5.3 7.7 8.3 13.2 6.7 5.8-1.7 9.3-7.8 7.4-13.4 0-.2-28-86.4-28-86.4z" id="path202" fill="#ecb32d"/><path class="st1" d="M35.53 21.611c-1.9-5.7-8-8.8-13.7-7-5.7 1.9-8.8 8-7 13.7l28.1 86.4c1.9 5.3 7.7 8.3 13.2 6.7 5.8-1.7 9.3-7.8 7.4-13.4 0-.2-28-86.4-28-86.4z" id="path204" fill="#63c1a0"/><path class="st2" d="M114.43 79.011c5.7-1.9 8.8-8 7-13.7-1.9-5.7-8-8.8-13.7-7l-86.5 28.2c-5.3 1.9-8.3 7.7-6.7 13.2 1.7 5.8 7.8 9.3 13.4 7.4.2 0 86.5-28.1 86.5-28.1z" id="path206" fill="#e01a59"/><path class="st3" d="M39.23 103.511c5.6-1.8 12.9-4.2 20.7-6.7-1.8-5.6-4.2-12.9-6.7-20.7l-20.7 6.7z" id="path208" fill="#331433"/><path class="st4" d="M82.83 89.311c7.8-2.5 15.1-4.9 20.7-6.7-1.8-5.6-4.2-12.9-6.7-20.7l-20.7 6.7z" id="path210" fill="#d62027"/><path class="st5" d="M100.23 35.511c5.7-1.9 8.8-8 7-13.7-1.9-5.7-8-8.8-13.7-7l-86.4 28.1c-5.3 1.9-8.3 7.7-6.7 13.2 1.7 5.8 7.8 9.3 13.4 7.4.2 0 86.4-28 86.4-28z" id="path212" fill="#89d3df"/><path class="st6" d="M25.13 59.911c5.6-1.8 12.9-4.2 20.7-6.7-2.5-7.8-4.9-15.1-6.7-20.7l-20.7 6.7z" id="path214" fill="#258b74"/><path class="st7" d="M68.63 45.811c7.8-2.5 15.1-4.9 20.7-6.7-2.5-7.8-4.9-15.1-6.7-20.7l-20.7 6.7z" id="path216" fill="#819c3c"/></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/todos_all_done.svg b/app/assets/images/illustrations/todos_all_done.svg
deleted file mode 100644
index 6387497a6fb..00000000000
--- a/app/assets/images/illustrations/todos_all_done.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 293 216"><g fill="none" fill-rule="evenodd"><g transform="rotate(-5 211.388 -693.89)"><rect width="163.6" height="200" x=".2" fill="#FFF" stroke="#EEE" stroke-width="3" stroke-linecap="round" stroke-dasharray="6 9" rx="6"/><g transform="translate(24 38)"><path fill="#FC6D26" d="M18.2 14l-4-3.8c-.4-.6-1.4-.6-2 0-.6.6-.6 1.5 0 2l5 5c.3.4.6.5 1 .5s.8 0 1-.4L28 8.8c.6-.6.6-1.5 0-2-.6-.7-1.6-.7-2 0L18 14z"/><path stroke="#6B4FBB" stroke-width="3" d="M27 23.3V27c0 2.3-1.7 4-4 4H4c-2.3 0-4-1.7-4-4V8c0-2.3 1.7-4 4-4h3.8" stroke-linecap="round"/><rect width="76" height="3" x="40" y="11" fill="#6B4FBB" opacity=".5" rx="1.5"/><rect width="43" height="3" x="40" y="21" fill="#6B4FBB" opacity=".5" rx="1.5"/></g><g transform="translate(24 83)"><path fill="#FC6D26" d="M18.2 14l-4-3.8c-.4-.6-1.4-.6-2 0-.6.6-.6 1.5 0 2l5 5c.3.4.6.5 1 .5s.8 0 1-.4L28 8.8c.6-.6.6-1.5 0-2-.6-.7-1.6-.7-2 0L18 14z"/><path stroke="#6B4FBB" stroke-width="3" d="M27 23.3V27c0 2.3-1.7 4-4 4H4c-2.3 0-4-1.7-4-4V8c0-2.3 1.7-4 4-4h3.8" stroke-linecap="round"/><rect width="76" height="3" x="40" y="11" fill="#B5A7DD" rx="1.5"/><rect width="43" height="3" x="40" y="21" fill="#B5A7DD" rx="1.5"/></g><g transform="translate(24 130)"><path fill="#FC6D26" d="M18.2 14l-4-3.8c-.4-.6-1.4-.6-2 0-.6.6-.6 1.5 0 2l5 5c.3.4.6.5 1 .5s.8 0 1-.4L28 8.8c.6-.6.6-1.5 0-2-.6-.7-1.6-.7-2 0L18 14z"/><path stroke="#6B4FBB" stroke-width="3" d="M27 23.3V27c0 2.3-1.7 4-4 4H4c-2.3 0-4-1.7-4-4V8c0-2.3 1.7-4 4-4h3.8" stroke-linecap="round"/><rect width="76" height="3" x="40" y="11" fill="#B5A7DD" rx="1.5"/><rect width="43" height="3" x="40" y="21" fill="#B5A7DD" rx="1.5"/></g></g><path fill="#FFCE29" d="M30 11l-1.8 4-2-4-4-1.8 4-2 2-4 2 4 4 2M286 60l-2.7 6.3-3-6-6-3 6-3 3-6 2.8 6.2 6.6 2.8M263 97l-2 4-2-4-4-2 4-2 2-4 2 4 4 2M12 85l-2.7 6.3-3-6-6-3 6-3 3-6 2.8 6.2 6.6 2.8"/></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/todos_empty.svg b/app/assets/images/illustrations/todos_empty.svg
deleted file mode 100644
index 4de6cb403b9..00000000000
--- a/app/assets/images/illustrations/todos_empty.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 284 337" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><rect id="a" width="180" height="220" x="66.2" y="74.4" rx="6"/><mask id="l" width="180" height="220" x="0" y="0" fill="#fff"><use xlink:href="#a"/></mask><rect id="b" width="180" height="220" rx="6"/><mask id="m" width="180" height="220" x="0" y="0" fill="#fff"><use xlink:href="#b"/></mask><rect id="c" width="28" height="28" rx="4"/><mask id="n" width="28" height="28" x="0" y="0" fill="#fff"><use xlink:href="#c"/></mask><rect id="d" width="28" height="28" rx="4"/><mask id="o" width="28" height="28" x="0" y="0" fill="#fff"><use xlink:href="#d"/></mask><circle id="e" cx="21.5" cy="21.5" r="21.5"/><mask id="p" width="43" height="43" x="0" y="0" fill="#fff"><use xlink:href="#e"/></mask><circle id="f" cx="26.5" cy="26.5" r="26.5"/><mask id="q" width="53" height="53" x="0" y="0" fill="#fff"><use xlink:href="#f"/></mask><circle id="g" cx="9.5" cy="4.5" r="4.5"/><mask id="r" width="13" height="13" x="-2" y="-2"><path fill="#fff" d="M3-2h13v13H3z"/><use xlink:href="#g"/></mask><circle id="h" cx="26.5" cy="26.5" r="26.5"/><mask id="s" width="53" height="53" x="0" y="0" fill="#fff"><use xlink:href="#h"/></mask><circle id="i" cx="21.5" cy="21.5" r="21.5"/><mask id="t" width="43" height="43" x="0" y="0" fill="#fff"><use xlink:href="#i"/></mask><path id="j" d="M18 38h15c10.5 0 19-8.5 19-19S43.5 0 33 0H19C8.5 0 0 8.5 0 19c0 6.3 3 12 7.8 15.3l5.2 9c.6 1 1.4 1 2 0l3-5.3z"/><mask id="u" width="52" height="44" x="0" y="0" fill="#fff"><use xlink:href="#j"/></mask><circle id="k" cx="18.5" cy="18.5" r="18.5"/><mask id="v" width="37" height="37" x="0" y="0" fill="#fff"><use xlink:href="#k"/></mask></defs><g fill="none" fill-rule="evenodd" transform="translate(-6 -4)"><use stroke="#EEE" stroke-width="6" mask="url(#l)" transform="rotate(-5 156.245 184.425)" xlink:href="#a"/><g transform="rotate(5 -707.333 618.042)"><use fill="#FFF" stroke="#EEE" stroke-width="6" mask="url(#m)" xlink:href="#b"/><g transform="translate(29 24)"><path fill="#FC6D26" d="M18.2 14l-4-3.8c-.4-.6-1.4-.6-2 0-.6.6-.6 1.5 0 2l5 5c.3.4.6.5 1 .5s.8 0 1-.4L28 8.8c.6-.6.6-1.5 0-2-.6-.7-1.6-.7-2 0L18 14z"/><path stroke="#6B4FBB" stroke-width="3" d="M27 23.3V27c0 2.3-1.7 4-4 4H4c-2.3 0-4-1.7-4-4V8c0-2.3 1.7-4 4-4h3.8" stroke-linecap="round"/><rect width="86" height="3" x="40" y="11" fill="#6B4FBB" opacity=".5" rx="1.5"/><rect width="43" height="3" x="40" y="21" fill="#6B4FBB" opacity=".5" rx="1.5"/></g><g transform="translate(29 69)"><path fill="#FC6D26" d="M18.2 14l-4-3.8c-.4-.6-1.4-.6-2 0-.6.6-.6 1.5 0 2l5 5c.3.4.6.5 1 .5s.8 0 1-.4L28 8.8c.6-.6.6-1.5 0-2-.6-.7-1.6-.7-2 0L18 14z"/><path stroke="#6B4FBB" stroke-width="3" d="M27 23.3V27c0 2.3-1.7 4-4 4H4c-2.3 0-4-1.7-4-4V8c0-2.3 1.7-4 4-4h3.8" stroke-linecap="round"/><rect width="86" height="3" x="40" y="11" fill="#B5A7DD" rx="1.5"/><rect width="43" height="3" x="40" y="21" fill="#B5A7DD" rx="1.5"/></g><g transform="translate(28 160)"><use stroke="#E5E5E5" stroke-width="6" mask="url(#n)" opacity=".7" xlink:href="#c"/><rect width="26" height="3" x="41" y="7" fill="#ECECEC" rx="1.5"/><rect width="43" height="3" x="41" y="17" fill="#ECECEC" rx="1.5"/></g><g transform="translate(28 116)"><use stroke="#E5E5E5" stroke-width="6" mask="url(#o)" xlink:href="#d"/><rect width="86" height="3" x="41" y="7" fill="#E5E5E5" rx="1.5"/><rect width="43" height="3" x="41" y="17" fill="#E5E5E5" rx="1.5"/></g></g><g transform="rotate(-15 601.917 -782.362)"><use fill="#FFF" stroke="#B5A7DD" stroke-width="6" mask="url(#p)" xlink:href="#e"/><text fill="#6B4FBB" font-family="SourceSansPro-Black, Source Sans Pro" font-size="20" font-weight="700" letter-spacing="-.1"><tspan x="12" y="27">@</tspan></text></g><g transform="rotate(15 -686.59 1035.907)"><use fill="#FFF" stroke="#FDE5D8" stroke-width="6" mask="url(#q)" xlink:href="#f"/><path fill="#FC6D26" d="M26.5 38.2c3.3 0 9.5-2.5 9.5-9.6 0-7-2.4-6.6-9.5-6.6-7 0-9.5-.4-9.5 6.6s6.2 9.6 9.5 9.6z"/><g transform="translate(17 14)"><use fill="#FC6D26" xlink:href="#g"/><use stroke="#FFF" stroke-width="4" mask="url(#r)" xlink:href="#g"/></g></g><g transform="rotate(15 -85.125 65.185)"><use fill="#FFF" stroke="#B5A7DD" stroke-width="6" mask="url(#s)" xlink:href="#h"/><path fill="#6B4FBB" d="M24 18.5c0-1.4 1-2.5 2.5-2.5 1.4 0 2.5 1 2.5 2.5v9c0 1.4-1 2.5-2.5 2.5-1.4 0-2.5-1-2.5-2.5v-9zM26.5 37c1.4 0 2.5-1 2.5-2.5 0-1.4-1-2.5-2.5-2.5-1.4 0-2.5 1-2.5 2.5 0 1.4 1 2.5 2.5 2.5z"/></g><g transform="rotate(-15 716.492 78.873)"><use fill="#FFF" stroke="#FDE5D8" stroke-width="6" mask="url(#t)" xlink:href="#i"/><path fill="#FC6D26" d="M20 23v-3h3v3h-3zm0 3v1.5c0 .8-.7 1.5-1.5 1.5s-1.5-.7-1.5-1.5V26h-2.5c-.8 0-1.5-.7-1.5-1.5s.7-1.5 1.5-1.5H17v-3h-1.5c-.8 0-1.5-.7-1.5-1.5s.7-1.5 1.5-1.5H17v-2.5c0-.8.7-1.5 1.5-1.5s1.5.7 1.5 1.5V17h3v-1.5c0-.8.7-1.5 1.5-1.5s1.5.7 1.5 1.5V17h2.5c.8 0 1.5.7 1.5 1.5s-.7 1.5-1.5 1.5H26v3h1.5c.8 0 1.5.7 1.5 1.5s-.7 1.5-1.5 1.5H26v2.5c0 .8-.7 1.5-1.5 1.5s-1.5-.7-1.5-1.5V26h-3z"/></g><g transform="rotate(-15 129.114 -585.74)"><use stroke="#FDE5D8" stroke-width="6" mask="url(#u)" xlink:href="#j"/><circle cx="16" cy="20" r="2" fill="#FC6D26"/><circle cx="27" cy="20" r="2" fill="#FC6D26"/><circle cx="38" cy="20" r="2" fill="#FC6D26"/></g><g transform="rotate(-15 1254.8 -458.986)"><use stroke="#FDE5D8" stroke-width="6" mask="url(#v)" xlink:href="#k"/><path fill="#FC6D26" d="M10.6 19l2-2c.5-.5.5-1 0-1.5-.3-.4-1-.4-1.3 0l-2.8 2.8c-.2.2-.3.4-.3.7 0 .3 0 .5.3.7l2.8 2.8c.4.4 1 .4 1.4 0 .4-.4.4-1 0-1.4l-2-2zm14.8 0l-2-2c-.5-.5-.5-1 0-1.5.3-.4 1-.4 1.3 0l2.8 2.8c.2.2.3.4.3.7 0 .3 0 .5-.3.7l-2.8 2.8c-.4.4-1 .4-1.4 0-.4-.4-.4-1 0-1.4l2-2z"/><rect width="2" height="7" x="17" y="15.1" fill="#FC6D26" opacity=".5" transform="rotate(15 18.002 18.64)" rx="1"/></g></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/welcome/add_new_group.svg b/app/assets/images/illustrations/welcome/add_new_group.svg
deleted file mode 100644
index b10a3ae8812..00000000000
--- a/app/assets/images/illustrations/welcome/add_new_group.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82"><g fill="none" fill-rule="evenodd"><path fill="#000" fill-opacity=".03" d="M2.12 42C2.04 43 2 44 2 45c0 20.43 16.57 37 37 37s37-16.57 37-37c0-1-.04-2-.12-3C74.35 61.03 58.42 76 39 76S3.65 61.03 2.12 42z"/><path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/><path fill="#E1DBF2" fill-rule="nonzero" d="M59.65 32.65H60l-2-2.42-2 2.4-2-2.4-2 2.4-2-2.4-2 2.4-2-2.4-2 2.42h.77C45.57 34.6 46 36.75 46 39c0 2.84-.7 5.5-1.92 7.86 1.97 2.28 4.83 3.64 7.92 3.64 5.8 0 10.5-4.74 10.5-10.6 0-2.8-1.08-5.36-2.85-7.25zM43.18 29.6c2.4-2.1 5.52-3.3 8.82-3.3 7.46 0 13.5 6.1 13.5 13.6S59.46 53.5 52 53.5c-3.68 0-7.1-1.5-9.6-4.04C39.3 53.44 34.44 56 29 56c-9.4 0-17-7.6-17-17s7.6-17 17-17c3.22 0 6.23.9 8.8 2.45 2.13 1.3 3.97 3.05 5.38 5.16zM17 34c-.65 1.54-1 3.23-1 5 0 7.18 5.82 13 13 13s13-5.82 13-13c0-1.77-.35-3.46-1-5h-9c-.53 0-1.04-.2-1.4-.6L29 31.84l-1.6 1.58c-.36.4-.87.6-1.4.6h-9zm21.38-4a12.996 12.996 0 0 0-18.76 0h5.55l2.42-2.4c.74-.8 2-.8 2.8 0l2.4 2.4h5.54z"/><path fill="#6B4FBB" d="M47.6 42.32c-.66 0-1.2-.54-1.2-1.2 0-.68.54-1.22 1.2-1.22.66 0 1.2.54 1.2 1.2 0 .68-.54 1.22-1.2 1.22zm8.8 0c-.66 0-1.2-.54-1.2-1.2 0-.68.54-1.22 1.2-1.22.66 0 1.2.54 1.2 1.2 0 .68-.54 1.22-1.2 1.22zM25 44h8c0 2.2-1.8 4-4 4s-4-1.8-4-4zm-1.5-1c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm11 0c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5z"/></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/welcome/add_new_project.svg b/app/assets/images/illustrations/welcome/add_new_project.svg
deleted file mode 100644
index 4b8dc34c088..00000000000
--- a/app/assets/images/illustrations/welcome/add_new_project.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82"><g fill="none" fill-rule="evenodd"><path fill="#F9F9F9" d="M2.12 42c-.08.99-.12 1.99-.12 3 0 20.435 16.565 37 37 37s37-16.565 37-37c0-1.01-.04-2.01-.12-3C74.353 61.032 58.425 76 39 76S3.647 61.032 2.12 42z"/><path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/><path fill="#E1DBF2" fill-rule="nonzero" d="M30 24c-2.21 0-4 1.79-4 4v22c0 2.21 1.79 4 4 4h18c2.21 0 4-1.79 4-4V28c0-2.21-1.79-4-4-4H30zm0-4h18a8 8 0 0 1 8 8v22a8 8 0 0 1-8 8H30a8 8 0 0 1-8-8V28a8 8 0 0 1 8-8z"/><path fill="#6B4FBB" d="M33 30h8a2 2 0 1 1 0 4h-8a2 2 0 1 1 0-4zm0 7h12a2 2 0 1 1 0 4H33a2 2 0 1 1 0-4zm0 7h12a2 2 0 1 1 0 4H33a2 2 0 1 1 0-4z"/></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/welcome/add_new_user.svg b/app/assets/images/illustrations/welcome/add_new_user.svg
deleted file mode 100644
index d4c184989bf..00000000000
--- a/app/assets/images/illustrations/welcome/add_new_user.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82"><g fill="none" fill-rule="evenodd"><path fill="#000" fill-opacity=".03" d="M2.12 42C2.04 43 2 44 2 45c0 20.43 16.57 37 37 37s37-16.57 37-37c0-1-.04-2-.12-3C74.35 61.03 58.42 76 39 76S3.65 61.03 2.12 42z"/><path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/><path fill="#E1DBF2" d="M44 31l-2.5-3-2.5 3-2.5-3-2.5 3-2.5-3-2.5 3h-2.72c2.65-4.2 7.36-7 12.72-7s10.07 2.8 12.72 7H49l-2.5-3-2.5 3z"/><path fill="#E1DBF2" fill-rule="nonzero" d="M39 57c-9.4 0-17-7.6-17-17s7.6-17 17-17 17 7.6 17 17-7.6 17-17 17zm0-4c7.18 0 13-5.82 13-13s-5.82-13-13-13-13 5.82-13 13 5.82 13 13 13z"/><path fill="#6B4FBB" d="M35 45h8c0 2.2-1.8 4-4 4s-4-1.8-4-4zm-1.5-2c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm11 0c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5z"/></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/welcome/configure_server.svg b/app/assets/images/illustrations/welcome/configure_server.svg
deleted file mode 100644
index f9dda816f11..00000000000
--- a/app/assets/images/illustrations/welcome/configure_server.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82"><g fill="none" fill-rule="evenodd"><path fill="#000" fill-opacity=".03" d="M2.12 42C2.04 43 2 44 2 45c0 20.43 16.57 37 37 37s37-16.57 37-37c0-1-.04-2-.12-3C74.35 61.03 58.42 76 39 76S3.65 61.03 2.12 42z"/><path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/><path fill="#FEE1D3" fill-rule="nonzero" d="M24.92 35.15a4.012 4.012 0 0 1-.6-5.63l1.26-1.55c1.4-1.72 3.9-2 5.63-.6l.7.56c.7-.4 1.4-.73 2.1-1V26c0-2.2 1.8-4 4-4h2c2.2 0 4 1.8 4 4v.92c.8.28 1.5.62 2.1 1l.7-.55c1.7-1.4 4.3-1.12 5.7.6l1.3 1.55c1.4 1.72 1.2 4.23-.6 5.63l-.7.6c.3.74.4 1.5.5 2.3l.9.2c2.2.5 3.5 2.64 3 4.8L56.4 45c-.5 2.15-2.64 3.5-4.8 3l-.88-.2c-.44.63-.92 1.24-1.46 1.8l.4.82c.9 1.98.1 4.38-1.9 5.35l-1.8.87c-2 .97-4.37.15-5.34-1.84l-.46-.85c-.34.03-.74.05-1.13.05-.4 0-.8-.02-1.2-.05l-.4.85c-.95 2-3.34 2.8-5.33 1.84l-1.8-.87a4.011 4.011 0 0 1-1.83-5.35l.4-.8c-.54-.58-1.02-1.2-1.46-1.83l-.8.2c-2.2.5-4.3-.9-4.8-3l-.4-2c-.5-2.2.85-4.3 3-4.8l.9-.2c.1-.8.3-1.6.5-2.3l-.7-.6zm4.95.77c-.53 1.2-.83 2.47-.87 3.8-.02.9-.66 1.68-1.55 1.9l-2.32.53.45 1.94 2.3-.6c.9-.2 1.8.2 2.23 1 .7 1.1 1.5 2.2 2.5 3 .7.6.9 1.6.5 2.4l-1 2.1 1.8.9 1.1-2.1c.4-.8 1.3-1.3 2.2-1.1.7.1 1.3.2 2 .2s1.3-.1 2-.2c.9-.2 1.8.3 2.2 1.1l1 2.1 1.8-.9-1.2-2c-.4-.8-.2-1.8.5-2.4 1-.85 1.84-1.88 2.45-3.05.4-.82 1.33-1.24 2.2-1.04l2.33.54.45-1.95-2.32-.54c-.9-.2-1.52-.97-1.54-1.88-.03-1.4-.33-2.6-.86-3.8-.4-.9-.2-1.8.5-2.4l1.9-1.5-1.3-1.6-1.8 1.5c-.8.5-1.8.6-2.5 0-1.1-.8-2.3-1.4-3.5-1.7-.9-.2-1.5-1-1.5-1.9V26h-2v2.38c0 .9-.6 1.7-1.5 1.93-1.3.4-2.5 1-3.5 1.7-.8.6-1.8.6-2.5 0l-1.9-1.5-1.26 1.6 1.8 1.5c.7.6.94 1.6.6 2.4z"/><path fill="#FC6D26" fill-rule="nonzero" d="M39 46c-3.3 0-6-2.7-6-6s2.7-6 6-6 6 2.7 6 6-2.7 6-6 6zm0-4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2z"/></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/welcome/ee_trial.svg b/app/assets/images/illustrations/welcome/ee_trial.svg
deleted file mode 100644
index 6d0dcf0020c..00000000000
--- a/app/assets/images/illustrations/welcome/ee_trial.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="330" height="132" viewBox="0 0 330 132"><g fill="none" fill-rule="evenodd"><path fill="#000" fill-opacity=".03" d="M174.12 42c-.08 1-.12 2-.12 3 0 20.43 16.57 37 37 37s37-16.57 37-37c0-1-.04-2-.12-3-1.53 19.03-17.46 34-36.88 34s-35.35-14.97-36.88-34z"/><path fill="#EEE" fill-rule="nonzero" d="M211 78c-21.54 0-39-17.46-39-39s17.46-39 39-39 39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S230.33 4 211 4s-35 15.67-35 35 15.67 35 35 35z"/><g fill-rule="nonzero"><path fill="#FEE1D3" d="M211.5 51c-6.42 0-12.26-2.84-17.43-8.4a4.008 4.008 0 0 1-.27-5.13C199 30.57 204.92 27 211.5 27s12.5 3.56 17.7 10.47a3.994 3.994 0 0 1-.27 5.12c-5.17 5.53-11 8.4-17.43 8.4zm0-4c5.25 0 10.05-2.34 14.5-7.13-4.5-5.98-9.3-8.87-14.5-8.87-5.2 0-10 2.9-14.5 8.87 4.45 4.8 9.25 7.13 14.5 7.13z"/><path fill="#FC6D26" d="M211 47c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm0-4c2.2 0 4-1.8 4-4s-1.8-4-4-4-4 1.8-4 4 1.8 4 4 4zm0-1c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3z"/></g><path fill="#000" fill-opacity=".03" d="M88.12 83c-.08 1-.12 2-.12 3 0 20.43 16.57 37 37 37s37-16.57 37-37c0-1-.04-2-.12-3-1.53 19.03-17.46 34-36.88 34s-35.35-14.97-36.88-34z"/><path fill="#EEE" fill-rule="nonzero" d="M125 119c-21.54 0-39-17.46-39-39s17.46-39 39-39 39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35s-15.67-35-35-35-35 15.67-35 35 15.67 35 35 35z"/><path fill="#FEE1D3" fill-rule="nonzero" d="M116 86.34c2.33.83 4 3.05 4 5.66 0 3.3-2.7 6-6 6s-6-2.7-6-6c0-2.6 1.67-4.83 4-5.66V72h4v14.34zM128 66c5.52 0 10 4.48 10 10v12h-4V76c0-3.3-2.7-6-6-6v1.83c0 .55-.45 1-1 1-.24 0-.47-.1-.65-.24l-4.46-3.87c-.46-.36-.5-1-.15-1.4.03-.05.07-.1.1-.12l4.47-3.82c.42-.35 1.05-.3 1.4.1.16.2.25.43.25.66V66zm-14 28c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2z"/><path fill="#FC6D26" fill-rule="nonzero" d="M114 74c-3.3 0-6-2.7-6-6s2.7-6 6-6 6 2.7 6 6-2.7 6-6 6zm0-4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm22 28c-3.3 0-6-2.7-6-6s2.7-6 6-6 6 2.7 6 6-2.7 6-6 6zm0-4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2z"/><path fill="#000" fill-opacity=".03" d="M2.12 52C2.04 53 2 54 2 55c0 20.43 16.57 37 37 37s37-16.57 37-37c0-1-.04-2-.12-3C74.35 71.03 58.42 86 39 86S3.65 71.03 2.12 52z"/><path fill="#EEE" fill-rule="nonzero" d="M39 88C17.46 88 0 70.54 0 49s17.46-39 39-39 39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 14 39 14 4 29.67 4 49s15.67 35 35 35z"/><path fill="#6B4FBB" fill-rule="nonzero" d="M48 41h-4c0-2.76-2.24-5-5-5s-5 2.24-5 5h-4a9 9 0 0 1 18 0zm-18 0h4v3h-4v-3zm14 0h4v3h-4v-3z"/><path fill="#E1DBF2" fill-rule="nonzero" d="M30 47c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h18c.55 0 1-.45 1-1V48c0-.55-.45-1-1-1H30zm0-4h18c2.76 0 5 2.24 5 5v12c0 2.76-2.24 5-5 5H30c-2.76 0-5-2.24-5-5V48c0-2.76 2.24-5 5-5z"/><path fill="#6B4FBB" d="M38 53.73c-.6-.34-1-1-1-1.73 0-1.1.9-2 2-2s2 .9 2 2c0 .74-.4 1.4-1 1.73V55c0 .55-.45 1-1 1s-1-.45-1-1v-1.27z"/><path fill="#000" fill-opacity=".03" d="M254.12 92c-.08 1-.12 2-.12 3 0 20.43 16.57 37 37 37s37-16.57 37-37c0-1-.04-2-.12-3-1.53 19.03-17.46 34-36.88 34s-35.35-14.97-36.88-34z"/><path fill="#EEE" fill-rule="nonzero" d="M291 128c-21.54 0-39-17.46-39-39s17.46-39 39-39 39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35s-15.67-35-35-35-35 15.67-35 35 15.67 35 35 35z"/><path fill="#6B4BBE" fill-rule="nonzero" d="M292 78c5.52 0 10 4.48 10 10 0 2.28-.76 4.43-2.14 6.18-1.03 1.3-.8 3.2.5 4.22 1.3 1.02 3.2.8 4.2-.5 2.22-2.8 3.44-6.26 3.44-9.9 0-8.84-7.16-16-16-16v-3.13c0-.2-.06-.4-.17-.56-.3-.42-.93-.54-1.38-.23l-9.2 6.13c-.1.06-.2.16-.28.27-.3.45-.18 1.08.28 1.38l9.2 6.13c.16.1.35.17.55.17.55 0 1-.45 1-1V78z"/><path fill="#E1DBF2" fill-rule="nonzero" d="M290 100c-5.52 0-10-4.48-10-10 0-2.25.74-4.38 2.1-6.12 1-1.3.77-3.2-.54-4.2-1.3-1.02-3.2-.78-4.2.53A15.796 15.796 0 0 0 274 90c0 8.84 7.16 16 16 16v3.13c0 .55.45 1 1 1 .2 0 .4-.06.55-.17l9.2-6.13c.46-.3.6-.93.28-1.38-.07-.1-.17-.2-.28-.28l-9.2-6.13c-.45-.3-1.08-.2-1.38.27-.1.2-.17.4-.17.6v3.1z"/></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/welcome/globe.svg b/app/assets/images/illustrations/welcome/globe.svg
deleted file mode 100644
index c2daae5f317..00000000000
--- a/app/assets/images/illustrations/welcome/globe.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82"><g fill="none" fill-rule="evenodd"><path fill="#F9F9F9" d="M2.12 42c-.08.99-.12 1.99-.12 3 0 20.435 16.565 37 37 37s37-16.565 37-37c0-1.01-.04-2.01-.12-3C74.353 61.032 58.425 76 39 76 19.575 76 3.647 61.032 2.12 42z"/><path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/><path fill="#E1DBF2" d="M30.24 27.823A14.98 14.98 0 0 0 24 40c0 2.549.636 4.949 1.757 7.051-.297-2.684.644-4.026 2.823-4.026 3.707 0 2.462 5.365 4.473 5.761 2.01.396 4.175.396 4.267 3.29.04 1.257-.265 2.157-.917 2.7a15.095 15.095 0 0 0 8.555-1.006c.035-1.91.303-4.941 2.21-5.61 2.373-.833-.55-1.431.734-3.368 1.17-1.762-3.297-5.2 0-4.832 3.477.388 5.044-.816 6.024-1.456a14.903 14.903 0 0 0-1.373-4.94c-.873.4-2.19.465-3.702-.538-.757-.502-1.084-3.944-2.107-3.944-3.823 0-4.065 3.17-5.994 3.944-1.076.431-4.193 3.773-5.614 3.596-1.126-.14-1.071-4.417-2.45-5.166-1.359-.738-2.174-1.948-2.447-3.633zM39 59c-10.493 0-19-8.507-19-19s8.507-19 19-19 19 8.507 19 19-8.507 19-19 19z"/></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/welcome/lightbulb.svg b/app/assets/images/illustrations/welcome/lightbulb.svg
deleted file mode 100644
index fce10312085..00000000000
--- a/app/assets/images/illustrations/welcome/lightbulb.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82"><g fill="none" fill-rule="evenodd"><path fill="#F9F9F9" d="M2.12 42c-.08.99-.12 1.99-.12 3 0 20.435 16.565 37 37 37s37-16.565 37-37c0-1.01-.04-2.01-.12-3C74.353 61.032 58.425 76 39 76S3.647 61.032 2.12 42z"/><path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/><path fill="#6B4FBB" d="M33 52h12a2 2 0 1 1 0 4H33a2 2 0 1 1 0-4zm1 5h10a2 2 0 1 1 0 4H34a2 2 0 1 1 0-4z"/><path fill="#E1DBF2" fill-rule="nonzero" d="M45.542 46.932l.346-2.36a8.004 8.004 0 0 1 1.566-3.705c3.025-3.946 4.485-7.29 4.547-9.96C52.153 24.41 46.843 20 39 20c-7.777 0-13 4.374-13 11 0 2.4 1.462 5.73 4.573 9.846a8.009 8.009 0 0 1 1.536 3.683l.353 2.456 13.08-.054zm-17.038.624L28.15 45.1a3.997 3.997 0 0 0-.768-1.842C23.794 38.51 22 34.424 22 31c0-9.39 7.61-15 17-15s17.218 5.614 17 15c-.085 3.64-1.875 7.74-5.37 12.3a3.99 3.99 0 0 0-.784 1.853l-.346 2.36a4.003 4.003 0 0 1-3.942 3.42l-13.08.053a4 4 0 0 1-3.974-3.43z"/><path fill="#6B4FBB" d="M41 38.732a2 2 0 1 1 2 0V42a1 1 0 0 1-2 0v-3.268zm-6 0a2 2 0 1 1 2 0V42a1 1 0 0 1-2 0v-3.268z"/></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/wiki-fro-logged-out-users.svg b/app/assets/images/illustrations/wiki-fro-logged-out-users.svg
deleted file mode 100644
index c71841f72e5..00000000000
--- a/app/assets/images/illustrations/wiki-fro-logged-out-users.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="412" height="260" viewBox="0 0 412 260" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M6.447.894L12 12H0L5.553.894a.5.5 0 0 1 .894 0z"/></defs><g fill="none" fill-rule="evenodd"><path fill="#FEF0E8" fill-rule="nonzero" d="M338 50.287C322.695 41.45 303.124 46.694 294.287 62c-8.836 15.305-3.592 34.876 11.713 43.712 15.306 8.837 34.877 3.593 43.713-11.712 8.837-15.306 3.593-34.877-11.713-43.713zm2-3.464C357.22 56.763 363.118 78.78 353.177 96c-9.941 17.218-31.958 23.118-49.177 13.176-17.218-9.94-23.118-31.958-13.177-49.176C300.764 42.78 322.782 36.88 340 46.823z"/><g transform="rotate(-150 171.003 8.53)"><path fill="#FC6D26" fill-rule="nonzero" d="M4 16v25a2 2 0 1 0 4 0V16H4zm8-4v29a6 6 0 1 1-12 0V12h12z"/><use fill="#D8D8D8" xlink:href="#a"/><path stroke="#FDC4A8" stroke-width="4" d="M6 4.472L3.236 10h5.528L6 4.472z"/><path fill="#FC6D26" d="M9 6L6.447.894a.5.5 0 0 0-.894 0L3 6c.836.628 1.874 1 3 1a4.978 4.978 0 0 0 3-1z"/></g><path fill="#F9F9F9" d="M263.116 237.116A10.002 10.002 0 0 1 254 243h-86c-11.046 0-20-8.954-20-20V121c0-4.056 2.414-7.547 5.884-9.116A9.964 9.964 0 0 0 153 116v106c0 8.837 7.163 16 16 16h90c1.467 0 2.86-.316 4.116-.884z"/><path fill="#EEE" fill-rule="nonzero" d="M214.5 106H163c-5.523 0-10 4.477-10 10v106c0 8.837 7.163 16 16 16h90c5.523 0 10-4.477 10-10v-17.999a10.036 10.036 0 0 1-4 3.167V228a6 6 0 0 1-6 6h-90c-6.627 0-12-5.373-12-12V116a6 6 0 0 1 6-6h7v-4h44.5z"/><path fill="#EEE" fill-rule="nonzero" d="M260 218.268V214h-90a6 6 0 0 0 0 12h86a4 4 0 0 0 4-4v-.268a1.99 1.99 0 0 1-1 .268h-50a2 2 0 0 1 0-4h50c.364 0 .706.097 1 .268zM170 210h90.5a3.5 3.5 0 0 1 3.5 3.5v8.5a8 8 0 0 1-8 8h-86c-5.523 0-10-4.477-10-10s4.477-10 10-10z"/><path fill="#EEE" fill-rule="nonzero" d="M174 110v100h87a6 6 0 0 0 6-6v-88a6 6 0 0 0-6-6h-87zm-4-4h91c5.523 0 10 4.477 10 10v88c0 5.523-4.477 10-10 10h-91V106z"/><path fill="#EFEDF8" d="M230 99h18a6 6 0 0 1 6 6v31.35a3 3 0 0 1-4.68 2.484l-9.277-6.274a1.5 1.5 0 0 0-1.664-.01l-9.731 6.395a3 3 0 0 1-4.648-2.507V105a6 6 0 0 1 6-6z"/><path fill="#C3B8E3" fill-rule="nonzero" d="M236.182 129.207a5.5 5.5 0 0 1 6.102.04l7.716 5.219V105a2 2 0 0 0-2-2h-18a2 2 0 0 0-2 2v29.584l8.182-5.377zM230 99h18a6 6 0 0 1 6 6v31.35a3 3 0 0 1-4.68 2.484l-9.277-6.274a1.5 1.5 0 0 0-1.664-.01l-9.731 6.395a3 3 0 0 1-4.648-2.507V105a6 6 0 0 1 6-6z"/><g fill-rule="nonzero"><path fill="#EFEDF8" d="M156 74c14.912 0 27-12.088 27-27s-12.088-27-27-27-27 12.088-27 27 12.088 27 27 27zm0 4c-17.12 0-31-13.88-31-31s13.88-31 31-31 31 13.88 31 31-13.88 31-31 31z"/><path fill="#6B4FBB" d="M147.535 44.916l-.116 1.086a8.446 8.446 0 0 0 .093 2.44l.2 1.08-2.262 1.202a.495.495 0 0 0-.213.678l.941 1.77c.128.239.434.332.68.201l2.25-1.196.785.775a8.544 8.544 0 0 0 1.967 1.45l.975.522-.486 2.5a.495.495 0 0 0 .392.59l1.968.383a.504.504 0 0 0 .585-.401l.489-2.515 1.086-.13a8.584 8.584 0 0 0 2.363-.633l1.005-.43 1.68 1.933a.495.495 0 0 0 .708.055l1.513-1.315a.504.504 0 0 0 .044-.708l-1.67-1.922.583-.94c.431-.696.761-1.45.978-2.239l.292-1.063 2.547-.089a.495.495 0 0 0 .488-.515l-.07-2.003a.504.504 0 0 0-.523-.48l-2.56.09-.367-1.037a8.446 8.446 0 0 0-1.139-2.159l-.644-.882 1.509-2.076a.495.495 0 0 0-.106-.702l-1.621-1.178a.504.504 0 0 0-.7.116l-1.494 2.057-1.05-.362a8.459 8.459 0 0 0-2.398-.455l-1.1-.047-.66-2.466a.495.495 0 0 0-.613-.36l-1.936.519a.504.504 0 0 0-.35.617l.661 2.466-.93.59a8.459 8.459 0 0 0-1.848 1.594l-.728.838-2.322-1.034a.495.495 0 0 0-.665.25l-.815 1.83a.504.504 0 0 0 .26.661l2.344 1.044zm-3.565 1.697a3.504 3.504 0 0 1-1.78-4.622l.815-1.83a3.495 3.495 0 0 1 4.626-1.77l.346.154c.259-.245.529-.477.81-.697l-.106-.394a3.504 3.504 0 0 1 2.471-4.292l1.936-.519a3.495 3.495 0 0 1 4.286 2.481l.106.395c.353.05.703.116 1.05.198l.222-.306a3.504 3.504 0 0 1 4.89-.78l1.622 1.178a3.495 3.495 0 0 1 .769 4.892l-.258.355c.184.312.354.633.508.962l.42-.014a3.504 3.504 0 0 1 3.625 3.373l.07 2.003a3.495 3.495 0 0 1-3.382 3.618l-.4.014c-.127.332-.27.659-.426.978l.256.294a3.504 3.504 0 0 1-.34 4.941l-1.512 1.315a3.495 3.495 0 0 1-4.94-.351l-.283-.325a11.669 11.669 0 0 1-1.05.28l-.082.424a3.504 3.504 0 0 1-4.103 2.774l-1.967-.382a3.495 3.495 0 0 1-2.765-4.11l.075-.383a11.547 11.547 0 0 1-.858-.633l-.354.188a3.504 3.504 0 0 1-4.738-1.442l-.94-1.77a3.495 3.495 0 0 1 1.453-4.734l.37-.197a11.436 11.436 0 0 1-.041-1.088l-.4-.178zm13.326 5.608a5.5 5.5 0 1 1-2.847-10.625 5.5 5.5 0 0 1 2.847 10.625zm-.776-2.898a2.5 2.5 0 1 0-1.294-4.83 2.5 2.5 0 0 0 1.294 4.83z"/></g><g fill-rule="nonzero"><path fill="#EFEDF8" d="M326.979 222.047c14.403 3.86 29.209-4.688 33.068-19.092 3.86-14.403-4.688-29.209-19.092-33.068-14.403-3.86-29.209 4.688-33.068 19.092-3.86 14.404 4.688 29.209 19.092 33.068zm-1.035 3.864c-16.538-4.431-26.352-21.43-21.92-37.967 4.43-16.538 21.429-26.352 37.966-21.92 16.538 4.43 26.352 21.429 21.92 37.966-4.43 16.538-21.429 26.352-37.966 21.92z"/><path fill="#6B4FBB" d="M329.376 201.598c-4.668-2.621-7.155-8.157-5.706-13.566 1.715-6.402 8.295-10.201 14.697-8.486 6.402 1.716 10.2 8.296 8.485 14.697-1.45 5.41-6.371 8.96-11.725 8.897a3.03 3.03 0 0 1-.074.365l-1.812 6.761a3 3 0 0 1-5.795-1.552l1.812-6.762a3.03 3.03 0 0 1 .118-.354zm3.815-2.733a8 8 0 1 0 4.14-15.455 8 8 0 0 0-4.14 15.455z"/></g><path fill="#FEF0E8" fill-rule="nonzero" d="M91.373 193c17.071-4.574 27.202-22.12 22.628-39.191-4.575-17.071-22.121-27.202-39.192-22.628-17.071 4.574-27.202 22.121-22.628 39.192 4.574 17.071 22.121 27.202 39.192 22.627zm1.035 3.864c-19.204 5.146-38.945-6.25-44.09-25.456-5.146-19.204 6.25-38.945 25.455-44.09 19.205-5.146 38.945 6.25 44.091 25.455 5.146 19.205-6.25 38.945-25.456 44.091z"/><path fill="#FDC4A8" fill-rule="nonzero" d="M70.067 152.122l6.73 25.114 19.318-5.176-6.73-25.114-19.318 5.176zm-1.035-3.864l19.318-5.176a4 4 0 0 1 4.9 2.828l6.729 25.114a4 4 0 0 1-2.829 4.9L77.832 181.1a4 4 0 0 1-4.9-2.829l-6.729-25.114a4 4 0 0 1 2.829-4.899z"/><path fill="#FC6D26" d="M76.898 154.433l7.727-2.07a2 2 0 0 1 1.036 3.863l-7.728 2.07a2 2 0 1 1-1.035-3.863zm1.812 6.761l5.795-1.553a2 2 0 0 1 1.035 3.864l-5.795 1.553a2 2 0 1 1-1.035-3.864zm1.811 6.762l7.728-2.07a2 2 0 0 1 1.035 3.863l-7.727 2.07a2 2 0 1 1-1.036-3.863z"/></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/wiki_login_empty.svg b/app/assets/images/illustrations/wiki_login_empty.svg
deleted file mode 100644
index 1cfa47220a5..00000000000
--- a/app/assets/images/illustrations/wiki_login_empty.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="386" height="298" viewBox="0 0 386 298" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M4 51h16v15.997A5.003 5.003 0 0 1 15.003 72H8.997A5.005 5.005 0 0 1 4 66.997V51z"/><rect id="b" width="24" height="10" y="44" rx="3"/></defs><g fill="none" fill-rule="evenodd" transform="translate(0 3)"><g transform="rotate(15 23.151 968.24)"><rect width="53" height="44" fill="#FFF" stroke="#FDE5D8" stroke-width="3" stroke-linecap="round" rx="5"/><path fill="#FDE5D8" d="M29.5 28.3l2.758-3.861c.962-1.347 2.527-1.34 3.484 0l6.516 9.122c.962 1.347.399 2.439-1.252 2.439H17.994c-1.653 0-2.21-1.099-1.252-2.439l6.516-9.122c.962-1.347 2.527-1.34 3.484 0L29.5 28.3z" opacity=".6"/><circle cx="16" cy="16" r="6" fill="#FDB997"/></g><g transform="scale(-1 1) rotate(25 -75.08 -334.15)"><rect width="3" height="11" x="12.45" y="23.45" fill="#6B4FBB" transform="rotate(45 13.95 28.95)" rx="1.5"/><rect width="3" height="14" x="9.45" y="15.45" fill="#6B4FBB" transform="rotate(45 10.95 22.45)" rx="1.5"/><path fill="#FFF" stroke="#E1DCF1" stroke-width="3" d="M16 39.6C6.871 37.747 0 29.676 0 20 0 8.954 8.954 0 20 0s20 8.954 20 20c0 8.955-5.886 16.536-14 19.084v15.91A5.007 5.007 0 0 1 21 60c-2.761 0-5-2.244-5-5.006V39.6zm4-7.6c6.627 0 12-5.373 12-12S26.627 8 20 8 8 13.373 8 20s5.373 12 12 12z"/></g><g transform="scale(1 -1) rotate(-15 -383.616 -172.407)"><path stroke="#FDE5D8" stroke-width="3" d="M1.5 38.5h9V4c0-1.378-1.12-2.5-2.496-2.5H3.996A2.503 2.503 0 0 0 1.5 4v34.5z"/><rect width="2" height="27" x="5" y="7" fill="#FDA77D" opacity=".8" rx="1"/><path stroke="#FDE5D8" stroke-width="3" d="M2.427 41.553h7.146L6 48.699l-3.573-7.146z"/></g><g transform="rotate(-30 420.145 -545.422)"><path fill="#FFF" stroke="#FDE5D8" stroke-width="3" d="M9 3c0-1.657 1.347-3 3-3 1.657 0 3 1.352 3 3v43H9V3z"/><use fill="#FFF" xlink:href="#a"/><path stroke="#FDE5D8" stroke-width="3" d="M5.5 52.5v14.497A3.505 3.505 0 0 0 8.997 70.5h6.006a3.503 3.503 0 0 0 3.497-3.503V52.5h-13z"/><rect width="2" height="14" x="9" y="51" fill="#FDA77D" rx="1"/><rect width="2" height="14" x="13" y="51" fill="#FDA77D" rx="1"/><use fill="#FFF" xlink:href="#b"/><rect width="21" height="7" x="1.5" y="45.5" stroke="#FDE5D8" stroke-width="3" rx="3"/></g><g transform="translate(72 97.488)"><rect width="125" height="160" fill="#FFF" stroke="#E5E5E5" stroke-width="4" stroke-linecap="round" rx="10"/><rect width="125" height="160" x="125" fill="#FFF" stroke="#E5E5E5" stroke-width="4" stroke-linecap="round" rx="10"/><path fill="#FFF" stroke="#E5E5E5" stroke-width="4" d="M7 12.008C7 8.69 9.686 6 12.993 6H125v148H12.993C9.683 154 7 151.305 7 147.992V12.008zm236 0C243 8.69 240.314 6 237.007 6H125v148h112.007c3.31 0 5.993-2.695 5.993-6.008V12.008z" stroke-linecap="round"/><rect width="84" height="42" x="142" y="29" stroke="#EEE" stroke-width="4" rx="3"/><rect width="88" height="4" x="141" y="93" fill="#E5E5E5" rx="2"/><rect width="88" height="4" x="141" y="107" fill="#BFBFBF" rx="2"/><rect width="56" height="4" x="141" y="121" fill="#E5E5E5" rx="2"/><rect width="56" height="4" x="22" y="93" fill="#E5E5E5" rx="2"/><rect width="26" height="4" x="22" y="27" fill="#BFBFBF" rx="2"/><rect width="56" height="4" x="22" y="41" fill="#E5E5E5" rx="2"/><rect width="36" height="4" x="22" y="55" fill="#BFBFBF" rx="2"/><rect width="56" height="4" x="22" y="69" fill="#E5E5E5" rx="2"/><rect width="36" height="4" x="22" y="107" fill="#E5E5E5" rx="2"/><rect width="56" height="4" x="22" y="121" fill="#BFBFBF" rx="2"/></g><path stroke="#B5A7DD" stroke-width="2.5" d="M23.139 182.922l-1.347-.6a2.004 2.004 0 0 1-1.02-2.64l.815-1.831a1.995 1.995 0 0 1 2.645-1.01l1.308.583a9.959 9.959 0 0 1 2.177-1.876l-.376-1.402a2.004 2.004 0 0 1 1.41-2.455l1.937-.519a1.995 1.995 0 0 1 2.449 1.421l.375 1.402a9.959 9.959 0 0 1 2.824.536l.84-1.158a2.004 2.004 0 0 1 2.796-.448l1.622 1.178a1.995 1.995 0 0 1 .437 2.797l-.867 1.193a9.946 9.946 0 0 1 1.341 2.541l1.461-.05a2.004 2.004 0 0 1 2.075 1.926l.07 2.003a1.995 1.995 0 0 1-1.935 2.067l-1.445.05c-.256.93-.644 1.817-1.15 2.632l.944 1.087a2.004 2.004 0 0 1-.191 2.825l-1.513 1.315a1.995 1.995 0 0 1-2.824-.204l-.963-1.108a10.084 10.084 0 0 1-2.776.744l-.28 1.441a2.004 2.004 0 0 1-2.344 1.588l-1.967-.382a1.995 1.995 0 0 1-1.579-2.35l.275-1.414a10.044 10.044 0 0 1-2.312-1.704l-1.277.678a2.004 2.004 0 0 1-2.709-.822l-.94-1.77a1.995 1.995 0 0 1 .833-2.705l1.29-.687a9.946 9.946 0 0 1-.11-2.872zm10.98 4.93a4 4 0 1 0-2.07-7.727 4 4 0 0 0 2.07 7.728z"/><ellipse cx="197" cy="289.988" fill="#F9F9F9" rx="125" ry="4.5"/><path fill="#6B4FBB" d="M164 100.492a3.002 3.002 0 0 1 3.001-3.004H183a3.006 3.006 0 0 1 3.001 3.004v34.988c0 2.213-1.45 2.954-3.24 1.651l-7.76-5.643-7.76 5.643c-1.789 1.302-3.24.566-3.24-1.651v-34.988z"/><g opacity=".2"><path fill="#FC8A51" d="M5.747 234.768l-2.688 1.114c-1.017.422-1.803-.134-1.754-1.228l.128-2.907-1.115-2.688c-.422-1.017.135-1.803 1.229-1.754l2.907.128 2.687-1.115c1.018-.422 1.803.135 1.755 1.229l-.128 2.907 1.114 2.687c.422 1.018-.134 1.803-1.228 1.755l-2.907-.128zM191.564 37.953l-3.72.164c-1.326.059-1.992-.88-1.48-2.115l1.426-3.438-.164-3.72c-.059-1.326.88-1.992 2.115-1.48l3.438 1.426 3.72-.164c1.326-.059 1.992.88 1.48 2.114l-1.426 3.44.164 3.719c.059 1.326-.88 1.992-2.114 1.48l-3.44-1.426z"/><path fill="#6B4FBB" d="M348.789 75.876l-1.967-2.144c-.744-.812-.49-1.74.555-2.07l2.775-.873 2.144-1.967c.812-.744 1.74-.49 2.07.555l.873 2.775 1.967 2.144c.744.812.49 1.74-.555 2.07l-2.775.873-2.144 1.967c-.812.745-1.74.49-2.07-.555l-.873-2.775zm9.261 164.735l-2.907-.125c-1.1-.048-1.577-.884-1.07-1.855l1.344-2.58.126-2.908c.047-1.1.883-1.577 1.855-1.07l2.58 1.344 2.907.126c1.1.047 1.577.883 1.07 1.855l-1.344 2.58-.125 2.907c-.048 1.1-.884 1.577-1.856 1.07l-2.58-1.344zM88.789 75.876l-1.967-2.144c-.744-.812-.49-1.74.555-2.07l2.775-.873 2.144-1.967c.812-.744 1.74-.49 2.07.555l.873 2.775 1.967 2.144c.744.812.49 1.74-.555 2.07l-2.775.873-2.144 1.967c-.812.745-1.74.49-2.07-.555l-.873-2.775z"/></g></g></svg> \ No newline at end of file
diff --git a/app/assets/images/illustrations/wiki_logout_empty.svg b/app/assets/images/illustrations/wiki_logout_empty.svg
deleted file mode 100644
index c71841f72e5..00000000000
--- a/app/assets/images/illustrations/wiki_logout_empty.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="412" height="260" viewBox="0 0 412 260" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M6.447.894L12 12H0L5.553.894a.5.5 0 0 1 .894 0z"/></defs><g fill="none" fill-rule="evenodd"><path fill="#FEF0E8" fill-rule="nonzero" d="M338 50.287C322.695 41.45 303.124 46.694 294.287 62c-8.836 15.305-3.592 34.876 11.713 43.712 15.306 8.837 34.877 3.593 43.713-11.712 8.837-15.306 3.593-34.877-11.713-43.713zm2-3.464C357.22 56.763 363.118 78.78 353.177 96c-9.941 17.218-31.958 23.118-49.177 13.176-17.218-9.94-23.118-31.958-13.177-49.176C300.764 42.78 322.782 36.88 340 46.823z"/><g transform="rotate(-150 171.003 8.53)"><path fill="#FC6D26" fill-rule="nonzero" d="M4 16v25a2 2 0 1 0 4 0V16H4zm8-4v29a6 6 0 1 1-12 0V12h12z"/><use fill="#D8D8D8" xlink:href="#a"/><path stroke="#FDC4A8" stroke-width="4" d="M6 4.472L3.236 10h5.528L6 4.472z"/><path fill="#FC6D26" d="M9 6L6.447.894a.5.5 0 0 0-.894 0L3 6c.836.628 1.874 1 3 1a4.978 4.978 0 0 0 3-1z"/></g><path fill="#F9F9F9" d="M263.116 237.116A10.002 10.002 0 0 1 254 243h-86c-11.046 0-20-8.954-20-20V121c0-4.056 2.414-7.547 5.884-9.116A9.964 9.964 0 0 0 153 116v106c0 8.837 7.163 16 16 16h90c1.467 0 2.86-.316 4.116-.884z"/><path fill="#EEE" fill-rule="nonzero" d="M214.5 106H163c-5.523 0-10 4.477-10 10v106c0 8.837 7.163 16 16 16h90c5.523 0 10-4.477 10-10v-17.999a10.036 10.036 0 0 1-4 3.167V228a6 6 0 0 1-6 6h-90c-6.627 0-12-5.373-12-12V116a6 6 0 0 1 6-6h7v-4h44.5z"/><path fill="#EEE" fill-rule="nonzero" d="M260 218.268V214h-90a6 6 0 0 0 0 12h86a4 4 0 0 0 4-4v-.268a1.99 1.99 0 0 1-1 .268h-50a2 2 0 0 1 0-4h50c.364 0 .706.097 1 .268zM170 210h90.5a3.5 3.5 0 0 1 3.5 3.5v8.5a8 8 0 0 1-8 8h-86c-5.523 0-10-4.477-10-10s4.477-10 10-10z"/><path fill="#EEE" fill-rule="nonzero" d="M174 110v100h87a6 6 0 0 0 6-6v-88a6 6 0 0 0-6-6h-87zm-4-4h91c5.523 0 10 4.477 10 10v88c0 5.523-4.477 10-10 10h-91V106z"/><path fill="#EFEDF8" d="M230 99h18a6 6 0 0 1 6 6v31.35a3 3 0 0 1-4.68 2.484l-9.277-6.274a1.5 1.5 0 0 0-1.664-.01l-9.731 6.395a3 3 0 0 1-4.648-2.507V105a6 6 0 0 1 6-6z"/><path fill="#C3B8E3" fill-rule="nonzero" d="M236.182 129.207a5.5 5.5 0 0 1 6.102.04l7.716 5.219V105a2 2 0 0 0-2-2h-18a2 2 0 0 0-2 2v29.584l8.182-5.377zM230 99h18a6 6 0 0 1 6 6v31.35a3 3 0 0 1-4.68 2.484l-9.277-6.274a1.5 1.5 0 0 0-1.664-.01l-9.731 6.395a3 3 0 0 1-4.648-2.507V105a6 6 0 0 1 6-6z"/><g fill-rule="nonzero"><path fill="#EFEDF8" d="M156 74c14.912 0 27-12.088 27-27s-12.088-27-27-27-27 12.088-27 27 12.088 27 27 27zm0 4c-17.12 0-31-13.88-31-31s13.88-31 31-31 31 13.88 31 31-13.88 31-31 31z"/><path fill="#6B4FBB" d="M147.535 44.916l-.116 1.086a8.446 8.446 0 0 0 .093 2.44l.2 1.08-2.262 1.202a.495.495 0 0 0-.213.678l.941 1.77c.128.239.434.332.68.201l2.25-1.196.785.775a8.544 8.544 0 0 0 1.967 1.45l.975.522-.486 2.5a.495.495 0 0 0 .392.59l1.968.383a.504.504 0 0 0 .585-.401l.489-2.515 1.086-.13a8.584 8.584 0 0 0 2.363-.633l1.005-.43 1.68 1.933a.495.495 0 0 0 .708.055l1.513-1.315a.504.504 0 0 0 .044-.708l-1.67-1.922.583-.94c.431-.696.761-1.45.978-2.239l.292-1.063 2.547-.089a.495.495 0 0 0 .488-.515l-.07-2.003a.504.504 0 0 0-.523-.48l-2.56.09-.367-1.037a8.446 8.446 0 0 0-1.139-2.159l-.644-.882 1.509-2.076a.495.495 0 0 0-.106-.702l-1.621-1.178a.504.504 0 0 0-.7.116l-1.494 2.057-1.05-.362a8.459 8.459 0 0 0-2.398-.455l-1.1-.047-.66-2.466a.495.495 0 0 0-.613-.36l-1.936.519a.504.504 0 0 0-.35.617l.661 2.466-.93.59a8.459 8.459 0 0 0-1.848 1.594l-.728.838-2.322-1.034a.495.495 0 0 0-.665.25l-.815 1.83a.504.504 0 0 0 .26.661l2.344 1.044zm-3.565 1.697a3.504 3.504 0 0 1-1.78-4.622l.815-1.83a3.495 3.495 0 0 1 4.626-1.77l.346.154c.259-.245.529-.477.81-.697l-.106-.394a3.504 3.504 0 0 1 2.471-4.292l1.936-.519a3.495 3.495 0 0 1 4.286 2.481l.106.395c.353.05.703.116 1.05.198l.222-.306a3.504 3.504 0 0 1 4.89-.78l1.622 1.178a3.495 3.495 0 0 1 .769 4.892l-.258.355c.184.312.354.633.508.962l.42-.014a3.504 3.504 0 0 1 3.625 3.373l.07 2.003a3.495 3.495 0 0 1-3.382 3.618l-.4.014c-.127.332-.27.659-.426.978l.256.294a3.504 3.504 0 0 1-.34 4.941l-1.512 1.315a3.495 3.495 0 0 1-4.94-.351l-.283-.325a11.669 11.669 0 0 1-1.05.28l-.082.424a3.504 3.504 0 0 1-4.103 2.774l-1.967-.382a3.495 3.495 0 0 1-2.765-4.11l.075-.383a11.547 11.547 0 0 1-.858-.633l-.354.188a3.504 3.504 0 0 1-4.738-1.442l-.94-1.77a3.495 3.495 0 0 1 1.453-4.734l.37-.197a11.436 11.436 0 0 1-.041-1.088l-.4-.178zm13.326 5.608a5.5 5.5 0 1 1-2.847-10.625 5.5 5.5 0 0 1 2.847 10.625zm-.776-2.898a2.5 2.5 0 1 0-1.294-4.83 2.5 2.5 0 0 0 1.294 4.83z"/></g><g fill-rule="nonzero"><path fill="#EFEDF8" d="M326.979 222.047c14.403 3.86 29.209-4.688 33.068-19.092 3.86-14.403-4.688-29.209-19.092-33.068-14.403-3.86-29.209 4.688-33.068 19.092-3.86 14.404 4.688 29.209 19.092 33.068zm-1.035 3.864c-16.538-4.431-26.352-21.43-21.92-37.967 4.43-16.538 21.429-26.352 37.966-21.92 16.538 4.43 26.352 21.429 21.92 37.966-4.43 16.538-21.429 26.352-37.966 21.92z"/><path fill="#6B4FBB" d="M329.376 201.598c-4.668-2.621-7.155-8.157-5.706-13.566 1.715-6.402 8.295-10.201 14.697-8.486 6.402 1.716 10.2 8.296 8.485 14.697-1.45 5.41-6.371 8.96-11.725 8.897a3.03 3.03 0 0 1-.074.365l-1.812 6.761a3 3 0 0 1-5.795-1.552l1.812-6.762a3.03 3.03 0 0 1 .118-.354zm3.815-2.733a8 8 0 1 0 4.14-15.455 8 8 0 0 0-4.14 15.455z"/></g><path fill="#FEF0E8" fill-rule="nonzero" d="M91.373 193c17.071-4.574 27.202-22.12 22.628-39.191-4.575-17.071-22.121-27.202-39.192-22.628-17.071 4.574-27.202 22.121-22.628 39.192 4.574 17.071 22.121 27.202 39.192 22.627zm1.035 3.864c-19.204 5.146-38.945-6.25-44.09-25.456-5.146-19.204 6.25-38.945 25.455-44.09 19.205-5.146 38.945 6.25 44.091 25.455 5.146 19.205-6.25 38.945-25.456 44.091z"/><path fill="#FDC4A8" fill-rule="nonzero" d="M70.067 152.122l6.73 25.114 19.318-5.176-6.73-25.114-19.318 5.176zm-1.035-3.864l19.318-5.176a4 4 0 0 1 4.9 2.828l6.729 25.114a4 4 0 0 1-2.829 4.9L77.832 181.1a4 4 0 0 1-4.9-2.829l-6.729-25.114a4 4 0 0 1 2.829-4.899z"/><path fill="#FC6D26" d="M76.898 154.433l7.727-2.07a2 2 0 0 1 1.036 3.863l-7.728 2.07a2 2 0 1 1-1.035-3.863zm1.812 6.761l5.795-1.553a2 2 0 0 1 1.035 3.864l-5.795 1.553a2 2 0 1 1-1.035-3.864zm1.811 6.762l7.728-2.07a2 2 0 0 1 1.035 3.863l-7.727 2.07a2 2 0 1 1-1.036-3.863z"/></g></svg> \ No newline at end of file
diff --git a/app/assets/javascripts/activities.js b/app/assets/javascripts/activities.js
index 6a0662ba903..c117d080bda 100644
--- a/app/assets/javascripts/activities.js
+++ b/app/assets/javascripts/activities.js
@@ -1,5 +1,6 @@
/* eslint-disable no-param-reassign, class-methods-use-this */
+import $ from 'jquery';
import Cookies from 'js-cookie';
import Pager from './pager';
import { localTimeAgo } from './lib/utils/datetime_utility';
diff --git a/app/assets/javascripts/ajax_loading_spinner.js b/app/assets/javascripts/ajax_loading_spinner.js
index 2bc77859c26..bd08308904c 100644
--- a/app/assets/javascripts/ajax_loading_spinner.js
+++ b/app/assets/javascripts/ajax_loading_spinner.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
export default class AjaxLoadingSpinner {
static init() {
const $elements = $('.js-ajax-loading-spinner');
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index 464611f66f0..cbcefb2c18f 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import _ from 'underscore';
import axios from './lib/utils/axios_utils';
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
index 26e62732b33..6da33a26e58 100644
--- a/app/assets/javascripts/awards_handler.js
+++ b/app/assets/javascripts/awards_handler.js
@@ -1,4 +1,6 @@
/* eslint-disable class-methods-use-this */
+
+import $ from 'jquery';
import _ from 'underscore';
import Cookies from 'js-cookie';
import { __ } from './locale';
diff --git a/app/assets/javascripts/behaviors/copy_as_gfm.js b/app/assets/javascripts/behaviors/copy_as_gfm.js
index ffe90595b5d..f5f4f00d587 100644
--- a/app/assets/javascripts/behaviors/copy_as_gfm.js
+++ b/app/assets/javascripts/behaviors/copy_as_gfm.js
@@ -1,5 +1,6 @@
/* eslint-disable class-methods-use-this, object-shorthand, no-unused-vars, no-use-before-define, no-new, max-len, no-restricted-syntax, guard-for-in, no-continue */
+import $ from 'jquery';
import _ from 'underscore';
import { insertText, getSelectedFragment, nodeMatchesSelector } from '../lib/utils/common_utils';
import { placeholderImage } from '../lazy_loader';
diff --git a/app/assets/javascripts/behaviors/copy_to_clipboard.js b/app/assets/javascripts/behaviors/copy_to_clipboard.js
index b669b63d23c..e2a73a1797c 100644
--- a/app/assets/javascripts/behaviors/copy_to_clipboard.js
+++ b/app/assets/javascripts/behaviors/copy_to_clipboard.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import Clipboard from 'clipboard';
function showTooltip(target, title) {
diff --git a/app/assets/javascripts/behaviors/details_behavior.js b/app/assets/javascripts/behaviors/details_behavior.js
index 7c9dbcc8d6e..1d63f5baeee 100644
--- a/app/assets/javascripts/behaviors/details_behavior.js
+++ b/app/assets/javascripts/behaviors/details_behavior.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
$(() => {
$('body').on('click', '.js-details-target', function target() {
diff --git a/app/assets/javascripts/behaviors/quick_submit.js b/app/assets/javascripts/behaviors/quick_submit.js
index 312edc0cd69..3ec932bdb73 100644
--- a/app/assets/javascripts/behaviors/quick_submit.js
+++ b/app/assets/javascripts/behaviors/quick_submit.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import '../commons/bootstrap';
import { isInIssuePage } from '../lib/utils/common_utils';
@@ -72,5 +73,5 @@ $(document).on('keyup.quick_submit', '.js-quick-submit input[type=submit], .js-q
title,
trigger: 'manual',
});
- $this.tooltip('show').one('blur', () => $this.tooltip('hide'));
+ $this.tooltip('show').one('blur click', () => $this.tooltip('hide'));
});
diff --git a/app/assets/javascripts/behaviors/requires_input.js b/app/assets/javascripts/behaviors/requires_input.js
index e10cb2e3dc4..ffff4ddb71a 100644
--- a/app/assets/javascripts/behaviors/requires_input.js
+++ b/app/assets/javascripts/behaviors/requires_input.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import _ from 'underscore';
import '../commons/bootstrap';
diff --git a/app/assets/javascripts/behaviors/toggler_behavior.js b/app/assets/javascripts/behaviors/toggler_behavior.js
index 81c89441424..4446be0e52f 100644
--- a/app/assets/javascripts/behaviors/toggler_behavior.js
+++ b/app/assets/javascripts/behaviors/toggler_behavior.js
@@ -1,3 +1,6 @@
+import $ from 'jquery';
+import { getLocationHash } from '../lib/utils/url_utility';
+
// Toggle button. Show/hide content inside parent container.
// Button does not change visibility. If button has icon - it changes chevron style.
//
@@ -5,7 +8,6 @@
// %button.js-toggle-button
// %div.js-toggle-content
//
-import { getLocationHash } from '../lib/utils/url_utility';
$(() => {
function toggleContainer(container, toggleState) {
diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js b/app/assets/javascripts/blob/blob_file_dropzone.js
index 83cac896f86..ff1739b1679 100644
--- a/app/assets/javascripts/blob/blob_file_dropzone.js
+++ b/app/assets/javascripts/blob/blob_file_dropzone.js
@@ -1,4 +1,6 @@
/* eslint-disable func-names, object-shorthand, prefer-arrow-callback */
+
+import $ from 'jquery';
import Dropzone from 'dropzone';
import { visitUrl } from '../lib/utils/url_utility';
import { HIDDEN_CLASS } from '../lib/utils/constants';
diff --git a/app/assets/javascripts/blob/blob_fork_suggestion.js b/app/assets/javascripts/blob/blob_fork_suggestion.js
index 47c431fb809..476b9405a9e 100644
--- a/app/assets/javascripts/blob/blob_fork_suggestion.js
+++ b/app/assets/javascripts/blob/blob_fork_suggestion.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
const defaults = {
// Buttons that will show the `suggestionSections`
// has `data-fork-path`, and `data-action`
diff --git a/app/assets/javascripts/blob/file_template_mediator.js b/app/assets/javascripts/blob/file_template_mediator.js
index 37074301b51..030ca1907e5 100644
--- a/app/assets/javascripts/blob/file_template_mediator.js
+++ b/app/assets/javascripts/blob/file_template_mediator.js
@@ -1,4 +1,6 @@
/* eslint-disable class-methods-use-this */
+
+import $ from 'jquery';
import Flash from '../flash';
import FileTemplateTypeSelector from './template_selectors/type_selector';
import BlobCiYamlSelector from './template_selectors/ci_yaml_selector';
diff --git a/app/assets/javascripts/blob/file_template_selector.js b/app/assets/javascripts/blob/file_template_selector.js
index 5ae30990aea..e52cf249f3a 100644
--- a/app/assets/javascripts/blob/file_template_selector.js
+++ b/app/assets/javascripts/blob/file_template_selector.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
export default class FileTemplateSelector {
constructor(mediator) {
this.mediator = mediator;
diff --git a/app/assets/javascripts/blob/template_selector.js b/app/assets/javascripts/blob/template_selector.js
index 888883163c5..9dfdb06007d 100644
--- a/app/assets/javascripts/blob/template_selector.js
+++ b/app/assets/javascripts/blob/template_selector.js
@@ -1,5 +1,7 @@
/* eslint-disable class-methods-use-this, no-unused-vars */
+import $ from 'jquery';
+
export default class TemplateSelector {
constructor({ dropdown, data, pattern, wrapper, editor, $input } = {}) {
this.pattern = pattern;
@@ -76,7 +78,7 @@ export default class TemplateSelector {
if (!skipFocus) this.editor.focus();
- if (this.editor instanceof jQuery) {
+ if (this.editor instanceof $) {
this.editor.get(0).dispatchEvent(this.autosizeUpdateEvent);
}
}
diff --git a/app/assets/javascripts/blob/viewer/index.js b/app/assets/javascripts/blob/viewer/index.js
index 92ea91c45a8..137e1f5a099 100644
--- a/app/assets/javascripts/blob/viewer/index.js
+++ b/app/assets/javascripts/blob/viewer/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import Flash from '../../flash';
import { handleLocationHash } from '../../lib/utils/common_utils';
import axios from '../../lib/utils/axios_utils';
diff --git a/app/assets/javascripts/blob_edit/blob_bundle.js b/app/assets/javascripts/blob_edit/blob_bundle.js
index 931ed042dfd..4424232f642 100644
--- a/app/assets/javascripts/blob_edit/blob_bundle.js
+++ b/app/assets/javascripts/blob_edit/blob_bundle.js
@@ -1,5 +1,7 @@
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, vars-on-top, no-unused-vars, no-new, max-len */
/* global EditBlob */
+
+import $ from 'jquery';
import NewCommitForm from '../new_commit_form';
import EditBlob from './edit_blob';
import BlobFileDropzone from '../blob/blob_file_dropzone';
diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js
index d4f6adaccbc..82a3d494b67 100644
--- a/app/assets/javascripts/blob_edit/edit_blob.js
+++ b/app/assets/javascripts/blob_edit/edit_blob.js
@@ -1,5 +1,6 @@
/* global ace */
+import $ from 'jquery';
import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
import { __ } from '~/locale';
diff --git a/app/assets/javascripts/boards/components/board.js b/app/assets/javascripts/boards/components/board.js
index 9c4cc2338c8..3cffd91716a 100644
--- a/app/assets/javascripts/boards/components/board.js
+++ b/app/assets/javascripts/boards/components/board.js
@@ -1,4 +1,6 @@
/* eslint-disable comma-dangle, space-before-function-paren, one-var */
+
+import $ from 'jquery';
import Sortable from 'vendor/Sortable';
import Vue from 'vue';
import AccessorUtilities from '../../lib/utils/accessor';
diff --git a/app/assets/javascripts/boards/components/board_delete.js b/app/assets/javascripts/boards/components/board_delete.js
index 8a1b177bba8..7be98825fda 100644
--- a/app/assets/javascripts/boards/components/board_delete.js
+++ b/app/assets/javascripts/boards/components/board_delete.js
@@ -1,5 +1,6 @@
/* eslint-disable comma-dangle, space-before-function-paren, no-alert */
+import $ from 'jquery';
import Vue from 'vue';
window.gl = window.gl || {};
diff --git a/app/assets/javascripts/boards/components/board_new_issue.vue b/app/assets/javascripts/boards/components/board_new_issue.vue
index 870d242e774..8d84c1735b8 100644
--- a/app/assets/javascripts/boards/components/board_new_issue.vue
+++ b/app/assets/javascripts/boards/components/board_new_issue.vue
@@ -1,4 +1,5 @@
<script>
+import $ from 'jquery';
import eventHub from '../eventhub';
import ProjectSelect from './project_select.vue';
import ListIssue from '../models/issue';
diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js
index 9501e35b178..a44969272a1 100644
--- a/app/assets/javascripts/boards/components/board_sidebar.js
+++ b/app/assets/javascripts/boards/components/board_sidebar.js
@@ -1,11 +1,12 @@
/* eslint-disable comma-dangle, space-before-function-paren, no-new */
+import $ from 'jquery';
import Vue from 'vue';
import Flash from '../../flash';
import { __ } from '../../locale';
import Sidebar from '../../right_sidebar';
import eventHub from '../../sidebar/event_hub';
-import assigneeTitle from '../../sidebar/components/assignees/assignee_title';
+import assigneeTitle from '../../sidebar/components/assignees/assignee_title.vue';
import assignees from '../../sidebar/components/assignees/assignees.vue';
import DueDateSelectors from '../../due_date_select';
import './sidebar/remove_issue';
diff --git a/app/assets/javascripts/boards/components/issue_card_inner.js b/app/assets/javascripts/boards/components/issue_card_inner.js
index fc2bad2415f..7e882a57202 100644
--- a/app/assets/javascripts/boards/components/issue_card_inner.js
+++ b/app/assets/javascripts/boards/components/issue_card_inner.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import Vue from 'vue';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import eventHub from '../eventhub';
diff --git a/app/assets/javascripts/boards/components/new_list_dropdown.js b/app/assets/javascripts/boards/components/new_list_dropdown.js
index 362ef43e6f7..71f49319c36 100644
--- a/app/assets/javascripts/boards/components/new_list_dropdown.js
+++ b/app/assets/javascripts/boards/components/new_list_dropdown.js
@@ -1,5 +1,6 @@
-/* eslint-disable func-names, no-new, space-before-function-paren, one-var,
- promise/catch-or-return */
+/* eslint-disable func-names, no-new, space-before-function-paren, one-var, promise/catch-or-return, max-len */
+
+import $ from 'jquery';
import axios from '~/lib/utils/axios_utils';
import _ from 'underscore';
import CreateLabelDropdown from '../../create_label';
diff --git a/app/assets/javascripts/boards/components/project_select.vue b/app/assets/javascripts/boards/components/project_select.vue
index d99b222c305..371774098b9 100644
--- a/app/assets/javascripts/boards/components/project_select.vue
+++ b/app/assets/javascripts/boards/components/project_select.vue
@@ -1,5 +1,7 @@
<script>
/* global ListIssue */
+
+ import $ from 'jquery';
import _ from 'underscore';
import eventHub from '../eventhub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js
index efc0da2e7a2..8b1c14c04ff 100644
--- a/app/assets/javascripts/boards/index.js
+++ b/app/assets/javascripts/boards/index.js
@@ -1,5 +1,6 @@
/* eslint-disable one-var, quote-props, comma-dangle, space-before-function-paren */
+import $ from 'jquery';
import _ from 'underscore';
import Vue from 'vue';
diff --git a/app/assets/javascripts/boards/mixins/sortable_default_options.js b/app/assets/javascripts/boards/mixins/sortable_default_options.js
index 5e31c6314b2..ac316c31deb 100644
--- a/app/assets/javascripts/boards/mixins/sortable_default_options.js
+++ b/app/assets/javascripts/boards/mixins/sortable_default_options.js
@@ -1,6 +1,7 @@
/* eslint-disable no-unused-vars, no-mixed-operators, comma-dangle */
/* global DocumentTouch */
+import $ from 'jquery';
import sortableConfig from '../../sortable/sortable_config';
window.gl = window.gl || {};
diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js
index 348cdeec737..20e78edf2a2 100644
--- a/app/assets/javascripts/boards/stores/boards_store.js
+++ b/app/assets/javascripts/boards/stores/boards_store.js
@@ -1,5 +1,7 @@
/* eslint-disable comma-dangle, space-before-function-paren, one-var, no-shadow, dot-notation, max-len */
/* global List */
+
+import $ from 'jquery';
import _ from 'underscore';
import Cookies from 'js-cookie';
import { getUrlParamsArray } from '~/lib/utils/common_utils';
diff --git a/app/assets/javascripts/branches/branches_delete_modal.js b/app/assets/javascripts/branches/branches_delete_modal.js
index cbc28374b80..839e369eaf6 100644
--- a/app/assets/javascripts/branches/branches_delete_modal.js
+++ b/app/assets/javascripts/branches/branches_delete_modal.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
const MODAL_SELECTOR = '#modal-delete-branch';
class DeleteModal {
diff --git a/app/assets/javascripts/breadcrumb.js b/app/assets/javascripts/breadcrumb.js
index 10fbcfe96cf..1474d93dde6 100644
--- a/app/assets/javascripts/breadcrumb.js
+++ b/app/assets/javascripts/breadcrumb.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
export const addTooltipToEl = (el) => {
const textEl = el.querySelector('.js-breadcrumb-item-text');
diff --git a/app/assets/javascripts/build_artifacts.js b/app/assets/javascripts/build_artifacts.js
index ace89398943..3fa16517388 100644
--- a/app/assets/javascripts/build_artifacts.js
+++ b/app/assets/javascripts/build_artifacts.js
@@ -1,4 +1,6 @@
/* eslint-disable func-names, prefer-arrow-callback, no-return-assign */
+
+import $ from 'jquery';
import { visitUrl } from './lib/utils/url_utility';
import { convertPermissionToBoolean } from './lib/utils/common_utils';
diff --git a/app/assets/javascripts/build_variables.js b/app/assets/javascripts/build_variables.js
index 35edf3e0017..d398e4a4c83 100644
--- a/app/assets/javascripts/build_variables.js
+++ b/app/assets/javascripts/build_variables.js
@@ -1,9 +1,9 @@
-/* eslint-disable func-names*/
+import $ from 'jquery';
export default function handleRevealVariables() {
$('.js-reveal-variables')
.off('click')
- .on('click', function () {
+ .on('click', function click() {
$('.js-build-variables').toggle();
$(this).hide();
});
diff --git a/app/assets/javascripts/ci_variable_list/native_form_variable_list.js b/app/assets/javascripts/ci_variable_list/native_form_variable_list.js
index d54ea7df1c3..7cd5916ac9c 100644
--- a/app/assets/javascripts/ci_variable_list/native_form_variable_list.js
+++ b/app/assets/javascripts/ci_variable_list/native_form_variable_list.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import VariableList from './ci_variable_list';
// Used for the variable list on scheduled pipeline edit page
diff --git a/app/assets/javascripts/commit/image_file.js b/app/assets/javascripts/commit/image_file.js
index 6504a0bbbfc..7f3d04655a7 100644
--- a/app/assets/javascripts/commit/image_file.js
+++ b/app/assets/javascripts/commit/image_file.js
@@ -1,5 +1,7 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-use-before-define, prefer-arrow-callback, no-else-return, consistent-return, prefer-template, quotes, one-var, one-var-declaration-per-line, no-unused-vars, no-return-assign, comma-dangle, quote-props, no-unused-expressions, no-sequences, object-shorthand, max-len */
+import $ from 'jquery';
+
// Width where images must fits in, for 2-up this gets divided by 2
const availWidth = 900;
const viewModes = ['two-up', 'swipe'];
diff --git a/app/assets/javascripts/commit_merge_requests.js b/app/assets/javascripts/commit_merge_requests.js
index f76c9b7e690..102b4ee8463 100644
--- a/app/assets/javascripts/commit_merge_requests.js
+++ b/app/assets/javascripts/commit_merge_requests.js
@@ -1,5 +1,6 @@
/* global Flash */
+import $ from 'jquery';
import axios from './lib/utils/axios_utils';
import { n__, s__ } from './locale';
diff --git a/app/assets/javascripts/commits.js b/app/assets/javascripts/commits.js
index 2be63bd8c76..7e2a3573f81 100644
--- a/app/assets/javascripts/commits.js
+++ b/app/assets/javascripts/commits.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import { pluralize } from './lib/utils/text_utility';
import { localTimeAgo } from './lib/utils/datetime_utility';
import Pager from './pager';
diff --git a/app/assets/javascripts/compare.js b/app/assets/javascripts/compare.js
index d5a35ed81a6..303a5bf4a53 100644
--- a/app/assets/javascripts/compare.js
+++ b/app/assets/javascripts/compare.js
@@ -1,4 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, no-var, object-shorthand, consistent-return, no-unused-vars, comma-dangle, vars-on-top, prefer-template, max-len */
+
+import $ from 'jquery';
import { localTimeAgo } from './lib/utils/datetime_utility';
import axios from './lib/utils/axios_utils';
diff --git a/app/assets/javascripts/compare_autocomplete.js b/app/assets/javascripts/compare_autocomplete.js
index fa341918fc1..260c91cac24 100644
--- a/app/assets/javascripts/compare_autocomplete.js
+++ b/app/assets/javascripts/compare_autocomplete.js
@@ -1,4 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, object-shorthand, comma-dangle, prefer-arrow-callback, no-else-return, newline-per-chained-call, wrap-iife, max-len */
+
+import $ from 'jquery';
import { __ } from './locale';
import axios from './lib/utils/axios_utils';
import flash from './flash';
diff --git a/app/assets/javascripts/confirm_danger_modal.js b/app/assets/javascripts/confirm_danger_modal.js
index eae4a7eab55..0932d836589 100644
--- a/app/assets/javascripts/confirm_danger_modal.js
+++ b/app/assets/javascripts/confirm_danger_modal.js
@@ -1,4 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, camelcase, one-var-declaration-per-line, no-else-return, max-len */
+
+import $ from 'jquery';
import { rstrip } from './lib/utils/common_utils';
window.ConfirmDangerModal = (function() {
diff --git a/app/assets/javascripts/contextual_sidebar.js b/app/assets/javascripts/contextual_sidebar.js
index 74520675a7c..3a50e73ad85 100644
--- a/app/assets/javascripts/contextual_sidebar.js
+++ b/app/assets/javascripts/contextual_sidebar.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import Cookies from 'js-cookie';
import _ from 'underscore';
import bp from './breakpoints';
diff --git a/app/assets/javascripts/create_label.js b/app/assets/javascripts/create_label.js
index 9a4c9bfcc80..a999c21b2e9 100644
--- a/app/assets/javascripts/create_label.js
+++ b/app/assets/javascripts/create_label.js
@@ -1,4 +1,6 @@
/* eslint-disable func-names, prefer-arrow-callback */
+
+import $ from 'jquery';
import Api from './api';
import { humanize } from './lib/utils/text_utility';
diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
index 46d89c825f9..87f8854f940 100644
--- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
+++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import Vue from 'vue';
import Cookies from 'js-cookie';
import Flash from '../flash';
diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js
index 3df082e8c0c..a044fc1ab42 100644
--- a/app/assets/javascripts/diff.js
+++ b/app/assets/javascripts/diff.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import axios from '~/lib/utils/axios_utils';
import flash from '~/flash';
import { __ } from '~/locale';
diff --git a/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js b/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js
index aed7cac4e62..d1260ff5373 100644
--- a/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js
+++ b/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js
@@ -1,6 +1,7 @@
/* eslint-disable comma-dangle, object-shorthand, func-names, no-else-return, quotes, no-lonely-if, max-len */
/* global CommentsStore */
+import $ from 'jquery';
import Vue from 'vue';
const CommentAndResolveBtn = Vue.extend({
diff --git a/app/assets/javascripts/diff_notes/components/diff_note_avatars.js b/app/assets/javascripts/diff_notes/components/diff_note_avatars.js
index 300b02da663..180a6bd67e7 100644
--- a/app/assets/javascripts/diff_notes/components/diff_note_avatars.js
+++ b/app/assets/javascripts/diff_notes/components/diff_note_avatars.js
@@ -1,5 +1,6 @@
/* global CommentsStore */
+import $ from 'jquery';
import Vue from 'vue';
import collapseIcon from '../icons/collapse_icon.svg';
import Notes from '../../notes';
diff --git a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js
index fadc34959e1..8f9186dfb9a 100644
--- a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js
+++ b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js
@@ -2,6 +2,7 @@
/* global DiscussionMixins */
/* global CommentsStore */
+import $ from 'jquery';
import Vue from 'vue';
import '../mixins/discussion';
diff --git a/app/assets/javascripts/diff_notes/components/resolve_btn.js b/app/assets/javascripts/diff_notes/components/resolve_btn.js
index cc9192deae3..df4c72ba0ed 100644
--- a/app/assets/javascripts/diff_notes/components/resolve_btn.js
+++ b/app/assets/javascripts/diff_notes/components/resolve_btn.js
@@ -2,6 +2,7 @@
/* global CommentsStore */
/* global ResolveService */
+import $ from 'jquery';
import Vue from 'vue';
import Flash from '../../flash';
diff --git a/app/assets/javascripts/diff_notes/diff_notes_bundle.js b/app/assets/javascripts/diff_notes/diff_notes_bundle.js
index 5f49609fe88..e17daec6a92 100644
--- a/app/assets/javascripts/diff_notes/diff_notes_bundle.js
+++ b/app/assets/javascripts/diff_notes/diff_notes_bundle.js
@@ -1,6 +1,7 @@
/* eslint-disable func-names, comma-dangle, new-cap, no-new, max-len */
/* global ResolveCount */
+import $ from 'jquery';
import Vue from 'vue';
import './models/discussion';
import './models/note';
diff --git a/app/assets/javascripts/diff_notes/models/discussion.js b/app/assets/javascripts/diff_notes/models/discussion.js
index 1b8a9af9390..c97c559dd14 100644
--- a/app/assets/javascripts/diff_notes/models/discussion.js
+++ b/app/assets/javascripts/diff_notes/models/discussion.js
@@ -1,6 +1,7 @@
/* eslint-disable space-before-function-paren, camelcase, guard-for-in, no-restricted-syntax, no-unused-vars, max-len */
/* global NoteModel */
+import $ from 'jquery';
import Vue from 'vue';
import { localTimeAgo } from '../../lib/utils/datetime_utility';
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 1ccf96a75dc..42ecc415173 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -1,4 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, wrap-iife, no-shadow, consistent-return, one-var, one-var-declaration-per-line, camelcase, default-case, no-new, quotes, no-duplicate-case, no-case-declarations, no-fallthrough, max-len */
+
+import $ from 'jquery';
import Flash from './flash';
import GfmAutoComplete from './gfm_auto_complete';
import { convertPermissionToBoolean } from './lib/utils/common_utils';
diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js
index ba89e5726fa..5528ad9f38d 100644
--- a/app/assets/javascripts/dropzone_input.js
+++ b/app/assets/javascripts/dropzone_input.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import Dropzone from 'dropzone';
import _ from 'underscore';
import './preview_markdown';
diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js
index 417258e0092..842a4255f08 100644
--- a/app/assets/javascripts/due_date_select.js
+++ b/app/assets/javascripts/due_date_select.js
@@ -1,5 +1,6 @@
/* global dateFormat */
+import $ from 'jquery';
import Pikaday from 'pikaday';
import axios from './lib/utils/axios_utils';
import { parsePikadayDate, pikadayToString } from './lib/utils/datefix';
diff --git a/app/assets/javascripts/environments/components/environment_stop.vue b/app/assets/javascripts/environments/components/environment_stop.vue
index 1eef17bf1fe..dda7429a726 100644
--- a/app/assets/javascripts/environments/components/environment_stop.vue
+++ b/app/assets/javascripts/environments/components/environment_stop.vue
@@ -3,6 +3,8 @@
* Renders the stop "button" that allows stop an environment.
* Used in environments table.
*/
+
+ import $ from 'jquery';
import eventHub from '../event_hub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tooltip from '../../vue_shared/directives/tooltip';
diff --git a/app/assets/javascripts/experimental_flags.js b/app/assets/javascripts/experimental_flags.js
index 6ee65ca72f9..1d60847147b 100644
--- a/app/assets/javascripts/experimental_flags.js
+++ b/app/assets/javascripts/experimental_flags.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import Cookies from 'js-cookie';
export default () => {
diff --git a/app/assets/javascripts/feature_highlight/feature_highlight.js b/app/assets/javascripts/feature_highlight/feature_highlight.js
index d65cc6d5d7d..c50ac667c20 100644
--- a/app/assets/javascripts/feature_highlight/feature_highlight.js
+++ b/app/assets/javascripts/feature_highlight/feature_highlight.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import _ from 'underscore';
import {
getSelector,
diff --git a/app/assets/javascripts/feature_highlight/feature_highlight_helper.js b/app/assets/javascripts/feature_highlight/feature_highlight_helper.js
index 939d12237f3..f480e72961c 100644
--- a/app/assets/javascripts/feature_highlight/feature_highlight_helper.js
+++ b/app/assets/javascripts/feature_highlight/feature_highlight_helper.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import axios from '../lib/utils/axios_utils';
import { __ } from '../locale';
import Flash from '../flash';
diff --git a/app/assets/javascripts/filterable_list.js b/app/assets/javascripts/filterable_list.js
index a10f027de53..b17ba3c21db 100644
--- a/app/assets/javascripts/filterable_list.js
+++ b/app/assets/javascripts/filterable_list.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import _ from 'underscore';
import axios from './lib/utils/axios_utils';
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index 57a1fa107e5..8259133c95b 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import _ from 'underscore';
import glRegexp from './lib/utils/regexp';
import AjaxCache from './lib/utils/ajax_cache';
@@ -131,9 +132,8 @@ class GfmAutoComplete {
callbacks: {
...this.getDefaultCallbacks(),
matcher(flag, subtext) {
- const relevantText = subtext.trim().split(/\s/).pop();
const regexp = new RegExp(`(?:[^${glRegexp.unicodeLetters}0-9:]|\n|^):([^:]*)$`, 'gi');
- const match = regexp.exec(relevantText);
+ const match = regexp.exec(subtext);
return match && match.length ? match[1] : null;
},
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index 6cf78bab6ad..86b34a6e360 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -1,5 +1,7 @@
/* eslint-disable func-names, no-underscore-dangle, space-before-function-paren, no-var, one-var, one-var-declaration-per-line, prefer-rest-params, max-len, vars-on-top, wrap-iife, no-unused-vars, quotes, no-shadow, no-cond-assign, prefer-arrow-callback, no-return-assign, no-else-return, camelcase, comma-dangle, no-lonely-if, guard-for-in, no-restricted-syntax, consistent-return, prefer-template, no-param-reassign, no-loop-func, no-mixed-operators */
/* global fuzzaldrinPlus */
+
+import $ from 'jquery';
import _ from 'underscore';
import fuzzaldrinPlus from 'fuzzaldrin-plus';
import axios from './lib/utils/axios_utils';
@@ -576,7 +578,7 @@ GitLabDropdown = (function() {
for (var i = 0; i < html.length; i += 1) {
var el = html[i];
- if (el instanceof jQuery) {
+ if (el instanceof $) {
el = el.get(0);
}
diff --git a/app/assets/javascripts/gl_field_error.js b/app/assets/javascripts/gl_field_error.js
index bd63f6f16f0..972b2252acb 100644
--- a/app/assets/javascripts/gl_field_error.js
+++ b/app/assets/javascripts/gl_field_error.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
/**
* This class overrides the browser's validation error bubbles, displaying custom
* error messages for invalid fields instead. To begin validating any form, add the
diff --git a/app/assets/javascripts/gl_field_errors.js b/app/assets/javascripts/gl_field_errors.js
index 73bcbd93565..b9c51045b1d 100644
--- a/app/assets/javascripts/gl_field_errors.js
+++ b/app/assets/javascripts/gl_field_errors.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import GlFieldError from './gl_field_error';
const customValidationFlag = 'gl-field-error-ignore';
diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js
index 2d40856e038..184c98813f1 100644
--- a/app/assets/javascripts/gl_form.js
+++ b/app/assets/javascripts/gl_form.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import autosize from 'autosize';
import GfmAutoComplete from './gfm_auto_complete';
import dropzoneInput from './dropzone_input';
diff --git a/app/assets/javascripts/gpg_badges.js b/app/assets/javascripts/gpg_badges.js
index 6bf21f4f27d..502e3569321 100644
--- a/app/assets/javascripts/gpg_badges.js
+++ b/app/assets/javascripts/gpg_badges.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import { parseQueryStringIntoObject } from '~/lib/utils/common_utils';
import axios from '~/lib/utils/axios_utils';
import flash from '~/flash';
diff --git a/app/assets/javascripts/group.js b/app/assets/javascripts/group.js
index 7732edde1e7..4365305c168 100644
--- a/app/assets/javascripts/group.js
+++ b/app/assets/javascripts/group.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
export default class Group {
constructor() {
this.groupPath = $('#group_path');
diff --git a/app/assets/javascripts/group_avatar.js b/app/assets/javascripts/group_avatar.js
index 2168ff3a8ba..beaac61e887 100644
--- a/app/assets/javascripts/group_avatar.js
+++ b/app/assets/javascripts/group_avatar.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
export default function groupAvatar() {
$('.js-choose-group-avatar-button').on('click', function onClickGroupAvatar() {
const form = $(this).closest('form');
diff --git a/app/assets/javascripts/group_label_subscription.js b/app/assets/javascripts/group_label_subscription.js
index df9429b1e02..5648cb9a888 100644
--- a/app/assets/javascripts/group_label_subscription.js
+++ b/app/assets/javascripts/group_label_subscription.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import axios from './lib/utils/axios_utils';
import flash from './flash';
import { __ } from './locale';
diff --git a/app/assets/javascripts/groups/components/app.vue b/app/assets/javascripts/groups/components/app.vue
index 0578f43d5af..63bb5832bd0 100644
--- a/app/assets/javascripts/groups/components/app.vue
+++ b/app/assets/javascripts/groups/components/app.vue
@@ -1,6 +1,7 @@
<script>
/* global Flash */
+import $ from 'jquery';
import { s__ } from '~/locale';
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import modal from '~/vue_shared/components/modal.vue';
diff --git a/app/assets/javascripts/groups/groups_filterable_list.js b/app/assets/javascripts/groups/groups_filterable_list.js
index 31d56d15c23..e6db1746487 100644
--- a/app/assets/javascripts/groups/groups_filterable_list.js
+++ b/app/assets/javascripts/groups/groups_filterable_list.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import FilterableList from '~/filterable_list';
import eventHub from './event_hub';
import { normalizeHeaders, getParameterByName } from '../lib/utils/common_utils';
diff --git a/app/assets/javascripts/groups/transfer_dropdown.js b/app/assets/javascripts/groups/transfer_dropdown.js
index 85b7b08db4d..e0eb118ddf7 100644
--- a/app/assets/javascripts/groups/transfer_dropdown.js
+++ b/app/assets/javascripts/groups/transfer_dropdown.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
export default class TransferDropdown {
constructor() {
this.groupDropdown = $('.js-groups-dropdown');
diff --git a/app/assets/javascripts/groups_select.js b/app/assets/javascripts/groups_select.js
index 12fc5f9b5c9..310f6fe06cf 100644
--- a/app/assets/javascripts/groups_select.js
+++ b/app/assets/javascripts/groups_select.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import axios from './lib/utils/axios_utils';
import Api from './api';
import { normalizeHeaders } from './lib/utils/common_utils';
diff --git a/app/assets/javascripts/header.js b/app/assets/javascripts/header.js
index 33a352e158a..4ae3a714bee 100644
--- a/app/assets/javascripts/header.js
+++ b/app/assets/javascripts/header.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import { highCountTrim } from '~/lib/utils/text_utility';
/**
diff --git a/app/assets/javascripts/help/help.js b/app/assets/javascripts/help/help.js
index d02477b19a2..f5333042bb8 100644
--- a/app/assets/javascripts/help/help.js
+++ b/app/assets/javascripts/help/help.js
@@ -1,4 +1,7 @@
// We will render the icons list here
+
+import $ from 'jquery';
+
export default () => {
if ($('#user-content-gitlab-icons').length > 0) {
const $iconsHeader = $('#user-content-gitlab-icons');
diff --git a/app/assets/javascripts/how_to_merge.js b/app/assets/javascripts/how_to_merge.js
index 12e6f24595a..bb734246584 100644
--- a/app/assets/javascripts/how_to_merge.js
+++ b/app/assets/javascripts/how_to_merge.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
export default () => {
const modal = $('#modal_merge_info');
diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js
index f3af92cf2b0..fab0255c378 100644
--- a/app/assets/javascripts/image_diff/image_diff.js
+++ b/app/assets/javascripts/image_diff/image_diff.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import imageDiffHelper from './helpers/index';
import ImageBadge from './image_badge';
import { isImageLoaded } from '../lib/utils/image_utility';
diff --git a/app/assets/javascripts/importer_status.js b/app/assets/javascripts/importer_status.js
index 523bd2adb93..b469e1e2adc 100644
--- a/app/assets/javascripts/importer_status.js
+++ b/app/assets/javascripts/importer_status.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import _ from 'underscore';
import { __, sprintf } from './locale';
import axios from './lib/utils/axios_utils';
diff --git a/app/assets/javascripts/init_changes_dropdown.js b/app/assets/javascripts/init_changes_dropdown.js
index 1bab7965c19..09cca1dc7d9 100644
--- a/app/assets/javascripts/init_changes_dropdown.js
+++ b/app/assets/javascripts/init_changes_dropdown.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import stickyMonitor from './lib/utils/sticky';
export default (stickyTop) => {
diff --git a/app/assets/javascripts/init_labels.js b/app/assets/javascripts/init_labels.js
index 5f20055510f..15da5d5cceb 100644
--- a/app/assets/javascripts/init_labels.js
+++ b/app/assets/javascripts/init_labels.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import LabelManager from './label_manager';
import GroupLabelSubscription from './group_label_subscription';
import ProjectLabelSubscription from './project_label_subscription';
diff --git a/app/assets/javascripts/integrations/integration_settings_form.js b/app/assets/javascripts/integrations/integration_settings_form.js
index 2848fe003cb..741894b5e6c 100644
--- a/app/assets/javascripts/integrations/integration_settings_form.js
+++ b/app/assets/javascripts/integrations/integration_settings_form.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import axios from '../lib/utils/axios_utils';
import flash from '../flash';
diff --git a/app/assets/javascripts/issuable/auto_width_dropdown_select.js b/app/assets/javascripts/issuable/auto_width_dropdown_select.js
index 14a2bfbe4e0..b2c2de9e5de 100644
--- a/app/assets/javascripts/issuable/auto_width_dropdown_select.js
+++ b/app/assets/javascripts/issuable/auto_width_dropdown_select.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
let instanceCount = 0;
class AutoWidthDropdownSelect {
diff --git a/app/assets/javascripts/issuable_bulk_update_actions.js b/app/assets/javascripts/issuable_bulk_update_actions.js
index 8c1b2e78ca4..e003fb1d127 100644
--- a/app/assets/javascripts/issuable_bulk_update_actions.js
+++ b/app/assets/javascripts/issuable_bulk_update_actions.js
@@ -1,4 +1,6 @@
/* eslint-disable comma-dangle, quotes, consistent-return, func-names, array-callback-return, space-before-function-paren, prefer-arrow-callback, max-len, no-unused-expressions, no-sequences, no-underscore-dangle, no-unused-vars, no-param-reassign */
+
+import $ from 'jquery';
import _ from 'underscore';
import axios from './lib/utils/axios_utils';
import Flash from './flash';
diff --git a/app/assets/javascripts/issuable_bulk_update_sidebar.js b/app/assets/javascripts/issuable_bulk_update_sidebar.js
index 2056efe701b..2307c8e0d85 100644
--- a/app/assets/javascripts/issuable_bulk_update_sidebar.js
+++ b/app/assets/javascripts/issuable_bulk_update_sidebar.js
@@ -1,5 +1,6 @@
/* eslint-disable class-methods-use-this, no-new */
+import $ from 'jquery';
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
import MilestoneSelect from './milestone_select';
import issueStatusSelect from './issue_status_select';
diff --git a/app/assets/javascripts/issuable_context.js b/app/assets/javascripts/issuable_context.js
index da99394ff90..7470d634b99 100644
--- a/app/assets/javascripts/issuable_context.js
+++ b/app/assets/javascripts/issuable_context.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import Cookies from 'js-cookie';
import bp from './breakpoints';
import UsersSelect from './users_select';
diff --git a/app/assets/javascripts/issuable_form.js b/app/assets/javascripts/issuable_form.js
index fdfad0b6a4f..bb8b3d91e40 100644
--- a/app/assets/javascripts/issuable_form.js
+++ b/app/assets/javascripts/issuable_form.js
@@ -1,6 +1,7 @@
/* eslint-disable func-names, prefer-rest-params, wrap-iife, no-use-before-define, no-useless-escape, no-new, object-shorthand, no-unused-vars, comma-dangle, no-alert, consistent-return, no-else-return, prefer-template, one-var, one-var-declaration-per-line, curly, max-len */
/* global GitLab */
+import $ from 'jquery';
import Pikaday from 'pikaday';
import Autosave from './autosave';
import UsersSelect from './users_select';
diff --git a/app/assets/javascripts/issuable_index.js b/app/assets/javascripts/issuable_index.js
index 0683ca82a38..06ec4546164 100644
--- a/app/assets/javascripts/issuable_index.js
+++ b/app/assets/javascripts/issuable_index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import axios from './lib/utils/axios_utils';
import flash from './flash';
import { __ } from './locale';
diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js
index 333bbd9e0ba..5113ac6775d 100644
--- a/app/assets/javascripts/issue.js
+++ b/app/assets/javascripts/issue.js
@@ -1,4 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, no-underscore-dangle, one-var-declaration-per-line, object-shorthand, no-unused-vars, no-new, comma-dangle, consistent-return, quotes, dot-notation, quote-props, prefer-arrow-callback, max-len */
+
+import $ from 'jquery';
import axios from './lib/utils/axios_utils';
import { addDelimiter } from './lib/utils/text_utility';
import flash from './flash';
diff --git a/app/assets/javascripts/issue_show/components/description.vue b/app/assets/javascripts/issue_show/components/description.vue
index 1338be0ec4b..ae577e04a56 100644
--- a/app/assets/javascripts/issue_show/components/description.vue
+++ b/app/assets/javascripts/issue_show/components/description.vue
@@ -1,4 +1,5 @@
<script>
+ import $ from 'jquery';
import animateMixin from '../mixins/animate';
import TaskList from '../../task_list';
import recaptchaModalImplementor from '../../vue_shared/mixins/recaptcha_modal_implementor';
diff --git a/app/assets/javascripts/issue_show/components/fields/description_template.vue b/app/assets/javascripts/issue_show/components/fields/description_template.vue
index 1ad0e59287e..7db0488e306 100644
--- a/app/assets/javascripts/issue_show/components/fields/description_template.vue
+++ b/app/assets/javascripts/issue_show/components/fields/description_template.vue
@@ -1,4 +1,5 @@
<script>
+ import $ from 'jquery';
import IssuableTemplateSelectors from '../../../templates/issuable_template_selectors';
export default {
diff --git a/app/assets/javascripts/issue_status_select.js b/app/assets/javascripts/issue_status_select.js
index 71c0f894389..c14803c80e7 100644
--- a/app/assets/javascripts/issue_status_select.js
+++ b/app/assets/javascripts/issue_status_select.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
export default function issueStatusSelect() {
$('.js-issue-status').each((i, el) => {
const fieldName = $(el).data('fieldName');
diff --git a/app/assets/javascripts/job.js b/app/assets/javascripts/job.js
index f39ae764d3c..ace45e9dd29 100644
--- a/app/assets/javascripts/job.js
+++ b/app/assets/javascripts/job.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import _ from 'underscore';
import axios from './lib/utils/axios_utils';
import { visitUrl } from './lib/utils/url_utility';
diff --git a/app/assets/javascripts/label_manager.js b/app/assets/javascripts/label_manager.js
index 61b40f79db1..e230dbbd4ac 100644
--- a/app/assets/javascripts/label_manager.js
+++ b/app/assets/javascripts/label_manager.js
@@ -1,4 +1,6 @@
/* eslint-disable comma-dangle, class-methods-use-this, no-underscore-dangle, no-param-reassign, no-unused-vars, consistent-return, func-names, space-before-function-paren, max-len */
+
+import $ from 'jquery';
import Sortable from 'vendor/Sortable';
import flash from './flash';
diff --git a/app/assets/javascripts/labels.js b/app/assets/javascripts/labels.js
index 7aab13ed9c6..d85ae851706 100644
--- a/app/assets/javascripts/labels.js
+++ b/app/assets/javascripts/labels.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
export default class Labels {
constructor() {
this.setSuggestedColor = this.setSuggestedColor.bind(this);
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index 9b46bbf83da..824d3f7ca09 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -1,6 +1,8 @@
/* eslint-disable no-useless-return, func-names, space-before-function-paren, wrap-iife, no-var, no-underscore-dangle, prefer-arrow-callback, max-len, one-var, no-unused-vars, one-var-declaration-per-line, prefer-template, no-new, consistent-return, object-shorthand, comma-dangle, no-shadow, no-param-reassign, brace-style, vars-on-top, quotes, no-lonely-if, no-else-return, dot-notation, no-empty, no-return-assign, camelcase, prefer-spread */
/* global Issuable */
/* global ListLabel */
+
+import $ from 'jquery';
import _ from 'underscore';
import { __ } from './locale';
import axios from './lib/utils/axios_utils';
diff --git a/app/assets/javascripts/layout_nav.js b/app/assets/javascripts/layout_nav.js
index 1b4900827b8..e3177188772 100644
--- a/app/assets/javascripts/layout_nav.js
+++ b/app/assets/javascripts/layout_nav.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import ContextualSidebar from './contextual_sidebar';
import initFlyOutNav from './fly_out_nav';
diff --git a/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js b/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js
index 0bf2ba6acc2..3873f4528ce 100644
--- a/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js
+++ b/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
/**
* Linked Tabs
*
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index ed90db317df..0830ebe9e4e 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -1,4 +1,4 @@
-import jQuery from 'jquery';
+import $ from 'jquery';
import Cookies from 'js-cookie';
import axios from './axios_utils';
import { getLocationHash } from './url_utility';
@@ -142,7 +142,7 @@ export const isMetaClick = e => e.metaKey || e.ctrlKey || e.which === 2;
export const scrollToElement = (element) => {
let $el = element;
- if (!(element instanceof jQuery)) {
+ if (!(element instanceof $)) {
$el = $(element);
}
const top = $el.offset().top;
diff --git a/app/assets/javascripts/lib/utils/csrf.js b/app/assets/javascripts/lib/utils/csrf.js
index 0bdb547d31a..ca9828c4682 100644
--- a/app/assets/javascripts/lib/utils/csrf.js
+++ b/app/assets/javascripts/lib/utils/csrf.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
/*
This module provides easy access to the CSRF token and caches
it for re-use. It also exposes some values commonly used in relation
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
index d6cccbef42b..c3d94d63c13 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import timeago from 'timeago.js';
import dateFormat from 'vendor/date.format';
import { pluralize } from './text_utility';
diff --git a/app/assets/javascripts/lib/utils/text_markdown.js b/app/assets/javascripts/lib/utils/text_markdown.js
index 5dc98b4a920..470e3e5c52e 100644
--- a/app/assets/javascripts/lib/utils/text_markdown.js
+++ b/app/assets/javascripts/lib/utils/text_markdown.js
@@ -1,5 +1,7 @@
/* eslint-disable import/prefer-default-export, func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, quotes, one-var, one-var-declaration-per-line, operator-assignment, no-else-return, prefer-template, prefer-arrow-callback, no-empty, max-len, consistent-return, no-unused-vars, no-return-assign, max-len, vars-on-top */
+import $ from 'jquery';
+
const textUtils = {};
textUtils.selectedText = function(text, textarea) {
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js
index c0ce0786518..94d03621bff 100644
--- a/app/assets/javascripts/lib/utils/text_utility.js
+++ b/app/assets/javascripts/lib/utils/text_utility.js
@@ -65,20 +65,6 @@ export function capitalizeFirstCharacter(text) {
return `${text[0].toUpperCase()}${text.slice(1)}`;
}
-export function camelCase(str) {
- return str.replace(/_+([a-z])/gi, ($1, $2) => $2.toUpperCase());
-}
-
-export function camelCaseKeys(obj = {}) {
- return Object.keys(obj).reduce((acc, key) => {
- const camelKey = camelCase(key);
- return {
- ...acc,
- [camelKey]: obj[key],
- };
- }, {});
-}
-
/**
* Replaces all html tags from a string with the given replacement.
*
diff --git a/app/assets/javascripts/line_highlighter.js b/app/assets/javascripts/line_highlighter.js
index e5c1fce3db9..f2323f57455 100644
--- a/app/assets/javascripts/line_highlighter.js
+++ b/app/assets/javascripts/line_highlighter.js
@@ -1,5 +1,7 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-use-before-define, no-underscore-dangle, no-param-reassign, prefer-template, quotes, comma-dangle, prefer-arrow-callback, consistent-return, one-var, one-var-declaration-per-line, no-else-return, max-len */
+import $ from 'jquery';
+
// LineHighlighter
//
// Handles single- and multi-line selection and highlight for blob views.
diff --git a/app/assets/javascripts/logo.js b/app/assets/javascripts/logo.js
index 3688a57937e..403e216e70f 100644
--- a/app/assets/javascripts/logo.js
+++ b/app/assets/javascripts/logo.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
export default function initLogoAnimation() {
window.addEventListener('beforeunload', () => {
$('.tanuki-logo').addClass('animate');
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index 53b01cca1d3..870285f7940 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -1,5 +1,6 @@
/* eslint-disable import/first */
/* global ConfirmDangerModal */
+/* global $ */
import jQuery from 'jquery';
import Cookies from 'js-cookie';
diff --git a/app/assets/javascripts/member_expiration_date.js b/app/assets/javascripts/member_expiration_date.js
index 84e70e35bad..d27922a2099 100644
--- a/app/assets/javascripts/member_expiration_date.js
+++ b/app/assets/javascripts/member_expiration_date.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import Pikaday from 'pikaday';
import { parsePikadayDate, pikadayToString } from './lib/utils/datefix';
diff --git a/app/assets/javascripts/members.js b/app/assets/javascripts/members.js
index 330ebed5f73..7d0c701fd70 100644
--- a/app/assets/javascripts/members.js
+++ b/app/assets/javascripts/members.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
export default class Members {
constructor() {
this.addListeners();
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js
index 8be7314ded8..db1d09eb2f2 100644
--- a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js
+++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js
@@ -1,5 +1,6 @@
/* eslint-disable comma-dangle, object-shorthand, no-param-reassign, camelcase, no-nested-ternary, no-continue, max-len */
+import $ from 'jquery';
import Vue from 'vue';
import Cookies from 'js-cookie';
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js
index 66b258839ae..4abd5433bb5 100644
--- a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js
+++ b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js
@@ -1,5 +1,6 @@
/* eslint-disable new-cap, comma-dangle, no-new */
+import $ from 'jquery';
import Vue from 'vue';
import Flash from '../flash';
import initIssuableSidebar from '../init_issuable_sidebar';
diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js
index a64093afcf4..d8222ebec63 100644
--- a/app/assets/javascripts/merge_request.js
+++ b/app/assets/javascripts/merge_request.js
@@ -1,4 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, no-underscore-dangle, one-var, one-var-declaration-per-line, consistent-return, dot-notation, quote-props, comma-dangle, object-shorthand, max-len, prefer-arrow-callback */
+
+import $ from 'jquery';
import { __ } from '~/locale';
import TaskList from './task_list';
import MergeRequestTabs from './merge_request_tabs';
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index 46789e324c2..e77318fef46 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -1,5 +1,6 @@
/* eslint-disable no-new, class-methods-use-this */
+import $ from 'jquery';
import Cookies from 'js-cookie';
import axios from './lib/utils/axios_utils';
import flash from './flash';
@@ -72,6 +73,7 @@ export default class MergeRequestTabs {
constructor({ action, setUrl, stubLocation } = {}) {
const mergeRequestTabs = document.querySelector('.js-tabs-affix');
const navbar = document.querySelector('.navbar-gitlab');
+ const peek = document.getElementById('peek');
const paddingTop = 16;
this.diffsLoaded = false;
@@ -85,6 +87,10 @@ export default class MergeRequestTabs {
this.showTab = this.showTab.bind(this);
this.stickyTop = navbar ? navbar.offsetHeight - paddingTop : 0;
+ if (peek) {
+ this.stickyTop += peek.offsetHeight;
+ }
+
if (mergeRequestTabs) {
this.stickyTop += mergeRequestTabs.offsetHeight;
}
diff --git a/app/assets/javascripts/milestone.js b/app/assets/javascripts/milestone.js
index b1d74250dfd..e6e3a66aa20 100644
--- a/app/assets/javascripts/milestone.js
+++ b/app/assets/javascripts/milestone.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import axios from './lib/utils/axios_utils';
import flash from './flash';
diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js
index c259d5405bd..add07c156a4 100644
--- a/app/assets/javascripts/milestone_select.js
+++ b/app/assets/javascripts/milestone_select.js
@@ -1,6 +1,8 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-underscore-dangle, prefer-arrow-callback, max-len, one-var, one-var-declaration-per-line, no-unused-vars, object-shorthand, comma-dangle, no-else-return, no-self-compare, consistent-return, no-param-reassign, no-shadow */
/* global Issuable */
/* global ListMilestone */
+
+import $ from 'jquery';
import _ from 'underscore';
import axios from './lib/utils/axios_utils';
import { timeFor } from './lib/utils/datetime_utility';
diff --git a/app/assets/javascripts/mini_pipeline_graph_dropdown.js b/app/assets/javascripts/mini_pipeline_graph_dropdown.js
index c7bccd483ac..01399de4c62 100644
--- a/app/assets/javascripts/mini_pipeline_graph_dropdown.js
+++ b/app/assets/javascripts/mini_pipeline_graph_dropdown.js
@@ -1,4 +1,6 @@
/* eslint-disable no-new */
+
+import $ from 'jquery';
import flash from './flash';
import axios from './lib/utils/axios_utils';
diff --git a/app/assets/javascripts/monitoring/components/graph.vue b/app/assets/javascripts/monitoring/components/graph.vue
index 9e67a6f2146..42615d2bb8e 100644
--- a/app/assets/javascripts/monitoring/components/graph.vue
+++ b/app/assets/javascripts/monitoring/components/graph.vue
@@ -209,6 +209,7 @@
const xAxis = d3.axisBottom()
.scale(axisXScale)
+ .ticks(this.graphWidth / 120)
.tickFormat(timeScaleFormat);
const yAxis = d3.axisLeft()
diff --git a/app/assets/javascripts/mr_notes/index.js b/app/assets/javascripts/mr_notes/index.js
index 972fdb2b791..096c4ef5f31 100644
--- a/app/assets/javascripts/mr_notes/index.js
+++ b/app/assets/javascripts/mr_notes/index.js
@@ -4,13 +4,15 @@ import discussionCounter from '../notes/components/discussion_counter.vue';
import store from '../notes/stores';
export default function initMrNotes() {
- new Vue({ // eslint-disable-line
+ // eslint-disable-next-line no-new
+ new Vue({
el: '#js-vue-mr-discussions',
components: {
notesApp,
},
data() {
- const notesDataset = document.getElementById('js-vue-mr-discussions').dataset;
+ const notesDataset = document.getElementById('js-vue-mr-discussions')
+ .dataset;
return {
noteableData: JSON.parse(notesDataset.noteableData),
currentUserData: JSON.parse(notesDataset.currentUserData),
@@ -28,7 +30,8 @@ export default function initMrNotes() {
},
});
- new Vue({ // eslint-disable-line
+ // eslint-disable-next-line no-new
+ new Vue({
el: '#js-vue-discussion-counter',
components: {
discussionCounter,
diff --git a/app/assets/javascripts/namespace_select.js b/app/assets/javascripts/namespace_select.js
index aa377327107..c7a8aac79df 100644
--- a/app/assets/javascripts/namespace_select.js
+++ b/app/assets/javascripts/namespace_select.js
@@ -1,4 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, no-var, comma-dangle, object-shorthand, no-else-return, prefer-template, quotes, prefer-arrow-callback, max-len */
+
+import $ from 'jquery';
import Api from './api';
import { mergeUrlParams } from './lib/utils/url_utility';
diff --git a/app/assets/javascripts/network/branch_graph.js b/app/assets/javascripts/network/branch_graph.js
index d3edcb724f1..bd007c707f2 100644
--- a/app/assets/javascripts/network/branch_graph.js
+++ b/app/assets/javascripts/network/branch_graph.js
@@ -1,5 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, no-var, wrap-iife, quotes, comma-dangle, one-var, one-var-declaration-per-line, no-mixed-operators, no-loop-func, no-floating-decimal, consistent-return, no-unused-vars, prefer-template, prefer-arrow-callback, camelcase, max-len */
+import $ from 'jquery';
import { __ } from '../locale';
import axios from '../lib/utils/axios_utils';
import flash from '../flash';
diff --git a/app/assets/javascripts/new_branch_form.js b/app/assets/javascripts/new_branch_form.js
index 77733b67c4d..40c08ee0ace 100644
--- a/app/assets/javascripts/new_branch_form.js
+++ b/app/assets/javascripts/new_branch_form.js
@@ -1,4 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, no-var, one-var, prefer-rest-params, max-len, vars-on-top, wrap-iife, consistent-return, comma-dangle, one-var-declaration-per-line, quotes, no-return-assign, prefer-arrow-callback, prefer-template, no-shadow, no-else-return, max-len, object-shorthand */
+
+import $ from 'jquery';
import RefSelectDropdown from './ref_select_dropdown';
export default class NewBranchForm {
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index ed692ce2598..2afa4e4c1bf 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -16,6 +16,10 @@ import Autosize from 'autosize';
import 'vendor/jquery.caret'; // required by jquery.atwho
import 'vendor/jquery.atwho';
import AjaxCache from '~/lib/utils/ajax_cache';
+import Vue from 'vue';
+import syntaxHighlight from '~/syntax_highlight';
+import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
+import { __ } from '~/locale';
import axios from './lib/utils/axios_utils';
import { getLocationHash } from './lib/utils/url_utility';
import Flash from './flash';
@@ -24,7 +28,13 @@ import GLForm from './gl_form';
import loadAwardsHandler from './awards_handler';
import Autosave from './autosave';
import TaskList from './task_list';
-import { isInViewport, getPagePath, scrollToElement, isMetaKey, hasVueMRDiscussionsCookie } from './lib/utils/common_utils';
+import {
+ isInViewport,
+ getPagePath,
+ scrollToElement,
+ isMetaKey,
+ hasVueMRDiscussionsCookie,
+} from './lib/utils/common_utils';
import imageDiffHelper from './image_diff/helpers/index';
import { localTimeAgo } from './lib/utils/datetime_utility';
@@ -38,9 +48,21 @@ const MAX_VISIBLE_COMMIT_LIST_COUNT = 3;
const REGEX_QUICK_ACTIONS = /^\/\w+.*$/gm;
export default class Notes {
- static initialize(notes_url, note_ids, last_fetched_at, view, enableGFM = true) {
+ static initialize(
+ notes_url,
+ note_ids,
+ last_fetched_at,
+ view,
+ enableGFM = true,
+ ) {
if (!this.instance) {
- this.instance = new Notes(notes_url, note_ids, last_fetched_at, view, enableGFM);
+ this.instance = new Notes(
+ notes_url,
+ note_ids,
+ last_fetched_at,
+ view,
+ enableGFM,
+ );
}
}
@@ -78,11 +100,14 @@ export default class Notes {
this.updatedNotesTrackingMap = {};
this.last_fetched_at = last_fetched_at;
this.noteable_url = document.URL;
- this.notesCountBadge || (this.notesCountBadge = $('.issuable-details').find('.notes-tab .badge'));
+ this.notesCountBadge ||
+ (this.notesCountBadge = $('.issuable-details').find('.notes-tab .badge'));
this.basePollingInterval = 15000;
this.maxPollingSteps = 4;
- this.$wrapperEl = hasVueMRDiscussionsCookie() ? $(document).find('.diffs') : $(document);
+ this.$wrapperEl = hasVueMRDiscussionsCookie()
+ ? $(document).find('.diffs')
+ : $(document);
this.cleanBinding();
this.addBinding();
this.setPollingInterval();
@@ -90,15 +115,24 @@ export default class Notes {
this.taskList = new TaskList({
dataType: 'note',
fieldName: 'note',
- selector: '.notes'
+ selector: '.notes',
});
this.collapseLongCommitList();
this.setViewType(view);
// We are in the Merge Requests page so we need another edit form for Changes tab
if (getPagePath(1) === 'merge_requests') {
- $('.note-edit-form').clone()
- .addClass('mr-note-edit-form').insertAfter('.note-edit-form');
+ $('.note-edit-form')
+ .clone()
+ .addClass('mr-note-edit-form')
+ .insertAfter('.note-edit-form');
+ }
+
+ const hash = getLocationHash();
+ const $anchor = hash && document.getElementById(hash);
+
+ if ($anchor) {
+ this.loadLazyDiff({ currentTarget: $anchor });
}
}
@@ -113,36 +147,78 @@ export default class Notes {
// Reopen and close actions for Issue/MR combined with note form submit
this.$wrapperEl.on('click', '.js-comment-submit-button', this.postComment);
this.$wrapperEl.on('click', '.js-comment-save-button', this.updateComment);
- this.$wrapperEl.on('keyup input', '.js-note-text', this.updateTargetButtons);
+ this.$wrapperEl.on(
+ 'keyup input',
+ '.js-note-text',
+ this.updateTargetButtons,
+ );
// resolve a discussion
this.$wrapperEl.on('click', '.js-comment-resolve-button', this.postComment);
// remove a note (in general)
this.$wrapperEl.on('click', '.js-note-delete', this.removeNote);
// delete note attachment
- this.$wrapperEl.on('click', '.js-note-attachment-delete', this.removeAttachment);
+ this.$wrapperEl.on(
+ 'click',
+ '.js-note-attachment-delete',
+ this.removeAttachment,
+ );
// reset main target form when clicking discard
this.$wrapperEl.on('click', '.js-note-discard', this.resetMainTargetForm);
// update the file name when an attachment is selected
- this.$wrapperEl.on('change', '.js-note-attachment-input', this.updateFormAttachment);
+ this.$wrapperEl.on(
+ 'change',
+ '.js-note-attachment-input',
+ this.updateFormAttachment,
+ );
// reply to diff/discussion notes
- this.$wrapperEl.on('click', '.js-discussion-reply-button', this.onReplyToDiscussionNote);
+ this.$wrapperEl.on(
+ 'click',
+ '.js-discussion-reply-button',
+ this.onReplyToDiscussionNote,
+ );
// add diff note
this.$wrapperEl.on('click', '.js-add-diff-note-button', this.onAddDiffNote);
// add diff note for images
- this.$wrapperEl.on('click', '.js-add-image-diff-note-button', this.onAddImageDiffNote);
+ this.$wrapperEl.on(
+ 'click',
+ '.js-add-image-diff-note-button',
+ this.onAddImageDiffNote,
+ );
// hide diff note form
- this.$wrapperEl.on('click', '.js-close-discussion-note-form', this.cancelDiscussionForm);
+ this.$wrapperEl.on(
+ 'click',
+ '.js-close-discussion-note-form',
+ this.cancelDiscussionForm,
+ );
// toggle commit list
- this.$wrapperEl.on('click', '.system-note-commit-list-toggler', this.toggleCommitList);
+ this.$wrapperEl.on(
+ 'click',
+ '.system-note-commit-list-toggler',
+ this.toggleCommitList,
+ );
+
+ this.$wrapperEl.on('click', '.js-toggle-lazy-diff', this.loadLazyDiff);
// fetch notes when tab becomes visible
this.$wrapperEl.on('visibilitychange', this.visibilityChange);
// when issue status changes, we need to refresh data
this.$wrapperEl.on('issuable:change', this.refresh);
// ajax:events that happen on Form when actions like Reopen, Close are performed on Issues and MRs.
this.$wrapperEl.on('ajax:success', '.js-main-target-form', this.addNote);
- this.$wrapperEl.on('ajax:success', '.js-discussion-note-form', this.addDiscussionNote);
- this.$wrapperEl.on('ajax:success', '.js-main-target-form', this.resetMainTargetForm);
- this.$wrapperEl.on('ajax:complete', '.js-main-target-form', this.reenableTargetFormSubmitButton);
+ this.$wrapperEl.on(
+ 'ajax:success',
+ '.js-discussion-note-form',
+ this.addDiscussionNote,
+ );
+ this.$wrapperEl.on(
+ 'ajax:success',
+ '.js-main-target-form',
+ this.resetMainTargetForm,
+ );
+ this.$wrapperEl.on(
+ 'ajax:complete',
+ '.js-main-target-form',
+ this.reenableTargetFormSubmitButton,
+ );
// when a key is clicked on the notes
this.$wrapperEl.on('keydown', '.js-note-text', this.keydownNoteText);
// When the URL fragment/hash has changed, `#note_xxx`
@@ -167,6 +243,7 @@ export default class Notes {
this.$wrapperEl.off('keydown', '.js-note-text');
this.$wrapperEl.off('click', '.js-comment-resolve-button');
this.$wrapperEl.off('click', '.system-note-commit-list-toggler');
+ this.$wrapperEl.off('click', '.js-toggle-lazy-diff');
this.$wrapperEl.off('ajax:success', '.js-main-target-form');
this.$wrapperEl.off('ajax:success', '.js-discussion-note-form');
this.$wrapperEl.off('ajax:complete', '.js-main-target-form');
@@ -175,10 +252,16 @@ export default class Notes {
}
static initCommentTypeToggle(form) {
- const dropdownTrigger = form.querySelector('.js-comment-type-dropdown .dropdown-toggle');
- const dropdownList = form.querySelector('.js-comment-type-dropdown .dropdown-menu');
+ const dropdownTrigger = form.querySelector(
+ '.js-comment-type-dropdown .dropdown-toggle',
+ );
+ const dropdownList = form.querySelector(
+ '.js-comment-type-dropdown .dropdown-menu',
+ );
const noteTypeInput = form.querySelector('#note_type');
- const submitButton = form.querySelector('.js-comment-type-dropdown .js-comment-submit-button');
+ const submitButton = form.querySelector(
+ '.js-comment-type-dropdown .js-comment-submit-button',
+ );
const closeButton = form.querySelector('.js-note-target-close');
const reopenButton = form.querySelector('.js-note-target-reopen');
@@ -195,7 +278,13 @@ export default class Notes {
}
keydownNoteText(e) {
- var $textarea, discussionNoteForm, editNote, myLastNote, myLastNoteEditBtn, newText, originalText;
+ var $textarea,
+ discussionNoteForm,
+ editNote,
+ myLastNote,
+ myLastNoteEditBtn,
+ newText,
+ originalText;
if (isMetaKey(e)) {
return;
}
@@ -207,7 +296,12 @@ export default class Notes {
if ($textarea.val() !== '') {
return;
}
- myLastNote = $(`li.note[data-author-id='${gon.current_user_id}'][data-editable]:last`, $textarea.closest('.note, .notes_holder, #notes'));
+ myLastNote = $(
+ `li.note[data-author-id='${
+ gon.current_user_id
+ }'][data-editable]:last`,
+ $textarea.closest('.note, .notes_holder, #notes'),
+ );
if (myLastNote.length) {
myLastNoteEditBtn = myLastNote.find('.js-note-edit');
return myLastNoteEditBtn.trigger('click', [true, myLastNote]);
@@ -218,7 +312,9 @@ export default class Notes {
discussionNoteForm = $textarea.closest('.js-discussion-note-form');
if (discussionNoteForm.length) {
if ($textarea.val() !== '') {
- if (!confirm('Are you sure you want to cancel creating this comment?')) {
+ if (
+ !confirm('Are you sure you want to cancel creating this comment?')
+ ) {
return;
}
}
@@ -230,7 +326,9 @@ export default class Notes {
originalText = $textarea.closest('form').data('originalNote');
newText = $textarea.val();
if (originalText !== newText) {
- if (!confirm('Are you sure you want to cancel editing this comment?')) {
+ if (
+ !confirm('Are you sure you want to cancel editing this comment?')
+ ) {
return;
}
}
@@ -243,11 +341,14 @@ export default class Notes {
if (Notes.interval) {
clearInterval(Notes.interval);
}
- return Notes.interval = setInterval((function(_this) {
- return function() {
- return _this.refresh();
- };
- })(this), this.pollingInterval);
+ return (Notes.interval = setInterval(
+ (function(_this) {
+ return function() {
+ return _this.refresh();
+ };
+ })(this),
+ this.pollingInterval,
+ ));
}
refresh() {
@@ -263,20 +364,23 @@ export default class Notes {
this.refreshing = true;
- axios.get(`${this.notes_url}?html=true`, {
- headers: {
- 'X-Last-Fetched-At': this.last_fetched_at,
- },
- }).then(({ data }) => {
- const notes = data.notes;
- this.last_fetched_at = data.last_fetched_at;
- this.setPollingInterval(data.notes.length);
- $.each(notes, (i, note) => this.renderNote(note));
-
- this.refreshing = false;
- }).catch(() => {
- this.refreshing = false;
- });
+ axios
+ .get(`${this.notes_url}?html=true`, {
+ headers: {
+ 'X-Last-Fetched-At': this.last_fetched_at,
+ },
+ })
+ .then(({ data }) => {
+ const notes = data.notes;
+ this.last_fetched_at = data.last_fetched_at;
+ this.setPollingInterval(data.notes.length);
+ $.each(notes, (i, note) => this.renderNote(note));
+
+ this.refreshing = false;
+ })
+ .catch(() => {
+ this.refreshing = false;
+ });
}
/**
@@ -292,7 +396,8 @@ export default class Notes {
if (shouldReset == null) {
shouldReset = true;
}
- nthInterval = this.basePollingInterval * Math.pow(2, this.maxPollingSteps - 1);
+ nthInterval =
+ this.basePollingInterval * Math.pow(2, this.maxPollingSteps - 1);
if (shouldReset) {
this.pollingInterval = this.basePollingInterval;
} else if (this.pollingInterval < nthInterval) {
@@ -311,12 +416,17 @@ export default class Notes {
if ('emoji_award' in noteEntity.commands_changes) {
votesBlock = $('.js-awards-block').eq(0);
- loadAwardsHandler().then((awardsHandler) => {
- awardsHandler.addAwardToEmojiBar(votesBlock, noteEntity.commands_changes.emoji_award);
- awardsHandler.scrollToAwards();
- }).catch(() => {
- // ignore
- });
+ loadAwardsHandler()
+ .then(awardsHandler => {
+ awardsHandler.addAwardToEmojiBar(
+ votesBlock,
+ noteEntity.commands_changes.emoji_award,
+ );
+ awardsHandler.scrollToAwards();
+ })
+ .catch(() => {
+ // ignore
+ });
}
}
}
@@ -361,11 +471,17 @@ export default class Notes {
if (!noteEntity.valid) {
if (noteEntity.errors && noteEntity.errors.commands_only) {
- if (noteEntity.commands_changes &&
- Object.keys(noteEntity.commands_changes).length > 0) {
+ if (
+ noteEntity.commands_changes &&
+ Object.keys(noteEntity.commands_changes).length > 0
+ ) {
$notesList.find('.system-note.being-posted').remove();
}
- this.addFlash(noteEntity.errors.commands_only, 'notice', this.parentTimeline.get(0));
+ this.addFlash(
+ noteEntity.errors.commands_only,
+ 'notice',
+ this.parentTimeline.get(0),
+ );
this.refresh();
}
return;
@@ -387,28 +503,30 @@ export default class Notes {
this.setupNewNote($newNote);
this.refresh();
return this.updateNotesCount(1);
- }
- // The server can send the same update multiple times so we need to make sure to only update once per actual update.
- else if (Notes.isUpdatedNote(noteEntity, $note)) {
+ } else if (Notes.isUpdatedNote(noteEntity, $note)) {
+ // The server can send the same update multiple times so we need to make sure to only update once per actual update.
const isEditing = $note.hasClass('is-editing');
const initialContent = normalizeNewlines(
- $note.find('.original-note-content').text().trim()
+ $note
+ .find('.original-note-content')
+ .text()
+ .trim(),
);
const $textarea = $note.find('.js-note-text');
const currentContent = $textarea.val();
// There can be CRLF vs LF mismatches if we don't sanitize and compare the same way
const sanitizedNoteNote = normalizeNewlines(noteEntity.note);
- const isTextareaUntouched = currentContent === initialContent || currentContent === sanitizedNoteNote;
+ const isTextareaUntouched =
+ currentContent === initialContent ||
+ currentContent === sanitizedNoteNote;
if (isEditing && isTextareaUntouched) {
$textarea.val(noteEntity.note);
this.updatedNotesTrackingMap[noteEntity.id] = noteEntity;
- }
- else if (isEditing && !isTextareaUntouched) {
+ } else if (isEditing && !isTextareaUntouched) {
this.putConflictEditWarningInPlace(noteEntity, $note);
this.updatedNotesTrackingMap[noteEntity.id] = noteEntity;
- }
- else {
+ } else {
const $updatedNote = Notes.animateUpdateNote(noteEntity.html, $note);
this.setupNewNote($updatedNote);
}
@@ -432,17 +550,31 @@ export default class Notes {
}
this.note_ids.push(noteEntity.id);
- form = $form || $(`.js-discussion-note-form[data-discussion-id="${noteEntity.discussion_id}"]`);
- row = (form.length || !noteEntity.discussion_line_code) ? form.closest('tr') : $(`#${noteEntity.discussion_line_code}`);
+ form =
+ $form ||
+ $(
+ `.js-discussion-note-form[data-discussion-id="${
+ noteEntity.discussion_id
+ }"]`,
+ );
+ row =
+ form.length || !noteEntity.discussion_line_code
+ ? form.closest('tr')
+ : $(`#${noteEntity.discussion_line_code}`);
if (noteEntity.on_image) {
row = form;
}
lineType = this.isParallelView() ? form.find('#line_type').val() : 'old';
- diffAvatarContainer = row.prevAll('.line_holder').first().find('.js-avatar-container.' + lineType + '_line');
+ diffAvatarContainer = row
+ .prevAll('.line_holder')
+ .first()
+ .find('.js-avatar-container.' + lineType + '_line');
// is this the first note of discussion?
- discussionContainer = $(`.notes[data-discussion-id="${noteEntity.discussion_id}"]`);
+ discussionContainer = $(
+ `.notes[data-discussion-id="${noteEntity.discussion_id}"]`,
+ );
if (!discussionContainer.length) {
discussionContainer = form.closest('.discussion').find('.notes');
}
@@ -450,25 +582,42 @@ export default class Notes {
if (noteEntity.diff_discussion_html) {
var $discussion = $(noteEntity.diff_discussion_html).renderGFM();
- if (!this.isParallelView() || row.hasClass('js-temp-notes-holder') || noteEntity.on_image) {
+ if (
+ !this.isParallelView() ||
+ row.hasClass('js-temp-notes-holder') ||
+ noteEntity.on_image
+ ) {
// insert the note and the reply button after the temp row
row.after($discussion);
} else {
// Merge new discussion HTML in
- var $notes = $discussion.find(`.notes[data-discussion-id="${noteEntity.discussion_id}"]`);
- var contentContainerClass = '.' + $notes.closest('.notes_content')
- .attr('class')
- .split(' ')
- .join('.');
-
- row.find(contentContainerClass + ' .content').append($notes.closest('.content').children());
+ var $notes = $discussion.find(
+ `.notes[data-discussion-id="${noteEntity.discussion_id}"]`,
+ );
+ var contentContainerClass =
+ '.' +
+ $notes
+ .closest('.notes_content')
+ .attr('class')
+ .split(' ')
+ .join('.');
+
+ row
+ .find(contentContainerClass + ' .content')
+ .append($notes.closest('.content').children());
}
}
// Init discussion on 'Discussion' page if it is merge request page
const page = $('body').attr('data-page');
- if ((page && page.indexOf('projects:merge_request') !== -1) || !noteEntity.diff_discussion_html) {
+ if (
+ (page && page.indexOf('projects:merge_request') !== -1) ||
+ !noteEntity.diff_discussion_html
+ ) {
if (!hasVueMRDiscussionsCookie()) {
- Notes.animateAppendNote(noteEntity.discussion_html, $('.main-notes-list'));
+ Notes.animateAppendNote(
+ noteEntity.discussion_html,
+ $('.main-notes-list'),
+ );
}
}
} else {
@@ -476,7 +625,10 @@ export default class Notes {
Notes.animateAppendNote(noteEntity.html, discussionContainer);
}
- if (typeof gl.diffNotesCompileComponents !== 'undefined' && noteEntity.discussion_resolvable) {
+ if (
+ typeof gl.diffNotesCompileComponents !== 'undefined' &&
+ noteEntity.discussion_resolvable
+ ) {
gl.diffNotesCompileComponents();
this.renderDiscussionAvatar(diffAvatarContainer, noteEntity);
@@ -488,7 +640,8 @@ export default class Notes {
}
getLineHolder(changesDiscussionContainer) {
- return $(changesDiscussionContainer).closest('.notes_holder')
+ return $(changesDiscussionContainer)
+ .closest('.notes_holder')
.prevAll('.line_holder')
.first()
.get(0);
@@ -521,8 +674,14 @@ export default class Notes {
form.find('.js-errors').remove();
// reset text and preview
form.find('.js-md-write-button').click();
- form.find('.js-note-text').val('').trigger('input');
- form.find('.js-note-text').data('autosave').reset();
+ form
+ .find('.js-note-text')
+ .val('')
+ .trigger('input');
+ form
+ .find('.js-note-text')
+ .data('autosave')
+ .reset();
var event = document.createEvent('Event');
event.initEvent('autosize:update', true, false);
@@ -558,7 +717,10 @@ export default class Notes {
form.find('#note_type').val('');
form.find('#note_project_id').remove();
form.find('#in_reply_to_discussion_id').remove();
- form.find('.js-comment-resolve-button').closest('comment-and-resolve-btn').remove();
+ form
+ .find('.js-comment-resolve-button')
+ .closest('comment-and-resolve-btn')
+ .remove();
this.parentTimeline = form.parents('.timeline');
if (form.length) {
@@ -612,11 +774,17 @@ export default class Notes {
} else if ($form.hasClass('js-discussion-note-form')) {
formParentTimeline = $form.closest('.discussion-notes').find('.notes');
}
- return this.addFlash('Your comment could not be submitted! Please check your network connection and try again.', 'alert', formParentTimeline.get(0));
+ return this.addFlash(
+ 'Your comment could not be submitted! Please check your network connection and try again.',
+ 'alert',
+ formParentTimeline.get(0),
+ );
}
updateNoteError($parentTimeline) {
- new Flash('Your comment could not be updated! Please check your network connection and try again.');
+ new Flash(
+ 'Your comment could not be updated! Please check your network connection and try again.',
+ );
}
/**
@@ -665,14 +833,16 @@ export default class Notes {
}
checkContentToAllowEditing($el) {
- var initialContent = $el.find('.original-note-content').text().trim();
+ var initialContent = $el
+ .find('.original-note-content')
+ .text()
+ .trim();
var currentContent = $el.find('.js-note-text').val();
var isAllowed = true;
if (currentContent === initialContent) {
this.removeNoteEditForm($el);
- }
- else {
+ } else {
var $buttons = $el.find('.note-form-actions');
var isWidgetVisible = isInViewport($el.get(0));
@@ -734,8 +904,7 @@ export default class Notes {
this.setupNewNote($newNote);
// Now that we have taken care of the update, clear it out
delete this.updatedNotesTrackingMap[noteId];
- }
- else {
+ } else {
$note.find('.js-finish-edit-warning').hide();
this.removeNoteEditForm($note);
}
@@ -768,7 +937,9 @@ export default class Notes {
form.removeClass('current-note-edit-form');
form.find('.js-finish-edit-warning').hide();
// Replace markdown textarea text with original note text.
- return form.find('.js-note-text').val(form.find('form.edit-note').data('originalNote'));
+ return form
+ .find('.js-note-text')
+ .val(form.find('form.edit-note').data('originalNote'));
}
/**
@@ -782,58 +953,67 @@ export default class Notes {
$note = $(e.currentTarget).closest('.note');
noteElId = $note.attr('id');
noteId = $note.attr('data-note-id');
- lineHolder = $(e.currentTarget).closest('.notes[data-discussion-id]')
+ lineHolder = $(e.currentTarget)
+ .closest('.notes[data-discussion-id]')
.closest('.notes_holder')
.prev('.line_holder');
- $(`.note[id="${noteElId}"]`).each((function(_this) {
- // A same note appears in the "Discussion" and in the "Changes" tab, we have
- // to remove all. Using $('.note[id='noteId']') ensure we get all the notes,
- // where $('#noteId') would return only one.
- return function(i, el) {
- var $note, $notes;
- $note = $(el);
- $notes = $note.closest('.discussion-notes');
- const discussionId = $('.notes', $notes).data('discussionId');
-
- if (typeof gl.diffNotesCompileComponents !== 'undefined') {
- if (gl.diffNoteApps[noteElId]) {
- gl.diffNoteApps[noteElId].$destroy();
+ $(`.note[id="${noteElId}"]`).each(
+ (function(_this) {
+ // A same note appears in the "Discussion" and in the "Changes" tab, we have
+ // to remove all. Using $('.note[id='noteId']') ensure we get all the notes,
+ // where $('#noteId') would return only one.
+ return function(i, el) {
+ var $note, $notes;
+ $note = $(el);
+ $notes = $note.closest('.discussion-notes');
+ const discussionId = $('.notes', $notes).data('discussionId');
+
+ if (typeof gl.diffNotesCompileComponents !== 'undefined') {
+ if (gl.diffNoteApps[noteElId]) {
+ gl.diffNoteApps[noteElId].$destroy();
+ }
}
- }
-
- $note.remove();
-
- // check if this is the last note for this line
- if ($notes.find('.note').length === 0) {
- var notesTr = $notes.closest('tr');
-
- // "Discussions" tab
- $notes.closest('.timeline-entry').remove();
-
- $(`.js-diff-avatars-${discussionId}`).trigger('remove.vue');
-
- // The notes tr can contain multiple lists of notes, like on the parallel diff
- // notesTr does not exist for image diffs
- if (notesTr.find('.discussion-notes').length > 1 || notesTr.length === 0) {
- const $diffFile = $notes.closest('.diff-file');
- if ($diffFile.length > 0) {
- const removeBadgeEvent = new CustomEvent('removeBadge.imageDiff', {
- detail: {
- // badgeNumber's start with 1 and index starts with 0
- badgeNumber: $notes.index() + 1,
- },
- });
- $diffFile[0].dispatchEvent(removeBadgeEvent);
+ $note.remove();
+
+ // check if this is the last note for this line
+ if ($notes.find('.note').length === 0) {
+ var notesTr = $notes.closest('tr');
+
+ // "Discussions" tab
+ $notes.closest('.timeline-entry').remove();
+
+ $(`.js-diff-avatars-${discussionId}`).trigger('remove.vue');
+
+ // The notes tr can contain multiple lists of notes, like on the parallel diff
+ // notesTr does not exist for image diffs
+ if (
+ notesTr.find('.discussion-notes').length > 1 ||
+ notesTr.length === 0
+ ) {
+ const $diffFile = $notes.closest('.diff-file');
+ if ($diffFile.length > 0) {
+ const removeBadgeEvent = new CustomEvent(
+ 'removeBadge.imageDiff',
+ {
+ detail: {
+ // badgeNumber's start with 1 and index starts with 0
+ badgeNumber: $notes.index() + 1,
+ },
+ },
+ );
+
+ $diffFile[0].dispatchEvent(removeBadgeEvent);
+ }
+
+ $notes.remove();
+ } else if (notesTr.length > 0) {
+ notesTr.remove();
}
-
- $notes.remove();
- } else if (notesTr.length > 0) {
- notesTr.remove();
}
- }
- };
- })(this));
+ };
+ })(this),
+ );
Notes.refreshVueNotes();
Notes.checkMergeRequestStatus();
@@ -915,7 +1095,12 @@ export default class Notes {
// DiffNote
form.find('#note_position').val(dataHolder.attr('data-position'));
- form.find('.js-note-discard').show().removeClass('js-note-discard').addClass('js-close-discussion-note-form').text(form.find('.js-close-discussion-note-form').data('cancelText'));
+ form
+ .find('.js-note-discard')
+ .show()
+ .removeClass('js-note-discard')
+ .addClass('js-close-discussion-note-form')
+ .text(form.find('.js-close-discussion-note-form').data('cancelText'));
form.find('.js-note-target-close').remove();
form.find('.js-note-new-discussion').remove();
this.setupNoteForm(form);
@@ -951,7 +1136,7 @@ export default class Notes {
this.toggleDiffNote({
target: $link,
lineType: link.dataset.lineType,
- showReplyInput
+ showReplyInput,
});
}
@@ -967,7 +1152,9 @@ export default class Notes {
// Setup comment form
let newForm;
- const $noteContainer = $link.closest('.diff-viewer').find('.note-container');
+ const $noteContainer = $link
+ .closest('.diff-viewer')
+ .find('.note-container');
const $form = $noteContainer.find('> .discussion-form');
if ($form.length === 0) {
@@ -980,13 +1167,17 @@ export default class Notes {
this.setupDiscussionNoteForm($link, newForm);
}
- toggleDiffNote({
- target,
- lineType,
- forceShow,
- showReplyInput = false,
- }) {
- var $link, addForm, hasNotes, newForm, noteForm, replyButton, row, rowCssToAdd, targetContent, isDiffCommentAvatar;
+ toggleDiffNote({ target, lineType, forceShow, showReplyInput = false }) {
+ var $link,
+ addForm,
+ hasNotes,
+ newForm,
+ noteForm,
+ replyButton,
+ row,
+ rowCssToAdd,
+ targetContent,
+ isDiffCommentAvatar;
$link = $(target);
row = $link.closest('tr');
const nextRow = row.next();
@@ -998,11 +1189,13 @@ export default class Notes {
hasNotes = nextRow.is('.notes_holder');
addForm = false;
let lineTypeSelector = '';
- rowCssToAdd = '<tr class="notes_holder js-temp-notes-holder"><td class="notes_line" colspan="2"></td><td class="notes_content"><div class="content"></div></td></tr>';
+ rowCssToAdd =
+ '<tr class="notes_holder js-temp-notes-holder"><td class="notes_line" colspan="2"></td><td class="notes_content"><div class="content"></div></td></tr>';
// In parallel view, look inside the correct left/right pane
if (this.isParallelView()) {
lineTypeSelector = `.${lineType}`;
- rowCssToAdd = '<tr class="notes_holder js-temp-notes-holder"><td class="notes_line old"></td><td class="notes_content parallel old"><div class="content"></div></td><td class="notes_line new"></td><td class="notes_content parallel new"><div class="content"></div></td></tr>';
+ rowCssToAdd =
+ '<tr class="notes_holder js-temp-notes-holder"><td class="notes_line old"></td><td class="notes_content parallel old"><div class="content"></div></td><td class="notes_line new"></td><td class="notes_content parallel new"><div class="content"></div></td></tr>';
}
const notesContentSelector = `.notes_content${lineTypeSelector} .content`;
let notesContent = targetRow.find(notesContentSelector);
@@ -1030,7 +1223,9 @@ export default class Notes {
notesContent = targetRow.find(notesContentSelector);
addForm = true;
} else {
- const isCurrentlyShown = targetRow.find('.content:not(:empty)').is(':visible');
+ const isCurrentlyShown = targetRow
+ .find('.content:not(:empty)')
+ .is(':visible');
const isForced = forceShow === true || forceShow === false;
const showNow = forceShow === true || (!isCurrentlyShown && !isForced);
@@ -1057,11 +1252,12 @@ export default class Notes {
row = form.closest('tr');
glForm = form.data('glForm');
glForm.destroy();
- form.find('.js-note-text').data('autosave').reset();
- // show the reply button (will only work for replies)
form
- .prev('.discussion-reply-holder')
- .show();
+ .find('.js-note-text')
+ .data('autosave')
+ .reset();
+ // show the reply button (will only work for replies)
+ form.prev('.discussion-reply-holder').show();
if (row.is('.js-temp-notes-holder')) {
// remove temporary row for diff lines
return row.remove();
@@ -1102,7 +1298,9 @@ export default class Notes {
var filename, form;
form = $(this).closest('form');
// get only the basename
- filename = $(this).val().replace(/^.*[\\\/]/, '');
+ filename = $(this)
+ .val()
+ .replace(/^.*[\\\/]/, '');
return form.find('.js-attachment-filename').text(filename);
}
@@ -1174,12 +1372,16 @@ export default class Notes {
this.glForm = new GLForm($editForm.find('form'), this.enableGFM);
- $editForm.find('form')
+ $editForm
+ .find('form')
.attr('action', `${postUrl}?html=true`)
.attr('data-remote', 'true');
$editForm.find('.js-form-target-id').val(targetId);
$editForm.find('.js-form-target-type').val(targetType);
- $editForm.find('.js-note-text').focus().val(originalContent);
+ $editForm
+ .find('.js-note-text')
+ .focus()
+ .val(originalContent);
$editForm.find('.js-md-write-button').trigger('click');
$editForm.find('.referenced-users').hide();
}
@@ -1188,7 +1390,9 @@ export default class Notes {
if ($note.find('.js-conflict-edit-warning').length === 0) {
const $alert = $(`<div class="js-conflict-edit-warning alert alert-danger">
This comment has changed since you started editing, please review the
- <a href="#note_${noteEntity.id}" target="_blank" rel="noopener noreferrer">
+ <a href="#note_${
+ noteEntity.id
+ }" target="_blank" rel="noopener noreferrer">
updated comment
</a>
to ensure information is not lost
@@ -1198,14 +1402,79 @@ export default class Notes {
}
updateNotesCount(updateCount) {
- return this.notesCountBadge.text(parseInt(this.notesCountBadge.text(), 10) + updateCount);
+ return this.notesCountBadge.text(
+ parseInt(this.notesCountBadge.text(), 10) + updateCount,
+ );
+ }
+
+ static renderPlaceholderComponent($container) {
+ const el = $container.find('.js-code-placeholder').get(0);
+ new Vue({
+ // eslint-disable-line no-new
+ el,
+ components: {
+ SkeletonLoadingContainer,
+ },
+ render(createElement) {
+ return createElement('skeleton-loading-container');
+ },
+ });
+ }
+
+ static renderDiffContent($container, data) {
+ const { discussion_html } = data;
+ const lines = $(discussion_html).find('.line_holder');
+ lines.addClass('fade-in');
+ $container.find('tbody').prepend(lines);
+ const fileHolder = $container.find('.file-holder');
+ $container.find('.line-holder-placeholder').remove();
+ syntaxHighlight(fileHolder);
+ }
+
+ static renderDiffError($container) {
+ $container.find('.line_content').html(
+ $(`
+ <div class="nothing-here-block">
+ ${__(
+ 'Unable to load the diff.',
+ )} <a class="js-toggle-lazy-diff" href="javascript:void(0)">Try again</a>?
+ </div>
+ `),
+ );
+ }
+
+ loadLazyDiff(e) {
+ const $container = $(e.currentTarget).closest('.js-toggle-container');
+ Notes.renderPlaceholderComponent($container);
+
+ $container.find('.js-toggle-lazy-diff').removeClass('js-toggle-lazy-diff');
+
+ const tableEl = $container.find('tbody');
+ if (tableEl.length === 0) return;
+
+ const fileHolder = $container.find('.file-holder');
+ const url = fileHolder.data('linesPath');
+
+ axios
+ .get(url)
+ .then(({ data }) => {
+ Notes.renderDiffContent($container, data);
+ })
+ .catch(() => {
+ Notes.renderDiffError($container);
+ });
}
toggleCommitList(e) {
const $element = $(e.currentTarget);
- const $closestSystemCommitList = $element.siblings('.system-note-commit-list');
+ const $closestSystemCommitList = $element.siblings(
+ '.system-note-commit-list',
+ );
- $element.find('.fa').toggleClass('fa-angle-down').toggleClass('fa-angle-up');
+ $element
+ .find('.fa')
+ .toggleClass('fa-angle-down')
+ .toggleClass('fa-angle-up');
$closestSystemCommitList.toggleClass('hide-shade');
}
@@ -1215,11 +1484,17 @@ export default class Notes {
* intrusive.
*/
collapseLongCommitList() {
- const systemNotes = $('#notes-list').find('li.system-note').has('ul');
+ const systemNotes = $('#notes-list')
+ .find('li.system-note')
+ .has('ul');
$.each(systemNotes, function(index, systemNote) {
const $systemNote = $(systemNote);
- const headerMessage = $systemNote.find('.note-text').find('p:first').text().replace(':', '');
+ const headerMessage = $systemNote
+ .find('.note-text')
+ .find('p:first')
+ .text()
+ .replace(':', '');
$systemNote.find('.note-header .system-note-message').html(headerMessage);
@@ -1227,7 +1502,9 @@ export default class Notes {
$systemNote.find('.note-text').addClass('system-note-commit-list');
$systemNote.find('.system-note-commit-list-toggler').show();
} else {
- $systemNote.find('.note-text').addClass('system-note-commit-list hide-shade');
+ $systemNote
+ .find('.note-text')
+ .addClass('system-note-commit-list hide-shade');
}
});
}
@@ -1245,14 +1522,10 @@ export default class Notes {
cleanForm($form) {
// Remove JS classes that are not needed here
- $form
- .find('.js-comment-type-dropdown')
- .removeClass('btn-group');
+ $form.find('.js-comment-type-dropdown').removeClass('btn-group');
// Remove dropdown
- $form
- .find('.dropdown-menu')
- .remove();
+ $form.find('.dropdown-menu').remove();
return $form;
}
@@ -1271,7 +1544,11 @@ export default class Notes {
// There can be CRLF vs LF mismatches if we don't sanitize and compare the same way
const sanitizedNoteEntityText = normalizeNewlines(noteEntity.note.trim());
const currentNoteText = normalizeNewlines(
- $note.find('.original-note-content').first().text().trim()
+ $note
+ .find('.original-note-content')
+ .first()
+ .text()
+ .trim(),
);
return sanitizedNoteEntityText !== currentNoteText;
}
@@ -1361,7 +1638,14 @@ export default class Notes {
* Once comment is _actually_ posted on server, we will have final element
* in response that we will show in place of this temporary element.
*/
- createPlaceholderNote({ formContent, uniqueId, isDiscussionNote, currentUsername, currentUserFullname, currentUserAvatar }) {
+ createPlaceholderNote({
+ formContent,
+ uniqueId,
+ isDiscussionNote,
+ currentUsername,
+ currentUserFullname,
+ currentUserAvatar,
+ }) {
const discussionClass = isDiscussionNote ? 'discussion' : '';
const $tempNote = $(
`<li id="${uniqueId}" class="note being-posted fade-in-half timeline-entry">
@@ -1375,8 +1659,12 @@ export default class Notes {
<div class="note-header">
<div class="note-header-info">
<a href="/${_.escape(currentUsername)}">
- <span class="hidden-xs">${_.escape(currentUsername)}</span>
- <span class="note-headline-light">${_.escape(currentUsername)}</span>
+ <span class="hidden-xs">${_.escape(
+ currentUsername,
+ )}</span>
+ <span class="note-headline-light">${_.escape(
+ currentUsername,
+ )}</span>
</a>
</div>
</div>
@@ -1387,11 +1675,13 @@ export default class Notes {
</div>
</div>
</div>
- </li>`
+ </li>`,
);
$tempNote.find('.hidden-xs').text(_.escape(currentUserFullname));
- $tempNote.find('.note-headline-light').text(`@${_.escape(currentUsername)}`);
+ $tempNote
+ .find('.note-headline-light')
+ .text(`@${_.escape(currentUsername)}`);
return $tempNote;
}
@@ -1407,7 +1697,7 @@ export default class Notes {
<i>${formContent}</i>
</div>
</div>
- </li>`
+ </li>`,
);
return $tempNote;
@@ -1439,11 +1729,22 @@ export default class Notes {
const $submitBtn = $(e.target);
let $form = $submitBtn.parents('form');
const $closeBtn = $form.find('.js-note-target-close');
- const isDiscussionNote = $submitBtn.parent().find('li.droplab-item-selected').attr('id') === 'discussion';
+ const isDiscussionNote =
+ $submitBtn
+ .parent()
+ .find('li.droplab-item-selected')
+ .attr('id') === 'discussion';
const isMainForm = $form.hasClass('js-main-target-form');
const isDiscussionForm = $form.hasClass('js-discussion-note-form');
- const isDiscussionResolve = $submitBtn.hasClass('js-comment-resolve-button');
- const { formData, formContent, formAction, formContentOriginal } = this.getFormData($form);
+ const isDiscussionResolve = $submitBtn.hasClass(
+ 'js-comment-resolve-button',
+ );
+ const {
+ formData,
+ formContent,
+ formAction,
+ formContentOriginal,
+ } = this.getFormData($form);
let noteUniqueId;
let systemNoteUniqueId;
let hasQuickActions = false;
@@ -1473,23 +1774,30 @@ export default class Notes {
// Show placeholder note
if (tempFormContent) {
noteUniqueId = _.uniqueId('tempNote_');
- $notesContainer.append(this.createPlaceholderNote({
- formContent: tempFormContent,
- uniqueId: noteUniqueId,
- isDiscussionNote,
- currentUsername: gon.current_username,
- currentUserFullname: gon.current_user_fullname,
- currentUserAvatar: gon.current_user_avatar_url,
- }));
+ $notesContainer.append(
+ this.createPlaceholderNote({
+ formContent: tempFormContent,
+ uniqueId: noteUniqueId,
+ isDiscussionNote,
+ currentUsername: gon.current_username,
+ currentUserFullname: gon.current_user_fullname,
+ currentUserAvatar: gon.current_user_avatar_url,
+ }),
+ );
}
// Show placeholder system note
if (hasQuickActions) {
systemNoteUniqueId = _.uniqueId('tempSystemNote_');
- $notesContainer.append(this.createPlaceholderSystemNote({
- formContent: this.getQuickActionDescription(formContent, AjaxCache.get(gl.GfmAutoComplete.dataSources.commands)),
- uniqueId: systemNoteUniqueId,
- }));
+ $notesContainer.append(
+ this.createPlaceholderSystemNote({
+ formContent: this.getQuickActionDescription(
+ formContent,
+ AjaxCache.get(gl.GfmAutoComplete.dataSources.commands),
+ ),
+ uniqueId: systemNoteUniqueId,
+ }),
+ );
}
// Clear the form textarea
@@ -1503,8 +1811,9 @@ export default class Notes {
/* eslint-disable promise/catch-or-return */
// Make request to submit comment on server
- axios.post(`${formAction}?html=true`, formData)
- .then((res) => {
+ axios
+ .post(`${formAction}?html=true`, formData)
+ .then(res => {
const note = res.data;
// Submission successful! remove placeholder
@@ -1521,7 +1830,9 @@ export default class Notes {
// Reset cached commands list when command is applied
if (hasQuickActions) {
- $form.find('textarea.js-note-text').trigger('clear-commands-cache.atwho');
+ $form
+ .find('textarea.js-note-text')
+ .trigger('clear-commands-cache.atwho');
}
// Clear previous form errors
@@ -1566,11 +1877,14 @@ export default class Notes {
// append flash-container to the Notes list
if ($notesContainer.length) {
- $notesContainer.append('<div class="flash-container" style="display: none;"></div>');
+ $notesContainer.append(
+ '<div class="flash-container" style="display: none;"></div>',
+ );
}
Notes.refreshVueNotes();
- } else if (isMainForm) { // Check if this was main thread comment
+ } else if (isMainForm) {
+ // Check if this was main thread comment
// Show final note element on UI and perform form and action buttons cleanup
this.addNote($form, note);
this.reenableTargetFormSubmitButton(e);
@@ -1581,7 +1895,8 @@ export default class Notes {
}
$form.trigger('ajax:success', [note]);
- }).catch(() => {
+ })
+ .catch(() => {
// Submission failed, remove placeholder note and show Flash error message
$notesContainer.find(`#${noteUniqueId}`).remove();
@@ -1601,7 +1916,9 @@ export default class Notes {
// Show form again on UI on failure
if (isDiscussionForm && $notesContainer.length) {
- const replyButton = $notesContainer.parent().find('.js-discussion-reply-button');
+ const replyButton = $notesContainer
+ .parent()
+ .find('.js-discussion-reply-button');
this.replyToDiscussionNote(replyButton[0]);
$form = $notesContainer.parent().find('form');
}
@@ -1646,12 +1963,19 @@ export default class Notes {
// Show updated comment content temporarily
$noteBodyText.html(formContent);
- $editingNote.removeClass('is-editing fade-in-full').addClass('being-posted fade-in-half');
- $editingNote.find('.note-headline-meta a').html('<i class="fa fa-spinner fa-spin" aria-label="Comment is being updated" aria-hidden="true"></i>');
+ $editingNote
+ .removeClass('is-editing fade-in-full')
+ .addClass('being-posted fade-in-half');
+ $editingNote
+ .find('.note-headline-meta a')
+ .html(
+ '<i class="fa fa-spinner fa-spin" aria-label="Comment is being updated" aria-hidden="true"></i>',
+ );
/* eslint-disable promise/catch-or-return */
// Make request to update comment on server
- axios.post(`${formAction}?html=true`, formData)
+ axios
+ .post(`${formAction}?html=true`, formData)
.then(({ data }) => {
// Submission successful! render final note element
this.updateNote(data, $editingNote);
diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue
index b85c1a6ad72..90dcafd75b7 100644
--- a/app/assets/javascripts/notes/components/comment_form.vue
+++ b/app/assets/javascripts/notes/components/comment_form.vue
@@ -1,297 +1,311 @@
<script>
- import { mapActions, mapGetters } from 'vuex';
- import _ from 'underscore';
- import Autosize from 'autosize';
- import { __, sprintf } from '~/locale';
- import Flash from '../../flash';
- import Autosave from '../../autosave';
- import TaskList from '../../task_list';
- import { capitalizeFirstCharacter, convertToCamelCase } from '../../lib/utils/text_utility';
- import * as constants from '../constants';
- import eventHub from '../event_hub';
- import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
- import markdownField from '../../vue_shared/components/markdown/field.vue';
- import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
- import loadingButton from '../../vue_shared/components/loading_button.vue';
- import noteSignedOutWidget from './note_signed_out_widget.vue';
- import discussionLockedWidget from './discussion_locked_widget.vue';
- import issuableStateMixin from '../mixins/issuable_state';
+import $ from 'jquery';
+import { mapActions, mapGetters, mapState } from 'vuex';
+import _ from 'underscore';
+import Autosize from 'autosize';
+import { __, sprintf } from '~/locale';
+import Flash from '../../flash';
+import Autosave from '../../autosave';
+import TaskList from '../../task_list';
+import {
+ capitalizeFirstCharacter,
+ convertToCamelCase,
+} from '../../lib/utils/text_utility';
+import * as constants from '../constants';
+import eventHub from '../event_hub';
+import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
+import markdownField from '../../vue_shared/components/markdown/field.vue';
+import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
+import loadingButton from '../../vue_shared/components/loading_button.vue';
+import noteSignedOutWidget from './note_signed_out_widget.vue';
+import discussionLockedWidget from './discussion_locked_widget.vue';
+import issuableStateMixin from '../mixins/issuable_state';
- export default {
- name: 'CommentForm',
- components: {
- issueWarning,
- noteSignedOutWidget,
- discussionLockedWidget,
- markdownField,
- userAvatarLink,
- loadingButton,
+export default {
+ name: 'CommentForm',
+ components: {
+ issueWarning,
+ noteSignedOutWidget,
+ discussionLockedWidget,
+ markdownField,
+ userAvatarLink,
+ loadingButton,
+ },
+ mixins: [issuableStateMixin],
+ props: {
+ noteableType: {
+ type: String,
+ required: true,
},
- mixins: [
- issuableStateMixin,
- ],
- props: {
- noteableType: {
- type: String,
- required: true,
- },
+ },
+ data() {
+ return {
+ note: '',
+ noteType: constants.COMMENT,
+ isSubmitting: false,
+ isSubmitButtonDisabled: true,
+ };
+ },
+ computed: {
+ ...mapGetters([
+ 'getCurrentUserLastNote',
+ 'getUserData',
+ 'getNoteableData',
+ 'getNotesData',
+ 'openState',
+ ]),
+ ...mapState(['isToggleStateButtonLoading']),
+ noteableDisplayName() {
+ return this.noteableType.replace(/_/g, ' ');
},
- data() {
- return {
- note: '',
- noteType: constants.COMMENT,
- isSubmitting: false,
- isSubmitButtonDisabled: true,
- };
+ isLoggedIn() {
+ return this.getUserData.id;
+ },
+ commentButtonTitle() {
+ return this.noteType === constants.COMMENT
+ ? 'Comment'
+ : 'Start discussion';
+ },
+ isOpen() {
+ return (
+ this.openState === constants.OPENED ||
+ this.openState === constants.REOPENED
+ );
},
- computed: {
- ...mapGetters([
- 'getCurrentUserLastNote',
- 'getUserData',
- 'getNoteableData',
- 'getNotesData',
- 'openState',
- ]),
- noteableDisplayName() {
- return this.noteableType.replace(/_/g, ' ');
- },
- isLoggedIn() {
- return this.getUserData.id;
- },
- commentButtonTitle() {
- return this.noteType === constants.COMMENT ? 'Comment' : 'Start discussion';
- },
- isOpen() {
- return this.openState === constants.OPENED || this.openState === constants.REOPENED;
- },
- canCreateNote() {
- return this.getNoteableData.current_user.can_create_note;
- },
- issueActionButtonTitle() {
- const openOrClose = this.isOpen ? 'close' : 'reopen';
+ canCreateNote() {
+ return this.getNoteableData.current_user.can_create_note;
+ },
+ issueActionButtonTitle() {
+ const openOrClose = this.isOpen ? 'close' : 'reopen';
- if (this.note.length) {
- return sprintf(
- __('%{actionText} & %{openOrClose} %{noteable}'),
- {
- actionText: this.commentButtonTitle,
- openOrClose,
- noteable: this.noteableDisplayName,
- },
- );
- }
+ if (this.note.length) {
+ return sprintf(__('%{actionText} & %{openOrClose} %{noteable}'), {
+ actionText: this.commentButtonTitle,
+ openOrClose,
+ noteable: this.noteableDisplayName,
+ });
+ }
- return sprintf(
- __('%{openOrClose} %{noteable}'),
- {
- openOrClose: capitalizeFirstCharacter(openOrClose),
- noteable: this.noteableDisplayName,
- },
- );
- },
- actionButtonClassNames() {
- return {
- 'btn-reopen': !this.isOpen,
- 'btn-close': this.isOpen,
- 'js-note-target-close': this.isOpen,
- 'js-note-target-reopen': !this.isOpen,
- };
- },
- markdownDocsPath() {
- return this.getNotesData.markdownDocsPath;
- },
- quickActionsDocsPath() {
- return this.getNotesData.quickActionsDocsPath;
- },
- markdownPreviewPath() {
- return this.getNoteableData.preview_note_path;
- },
- author() {
- return this.getUserData;
- },
- canUpdateIssue() {
- return this.getNoteableData.current_user.can_update;
- },
- endpoint() {
- return this.getNoteableData.create_note_path;
- },
+ return sprintf(__('%{openOrClose} %{noteable}'), {
+ openOrClose: capitalizeFirstCharacter(openOrClose),
+ noteable: this.noteableDisplayName,
+ });
},
- watch: {
- note(newNote) {
- this.setIsSubmitButtonDisabled(newNote, this.isSubmitting);
- },
- isSubmitting(newValue) {
- this.setIsSubmitButtonDisabled(this.note, newValue);
- },
+ actionButtonClassNames() {
+ return {
+ 'btn-reopen': !this.isOpen,
+ 'btn-close': this.isOpen,
+ 'js-note-target-close': this.isOpen,
+ 'js-note-target-reopen': !this.isOpen,
+ };
},
- mounted() {
- // jQuery is needed here because it is a custom event being dispatched with jQuery.
- $(document).on('issuable:change', (e, isClosed) => {
- this.toggleIssueLocalState(isClosed ? constants.CLOSED : constants.REOPENED);
- });
+ markdownDocsPath() {
+ return this.getNotesData.markdownDocsPath;
+ },
+ quickActionsDocsPath() {
+ return this.getNotesData.quickActionsDocsPath;
+ },
+ markdownPreviewPath() {
+ return this.getNoteableData.preview_note_path;
+ },
+ author() {
+ return this.getUserData;
+ },
+ canUpdateIssue() {
+ return this.getNoteableData.current_user.can_update;
+ },
+ endpoint() {
+ return this.getNoteableData.create_note_path;
+ },
+ },
+ watch: {
+ note(newNote) {
+ this.setIsSubmitButtonDisabled(newNote, this.isSubmitting);
+ },
+ isSubmitting(newValue) {
+ this.setIsSubmitButtonDisabled(this.note, newValue);
+ },
+ },
+ mounted() {
+ // jQuery is needed here because it is a custom event being dispatched with jQuery.
+ $(document).on('issuable:change', (e, isClosed) => {
+ this.toggleIssueLocalState(
+ isClosed ? constants.CLOSED : constants.REOPENED,
+ );
+ });
- this.initAutoSave();
- this.initTaskList();
+ this.initAutoSave();
+ this.initTaskList();
+ },
+ methods: {
+ ...mapActions([
+ 'saveNote',
+ 'stopPolling',
+ 'restartPolling',
+ 'removePlaceholderNotes',
+ 'closeIssue',
+ 'reopenIssue',
+ 'toggleIssueLocalState',
+ 'toggleStateButtonLoading',
+ ]),
+ setIsSubmitButtonDisabled(note, isSubmitting) {
+ if (!_.isEmpty(note) && !isSubmitting) {
+ this.isSubmitButtonDisabled = false;
+ } else {
+ this.isSubmitButtonDisabled = true;
+ }
},
- methods: {
- ...mapActions([
- 'saveNote',
- 'stopPolling',
- 'restartPolling',
- 'removePlaceholderNotes',
- 'closeIssue',
- 'reopenIssue',
- 'toggleIssueLocalState',
- ]),
- setIsSubmitButtonDisabled(note, isSubmitting) {
- if (!_.isEmpty(note) && !isSubmitting) {
- this.isSubmitButtonDisabled = false;
- } else {
- this.isSubmitButtonDisabled = true;
- }
- },
- handleSave(withIssueAction) {
- this.isSubmitting = true;
+ handleSave(withIssueAction) {
+ this.isSubmitting = true;
- if (this.note.length) {
- const noteData = {
- endpoint: this.endpoint,
- flashContainer: this.$el,
- data: {
- note: {
- noteable_type: this.noteableType,
- noteable_id: this.getNoteableData.id,
- note: this.note,
- },
+ if (this.note.length) {
+ const noteData = {
+ endpoint: this.endpoint,
+ flashContainer: this.$el,
+ data: {
+ note: {
+ noteable_type: this.noteableType,
+ noteable_id: this.getNoteableData.id,
+ note: this.note,
},
- };
+ },
+ };
- if (this.noteType === constants.DISCUSSION) {
- noteData.data.note.type = constants.DISCUSSION_NOTE;
- }
- this.note = ''; // Empty textarea while being requested. Repopulate in catch
- this.resizeTextarea();
- this.stopPolling();
+ if (this.noteType === constants.DISCUSSION) {
+ noteData.data.note.type = constants.DISCUSSION_NOTE;
+ }
- this.saveNote(noteData)
- .then((res) => {
- this.isSubmitting = false;
- this.restartPolling();
+ this.note = ''; // Empty textarea while being requested. Repopulate in catch
+ this.resizeTextarea();
+ this.stopPolling();
- if (res.errors) {
- if (res.errors.commands_only) {
- this.discard();
- } else {
- Flash(
- 'Something went wrong while adding your comment. Please try again.',
- 'alert',
- this.$refs.commentForm,
- );
- }
- } else {
+ this.saveNote(noteData)
+ .then(res => {
+ this.enableButton();
+ this.restartPolling();
+
+ if (res.errors) {
+ if (res.errors.commands_only) {
this.discard();
+ } else {
+ Flash(
+ 'Something went wrong while adding your comment. Please try again.',
+ 'alert',
+ this.$refs.commentForm,
+ );
}
+ } else {
+ this.discard();
+ }
- if (withIssueAction) {
- this.toggleIssueState();
- }
- })
- .catch(() => {
- this.isSubmitting = false;
- this.discard(false);
- const msg =
- `Your comment could not be submitted!
+ if (withIssueAction) {
+ this.toggleIssueState();
+ }
+ })
+ .catch(() => {
+ this.enableButton();
+ this.discard(false);
+ const msg = `Your comment could not be submitted!
Please check your network connection and try again.`;
- Flash(msg, 'alert', this.$el);
- this.note = noteData.data.note.note; // Restore textarea content.
- this.removePlaceholderNotes();
- });
- } else {
- this.toggleIssueState();
- }
- },
- enableButton() {
- this.isSubmitting = false;
- },
- toggleIssueState() {
- if (this.isOpen) {
- this.closeIssue()
- .then(() => this.enableButton())
- .catch(() => {
- this.enableButton();
- Flash(
- sprintf(
- __('Something went wrong while closing the %{issuable}. Please try again later'),
- { issuable: this.noteableDisplayName },
+ Flash(msg, 'alert', this.$el);
+ this.note = noteData.data.note.note; // Restore textarea content.
+ this.removePlaceholderNotes();
+ });
+ } else {
+ this.toggleIssueState();
+ }
+ },
+ enableButton() {
+ this.isSubmitting = false;
+ },
+ toggleIssueState() {
+ if (this.isOpen) {
+ this.closeIssue()
+ .then(() => this.enableButton())
+ .catch(() => {
+ this.enableButton();
+ this.toggleStateButtonLoading(false);
+ Flash(
+ sprintf(
+ __(
+ 'Something went wrong while closing the %{issuable}. Please try again later',
),
- );
- });
- } else {
- this.reopenIssue()
- .then(() => this.enableButton())
- .catch(() => {
- this.enableButton();
- Flash(
- sprintf(
- __('Something went wrong while reopening the %{issuable}. Please try again later'),
- { issuable: this.noteableDisplayName },
+ { issuable: this.noteableDisplayName },
+ ),
+ );
+ });
+ } else {
+ this.reopenIssue()
+ .then(() => this.enableButton())
+ .catch(() => {
+ this.enableButton();
+ this.toggleStateButtonLoading(false);
+ Flash(
+ sprintf(
+ __(
+ 'Something went wrong while reopening the %{issuable}. Please try again later',
),
- );
- });
- }
- },
- discard(shouldClear = true) {
- // `blur` is needed to clear slash commands autocomplete cache if event fired.
- // `focus` is needed to remain cursor in the textarea.
- this.$refs.textarea.blur();
- this.$refs.textarea.focus();
+ { issuable: this.noteableDisplayName },
+ ),
+ );
+ });
+ }
+ },
+ discard(shouldClear = true) {
+ // `blur` is needed to clear slash commands autocomplete cache if event fired.
+ // `focus` is needed to remain cursor in the textarea.
+ this.$refs.textarea.blur();
+ this.$refs.textarea.focus();
- if (shouldClear) {
- this.note = '';
- this.resizeTextarea();
- this.$refs.markdownField.previewMarkdown = false;
- }
+ if (shouldClear) {
+ this.note = '';
+ this.resizeTextarea();
+ this.$refs.markdownField.previewMarkdown = false;
+ }
- this.autosave.reset();
- },
- setNoteType(type) {
- this.noteType = type;
- },
- editCurrentUserLastNote() {
- if (this.note === '') {
- const lastNote = this.getCurrentUserLastNote;
+ this.autosave.reset();
+ },
+ setNoteType(type) {
+ this.noteType = type;
+ },
+ editCurrentUserLastNote() {
+ if (this.note === '') {
+ const lastNote = this.getCurrentUserLastNote;
- if (lastNote) {
- eventHub.$emit('enterEditMode', {
- noteId: lastNote.id,
- });
- }
+ if (lastNote) {
+ eventHub.$emit('enterEditMode', {
+ noteId: lastNote.id,
+ });
}
- },
- initAutoSave() {
- if (this.isLoggedIn) {
- const noteableType = capitalizeFirstCharacter(convertToCamelCase(this.noteableType));
+ }
+ },
+ initAutoSave() {
+ if (this.isLoggedIn) {
+ const noteableType = capitalizeFirstCharacter(
+ convertToCamelCase(this.noteableType),
+ );
- this.autosave = new Autosave(
- $(this.$refs.textarea),
- ['Note', noteableType, this.getNoteableData.id],
- );
- }
- },
- initTaskList() {
- return new TaskList({
- dataType: 'note',
- fieldName: 'note',
- selector: '.notes',
- });
- },
- resizeTextarea() {
- this.$nextTick(() => {
- Autosize.update(this.$refs.textarea);
- });
- },
+ this.autosave = new Autosave($(this.$refs.textarea), [
+ 'Note',
+ noteableType,
+ this.getNoteableData.id,
+ ]);
+ }
+ },
+ initTaskList() {
+ return new TaskList({
+ dataType: 'note',
+ fieldName: 'note',
+ selector: '.notes',
+ });
},
- };
+ resizeTextarea() {
+ this.$nextTick(() => {
+ Autosize.update(this.$refs.textarea);
+ });
+ },
+ },
+};
</script>
<template>
@@ -418,13 +432,13 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
<loading-button
v-if="canUpdateIssue"
- :loading="isSubmitting"
+ :loading="isToggleStateButtonLoading"
@click="handleSave(true)"
:container-class="[
actionButtonClassNames,
'btn btn-comment btn-comment-and-close js-action-button'
]"
- :disabled="isSubmitting"
+ :disabled="isToggleStateButtonLoading || isSubmitting"
:label="issueActionButtonTitle"
/>
diff --git a/app/assets/javascripts/notes/components/diff_file_header.vue b/app/assets/javascripts/notes/components/diff_file_header.vue
index 3bcde17f07c..94d9dc69964 100644
--- a/app/assets/javascripts/notes/components/diff_file_header.vue
+++ b/app/assets/javascripts/notes/components/diff_file_header.vue
@@ -1,24 +1,24 @@
<script>
- import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
- import Icon from '~/vue_shared/components/icon.vue';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import Icon from '~/vue_shared/components/icon.vue';
- export default {
- components: {
- ClipboardButton,
- Icon,
+export default {
+ components: {
+ ClipboardButton,
+ Icon,
+ },
+ props: {
+ diffFile: {
+ type: Object,
+ required: true,
},
- props: {
- diffFile: {
- type: Object,
- required: true,
- },
+ },
+ computed: {
+ titleTag() {
+ return this.diffFile.discussionPath ? 'a' : 'span';
},
- computed: {
- titleTag() {
- return this.diffFile.discussionPath ? 'a' : 'span';
- },
- },
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/notes/components/diff_with_note.vue b/app/assets/javascripts/notes/components/diff_with_note.vue
index 75a32709ad5..ee01ec85bbb 100644
--- a/app/assets/javascripts/notes/components/diff_with_note.vue
+++ b/app/assets/javascripts/notes/components/diff_with_note.vue
@@ -1,55 +1,60 @@
<script>
- import syntaxHighlight from '~/syntax_highlight';
- import imageDiffHelper from '~/image_diff/helpers/index';
- import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
- import DiffFileHeader from './diff_file_header.vue';
+import $ from 'jquery';
+import syntaxHighlight from '~/syntax_highlight';
+import imageDiffHelper from '~/image_diff/helpers/index';
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+import DiffFileHeader from './diff_file_header.vue';
- export default {
- components: {
- DiffFileHeader,
+export default {
+ components: {
+ DiffFileHeader,
+ },
+ props: {
+ discussion: {
+ type: Object,
+ required: true,
},
- props: {
- discussion: {
- type: Object,
- required: true,
- },
+ },
+ computed: {
+ isImageDiff() {
+ return !this.diffFile.text;
},
- computed: {
- isImageDiff() {
- return !this.diffFile.text;
- },
- diffFileClass() {
- const { text } = this.diffFile;
- return text ? 'text-file' : 'js-image-file';
- },
- diffRows() {
- return $(this.discussion.truncatedDiffLines);
- },
- diffFile() {
- return convertObjectPropsToCamelCase(this.discussion.diffFile);
- },
- imageDiffHtml() {
- return this.discussion.imageDiffHtml;
- },
+ diffFileClass() {
+ const { text } = this.diffFile;
+ return text ? 'text-file' : 'js-image-file';
},
- mounted() {
- if (this.isImageDiff) {
- const canCreateNote = false;
- const renderCommentBadge = true;
- imageDiffHelper.initImageDiff(this.$refs.fileHolder, canCreateNote, renderCommentBadge);
- } else {
- const fileHolder = $(this.$refs.fileHolder);
- this.$nextTick(() => {
- syntaxHighlight(fileHolder);
- });
- }
+ diffRows() {
+ return $(this.discussion.truncatedDiffLines);
},
- methods: {
- rowTag(html) {
- return html.outerHTML ? 'tr' : 'template';
- },
+ diffFile() {
+ return convertObjectPropsToCamelCase(this.discussion.diffFile);
},
- };
+ imageDiffHtml() {
+ return this.discussion.imageDiffHtml;
+ },
+ },
+ mounted() {
+ if (this.isImageDiff) {
+ const canCreateNote = false;
+ const renderCommentBadge = true;
+ imageDiffHelper.initImageDiff(
+ this.$refs.fileHolder,
+ canCreateNote,
+ renderCommentBadge,
+ );
+ } else {
+ const fileHolder = $(this.$refs.fileHolder);
+ this.$nextTick(() => {
+ syntaxHighlight(fileHolder);
+ });
+ }
+ },
+ methods: {
+ rowTag(html) {
+ return html.outerHTML ? 'tr' : 'template';
+ },
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/notes/components/discussion_counter.vue b/app/assets/javascripts/notes/components/discussion_counter.vue
index 0158f58b569..d492d1cd001 100644
--- a/app/assets/javascripts/notes/components/discussion_counter.vue
+++ b/app/assets/javascripts/notes/components/discussion_counter.vue
@@ -1,67 +1,69 @@
<script>
- import { mapGetters } from 'vuex';
- import resolveSvg from 'icons/_icon_resolve_discussion.svg';
- import resolvedSvg from 'icons/_icon_status_success_solid.svg';
- import mrIssueSvg from 'icons/_icon_mr_issue.svg';
- import nextDiscussionSvg from 'icons/_next_discussion.svg';
- import { pluralize } from '../../lib/utils/text_utility';
- import { scrollToElement } from '../../lib/utils/common_utils';
- import tooltip from '../../vue_shared/directives/tooltip';
+import { mapGetters } from 'vuex';
+import resolveSvg from 'icons/_icon_resolve_discussion.svg';
+import resolvedSvg from 'icons/_icon_status_success_solid.svg';
+import mrIssueSvg from 'icons/_icon_mr_issue.svg';
+import nextDiscussionSvg from 'icons/_next_discussion.svg';
+import { pluralize } from '../../lib/utils/text_utility';
+import { scrollToElement } from '../../lib/utils/common_utils';
+import tooltip from '../../vue_shared/directives/tooltip';
- export default {
- directives: {
- tooltip,
+export default {
+ directives: {
+ tooltip,
+ },
+ computed: {
+ ...mapGetters([
+ 'getUserData',
+ 'getNoteableData',
+ 'discussionCount',
+ 'unresolvedDiscussions',
+ 'resolvedDiscussionCount',
+ ]),
+ isLoggedIn() {
+ return this.getUserData.id;
},
- computed: {
- ...mapGetters([
- 'getUserData',
- 'getNoteableData',
- 'discussionCount',
- 'unresolvedDiscussions',
- 'resolvedDiscussionCount',
- ]),
- isLoggedIn() {
- return this.getUserData.id;
- },
- hasNextButton() {
- return this.isLoggedIn && !this.allResolved;
- },
- countText() {
- return pluralize('discussion', this.discussionCount);
- },
- allResolved() {
- return this.resolvedDiscussionCount === this.discussionCount;
- },
- resolveAllDiscussionsIssuePath() {
- return this.getNoteableData.create_issue_to_resolve_discussions_path;
- },
- firstUnresolvedDiscussionId() {
- const item = this.unresolvedDiscussions[0] || {};
-
- return item.id;
- },
+ hasNextButton() {
+ return this.isLoggedIn && !this.allResolved;
+ },
+ countText() {
+ return pluralize('discussion', this.discussionCount);
+ },
+ allResolved() {
+ return this.resolvedDiscussionCount === this.discussionCount;
},
- created() {
- this.resolveSvg = resolveSvg;
- this.resolvedSvg = resolvedSvg;
- this.mrIssueSvg = mrIssueSvg;
- this.nextDiscussionSvg = nextDiscussionSvg;
+ resolveAllDiscussionsIssuePath() {
+ return this.getNoteableData.create_issue_to_resolve_discussions_path;
+ },
+ firstUnresolvedDiscussionId() {
+ const item = this.unresolvedDiscussions[0] || {};
+
+ return item.id;
},
- methods: {
- jumpToFirstDiscussion() {
- const el = document.querySelector(`[data-discussion-id="${this.firstUnresolvedDiscussionId}"]`);
- const activeTab = window.mrTabs.currentAction;
+ },
+ created() {
+ this.resolveSvg = resolveSvg;
+ this.resolvedSvg = resolvedSvg;
+ this.mrIssueSvg = mrIssueSvg;
+ this.nextDiscussionSvg = nextDiscussionSvg;
+ },
+ methods: {
+ jumpToFirstDiscussion() {
+ const el = document.querySelector(
+ `[data-discussion-id="${this.firstUnresolvedDiscussionId}"]`,
+ );
+ const activeTab = window.mrTabs.currentAction;
- if (activeTab === 'commits' || activeTab === 'pipelines') {
- window.mrTabs.activateTab('show');
- }
+ if (activeTab === 'commits' || activeTab === 'pipelines') {
+ window.mrTabs.activateTab('show');
+ }
- if (el) {
- scrollToElement(el);
- }
- },
+ if (el) {
+ scrollToElement(el);
+ }
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/notes/components/discussion_locked_widget.vue b/app/assets/javascripts/notes/components/discussion_locked_widget.vue
index fc0722042cc..13283b187d1 100644
--- a/app/assets/javascripts/notes/components/discussion_locked_widget.vue
+++ b/app/assets/javascripts/notes/components/discussion_locked_widget.vue
@@ -1,15 +1,13 @@
<script>
- import Icon from '~/vue_shared/components/icon.vue';
- import Issuable from '~/vue_shared/mixins/issuable';
+import Icon from '~/vue_shared/components/icon.vue';
+import Issuable from '~/vue_shared/mixins/issuable';
- export default {
- components: {
- Icon,
- },
- mixins: [
- Issuable,
- ],
- };
+export default {
+ components: {
+ Icon,
+ },
+ mixins: [Issuable],
+};
</script>
<template>
diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue
index c26aa6fa15d..a7e2d857013 100644
--- a/app/assets/javascripts/notes/components/note_actions.vue
+++ b/app/assets/javascripts/notes/components/note_actions.vue
@@ -1,121 +1,119 @@
<script>
- import { mapGetters } from 'vuex';
- import emojiSmiling from 'icons/_emoji_slightly_smiling_face.svg';
- import emojiSmile from 'icons/_emoji_smile.svg';
- import emojiSmiley from 'icons/_emoji_smiley.svg';
- import editSvg from 'icons/_icon_pencil.svg';
- import resolveDiscussionSvg from 'icons/_icon_resolve_discussion.svg';
- import resolvedDiscussionSvg from 'icons/_icon_status_success_solid.svg';
- import ellipsisSvg from 'icons/_ellipsis_v.svg';
- import loadingIcon from '~/vue_shared/components/loading_icon.vue';
- import tooltip from '~/vue_shared/directives/tooltip';
+import { mapGetters } from 'vuex';
+import emojiSmiling from 'icons/_emoji_slightly_smiling_face.svg';
+import emojiSmile from 'icons/_emoji_smile.svg';
+import emojiSmiley from 'icons/_emoji_smiley.svg';
+import editSvg from 'icons/_icon_pencil.svg';
+import resolveDiscussionSvg from 'icons/_icon_resolve_discussion.svg';
+import resolvedDiscussionSvg from 'icons/_icon_status_success_solid.svg';
+import ellipsisSvg from 'icons/_ellipsis_v.svg';
+import loadingIcon from '~/vue_shared/components/loading_icon.vue';
+import tooltip from '~/vue_shared/directives/tooltip';
- export default {
- name: 'NoteActions',
- directives: {
- tooltip,
- },
- components: {
- loadingIcon,
- },
- props: {
- authorId: {
- type: Number,
- required: true,
- },
- noteId: {
- type: Number,
- required: true,
- },
- accessLevel: {
- type: String,
- required: false,
- default: '',
- },
- reportAbusePath: {
- type: String,
- required: true,
- },
- canEdit: {
- type: Boolean,
- required: true,
- },
- canDelete: {
- type: Boolean,
- required: true,
- },
- resolvable: {
- type: Boolean,
- required: false,
- default: false,
- },
- isResolved: {
- type: Boolean,
- required: false,
- default: false,
- },
- isResolving: {
- type: Boolean,
- required: false,
- default: false,
- },
- resolvedBy: {
- type: Object,
- required: false,
- default: () => ({}),
- },
- canReportAsAbuse: {
- type: Boolean,
- required: true,
- },
- },
- computed: {
- ...mapGetters([
- 'getUserDataByProp',
- ]),
- shouldShowActionsDropdown() {
- return this.currentUserId && (this.canEdit || this.canReportAsAbuse);
- },
- canAddAwardEmoji() {
- return this.currentUserId;
- },
- isAuthoredByCurrentUser() {
- return this.authorId === this.currentUserId;
- },
- currentUserId() {
- return this.getUserDataByProp('id');
- },
- resolveButtonTitle() {
- let title = 'Mark as resolved';
+export default {
+ name: 'NoteActions',
+ directives: {
+ tooltip,
+ },
+ components: {
+ loadingIcon,
+ },
+ props: {
+ authorId: {
+ type: Number,
+ required: true,
+ },
+ noteId: {
+ type: Number,
+ required: true,
+ },
+ accessLevel: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ reportAbusePath: {
+ type: String,
+ required: true,
+ },
+ canEdit: {
+ type: Boolean,
+ required: true,
+ },
+ canDelete: {
+ type: Boolean,
+ required: true,
+ },
+ resolvable: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ isResolved: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ isResolving: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ resolvedBy: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ canReportAsAbuse: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ computed: {
+ ...mapGetters(['getUserDataByProp']),
+ shouldShowActionsDropdown() {
+ return this.currentUserId && (this.canEdit || this.canReportAsAbuse);
+ },
+ canAddAwardEmoji() {
+ return this.currentUserId;
+ },
+ isAuthoredByCurrentUser() {
+ return this.authorId === this.currentUserId;
+ },
+ currentUserId() {
+ return this.getUserDataByProp('id');
+ },
+ resolveButtonTitle() {
+ let title = 'Mark as resolved';
- if (this.resolvedBy) {
- title = `Resolved by ${this.resolvedBy.name}`;
- }
+ if (this.resolvedBy) {
+ title = `Resolved by ${this.resolvedBy.name}`;
+ }
- return title;
- },
- },
- created() {
- this.emojiSmiling = emojiSmiling;
- this.emojiSmile = emojiSmile;
- this.emojiSmiley = emojiSmiley;
- this.editSvg = editSvg;
- this.ellipsisSvg = ellipsisSvg;
- this.resolveDiscussionSvg = resolveDiscussionSvg;
- this.resolvedDiscussionSvg = resolvedDiscussionSvg;
- },
- methods: {
- onEdit() {
- this.$emit('handleEdit');
- },
- onDelete() {
- this.$emit('handleDelete');
- },
- onResolve() {
- this.$emit('handleResolve');
- },
- },
- };
+ return title;
+ },
+ },
+ created() {
+ this.emojiSmiling = emojiSmiling;
+ this.emojiSmile = emojiSmile;
+ this.emojiSmiley = emojiSmiley;
+ this.editSvg = editSvg;
+ this.ellipsisSvg = ellipsisSvg;
+ this.resolveDiscussionSvg = resolveDiscussionSvg;
+ this.resolvedDiscussionSvg = resolvedDiscussionSvg;
+ },
+ methods: {
+ onEdit() {
+ this.$emit('handleEdit');
+ },
+ onDelete() {
+ this.$emit('handleDelete');
+ },
+ onResolve() {
+ this.$emit('handleResolve');
+ },
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/notes/components/note_attachment.vue b/app/assets/javascripts/notes/components/note_attachment.vue
index 618b807b9cc..34ecbd00c63 100644
--- a/app/assets/javascripts/notes/components/note_attachment.vue
+++ b/app/assets/javascripts/notes/components/note_attachment.vue
@@ -1,13 +1,13 @@
<script>
- export default {
- name: 'NoteAttachment',
- props: {
- attachment: {
- type: Object,
- required: true,
- },
+export default {
+ name: 'NoteAttachment',
+ props: {
+ attachment: {
+ type: Object,
+ required: true,
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/notes/components/note_awards_list.vue b/app/assets/javascripts/notes/components/note_awards_list.vue
index caa9701e03f..6cb8229e268 100644
--- a/app/assets/javascripts/notes/components/note_awards_list.vue
+++ b/app/assets/javascripts/notes/components/note_awards_list.vue
@@ -1,179 +1,192 @@
<script>
- import { mapActions, mapGetters } from 'vuex';
- import emojiSmiling from 'icons/_emoji_slightly_smiling_face.svg';
- import emojiSmile from 'icons/_emoji_smile.svg';
- import emojiSmiley from 'icons/_emoji_smiley.svg';
- import Flash from '../../flash';
- import { glEmojiTag } from '../../emoji';
- import tooltip from '../../vue_shared/directives/tooltip';
-
- export default {
- directives: {
- tooltip,
+import { mapActions, mapGetters } from 'vuex';
+import emojiSmiling from 'icons/_emoji_slightly_smiling_face.svg';
+import emojiSmile from 'icons/_emoji_smile.svg';
+import emojiSmiley from 'icons/_emoji_smiley.svg';
+import Flash from '../../flash';
+import { glEmojiTag } from '../../emoji';
+import tooltip from '../../vue_shared/directives/tooltip';
+
+export default {
+ directives: {
+ tooltip,
+ },
+ props: {
+ awards: {
+ type: Array,
+ required: true,
},
- props: {
- awards: {
- type: Array,
- required: true,
- },
- toggleAwardPath: {
- type: String,
- required: true,
- },
- noteAuthorId: {
- type: Number,
- required: true,
- },
- noteId: {
- type: Number,
- required: true,
- },
+ toggleAwardPath: {
+ type: String,
+ required: true,
},
- computed: {
- ...mapGetters([
- 'getUserData',
- ]),
- // `this.awards` is an array with emojis but they are not grouped by emoji name. See below.
- // [ { name: foo, user: user1 }, { name: bar, user: user1 }, { name: foo, user: user2 } ]
- // This method will group emojis by their name as an Object. See below.
- // {
- // foo: [ { name: foo, user: user1 }, { name: foo, user: user2 } ],
- // bar: [ { name: bar, user: user1 } ]
- // }
- // We need to do this otherwise we will render the same emoji over and over again.
- groupedAwards() {
- const awards = this.awards.reduce((acc, award) => {
- if (Object.prototype.hasOwnProperty.call(acc, award.name)) {
- acc[award.name].push(award);
- } else {
- Object.assign(acc, { [award.name]: [award] });
- }
-
- return acc;
- }, {});
-
- const orderedAwards = {};
- const { thumbsdown, thumbsup } = awards;
- // Always show thumbsup and thumbsdown first
- if (thumbsup) {
- orderedAwards.thumbsup = thumbsup;
- delete awards.thumbsup;
- }
- if (thumbsdown) {
- orderedAwards.thumbsdown = thumbsdown;
- delete awards.thumbsdown;
- }
-
- return Object.assign({}, orderedAwards, awards);
- },
- isAuthoredByMe() {
- return this.noteAuthorId === this.getUserData.id;
- },
- isLoggedIn() {
- return this.getUserData.id;
- },
+ noteAuthorId: {
+ type: Number,
+ required: true,
},
- created() {
- this.emojiSmiling = emojiSmiling;
- this.emojiSmile = emojiSmile;
- this.emojiSmiley = emojiSmiley;
+ noteId: {
+ type: Number,
+ required: true,
},
- methods: {
- ...mapActions([
- 'toggleAwardRequest',
- ]),
- getAwardHTML(name) {
- return glEmojiTag(name);
- },
- getAwardClassBindings(awardList, awardName) {
- return {
- active: this.hasReactionByCurrentUser(awardList),
- disabled: !this.canInteractWithEmoji(awardList, awardName),
- };
- },
- canInteractWithEmoji(awardList, awardName) {
- let isAllowed = true;
- const restrictedEmojis = ['thumbsup', 'thumbsdown'];
-
- // Users can not add :+1: and :-1: to their own notes
- if (this.getUserData.id === this.noteAuthorId && restrictedEmojis.indexOf(awardName) > -1) {
- isAllowed = false;
- }
-
- return this.getUserData.id && isAllowed;
- },
- hasReactionByCurrentUser(awardList) {
- return awardList.filter(award => award.user.id === this.getUserData.id).length;
- },
- awardTitle(awardsList) {
- const hasReactionByCurrentUser = this.hasReactionByCurrentUser(awardsList);
- const TOOLTIP_NAME_COUNT = hasReactionByCurrentUser ? 9 : 10;
- let awardList = awardsList;
-
- // Filter myself from list if I am awarded.
- if (hasReactionByCurrentUser) {
- awardList = awardList.filter(award => award.user.id !== this.getUserData.id);
- }
-
- // Get only 9-10 usernames to show in tooltip text.
- const namesToShow = awardList.slice(0, TOOLTIP_NAME_COUNT).map(award => award.user.name);
-
- // Get the remaining list to use in `and x more` text.
- const remainingAwardList = awardList.slice(TOOLTIP_NAME_COUNT, awardList.length);
-
- // Add myself to the begining of the list so title will start with You.
- if (hasReactionByCurrentUser) {
- namesToShow.unshift('You');
- }
-
- let title = '';
-
- // We have 10+ awarded user, join them with comma and add `and x more`.
- if (remainingAwardList.length) {
- title = `${namesToShow.join(', ')}, and ${remainingAwardList.length} more.`;
- } else if (namesToShow.length > 1) {
- // Join all names with comma but not the last one, it will be added with and text.
- title = namesToShow.slice(0, namesToShow.length - 1).join(', ');
- // If we have more than 2 users we need an extra comma before and text.
- title += namesToShow.length > 2 ? ',' : '';
- title += ` and ${namesToShow.slice(-1)}`; // Append and text
- } else { // We have only 2 users so join them with and.
- title = namesToShow.join(' and ');
- }
-
- return title;
- },
- handleAward(awardName) {
- if (!this.isLoggedIn) {
- return;
- }
-
- let parsedName;
-
- // 100 and 1234 emoji are a number. Callback for v-for click sends it as a string
- switch (awardName) {
- case '100':
- parsedName = 100;
- break;
- case '1234':
- parsedName = 1234;
- break;
- default:
- parsedName = awardName;
- break;
+ },
+ computed: {
+ ...mapGetters(['getUserData']),
+ // `this.awards` is an array with emojis but they are not grouped by emoji name. See below.
+ // [ { name: foo, user: user1 }, { name: bar, user: user1 }, { name: foo, user: user2 } ]
+ // This method will group emojis by their name as an Object. See below.
+ // {
+ // foo: [ { name: foo, user: user1 }, { name: foo, user: user2 } ],
+ // bar: [ { name: bar, user: user1 } ]
+ // }
+ // We need to do this otherwise we will render the same emoji over and over again.
+ groupedAwards() {
+ const awards = this.awards.reduce((acc, award) => {
+ if (Object.prototype.hasOwnProperty.call(acc, award.name)) {
+ acc[award.name].push(award);
+ } else {
+ Object.assign(acc, { [award.name]: [award] });
}
- const data = {
- endpoint: this.toggleAwardPath,
- noteId: this.noteId,
- awardName: parsedName,
- };
-
- this.toggleAwardRequest(data)
- .catch(() => Flash('Something went wrong on our end.'));
- },
+ return acc;
+ }, {});
+
+ const orderedAwards = {};
+ const { thumbsdown, thumbsup } = awards;
+ // Always show thumbsup and thumbsdown first
+ if (thumbsup) {
+ orderedAwards.thumbsup = thumbsup;
+ delete awards.thumbsup;
+ }
+ if (thumbsdown) {
+ orderedAwards.thumbsdown = thumbsdown;
+ delete awards.thumbsdown;
+ }
+
+ return Object.assign({}, orderedAwards, awards);
+ },
+ isAuthoredByMe() {
+ return this.noteAuthorId === this.getUserData.id;
+ },
+ isLoggedIn() {
+ return this.getUserData.id;
+ },
+ },
+ created() {
+ this.emojiSmiling = emojiSmiling;
+ this.emojiSmile = emojiSmile;
+ this.emojiSmiley = emojiSmiley;
+ },
+ methods: {
+ ...mapActions(['toggleAwardRequest']),
+ getAwardHTML(name) {
+ return glEmojiTag(name);
+ },
+ getAwardClassBindings(awardList, awardName) {
+ return {
+ active: this.hasReactionByCurrentUser(awardList),
+ disabled: !this.canInteractWithEmoji(awardList, awardName),
+ };
+ },
+ canInteractWithEmoji(awardList, awardName) {
+ let isAllowed = true;
+ const restrictedEmojis = ['thumbsup', 'thumbsdown'];
+
+ // Users can not add :+1: and :-1: to their own notes
+ if (
+ this.getUserData.id === this.noteAuthorId &&
+ restrictedEmojis.indexOf(awardName) > -1
+ ) {
+ isAllowed = false;
+ }
+
+ return this.getUserData.id && isAllowed;
+ },
+ hasReactionByCurrentUser(awardList) {
+ return awardList.filter(award => award.user.id === this.getUserData.id)
+ .length;
+ },
+ awardTitle(awardsList) {
+ const hasReactionByCurrentUser = this.hasReactionByCurrentUser(
+ awardsList,
+ );
+ const TOOLTIP_NAME_COUNT = hasReactionByCurrentUser ? 9 : 10;
+ let awardList = awardsList;
+
+ // Filter myself from list if I am awarded.
+ if (hasReactionByCurrentUser) {
+ awardList = awardList.filter(
+ award => award.user.id !== this.getUserData.id,
+ );
+ }
+
+ // Get only 9-10 usernames to show in tooltip text.
+ const namesToShow = awardList
+ .slice(0, TOOLTIP_NAME_COUNT)
+ .map(award => award.user.name);
+
+ // Get the remaining list to use in `and x more` text.
+ const remainingAwardList = awardList.slice(
+ TOOLTIP_NAME_COUNT,
+ awardList.length,
+ );
+
+ // Add myself to the begining of the list so title will start with You.
+ if (hasReactionByCurrentUser) {
+ namesToShow.unshift('You');
+ }
+
+ let title = '';
+
+ // We have 10+ awarded user, join them with comma and add `and x more`.
+ if (remainingAwardList.length) {
+ title = `${namesToShow.join(', ')}, and ${
+ remainingAwardList.length
+ } more.`;
+ } else if (namesToShow.length > 1) {
+ // Join all names with comma but not the last one, it will be added with and text.
+ title = namesToShow.slice(0, namesToShow.length - 1).join(', ');
+ // If we have more than 2 users we need an extra comma before and text.
+ title += namesToShow.length > 2 ? ',' : '';
+ title += ` and ${namesToShow.slice(-1)}`; // Append and text
+ } else {
+ // We have only 2 users so join them with and.
+ title = namesToShow.join(' and ');
+ }
+
+ return title;
+ },
+ handleAward(awardName) {
+ if (!this.isLoggedIn) {
+ return;
+ }
+
+ let parsedName;
+
+ // 100 and 1234 emoji are a number. Callback for v-for click sends it as a string
+ switch (awardName) {
+ case '100':
+ parsedName = 100;
+ break;
+ case '1234':
+ parsedName = 1234;
+ break;
+ default:
+ parsedName = awardName;
+ break;
+ }
+
+ const data = {
+ endpoint: this.toggleAwardPath,
+ noteId: this.noteId,
+ awardName: parsedName,
+ };
+
+ this.toggleAwardRequest(data).catch(() =>
+ Flash('Something went wrong on our end.'),
+ );
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/notes/components/note_body.vue b/app/assets/javascripts/notes/components/note_body.vue
index ca12df9db64..069f94c5845 100644
--- a/app/assets/javascripts/notes/components/note_body.vue
+++ b/app/assets/javascripts/notes/components/note_body.vue
@@ -1,82 +1,81 @@
<script>
- import noteEditedText from './note_edited_text.vue';
- import noteAwardsList from './note_awards_list.vue';
- import noteAttachment from './note_attachment.vue';
- import noteForm from './note_form.vue';
- import TaskList from '../../task_list';
- import autosave from '../mixins/autosave';
+import $ from 'jquery';
+import noteEditedText from './note_edited_text.vue';
+import noteAwardsList from './note_awards_list.vue';
+import noteAttachment from './note_attachment.vue';
+import noteForm from './note_form.vue';
+import TaskList from '../../task_list';
+import autosave from '../mixins/autosave';
- export default {
- components: {
- noteEditedText,
- noteAwardsList,
- noteAttachment,
- noteForm,
+export default {
+ components: {
+ noteEditedText,
+ noteAwardsList,
+ noteAttachment,
+ noteForm,
+ },
+ mixins: [autosave],
+ props: {
+ note: {
+ type: Object,
+ required: true,
},
- mixins: [
- autosave,
- ],
- props: {
- note: {
- type: Object,
- required: true,
- },
- canEdit: {
- type: Boolean,
- required: true,
- },
- isEditing: {
- type: Boolean,
- required: false,
- default: false,
- },
+ canEdit: {
+ type: Boolean,
+ required: true,
},
- computed: {
- noteBody() {
- return this.note.note;
- },
+ isEditing: {
+ type: Boolean,
+ required: false,
+ default: false,
},
- mounted() {
- this.renderGFM();
- this.initTaskList();
+ },
+ computed: {
+ noteBody() {
+ return this.note.note;
+ },
+ },
+ mounted() {
+ this.renderGFM();
+ this.initTaskList();
+
+ if (this.isEditing) {
+ this.initAutoSave(this.note.noteable_type);
+ }
+ },
+ updated() {
+ this.initTaskList();
+ this.renderGFM();
- if (this.isEditing) {
+ if (this.isEditing) {
+ if (!this.autosave) {
this.initAutoSave(this.note.noteable_type);
+ } else {
+ this.setAutoSave();
}
+ }
+ },
+ methods: {
+ renderGFM() {
+ $(this.$refs['note-body']).renderGFM();
},
- updated() {
- this.initTaskList();
- this.renderGFM();
-
- if (this.isEditing) {
- if (!this.autosave) {
- this.initAutoSave(this.note.noteable_type);
- } else {
- this.setAutoSave();
- }
+ initTaskList() {
+ if (this.canEdit) {
+ this.taskList = new TaskList({
+ dataType: 'note',
+ fieldName: 'note',
+ selector: '.notes',
+ });
}
},
- methods: {
- renderGFM() {
- $(this.$refs['note-body']).renderGFM();
- },
- initTaskList() {
- if (this.canEdit) {
- this.taskList = new TaskList({
- dataType: 'note',
- fieldName: 'note',
- selector: '.notes',
- });
- }
- },
- handleFormUpdate(note, parentElement, callback) {
- this.$emit('handleFormUpdate', note, parentElement, callback);
- },
- formCancelHandler(shouldConfirm, isDirty) {
- this.$emit('cancelFormEdition', shouldConfirm, isDirty);
- },
+ handleFormUpdate(note, parentElement, callback) {
+ this.$emit('handleFormUpdate', note, parentElement, callback);
+ },
+ formCancelHandler(shouldConfirm, isDirty) {
+ this.$emit('cancelFormEdition', shouldConfirm, isDirty);
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/notes/components/note_edited_text.vue b/app/assets/javascripts/notes/components/note_edited_text.vue
index ae2e52554d2..4ddca918495 100644
--- a/app/assets/javascripts/notes/components/note_edited_text.vue
+++ b/app/assets/javascripts/notes/components/note_edited_text.vue
@@ -1,32 +1,32 @@
<script>
- import timeAgoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
+import timeAgoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
- export default {
- name: 'EditedNoteText',
- components: {
- timeAgoTooltip,
+export default {
+ name: 'EditedNoteText',
+ components: {
+ timeAgoTooltip,
+ },
+ props: {
+ actionText: {
+ type: String,
+ required: true,
},
- props: {
- actionText: {
- type: String,
- required: true,
- },
- editedAt: {
- type: String,
- required: true,
- },
- editedBy: {
- type: Object,
- required: false,
- default: () => ({}),
- },
- className: {
- type: String,
- required: false,
- default: 'edited-text',
- },
+ editedAt: {
+ type: String,
+ required: true,
},
- };
+ editedBy: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ className: {
+ type: String,
+ required: false,
+ default: 'edited-text',
+ },
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue
index 1a13fdbeb7c..c59a2e7a406 100644
--- a/app/assets/javascripts/notes/components/note_form.vue
+++ b/app/assets/javascripts/notes/components/note_form.vue
@@ -1,128 +1,136 @@
<script>
- import { mapGetters, mapActions } from 'vuex';
- import eventHub from '../event_hub';
- import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
- import markdownField from '../../vue_shared/components/markdown/field.vue';
- import issuableStateMixin from '../mixins/issuable_state';
- import resolvable from '../mixins/resolvable';
+import { mapGetters, mapActions } from 'vuex';
+import eventHub from '../event_hub';
+import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
+import markdownField from '../../vue_shared/components/markdown/field.vue';
+import issuableStateMixin from '../mixins/issuable_state';
+import resolvable from '../mixins/resolvable';
- export default {
- name: 'IssueNoteForm',
- components: {
- issueWarning,
- markdownField,
+export default {
+ name: 'IssueNoteForm',
+ components: {
+ issueWarning,
+ markdownField,
+ },
+ mixins: [issuableStateMixin, resolvable],
+ props: {
+ noteBody: {
+ type: String,
+ required: false,
+ default: '',
},
- mixins: [
- issuableStateMixin,
- resolvable,
- ],
- props: {
- noteBody: {
- type: String,
- required: false,
- default: '',
- },
- noteId: {
- type: Number,
- required: false,
- default: 0,
- },
- saveButtonTitle: {
- type: String,
- required: false,
- default: 'Save comment',
- },
- note: {
- type: Object,
- required: false,
- default: () => ({}),
- },
- isEditing: {
- type: Boolean,
- required: true,
- },
+ noteId: {
+ type: Number,
+ required: false,
+ default: 0,
},
- data() {
- return {
- updatedNoteBody: this.noteBody,
- conflictWhileEditing: false,
- isSubmitting: false,
- isResolving: false,
- resolveAsThread: true,
- };
+ saveButtonTitle: {
+ type: String,
+ required: false,
+ default: 'Save comment',
},
- computed: {
- ...mapGetters([
- 'getDiscussionLastNote',
- 'getNoteableData',
- 'getNoteableDataByProp',
- 'getNotesDataByProp',
- 'getUserDataByProp',
- ]),
- noteHash() {
- return `#note_${this.noteId}`;
- },
- markdownPreviewPath() {
- return this.getNoteableDataByProp('preview_note_path');
- },
- markdownDocsPath() {
- return this.getNotesDataByProp('markdownDocsPath');
- },
- quickActionsDocsPath() {
- return !this.isEditing ? this.getNotesDataByProp('quickActionsDocsPath') : undefined;
- },
- currentUserId() {
- return this.getUserDataByProp('id');
- },
- isDisabled() {
- return !this.updatedNoteBody.length || this.isSubmitting;
- },
+ note: {
+ type: Object,
+ required: false,
+ default: () => ({}),
},
- watch: {
- noteBody() {
- if (this.updatedNoteBody === this.noteBody) {
- this.updatedNoteBody = this.noteBody;
- } else {
- this.conflictWhileEditing = true;
- }
- },
+ isEditing: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ updatedNoteBody: this.noteBody,
+ conflictWhileEditing: false,
+ isSubmitting: false,
+ isResolving: false,
+ resolveAsThread: true,
+ };
+ },
+ computed: {
+ ...mapGetters([
+ 'getDiscussionLastNote',
+ 'getNoteableData',
+ 'getNoteableDataByProp',
+ 'getNotesDataByProp',
+ 'getUserDataByProp',
+ ]),
+ noteHash() {
+ return `#note_${this.noteId}`;
+ },
+ markdownPreviewPath() {
+ return this.getNoteableDataByProp('preview_note_path');
+ },
+ markdownDocsPath() {
+ return this.getNotesDataByProp('markdownDocsPath');
+ },
+ quickActionsDocsPath() {
+ return !this.isEditing
+ ? this.getNotesDataByProp('quickActionsDocsPath')
+ : undefined;
},
- mounted() {
- this.$refs.textarea.focus();
+ currentUserId() {
+ return this.getUserDataByProp('id');
},
- methods: {
- ...mapActions([
- 'toggleResolveNote',
- ]),
- handleUpdate(shouldResolve) {
- const beforeSubmitDiscussionState = this.discussionResolved;
- this.isSubmitting = true;
+ isDisabled() {
+ return !this.updatedNoteBody.length || this.isSubmitting;
+ },
+ },
+ watch: {
+ noteBody() {
+ if (this.updatedNoteBody === this.noteBody) {
+ this.updatedNoteBody = this.noteBody;
+ } else {
+ this.conflictWhileEditing = true;
+ }
+ },
+ },
+ mounted() {
+ this.$refs.textarea.focus();
+ },
+ methods: {
+ ...mapActions(['toggleResolveNote']),
+ handleUpdate(shouldResolve) {
+ const beforeSubmitDiscussionState = this.discussionResolved;
+ this.isSubmitting = true;
- this.$emit('handleFormUpdate', this.updatedNoteBody, this.$refs.editNoteForm, () => {
+ this.$emit(
+ 'handleFormUpdate',
+ this.updatedNoteBody,
+ this.$refs.editNoteForm,
+ () => {
this.isSubmitting = false;
if (shouldResolve) {
this.resolveHandler(beforeSubmitDiscussionState);
}
- });
- },
- editMyLastNote() {
- if (this.updatedNoteBody === '') {
- const lastNoteInDiscussion = this.getDiscussionLastNote(this.updatedNoteBody);
+ },
+ );
+ },
+ editMyLastNote() {
+ if (this.updatedNoteBody === '') {
+ const lastNoteInDiscussion = this.getDiscussionLastNote(
+ this.updatedNoteBody,
+ );
- if (lastNoteInDiscussion) {
- eventHub.$emit('enterEditMode', {
- noteId: lastNoteInDiscussion.id,
- });
- }
+ if (lastNoteInDiscussion) {
+ eventHub.$emit('enterEditMode', {
+ noteId: lastNoteInDiscussion.id,
+ });
}
- },
- cancelHandler(shouldConfirm = false) {
- // Sends information about confirm message and if the textarea has changed
- this.$emit('cancelFormEdition', shouldConfirm, this.noteBody !== this.updatedNoteBody);
- },
+ }
+ },
+ cancelHandler(shouldConfirm = false) {
+ // Sends information about confirm message and if the textarea has changed
+ this.$emit(
+ 'cancelFormEdition',
+ shouldConfirm,
+ this.noteBody !== this.updatedNoteBody,
+ );
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/notes/components/note_header.vue b/app/assets/javascripts/notes/components/note_header.vue
index 4743d95b951..c3d1ef1fcc6 100644
--- a/app/assets/javascripts/notes/components/note_header.vue
+++ b/app/assets/javascripts/notes/components/note_header.vue
@@ -1,65 +1,63 @@
<script>
- import { mapActions } from 'vuex';
- import timeAgoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
+import { mapActions } from 'vuex';
+import timeAgoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
- export default {
- components: {
- timeAgoTooltip,
+export default {
+ components: {
+ timeAgoTooltip,
+ },
+ props: {
+ author: {
+ type: Object,
+ required: true,
},
- props: {
- author: {
- type: Object,
- required: true,
- },
- createdAt: {
- type: String,
- required: true,
- },
- actionText: {
- type: String,
- required: false,
- default: '',
- },
- actionTextHtml: {
- type: String,
- required: false,
- default: '',
- },
- noteId: {
- type: Number,
- required: true,
- },
- includeToggle: {
- type: Boolean,
- required: false,
- default: false,
- },
- expanded: {
- type: Boolean,
- required: false,
- default: true,
- },
+ createdAt: {
+ type: String,
+ required: true,
},
- computed: {
- toggleChevronClass() {
- return this.expanded ? 'fa-chevron-up' : 'fa-chevron-down';
- },
- noteTimestampLink() {
- return `#note_${this.noteId}`;
- },
+ actionText: {
+ type: String,
+ required: false,
+ default: '',
},
- methods: {
- ...mapActions([
- 'setTargetNoteHash',
- ]),
- handleToggle() {
- this.$emit('toggleHandler');
- },
- updateTargetNoteHash() {
- this.setTargetNoteHash(this.noteTimestampLink);
- },
+ actionTextHtml: {
+ type: String,
+ required: false,
+ default: '',
},
- };
+ noteId: {
+ type: Number,
+ required: true,
+ },
+ includeToggle: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ expanded: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ },
+ computed: {
+ toggleChevronClass() {
+ return this.expanded ? 'fa-chevron-up' : 'fa-chevron-down';
+ },
+ noteTimestampLink() {
+ return `#note_${this.noteId}`;
+ },
+ },
+ methods: {
+ ...mapActions(['setTargetNoteHash']),
+ handleToggle() {
+ this.$emit('toggleHandler');
+ },
+ updateTargetNoteHash() {
+ this.setTargetNoteHash(this.noteTimestampLink);
+ },
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/notes/components/note_signed_out_widget.vue b/app/assets/javascripts/notes/components/note_signed_out_widget.vue
index 45d3c2de355..91f7c269757 100644
--- a/app/assets/javascripts/notes/components/note_signed_out_widget.vue
+++ b/app/assets/javascripts/notes/components/note_signed_out_widget.vue
@@ -1,19 +1,17 @@
<script>
- import { mapGetters } from 'vuex';
+import { mapGetters } from 'vuex';
- export default {
- computed: {
- ...mapGetters([
- 'getNotesDataByProp',
- ]),
- registerLink() {
- return this.getNotesDataByProp('registerPath');
- },
- signInLink() {
- return this.getNotesDataByProp('newSessionPath');
- },
+export default {
+ computed: {
+ ...mapGetters(['getNotesDataByProp']),
+ registerLink() {
+ return this.getNotesDataByProp('registerPath');
},
- };
+ signInLink() {
+ return this.getNotesDataByProp('newSessionPath');
+ },
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue
index 76bb53eaf2f..cf579c5d4dc 100644
--- a/app/assets/javascripts/notes/components/noteable_discussion.vue
+++ b/app/assets/javascripts/notes/components/noteable_discussion.vue
@@ -1,210 +1,210 @@
<script>
- import { mapActions, mapGetters } from 'vuex';
- import resolveDiscussionsSvg from 'icons/_icon_mr_issue.svg';
- import nextDiscussionsSvg from 'icons/_next_discussion.svg';
- import Flash from '../../flash';
- import { SYSTEM_NOTE } from '../constants';
- import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
- import noteableNote from './noteable_note.vue';
- import noteHeader from './note_header.vue';
- import noteSignedOutWidget from './note_signed_out_widget.vue';
- import noteEditedText from './note_edited_text.vue';
- import noteForm from './note_form.vue';
- import diffWithNote from './diff_with_note.vue';
- import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue';
- import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue';
- import autosave from '../mixins/autosave';
- import noteable from '../mixins/noteable';
- import resolvable from '../mixins/resolvable';
- import tooltip from '../../vue_shared/directives/tooltip';
- import { scrollToElement } from '../../lib/utils/common_utils';
+import { mapActions, mapGetters } from 'vuex';
+import resolveDiscussionsSvg from 'icons/_icon_mr_issue.svg';
+import nextDiscussionsSvg from 'icons/_next_discussion.svg';
+import Flash from '../../flash';
+import { SYSTEM_NOTE } from '../constants';
+import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
+import noteableNote from './noteable_note.vue';
+import noteHeader from './note_header.vue';
+import noteSignedOutWidget from './note_signed_out_widget.vue';
+import noteEditedText from './note_edited_text.vue';
+import noteForm from './note_form.vue';
+import diffWithNote from './diff_with_note.vue';
+import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue';
+import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue';
+import autosave from '../mixins/autosave';
+import noteable from '../mixins/noteable';
+import resolvable from '../mixins/resolvable';
+import tooltip from '../../vue_shared/directives/tooltip';
+import { scrollToElement } from '../../lib/utils/common_utils';
- export default {
- components: {
- noteableNote,
- diffWithNote,
- userAvatarLink,
- noteHeader,
- noteSignedOutWidget,
- noteEditedText,
- noteForm,
- placeholderNote,
- placeholderSystemNote,
+export default {
+ components: {
+ noteableNote,
+ diffWithNote,
+ userAvatarLink,
+ noteHeader,
+ noteSignedOutWidget,
+ noteEditedText,
+ noteForm,
+ placeholderNote,
+ placeholderSystemNote,
+ },
+ directives: {
+ tooltip,
+ },
+ mixins: [autosave, noteable, resolvable],
+ props: {
+ note: {
+ type: Object,
+ required: true,
},
- directives: {
- tooltip,
- },
- mixins: [
- autosave,
- noteable,
- resolvable,
- ],
- props: {
- note: {
- type: Object,
- required: true,
- },
- },
- data() {
+ },
+ data() {
+ return {
+ isReplying: false,
+ isResolving: false,
+ resolveAsThread: true,
+ };
+ },
+ computed: {
+ ...mapGetters([
+ 'getNoteableData',
+ 'discussionCount',
+ 'resolvedDiscussionCount',
+ 'unresolvedDiscussions',
+ ]),
+ discussion() {
return {
- isReplying: false,
- isResolving: false,
- resolveAsThread: true,
+ ...this.note.notes[0],
+ truncatedDiffLines: this.note.truncated_diff_lines,
+ diffFile: this.note.diff_file,
+ diffDiscussion: this.note.diff_discussion,
+ imageDiffHtml: this.note.image_diff_html,
};
},
- computed: {
- ...mapGetters([
- 'getNoteableData',
- 'discussionCount',
- 'resolvedDiscussionCount',
- 'unresolvedDiscussions',
- ]),
- discussion() {
- return {
- ...this.note.notes[0],
- truncatedDiffLines: this.note.truncated_diff_lines,
- diffFile: this.note.diff_file,
- diffDiscussion: this.note.diff_discussion,
- imageDiffHtml: this.note.image_diff_html,
- };
- },
- author() {
- return this.discussion.author;
- },
- canReply() {
- return this.getNoteableData.current_user.can_create_note;
- },
- newNotePath() {
- return this.getNoteableData.create_note_path;
- },
- lastUpdatedBy() {
- const { notes } = this.note;
+ author() {
+ return this.discussion.author;
+ },
+ canReply() {
+ return this.getNoteableData.current_user.can_create_note;
+ },
+ newNotePath() {
+ return this.getNoteableData.create_note_path;
+ },
+ lastUpdatedBy() {
+ const { notes } = this.note;
- if (notes.length > 1) {
- return notes[notes.length - 1].author;
- }
+ if (notes.length > 1) {
+ return notes[notes.length - 1].author;
+ }
- return null;
- },
- lastUpdatedAt() {
- const { notes } = this.note;
+ return null;
+ },
+ lastUpdatedAt() {
+ const { notes } = this.note;
- if (notes.length > 1) {
- return notes[notes.length - 1].created_at;
- }
+ if (notes.length > 1) {
+ return notes[notes.length - 1].created_at;
+ }
- return null;
- },
- hasUnresolvedDiscussion() {
- return this.unresolvedDiscussions.length > 0;
- },
- wrapperComponent() {
- return (this.discussion.diffDiscussion && this.discussion.diffFile) ? diffWithNote : 'div';
- },
- wrapperClass() {
- return this.isDiffDiscussion ? '' : 'panel panel-default';
- },
+ return null;
+ },
+ hasUnresolvedDiscussion() {
+ return this.unresolvedDiscussions.length > 0;
+ },
+ wrapperComponent() {
+ return this.discussion.diffDiscussion && this.discussion.diffFile
+ ? diffWithNote
+ : 'div';
},
- mounted() {
- if (this.isReplying) {
+ wrapperClass() {
+ return this.isDiffDiscussion ? '' : 'panel panel-default';
+ },
+ },
+ mounted() {
+ if (this.isReplying) {
+ this.initAutoSave(this.discussion.noteable_type);
+ }
+ },
+ updated() {
+ if (this.isReplying) {
+ if (!this.autosave) {
this.initAutoSave(this.discussion.noteable_type);
+ } else {
+ this.setAutoSave();
}
- },
- updated() {
- if (this.isReplying) {
- if (!this.autosave) {
- this.initAutoSave(this.discussion.noteable_type);
- } else {
- this.setAutoSave();
+ }
+ },
+ created() {
+ this.resolveDiscussionsSvg = resolveDiscussionsSvg;
+ this.nextDiscussionsSvg = nextDiscussionsSvg;
+ },
+ methods: {
+ ...mapActions([
+ 'saveNote',
+ 'toggleDiscussion',
+ 'removePlaceholderNotes',
+ 'toggleResolveNote',
+ ]),
+ componentName(note) {
+ if (note.isPlaceholderNote) {
+ if (note.placeholderType === SYSTEM_NOTE) {
+ return placeholderSystemNote;
}
+ return placeholderNote;
}
+
+ return noteableNote;
},
- created() {
- this.resolveDiscussionsSvg = resolveDiscussionsSvg;
- this.nextDiscussionsSvg = nextDiscussionsSvg;
+ componentData(note) {
+ return note.isPlaceholderNote ? this.note.notes[0] : note;
},
- methods: {
- ...mapActions([
- 'saveNote',
- 'toggleDiscussion',
- 'removePlaceholderNotes',
- 'toggleResolveNote',
- ]),
- componentName(note) {
- if (note.isPlaceholderNote) {
- if (note.placeholderType === SYSTEM_NOTE) {
- return placeholderSystemNote;
- }
- return placeholderNote;
- }
+ toggleDiscussionHandler() {
+ this.toggleDiscussion({ discussionId: this.note.id });
+ },
+ showReplyForm() {
+ this.isReplying = true;
+ },
+ cancelReplyForm(shouldConfirm) {
+ if (shouldConfirm && this.$refs.noteForm.isDirty) {
+ const msg = 'Are you sure you want to cancel creating this comment?';
- return noteableNote;
- },
- componentData(note) {
- return note.isPlaceholderNote ? this.note.notes[0] : note;
- },
- toggleDiscussionHandler() {
- this.toggleDiscussion({ discussionId: this.note.id });
- },
- showReplyForm() {
- this.isReplying = true;
- },
- cancelReplyForm(shouldConfirm) {
- if (shouldConfirm && this.$refs.noteForm.isDirty) {
- // eslint-disable-next-line no-alert
- if (!confirm('Are you sure you want to cancel creating this comment?')) {
- return;
- }
+ // eslint-disable-next-line no-alert
+ if (!confirm(msg)) {
+ return;
}
+ }
- this.resetAutoSave();
- this.isReplying = false;
- },
- saveReply(noteText, form, callback) {
- const replyData = {
- endpoint: this.newNotePath,
- flashContainer: this.$el,
- data: {
- in_reply_to_discussion_id: this.note.reply_id,
- target_type: this.noteableType,
- target_id: this.discussion.noteable_id,
- note: { note: noteText },
- },
- };
- this.isReplying = false;
+ this.resetAutoSave();
+ this.isReplying = false;
+ },
+ saveReply(noteText, form, callback) {
+ const replyData = {
+ endpoint: this.newNotePath,
+ flashContainer: this.$el,
+ data: {
+ in_reply_to_discussion_id: this.note.reply_id,
+ target_type: this.noteableType,
+ target_id: this.discussion.noteable_id,
+ note: { note: noteText },
+ },
+ };
+ this.isReplying = false;
- this.saveNote(replyData)
- .then(() => {
- this.resetAutoSave();
- callback();
- })
- .catch((err) => {
- this.removePlaceholderNotes();
- this.isReplying = true;
- this.$nextTick(() => {
- const msg = `Your comment could not be submitted!
+ this.saveNote(replyData)
+ .then(() => {
+ this.resetAutoSave();
+ callback();
+ })
+ .catch(err => {
+ this.removePlaceholderNotes();
+ this.isReplying = true;
+ this.$nextTick(() => {
+ const msg = `Your comment could not be submitted!
Please check your network connection and try again.`;
- Flash(msg, 'alert', this.$el);
- this.$refs.noteForm.note = noteText;
- callback(err);
- });
+ Flash(msg, 'alert', this.$el);
+ this.$refs.noteForm.note = noteText;
+ callback(err);
});
- },
- jumpToDiscussion() {
- const unresolvedIds = this.unresolvedDiscussions.map(d => d.id);
- const index = unresolvedIds.indexOf(this.note.id);
+ });
+ },
+ jumpToDiscussion() {
+ const unresolvedIds = this.unresolvedDiscussions.map(d => d.id);
+ const index = unresolvedIds.indexOf(this.note.id);
- if (index >= 0 && index !== unresolvedIds.length) {
- const nextId = unresolvedIds[index + 1];
- const el = document.querySelector(`[data-discussion-id="${nextId}"]`);
+ if (index >= 0 && index !== unresolvedIds.length) {
+ const nextId = unresolvedIds[index + 1];
+ const el = document.querySelector(`[data-discussion-id="${nextId}"]`);
- if (el) {
- scrollToElement(el);
- }
+ if (el) {
+ scrollToElement(el);
}
- },
+ }
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue
index 4d17bd5acc2..3554027d2b4 100644
--- a/app/assets/javascripts/notes/components/noteable_note.vue
+++ b/app/assets/javascripts/notes/components/noteable_note.vue
@@ -1,151 +1,152 @@
<script>
- import { mapGetters, mapActions } from 'vuex';
- import { escape } from 'underscore';
- import Flash from '../../flash';
- import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
- import noteHeader from './note_header.vue';
- import noteActions from './note_actions.vue';
- import noteBody from './note_body.vue';
- import eventHub from '../event_hub';
- import noteable from '../mixins/noteable';
- import resolvable from '../mixins/resolvable';
+import $ from 'jquery';
+import { mapGetters, mapActions } from 'vuex';
+import { escape } from 'underscore';
+import Flash from '../../flash';
+import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
+import noteHeader from './note_header.vue';
+import noteActions from './note_actions.vue';
+import noteBody from './note_body.vue';
+import eventHub from '../event_hub';
+import noteable from '../mixins/noteable';
+import resolvable from '../mixins/resolvable';
- export default {
- components: {
- userAvatarLink,
- noteHeader,
- noteActions,
- noteBody,
+export default {
+ components: {
+ userAvatarLink,
+ noteHeader,
+ noteActions,
+ noteBody,
+ },
+ mixins: [noteable, resolvable],
+ props: {
+ note: {
+ type: Object,
+ required: true,
},
- mixins: [
- noteable,
- resolvable,
- ],
- props: {
- note: {
- type: Object,
- required: true,
- },
+ },
+ data() {
+ return {
+ isEditing: false,
+ isDeleting: false,
+ isRequesting: false,
+ isResolving: false,
+ };
+ },
+ computed: {
+ ...mapGetters(['targetNoteHash', 'getUserData']),
+ author() {
+ return this.note.author;
},
- data() {
+ classNameBindings() {
return {
- isEditing: false,
- isDeleting: false,
- isRequesting: false,
- isResolving: false,
+ 'is-editing': this.isEditing && !this.isRequesting,
+ 'is-requesting being-posted': this.isRequesting,
+ 'disabled-content': this.isDeleting,
+ target: this.targetNoteHash === this.noteAnchorId,
};
},
- computed: {
- ...mapGetters([
- 'targetNoteHash',
- 'getUserData',
- ]),
- author() {
- return this.note.author;
- },
- classNameBindings() {
- return {
- 'is-editing': this.isEditing && !this.isRequesting,
- 'is-requesting being-posted': this.isRequesting,
- 'disabled-content': this.isDeleting,
- target: this.targetNoteHash === this.noteAnchorId,
- };
- },
- canReportAsAbuse() {
- return this.note.report_abuse_path && this.author.id !== this.getUserData.id;
- },
- noteAnchorId() {
- return `note_${this.note.id}`;
- },
+ canReportAsAbuse() {
+ return (
+ this.note.report_abuse_path && this.author.id !== this.getUserData.id
+ );
},
-
- created() {
- eventHub.$on('enterEditMode', ({ noteId }) => {
- if (noteId === this.note.id) {
- this.isEditing = true;
- this.scrollToNoteIfNeeded($(this.$el));
- }
- });
+ noteAnchorId() {
+ return `note_${this.note.id}`;
},
+ },
- methods: {
- ...mapActions([
- 'deleteNote',
- 'updateNote',
- 'toggleResolveNote',
- 'scrollToNoteIfNeeded',
- ]),
- editHandler() {
+ created() {
+ eventHub.$on('enterEditMode', ({ noteId }) => {
+ if (noteId === this.note.id) {
this.isEditing = true;
- },
- deleteHandler() {
- // eslint-disable-next-line no-alert
- if (confirm('Are you sure you want to delete this comment?')) {
- this.isDeleting = true;
+ this.scrollToNoteIfNeeded($(this.$el));
+ }
+ });
+ },
- this.deleteNote(this.note)
- .then(() => {
- this.isDeleting = false;
- })
- .catch(() => {
- Flash('Something went wrong while deleting your note. Please try again.');
- this.isDeleting = false;
- });
- }
- },
- formUpdateHandler(noteText, parentElement, callback) {
- const data = {
- endpoint: this.note.path,
- note: {
- target_type: this.noteableType,
- target_id: this.note.noteable_id,
- note: { note: noteText },
- },
- };
- this.isRequesting = true;
- this.oldContent = this.note.note_html;
- this.note.note_html = escape(noteText);
+ methods: {
+ ...mapActions([
+ 'deleteNote',
+ 'updateNote',
+ 'toggleResolveNote',
+ 'scrollToNoteIfNeeded',
+ ]),
+ editHandler() {
+ this.isEditing = true;
+ },
+ deleteHandler() {
+ // eslint-disable-next-line no-alert
+ if (confirm('Are you sure you want to delete this comment?')) {
+ this.isDeleting = true;
- this.updateNote(data)
+ this.deleteNote(this.note)
.then(() => {
- this.isEditing = false;
- this.isRequesting = false;
- this.oldContent = null;
- $(this.$refs.noteBody.$el).renderGFM();
- this.$refs.noteBody.resetAutoSave();
- callback();
+ this.isDeleting = false;
})
.catch(() => {
- this.isRequesting = false;
- this.isEditing = true;
- this.$nextTick(() => {
- const msg = 'Something went wrong while editing your comment. Please try again.';
- Flash(msg, 'alert', this.$el);
- this.recoverNoteContent(noteText);
- callback();
- });
+ Flash(
+ 'Something went wrong while deleting your note. Please try again.',
+ );
+ this.isDeleting = false;
});
- },
- formCancelHandler(shouldConfirm, isDirty) {
- if (shouldConfirm && isDirty) {
- // eslint-disable-next-line no-alert
- if (!confirm('Are you sure you want to cancel editing this comment?')) return;
- }
- this.$refs.noteBody.resetAutoSave();
- if (this.oldContent) {
- this.note.note_html = this.oldContent;
+ }
+ },
+ formUpdateHandler(noteText, parentElement, callback) {
+ const data = {
+ endpoint: this.note.path,
+ note: {
+ target_type: this.noteableType,
+ target_id: this.note.noteable_id,
+ note: { note: noteText },
+ },
+ };
+ this.isRequesting = true;
+ this.oldContent = this.note.note_html;
+ this.note.note_html = escape(noteText);
+
+ this.updateNote(data)
+ .then(() => {
+ this.isEditing = false;
+ this.isRequesting = false;
this.oldContent = null;
- }
- this.isEditing = false;
- },
- recoverNoteContent(noteText) {
- // we need to do this to prevent noteForm inconsistent content warning
- // this is something we intentionally do so we need to recover the content
- this.note.note = noteText;
- this.$refs.noteBody.$refs.noteForm.note.note = noteText;
- },
+ $(this.$refs.noteBody.$el).renderGFM();
+ this.$refs.noteBody.resetAutoSave();
+ callback();
+ })
+ .catch(() => {
+ this.isRequesting = false;
+ this.isEditing = true;
+ this.$nextTick(() => {
+ const msg =
+ 'Something went wrong while editing your comment. Please try again.';
+ Flash(msg, 'alert', this.$el);
+ this.recoverNoteContent(noteText);
+ callback();
+ });
+ });
+ },
+ formCancelHandler(shouldConfirm, isDirty) {
+ if (shouldConfirm && isDirty) {
+ // eslint-disable-next-line no-alert
+ if (!confirm('Are you sure you want to cancel editing this comment?'))
+ return;
+ }
+ this.$refs.noteBody.resetAutoSave();
+ if (this.oldContent) {
+ this.note.note_html = this.oldContent;
+ this.oldContent = null;
+ }
+ this.isEditing = false;
+ },
+ recoverNoteContent(noteText) {
+ // we need to do this to prevent noteForm inconsistent content warning
+ // this is something we intentionally do so we need to recover the content
+ this.note.note = noteText;
+ this.$refs.noteBody.$refs.noteForm.note.note = noteText;
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue
index 74afed5560b..a90c6d6381d 100644
--- a/app/assets/javascripts/notes/components/notes_app.vue
+++ b/app/assets/javascripts/notes/components/notes_app.vue
@@ -1,159 +1,162 @@
<script>
- import { mapGetters, mapActions } from 'vuex';
- import { getLocationHash } from '../../lib/utils/url_utility';
- import Flash from '../../flash';
- import store from '../stores/';
- import * as constants from '../constants';
- import noteableNote from './noteable_note.vue';
- import noteableDiscussion from './noteable_discussion.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';
- import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue';
- import loadingIcon from '../../vue_shared/components/loading_icon.vue';
- import skeletonLoadingContainer from '../../vue_shared/components/notes/skeleton_note.vue';
+import $ from 'jquery';
+import { mapGetters, mapActions } from 'vuex';
+import { getLocationHash } from '../../lib/utils/url_utility';
+import Flash from '../../flash';
+import store from '../stores/';
+import * as constants from '../constants';
+import noteableNote from './noteable_note.vue';
+import noteableDiscussion from './noteable_discussion.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';
+import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue';
+import loadingIcon from '../../vue_shared/components/loading_icon.vue';
+import skeletonLoadingContainer from '../../vue_shared/components/notes/skeleton_note.vue';
- export default {
- name: 'NotesApp',
- components: {
- noteableNote,
- noteableDiscussion,
- systemNote,
- commentForm,
- loadingIcon,
- placeholderNote,
- placeholderSystemNote,
+export default {
+ name: 'NotesApp',
+ components: {
+ noteableNote,
+ noteableDiscussion,
+ systemNote,
+ commentForm,
+ loadingIcon,
+ placeholderNote,
+ placeholderSystemNote,
+ },
+ props: {
+ noteableData: {
+ type: Object,
+ required: true,
},
- props: {
- noteableData: {
- type: Object,
- required: true,
- },
- notesData: {
- type: Object,
- required: true,
- },
- userData: {
- type: Object,
- required: false,
- default: () => ({}),
- },
+ notesData: {
+ type: Object,
+ required: true,
},
- store,
- data() {
- return {
- isLoading: true,
- };
+ userData: {
+ type: Object,
+ required: false,
+ default: () => ({}),
},
- computed: {
- ...mapGetters([
- 'notes',
- 'getNotesDataByProp',
- 'discussionCount',
- ]),
- noteableType() {
- // FIXME -- @fatihacet Get this from JSON data.
- const { ISSUE_NOTEABLE_TYPE, MERGE_REQUEST_NOTEABLE_TYPE } = constants;
+ },
+ store,
+ data() {
+ return {
+ isLoading: true,
+ };
+ },
+ computed: {
+ ...mapGetters(['notes', 'getNotesDataByProp', 'discussionCount']),
+ noteableType() {
+ // FIXME -- @fatihacet Get this from JSON data.
+ const { ISSUE_NOTEABLE_TYPE, MERGE_REQUEST_NOTEABLE_TYPE } = constants;
- return this.noteableData.merge_params ? MERGE_REQUEST_NOTEABLE_TYPE : ISSUE_NOTEABLE_TYPE;
- },
- allNotes() {
- if (this.isLoading) {
- const totalNotes = parseInt(this.notesData.totalNotes, 10) || 0;
-
- return new Array(totalNotes).fill({
- isSkeletonNote: true,
- });
- }
- return this.notes;
- },
- },
- created() {
- this.setNotesData(this.notesData);
- this.setNoteableData(this.noteableData);
- this.setUserData(this.userData);
+ return this.noteableData.merge_params
+ ? MERGE_REQUEST_NOTEABLE_TYPE
+ : ISSUE_NOTEABLE_TYPE;
},
- mounted() {
- this.fetchNotes();
+ allNotes() {
+ if (this.isLoading) {
+ const totalNotes = parseInt(this.notesData.totalNotes, 10) || 0;
- const parentElement = this.$el.parentElement;
-
- if (parentElement &&
- parentElement.classList.contains('js-vue-notes-event')) {
- parentElement.addEventListener('toggleAward', (event) => {
- const { awardName, noteId } = event.detail;
- this.actionToggleAward({ awardName, noteId });
+ return new Array(totalNotes).fill({
+ isSkeletonNote: true,
});
}
- document.addEventListener('refreshVueNotes', this.fetchNotes);
- },
- beforeDestroy() {
- document.removeEventListener('refreshVueNotes', this.fetchNotes);
+ return this.notes;
},
- methods: {
- ...mapActions({
- actionFetchNotes: 'fetchNotes',
- poll: 'poll',
- actionToggleAward: 'toggleAward',
- scrollToNoteIfNeeded: 'scrollToNoteIfNeeded',
- setNotesData: 'setNotesData',
- setNoteableData: 'setNoteableData',
- setUserData: 'setUserData',
- setLastFetchedAt: 'setLastFetchedAt',
- setTargetNoteHash: 'setTargetNoteHash',
- }),
- getComponentName(note) {
- if (note.isSkeletonNote) {
- return skeletonLoadingContainer;
- }
- if (note.isPlaceholderNote) {
- if (note.placeholderType === constants.SYSTEM_NOTE) {
- return placeholderSystemNote;
- }
- return placeholderNote;
- } else if (note.individual_note) {
- return note.notes[0].system ? systemNote : noteableNote;
- }
+ },
+ created() {
+ this.setNotesData(this.notesData);
+ this.setNoteableData(this.noteableData);
+ this.setUserData(this.userData);
+ },
+ mounted() {
+ this.fetchNotes();
+
+ const parentElement = this.$el.parentElement;
- return noteableDiscussion;
- },
- getComponentData(note) {
- return note.individual_note ? note.notes[0] : note;
- },
- fetchNotes() {
- return this.actionFetchNotes(this.getNotesDataByProp('discussionsPath'))
- .then(() => this.initPolling())
- .then(() => {
- this.isLoading = false;
- })
- .then(() => this.$nextTick())
- .then(() => this.checkLocationHash())
- .catch(() => {
- this.isLoading = false;
- Flash('Something went wrong while fetching comments. Please try again.');
- });
- },
- initPolling() {
- if (this.isPollingInitialized) {
- return;
+ if (
+ parentElement &&
+ parentElement.classList.contains('js-vue-notes-event')
+ ) {
+ parentElement.addEventListener('toggleAward', event => {
+ const { awardName, noteId } = event.detail;
+ this.actionToggleAward({ awardName, noteId });
+ });
+ }
+ document.addEventListener('refreshVueNotes', this.fetchNotes);
+ },
+ beforeDestroy() {
+ document.removeEventListener('refreshVueNotes', this.fetchNotes);
+ },
+ methods: {
+ ...mapActions({
+ actionFetchNotes: 'fetchNotes',
+ poll: 'poll',
+ actionToggleAward: 'toggleAward',
+ scrollToNoteIfNeeded: 'scrollToNoteIfNeeded',
+ setNotesData: 'setNotesData',
+ setNoteableData: 'setNoteableData',
+ setUserData: 'setUserData',
+ setLastFetchedAt: 'setLastFetchedAt',
+ setTargetNoteHash: 'setTargetNoteHash',
+ }),
+ getComponentName(note) {
+ if (note.isSkeletonNote) {
+ return skeletonLoadingContainer;
+ }
+ if (note.isPlaceholderNote) {
+ if (note.placeholderType === constants.SYSTEM_NOTE) {
+ return placeholderSystemNote;
}
+ return placeholderNote;
+ } else if (note.individual_note) {
+ return note.notes[0].system ? systemNote : noteableNote;
+ }
- this.setLastFetchedAt(this.getNotesDataByProp('lastFetchedAt'));
+ return noteableDiscussion;
+ },
+ getComponentData(note) {
+ return note.individual_note ? note.notes[0] : note;
+ },
+ fetchNotes() {
+ return this.actionFetchNotes(this.getNotesDataByProp('discussionsPath'))
+ .then(() => this.initPolling())
+ .then(() => {
+ this.isLoading = false;
+ })
+ .then(() => this.$nextTick())
+ .then(() => this.checkLocationHash())
+ .catch(() => {
+ this.isLoading = false;
+ Flash(
+ 'Something went wrong while fetching comments. Please try again.',
+ );
+ });
+ },
+ initPolling() {
+ if (this.isPollingInitialized) {
+ return;
+ }
- this.poll();
- this.isPollingInitialized = true;
- },
- checkLocationHash() {
- const hash = getLocationHash();
- const element = document.getElementById(hash);
+ this.setLastFetchedAt(this.getNotesDataByProp('lastFetchedAt'));
- if (hash && element) {
- this.setTargetNoteHash(hash);
- this.scrollToNoteIfNeeded($(element));
- }
- },
+ this.poll();
+ this.isPollingInitialized = true;
+ },
+ checkLocationHash() {
+ const hash = getLocationHash();
+ const element = document.getElementById(hash);
+
+ if (hash && element) {
+ this.setTargetNoteHash(hash);
+ this.scrollToNoteIfNeeded($(element));
+ }
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/notes/index.js b/app/assets/javascripts/notes/index.js
index 545bf2c99a7..f90775d0157 100644
--- a/app/assets/javascripts/notes/index.js
+++ b/app/assets/javascripts/notes/index.js
@@ -1,35 +1,43 @@
import Vue from 'vue';
import notesApp from './components/notes_app.vue';
-document.addEventListener('DOMContentLoaded', () => new Vue({
- el: '#js-vue-notes',
- components: {
- notesApp,
- },
- data() {
- const notesDataset = document.getElementById('js-vue-notes').dataset;
- const parsedUserData = JSON.parse(notesDataset.currentUserData);
- const currentUserData = parsedUserData ? {
- id: parsedUserData.id,
- name: parsedUserData.name,
- username: parsedUserData.username,
- avatar_url: parsedUserData.avatar_path || parsedUserData.avatar_url,
- path: parsedUserData.path,
- } : {};
+document.addEventListener(
+ 'DOMContentLoaded',
+ () =>
+ new Vue({
+ el: '#js-vue-notes',
+ components: {
+ notesApp,
+ },
+ data() {
+ const notesDataset = document.getElementById('js-vue-notes').dataset;
+ const parsedUserData = JSON.parse(notesDataset.currentUserData);
+ let currentUserData = {};
+
+ if (parsedUserData) {
+ currentUserData = {
+ id: parsedUserData.id,
+ name: parsedUserData.name,
+ username: parsedUserData.username,
+ avatar_url: parsedUserData.avatar_path || parsedUserData.avatar_url,
+ path: parsedUserData.path,
+ };
+ }
- return {
- noteableData: JSON.parse(notesDataset.noteableData),
- currentUserData,
- notesData: JSON.parse(notesDataset.notesData),
- };
- },
- render(createElement) {
- return createElement('notes-app', {
- props: {
- noteableData: this.noteableData,
- notesData: this.notesData,
- userData: this.currentUserData,
+ return {
+ noteableData: JSON.parse(notesDataset.noteableData),
+ currentUserData,
+ notesData: JSON.parse(notesDataset.notesData),
+ };
+ },
+ render(createElement) {
+ return createElement('notes-app', {
+ props: {
+ noteableData: this.noteableData,
+ notesData: this.notesData,
+ userData: this.currentUserData,
+ },
+ });
},
- });
- },
-}));
+ }),
+);
diff --git a/app/assets/javascripts/notes/mixins/autosave.js b/app/assets/javascripts/notes/mixins/autosave.js
index a3d897f2f12..3dff715905f 100644
--- a/app/assets/javascripts/notes/mixins/autosave.js
+++ b/app/assets/javascripts/notes/mixins/autosave.js
@@ -1,10 +1,15 @@
+import $ from 'jquery';
import Autosave from '../../autosave';
import { capitalizeFirstCharacter } from '../../lib/utils/text_utility';
export default {
methods: {
initAutoSave(noteableType) {
- this.autosave = new Autosave($(this.$refs.noteForm.$refs.textarea), ['Note', capitalizeFirstCharacter(noteableType), this.note.id]);
+ this.autosave = new Autosave($(this.$refs.noteForm.$refs.textarea), [
+ 'Note',
+ capitalizeFirstCharacter(noteableType),
+ this.note.id,
+ ]);
},
resetAutoSave() {
this.autosave.reset();
diff --git a/app/assets/javascripts/notes/mixins/resolvable.js b/app/assets/javascripts/notes/mixins/resolvable.js
index ab1ae115e52..f79049b85f6 100644
--- a/app/assets/javascripts/notes/mixins/resolvable.js
+++ b/app/assets/javascripts/notes/mixins/resolvable.js
@@ -12,7 +12,8 @@ export default {
discussionResolved() {
const { notes, resolved } = this.note;
- if (notes) { // Decide resolved state using store. Only valid for discussions.
+ if (notes) {
+ // Decide resolved state using store. Only valid for discussions.
return notes.every(note => note.resolved && !note.system);
}
@@ -26,7 +27,9 @@ export default {
return __('Comment and resolve discussion');
}
- return this.discussionResolved ? __('Unresolve discussion') : __('Resolve discussion');
+ return this.discussionResolved
+ ? __('Unresolve discussion')
+ : __('Resolve discussion');
},
},
methods: {
@@ -42,7 +45,9 @@ export default {
})
.catch(() => {
this.isResolving = false;
- const msg = __('Something went wrong while resolving this discussion. Please try again.');
+ const msg = __(
+ 'Something went wrong while resolving this discussion. Please try again.',
+ );
Flash(msg, 'alert', this.$el);
});
},
diff --git a/app/assets/javascripts/notes/services/notes_service.js b/app/assets/javascripts/notes/services/notes_service.js
index 4766351dfc5..7c623aac6ed 100644
--- a/app/assets/javascripts/notes/services/notes_service.js
+++ b/app/assets/javascripts/notes/services/notes_service.js
@@ -22,15 +22,18 @@ export default {
},
toggleResolveNote(endpoint, isResolved) {
const { RESOLVE_NOTE_METHOD_NAME, UNRESOLVE_NOTE_METHOD_NAME } = constants;
- const method = isResolved ? UNRESOLVE_NOTE_METHOD_NAME : RESOLVE_NOTE_METHOD_NAME;
+ const method = isResolved
+ ? UNRESOLVE_NOTE_METHOD_NAME
+ : RESOLVE_NOTE_METHOD_NAME;
return Vue.http[method](endpoint);
},
poll(data = {}) {
- const { endpoint, lastFetchedAt } = data;
+ const endpoint = data.notesData.notesPath;
+ const lastFetchedAt = data.lastFetchedAt;
const options = {
headers: {
- 'X-Last-Fetched-At': lastFetchedAt,
+ 'X-Last-Fetched-At': lastFetchedAt ? `${lastFetchedAt}` : undefined,
},
};
diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js
index 42fc2a131b8..244a6980b5a 100644
--- a/app/assets/javascripts/notes/stores/actions.js
+++ b/app/assets/javascripts/notes/stores/actions.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import Visibility from 'visibilityjs';
import Flash from '../../flash';
import Poll from '../../lib/utils/poll';
@@ -11,86 +12,115 @@ import { isInViewport, scrollToElement } from '../../lib/utils/common_utils';
let eTagPoll;
-export const setNotesData = ({ commit }, data) => commit(types.SET_NOTES_DATA, data);
-export const setNoteableData = ({ commit }, data) => commit(types.SET_NOTEABLE_DATA, data);
-export const setUserData = ({ commit }, data) => commit(types.SET_USER_DATA, data);
-export const setLastFetchedAt = ({ commit }, data) => commit(types.SET_LAST_FETCHED_AT, data);
-export const setInitialNotes = ({ commit }, data) => commit(types.SET_INITIAL_NOTES, data);
-export const setTargetNoteHash = ({ commit }, data) => commit(types.SET_TARGET_NOTE_HASH, data);
-export const toggleDiscussion = ({ commit }, data) => commit(types.TOGGLE_DISCUSSION, data);
-
-export const fetchNotes = ({ commit }, path) => service
- .fetchNotes(path)
- .then(res => res.json())
- .then((res) => {
- commit(types.SET_INITIAL_NOTES, res);
- });
+export const setNotesData = ({ commit }, data) =>
+ commit(types.SET_NOTES_DATA, data);
+export const setNoteableData = ({ commit }, data) =>
+ commit(types.SET_NOTEABLE_DATA, data);
+export const setUserData = ({ commit }, data) =>
+ commit(types.SET_USER_DATA, data);
+export const setLastFetchedAt = ({ commit }, data) =>
+ commit(types.SET_LAST_FETCHED_AT, data);
+export const setInitialNotes = ({ commit }, data) =>
+ commit(types.SET_INITIAL_NOTES, data);
+export const setTargetNoteHash = ({ commit }, data) =>
+ commit(types.SET_TARGET_NOTE_HASH, data);
+export const toggleDiscussion = ({ commit }, data) =>
+ commit(types.TOGGLE_DISCUSSION, data);
+
+export const fetchNotes = ({ commit }, path) =>
+ service
+ .fetchNotes(path)
+ .then(res => res.json())
+ .then(res => {
+ commit(types.SET_INITIAL_NOTES, res);
+ });
-export const deleteNote = ({ commit }, note) => service
- .deleteNote(note.path)
- .then(() => {
+export const deleteNote = ({ commit }, note) =>
+ service.deleteNote(note.path).then(() => {
commit(types.DELETE_NOTE, note);
});
-export const updateNote = ({ commit }, { endpoint, note }) => service
- .updateNote(endpoint, note)
- .then(res => res.json())
- .then((res) => {
- commit(types.UPDATE_NOTE, res);
- });
+export const updateNote = ({ commit }, { endpoint, note }) =>
+ service
+ .updateNote(endpoint, note)
+ .then(res => res.json())
+ .then(res => {
+ commit(types.UPDATE_NOTE, res);
+ });
-export const replyToDiscussion = ({ commit }, { endpoint, data }) => service
- .replyToDiscussion(endpoint, data)
- .then(res => res.json())
- .then((res) => {
- commit(types.ADD_NEW_REPLY_TO_DISCUSSION, res);
+export const replyToDiscussion = ({ commit }, { endpoint, data }) =>
+ service
+ .replyToDiscussion(endpoint, data)
+ .then(res => res.json())
+ .then(res => {
+ commit(types.ADD_NEW_REPLY_TO_DISCUSSION, res);
- return res;
- });
+ return res;
+ });
-export const createNewNote = ({ commit }, { endpoint, data }) => service
- .createNewNote(endpoint, data)
- .then(res => res.json())
- .then((res) => {
- if (!res.errors) {
- commit(types.ADD_NEW_NOTE, res);
- }
- return res;
- });
+export const createNewNote = ({ commit }, { endpoint, data }) =>
+ service
+ .createNewNote(endpoint, data)
+ .then(res => res.json())
+ .then(res => {
+ if (!res.errors) {
+ commit(types.ADD_NEW_NOTE, res);
+ }
+ return res;
+ });
export const removePlaceholderNotes = ({ commit }) =>
commit(types.REMOVE_PLACEHOLDER_NOTES);
-export const toggleResolveNote = ({ commit }, { endpoint, isResolved, discussion }) => service
- .toggleResolveNote(endpoint, isResolved)
- .then(res => res.json())
- .then((res) => {
- const mutationType = discussion ? types.UPDATE_DISCUSSION : types.UPDATE_NOTE;
+export const toggleResolveNote = (
+ { commit },
+ { endpoint, isResolved, discussion },
+) =>
+ service
+ .toggleResolveNote(endpoint, isResolved)
+ .then(res => res.json())
+ .then(res => {
+ const mutationType = discussion
+ ? types.UPDATE_DISCUSSION
+ : types.UPDATE_NOTE;
- commit(mutationType, res);
- });
+ commit(mutationType, res);
+ });
-export const closeIssue = ({ commit, dispatch, state }) => service
- .toggleIssueState(state.notesData.closePath)
- .then(res => res.json())
- .then((data) => {
- commit(types.CLOSE_ISSUE);
- dispatch('emitStateChangedEvent', data);
- });
+export const closeIssue = ({ commit, dispatch, state }) => {
+ dispatch('toggleStateButtonLoading', true);
+ return service
+ .toggleIssueState(state.notesData.closePath)
+ .then(res => res.json())
+ .then(data => {
+ commit(types.CLOSE_ISSUE);
+ dispatch('emitStateChangedEvent', data);
+ dispatch('toggleStateButtonLoading', false);
+ });
+};
-export const reopenIssue = ({ commit, dispatch, state }) => service
- .toggleIssueState(state.notesData.reopenPath)
- .then(res => res.json())
- .then((data) => {
- commit(types.REOPEN_ISSUE);
- dispatch('emitStateChangedEvent', data);
- });
+export const reopenIssue = ({ commit, dispatch, state }) => {
+ dispatch('toggleStateButtonLoading', true);
+ return service
+ .toggleIssueState(state.notesData.reopenPath)
+ .then(res => res.json())
+ .then(data => {
+ commit(types.REOPEN_ISSUE);
+ dispatch('emitStateChangedEvent', data);
+ dispatch('toggleStateButtonLoading', false);
+ });
+};
+
+export const toggleStateButtonLoading = ({ commit }, value) =>
+ commit(types.TOGGLE_STATE_BUTTON_LOADING, value);
export const emitStateChangedEvent = ({ commit, getters }, data) => {
- const event = new CustomEvent('issuable_vue_app:change', { detail: {
- data,
- isClosed: getters.openState === constants.CLOSED,
- } });
+ const event = new CustomEvent('issuable_vue_app:change', {
+ detail: {
+ data,
+ isClosed: getters.openState === constants.CLOSED,
+ },
+ });
document.dispatchEvent(event);
};
@@ -132,59 +162,70 @@ export const saveNote = ({ commit, dispatch }, noteData) => {
});
}
- return dispatch(methodToDispatch, noteData)
- .then((res) => {
- const { errors } = res;
- const commandsChanges = res.commands_changes;
-
- if (hasQuickActions && errors && Object.keys(errors).length) {
- eTagPoll.makeRequest();
+ return dispatch(methodToDispatch, noteData).then(res => {
+ const { errors } = res;
+ const commandsChanges = res.commands_changes;
- $('.js-gfm-input').trigger('clear-commands-cache.atwho');
- Flash('Commands applied', 'notice', noteData.flashContainer);
- }
+ if (hasQuickActions && errors && Object.keys(errors).length) {
+ eTagPoll.makeRequest();
- if (commandsChanges) {
- if (commandsChanges.emoji_award) {
- const votesBlock = $('.js-awards-block').eq(0);
-
- loadAwardsHandler()
- .then((awardsHandler) => {
- awardsHandler.addAwardToEmojiBar(votesBlock, commandsChanges.emoji_award);
- awardsHandler.scrollToAwards();
- })
- .catch(() => {
- Flash(
- 'Something went wrong while adding your award. Please try again.',
- 'alert',
- noteData.flashContainer,
- );
- });
- }
+ $('.js-gfm-input').trigger('clear-commands-cache.atwho');
+ Flash('Commands applied', 'notice', noteData.flashContainer);
+ }
- if (commandsChanges.spend_time != null || commandsChanges.time_estimate != null) {
- sidebarTimeTrackingEventHub.$emit('timeTrackingUpdated', res);
- }
+ if (commandsChanges) {
+ if (commandsChanges.emoji_award) {
+ const votesBlock = $('.js-awards-block').eq(0);
+
+ loadAwardsHandler()
+ .then(awardsHandler => {
+ awardsHandler.addAwardToEmojiBar(
+ votesBlock,
+ commandsChanges.emoji_award,
+ );
+ awardsHandler.scrollToAwards();
+ })
+ .catch(() => {
+ Flash(
+ 'Something went wrong while adding your award. Please try again.',
+ 'alert',
+ noteData.flashContainer,
+ );
+ });
}
- if (errors && errors.commands_only) {
- Flash(errors.commands_only, 'notice', noteData.flashContainer);
+ if (
+ commandsChanges.spend_time != null ||
+ commandsChanges.time_estimate != null
+ ) {
+ sidebarTimeTrackingEventHub.$emit('timeTrackingUpdated', res);
}
- commit(types.REMOVE_PLACEHOLDER_NOTES);
+ }
- return res;
- });
+ if (errors && errors.commands_only) {
+ Flash(errors.commands_only, 'notice', noteData.flashContainer);
+ }
+ commit(types.REMOVE_PLACEHOLDER_NOTES);
+
+ return res;
+ });
};
const pollSuccessCallBack = (resp, commit, state, getters) => {
if (resp.notes && resp.notes.length) {
const { notesById } = getters;
- resp.notes.forEach((note) => {
+ 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.notes, note.discussion_id);
+ } else if (
+ note.type === constants.DISCUSSION_NOTE ||
+ note.type === constants.DIFF_NOTE
+ ) {
+ const discussion = utils.findNoteObjectById(
+ state.notes,
+ note.discussion_id,
+ );
if (discussion) {
commit(types.ADD_NEW_REPLY_TO_DISCUSSION, note);
@@ -197,27 +238,28 @@ const pollSuccessCallBack = (resp, commit, state, getters) => {
});
}
- commit(types.SET_LAST_FETCHED_AT, resp.lastFetchedAt);
+ commit(types.SET_LAST_FETCHED_AT, resp.last_fetched_at);
return resp;
};
export const poll = ({ commit, state, getters }) => {
- const requestData = { endpoint: state.notesData.notesPath, lastFetchedAt: state.lastFetchedAt };
-
eTagPoll = new Poll({
resource: service,
method: 'poll',
- data: requestData,
- successCallback: resp => resp.json()
- .then(data => pollSuccessCallBack(data, commit, state, getters)),
- errorCallback: () => Flash('Something went wrong while fetching latest comments.'),
+ data: state,
+ successCallback: resp =>
+ resp
+ .json()
+ .then(data => pollSuccessCallBack(data, commit, state, getters)),
+ errorCallback: () =>
+ Flash('Something went wrong while fetching latest comments.'),
});
if (!Visibility.hidden()) {
eTagPoll.makeRequest();
} else {
- service.poll(requestData);
+ service.poll(state);
}
Visibility.change(() => {
@@ -238,15 +280,22 @@ export const restartPolling = () => {
};
export const fetchData = ({ commit, state, getters }) => {
- const requestData = { endpoint: state.notesData.notesPath, lastFetchedAt: state.lastFetchedAt };
+ const requestData = {
+ endpoint: state.notesData.notesPath,
+ lastFetchedAt: state.lastFetchedAt,
+ };
- service.poll(requestData)
+ service
+ .poll(requestData)
.then(resp => resp.json)
.then(data => pollSuccessCallBack(data, commit, state, getters))
.catch(() => Flash('Something went wrong while fetching latest comments.'));
};
-export const toggleAward = ({ commit, state, getters, dispatch }, { awardName, noteId }) => {
+export const toggleAward = (
+ { commit, state, getters, dispatch },
+ { awardName, noteId },
+) => {
commit(types.TOGGLE_AWARD, { awardName, note: getters.notesById[noteId] });
};
diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js
index e6180101c58..f89591a54d6 100644
--- a/app/assets/javascripts/notes/stores/getters.js
+++ b/app/assets/javascripts/notes/stores/getters.js
@@ -11,27 +11,31 @@ export const getNoteableDataByProp = state => prop => state.noteableData[prop];
export const openState = state => state.noteableData.state;
export const getUserData = state => state.userData || {};
-export const getUserDataByProp = state => prop => state.userData && state.userData[prop];
+export const getUserDataByProp = state => prop =>
+ state.userData && state.userData[prop];
-export const notesById = state => state.notes.reduce((acc, note) => {
- note.notes.every(n => Object.assign(acc, { [n.id]: n }));
- return acc;
-}, {});
+export const notesById = state =>
+ state.notes.reduce((acc, note) => {
+ note.notes.every(n => Object.assign(acc, { [n.id]: n }));
+ return acc;
+ }, {});
const reverseNotes = array => array.slice(0).reverse();
-const isLastNote = (note, state) => !note.system &&
- state.userData && note.author &&
+const isLastNote = (note, state) =>
+ !note.system &&
+ state.userData &&
+ note.author &&
note.author.id === state.userData.id;
-export const getCurrentUserLastNote = state => _.flatten(
- reverseNotes(state.notes)
- .map(note => reverseNotes(note.notes)),
+export const getCurrentUserLastNote = state =>
+ _.flatten(
+ reverseNotes(state.notes).map(note => reverseNotes(note.notes)),
).find(el => isLastNote(el, state));
-export const getDiscussionLastNote = state => discussion => reverseNotes(discussion.notes)
- .find(el => isLastNote(el, state));
+export const getDiscussionLastNote = state => discussion =>
+ reverseNotes(discussion.notes).find(el => isLastNote(el, state));
-export const discussionCount = (state) => {
+export const discussionCount = state => {
const discussions = state.notes.filter(n => !n.individual_note);
return discussions.length;
@@ -43,10 +47,10 @@ export const unresolvedDiscussions = (state, getters) => {
return state.notes.filter(n => !n.individual_note && !resolvedMap[n.id]);
};
-export const resolvedDiscussionsById = (state) => {
+export const resolvedDiscussionsById = state => {
const map = {};
- state.notes.forEach((n) => {
+ state.notes.forEach(n => {
if (n.notes) {
const resolved = n.notes.every(note => note.resolved && !note.system);
diff --git a/app/assets/javascripts/notes/stores/index.js b/app/assets/javascripts/notes/stores/index.js
index 488a9ca38d3..9ed19bf171e 100644
--- a/app/assets/javascripts/notes/stores/index.js
+++ b/app/assets/javascripts/notes/stores/index.js
@@ -12,6 +12,9 @@ export default new Vuex.Store({
targetNoteHash: null,
lastFetchedAt: null,
+ // View layer
+ isToggleStateButtonLoading: false,
+
// holds endpoints and permissions provided through haml
notesData: {},
userData: {},
diff --git a/app/assets/javascripts/notes/stores/mutation_types.js b/app/assets/javascripts/notes/stores/mutation_types.js
index da1b5a9e51a..b455e23ecde 100644
--- a/app/assets/javascripts/notes/stores/mutation_types.js
+++ b/app/assets/javascripts/notes/stores/mutation_types.js
@@ -17,3 +17,4 @@ export const UPDATE_DISCUSSION = 'UPDATE_DISCUSSION';
// Issue
export const CLOSE_ISSUE = 'CLOSE_ISSUE';
export const REOPEN_ISSUE = 'REOPEN_ISSUE';
+export const TOGGLE_STATE_BUTTON_LOADING = 'TOGGLE_STATE_BUTTON_LOADING';
diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js
index 963b40be3fd..c8edc06349f 100644
--- a/app/assets/javascripts/notes/stores/mutations.js
+++ b/app/assets/javascripts/notes/stores/mutations.js
@@ -7,7 +7,7 @@ export default {
[types.ADD_NEW_NOTE](state, note) {
const { discussion_id, type } = note;
const [exists] = state.notes.filter(n => n.id === note.discussion_id);
- const isDiscussion = (type === constants.DISCUSSION_NOTE);
+ const isDiscussion = type === constants.DISCUSSION_NOTE;
if (!exists) {
const noteData = {
@@ -63,13 +63,15 @@ export default {
const note = notes[i];
const children = note.notes;
- if (children.length && !note.individual_note) { // remove placeholder from discussions
+ if (children.length && !note.individual_note) {
+ // remove placeholder from discussions
for (let j = children.length - 1; j >= 0; j -= 1) {
if (children[j].isPlaceholderNote) {
children.splice(j, 1);
}
}
- } else if (note.isPlaceholderNote) { // remove placeholders from state root
+ } else if (note.isPlaceholderNote) {
+ // remove placeholders from state root
notes.splice(i, 1);
}
}
@@ -89,20 +91,22 @@ export default {
[types.SET_INITIAL_NOTES](state, notesData) {
const notes = [];
- notesData.forEach((note) => {
- const nn = Object.assign({}, note);
-
+ notesData.forEach(note => {
// To support legacy notes, should be very rare case.
if (note.individual_note && note.notes.length > 1) {
- note.notes.forEach((n) => {
- nn.notes = [n]; // override notes array to only have one item to mimick individual_note
- notes.push(nn);
+ note.notes.forEach(n => {
+ notes.push({
+ ...note,
+ notes: [n], // override notes array to only have one item to mimick individual_note
+ });
});
} else {
const oldNote = utils.findNoteObjectById(state.notes, note.id);
- nn.expanded = oldNote ? oldNote.expanded : note.expanded;
- notes.push(nn);
+ notes.push({
+ ...note,
+ expanded: oldNote ? oldNote.expanded : note.expanded,
+ });
}
});
@@ -126,7 +130,9 @@ export default {
notesArr.push({
individual_note: true,
isPlaceholderNote: true,
- placeholderType: data.isSystemNote ? constants.SYSTEM_NOTE : constants.NOTE,
+ placeholderType: data.isSystemNote
+ ? constants.SYSTEM_NOTE
+ : constants.NOTE,
notes: [
{
body: data.noteBody,
@@ -139,12 +145,16 @@ export default {
const { awardName, note } = data;
const { id, name, username } = state.userData;
- const hasEmojiAwardedByCurrentUser = note.award_emoji
- .filter(emoji => emoji.name === data.awardName && emoji.user.id === id);
+ const hasEmojiAwardedByCurrentUser = note.award_emoji.filter(
+ emoji => emoji.name === data.awardName && emoji.user.id === id,
+ );
if (hasEmojiAwardedByCurrentUser.length) {
// If current user has awarded this emoji, remove it.
- note.award_emoji.splice(note.award_emoji.indexOf(hasEmojiAwardedByCurrentUser[0]), 1);
+ note.award_emoji.splice(
+ note.award_emoji.indexOf(hasEmojiAwardedByCurrentUser[0]),
+ 1,
+ );
} else {
note.award_emoji.push({
name: awardName,
@@ -197,4 +207,8 @@ export default {
[types.REOPEN_ISSUE](state) {
Object.assign(state.noteableData, { state: constants.REOPENED });
},
+
+ [types.TOGGLE_STATE_BUTTON_LOADING](state, value) {
+ Object.assign(state, { isToggleStateButtonLoading: value });
+ },
};
diff --git a/app/assets/javascripts/notes/stores/utils.js b/app/assets/javascripts/notes/stores/utils.js
index 275263a2aaa..a0e096ebfaf 100644
--- a/app/assets/javascripts/notes/stores/utils.js
+++ b/app/assets/javascripts/notes/stores/utils.js
@@ -2,13 +2,15 @@ import AjaxCache from '~/lib/utils/ajax_cache';
const REGEX_QUICK_ACTIONS = /^\/\w+.*$/gm;
-export const findNoteObjectById = (notes, id) => notes.filter(n => n.id === id)[0];
+export const findNoteObjectById = (notes, id) =>
+ notes.filter(n => n.id === id)[0];
-export const getQuickActionText = (note) => {
+export const getQuickActionText = note => {
let text = 'Applying command';
- const quickActions = AjaxCache.get(gl.GfmAutoComplete.dataSources.commands) || [];
+ const quickActions =
+ AjaxCache.get(gl.GfmAutoComplete.dataSources.commands) || [];
- const executedCommands = quickActions.filter((command) => {
+ const executedCommands = quickActions.filter(command => {
const commandRegex = new RegExp(`/${command.name}`);
return commandRegex.test(note);
});
@@ -27,4 +29,5 @@ export const getQuickActionText = (note) => {
export const hasQuickActions = note => REGEX_QUICK_ACTIONS.test(note);
-export const stripQuickActions = note => note.replace(REGEX_QUICK_ACTIONS, '').trim();
+export const stripQuickActions = note =>
+ note.replace(REGEX_QUICK_ACTIONS, '').trim();
diff --git a/app/assets/javascripts/notifications_dropdown.js b/app/assets/javascripts/notifications_dropdown.js
index 479a512ed65..8ff8bb446ad 100644
--- a/app/assets/javascripts/notifications_dropdown.js
+++ b/app/assets/javascripts/notifications_dropdown.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import Flash from './flash';
export default function notificationsDropdown() {
diff --git a/app/assets/javascripts/notifications_form.js b/app/assets/javascripts/notifications_form.js
index 4e0afe13590..9e6cf67dff0 100644
--- a/app/assets/javascripts/notifications_form.js
+++ b/app/assets/javascripts/notifications_form.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import { __ } from './locale';
import axios from './lib/utils/axios_utils';
import flash from './flash';
diff --git a/app/assets/javascripts/pager.js b/app/assets/javascripts/pager.js
index 7e85bce0d73..86a43b66cc8 100644
--- a/app/assets/javascripts/pager.js
+++ b/app/assets/javascripts/pager.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import { getParameterByName } from '~/lib/utils/common_utils';
import axios from './lib/utils/axios_utils';
import { removeParams } from './lib/utils/url_utility';
diff --git a/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js b/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js
index 66702ec4ca0..15e737fff05 100644
--- a/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js
+++ b/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import { truncate } from '../../../lib/utils/text_utility';
const MAX_MESSAGE_LENGTH = 500;
diff --git a/app/assets/javascripts/pages/admin/admin.js b/app/assets/javascripts/pages/admin/admin.js
index 45e05f111a7..91f154b7ecd 100644
--- a/app/assets/javascripts/pages/admin/admin.js
+++ b/app/assets/javascripts/pages/admin/admin.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import { refreshCurrentPage } from '../../lib/utils/url_utility';
function showBlacklistType() {
diff --git a/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js b/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js
index f92450cbaa7..e7ceccb6f47 100644
--- a/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js
+++ b/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import _ from 'underscore';
import axios from '~/lib/utils/axios_utils';
import flash from '~/flash';
diff --git a/app/assets/javascripts/pages/admin/projects/index/index.js b/app/assets/javascripts/pages/admin/projects/index/index.js
index 3c597a1093e..ddbefec87b6 100644
--- a/app/assets/javascripts/pages/admin/projects/index/index.js
+++ b/app/assets/javascripts/pages/admin/projects/index/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import Vue from 'vue';
import Translate from '~/vue_shared/translate';
diff --git a/app/assets/javascripts/pages/admin/users/index.js b/app/assets/javascripts/pages/admin/users/index.js
index 4f5d6b55031..06599c3fd5f 100644
--- a/app/assets/javascripts/pages/admin/users/index.js
+++ b/app/assets/javascripts/pages/admin/users/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import Vue from 'vue';
import Translate from '~/vue_shared/translate';
diff --git a/app/assets/javascripts/pages/ci/lints/create/index.js b/app/assets/javascripts/pages/ci/lints/new/index.js
index 8e8a843da0b..8e8a843da0b 100644
--- a/app/assets/javascripts/pages/ci/lints/create/index.js
+++ b/app/assets/javascripts/pages/ci/lints/new/index.js
diff --git a/app/assets/javascripts/pages/dashboard/todos/index/todos.js b/app/assets/javascripts/pages/dashboard/todos/index/todos.js
index 42f7460ad55..c334eaa90f8 100644
--- a/app/assets/javascripts/pages/dashboard/todos/index/todos.js
+++ b/app/assets/javascripts/pages/dashboard/todos/index/todos.js
@@ -1,4 +1,6 @@
/* eslint-disable class-methods-use-this, no-unneeded-ternary, quote-props */
+
+import $ from 'jquery';
import { visitUrl } from '~/lib/utils/url_utility';
import UsersSelect from '~/users_select';
import { isMetaClick } from '~/lib/utils/common_utils';
diff --git a/app/assets/javascripts/pages/help/index/index.js b/app/assets/javascripts/pages/help/index/index.js
index 05c81fc618b..1bafe564a37 100644
--- a/app/assets/javascripts/pages/help/index/index.js
+++ b/app/assets/javascripts/pages/help/index/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import VersionCheckImage from '~/version_check_image';
import docs from '~/docs/docs_bundle';
diff --git a/app/assets/javascripts/pages/profiles/index.js b/app/assets/javascripts/pages/profiles/index.js
index c52ad7bc335..04e50963699 100644
--- a/app/assets/javascripts/pages/profiles/index.js
+++ b/app/assets/javascripts/pages/profiles/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import '~/profile/gl_crop';
import Profile from '~/profile/profile';
diff --git a/app/assets/javascripts/pages/profiles/notifications/show/index.js b/app/assets/javascripts/pages/profiles/notifications/show/index.js
new file mode 100644
index 00000000000..2e24a10fa5c
--- /dev/null
+++ b/app/assets/javascripts/pages/profiles/notifications/show/index.js
@@ -0,0 +1,7 @@
+import NotificationsForm from '../../../../notifications_form';
+import notificationsDropdown from '../../../../notifications_dropdown';
+
+document.addEventListener('DOMContentLoaded', () => {
+ new NotificationsForm(); // eslint-disable-line no-new
+ notificationsDropdown();
+});
diff --git a/app/assets/javascripts/pages/profiles/two_factor_auths/index.js b/app/assets/javascripts/pages/profiles/two_factor_auths/index.js
index 5b2473e0989..fbdef329ab2 100644
--- a/app/assets/javascripts/pages/profiles/two_factor_auths/index.js
+++ b/app/assets/javascripts/pages/profiles/two_factor_auths/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import U2FRegister from '~/u2f/register';
document.addEventListener('DOMContentLoaded', () => {
diff --git a/app/assets/javascripts/pages/projects/blob/show/index.js b/app/assets/javascripts/pages/projects/blob/show/index.js
index 26cbb279d4a..85c6862d629 100644
--- a/app/assets/javascripts/pages/projects/blob/show/index.js
+++ b/app/assets/javascripts/pages/projects/blob/show/index.js
@@ -1,7 +1,29 @@
+import Vue from 'vue';
+import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
import BlobViewer from '~/blob/viewer/index';
import initBlob from '~/pages/projects/init_blob';
document.addEventListener('DOMContentLoaded', () => {
new BlobViewer(); // eslint-disable-line no-new
initBlob();
+
+ const CommitPipelineStatusEl = document.querySelector('.js-commit-pipeline-status');
+ const statusLink = document.querySelector('.commit-actions .ci-status-link');
+ if (statusLink) {
+ statusLink.remove();
+ // eslint-disable-next-line no-new
+ new Vue({
+ el: CommitPipelineStatusEl,
+ components: {
+ commitPipelineStatus,
+ },
+ render(createElement) {
+ return createElement('commit-pipeline-status', {
+ props: {
+ endpoint: CommitPipelineStatusEl.dataset.endpoint,
+ },
+ });
+ },
+ });
+ }
});
diff --git a/app/assets/javascripts/pages/projects/branches/new/index.js b/app/assets/javascripts/pages/projects/branches/new/index.js
index d32d5c6cb29..a9658fd1eb4 100644
--- a/app/assets/javascripts/pages/projects/branches/new/index.js
+++ b/app/assets/javascripts/pages/projects/branches/new/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import NewBranchForm from '~/new_branch_form';
document.addEventListener('DOMContentLoaded', () => (
diff --git a/app/assets/javascripts/pages/projects/commit/pipelines/index.js b/app/assets/javascripts/pages/projects/commit/pipelines/index.js
index cd923f13ce8..8cc3cb0a57c 100644
--- a/app/assets/javascripts/pages/projects/commit/pipelines/index.js
+++ b/app/assets/javascripts/pages/projects/commit/pipelines/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown';
import initPipelines from '~/commit/pipelines/pipelines_bundle';
diff --git a/app/assets/javascripts/pages/projects/commit/show/index.js b/app/assets/javascripts/pages/projects/commit/show/index.js
index 1aeed197385..2e23cce11ce 100644
--- a/app/assets/javascripts/pages/projects/commit/show/index.js
+++ b/app/assets/javascripts/pages/projects/commit/show/index.js
@@ -1,4 +1,6 @@
/* eslint-disable no-new */
+
+import $ from 'jquery';
import Diff from '~/diff';
import ZenMode from '~/zen_mode';
import ShortcutsNavigation from '~/shortcuts_navigation';
diff --git a/app/assets/javascripts/pages/projects/find_file/show/index.js b/app/assets/javascripts/pages/projects/find_file/show/index.js
index 23d857d69ec..24630c2aa05 100644
--- a/app/assets/javascripts/pages/projects/find_file/show/index.js
+++ b/app/assets/javascripts/pages/projects/find_file/show/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import ProjectFindFile from '~/project_find_file';
import ShortcutsFindFile from '~/shortcuts_find_file';
diff --git a/app/assets/javascripts/pages/projects/graphs/charts/index.js b/app/assets/javascripts/pages/projects/graphs/charts/index.js
index 42df19c2968..80159a82bd4 100644
--- a/app/assets/javascripts/pages/projects/graphs/charts/index.js
+++ b/app/assets/javascripts/pages/projects/graphs/charts/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import Chart from 'chart.js';
import _ from 'underscore';
diff --git a/app/assets/javascripts/pages/projects/graphs/show/index.js b/app/assets/javascripts/pages/projects/graphs/show/index.js
index f516ff20995..71f629fbc13 100644
--- a/app/assets/javascripts/pages/projects/graphs/show/index.js
+++ b/app/assets/javascripts/pages/projects/graphs/show/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import flash from '~/flash';
import { __ } from '~/locale';
import axios from '~/lib/utils/axios_utils';
diff --git a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js
index 9ac0b4c07e5..653e2502d01 100644
--- a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js
+++ b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js
@@ -1,5 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, camelcase, one-var-declaration-per-line, quotes, no-param-reassign, quote-props, comma-dangle, prefer-template, max-len, no-return-assign, no-shadow */
+import $ from 'jquery';
import _ from 'underscore';
import { n__, s__, createDateTimeFormat, sprintf } from '~/locale';
import { ContributorsGraph, ContributorsAuthorGraph, ContributorsMasterGraph } from './stat_graph_contributors_graph';
diff --git a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js
index 6ffaa277a0a..a99ce0f1c36 100644
--- a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js
+++ b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js
@@ -1,4 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, max-len, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, comma-dangle, no-return-assign, prefer-arrow-callback, quotes, prefer-template, newline-per-chained-call, no-else-return, no-shadow */
+
+import $ from 'jquery';
import _ from 'underscore';
import { extent, max } from 'd3-array';
import { select, event as d3Event } from 'd3-selection';
diff --git a/app/assets/javascripts/pages/projects/issues/form.js b/app/assets/javascripts/pages/projects/issues/form.js
index 5c7daf84738..14fddbc9a05 100644
--- a/app/assets/javascripts/pages/projects/issues/form.js
+++ b/app/assets/javascripts/pages/projects/issues/form.js
@@ -1,4 +1,6 @@
/* eslint-disable no-new */
+
+import $ from 'jquery';
import GLForm from '~/gl_form';
import IssuableForm from '~/issuable_form';
import LabelsSelect from '~/labels_select';
diff --git a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js
index 8bfac606aab..406fc32f9a2 100644
--- a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js
+++ b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js
@@ -1,5 +1,6 @@
/* eslint-disable no-new */
+import $ from 'jquery';
import Diff from '~/diff';
import ShortcutsNavigation from '~/shortcuts_navigation';
import GLForm from '~/gl_form';
diff --git a/app/assets/javascripts/pages/projects/network/network.js b/app/assets/javascripts/pages/projects/network/network.js
index 7354243e4c8..aa50dd4bb25 100644
--- a/app/assets/javascripts/pages/projects/network/network.js
+++ b/app/assets/javascripts/pages/projects/network/network.js
@@ -1,5 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, quote-props, prefer-template, comma-dangle, max-len */
+import $ from 'jquery';
import BranchGraph from '../../../network/branch_graph';
export default (function() {
diff --git a/app/assets/javascripts/pages/projects/network/show/index.js b/app/assets/javascripts/pages/projects/network/show/index.js
index e7dfd2d0128..a0b14fed10f 100644
--- a/app/assets/javascripts/pages/projects/network/show/index.js
+++ b/app/assets/javascripts/pages/projects/network/show/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import ShortcutsNetwork from '../../../../shortcuts_network';
import Network from '../network';
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/target_branch_dropdown.js b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/target_branch_dropdown.js
index 0c3926d76b5..4ef0d11dd36 100644
--- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/target_branch_dropdown.js
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/target_branch_dropdown.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
export default class TargetBranchDropdown {
constructor() {
this.$dropdown = $('.js-target-branch-dropdown');
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js
index 95ed9c7dc21..95b57d5e048 100644
--- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js
@@ -1,5 +1,7 @@
/* eslint-disable class-methods-use-this */
+import $ from 'jquery';
+
const defaultTimezone = 'UTC';
export default class TimezoneDropdown {
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js
index cfd30d6053f..c3ac54733a3 100644
--- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import Vue from 'vue';
import Translate from '../../../../vue_shared/translate';
import GlFieldErrors from '../../../../gl_field_errors';
diff --git a/app/assets/javascripts/pages/projects/pipelines/charts/index.js b/app/assets/javascripts/pages/projects/pipelines/charts/index.js
index bb92f4e1459..07b6992eba1 100644
--- a/app/assets/javascripts/pages/projects/pipelines/charts/index.js
+++ b/app/assets/javascripts/pages/projects/pipelines/charts/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import Chart from 'chart.js';
const options = {
diff --git a/app/assets/javascripts/pages/projects/pipelines/new/index.js b/app/assets/javascripts/pages/projects/pipelines/new/index.js
index da20bd995e9..9aa8945e268 100644
--- a/app/assets/javascripts/pages/projects/pipelines/new/index.js
+++ b/app/assets/javascripts/pages/projects/pipelines/new/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import NewBranchForm from '~/new_branch_form';
document.addEventListener('DOMContentLoaded', () => {
diff --git a/app/assets/javascripts/pages/projects/project.js b/app/assets/javascripts/pages/projects/project.js
index d23ad9a92f4..c1e3425ec75 100644
--- a/app/assets/javascripts/pages/projects/project.js
+++ b/app/assets/javascripts/pages/projects/project.js
@@ -1,5 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, no-var, consistent-return, no-new, prefer-arrow-callback, no-return-assign, one-var, one-var-declaration-per-line, object-shorthand, no-else-return, newline-per-chained-call, no-shadow, vars-on-top, prefer-template, max-len */
+import $ from 'jquery';
import Cookies from 'js-cookie';
import { __ } from '~/locale';
import { visitUrl } from '~/lib/utils/url_utility';
diff --git a/app/assets/javascripts/pages/projects/releases/edit/index.js b/app/assets/javascripts/pages/projects/releases/edit/index.js
index 0bf53a8de09..c70271b09c4 100644
--- a/app/assets/javascripts/pages/projects/releases/edit/index.js
+++ b/app/assets/javascripts/pages/projects/releases/edit/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import initForm from '~/pages/projects/init_form';
document.addEventListener('DOMContentLoaded', () => initForm($('.release-form')));
diff --git a/app/assets/javascripts/pages/projects/shared/project_avatar.js b/app/assets/javascripts/pages/projects/shared/project_avatar.js
index 56627aa155c..447877752fe 100644
--- a/app/assets/javascripts/pages/projects/shared/project_avatar.js
+++ b/app/assets/javascripts/pages/projects/shared/project_avatar.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
export default function projectAvatar() {
$('.js-choose-project-avatar-button').bind('click', function onClickAvatar() {
const form = $(this).closest('form');
diff --git a/app/assets/javascripts/pages/projects/shared/project_new.js b/app/assets/javascripts/pages/projects/shared/project_new.js
index 86faba0b910..56d5574aa2f 100644
--- a/app/assets/javascripts/pages/projects/shared/project_new.js
+++ b/app/assets/javascripts/pages/projects/shared/project_new.js
@@ -1,5 +1,6 @@
/* eslint-disable func-names, no-var, no-underscore-dangle, prefer-template, prefer-arrow-callback*/
+import $ from 'jquery';
import VisibilitySelect from '../../../visibility_select';
function highlightChanges($elm) {
diff --git a/app/assets/javascripts/pages/projects/show/index.js b/app/assets/javascripts/pages/projects/show/index.js
index 9b87f249f09..3b0f0f960b8 100644
--- a/app/assets/javascripts/pages/projects/show/index.js
+++ b/app/assets/javascripts/pages/projects/show/index.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+import initBlob from '~/blob_edit/blob_bundle';
import ShortcutsNavigation from '~/shortcuts_navigation';
import NotificationsForm from '~/notifications_form';
import UserCallout from '~/user_callout';
@@ -18,10 +20,22 @@ document.addEventListener('DOMContentLoaded', () => {
className: 'js-autodevops-banner',
});
- if ($('#tree-slider').length) new TreeView(); // eslint-disable-line no-new
- if ($('.blob-viewer').length) new BlobViewer(); // eslint-disable-line no-new
- if ($('.project-show-activity').length) new Activities(); // eslint-disable-line no-new
- $('#tree-slider').waitForImages(() => {
+ // Project show page loads different overview content based on user preferences
+ const treeSlider = document.querySelector('#tree-slider');
+ if (treeSlider) {
+ new TreeView(); // eslint-disable-line no-new
+ initBlob();
+ }
+
+ if (document.querySelector('.blob-viewer')) {
+ new BlobViewer(); // eslint-disable-line no-new
+ }
+
+ if (document.querySelector('.project-show-activity')) {
+ new Activities(); // eslint-disable-line no-new
+ }
+
+ $(treeSlider).waitForImages(() => {
ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath);
});
});
diff --git a/app/assets/javascripts/pages/projects/snippets/edit/index.js b/app/assets/javascripts/pages/projects/snippets/edit/index.js
index c15f798b630..53606acc508 100644
--- a/app/assets/javascripts/pages/projects/snippets/edit/index.js
+++ b/app/assets/javascripts/pages/projects/snippets/edit/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import initSnippet from '~/snippet/snippet_bundle';
import initForm from '~/pages/projects/init_form';
diff --git a/app/assets/javascripts/pages/projects/snippets/new/index.js b/app/assets/javascripts/pages/projects/snippets/new/index.js
index c15f798b630..53606acc508 100644
--- a/app/assets/javascripts/pages/projects/snippets/new/index.js
+++ b/app/assets/javascripts/pages/projects/snippets/new/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import initSnippet from '~/snippet/snippet_bundle';
import initForm from '~/pages/projects/init_form';
diff --git a/app/assets/javascripts/pages/projects/tags/new/index.js b/app/assets/javascripts/pages/projects/tags/new/index.js
index 191c98b36bb..8d0edf7e06c 100644
--- a/app/assets/javascripts/pages/projects/tags/new/index.js
+++ b/app/assets/javascripts/pages/projects/tags/new/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import RefSelectDropdown from '../../../../ref_select_dropdown';
import ZenMode from '../../../../zen_mode';
import GLForm from '../../../../gl_form';
diff --git a/app/assets/javascripts/pages/projects/tree/show/index.js b/app/assets/javascripts/pages/projects/tree/show/index.js
index ed7d3f1747c..7ad082a5e61 100644
--- a/app/assets/javascripts/pages/projects/tree/show/index.js
+++ b/app/assets/javascripts/pages/projects/tree/show/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import Vue from 'vue';
import initBlob from '~/blob_edit/blob_bundle';
import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
diff --git a/app/assets/javascripts/pages/projects/wikis/index.js b/app/assets/javascripts/pages/projects/wikis/index.js
index b9f8707fd6e..ec01c66ffda 100644
--- a/app/assets/javascripts/pages/projects/wikis/index.js
+++ b/app/assets/javascripts/pages/projects/wikis/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import Wikis from './wikis';
import ShortcutsWiki from '../../../shortcuts_wiki';
import ZenMode from '../../../zen_mode';
diff --git a/app/assets/javascripts/pages/search/show/search.js b/app/assets/javascripts/pages/search/show/search.js
index cf44e291199..2e1fe78b3fa 100644
--- a/app/assets/javascripts/pages/search/show/search.js
+++ b/app/assets/javascripts/pages/search/show/search.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import Flash from '~/flash';
import Api from '~/api';
diff --git a/app/assets/javascripts/pages/sessions/new/index.js b/app/assets/javascripts/pages/sessions/new/index.js
index a0aa0499776..80a7114f94d 100644
--- a/app/assets/javascripts/pages/sessions/new/index.js
+++ b/app/assets/javascripts/pages/sessions/new/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import UsernameValidator from './username_validator';
import SigninTabsMemoizer from './signin_tabs_memoizer';
import OAuthRememberMe from './oauth_remember_me';
diff --git a/app/assets/javascripts/pages/sessions/new/oauth_remember_me.js b/app/assets/javascripts/pages/sessions/new/oauth_remember_me.js
index ffc2dd6bbca..53030045292 100644
--- a/app/assets/javascripts/pages/sessions/new/oauth_remember_me.js
+++ b/app/assets/javascripts/pages/sessions/new/oauth_remember_me.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
/**
* OAuth-based login buttons have a separate "remember me" checkbox.
*
diff --git a/app/assets/javascripts/pages/sessions/new/username_validator.js b/app/assets/javascripts/pages/sessions/new/username_validator.js
index 745543c22da..825de01b5a2 100644
--- a/app/assets/javascripts/pages/sessions/new/username_validator.js
+++ b/app/assets/javascripts/pages/sessions/new/username_validator.js
@@ -1,5 +1,6 @@
/* eslint-disable comma-dangle, consistent-return, class-methods-use-this, arrow-parens, no-param-reassign, max-len */
+import $ from 'jquery';
import _ from 'underscore';
import axios from '~/lib/utils/axios_utils';
import flash from '~/flash';
diff --git a/app/assets/javascripts/pages/snippets/form.js b/app/assets/javascripts/pages/snippets/form.js
index f996d3cd74e..72d05da1069 100644
--- a/app/assets/javascripts/pages/snippets/form.js
+++ b/app/assets/javascripts/pages/snippets/form.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import GLForm from '~/gl_form';
import ZenMode from '~/zen_mode';
diff --git a/app/assets/javascripts/pages/users/activity_calendar.js b/app/assets/javascripts/pages/users/activity_calendar.js
index 57306322aa4..8ce938c958b 100644
--- a/app/assets/javascripts/pages/users/activity_calendar.js
+++ b/app/assets/javascripts/pages/users/activity_calendar.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import _ from 'underscore';
import { scaleLinear, scaleThreshold } from 'd3-scale';
import { select } from 'd3-selection';
diff --git a/app/assets/javascripts/pages/users/index.js b/app/assets/javascripts/pages/users/index.js
index 899dcd42e37..6b1626b0161 100644
--- a/app/assets/javascripts/pages/users/index.js
+++ b/app/assets/javascripts/pages/users/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import UserCallout from '~/user_callout';
import Cookies from 'js-cookie';
import UserTabs from './user_tabs';
diff --git a/app/assets/javascripts/pages/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js
index c1217623467..124bc2ba710 100644
--- a/app/assets/javascripts/pages/users/user_tabs.js
+++ b/app/assets/javascripts/pages/users/user_tabs.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import axios from '~/lib/utils/axios_utils';
import Activities from '~/activities';
import { localTimeAgo } from '~/lib/utils/datetime_utility';
diff --git a/app/assets/javascripts/performance_bar.js b/app/assets/javascripts/performance_bar.js
index 0562a681c4b..c22598ee665 100644
--- a/app/assets/javascripts/performance_bar.js
+++ b/app/assets/javascripts/performance_bar.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import 'vendor/peek';
import 'vendor/peek.performance_bar';
import { getParameterValues } from './lib/utils/url_utility';
@@ -13,8 +14,6 @@ export default class PerformanceBar {
init(opts) {
const $container = $(opts.container);
- this.$sqlProfileLink = $container.find('.js-toggle-modal-peek-sql');
- this.$sqlProfileModal = $container.find('#modal-peek-pg-queries');
this.$lineProfileLink = $container.find('.js-toggle-modal-peek-line-profile');
this.$lineProfileModal = $('#modal-peek-line-profile');
this.initEventListeners();
@@ -22,7 +21,6 @@ export default class PerformanceBar {
}
initEventListeners() {
- this.$sqlProfileLink.on('click', () => this.handleSQLProfileLink());
this.$lineProfileLink.on('click', e => this.handleLineProfileLink(e));
$(document).on('click', '.js-lineprof-file', PerformanceBar.toggleLineProfileFile);
}
@@ -35,10 +33,6 @@ export default class PerformanceBar {
}
}
- handleSQLProfileLink() {
- PerformanceBar.toggleModal(this.$sqlProfileModal);
- }
-
handleLineProfileLink(e) {
const lineProfilerParameter = getParameterValues('lineprofiler');
const lineProfilerParameterRegex = new RegExp(`lineprofiler=${lineProfilerParameter[0]}`);
diff --git a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue b/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue
index b86e95f0b4a..be213c2ee78 100644
--- a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue
@@ -1,4 +1,5 @@
<script>
+ import $ from 'jquery';
import jobNameComponent from './job_name_component.vue';
import jobComponent from './job_component.vue';
import tooltip from '../../../vue_shared/directives/tooltip';
diff --git a/app/assets/javascripts/pipelines/components/nav_controls.vue b/app/assets/javascripts/pipelines/components/nav_controls.vue
index 383ab51fe56..eba5678e3e5 100644
--- a/app/assets/javascripts/pipelines/components/nav_controls.vue
+++ b/app/assets/javascripts/pipelines/components/nav_controls.vue
@@ -1,6 +1,11 @@
<script>
+ import LoadingButton from '../../vue_shared/components/loading_button.vue';
+
export default {
name: 'PipelineNavControls',
+ components: {
+ LoadingButton,
+ },
props: {
newPipelinePath: {
type: String,
@@ -19,6 +24,17 @@
required: false,
default: null,
},
+
+ isResetCacheButtonLoading: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ methods: {
+ onClickResetCache() {
+ this.$emit('resetRunnersCache', this.resetCachePath);
+ },
},
};
</script>
@@ -32,14 +48,13 @@
{{ s__('Pipelines|Run Pipeline') }}
</a>
- <a
+ <loading-button
v-if="resetCachePath"
- data-method="post"
- :href="resetCachePath"
+ @click="onClickResetCache"
+ :loading="isResetCacheButtonLoading"
class="btn btn-default js-clear-cache"
- >
- {{ s__('Pipelines|Clear Runner Caches') }}
- </a>
+ :label="s__('Pipelines|Clear Runner Caches')"
+ />
<a
v-if="ciLintPath"
diff --git a/app/assets/javascripts/pipelines/components/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines.vue
index 6e5ee68eeb1..e0a7284124d 100644
--- a/app/assets/javascripts/pipelines/components/pipelines.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines.vue
@@ -1,6 +1,7 @@
<script>
import _ from 'underscore';
import { __, sprintf, s__ } from '../../locale';
+ import createFlash from '../../flash';
import PipelinesService from '../services/pipelines_service';
import pipelinesMixin from '../mixins/pipelines';
import TablePagination from '../../vue_shared/components/table_pagination.vue';
@@ -92,6 +93,7 @@
scope: getParameterByName('scope') || 'all',
page: getParameterByName('page') || '1',
requestData: {},
+ isResetCacheButtonLoading: false,
};
},
stateMap: {
@@ -265,6 +267,23 @@
this.poll.restart({ data: this.requestData });
});
},
+
+ handleResetRunnersCache(endpoint) {
+ this.isResetCacheButtonLoading = true;
+
+ this.service.postAction(endpoint)
+ .then(() => {
+ this.isResetCacheButtonLoading = false;
+ createFlash(
+ s__('Pipelines|Project cache successfully reset.'),
+ 'notice',
+ );
+ })
+ .catch(() => {
+ this.isResetCacheButtonLoading = false;
+ createFlash(s__('Pipelines|Something went wrong while cleaning runners cache.'));
+ });
+ },
},
};
</script>
@@ -301,6 +320,8 @@
:new-pipeline-path="newPipelinePath"
:reset-cache-path="resetCachePath"
:ci-lint-path="ciLintPath"
+ @resetRunnersCache="handleResetRunnersCache"
+ :is-reset-cache-button-loading="isResetCacheButtonLoading"
/>
</div>
diff --git a/app/assets/javascripts/pipelines/components/stage.vue b/app/assets/javascripts/pipelines/components/stage.vue
index ecf2b10486e..8bc7a1f20b2 100644
--- a/app/assets/javascripts/pipelines/components/stage.vue
+++ b/app/assets/javascripts/pipelines/components/stage.vue
@@ -1,4 +1,5 @@
<script>
+ import $ from 'jquery';
/**
* Renders each stage of the pipeline mini graph.
diff --git a/app/assets/javascripts/pipelines/mixins/pipelines.js b/app/assets/javascripts/pipelines/mixins/pipelines.js
index 9fcc07abee5..522a4277bd7 100644
--- a/app/assets/javascripts/pipelines/mixins/pipelines.js
+++ b/app/assets/javascripts/pipelines/mixins/pipelines.js
@@ -51,12 +51,10 @@ export default {
}
});
- eventHub.$on('refreshPipelines', this.fetchPipelines);
eventHub.$on('postAction', this.postAction);
},
beforeDestroy() {
- eventHub.$off('refreshPipelines');
- eventHub.$on('postAction', this.postAction);
+ eventHub.$off('postAction', this.postAction);
},
destroyed() {
this.poll.stop();
@@ -92,7 +90,7 @@ export default {
},
postAction(endpoint) {
this.service.postAction(endpoint)
- .then(() => eventHub.$emit('refreshPipelines'))
+ .then(() => this.fetchPipelines())
.catch(() => Flash(__('An error occurred while making the request.')));
},
},
diff --git a/app/assets/javascripts/preview_markdown.js b/app/assets/javascripts/preview_markdown.js
index 464bfb351e7..246a265ef2b 100644
--- a/app/assets/javascripts/preview_markdown.js
+++ b/app/assets/javascripts/preview_markdown.js
@@ -1,5 +1,10 @@
/* eslint-disable func-names, no-var, object-shorthand, comma-dangle, prefer-arrow-callback */
+import $ from 'jquery';
+import axios from '~/lib/utils/axios_utils';
+import flash from '~/flash';
+import { __ } from '~/locale';
+
// MarkdownPreview
//
// Handles toggling the "Write" and "Preview" tab clicks, rendering the preview
@@ -7,10 +12,6 @@
// more than `x` users are referenced.
//
-import axios from '~/lib/utils/axios_utils';
-import flash from '~/flash';
-import { __ } from '~/locale';
-
var lastTextareaPreviewed;
var lastTextareaHeight = null;
var markdownPreview;
diff --git a/app/assets/javascripts/profile/gl_crop.js b/app/assets/javascripts/profile/gl_crop.js
index 4bdda611cfc..8f93156cdd1 100644
--- a/app/assets/javascripts/profile/gl_crop.js
+++ b/app/assets/javascripts/profile/gl_crop.js
@@ -1,5 +1,6 @@
/* eslint-disable no-useless-escape, max-len, quotes, no-var, no-underscore-dangle, func-names, space-before-function-paren, no-unused-vars, no-return-assign, object-shorthand, one-var, one-var-declaration-per-line, comma-dangle, consistent-return, class-methods-use-this, new-parens */
+import $ from 'jquery';
import 'cropper';
import _ from 'underscore';
diff --git a/app/assets/javascripts/profile/profile.js b/app/assets/javascripts/profile/profile.js
index a811781853b..3c1bef23446 100644
--- a/app/assets/javascripts/profile/profile.js
+++ b/app/assets/javascripts/profile/profile.js
@@ -1,4 +1,6 @@
/* eslint-disable comma-dangle, no-unused-vars, class-methods-use-this, quotes, consistent-return, func-names, prefer-arrow-callback, space-before-function-paren, max-len */
+
+import $ from 'jquery';
import Cookies from 'js-cookie';
import axios from '~/lib/utils/axios_utils';
import { __ } from '~/locale';
diff --git a/app/assets/javascripts/project_edit.js b/app/assets/javascripts/project_edit.js
index 7572fec15e0..47bf2226781 100644
--- a/app/assets/javascripts/project_edit.js
+++ b/app/assets/javascripts/project_edit.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
export default function setupProjectEdit() {
const $transferForm = $('.js-project-transfer-form');
const $selectNamespace = $transferForm.find('select.select2');
diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/project_find_file.js
index 4fd639cce8e..4c4acd487f8 100644
--- a/app/assets/javascripts/project_find_file.js
+++ b/app/assets/javascripts/project_find_file.js
@@ -1,5 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, consistent-return, one-var, one-var-declaration-per-line, no-cond-assign, max-len, object-shorthand, no-param-reassign, comma-dangle, prefer-template, no-unused-vars, no-return-assign */
+import $ from 'jquery';
import fuzzaldrinPlus from 'fuzzaldrin-plus';
import axios from '~/lib/utils/axios_utils';
import flash from '~/flash';
diff --git a/app/assets/javascripts/project_fork.js b/app/assets/javascripts/project_fork.js
index 65d46fa9a73..6fedd94a6a9 100644
--- a/app/assets/javascripts/project_fork.js
+++ b/app/assets/javascripts/project_fork.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
export default () => {
$('.js-fork-thumbnail').on('click', function forkThumbnailClicked() {
if ($(this).hasClass('disabled')) return false;
diff --git a/app/assets/javascripts/project_label_subscription.js b/app/assets/javascripts/project_label_subscription.js
index 64b7dd540f9..f31beb4dc78 100644
--- a/app/assets/javascripts/project_label_subscription.js
+++ b/app/assets/javascripts/project_label_subscription.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import { __ } from './locale';
import axios from './lib/utils/axios_utils';
import flash from './flash';
diff --git a/app/assets/javascripts/project_select.js b/app/assets/javascripts/project_select.js
index 412aca7bfed..cb2e6855d1d 100644
--- a/app/assets/javascripts/project_select.js
+++ b/app/assets/javascripts/project_select.js
@@ -1,4 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-var, comma-dangle, object-shorthand, one-var, one-var-declaration-per-line, no-else-return, quotes, max-len */
+
+import $ from 'jquery';
import Api from './api';
import ProjectSelectComboButton from './project_select_combo_button';
diff --git a/app/assets/javascripts/project_select_combo_button.js b/app/assets/javascripts/project_select_combo_button.js
index 99cea683d9a..9b404896e86 100644
--- a/app/assets/javascripts/project_select_combo_button.js
+++ b/app/assets/javascripts/project_select_combo_button.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import AccessorUtilities from './lib/utils/accessor';
export default class ProjectSelectComboButton {
diff --git a/app/assets/javascripts/project_visibility.js b/app/assets/javascripts/project_visibility.js
index c3f5e8cb907..7c95c71e239 100644
--- a/app/assets/javascripts/project_visibility.js
+++ b/app/assets/javascripts/project_visibility.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
function setVisibilityOptions(namespaceSelector) {
if (!namespaceSelector || !('selectedIndex' in namespaceSelector)) {
return;
diff --git a/app/assets/javascripts/projects/project_import_gitlab_project.js b/app/assets/javascripts/projects/project_import_gitlab_project.js
index d2c7d77bb2d..4e20fce1460 100644
--- a/app/assets/javascripts/projects/project_import_gitlab_project.js
+++ b/app/assets/javascripts/projects/project_import_gitlab_project.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import { getParameterValues } from '../lib/utils/url_utility';
export default () => {
diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js
index 8da37d14f0b..93603dfc14d 100644
--- a/app/assets/javascripts/projects/project_new.js
+++ b/app/assets/javascripts/projects/project_new.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import { addSelectOnFocusBehaviour } from '../lib/utils/common_utils';
let hasUserDefinedProjectPath = false;
diff --git a/app/assets/javascripts/projects_dropdown/index.js b/app/assets/javascripts/projects_dropdown/index.js
index e78ebce2923..e1ca70c51a6 100644
--- a/app/assets/javascripts/projects_dropdown/index.js
+++ b/app/assets/javascripts/projects_dropdown/index.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import Vue from 'vue';
import Translate from '../vue_shared/translate';
diff --git a/app/assets/javascripts/prometheus_metrics/prometheus_metrics.js b/app/assets/javascripts/prometheus_metrics/prometheus_metrics.js
index e8126ac573d..0a60f4845b2 100644
--- a/app/assets/javascripts/prometheus_metrics/prometheus_metrics.js
+++ b/app/assets/javascripts/prometheus_metrics/prometheus_metrics.js
@@ -1,3 +1,6 @@
+import $ from 'jquery';
+import _ from 'underscore';
+import { s__, n__, sprintf } from '~/locale';
import axios from '../lib/utils/axios_utils';
import PANEL_STATE from './constants';
import { backOff } from '../lib/utils/common_utils';
@@ -20,6 +23,7 @@ export default class PrometheusMetrics {
this.$missingEnvVarMetricsList = this.$missingEnvVarPanel.find('.js-missing-var-metrics-list');
this.activeMetricsEndpoint = this.$monitoredMetricsPanel.data('activeMetrics');
+ this.helpMetricsPath = this.$monitoredMetricsPanel.data('metrics-help-path');
this.$panelToggle.on('click', e => this.handlePanelToggle(e));
}
@@ -59,23 +63,39 @@ export default class PrometheusMetrics {
populateActiveMetrics(metrics) {
let totalMonitoredMetrics = 0;
let totalMissingEnvVarMetrics = 0;
+ let totalExporters = 0;
metrics.forEach((metric) => {
- this.$monitoredMetricsList.append(`<li>${metric.group}<span class="badge">${metric.active_metrics}</span></li>`);
- totalMonitoredMetrics += metric.active_metrics;
- if (metric.metrics_missing_requirements > 0) {
- this.$missingEnvVarMetricsList.append(`<li>${metric.group}</li>`);
- totalMissingEnvVarMetrics += 1;
+ if (metric.active_metrics > 0) {
+ totalExporters += 1;
+ this.$monitoredMetricsList.append(`<li>${_.escape(metric.group)}<span class="badge">${_.escape(metric.active_metrics)}</span></li>`);
+ totalMonitoredMetrics += metric.active_metrics;
+ if (metric.metrics_missing_requirements > 0) {
+ this.$missingEnvVarMetricsList.append(`<li>${_.escape(metric.group)}</li>`);
+ totalMissingEnvVarMetrics += 1;
+ }
}
});
- this.$monitoredMetricsCount.text(totalMonitoredMetrics);
- this.showMonitoringMetricsPanelState(PANEL_STATE.LIST);
+ if (totalMonitoredMetrics === 0) {
+ const emptyCommonMetricsText = sprintf(s__('PrometheusService|<p class="text-tertiary">No <a href="%{docsUrl}">common metrics</a> were found</p>'), {
+ docsUrl: this.helpMetricsPath,
+ }, false);
+ this.$monitoredMetricsEmpty.empty();
+ this.$monitoredMetricsEmpty.append(emptyCommonMetricsText);
+ this.showMonitoringMetricsPanelState(PANEL_STATE.EMPTY);
+ } else {
+ const metricsCountText = sprintf(s__('PrometheusService|%{exporters} with %{metrics} were found'), {
+ exporters: n__('%d exporter', '%d exporters', totalExporters),
+ metrics: n__('%d metric', '%d metrics', totalMonitoredMetrics),
+ });
+ this.$monitoredMetricsCount.text(metricsCountText);
+ this.showMonitoringMetricsPanelState(PANEL_STATE.LIST);
- if (totalMissingEnvVarMetrics > 0) {
- this.$missingEnvVarPanel.removeClass('hidden');
- this.$missingEnvVarPanel.find('.flash-container').off('click');
- this.$missingEnvVarMetricCount.text(totalMissingEnvVarMetrics);
+ if (totalMissingEnvVarMetrics > 0) {
+ this.$missingEnvVarPanel.removeClass('hidden');
+ this.$missingEnvVarMetricCount.text(totalMissingEnvVarMetrics);
+ }
}
}
@@ -97,15 +117,15 @@ export default class PrometheusMetrics {
})
.catch(stop);
})
- .then((res) => {
- if (res && res.data && res.data.length) {
- this.populateActiveMetrics(res.data);
- } else {
+ .then((res) => {
+ if (res && res.data && res.data.length) {
+ this.populateActiveMetrics(res.data);
+ } else {
+ this.showMonitoringMetricsPanelState(PANEL_STATE.EMPTY);
+ }
+ })
+ .catch(() => {
this.showMonitoringMetricsPanelState(PANEL_STATE.EMPTY);
- }
- })
- .catch(() => {
- this.showMonitoringMetricsPanelState(PANEL_STATE.EMPTY);
- });
+ });
}
}
diff --git a/app/assets/javascripts/protected_branches/protected_branch_create.js b/app/assets/javascripts/protected_branches/protected_branch_create.js
index 8fc87633e18..7c61c070a35 100644
--- a/app/assets/javascripts/protected_branches/protected_branch_create.js
+++ b/app/assets/javascripts/protected_branches/protected_branch_create.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import _ from 'underscore';
import ProtectedBranchAccessDropdown from './protected_branch_access_dropdown';
import CreateItemDropdown from '../create_item_dropdown';
diff --git a/app/assets/javascripts/protected_branches/protected_branch_edit_list.js b/app/assets/javascripts/protected_branches/protected_branch_edit_list.js
index b40d3827c30..10253c0febc 100644
--- a/app/assets/javascripts/protected_branches/protected_branch_edit_list.js
+++ b/app/assets/javascripts/protected_branches/protected_branch_edit_list.js
@@ -1,5 +1,6 @@
/* eslint-disable no-new */
+import $ from 'jquery';
import ProtectedBranchEdit from './protected_branch_edit';
export default class ProtectedBranchEditList {
diff --git a/app/assets/javascripts/protected_tags/protected_tag_create.js b/app/assets/javascripts/protected_tags/protected_tag_create.js
index 2f94ffe2507..2f8116df0d2 100644
--- a/app/assets/javascripts/protected_tags/protected_tag_create.js
+++ b/app/assets/javascripts/protected_tags/protected_tag_create.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import ProtectedTagAccessDropdown from './protected_tag_access_dropdown';
import CreateItemDropdown from '../create_item_dropdown';
diff --git a/app/assets/javascripts/protected_tags/protected_tag_edit_list.js b/app/assets/javascripts/protected_tags/protected_tag_edit_list.js
index bd9fc872266..b35bf4d4606 100644
--- a/app/assets/javascripts/protected_tags/protected_tag_edit_list.js
+++ b/app/assets/javascripts/protected_tags/protected_tag_edit_list.js
@@ -1,5 +1,6 @@
/* eslint-disable no-new */
+import $ from 'jquery';
import ProtectedTagEdit from './protected_tag_edit';
export default class ProtectedTagEditList {
diff --git a/app/assets/javascripts/ref_select_dropdown.js b/app/assets/javascripts/ref_select_dropdown.js
index 56c25a35e6d..95c5cf7b345 100644
--- a/app/assets/javascripts/ref_select_dropdown.js
+++ b/app/assets/javascripts/ref_select_dropdown.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
class RefSelectDropdown {
constructor($dropdownButton, availableRefs) {
const availableRefsValue = availableRefs || JSON.parse(document.getElementById('availableRefs').innerHTML);
diff --git a/app/assets/javascripts/render_gfm.js b/app/assets/javascripts/render_gfm.js
index 05a623ca6d9..94fffcd2f61 100644
--- a/app/assets/javascripts/render_gfm.js
+++ b/app/assets/javascripts/render_gfm.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import renderMath from './render_math';
import renderMermaid from './render_mermaid';
import syntaxHighlight from './syntax_highlight';
diff --git a/app/assets/javascripts/render_math.js b/app/assets/javascripts/render_math.js
index eabdb01b2a9..8572bf64d46 100644
--- a/app/assets/javascripts/render_math.js
+++ b/app/assets/javascripts/render_math.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import { __ } from './locale';
import flash from './flash';
diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js
index 8d3cc849f81..2088a49590a 100644
--- a/app/assets/javascripts/right_sidebar.js
+++ b/app/assets/javascripts/right_sidebar.js
@@ -1,5 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-unused-vars, consistent-return, one-var, one-var-declaration-per-line, quotes, prefer-template, object-shorthand, comma-dangle, no-else-return, no-param-reassign, max-len */
+import $ from 'jquery';
import _ from 'underscore';
import Cookies from 'js-cookie';
import flash from './flash';
diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js
index fdfa4f28aba..7dd3e9858c6 100644
--- a/app/assets/javascripts/search_autocomplete.js
+++ b/app/assets/javascripts/search_autocomplete.js
@@ -1,4 +1,6 @@
/* eslint-disable no-return-assign, one-var, no-var, no-underscore-dangle, one-var-declaration-per-line, no-unused-vars, no-cond-assign, consistent-return, object-shorthand, prefer-arrow-callback, func-names, space-before-function-paren, prefer-template, quotes, class-methods-use-this, no-sequences, wrap-iife, no-lonely-if, no-else-return, no-param-reassign, vars-on-top, max-len */
+
+import $ from 'jquery';
import axios from './lib/utils/axios_utils';
import DropdownUtils from './filtered_search/dropdown_utils';
import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from './lib/utils/common_utils';
diff --git a/app/assets/javascripts/settings_panels.js b/app/assets/javascripts/settings_panels.js
index d0e4f533d8a..eecde4550f9 100644
--- a/app/assets/javascripts/settings_panels.js
+++ b/app/assets/javascripts/settings_panels.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
function expandSection($section) {
$section.find('.js-settings-toggle').text('Collapse');
$section.find('.settings-content').off('scroll.expandSection').scrollTop(0);
diff --git a/app/assets/javascripts/shared/milestones/form.js b/app/assets/javascripts/shared/milestones/form.js
index db466f722c4..2f974d6ff9d 100644
--- a/app/assets/javascripts/shared/milestones/form.js
+++ b/app/assets/javascripts/shared/milestones/form.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import ZenMode from '../../zen_mode';
import DueDateSelectors from '../../due_date_select';
import GLForm from '../../gl_form';
diff --git a/app/assets/javascripts/shared/sessions/u2f.js b/app/assets/javascripts/shared/sessions/u2f.js
index 1d075f7e872..6ae9faf1dde 100644
--- a/app/assets/javascripts/shared/sessions/u2f.js
+++ b/app/assets/javascripts/shared/sessions/u2f.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import U2FAuthenticate from '../../u2f/authenticate';
export default () => {
diff --git a/app/assets/javascripts/shortcuts.js b/app/assets/javascripts/shortcuts.js
index c5dddd001bb..e31e067033f 100644
--- a/app/assets/javascripts/shortcuts.js
+++ b/app/assets/javascripts/shortcuts.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import Cookies from 'js-cookie';
import Mousetrap from 'mousetrap';
import axios from './lib/utils/axios_utils';
diff --git a/app/assets/javascripts/shortcuts_issuable.js b/app/assets/javascripts/shortcuts_issuable.js
index 14545824e74..3031230277d 100644
--- a/app/assets/javascripts/shortcuts_issuable.js
+++ b/app/assets/javascripts/shortcuts_issuable.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import Mousetrap from 'mousetrap';
import _ from 'underscore';
import Sidebar from './right_sidebar';
diff --git a/app/assets/javascripts/sidebar/components/assignees/assignee_title.js b/app/assets/javascripts/sidebar/components/assignees/assignee_title.js
deleted file mode 100644
index 129ba2e4e89..00000000000
--- a/app/assets/javascripts/sidebar/components/assignees/assignee_title.js
+++ /dev/null
@@ -1,59 +0,0 @@
-export default {
- name: 'AssigneeTitle',
- props: {
- loading: {
- type: Boolean,
- required: false,
- default: false,
- },
- numberOfAssignees: {
- type: Number,
- required: true,
- },
- editable: {
- type: Boolean,
- required: true,
- },
- showToggle: {
- type: Boolean,
- required: false,
- default: false,
- },
- },
- computed: {
- assigneeTitle() {
- const assignees = this.numberOfAssignees;
- return assignees > 1 ? `${assignees} Assignees` : 'Assignee';
- },
- },
- template: `
- <div class="title hide-collapsed">
- {{assigneeTitle}}
- <i
- v-if="loading"
- aria-hidden="true"
- class="fa fa-spinner fa-spin block-loading"
- />
- <a
- v-if="editable"
- class="js-sidebar-dropdown-toggle edit-link pull-right"
- href="#"
- >
- {{ __('Edit') }}
- </a>
- <a
- v-if="showToggle"
- aria-label="Toggle sidebar"
- class="gutter-toggle pull-right js-sidebar-toggle"
- href="#"
- role="button"
- >
- <i
- aria-hidden="true"
- data-hidden="true"
- class="fa fa-angle-double-right"
- />
- </a>
- </div>
- `,
-};
diff --git a/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue b/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue
new file mode 100644
index 00000000000..5eeb2a41bae
--- /dev/null
+++ b/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue
@@ -0,0 +1,64 @@
+<script>
+export default {
+ name: 'AssigneeTitle',
+ props: {
+ loading: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ numberOfAssignees: {
+ type: Number,
+ required: true,
+ },
+ editable: {
+ type: Boolean,
+ required: true,
+ },
+ showToggle: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ computed: {
+ assigneeTitle() {
+ const assignees = this.numberOfAssignees;
+ return assignees > 1 ? `${assignees} Assignees` : 'Assignee';
+ },
+ },
+};
+</script>
+<template>
+ <div class="title hide-collapsed">
+ {{ assigneeTitle }}
+ <i
+ v-if="loading"
+ aria-hidden="true"
+ class="fa fa-spinner fa-spin block-loading"
+ >
+
+ </i>
+ <a
+ v-if="editable"
+ class="js-sidebar-dropdown-toggle edit-link pull-right"
+ href="#"
+ >
+ {{ __('Edit') }}
+ </a>
+ <a
+ v-if="showToggle"
+ aria-label="Toggle sidebar"
+ class="gutter-toggle pull-right js-sidebar-toggle"
+ href="#"
+ role="button"
+ >
+ <i
+ aria-hidden="true"
+ data-hidden="true"
+ class="fa fa-angle-double-right"
+ >
+ </i>
+ </a>
+ </div>
+</template>
diff --git a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue
index b58e04b5e60..3c6b9c27814 100644
--- a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue
@@ -1,6 +1,6 @@
<script>
import Flash from '../../../flash';
-import AssigneeTitle from './assignee_title';
+import AssigneeTitle from './assignee_title.vue';
import Assignees from './assignees.vue';
import Store from '../../stores/sidebar_store';
import eventHub from '../../event_hub';
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.js b/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.js
index 782e4ba4fad..5626cccc022 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.js
+++ b/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import _ from 'underscore';
import '~/smart_interval';
diff --git a/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js b/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js
index b10e2cc60ef..1eadebc7004 100644
--- a/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js
+++ b/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
function isValidProjectId(id) {
return id > 0;
}
diff --git a/app/assets/javascripts/sidebar/mount_sidebar.js b/app/assets/javascripts/sidebar/mount_sidebar.js
index ef748f18301..9f5d852260e 100644
--- a/app/assets/javascripts/sidebar/mount_sidebar.js
+++ b/app/assets/javascripts/sidebar/mount_sidebar.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import Vue from 'vue';
import SidebarTimeTracking from './components/time_tracking/sidebar_time_tracking';
import SidebarAssignees from './components/assignees/sidebar_assignees.vue';
diff --git a/app/assets/javascripts/single_file_diff.js b/app/assets/javascripts/single_file_diff.js
index 6142ce6c6a3..1afff0dba38 100644
--- a/app/assets/javascripts/single_file_diff.js
+++ b/app/assets/javascripts/single_file_diff.js
@@ -1,5 +1,6 @@
/* eslint-disable func-names, prefer-arrow-callback, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, one-var-declaration-per-line, consistent-return, no-param-reassign, max-len */
+import $ from 'jquery';
import { __ } from './locale';
import axios from './lib/utils/axios_utils';
import createFlash from './flash';
diff --git a/app/assets/javascripts/smart_interval.js b/app/assets/javascripts/smart_interval.js
index 8e931995fc6..77ab7c964e6 100644
--- a/app/assets/javascripts/smart_interval.js
+++ b/app/assets/javascripts/smart_interval.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
/**
* Instances of SmartInterval extend the functionality of `setInterval`, make it configurable
* and controllable by a public API.
diff --git a/app/assets/javascripts/snippet/snippet_bundle.js b/app/assets/javascripts/snippet/snippet_bundle.js
index ce0fd3f6ff8..dcee17453b8 100644
--- a/app/assets/javascripts/snippet/snippet_bundle.js
+++ b/app/assets/javascripts/snippet/snippet_bundle.js
@@ -1,5 +1,7 @@
/* global ace */
+import $ from 'jquery';
+
export default () => {
const editor = ace.edit('editor');
diff --git a/app/assets/javascripts/star.js b/app/assets/javascripts/star.js
index 3deb629d5f2..f5a7fdae5d7 100644
--- a/app/assets/javascripts/star.js
+++ b/app/assets/javascripts/star.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import Flash from './flash';
import { __, s__ } from './locale';
import { spriteIcon } from './lib/utils/common_utils';
diff --git a/app/assets/javascripts/subscription_select.js b/app/assets/javascripts/subscription_select.js
index 3ed064f87a9..ebe1c6dd02d 100644
--- a/app/assets/javascripts/subscription_select.js
+++ b/app/assets/javascripts/subscription_select.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
export default function subscriptionSelect() {
$('.js-subscription-event').each((i, element) => {
const fieldName = $(element).data('fieldName');
diff --git a/app/assets/javascripts/syntax_highlight.js b/app/assets/javascripts/syntax_highlight.js
index 62bdef76c55..f52990ba232 100644
--- a/app/assets/javascripts/syntax_highlight.js
+++ b/app/assets/javascripts/syntax_highlight.js
@@ -1,5 +1,7 @@
/* eslint-disable func-names, space-before-function-paren, consistent-return, no-var, no-else-return, prefer-arrow-callback, max-len */
+import $ from 'jquery';
+
// Syntax Highlighter
//
// Applies a syntax highlighting color scheme CSS class to any element with the
diff --git a/app/assets/javascripts/task_list.js b/app/assets/javascripts/task_list.js
index 8fa78b636f8..48782e63b9b 100644
--- a/app/assets/javascripts/task_list.js
+++ b/app/assets/javascripts/task_list.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import 'deckar01-task_list';
import axios from './lib/utils/axios_utils';
import Flash from './flash';
diff --git a/app/assets/javascripts/templates/issuable_template_selector.js b/app/assets/javascripts/templates/issuable_template_selector.js
index b5b64f44a11..6fea03af46a 100644
--- a/app/assets/javascripts/templates/issuable_template_selector.js
+++ b/app/assets/javascripts/templates/issuable_template_selector.js
@@ -1,5 +1,6 @@
/* eslint-disable no-useless-return, max-len */
+import $ from 'jquery';
import Api from '../api';
import TemplateSelector from '../blob/template_selector';
diff --git a/app/assets/javascripts/templates/issuable_template_selectors.js b/app/assets/javascripts/templates/issuable_template_selectors.js
index 66d868c5839..50e58ec5c46 100644
--- a/app/assets/javascripts/templates/issuable_template_selectors.js
+++ b/app/assets/javascripts/templates/issuable_template_selectors.js
@@ -1,4 +1,6 @@
/* eslint-disable no-new, class-methods-use-this */
+
+import $ from 'jquery';
import IssuableTemplateSelector from './issuable_template_selector';
export default class IssuableTemplateSelectors {
diff --git a/app/assets/javascripts/terminal/terminal.js b/app/assets/javascripts/terminal/terminal.js
index 904b0093f7b..caffcddf3b0 100644
--- a/app/assets/javascripts/terminal/terminal.js
+++ b/app/assets/javascripts/terminal/terminal.js
@@ -1,5 +1,7 @@
/* global Terminal */
+import $ from 'jquery';
+
(() => {
class GLTerminal {
diff --git a/app/assets/javascripts/tree.js b/app/assets/javascripts/tree.js
index 1a0b2c0415b..afbb958d058 100644
--- a/app/assets/javascripts/tree.js
+++ b/app/assets/javascripts/tree.js
@@ -1,4 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, quotes, consistent-return, no-var, one-var, one-var-declaration-per-line, no-else-return, prefer-arrow-callback, class-methods-use-this */
+
+import $ from 'jquery';
import { visitUrl } from './lib/utils/url_utility';
export default class TreeView {
diff --git a/app/assets/javascripts/u2f/authenticate.js b/app/assets/javascripts/u2f/authenticate.js
index fd42f9c3baa..96af6d2fcca 100644
--- a/app/assets/javascripts/u2f/authenticate.js
+++ b/app/assets/javascripts/u2f/authenticate.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import _ from 'underscore';
import importU2FLibrary from './util';
import U2FError from './error';
diff --git a/app/assets/javascripts/u2f/register.js b/app/assets/javascripts/u2f/register.js
index 869fac658e8..01e259a741d 100644
--- a/app/assets/javascripts/u2f/register.js
+++ b/app/assets/javascripts/u2f/register.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import _ from 'underscore';
import importU2FLibrary from './util';
import U2FError from './error';
diff --git a/app/assets/javascripts/ui_development_kit.js b/app/assets/javascripts/ui_development_kit.js
index 78dda172ee6..9b242ea779d 100644
--- a/app/assets/javascripts/ui_development_kit.js
+++ b/app/assets/javascripts/ui_development_kit.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import Api from './api';
export default () => {
diff --git a/app/assets/javascripts/user_callout.js b/app/assets/javascripts/user_callout.js
index a783122d500..97d5cf96bcb 100644
--- a/app/assets/javascripts/user_callout.js
+++ b/app/assets/javascripts/user_callout.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import Cookies from 'js-cookie';
export default class UserCallout {
diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js
index 3385aba0279..f3b961eb109 100644
--- a/app/assets/javascripts/users_select.js
+++ b/app/assets/javascripts/users_select.js
@@ -1,6 +1,8 @@
/* eslint-disable func-names, space-before-function-paren, one-var, no-var, prefer-rest-params, wrap-iife, quotes, max-len, one-var-declaration-per-line, vars-on-top, prefer-arrow-callback, consistent-return, comma-dangle, object-shorthand, no-shadow, no-unused-vars, no-else-return, no-self-compare, prefer-template, no-unused-expressions, no-lonely-if, yoda, prefer-spread, no-void, camelcase, no-param-reassign */
/* global Issuable */
/* global emitSidebarEvent */
+
+import $ from 'jquery';
import _ from 'underscore';
import axios from './lib/utils/axios_utils';
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
new file mode 100644
index 00000000000..7bef2e97349
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
@@ -0,0 +1,144 @@
+<script>
+import timeagoMixin from '../../vue_shared/mixins/timeago';
+import tooltip from '../../vue_shared/directives/tooltip';
+import LoadingButton from '../../vue_shared/components/loading_button.vue';
+import { visitUrl } from '../../lib/utils/url_utility';
+import createFlash from '../../flash';
+import MemoryUsage from './memory_usage.vue';
+import StatusIcon from './mr_widget_status_icon.vue';
+import MRWidgetService from '../services/mr_widget_service';
+
+export default {
+ name: 'Deployment',
+ components: {
+ LoadingButton,
+ MemoryUsage,
+ StatusIcon,
+ },
+ directives: {
+ tooltip,
+ },
+ mixins: [
+ timeagoMixin,
+ ],
+ props: {
+ deployment: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ isStopping: false,
+ };
+ },
+ computed: {
+ deployTimeago() {
+ return this.timeFormated(this.deployment.deployed_at);
+ },
+ hasExternalUrls() {
+ return !!(this.deployment.external_url && this.deployment.external_url_formatted);
+ },
+ hasDeploymentTime() {
+ return !!(this.deployment.deployed_at && this.deployment.deployed_at_formatted);
+ },
+ hasDeploymentMeta() {
+ return !!(this.deployment.url && this.deployment.name);
+ },
+ hasMetrics() {
+ return !!(this.deployment.metrics_url);
+ },
+ },
+ methods: {
+ stopEnvironment() {
+ const msg = 'Are you sure you want to stop this environment?';
+ const isConfirmed = confirm(msg); // eslint-disable-line
+
+ if (isConfirmed) {
+ this.isStopping = true;
+
+ MRWidgetService.stopEnvironment(this.deployment.stop_url)
+ .then(res => res.data)
+ .then((data) => {
+ if (data.redirect_url) {
+ visitUrl(data.redirect_url);
+ }
+
+ this.isStopping = false;
+ })
+ .catch(() => {
+ createFlash('Something went wrong while stopping this environment. Please try again.');
+ this.isStopping = false;
+ });
+ }
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="mr-widget-heading deploy-heading">
+ <div class="ci-widget media">
+ <div class="ci-status-icon ci-status-icon-success">
+ <span class="js-icon-link icon-link">
+ <status-icon status="success" />
+ </span>
+ </div>
+ <div class="media-body">
+ <div class="deploy-body">
+ <template v-if="hasDeploymentMeta">
+ <span>
+ Deployed to
+ </span>
+ <a
+ :href="deployment.url"
+ target="_blank"
+ rel="noopener noreferrer nofollow"
+ class="deploy-link js-deploy-meta"
+ >
+ {{ deployment.name }}
+ </a>
+ </template>
+ <template v-if="hasExternalUrls">
+ <span>
+ on
+ </span>
+ <a
+ :href="deployment.external_url"
+ target="_blank"
+ rel="noopener noreferrer nofollow"
+ class="deploy-link js-deploy-url"
+ >
+ <i
+ class="fa fa-external-link"
+ aria-hidden="true"
+ >
+ </i>
+ {{ deployment.external_url_formatted }}
+ </a>
+ </template>
+ <span
+ v-if="hasDeploymentTime"
+ v-tooltip
+ :title="deployment.deployed_at_formatted"
+ class="js-deploy-time"
+ >
+ {{ deployTimeago }}
+ </span>
+ <loading-button
+ v-if="deployment.stop_url"
+ container-class="btn btn-default btn-xs prepend-left-default"
+ label="Stop environment"
+ :loading="isStopping"
+ @click="stopEnvironment"
+ />
+ </div>
+ <memory-usage
+ v-if="hasMetrics"
+ :metrics-url="deployment.metrics_url"
+ :metrics-monitoring-url="deployment.metrics_monitoring_url"
+ />
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js b/app/assets/javascripts/vue_merge_request_widget/components/memory_usage.vue
index 69e70ba1dd6..a16f9055a6d 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/memory_usage.vue
@@ -1,11 +1,15 @@
+<script>
import statusCodes from '../../lib/utils/http_status';
import { bytesToMiB } from '../../lib/utils/number_utils';
import { backOff } from '../../lib/utils/common_utils';
-import MemoryGraph from '../../vue_shared/components/memory_graph';
+import MemoryGraph from '../../vue_shared/components/memory_graph.vue';
import MRWidgetService from '../services/mr_widget_service';
export default {
name: 'MemoryUsage',
+ components: {
+ MemoryGraph,
+ },
props: {
metricsUrl: {
type: String,
@@ -28,9 +32,6 @@ export default {
backOffRequestCounter: 0,
};
},
- components: {
- 'mr-memory-graph': MemoryGraph,
- },
computed: {
shouldShowLoading() {
return this.loadingMetrics && !this.hasMetrics && !this.loadFailed;
@@ -57,6 +58,10 @@ export default {
return 'unchanged';
},
},
+ mounted() {
+ this.loadingMetrics = true;
+ this.loadMetrics();
+ },
methods: {
getMegabytes(bytesString) {
const valueInBytes = Number(bytesString).toFixed(2);
@@ -114,40 +119,42 @@ export default {
});
},
},
- mounted() {
- this.loadingMetrics = true;
- this.loadMetrics();
- },
- template: `
- <div class="mr-info-list clearfix mr-memory-usage js-mr-memory-usage">
- <p
- v-if="shouldShowLoading"
- class="usage-info js-usage-info usage-info-loading">
- <i
- class="fa fa-spinner fa-spin usage-info-load-spinner"
- aria-hidden="true" />Loading deployment statistics
- </p>
- <p
- v-if="shouldShowMemoryGraph"
- class="usage-info js-usage-info">
- <a :href="metricsMonitoringUrl">Memory</a> usage <b>{{memoryChangeType}}</b> from {{memoryFrom}}MB to {{memoryTo}}MB
- </p>
- <p
- v-if="shouldShowLoadFailure"
- class="usage-info js-usage-info usage-info-failed">
- Failed to load deployment statistics
- </p>
- <p
- v-if="shouldShowMetricsUnavailable"
- class="usage-info js-usage-info usage-info-unavailable">
- Deployment statistics are not available currently
- </p>
- <mr-memory-graph
- v-if="shouldShowMemoryGraph"
- :metrics="memoryMetrics"
- :deploymentTime="deploymentTime"
- height="25"
- width="100" />
- </div>
- `,
};
+</script>
+
+<template>
+ <div class="mr-info-list clearfix mr-memory-usage js-mr-memory-usage">
+ <p
+ v-if="shouldShowLoading"
+ class="usage-info js-usage-info usage-info-loading">
+ <i
+ class="fa fa-spinner fa-spin usage-info-load-spinner"
+ aria-hidden="true">
+ </i>Loading deployment statistics
+ </p>
+ <p
+ v-if="shouldShowMemoryGraph"
+ class="usage-info js-usage-info">
+ <a
+ :href="metricsMonitoringUrl"
+ >Memory</a> usage <b>{{ memoryChangeType }}</b> from {{ memoryFrom }}MB to {{ memoryTo }}MB
+ </p>
+ <p
+ v-if="shouldShowLoadFailure"
+ class="usage-info js-usage-info usage-info-failed">
+ Failed to load deployment statistics
+ </p>
+ <p
+ v-if="shouldShowMetricsUnavailable"
+ class="usage-info js-usage-info usage-info-unavailable">
+ Deployment statistics are not available currently
+ </p>
+ <memory-graph
+ v-if="shouldShowMemoryGraph"
+ :metrics="memoryMetrics"
+ :deployment-time="deploymentTime"
+ height="25"
+ width="100"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js
deleted file mode 100644
index d174a900f63..00000000000
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js
+++ /dev/null
@@ -1,113 +0,0 @@
-import { getTimeago } from '~/lib/utils/datetime_utility';
-import { visitUrl } from '../../lib/utils/url_utility';
-import Flash from '../../flash';
-import MemoryUsage from './mr_widget_memory_usage';
-import StatusIcon from './mr_widget_status_icon.vue';
-import MRWidgetService from '../services/mr_widget_service';
-
-export default {
- name: 'MRWidgetDeployment',
- props: {
- mr: { type: Object, required: true },
- service: { type: Object, required: true },
- },
- components: {
- 'mr-widget-memory-usage': MemoryUsage,
- 'status-icon': StatusIcon,
- },
- methods: {
- formatDate(date) {
- return getTimeago().format(date);
- },
- hasExternalUrls(deployment = {}) {
- return deployment.external_url && deployment.external_url_formatted;
- },
- hasDeploymentTime(deployment = {}) {
- return deployment.deployed_at && deployment.deployed_at_formatted;
- },
- hasDeploymentMeta(deployment = {}) {
- return deployment.url && deployment.name;
- },
- stopEnvironment(deployment) {
- const msg = 'Are you sure you want to stop this environment?';
- const isConfirmed = confirm(msg); // eslint-disable-line
-
- if (isConfirmed) {
- MRWidgetService.stopEnvironment(deployment.stop_url)
- .then(res => res.data)
- .then((data) => {
- if (data.redirect_url) {
- visitUrl(data.redirect_url);
- }
- })
- .catch(() => {
- new Flash('Something went wrong while stopping this environment. Please try again.'); // eslint-disable-line
- });
- }
- },
- },
- template: `
- <div class="mr-widget-heading deploy-heading">
- <div v-for="deployment in mr.deployments">
- <div class="ci-widget media">
- <div class="ci-status-icon ci-status-icon-success">
- <span class="js-icon-link icon-link">
- <status-icon status="success" />
- </span>
- </div>
- <div class="media-body space-children">
- <span>
- <span
- v-if="hasDeploymentMeta(deployment)">
- Deployed to
- </span>
- <a
- v-if="hasDeploymentMeta(deployment)"
- :href="deployment.url"
- target="_blank"
- rel="noopener noreferrer nofollow"
- class="js-deploy-meta inline">
- {{deployment.name}}
- </a>
- <span
- v-if="hasExternalUrls(deployment)">
- on
- </span>
- <a
- v-if="hasExternalUrls(deployment)"
- :href="deployment.external_url"
- target="_blank"
- rel="noopener noreferrer nofollow"
- class="js-deploy-url inline">
- <i
- class="fa fa-external-link"
- aria-hidden="true" />
- {{deployment.external_url_formatted}}
- </a>
- <span
- v-if="hasDeploymentTime(deployment)"
- :data-title="deployment.deployed_at_formatted"
- class="js-deploy-time"
- data-toggle="tooltip"
- data-placement="top">
- {{formatDate(deployment.deployed_at)}}
- </span>
- </span>
- <button
- type="button"
- v-if="deployment.stop_url"
- @click="stopEnvironment(deployment)"
- class="btn btn-default btn-xs">
- Stop environment
- </button>
- <mr-widget-memory-usage
- v-if="deployment.metrics_url"
- :metrics-url="deployment.metrics_url"
- :metrics-monitoring-url="deployment.metrics_monitoring_url"
- />
- </div>
- </div>
- </div>
- </div>
- `,
-};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_maintainer_edit.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_maintainer_edit.vue
index 7a69cce695e..f0298f732ea 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_maintainer_edit.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_maintainer_edit.vue
@@ -10,8 +10,9 @@
},
};
</script>
+
<template>
- <section class="mr-info-list mr-maintainer-edit">
+ <section class="mr-info-list mr-links">
<p v-if="maintainerEditAllowed">
{{ s__("mrWidget|Allows edits from maintainers") }}
</p>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue b/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue
new file mode 100644
index 00000000000..460437ceeff
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue
@@ -0,0 +1,34 @@
+<script>
+ import tooltip from '../../vue_shared/directives/tooltip';
+ import { __ } from '../../locale';
+
+ export default {
+ directives: {
+ tooltip,
+ },
+ created() {
+ this.removesBranchText = __('<strong>Removes</strong> source branch');
+ this.tooltipTitle = __('A user with write access to the source branch selected this option');
+ },
+ };
+</script>
+
+<template>
+ <p
+ v-once
+ class="mr-info-list mr-links source-branch-removal-status append-bottom-0"
+ >
+ <span
+ class="status-text"
+ v-html="removesBranchText"
+ >
+ </span>
+ <i
+ v-tooltip
+ class="fa fa-question-circle"
+ :title="tooltipTitle"
+ :aria-label="tooltipTitle"
+ >
+ </i>
+ </p>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue
index de98a77be6f..7ff7fc7988a 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue
@@ -63,7 +63,7 @@
};
this.isRemovingSourceBranch = true;
- this.service.mergeResource.save(options)
+ this.service.merge(options)
.then(res => res.data)
.then((data) => {
if (data.status === 'merge_when_pipeline_succeeds') {
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge.js
deleted file mode 100644
index ebfd6765934..00000000000
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import emptyStateSVG from 'icons/_mr_widget_empty_state.svg';
-
-export default {
- name: 'MRWidgetNothingToMerge',
- props: {
- mr: {
- type: Object,
- required: true,
- },
- },
- data() {
- return { emptyStateSVG };
- },
- template: `
- <div class="mr-widget-body mr-widget-empty-state">
- <div class="row">
- <div class="artwork col-sm-5 col-sm-push-7 col-xs-12 text-center">
- <span v-html="emptyStateSVG"></span>
- </div>
- <div class="text col-sm-7 col-sm-pull-5 col-xs-12">
- <span>
- Merge requests are a place to propose changes you have made to a project
- and discuss those changes with others.
- </span>
- <p>
- Interested parties can even contribute by pushing commits if they want to.
- </p>
- <p>
- Currently there are no changes in this merge request's source branch.
- Please push new commits or use a different branch.
- </p>
- <div>
- <a
- v-if="mr.newBlobPath"
- :href="mr.newBlobPath"
- class="btn btn-inverted btn-save">
- Create file
- </a>
- </div>
- </div>
- </div>
- </div>
- `,
-};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js
index 162f048aac7..3c781ccddc8 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js
@@ -93,7 +93,7 @@ export default {
|| this.mr.preventMerge);
},
isRemoveSourceBranchButtonDisabled() {
- return this.isMergeButtonDisabled || !this.mr.canRemoveSourceBranch;
+ return this.isMergeButtonDisabled;
},
shouldShowSquashBeforeMerge() {
const { commitsCount, enableSquashBeforeMerge } = this.mr;
@@ -282,7 +282,7 @@ export default {
</span>
<div class="media-body-wrap space-children">
<template v-if="shouldShowMergeControls()">
- <label>
+ <label v-if="mr.canRemoveSourceBranch">
<input
id="remove-source-branch-input"
v-model="removeSourceBranch"
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions.js
deleted file mode 100644
index 67b271c69ca..00000000000
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import statusIcon from '../mr_widget_status_icon.vue';
-
-export default {
- name: 'MRWidgetUnresolvedDiscussions',
- props: {
- mr: { type: Object, required: true },
- },
- components: {
- statusIcon,
- },
- template: `
- <div class="mr-widget-body media">
- <status-icon status="warning" :show-disabled-button="true" />
- <div class="media-body space-children">
- <span class="bold">
- There are unresolved discussions. Please resolve these discussions
- </span>
- <a
- v-if="mr.createIssueToResolveDiscussionsPath"
- :href="mr.createIssueToResolveDiscussionsPath"
- class="btn btn-default btn-xs js-create-issue">
- Create an issue to resolve them later
- </a>
- </div>
- </div>
- `,
-};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js
index bbca641f65e..44e1a616a19 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_wip.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import statusIcon from '../mr_widget_status_icon.vue';
import tooltip from '../../../vue_shared/directives/tooltip';
import eventHub from '../../event_hub';
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue
new file mode 100644
index 00000000000..3d9161f6926
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue
@@ -0,0 +1,47 @@
+<script>
+import emptyStateSVG from 'icons/_mr_widget_empty_state.svg';
+
+export default {
+ name: 'MRWidgetNothingToMerge',
+ props: {
+ mr: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return { emptyStateSVG };
+ },
+};
+</script>
+
+<template>
+ <div class="mr-widget-body mr-widget-empty-state">
+ <div class="row">
+ <div class="artwork col-sm-5 col-sm-push-7 col-xs-12 text-center">
+ <span v-html="emptyStateSVG"></span>
+ </div>
+ <div class="text col-sm-7 col-sm-pull-5 col-xs-12">
+ <span>
+ Merge requests are a place to propose changes you have made to a project
+ and discuss those changes with others.
+ </span>
+ <p>
+ Interested parties can even contribute by pushing commits if they want to.
+ </p>
+ <p>
+ Currently there are no changes in this merge request's source branch.
+ Please push new commits or use a different branch.
+ </p>
+ <div>
+ <a
+ v-if="mr.newBlobPath"
+ :href="mr.newBlobPath"
+ class="btn btn-inverted btn-save">
+ Create file
+ </a>
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/unresolved_discussions.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/unresolved_discussions.vue
new file mode 100644
index 00000000000..9ade6a91747
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/unresolved_discussions.vue
@@ -0,0 +1,33 @@
+<script>
+import statusIcon from '../mr_widget_status_icon.vue';
+
+export default {
+ name: 'UnresolvedDiscussions',
+ components: {
+ statusIcon,
+ },
+ props: {
+ mr: { type: Object, required: true },
+ },
+};
+</script>
+
+<template>
+ <div class="mr-widget-body media">
+ <status-icon
+ status="warning"
+ :show-disabled-button="true"
+ />
+ <div class="media-body space-children">
+ <span class="bold">
+ There are unresolved discussions. Please resolve these discussions
+ </span>
+ <a
+ v-if="mr.createIssueToResolveDiscussionsPath"
+ :href="mr.createIssueToResolveDiscussionsPath"
+ class="btn btn-default btn-xs js-create-issue">
+ Create an issue to resolve them later
+ </a>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/dependencies.js b/app/assets/javascripts/vue_merge_request_widget/dependencies.js
index a1bc28873df..021c2237661 100644
--- a/app/assets/javascripts/vue_merge_request_widget/dependencies.js
+++ b/app/assets/javascripts/vue_merge_request_widget/dependencies.js
@@ -14,7 +14,7 @@ export { default as SmartInterval } from '~/smart_interval';
export { default as WidgetHeader } from './components/mr_widget_header.vue';
export { default as WidgetMergeHelp } from './components/mr_widget_merge_help.vue';
export { default as WidgetPipeline } from './components/mr_widget_pipeline.vue';
-export { default as WidgetDeployment } from './components/mr_widget_deployment';
+export { default as Deployment } from './components/deployment.vue';
export { default as WidgetMaintainerEdit } from './components/mr_widget_maintainer_edit.vue';
export { default as WidgetRelatedLinks } from './components/mr_widget_related_links.vue';
export { default as MergedState } from './components/states/mr_widget_merged.vue';
@@ -24,12 +24,12 @@ export { default as MergingState } from './components/states/mr_widget_merging.v
export { default as WipState } from './components/states/mr_widget_wip';
export { default as ArchivedState } from './components/states/mr_widget_archived.vue';
export { default as ConflictsState } from './components/states/mr_widget_conflicts.vue';
-export { default as NothingToMergeState } from './components/states/mr_widget_nothing_to_merge';
+export { default as NothingToMergeState } from './components/states/nothing_to_merge.vue';
export { default as MissingBranchState } from './components/states/mr_widget_missing_branch.vue';
export { default as NotAllowedState } from './components/states/mr_widget_not_allowed.vue';
export { default as ReadyToMergeState } from './components/states/mr_widget_ready_to_merge';
export { default as SHAMismatchState } from './components/states/mr_widget_sha_mismatch';
-export { default as UnresolvedDiscussionsState } from './components/states/mr_widget_unresolved_discussions';
+export { default as UnresolvedDiscussionsState } from './components/states/unresolved_discussions.vue';
export { default as PipelineBlockedState } from './components/states/mr_widget_pipeline_blocked.vue';
export { default as PipelineFailedState } from './components/states/mr_widget_pipeline_failed';
export { default as MergeWhenPipelineSucceedsState } from './components/states/mr_widget_merge_when_pipeline_succeeds.vue';
@@ -40,7 +40,9 @@ export { default as MRWidgetStore } from './stores/mr_widget_store';
export { default as MRWidgetService } from './services/mr_widget_service';
export { default as eventHub } from './event_hub';
export { default as getStateKey } from './stores/get_state_key';
-export { default as mrWidgetOptions } from './mr_widget_options';
export { default as stateMaps } from './stores/state_maps';
export { default as SquashBeforeMerge } from './components/states/mr_widget_squash_before_merge';
export { default as notify } from '../lib/utils/notify';
+export { default as SourceBranchRemovalStatus } from './components/source_branch_removal_status.vue';
+
+export { default as mrWidgetOptions } from './mr_widget_options';
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
index df3eb86f35c..169adfe0a1d 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
@@ -5,7 +5,7 @@ import {
WidgetHeader,
WidgetMergeHelp,
WidgetPipeline,
- WidgetDeployment,
+ Deployment,
WidgetMaintainerEdit,
WidgetRelatedLinks,
MergedState,
@@ -33,6 +33,7 @@ import {
stateMaps,
SquashBeforeMerge,
notify,
+ SourceBranchRemovalStatus,
} from './dependencies';
import { setFavicon } from '../lib/utils/common_utils';
@@ -66,8 +67,9 @@ export default {
shouldRenderRelatedLinks() {
return !!this.mr.relatedLinks && !this.mr.isNothingToMergeState;
},
- shouldRenderDeployments() {
- return this.mr.deployments.length;
+ shouldRenderSourceBranchRemovalStatus() {
+ return !this.mr.canRemoveSourceBranch && this.mr.shouldRemoveSourceBranch &&
+ (!this.mr.isNothingToMergeState && !this.mr.isMergedState);
},
},
methods: {
@@ -211,7 +213,7 @@ export default {
'mr-widget-header': WidgetHeader,
'mr-widget-merge-help': WidgetMergeHelp,
'mr-widget-pipeline': WidgetPipeline,
- 'mr-widget-deployment': WidgetDeployment,
+ Deployment,
'mr-widget-maintainer-edit': WidgetMaintainerEdit,
'mr-widget-related-links': WidgetRelatedLinks,
'mr-widget-merged': MergedState,
@@ -234,6 +236,7 @@ export default {
'mr-widget-merge-when-pipeline-succeeds': MergeWhenPipelineSucceedsState,
'mr-widget-auto-merge-failed': AutoMergeFailed,
'mr-widget-rebase': RebaseState,
+ SourceBranchRemovalStatus,
},
template: `
<div class="mr-state-widget prepend-top-default">
@@ -244,10 +247,11 @@ export default {
:ci-status="mr.ciStatus"
:has-ci="mr.hasCI"
/>
- <mr-widget-deployment
- v-if="shouldRenderDeployments"
- :mr="mr"
- :service="service" />
+ <deployment
+ v-for="deployment in mr.deployments"
+ :key="deployment.id"
+ :deployment="deployment"
+ />
<div class="mr-widget-section">
<component
:is="componentName"
@@ -259,6 +263,9 @@ export default {
v-if="shouldRenderRelatedLinks"
:state="mr.state"
:related-links="mr.relatedLinks" />
+ <source-branch-removal-status
+ v-if="shouldRenderSourceBranchRemovalStatus"
+ />
</div>
<div
class="mr-widget-footer"
diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
index 5d07bcf1bb9..a47ca9fae86 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
@@ -125,6 +125,10 @@ export default class MergeRequestStore {
return this.state === stateKey.nothingToMerge;
}
+ get isMergedState() {
+ return this.state === stateKey.merged;
+ }
+
initRebase(data) {
this.canPushToSourceBranch = data.can_push_to_source_branch;
this.rebaseInProgress = data.rebase_in_progress;
diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js b/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js
index 29d5bd4a1da..483ad52b8cc 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js
@@ -49,6 +49,7 @@ export const stateKey = {
notAllowedToMerge: 'notAllowedToMerge',
readyToMerge: 'readyToMerge',
rebase: 'rebase',
+ merged: 'merged',
};
export default {
diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue
index d2e968a8419..12c7d125062 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/field.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue
@@ -1,4 +1,5 @@
<script>
+ import $ from 'jquery';
import Flash from '../../../flash';
import GLForm from '../../../gl_form';
import markdownHeader from './header.vue';
diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue
index 177d2cfc8da..d91fe3cf0c5 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue
@@ -1,4 +1,5 @@
<script>
+ import $ from 'jquery';
import tooltip from '../../directives/tooltip';
import toolbarButton from './toolbar_button.vue';
import icon from '../icon.vue';
diff --git a/app/assets/javascripts/vue_shared/components/memory_graph.js b/app/assets/javascripts/vue_shared/components/memory_graph.vue
index f37ef1a5ca3..b07f6b07afe 100644
--- a/app/assets/javascripts/vue_shared/components/memory_graph.js
+++ b/app/assets/javascripts/vue_shared/components/memory_graph.vue
@@ -1,3 +1,4 @@
+<script>
import { getTimeago } from '../../lib/utils/datetime_utility';
export default {
@@ -22,6 +23,9 @@ export default {
return `Deployed ${deployedSince}`;
},
},
+ mounted() {
+ this.renderGraph(this.deploymentTime, this.metrics);
+ },
methods: {
/**
* Returns metric value index in metrics array
@@ -103,15 +107,27 @@ export default {
this.dotY = dotY;
},
},
- mounted() {
- this.renderGraph(this.deploymentTime, this.metrics);
- },
- template: `
- <div class="memory-graph-container">
- <svg class="has-tooltip" :title="getFormattedMedian" :width="width" :height="height" xmlns="http://www.w3.org/2000/svg">
- <path :d="pathD" :viewBox="pathViewBox" />
- <circle r="1.5" :cx="dotX" :cy="dotY" tranform="translate(0 -1)" />
- </svg>
- </div>
- `,
};
+</script>
+
+<template>
+ <div class="memory-graph-container">
+ <svg
+ class="has-tooltip"
+ :title="getFormattedMedian"
+ :width="width"
+ :height="height"
+ xmlns="http://www.w3.org/2000/svg">
+ <path
+ :d="pathD"
+ :viewBox="pathViewBox"
+ />
+ <circle
+ r="1.5"
+ :cx="dotX"
+ :cy="dotY"
+ tranform="translate(0 -1)"
+ />
+ </svg>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/navigation_tabs.vue b/app/assets/javascripts/vue_shared/components/navigation_tabs.vue
index 63d8329e495..b33a0101dbf 100644
--- a/app/assets/javascripts/vue_shared/components/navigation_tabs.vue
+++ b/app/assets/javascripts/vue_shared/components/navigation_tabs.vue
@@ -1,4 +1,6 @@
<script>
+ import $ from 'jquery';
+
/**
* Given an array of tabs, renders non linked bootstrap tabs.
* When a tab is clicked it will trigger an event and provide the clicked scope.
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue
index c1dd4d42d9d..5ede53d8d01 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue
@@ -1,4 +1,5 @@
<script>
+import { __ } from '~/locale';
import LabelsSelect from '~/labels_select';
import LoadingIcon from '../../loading_icon.vue';
@@ -31,6 +32,11 @@ export default {
required: false,
default: false,
},
+ isProject: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
abilityName: {
type: String,
required: true,
@@ -73,6 +79,20 @@ export default {
hiddenInputName() {
return this.showCreate ? `${this.abilityName}[label_names][]` : 'label_id[]';
},
+ createLabelTitle() {
+ if (this.isProject) {
+ return __('Create project label');
+ }
+
+ return __('Create group label');
+ },
+ manageLabelsTitle() {
+ if (this.isProject) {
+ return __('Manage project labels');
+ }
+
+ return __('Manage group labels');
+ },
},
mounted() {
this.labelsDropdown = new LabelsSelect(this.$refs.dropdownButton, {
@@ -137,10 +157,14 @@ dropdown-menu-labels dropdown-menu-selectable"
<dropdown-footer
v-if="showCreate"
:labels-web-url="labelsWebUrl"
+ :create-label-title="createLabelTitle"
+ :manage-labels-title="manageLabelsTitle"
/>
</div>
<dropdown-create-label
v-if="showCreate"
+ :is-project="isProject"
+ :header-title="createLabelTitle"
/>
</div>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue
index 4200d1e8473..34a07f33a23 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue
@@ -1,5 +1,14 @@
<script>
+import { __ } from '~/locale';
+
export default {
+ props: {
+ headerTitle: {
+ type: String,
+ required: false,
+ default: () => __('Create new label'),
+ },
+ },
created() {
this.suggestedColors = gon.suggested_label_colors;
},
@@ -21,7 +30,7 @@ export default {
>
</i>
</button>
- {{ __('Create new label') }}
+ {{ headerTitle }}
<button
type="button"
class="dropdown-title-button dropdown-menu-close"
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer.vue
index e951a863811..5f61e9fbe80 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer.vue
@@ -1,10 +1,22 @@
<script>
+import { __ } from '~/locale';
+
export default {
props: {
labelsWebUrl: {
type: String,
required: true,
},
+ createLabelTitle: {
+ type: String,
+ required: false,
+ default: () => __('Create new label'),
+ },
+ manageLabelsTitle: {
+ type: String,
+ required: false,
+ default: () => __('Manage labels'),
+ },
},
};
</script>
@@ -17,7 +29,7 @@ export default {
href="#"
class="dropdown-toggle-page"
>
- {{ __('Create new label') }}
+ {{ createLabelTitle }}
</a>
</li>
<li>
@@ -26,7 +38,7 @@ export default {
class="dropdown-external-link"
:href="labelsWebUrl"
>
- {{ __('Manage labels') }}
+ {{ manageLabelsTitle }}
</a>
</li>
</ul>
diff --git a/app/assets/javascripts/vue_shared/directives/popover.js b/app/assets/javascripts/vue_shared/directives/popover.js
index 05fa563cbd0..eb35294906b 100644
--- a/app/assets/javascripts/vue_shared/directives/popover.js
+++ b/app/assets/javascripts/vue_shared/directives/popover.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
/**
* Helper to user bootstrap popover in vue.js.
* Follow docs for html attributes: https://getbootstrap.com/docs/3.3/javascript/#static-popover
diff --git a/app/assets/javascripts/vue_shared/directives/tooltip.js b/app/assets/javascripts/vue_shared/directives/tooltip.js
index dc896cf5c7d..b7f7e9fec15 100644
--- a/app/assets/javascripts/vue_shared/directives/tooltip.js
+++ b/app/assets/javascripts/vue_shared/directives/tooltip.js
@@ -1,3 +1,5 @@
+import $ from 'jquery';
+
export default {
bind(el) {
$(el).tooltip();
diff --git a/app/assets/javascripts/zen_mode.js b/app/assets/javascripts/zen_mode.js
index 4592003f57e..f68a4f28714 100644
--- a/app/assets/javascripts/zen_mode.js
+++ b/app/assets/javascripts/zen_mode.js
@@ -5,6 +5,7 @@
/*= provides zen_mode:enter */
/*= provides zen_mode:leave */
+import $ from 'jquery';
import 'vendor/jquery.scrollTo';
import Dropzone from 'dropzone';
import Mousetrap from 'mousetrap';
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index ae517c41cb2..37d33320445 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -14,6 +14,10 @@
color: $gl-text-color-secondary;
}
+.text-tertiary {
+ color: $gl-text-color-tertiary;
+}
+
.text-primary,
.text-primary:hover {
color: $brand-primary;
diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss
index 88ce119ee3a..cb2f71b0033 100644
--- a/app/assets/stylesheets/framework/flash.scss
+++ b/app/assets/stylesheets/framework/flash.scss
@@ -12,6 +12,12 @@
margin: 0;
}
+ .flash-warning {
+ @extend .alert;
+ @extend .alert-warning;
+ margin: 0;
+ }
+
.flash-alert {
@extend .alert;
@extend .alert-danger;
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index ddd9dbb2be4..e12b5aab381 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -17,8 +17,6 @@
*/
@mixin markdown-table {
width: auto;
- display: block;
- overflow-x: auto;
}
/*
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index 8b680c2dc52..b487f6278c2 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -194,8 +194,6 @@
.commit-actions {
@media (min-width: $screen-sm-min) {
- font-size: 0;
-
.fa-spinner {
font-size: 12px;
}
@@ -204,7 +202,7 @@
.ci-status-link {
display: inline-block;
position: relative;
- top: 1px;
+ top: 2px;
}
.btn-clipboard,
@@ -226,7 +224,7 @@
.ci-status-icon {
position: relative;
- top: 1px;
+ top: 2px;
}
}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 4c9732c26d9..e21a9f0afc9 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -137,12 +137,22 @@
z-index: 200;
overflow: hidden;
- a:not(.btn-retry),
- .btn-link {
+ a:not(.btn) {
color: inherit;
+
+ &:hover {
+ color: $gl-link-hover-color;
+
+ .avatar {
+ border-color: rgba($avatar-border, .2);
+ }
+
+ }
+
}
.btn-link {
+ color: inherit;
outline: none;
}
@@ -214,7 +224,7 @@
&:hover {
text-decoration: underline;
- color: $md-link-color;
+ color: $gl-link-hover-color;
}
}
}
@@ -486,16 +496,6 @@
}
}
- a:not(.btn-retry) {
- &:hover {
- color: $md-link-color;
-
- .avatar {
- border-color: rgba($avatar-border, .2);
- }
- }
- }
-
.dropdown-menu-toggle {
width: 100%;
padding-top: 6px;
@@ -503,6 +503,20 @@
.dropdown-menu {
width: 100%;
+
+ /*
+ * Overwrite hover style for dropdown items, so that they are not blue
+ * This should be removed during dev of https://gitlab.com/gitlab-org/gitlab-ce/issues/44040
+ */
+ li a {
+ &:hover,
+ &:active,
+ &:focus,
+ &.is-focused {
+ @include dropdown-item-hover;
+ }
+ }
+
}
}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index f8708a2c0a3..4692d0fb873 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -453,8 +453,7 @@
}
}
-.mr-links,
-.mr-maintainer-edit {
+.mr-links {
padding-left: $status-icon-size + $status-icon-margin;
}
@@ -719,6 +718,8 @@
}
.mr-memory-usage {
+ width: 100%;
+
p.usage-info-loading .usage-info-load-spinner {
margin-right: 10px;
font-size: 16px;
@@ -728,3 +729,36 @@
.fork-sprite {
margin-right: -5px;
}
+
+.deploy-heading {
+ .media-body {
+ min-width: 0;
+ }
+}
+
+.deploy-body {
+ display: flex;
+ flex-wrap: wrap;
+
+ @media (min-width: $screen-xs) {
+ flex-wrap: nowrap;
+ white-space: nowrap;
+ }
+
+ > *:not(:last-child) {
+ margin-right: .3em;
+ }
+}
+
+.deploy-link {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ min-width: 100px;
+ max-width: 150px;
+
+ @media (min-width: $screen-xs) {
+ min-width: 0;
+ max-width: 100%;
+ }
+}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 3c565837383..085a2e74328 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -16,7 +16,7 @@ ul.notes {
.note-created-ago,
.note-updated-at {
- white-space: nowrap;
+ white-space: normal;
}
.discussion-body {
diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss
index 47672783d5a..a6ca8ed5016 100644
--- a/app/assets/stylesheets/pages/settings.scss
+++ b/app/assets/stylesheets/pages/settings.scss
@@ -205,7 +205,8 @@
}
.badge {
- font-size: inherit;
+ font-size: 12px;
+ line-height: 12px;
}
.panel-heading .badge-count {
diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss
index e70a57c2a67..9a0ec936979 100644
--- a/app/assets/stylesheets/pages/wiki.scss
+++ b/app/assets/stylesheets/pages/wiki.scss
@@ -180,6 +180,11 @@ ul.wiki-pages-list.content-list {
}
}
+.wiki-holder {
+ overflow-x: auto;
+ overflow-y: hidden;
+}
+
.wiki {
table {
@include markdown-table;
diff --git a/app/controllers/projects/discussions_controller.rb b/app/controllers/projects/discussions_controller.rb
index ee507009e50..cba9a53dc4b 100644
--- a/app/controllers/projects/discussions_controller.rb
+++ b/app/controllers/projects/discussions_controller.rb
@@ -19,6 +19,12 @@ class Projects::DiscussionsController < Projects::ApplicationController
render_discussion
end
+ def show
+ render json: {
+ discussion_html: view_to_html_string('discussions/_diff_with_notes', discussion: discussion, expanded: true)
+ }
+ end
+
private
def render_discussion
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
index daa5c88aae0..f14cb5f6a9f 100644
--- a/app/controllers/projects/services_controller.rb
+++ b/app/controllers/projects/services_controller.rb
@@ -3,7 +3,8 @@ class Projects::ServicesController < Projects::ApplicationController
# Authorize
before_action :authorize_admin_project!
- before_action :service, only: [:edit, :update, :test]
+ before_action :ensure_service_enabled
+ before_action :service
respond_to :html
@@ -23,26 +24,30 @@ class Projects::ServicesController < Projects::ApplicationController
end
def test
- message = {}
+ if @service.can_test?
+ render json: service_test_response, status: :ok
+ else
+ render json: {}, status: :not_found
+ end
+ end
- if @service.can_test? && @service.update_attributes(service_params[:service])
+ private
+
+ def service_test_response
+ if @service.update_attributes(service_params[:service])
data = @service.test_data(project, current_user)
outcome = @service.test(data)
- unless outcome[:success]
- message = { error: true, message: 'Test failed.', service_response: outcome[:result].to_s }
+ if outcome[:success]
+ {}
+ else
+ { error: true, message: 'Test failed.', service_response: outcome[:result].to_s }
end
-
- status = :ok
else
- status = :not_found
+ { error: true, message: 'Validations failed.', service_response: @service.errors.full_messages.join(',') }
end
-
- render json: message, status: status
end
- private
-
def success_message
if @service.active?
"#{@service.title} activated."
@@ -54,4 +59,8 @@ class Projects::ServicesController < Projects::ApplicationController
def service
@service ||= @project.find_or_initialize_service(params[:id])
end
+
+ def ensure_service_enabled
+ render_404 unless service
+ end
end
diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb
index 86717bb7242..259809f3429 100644
--- a/app/controllers/projects/settings/ci_cd_controller.rb
+++ b/app/controllers/projects/settings/ci_cd_controller.rb
@@ -13,12 +13,14 @@ module Projects
def reset_cache
if ResetProjectCacheService.new(@project, current_user).execute
- flash[:notice] = _("Project cache successfully reset.")
+ respond_to do |format|
+ format.json { head :ok }
+ end
else
- flash[:error] = _("Unable to reset project cache.")
+ respond_to do |format|
+ format.json { head :bad_request }
+ end
end
-
- redirect_to project_pipelines_path(@project)
end
private
diff --git a/app/finders/admin/projects_finder.rb b/app/finders/admin/projects_finder.rb
index d6bcd939522..5c507fe8d50 100644
--- a/app/finders/admin/projects_finder.rb
+++ b/app/finders/admin/projects_finder.rb
@@ -16,8 +16,7 @@ class Admin::ProjectsFinder
items = by_archived(items)
items = by_personal(items)
items = by_name(items)
- items = sort(items)
- items.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page])
+ sort(items).page(params[:page])
end
private
diff --git a/app/finders/user_recent_events_finder.rb b/app/finders/user_recent_events_finder.rb
index 6f7f7c30d92..65d6e019746 100644
--- a/app/finders/user_recent_events_finder.rb
+++ b/app/finders/user_recent_events_finder.rb
@@ -12,6 +12,8 @@ class UserRecentEventsFinder
attr_reader :current_user, :target_user, :params
+ LIMIT = 20
+
def initialize(current_user, target_user, params = {})
@current_user = current_user
@target_user = target_user
@@ -19,15 +21,44 @@ class UserRecentEventsFinder
end
def execute
- target_user
- .recent_events
- .merge(projects_for_current_user)
- .references(:project)
+ recent_events(params[:offset] || 0)
+ .joins(:project)
.with_associations
- .limit_recent(20, params[:offset])
+ .limit_recent(LIMIT, params[:offset])
+ end
+
+ private
+
+ def recent_events(offset)
+ sql = <<~SQL
+ (#{projects}) AS projects_for_join
+ JOIN (#{target_events.to_sql}) AS #{Event.table_name}
+ ON #{Event.table_name}.project_id = projects_for_join.id
+ SQL
+
+ # Workaround for https://github.com/rails/rails/issues/24193
+ Event.from([Arel.sql(sql)])
end
- def projects_for_current_user
- ProjectsFinder.new(current_user: current_user).execute
+ def target_events
+ Event.where(author: target_user)
+ end
+
+ def projects
+ # Compile a list of projects `current_user` interacted with
+ # and `target_user` is allowed to see.
+
+ authorized = target_user
+ .project_interactions
+ .joins(:project_authorizations)
+ .where(project_authorizations: { user: current_user })
+ .select(:id)
+
+ visible = target_user
+ .project_interactions
+ .where(visibility_level: [Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PUBLIC])
+ .select(:id)
+
+ Gitlab::SQL::Union.new([authorized, visible]).to_sql
end
end
diff --git a/app/helpers/import_helper.rb b/app/helpers/import_helper.rb
index 9149d79ecb8..4664b1728c4 100644
--- a/app/helpers/import_helper.rb
+++ b/app/helpers/import_helper.rb
@@ -1,4 +1,6 @@
module ImportHelper
+ include ::Gitlab::Utils::StrongMemoize
+
def has_ci_cd_only_params?
false
end
@@ -75,17 +77,18 @@ module ImportHelper
private
def github_project_url(full_path)
- "#{github_root_url}/#{full_path}"
+ URI.join(github_root_url, full_path).to_s
end
def github_root_url
- return @github_url if defined?(@github_url)
+ strong_memoize(:github_url) do
+ provider = Gitlab::Auth::OAuth::Provider.config_for('github')
- provider = Gitlab.config.omniauth.providers.find { |p| p.name == 'github' }
- @github_url = provider.fetch('url', 'https://github.com') if provider
+ provider&.dig('url').presence || 'https://github.com'
+ end
end
def gitea_project_url(full_path)
- "#{@gitea_host_url.sub(%r{/+\z}, '')}/#{full_path}"
+ URI.join(@gitea_host_url, full_path).to_s
end
end
diff --git a/app/helpers/javascript_helper.rb b/app/helpers/javascript_helper.rb
index d5e77c7e271..cd4075b340d 100644
--- a/app/helpers/javascript_helper.rb
+++ b/app/helpers/javascript_helper.rb
@@ -2,9 +2,4 @@ module JavascriptHelper
def page_specific_javascript_tag(js)
javascript_include_tag asset_path(js)
end
-
- # deprecated; use webpack_bundle_tag directly instead
- def page_specific_javascript_bundle_tag(bundle)
- webpack_bundle_tag(bundle)
- end
end
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index b2c641a5dbd..87ff607dc3f 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -174,6 +174,39 @@ module LabelsHelper
end
end
+ def create_label_title(subject)
+ case subject
+ when Group
+ _('Create group label')
+ when Project
+ _('Create project label')
+ else
+ _('Create new label')
+ end
+ end
+
+ def manage_labels_title(subject)
+ case subject
+ when Group
+ _('Manage group labels')
+ when Project
+ _('Manage project labels')
+ else
+ _('Manage labels')
+ end
+ end
+
+ def view_labels_title(subject)
+ case subject
+ when Group
+ _('View group labels')
+ when Project
+ _('View project labels')
+ else
+ _('View labels')
+ end
+ end
+
# Required for Banzai::Filter::LabelReferenceFilter
module_function :render_colored_label, :text_color_for_bg, :escape_once
end
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index a70e73a6da9..20aed60cb7a 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -169,7 +169,7 @@ module NotesHelper
reopenPath: reopen_issuable_path(issuable),
notesPath: notes_url,
totalNotes: issuable.discussions.length,
- lastFetchedAt: Time.now
+ lastFetchedAt: Time.now.to_i
}.to_json
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 0dee6df525d..3cbbf8b5dfa 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -347,15 +347,15 @@ class ApplicationSetting < ActiveRecord::Base
end
def home_page_url_column_exists?
- ActiveRecord::Base.connection.column_exists?(:application_settings, :home_page_url)
+ ::Gitlab::Database.cached_column_exists?(:application_settings, :home_page_url)
end
def help_page_support_url_column_exists?
- ActiveRecord::Base.connection.column_exists?(:application_settings, :help_page_support_url)
+ ::Gitlab::Database.cached_column_exists?(:application_settings, :help_page_support_url)
end
def sidekiq_throttling_column_exists?
- ActiveRecord::Base.connection.column_exists?(:application_settings, :sidekiq_throttling_enabled)
+ ::Gitlab::Database.cached_column_exists?(:application_settings, :sidekiq_throttling_enabled)
end
def domain_whitelist_raw
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index b230b7f47ef..c1da2081465 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -41,12 +41,12 @@ module Ci
scope :unstarted, ->() { where(runner_id: nil) }
scope :ignore_failures, ->() { where(allow_failure: false) }
- scope :with_artifacts, ->() do
+ scope :with_artifacts_archive, ->() do
where('(artifacts_file IS NOT NULL AND artifacts_file <> ?) OR EXISTS (?)',
- '', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id'))
+ '', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').archive)
end
- scope :with_artifacts_not_expired, ->() { with_artifacts.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) }
- scope :with_expired_artifacts, ->() { with_artifacts.where('artifacts_expire_at < ?', Time.now) }
+ scope :with_artifacts_not_expired, ->() { with_artifacts_archive.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) }
+ scope :with_expired_artifacts, ->() { with_artifacts_archive.where('artifacts_expire_at < ?', Time.now) }
scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) }
scope :manual_actions, ->() { where(when: :manual, status: COMPLETED_STATUSES + [:manual]) }
scope :ref_protected, -> { where(protected: true) }
@@ -252,23 +252,23 @@ module Ci
# All variables, including those dependent on environment, which could
# contain unexpanded variables.
def variables(environment: persisted_environment)
- variables = predefined_variables
- variables += project.predefined_variables
- variables += pipeline.predefined_variables
- variables += runner.predefined_variables if runner
- variables += project.container_registry_variables
- variables += project.deployment_variables if has_environment?
- variables += project.auto_devops_variables
- variables += yaml_variables
- variables += user_variables
- variables += project.group.secret_variables_for(ref, project).map(&:to_runner_variable) if project.group
- variables += secret_variables(environment: environment)
- variables += trigger_request.user_variables if trigger_request
- variables += pipeline.variables.map(&:to_runner_variable)
- variables += pipeline.pipeline_schedule.job_variables if pipeline.pipeline_schedule
- variables += persisted_environment_variables if environment
-
- variables
+ collection = Gitlab::Ci::Variables::Collection.new.tap do |variables|
+ variables.concat(predefined_variables)
+ variables.concat(project.predefined_variables)
+ variables.concat(pipeline.predefined_variables)
+ variables.concat(runner.predefined_variables) if runner
+ variables.concat(project.deployment_variables(environment: environment)) if has_environment?
+ variables.concat(yaml_variables)
+ variables.concat(user_variables)
+ variables.concat(project.group.secret_variables_for(ref, project)) if project.group
+ variables.concat(secret_variables(environment: environment))
+ variables.concat(trigger_request.user_variables) if trigger_request
+ variables.concat(pipeline.variables)
+ variables.concat(pipeline.pipeline_schedule.job_variables) if pipeline.pipeline_schedule
+ variables.concat(persisted_environment_variables) if environment
+ end
+
+ collection.to_runner_variables
end
def features
@@ -430,14 +430,14 @@ module Ci
end
def user_variables
- return [] if user.blank?
+ Gitlab::Ci::Variables::Collection.new.tap do |variables|
+ return variables if user.blank?
- [
- { 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 }
- ]
+ variables.append(key: 'GITLAB_USER_ID', value: user.id.to_s)
+ variables.append(key: 'GITLAB_USER_EMAIL', value: user.email)
+ variables.append(key: 'GITLAB_USER_LOGIN', value: user.username)
+ variables.append(key: 'GITLAB_USER_NAME', value: user.name)
+ end
end
def secret_variables(environment: persisted_environment)
@@ -540,60 +540,57 @@ module Ci
CI_REGISTRY_USER = 'gitlab-ci-token'.freeze
def predefined_variables
- variables = [
- { key: 'CI', value: 'true', public: true },
- { key: 'GITLAB_CI', value: 'true', public: true },
- { key: 'GITLAB_FEATURES', value: project.namespace.features.join(','), public: true },
- { key: 'CI_SERVER_NAME', value: 'GitLab', public: true },
- { key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true },
- { key: 'CI_SERVER_REVISION', value: Gitlab::REVISION, public: true },
- { key: 'CI_JOB_ID', value: id.to_s, public: true },
- { key: 'CI_JOB_NAME', value: name, public: true },
- { key: 'CI_JOB_STAGE', value: stage, public: true },
- { key: 'CI_JOB_TOKEN', value: token, public: false },
- { key: 'CI_COMMIT_SHA', value: sha, public: true },
- { key: 'CI_COMMIT_REF_NAME', value: ref, public: true },
- { key: 'CI_COMMIT_REF_SLUG', value: ref_slug, public: true },
- { key: 'CI_REGISTRY_USER', value: CI_REGISTRY_USER, public: true },
- { key: 'CI_REGISTRY_PASSWORD', value: token, public: false },
- { key: 'CI_REPOSITORY_URL', value: repo_url, public: false }
- ]
-
- variables << { key: "CI_COMMIT_TAG", value: ref, public: true } if tag?
- variables << { key: "CI_PIPELINE_TRIGGERED", value: 'true', public: true } if trigger_request
- variables << { key: "CI_JOB_MANUAL", value: 'true', public: true } if action?
- variables.concat(legacy_variables)
+ Gitlab::Ci::Variables::Collection.new.tap do |variables|
+ variables.append(key: 'CI', value: 'true')
+ variables.append(key: 'GITLAB_CI', value: 'true')
+ variables.append(key: 'GITLAB_FEATURES', value: project.namespace.features.join(','))
+ variables.append(key: 'CI_SERVER_NAME', value: 'GitLab')
+ variables.append(key: 'CI_SERVER_VERSION', value: Gitlab::VERSION)
+ variables.append(key: 'CI_SERVER_REVISION', value: Gitlab::REVISION)
+ variables.append(key: 'CI_JOB_ID', value: id.to_s)
+ variables.append(key: 'CI_JOB_NAME', value: name)
+ variables.append(key: 'CI_JOB_STAGE', value: stage)
+ variables.append(key: 'CI_JOB_TOKEN', value: token, public: false)
+ variables.append(key: 'CI_COMMIT_SHA', value: sha)
+ variables.append(key: 'CI_COMMIT_REF_NAME', value: ref)
+ variables.append(key: 'CI_COMMIT_REF_SLUG', value: ref_slug)
+ variables.append(key: 'CI_REGISTRY_USER', value: CI_REGISTRY_USER)
+ variables.append(key: 'CI_REGISTRY_PASSWORD', value: token, public: false)
+ variables.append(key: 'CI_REPOSITORY_URL', value: repo_url, public: false)
+ variables.append(key: "CI_COMMIT_TAG", value: ref) if tag?
+ variables.append(key: "CI_PIPELINE_TRIGGERED", value: 'true') if trigger_request
+ variables.append(key: "CI_JOB_MANUAL", value: 'true') if action?
+ variables.concat(legacy_variables)
+ end
end
def persisted_environment_variables
- return [] unless persisted_environment
+ Gitlab::Ci::Variables::Collection.new.tap do |variables|
+ return variables unless persisted_environment
- variables = persisted_environment.predefined_variables
+ variables.concat(persisted_environment.predefined_variables)
- # Here we're passing unexpanded environment_url for runner to expand,
- # and we need to make sure that CI_ENVIRONMENT_NAME and
- # CI_ENVIRONMENT_SLUG so on are available for the URL be expanded.
- variables << { key: 'CI_ENVIRONMENT_URL', value: environment_url, public: true } if environment_url
-
- variables
+ # Here we're passing unexpanded environment_url for runner to expand,
+ # and we need to make sure that CI_ENVIRONMENT_NAME and
+ # CI_ENVIRONMENT_SLUG so on are available for the URL be expanded.
+ variables.append(key: 'CI_ENVIRONMENT_URL', value: environment_url) if environment_url
+ end
end
def legacy_variables
- variables = [
- { key: 'CI_BUILD_ID', value: id.to_s, public: true },
- { key: 'CI_BUILD_TOKEN', value: token, public: false },
- { key: 'CI_BUILD_REF', value: sha, public: true },
- { key: 'CI_BUILD_BEFORE_SHA', value: before_sha, public: true },
- { key: 'CI_BUILD_REF_NAME', value: ref, public: true },
- { key: 'CI_BUILD_REF_SLUG', value: ref_slug, public: true },
- { key: 'CI_BUILD_NAME', value: name, public: true },
- { key: 'CI_BUILD_STAGE', value: stage, public: true }
- ]
-
- variables << { key: "CI_BUILD_TAG", value: ref, public: true } if tag?
- variables << { key: "CI_BUILD_TRIGGERED", value: 'true', public: true } if trigger_request
- variables << { key: "CI_BUILD_MANUAL", value: 'true', public: true } if action?
- variables
+ Gitlab::Ci::Variables::Collection.new.tap do |variables|
+ variables.append(key: 'CI_BUILD_ID', value: id.to_s)
+ variables.append(key: 'CI_BUILD_TOKEN', value: token, public: false)
+ variables.append(key: 'CI_BUILD_REF', value: sha)
+ variables.append(key: 'CI_BUILD_BEFORE_SHA', value: before_sha)
+ variables.append(key: 'CI_BUILD_REF_NAME', value: ref)
+ variables.append(key: 'CI_BUILD_REF_SLUG', value: ref_slug)
+ variables.append(key: 'CI_BUILD_NAME', value: name)
+ variables.append(key: 'CI_BUILD_STAGE', value: stage)
+ variables.append(key: "CI_BUILD_TAG", value: ref) if tag?
+ variables.append(key: "CI_BUILD_TRIGGERED", value: 'true') if trigger_request
+ variables.append(key: "CI_BUILD_MANUAL", value: 'true') if action?
+ end
end
def environment_url
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index a72a815bfe8..f2edcdd61fd 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -473,11 +473,10 @@ module Ci
end
def predefined_variables
- [
- { key: 'CI_PIPELINE_ID', value: id.to_s, public: true },
- { key: 'CI_CONFIG_PATH', value: ci_yaml_file_path, public: true },
- { key: 'CI_PIPELINE_SOURCE', value: source.to_s, public: true }
- ]
+ Gitlab::Ci::Variables::Collection.new
+ .append(key: 'CI_PIPELINE_ID', value: id.to_s)
+ .append(key: 'CI_CONFIG_PATH', value: ci_yaml_file_path)
+ .append(key: 'CI_PIPELINE_SOURCE', value: source.to_s)
end
def queued_duration
@@ -514,7 +513,7 @@ module Ci
# We purposely cast the builds to an Array here. Because we always use the
# rows if there are more than 0 this prevents us from having to run two
# queries: one to get the count and one to get the rows.
- @latest_builds_with_artifacts ||= builds.latest.with_artifacts.to_a
+ @latest_builds_with_artifacts ||= builds.latest.with_artifacts_archive.to_a
end
private
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 609620a62bb..7173f88f1c7 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -132,11 +132,10 @@ module Ci
end
def predefined_variables
- [
- { key: 'CI_RUNNER_ID', value: id.to_s, public: true },
- { key: 'CI_RUNNER_DESCRIPTION', value: description, public: true },
- { key: 'CI_RUNNER_TAGS', value: tag_list.to_s, public: true }
- ]
+ Gitlab::Ci::Variables::Collection.new
+ .append(key: 'CI_RUNNER_ID', value: id.to_s)
+ .append(key: 'CI_RUNNER_DESCRIPTION', value: description)
+ .append(key: 'CI_RUNNER_TAGS', value: tag_list.to_s)
end
def tick_runner_queue
diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb
index 7ce8befeeeb..ba6552f238f 100644
--- a/app/models/clusters/platforms/kubernetes.rb
+++ b/app/models/clusters/platforms/kubernetes.rb
@@ -56,19 +56,19 @@ module Clusters
def predefined_variables
config = YAML.dump(kubeconfig)
- variables = [
- { key: 'KUBE_URL', value: api_url, public: true },
- { key: 'KUBE_TOKEN', value: token, public: false },
- { key: 'KUBE_NAMESPACE', value: actual_namespace, public: true },
- { key: 'KUBECONFIG', value: config, public: false, file: true }
- ]
-
- if ca_pem.present?
- variables << { key: 'KUBE_CA_PEM', value: ca_pem, public: true }
- variables << { key: 'KUBE_CA_PEM_FILE', value: ca_pem, public: true, file: true }
+ Gitlab::Ci::Variables::Collection.new.tap do |variables|
+ variables
+ .append(key: 'KUBE_URL', value: api_url)
+ .append(key: 'KUBE_TOKEN', value: token, public: false)
+ .append(key: 'KUBE_NAMESPACE', value: actual_namespace)
+ .append(key: 'KUBECONFIG', value: config, public: false, file: true)
+
+ if ca_pem.present?
+ variables
+ .append(key: 'KUBE_CA_PEM', value: ca_pem)
+ .append(key: 'KUBE_CA_PEM_FILE', value: ca_pem, file: true)
+ end
end
-
- variables
end
# Constructs a list of terminals from the reactive cache
@@ -134,7 +134,7 @@ module Clusters
kubeclient = build_kubeclient!
kubeclient.get_pods(namespace: actual_namespace).as_json
- rescue KubeException => err
+ rescue Kubeclient::HttpError => err
raise err unless err.error_code == 404
[]
diff --git a/app/models/compare.rb b/app/models/compare.rb
index 3a8bbcb1acd..feb4b89c781 100644
--- a/app/models/compare.rb
+++ b/app/models/compare.rb
@@ -1,4 +1,6 @@
class Compare
+ include Gitlab::Utils::StrongMemoize
+
delegate :same, :head, :base, to: :@compare
attr_reader :project
@@ -11,9 +13,10 @@ class Compare
end
end
- def initialize(compare, project, straight: false)
+ def initialize(compare, project, base_sha: nil, straight: false)
@compare = compare
@project = project
+ @base_sha = base_sha
@straight = straight
end
@@ -22,40 +25,36 @@ class Compare
end
def start_commit
- return @start_commit if defined?(@start_commit)
+ strong_memoize(:start_commit) do
+ commit = @compare.base
- commit = @compare.base
- @start_commit = commit ? ::Commit.new(commit, project) : nil
+ ::Commit.new(commit, project) if commit
+ end
end
def head_commit
- return @head_commit if defined?(@head_commit)
+ strong_memoize(:head_commit) do
+ commit = @compare.head
- commit = @compare.head
- @head_commit = commit ? ::Commit.new(commit, project) : nil
+ ::Commit.new(commit, project) if commit
+ end
end
alias_method :commit, :head_commit
- def base_commit
- return @base_commit if defined?(@base_commit)
-
- @base_commit = if start_commit && head_commit
- project.merge_base_commit(start_commit.id, head_commit.id)
- else
- nil
- end
- end
-
def start_commit_sha
- start_commit.try(:sha)
+ start_commit&.sha
end
def base_commit_sha
- base_commit.try(:sha)
+ strong_memoize(:base_commit) do
+ next unless start_commit && head_commit
+
+ @base_sha || project.merge_base_commit(start_commit.id, head_commit.id)&.sha
+ end
end
def head_commit_sha
- commit.try(:sha)
+ commit&.sha
end
def raw_diffs(*args)
diff --git a/app/models/concerns/storage/legacy_namespace.rb b/app/models/concerns/storage/legacy_namespace.rb
index 67a988addbe..f05e606995d 100644
--- a/app/models/concerns/storage/legacy_namespace.rb
+++ b/app/models/concerns/storage/legacy_namespace.rb
@@ -7,29 +7,24 @@ module Storage
raise Gitlab::UpdatePathError.new('Namespace cannot be moved, because at least one project has tags in container registry')
end
- expires_full_path_cache
-
- # Move the namespace directory in all storage paths used by member projects
- repository_storage_paths.each do |repository_storage_path|
- # Ensure old directory exists before moving it
- gitlab_shell.add_namespace(repository_storage_path, full_path_was)
-
- # Ensure new directory exists before moving it (if there's a parent)
- gitlab_shell.add_namespace(repository_storage_path, parent.full_path) if parent
+ parent_was = if parent_changed? && parent_id_was.present?
+ Namespace.find(parent_id_was) # raise NotFound early if needed
+ end
- unless gitlab_shell.mv_namespace(repository_storage_path, full_path_was, full_path)
+ expires_full_path_cache
- Rails.logger.error "Exception moving path #{repository_storage_path} from #{full_path_was} to #{full_path}"
+ move_repositories
- # if we cannot move namespace directory we should rollback
- # db changes in order to prevent out of sync between db and fs
- raise Gitlab::UpdatePathError.new('namespace directory cannot be moved')
- end
+ if parent_changed?
+ former_parent_full_path = parent_was&.full_path
+ parent_full_path = parent&.full_path
+ Gitlab::UploadsTransfer.new.move_namespace(path, former_parent_full_path, parent_full_path)
+ Gitlab::PagesTransfer.new.move_namespace(path, former_parent_full_path, parent_full_path)
+ else
+ Gitlab::UploadsTransfer.new.rename_namespace(full_path_was, full_path)
+ Gitlab::PagesTransfer.new.rename_namespace(full_path_was, full_path)
end
- Gitlab::UploadsTransfer.new.rename_namespace(full_path_was, full_path)
- Gitlab::PagesTransfer.new.rename_namespace(full_path_was, full_path)
-
remove_exports!
# If repositories moved successfully we need to
@@ -57,6 +52,26 @@ module Storage
private
+ def move_repositories
+ # Move the namespace directory in all storage paths used by member projects
+ repository_storage_paths.each do |repository_storage_path|
+ # Ensure old directory exists before moving it
+ gitlab_shell.add_namespace(repository_storage_path, full_path_was)
+
+ # Ensure new directory exists before moving it (if there's a parent)
+ gitlab_shell.add_namespace(repository_storage_path, parent.full_path) if parent
+
+ unless gitlab_shell.mv_namespace(repository_storage_path, full_path_was, full_path)
+
+ Rails.logger.error "Exception moving path #{repository_storage_path} from #{full_path_was} to #{full_path}"
+
+ # if we cannot move namespace directory we should rollback
+ # db changes in order to prevent out of sync between db and fs
+ raise Gitlab::UpdatePathError.new('namespace directory cannot be moved')
+ end
+ end
+ end
+
def old_repository_storage_paths
@old_repository_storage_paths ||= repository_storage_paths
end
diff --git a/app/models/cycle_analytics/summary.rb b/app/models/cycle_analytics/summary.rb
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/app/models/cycle_analytics/summary.rb
+++ /dev/null
diff --git a/app/models/environment.rb b/app/models/environment.rb
index 2b0a88ac5b4..9517723d9d9 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -65,10 +65,9 @@ class Environment < ActiveRecord::Base
end
def predefined_variables
- [
- { key: 'CI_ENVIRONMENT_NAME', value: name, public: true },
- { key: 'CI_ENVIRONMENT_SLUG', value: slug, public: true }
- ]
+ Gitlab::Ci::Variables::Collection.new
+ .append(key: 'CI_ENVIRONMENT_NAME', value: name)
+ .append(key: 'CI_ENVIRONMENT_SLUG', value: slug)
end
def recently_updated_on_branch?(ref)
diff --git a/app/models/member.rb b/app/models/member.rb
index 408e8b2d704..ec8156bbb01 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -55,7 +55,7 @@ class Member < ActiveRecord::Base
scope :active_without_invites, -> do
left_join_users
.where(users: { state: 'active' })
- .where(requested_at: nil)
+ .non_request
.reorder(nil)
end
@@ -85,6 +85,7 @@ class Member < ActiveRecord::Base
after_create :create_notification_setting, unless: [:pending?, :importing?]
after_create :post_create_hook, unless: [:pending?, :importing?]
after_update :post_update_hook, unless: [:pending?, :importing?]
+ after_destroy :destroy_notification_setting
after_destroy :post_destroy_hook, unless: :pending?
after_commit :refresh_member_authorized_projects
@@ -315,6 +316,10 @@ class Member < ActiveRecord::Base
user.notification_settings.find_or_create_for(source)
end
+ def destroy_notification_setting
+ notification_setting&.destroy
+ end
+
def notification_setting
@notification_setting ||= user&.notification_settings_for(source)
end
diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb
index b6f1dd272cd..1c7ed4a96df 100644
--- a/app/models/members/project_member.rb
+++ b/app/models/members/project_member.rb
@@ -13,8 +13,6 @@ class ProjectMember < Member
scope :in_project, ->(project) { where(source_id: project.id) }
- before_destroy :delete_member_todos
-
class << self
# Add users to projects with passed access option
#
@@ -93,10 +91,6 @@ class ProjectMember < Member
private
- def delete_member_todos
- user.todos.where(project_id: source_id).destroy_all if user
- end
-
def send_invite
notification_service.invite_project_member(self, @raw_invite_token)
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index c2bae379a94..149ef7ec429 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -579,9 +579,10 @@ class MergeRequest < ActiveRecord::Base
return unless open?
old_diff_refs = self.diff_refs
+ new_diff = create_merge_request_diff
+
+ MergeRequests::MergeRequestDiffCacheService.new.execute(self, new_diff)
- create_merge_request_diff
- MergeRequests::MergeRequestDiffCacheService.new.execute(self)
new_diff_refs = self.diff_refs
update_diff_discussion_positions(
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 06aa67c600f..c1c27ccf3e5 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -197,6 +197,10 @@ class MergeRequestDiff < ActiveRecord::Base
CompareService.new(project, head_commit_sha).execute(project, sha, straight: true)
end
+ def commits_count
+ super || merge_request_diff_commits.size
+ end
+
private
def create_merge_request_diff_files(diffs)
diff --git a/app/models/project.rb b/app/models/project.rb
index 5f9d9785d64..d6e663f14e4 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -542,7 +542,7 @@ class Project < ActiveRecord::Base
latest_pipeline = pipelines.latest_successful_for(ref)
if latest_pipeline
- latest_pipeline.builds.latest.with_artifacts
+ latest_pipeline.builds.latest.with_artifacts_archive
else
builds.none
end
@@ -1083,7 +1083,7 @@ class Project < ActiveRecord::Base
# Forked import is handled asynchronously
return if forked? && !force
- if gitlab_shell.add_repository(repository_storage, disk_path)
+ if gitlab_shell.create_repository(repository_storage, disk_path)
repository.after_create
true
else
@@ -1519,8 +1519,8 @@ class Project < ActiveRecord::Base
@errors = original_errors
end
- def add_export_job(current_user:)
- job_id = ProjectExportWorker.perform_async(current_user.id, self.id)
+ def add_export_job(current_user:, params: {})
+ job_id = ProjectExportWorker.perform_async(current_user.id, self.id, params)
if job_id
Rails.logger.info "Export job started for project ID #{self.id} with job ID #{job_id}"
@@ -1572,29 +1572,30 @@ class Project < ActiveRecord::Base
end
def predefined_variables
- [
- { key: 'CI_PROJECT_ID', value: id.to_s, public: true },
- { key: 'CI_PROJECT_NAME', value: path, public: true },
- { key: 'CI_PROJECT_PATH', value: full_path, public: true },
- { key: 'CI_PROJECT_PATH_SLUG', value: full_path_slug, public: true },
- { key: 'CI_PROJECT_NAMESPACE', value: namespace.full_path, public: true },
- { key: 'CI_PROJECT_URL', value: web_url, public: true },
- { key: 'CI_PROJECT_VISIBILITY', value: Gitlab::VisibilityLevel.string_level(visibility_level), public: true }
- ]
+ visibility = Gitlab::VisibilityLevel.string_level(visibility_level)
+
+ Gitlab::Ci::Variables::Collection.new
+ .append(key: 'CI_PROJECT_ID', value: id.to_s)
+ .append(key: 'CI_PROJECT_NAME', value: path)
+ .append(key: 'CI_PROJECT_PATH', value: full_path)
+ .append(key: 'CI_PROJECT_PATH_SLUG', value: full_path_slug)
+ .append(key: 'CI_PROJECT_NAMESPACE', value: namespace.full_path)
+ .append(key: 'CI_PROJECT_URL', value: web_url)
+ .append(key: 'CI_PROJECT_VISIBILITY', value: visibility)
+ .concat(container_registry_variables)
+ .concat(auto_devops_variables)
end
def container_registry_variables
- return [] unless Gitlab.config.registry.enabled
+ Gitlab::Ci::Variables::Collection.new.tap do |variables|
+ return variables unless Gitlab.config.registry.enabled
- variables = [
- { key: 'CI_REGISTRY', value: Gitlab.config.registry.host_port, public: true }
- ]
+ variables.append(key: 'CI_REGISTRY', value: Gitlab.config.registry.host_port)
- if container_registry_enabled?
- variables << { key: 'CI_REGISTRY_IMAGE', value: container_registry_url, public: true }
+ if container_registry_enabled?
+ variables.append(key: 'CI_REGISTRY_IMAGE', value: container_registry_url)
+ end
end
-
- variables
end
def secret_variables_for(ref:, environment: nil)
@@ -1614,16 +1615,14 @@ class Project < ActiveRecord::Base
end
end
- def deployment_variables
- return [] unless deployment_platform
-
- deployment_platform.predefined_variables
+ def deployment_variables(environment: nil)
+ deployment_platform(environment: environment)&.predefined_variables || []
end
def auto_devops_variables
return [] unless auto_devops_enabled?
- (auto_devops || build_auto_devops)&.variables
+ (auto_devops || build_auto_devops)&.predefined_variables
end
def append_or_update_attribute(name, value)
diff --git a/app/models/project_auto_devops.rb b/app/models/project_auto_devops.rb
index 112ed7ed434..ed6c1eddbc1 100644
--- a/app/models/project_auto_devops.rb
+++ b/app/models/project_auto_devops.rb
@@ -14,9 +14,12 @@ class ProjectAutoDevops < ActiveRecord::Base
domain.present? || instance_domain.present?
end
- def variables
- variables = []
- variables << { key: 'AUTO_DEVOPS_DOMAIN', value: domain.presence || instance_domain, public: true } if has_domain?
- variables
+ def predefined_variables
+ Gitlab::Ci::Variables::Collection.new.tap do |variables|
+ if has_domain?
+ variables.append(key: 'AUTO_DEVOPS_DOMAIN',
+ value: domain.presence || instance_domain)
+ end
+ end
end
end
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index e5035c81df0..601a6a077f5 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -161,11 +161,6 @@ class JiraService < IssueTrackerService
add_comment(data, jira_issue)
end
- # reason why service cannot be tested
- def disabled_title
- "Please fill in Password and Username."
- end
-
def test(_)
result = test_settings
success = result.present?
diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb
index ad4ad7903ad..20fed432e55 100644
--- a/app/models/project_services/kubernetes_service.rb
+++ b/app/models/project_services/kubernetes_service.rb
@@ -105,19 +105,19 @@ class KubernetesService < DeploymentService
def predefined_variables
config = YAML.dump(kubeconfig)
- variables = [
- { key: 'KUBE_URL', value: api_url, public: true },
- { key: 'KUBE_TOKEN', value: token, public: false },
- { key: 'KUBE_NAMESPACE', value: actual_namespace, public: true },
- { key: 'KUBECONFIG', value: config, public: false, file: true }
- ]
-
- if ca_pem.present?
- variables << { key: 'KUBE_CA_PEM', value: ca_pem, public: true }
- variables << { key: 'KUBE_CA_PEM_FILE', value: ca_pem, public: true, file: true }
+ Gitlab::Ci::Variables::Collection.new.tap do |variables|
+ variables
+ .append(key: 'KUBE_URL', value: api_url)
+ .append(key: 'KUBE_TOKEN', value: token, public: false)
+ .append(key: 'KUBE_NAMESPACE', value: actual_namespace)
+ .append(key: 'KUBECONFIG', value: config, public: false, file: true)
+
+ if ca_pem.present?
+ variables
+ .append(key: 'KUBE_CA_PEM', value: ca_pem)
+ .append(key: 'KUBE_CA_PEM_FILE', value: ca_pem, file: true)
+ end
end
-
- variables
end
# Constructs a list of terminals from the reactive cache
@@ -197,7 +197,7 @@ class KubernetesService < DeploymentService
kubeclient = build_kubeclient!
kubeclient.get_pods(namespace: actual_namespace).as_json
- rescue KubeException => err
+ rescue Kubeclient::HttpError => err
raise err unless err.error_code == 404
[]
diff --git a/app/models/project_services/pipelines_email_service.rb b/app/models/project_services/pipelines_email_service.rb
index 9c7b58dead5..4cf149ac044 100644
--- a/app/models/project_services/pipelines_email_service.rb
+++ b/app/models/project_services/pipelines_email_service.rb
@@ -39,10 +39,6 @@ class PipelinesEmailService < Service
project.pipelines.any?
end
- def disabled_title
- 'Please setup a pipeline on your repository.'
- end
-
def test_data(project, user)
data = Gitlab::DataBuilder::Pipeline.build(project.pipelines.last)
data[:user] = user.hook_attrs
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index f6041da986c..52e067cb44c 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -169,7 +169,7 @@ class ProjectWiki
private
def create_repo!(raw_repository)
- gitlab_shell.add_repository(project.repository_storage, disk_path)
+ gitlab_shell.create_repository(project.repository_storage, disk_path)
raise CouldNotCreateWikiError unless raw_repository.exists?
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 461dfbec2bc..42f1ac43e29 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -16,6 +16,7 @@ class Repository
].freeze
include Gitlab::ShellAdapter
+ include Gitlab::RepositoryCacheAdapter
attr_accessor :full_path, :disk_path, :project, :is_wiki
@@ -35,7 +36,7 @@ class Repository
CACHED_METHODS = %i(size commit_count rendered_readme contribution_guide
changelog license_blob license_key gitignore koding_yml
gitlab_ci_yml branch_names tag_names branch_count
- tag_count avatar exists? empty? root_ref has_visible_content?
+ tag_count avatar exists? root_ref has_visible_content?
issue_template_names merge_request_template_names).freeze
# Methods that use cache_method but only memoize the value
@@ -57,22 +58,6 @@ class Repository
merge_request_template: :merge_request_template_names
}.freeze
- # Wraps around the given method and caches its output in Redis and an instance
- # variable.
- #
- # This only works for methods that do not take any arguments.
- def self.cache_method(name, fallback: nil, memoize_only: false)
- original = :"_uncached_#{name}"
-
- alias_method(original, name)
-
- define_method(name) do
- cache_method_output(name, fallback: fallback, memoize_only: memoize_only) do
- __send__(original) # rubocop:disable GitlabSecurity/PublicSend
- end
- end
- end
-
def initialize(full_path, project, disk_path: nil, is_wiki: false)
@full_path = full_path
@disk_path = disk_path || full_path
@@ -302,17 +287,6 @@ class Repository
expire_method_caches(CACHED_METHODS)
end
- # Expires the caches of a specific set of methods
- def expire_method_caches(methods)
- methods.each do |key|
- cache.expire(key)
-
- ivar = cache_instance_variable_name(key)
-
- remove_instance_variable(ivar) if instance_variable_defined?(ivar)
- end
- end
-
def expire_avatar_cache
expire_method_caches(%i(avatar))
end
@@ -360,7 +334,7 @@ class Repository
def expire_emptiness_caches
return unless empty?
- expire_method_caches(%i(empty? has_visible_content?))
+ expire_method_caches(%i(has_visible_content?))
end
def lookup_cache
@@ -506,12 +480,14 @@ class Repository
end
cache_method :exists?
+ # We don't need to cache the output of this method because both exists? and
+ # has_visible_content? are already memoized and cached. There's no guarantee
+ # that the values are expired and loaded atomically.
def empty?
return true unless exists?
!has_visible_content?
end
- cache_method :empty?
# The size of this repository in megabytes.
def size
@@ -922,49 +898,6 @@ class Repository
end
end
- # Caches the supplied block both in a cache and in an instance variable.
- #
- # The cache key and instance variable are named the same way as the value of
- # the `key` argument.
- #
- # This method will return `nil` if the corresponding instance variable is also
- # set to `nil`. This ensures we don't keep yielding the block when it returns
- # `nil`.
- #
- # key - The name of the key to cache the data in.
- # fallback - A value to fall back to in the event of a Git error.
- def cache_method_output(key, fallback: nil, memoize_only: false, &block)
- ivar = cache_instance_variable_name(key)
-
- if instance_variable_defined?(ivar)
- instance_variable_get(ivar)
- else
- # If the repository doesn't exist and a fallback was specified we return
- # that value inmediately. This saves us Rugged/gRPC invocations.
- return fallback unless fallback.nil? || exists?
-
- begin
- value =
- if memoize_only
- yield
- else
- cache.fetch(key, &block)
- end
-
- instance_variable_set(ivar, value)
- rescue Gitlab::Git::Repository::NoRepository
- # Even if the above `#exists?` check passes these errors might still
- # occur (for example because of a non-existing HEAD). We want to
- # gracefully handle this and not cache anything
- fallback
- end
- end
- end
-
- def cache_instance_variable_name(key)
- :"@#{key.to_s.tr('?!', '')}"
- end
-
def file_on_head(type)
if head = tree(:head)
head.blobs.find do |blob|
@@ -1019,8 +952,7 @@ class Repository
end
def cache
- # TODO: should we use UUIDs here? We could move repositories without clearing this cache
- @cache ||= RepositoryCache.new(full_path, @project.id)
+ @cache ||= Gitlab::RepositoryCache.new(self)
end
def tags_sorted_by_committed_date
diff --git a/app/models/service.rb b/app/models/service.rb
index 369cae2e85f..2556db68146 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -129,6 +129,17 @@ class Service < ActiveRecord::Base
fields
end
+ def configurable_events
+ events = self.class.supported_events
+
+ # No need to disable individual triggers when there is only one
+ if events.count == 1
+ []
+ else
+ events
+ end
+ end
+
def supported_events
self.class.supported_events
end
@@ -151,11 +162,6 @@ class Service < ActiveRecord::Base
true
end
- # reason why service cannot be tested
- def disabled_title
- "Please setup a project repository."
- end
-
# Provide convenient accessor methods
# for each serialized property.
# Also keep track of updated properties in a similar way as ActiveModel::Dirty
diff --git a/app/models/user.rb b/app/models/user.rb
index 9c60adf0c90..b8c55205ab8 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -114,13 +114,15 @@ class User < ActiveRecord::Base
has_many :project_authorizations
has_many :authorized_projects, through: :project_authorizations, source: :project
+ has_many :user_interacted_projects
+ has_many :project_interactions, through: :user_interacted_projects, source: :project, class_name: 'Project'
+
has_many :snippets, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
has_many :notes, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
has_many :issues, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
has_many :merge_requests, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
has_many :events, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
has_many :subscriptions, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
- has_many :recent_events, -> { order "id DESC" }, foreign_key: :author_id, class_name: "Event"
has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_one :abuse_report, dependent: :destroy, foreign_key: :user_id # rubocop:disable Cop/ActiveRecordDependent
has_many :reported_abuse_reports, dependent: :destroy, foreign_key: :reporter_id, class_name: "AbuseReport" # rubocop:disable Cop/ActiveRecordDependent
@@ -1035,14 +1037,33 @@ class User < ActiveRecord::Base
end
end
+ def todos_done_count(force: false)
+ Rails.cache.fetch(['users', id, 'todos_done_count'], force: force, expires_in: 20.minutes) do
+ TodosFinder.new(self, state: :done).execute.count
+ end
+ end
+
+ def todos_pending_count(force: false)
+ Rails.cache.fetch(['users', id, 'todos_pending_count'], force: force, expires_in: 20.minutes) do
+ TodosFinder.new(self, state: :pending).execute.count
+ end
+ end
+
def update_cache_counts
assigned_open_merge_requests_count(force: true)
assigned_open_issues_count(force: true)
end
+ def update_todos_count_cache
+ todos_done_count(force: true)
+ todos_pending_count(force: true)
+ end
+
def invalidate_cache_counts
invalidate_issue_cache_counts
invalidate_merge_request_cache_counts
+ invalidate_todos_done_count
+ invalidate_todos_pending_count
end
def invalidate_issue_cache_counts
@@ -1053,21 +1074,12 @@ class User < ActiveRecord::Base
Rails.cache.delete(['users', id, 'assigned_open_merge_requests_count'])
end
- def todos_done_count(force: false)
- Rails.cache.fetch(['users', id, 'todos_done_count'], force: force, expires_in: 20.minutes) do
- TodosFinder.new(self, state: :done).execute.count
- end
+ def invalidate_todos_done_count
+ Rails.cache.delete(['users', id, 'todos_done_count'])
end
- def todos_pending_count(force: false)
- Rails.cache.fetch(['users', id, 'todos_pending_count'], force: force, expires_in: 20.minutes) do
- TodosFinder.new(self, state: :pending).execute.count
- end
- end
-
- def update_todos_count_cache
- todos_done_count(force: true)
- todos_pending_count(force: true)
+ def invalidate_todos_pending_count
+ Rails.cache.delete(['users', id, 'todos_pending_count'])
end
# This is copied from Devise::Models::Lockable#valid_for_authentication?, as our auth
diff --git a/app/services/ci/fetch_kubernetes_token_service.rb b/app/services/ci/fetch_kubernetes_token_service.rb
index e73c6ad6780..bca883ec0a0 100644
--- a/app/services/ci/fetch_kubernetes_token_service.rb
+++ b/app/services/ci/fetch_kubernetes_token_service.rb
@@ -32,7 +32,7 @@ module Ci
kubeclient = build_kubeclient!
kubeclient.get_secrets.as_json
- rescue KubeException => err
+ rescue Kubeclient::HttpError => err
raise err unless err.error_code == 404
[]
diff --git a/app/services/clusters/applications/check_installation_progress_service.rb b/app/services/clusters/applications/check_installation_progress_service.rb
index bde090eaeec..90393e951a4 100644
--- a/app/services/clusters/applications/check_installation_progress_service.rb
+++ b/app/services/clusters/applications/check_installation_progress_service.rb
@@ -12,7 +12,7 @@ module Clusters
else
check_timeout
end
- rescue KubeException => ke
+ rescue Kubeclient::HttpError => ke
app.make_errored!("Kubernetes error: #{ke.message}") unless app.errored?
end
diff --git a/app/services/clusters/applications/install_service.rb b/app/services/clusters/applications/install_service.rb
index 8ceeec687cd..4c25a09814b 100644
--- a/app/services/clusters/applications/install_service.rb
+++ b/app/services/clusters/applications/install_service.rb
@@ -10,7 +10,7 @@ module Clusters
ClusterWaitForAppInstallationWorker.perform_in(
ClusterWaitForAppInstallationWorker::INTERVAL, app.name, app.id)
- rescue KubeException => ke
+ rescue Kubeclient::HttpError => ke
app.make_errored!("Kubernetes error: #{ke.message}")
rescue StandardError
app.make_errored!("Can't start installation process")
diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb
index 1db91c3c90c..2a69a205629 100644
--- a/app/services/compare_service.rb
+++ b/app/services/compare_service.rb
@@ -10,9 +10,14 @@ class CompareService
@start_ref_name = new_start_ref_name
end
- def execute(target_project, target_ref, straight: false)
+ def execute(target_project, target_ref, base_sha: nil, straight: false)
raw_compare = target_project.repository.compare_source_branch(target_ref, start_project.repository, start_ref_name, straight: straight)
- Compare.new(raw_compare, target_project, straight: straight) if raw_compare
+ return unless raw_compare
+
+ Compare.new(raw_compare,
+ target_project,
+ base_sha: base_sha,
+ straight: straight)
end
end
diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb
index 46acdc5406c..a954564946b 100644
--- a/app/services/files/create_service.rb
+++ b/app/services/files/create_service.rb
@@ -1,11 +1,11 @@
module Files
class CreateService < Files::BaseService
def create_commit!
- handler = Lfs::FileModificationHandler.new(project, @branch_name)
+ transformer = Lfs::FileTransformer.new(project, @branch_name)
- handler.new_file(@file_path, @file_content) do |content_or_lfs_pointer|
- create_transformed_commit(content_or_lfs_pointer)
- end
+ result = transformer.new_file(@file_path, @file_content)
+
+ create_transformed_commit(result.content)
end
private
diff --git a/app/services/files/multi_service.rb b/app/services/files/multi_service.rb
index a03c59f569d..13a1dee4173 100644
--- a/app/services/files/multi_service.rb
+++ b/app/services/files/multi_service.rb
@@ -3,11 +3,33 @@ module Files
UPDATE_FILE_ACTIONS = %w(update move delete).freeze
def create_commit!
+ transformer = Lfs::FileTransformer.new(project, @branch_name)
+
+ actions = actions_after_lfs_transformation(transformer, params[:actions])
+
+ commit_actions!(actions)
+ end
+
+ private
+
+ def actions_after_lfs_transformation(transformer, actions)
+ actions.map do |action|
+ if action[:action] == 'create'
+ result = transformer.new_file(action[:file_path], action[:content], encoding: action[:encoding])
+ action[:content] = result.content
+ action[:encoding] = result.encoding
+ end
+
+ action
+ end
+ end
+
+ def commit_actions!(actions)
repository.multi_action(
current_user,
message: @commit_message,
branch_name: @branch_name,
- actions: params[:actions],
+ actions: actions,
author_email: @author_email,
author_name: @author_name,
start_project: @start_project,
@@ -17,8 +39,6 @@ module Files
raise_error(e)
end
- private
-
def validate!
super
diff --git a/app/services/lfs/file_modification_handler.rb b/app/services/lfs/file_modification_handler.rb
deleted file mode 100644
index fe9091a6e5d..00000000000
--- a/app/services/lfs/file_modification_handler.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-module Lfs
- class FileModificationHandler
- attr_reader :project, :branch_name
-
- delegate :repository, to: :project
-
- def initialize(project, branch_name)
- @project = project
- @branch_name = branch_name
- end
-
- def new_file(file_path, file_content)
- if project.lfs_enabled? && lfs_file?(file_path)
- lfs_pointer_file = Gitlab::Git::LfsPointerFile.new(file_content)
- lfs_object = create_lfs_object!(lfs_pointer_file, file_content)
- content = lfs_pointer_file.pointer
-
- success = yield(content)
-
- link_lfs_object!(lfs_object) if success
- else
- yield(file_content)
- end
- end
-
- private
-
- def lfs_file?(file_path)
- repository.attributes_at(branch_name, file_path)['filter'] == 'lfs'
- end
-
- def create_lfs_object!(lfs_pointer_file, file_content)
- LfsObject.find_or_create_by(oid: lfs_pointer_file.sha256, size: lfs_pointer_file.size) do |lfs_object|
- lfs_object.file = CarrierWaveStringFile.new(file_content)
- end
- end
-
- def link_lfs_object!(lfs_object)
- project.lfs_objects << lfs_object
- end
- end
-end
diff --git a/app/services/lfs/file_transformer.rb b/app/services/lfs/file_transformer.rb
new file mode 100644
index 00000000000..69281ee3137
--- /dev/null
+++ b/app/services/lfs/file_transformer.rb
@@ -0,0 +1,66 @@
+module Lfs
+ # Usage: Calling `new_file` check to see if a file should be in LFS and
+ # return a transformed result with `content` and `encoding` to commit.
+ #
+ # For LFS an LfsObject linked to the project is stored and an LFS
+ # pointer returned. If the file isn't in LFS the untransformed content
+ # is returned to save in the commit.
+ #
+ # transformer = Lfs::FileTransformer.new(project, @branch_name)
+ # content_or_lfs_pointer = transformer.new_file(file_path, content).content
+ # create_transformed_commit(content_or_lfs_pointer)
+ #
+ class FileTransformer
+ attr_reader :project, :branch_name
+
+ delegate :repository, to: :project
+
+ def initialize(project, branch_name)
+ @project = project
+ @branch_name = branch_name
+ end
+
+ def new_file(file_path, file_content, encoding: nil)
+ if project.lfs_enabled? && lfs_file?(file_path)
+ file_content = Base64.decode64(file_content) if encoding == 'base64'
+ lfs_pointer_file = Gitlab::Git::LfsPointerFile.new(file_content)
+ lfs_object = create_lfs_object!(lfs_pointer_file, file_content)
+
+ link_lfs_object!(lfs_object)
+
+ Result.new(content: lfs_pointer_file.pointer, encoding: 'text')
+ else
+ Result.new(content: file_content, encoding: encoding)
+ end
+ end
+
+ class Result
+ attr_reader :content, :encoding
+
+ def initialize(content:, encoding:)
+ @content = content
+ @encoding = encoding
+ end
+ end
+
+ private
+
+ def lfs_file?(file_path)
+ cached_attributes.attributes(file_path)['filter'] == 'lfs'
+ end
+
+ def cached_attributes
+ @cached_attributes ||= Gitlab::Git::AttributesAtRefParser.new(repository, branch_name)
+ end
+
+ def create_lfs_object!(lfs_pointer_file, file_content)
+ LfsObject.find_or_create_by(oid: lfs_pointer_file.sha256, size: lfs_pointer_file.size) do |lfs_object|
+ lfs_object.file = CarrierWaveStringFile.new(file_content)
+ end
+ end
+
+ def link_lfs_object!(lfs_object)
+ project.lfs_objects << lfs_object
+ end
+ end
+end
diff --git a/app/services/members/destroy_service.rb b/app/services/members/destroy_service.rb
index b141bfd5fbc..5b51e1982f1 100644
--- a/app/services/members/destroy_service.rb
+++ b/app/services/members/destroy_service.rb
@@ -5,12 +5,9 @@ module Members
return member if member.is_a?(GroupMember) && member.source.last_owner?(member.user)
- Member.transaction do
- unassign_issues_and_merge_requests(member) unless member.invite?
- member.notification_setting&.destroy
+ member.destroy
- member.destroy
- end
+ member.user&.invalidate_cache_counts
if member.request? && member.user != current_user
notification_service.decline_access_request(member)
@@ -37,38 +34,5 @@ module Members
raise "Unknown member type: #{member}!"
end
end
-
- def unassign_issues_and_merge_requests(member)
- if member.is_a?(GroupMember)
- issues = Issue.unscoped.select(1)
- .joins(:project)
- .where('issues.id = issue_assignees.issue_id AND projects.namespace_id = ?', member.source_id)
-
- # DELETE FROM issue_assignees WHERE user_id = X AND EXISTS (...)
- IssueAssignee.unscoped
- .where('user_id = :user_id AND EXISTS (:sub)', user_id: member.user_id, sub: issues)
- .delete_all
-
- MergeRequestsFinder.new(current_user, group_id: member.source_id, assignee_id: member.user_id)
- .execute
- .update_all(assignee_id: nil)
- else
- project = member.source
-
- # SELECT 1 FROM issues WHERE issues.id = issue_assignees.issue_id AND issues.project_id = X
- issues = Issue.unscoped.select(1)
- .where('issues.id = issue_assignees.issue_id')
- .where(project_id: project.id)
-
- # DELETE FROM issue_assignees WHERE user_id = X AND EXISTS (...)
- IssueAssignee.unscoped
- .where('user_id = :user_id AND EXISTS (:sub)', user_id: member.user_id, sub: issues)
- .delete_all
-
- project.merge_requests.opened.assigned_to(member.user).update_all(assignee_id: nil)
- end
-
- member.user.invalidate_cache_counts
- end
end
end
diff --git a/app/services/merge_requests/conflicts/list_service.rb b/app/services/merge_requests/conflicts/list_service.rb
index ca9a33678e4..72cbc49adb2 100644
--- a/app/services/merge_requests/conflicts/list_service.rb
+++ b/app/services/merge_requests/conflicts/list_service.rb
@@ -17,15 +17,7 @@ module MergeRequests
return @conflicts_can_be_resolved_in_ui = false unless merge_request.has_complete_diff_refs?
return @conflicts_can_be_resolved_in_ui = false if merge_request.branch_missing?
- begin
- # Try to parse each conflict. If the MR's mergeable status hasn't been
- # updated, ensure that we don't say there are conflicts to resolve
- # when there are no conflict files.
- conflicts.files.each(&:lines)
- @conflicts_can_be_resolved_in_ui = conflicts.files.length > 0
- rescue Gitlab::Git::CommandError, Gitlab::Git::Conflict::Parser::UnresolvableError, Gitlab::Git::Conflict::Resolver::ConflictSideMissing
- @conflicts_can_be_resolved_in_ui = false
- end
+ @conflicts_can_be_resolved_in_ui = conflicts.can_be_resolved_in_ui?
end
def conflicts
diff --git a/app/services/merge_requests/merge_request_diff_cache_service.rb b/app/services/merge_requests/merge_request_diff_cache_service.rb
index 2945a7fd4e4..10aa9ae609c 100644
--- a/app/services/merge_requests/merge_request_diff_cache_service.rb
+++ b/app/services/merge_requests/merge_request_diff_cache_service.rb
@@ -1,8 +1,17 @@
module MergeRequests
class MergeRequestDiffCacheService
- def execute(merge_request)
+ def execute(merge_request, new_diff)
# Executing the iteration we cache all the highlighted diff information
merge_request.diffs.diff_files.to_a
+
+ # Remove cache for all diffs on this MR. Do not use the association on the
+ # model, as that will interfere with other actions happening when
+ # reloading the diff.
+ MergeRequestDiff.where(merge_request: merge_request).each do |merge_request_diff|
+ next if merge_request_diff == new_diff
+
+ merge_request_diff.diffs.clear_cache!
+ end
end
end
end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index e07ecda27b5..ab94db2c1e5 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -208,9 +208,9 @@ class NotificationService
def new_access_request(member)
return true unless member.notifiable?(:subscription)
- recipients = member.source.members.owners_and_masters
+ recipients = member.source.members.active_without_invites.owners_and_masters
if fallback_to_group_owners_masters?(recipients, member)
- recipients = member.source.group.members.owners_and_masters
+ recipients = member.source.group.members.active_without_invites.owners_and_masters
end
recipients.each { |recipient| deliver_access_request_email(recipient, member) }
diff --git a/app/services/projects/import_export/export_service.rb b/app/services/projects/import_export/export_service.rb
index af41ce82f65..d16aa3de639 100644
--- a/app/services/projects/import_export/export_service.rb
+++ b/app/services/projects/import_export/export_service.rb
@@ -26,7 +26,7 @@ module Projects
end
def project_tree_saver
- Gitlab::ImportExport::ProjectTreeSaver.new(project: project, current_user: @current_user, shared: @shared)
+ Gitlab::ImportExport::ProjectTreeSaver.new(project: project, current_user: @current_user, shared: @shared, params: @params)
end
def uploads_saver
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index 379a8068023..5f2615a2c01 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -58,7 +58,7 @@ module Projects
def enabling_wiki?
return false if @project.wiki_enabled?
- params[:project_feature_attributes][:wiki_access_level].to_i > ProjectFeature::DISABLED
+ params.dig(:project_feature_attributes, :wiki_access_level).to_i > ProjectFeature::DISABLED
end
def ensure_wiki_exists
diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml
index 185e9d7b35d..37269862de6 100644
--- a/app/views/admin/runners/show.html.haml
+++ b/app/views/admin/runners/show.html.haml
@@ -9,6 +9,10 @@
%span.runner-state.runner-state-specific
Specific
+- add_to_breadcrumbs _("Runners"), admin_runners_path
+- breadcrumb_title "##{@runner.id}"
+- @no_container = true
+
- if @runner.shared?
.bs-callout.bs-callout-success
%h4 This Runner will process jobs from ALL UNASSIGNED projects
diff --git a/app/views/discussions/_diff_with_notes.html.haml b/app/views/discussions/_diff_with_notes.html.haml
index f9bfc01f213..8680ec2e298 100644
--- a/app/views/discussions/_diff_with_notes.html.haml
+++ b/app/views/discussions/_diff_with_notes.html.haml
@@ -2,8 +2,12 @@
- blob = discussion.blob
- discussions = { discussion.original_line_code => [discussion] }
- diff_file_class = diff_file.text? ? 'text-file' : 'js-image-file'
+- diff_data = {}
+- expanded = discussion.expanded? || local_assigns.fetch(:expanded, nil)
+- unless expanded
+ - diff_data = { lines_path: project_merge_request_discussion_path(discussion.project, discussion.noteable, discussion) }
-.diff-file.file-holder{ class: diff_file_class }
+.diff-file.file-holder{ class: diff_file_class, data: diff_data }
.js-file-title.file-title.file-title-flex-parent
.file-header-content
= render "projects/diffs/file_header", diff_file: diff_file, url: discussion_path(discussion), show_toggle: false
@@ -11,17 +15,24 @@
- if diff_file.text?
.diff-content.code.js-syntax-highlight
%table
- = render partial: "projects/diffs/line",
- collection: discussion.truncated_diff_lines,
- as: :line,
- locals: { diff_file: diff_file,
- discussions: discussions,
- discussion_expanded: true,
- plain: true }
+ - if expanded
+ - discussions = { discussion.original_line_code => [discussion] }
+ = render partial: "projects/diffs/line",
+ collection: discussion.truncated_diff_lines,
+ as: :line,
+ locals: { diff_file: diff_file,
+ discussions: discussions,
+ discussion_expanded: true,
+ plain: true }
+ - else
+ %tr.line_holder.line-holder-placeholder
+ %td.old_line.diff-line-num
+ %td.new_line.diff-line-num
+ %td.line_content
+ .js-code-placeholder
+ = render "discussions/diff_discussion", discussions: [discussion], expanded: true
- else
- partial = (diff_file.new_file? || diff_file.deleted_file?) ? 'single_image_diff' : 'replaced_image_diff'
-
= render partial: "projects/diffs/#{partial}", locals: { diff_file: diff_file, position: discussion.position.to_json, click_to_comment: false }
-
.note-container
= render partial: "discussions/notes", locals: { discussion: discussion, show_toggle: false, show_image_comment_badge: true, disable_collapse_class: true }
diff --git a/app/views/discussions/_discussion.html.haml b/app/views/discussions/_discussion.html.haml
index 8b9fa3d6b05..e9589213f80 100644
--- a/app/views/discussions/_discussion.html.haml
+++ b/app/views/discussions/_discussion.html.haml
@@ -8,7 +8,7 @@
.discussion.js-toggle-container{ data: { discussion_id: discussion.id } }
.discussion-header
.discussion-actions
- %button.note-action-button.discussion-toggle-button.js-toggle-button{ type: "button" }
+ %button.note-action-button.discussion-toggle-button.js-toggle-button{ type: "button", class: ("js-toggle-lazy-diff" unless expanded) }
- if expanded
= icon("chevron-up")
- else
diff --git a/app/views/import/github/new.html.haml b/app/views/import/github/new.html.haml
index ca47ab5f274..54ef51b30e3 100644
--- a/app/views/import/github/new.html.haml
+++ b/app/views/import/github/new.html.haml
@@ -10,7 +10,7 @@
%p
= import_github_authorize_message
- = link_to _('List your GitHub repositories'), status_import_github_path, class: 'btn btn-success'
+ = link_to _('List your GitHub repositories'), status_import_github_path(ci_cd_only: params[:ci_cd_only]), class: 'btn btn-success'
%hr
diff --git a/app/views/peek/views/_gc.html.haml b/app/views/peek/views/_gc.html.haml
new file mode 100644
index 00000000000..9fc83e56ee7
--- /dev/null
+++ b/app/views/peek/views/_gc.html.haml
@@ -0,0 +1,7 @@
+- local_assigns.fetch(:view)
+
+%span.bold
+ %span{ title: 'Invoke Time', data: { defer_to: "#{view.defer_key}-gc_time" } }...
+ \/
+ %span{ title: 'Invoke Count', data: { defer_to: "#{view.defer_key}-invokes" } }...
+gc
diff --git a/app/views/peek/views/_gitaly.html.haml b/app/views/peek/views/_gitaly.html.haml
index a7d040d6821..945bb287429 100644
--- a/app/views/peek/views/_gitaly.html.haml
+++ b/app/views/peek/views/_gitaly.html.haml
@@ -1,7 +1,17 @@
- local_assigns.fetch(:view)
-%strong
- %span{ data: { defer_to: "#{view.defer_key}-duration" } } ...
+%button.btn-blank.btn-link.bold{ type: 'button', data: { toggle: 'modal', target: '#modal-peek-gitaly-details' } }
+ %span{ data: { defer_to: "#{view.defer_key}-duration" } }...
\/
- %span{ data: { defer_to: "#{view.defer_key}-calls" } } ...
- Gitaly
+ %span{ data: { defer_to: "#{view.defer_key}-calls" } }...
+#modal-peek-gitaly-details.modal{ tabindex: -1, role: 'dialog' }
+ .modal-dialog.modal-full
+ .modal-content
+ .modal-header
+ %button.close{ type: 'button', data: { dismiss: 'modal' }, 'aria-label' => 'Close' }
+ %span{ 'aria-hidden' => 'true' }
+ &times;
+ %h4
+ Gitaly requests
+ .modal-body{ data: { defer_to: "#{view.defer_key}-details" } }...
+gitaly
diff --git a/app/views/peek/views/_redis.html.haml b/app/views/peek/views/_redis.html.haml
new file mode 100644
index 00000000000..f7fba6c95fc
--- /dev/null
+++ b/app/views/peek/views/_redis.html.haml
@@ -0,0 +1,7 @@
+- local_assigns.fetch(:view)
+
+%span.bold
+ %span{ data: { defer_to: "#{view.defer_key}-duration" } }...
+ \/
+ %span{ data: { defer_to: "#{view.defer_key}-calls" } }...
+redis
diff --git a/app/views/peek/views/_sidekiq.html.haml b/app/views/peek/views/_sidekiq.html.haml
new file mode 100644
index 00000000000..7efbc05890d
--- /dev/null
+++ b/app/views/peek/views/_sidekiq.html.haml
@@ -0,0 +1,7 @@
+- local_assigns.fetch(:view)
+
+%span.bold
+ %span{ data: { defer_to: "#{view.defer_key}-duration" } }...
+ \/
+ %span{ data: { defer_to: "#{view.defer_key}-calls" } }...
+sidekiq
diff --git a/app/views/peek/views/_sql.html.haml b/app/views/peek/views/_sql.html.haml
index dd8b524064f..36583df898a 100644
--- a/app/views/peek/views/_sql.html.haml
+++ b/app/views/peek/views/_sql.html.haml
@@ -1,13 +1,14 @@
-%strong
- %a.js-toggle-modal-peek-sql
- %span{ data: { defer_to: "#{view.defer_key}-duration" } }...
- \/
- %span{ data: { defer_to: "#{view.defer_key}-calls" } }...
+%button.btn-blank.btn-link.bold{ type: 'button', data: { toggle: 'modal', target: '#modal-peek-pg-queries' } }
+ %span{ data: { defer_to: "#{view.defer_key}-duration" } }...
+ \/
+ %span{ data: { defer_to: "#{view.defer_key}-calls" } }...
#modal-peek-pg-queries.modal{ tabindex: -1 }
.modal-dialog.modal-full
.modal-content
.modal-header
- %button.close.btn.btn-link.btn-sm{ type: 'button', data: { dismiss: 'modal' } } X
+ %button.close{ type: 'button', data: { dismiss: 'modal' }, 'aria-label' => 'Close' }
+ %span{ 'aria-hidden' => 'true' }
+ &times;
%h4
SQL queries
.modal-body{ data: { defer_to: "#{view.defer_key}-queries" } }...
diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml
index 17e804d682b..684b082efbb 100644
--- a/app/views/projects/services/_form.html.haml
+++ b/app/views/projects/services/_form.html.haml
@@ -5,6 +5,9 @@
= boolean_to_icon @service.activated?
%p= @service.description
+
+ - if @service.respond_to?(:detailed_description)
+ %p= @service.detailed_description
.col-lg-9
= form_for(@service, as: :service, url: project_service_path(@project, @service.to_param), method: :put, html: { class: 'gl-show-field-errors form-horizontal integration-settings-form js-integration-settings-form', data: { 'can-test' => @service.can_test?, 'test-url' => test_project_service_path(@project, @service) } }) do |form|
= render 'shared/service_settings', form: form, subject: @service
@@ -12,11 +15,6 @@
.footer-block.row-content-block
= service_save_button(@service)
&nbsp;
- - if @service.valid? && @service.activated?
- - unless @service.can_test?
- - disabled_class = 'disabled'
- - disabled_title = @service.disabled_title
-
= link_to 'Cancel', project_settings_integrations_path(@project), class: 'btn btn-cancel'
- if lookup_context.template_exists?('show', "projects/services/#{@service.to_param}", true)
diff --git a/app/views/projects/services/prometheus/_show.html.haml b/app/views/projects/services/prometheus/_show.html.haml
index 6dc2b85fd32..43e6a173108 100644
--- a/app/views/projects/services/prometheus/_show.html.haml
+++ b/app/views/projects/services/prometheus/_show.html.haml
@@ -7,21 +7,19 @@
= link_to s_('PrometheusService|More information'), help_page_path('user/project/integrations/prometheus')
.col-lg-9
- .panel.panel-default.js-panel-monitored-metrics{ data: { active_metrics: active_common_project_prometheus_metrics_path(@project, :json) } }
+ .panel.panel-default.js-panel-monitored-metrics{ data: { active_metrics: active_common_project_prometheus_metrics_path(@project, :json), metrics_help_path: help_page_path('user/project/integrations/prometheus_library/metrics') } }
.panel-heading
%h3.panel-title
- = s_('PrometheusService|Monitored')
+ = s_('PrometheusService|Common metrics')
%span.badge.js-monitored-count 0
.panel-body
- .loading-metrics.text-center.js-loading-metrics
- = icon('spinner spin 3x', class: 'metrics-load-spinner')
- %p
+ .loading-metrics.js-loading-metrics
+ %p.prepend-top-10.prepend-left-10
+ = icon('spinner spin', class: 'metrics-load-spinner')
= s_('PrometheusService|Finding and configuring metrics...')
- .empty-metrics.text-center.hidden.js-empty-metrics
- = custom_icon('icon_empty_metrics')
- %p
- = s_('PrometheusService|No metrics are being monitored. To start monitoring, deploy to an environment.')
- = link_to s_('PrometheusService|View environments'), project_environments_path(@project), class: 'btn btn-success'
+ .empty-metrics.hidden.js-empty-metrics
+ %p.text-tertiary.prepend-top-10.prepend-left-10
+ = s_('PrometheusService|Waiting for your first deployment to an environment to find common metrics')
%ul.list-unstyled.metrics-list.hidden.js-metrics-list
.panel.panel-default.hidden.js-panel-missing-env-vars
diff --git a/app/views/shared/_service_settings.html.haml b/app/views/shared/_service_settings.html.haml
index 61b39afb5d4..355b3ac75ae 100644
--- a/app/views/shared/_service_settings.html.haml
+++ b/app/views/shared/_service_settings.html.haml
@@ -13,12 +13,12 @@
.col-sm-10
= form.check_box :active, disabled: disable_fields_service?(@service)
- - if @service.supported_events.present?
+ - if @service.configurable_events.present?
.form-group
= form.label :url, "Trigger", class: 'control-label'
.col-sm-10
- - @service.supported_events.each do |event|
+ - @service.configurable_events.each do |event|
%div
= form.check_box service_event_field_name(event), class: 'pull-left'
.prepend-left-20
diff --git a/app/views/shared/boards/_show.html.haml b/app/views/shared/boards/_show.html.haml
index 44b09545a61..dac60094686 100644
--- a/app/views/shared/boards/_show.html.haml
+++ b/app/views/shared/boards/_show.html.haml
@@ -27,7 +27,7 @@
":issue-link-base" => "issueLinkBase",
":root-path" => "rootPath",
":board-id" => "boardId",
- ":key" => "_uid" }
+ ":key" => "list.id" }
= render "shared/boards/components/sidebar", group: group
- if @project
%board-add-issues-modal{ "new-issue-path" => new_project_issue_path(@project),
diff --git a/app/views/shared/issuable/_label_page_create.html.haml b/app/views/shared/issuable/_label_page_create.html.haml
index d5e7d3b87b7..91aa329eb93 100644
--- a/app/views/shared/issuable/_label_page_create.html.haml
+++ b/app/views/shared/issuable/_label_page_create.html.haml
@@ -1,5 +1,6 @@
+- subject = @project || @group
.dropdown-page-two.dropdown-new-label
- = dropdown_title("Create new label", options: { back: true })
+ = dropdown_title(create_label_title(subject), options: { back: true })
= dropdown_content do
.dropdown-labels-error.js-label-error
%input#new_label_name.default-dropdown-input{ type: "text", placeholder: _('Name new label') }
diff --git a/app/views/shared/issuable/_label_page_default.html.haml b/app/views/shared/issuable/_label_page_default.html.haml
index 6a83321abcb..2bd922bca2b 100644
--- a/app/views/shared/issuable/_label_page_default.html.haml
+++ b/app/views/shared/issuable/_label_page_default.html.haml
@@ -3,6 +3,7 @@
- show_footer = local_assigns.fetch(:show_footer, true)
- filter_placeholder = local_assigns.fetch(:filter_placeholder, 'Search')
- show_boards_content = local_assigns.fetch(:show_boards_content, false)
+- subject = @project || @group
.dropdown-page-one
= dropdown_title(title)
- if show_boards_content
@@ -17,11 +18,11 @@
- if can?(current_user, :admin_label, current_board_parent)
%li
%a.dropdown-toggle-page{ href: "#" }
- = _('Create new label')
+ = create_label_title(subject)
%li
= link_to labels_path, :"data-is-link" => true do
- if show_create && can?(current_user, :admin_label, current_board_parent)
- = _('Manage labels')
+ = manage_labels_title(subject)
- else
- = _('View labels')
+ = view_labels_title(subject)
= dropdown_loading
diff --git a/app/views/shared/issuable/form/_contribution.html.haml b/app/views/shared/issuable/form/_contribution.html.haml
index 0f2d313a5cc..de508278d7c 100644
--- a/app/views/shared/issuable/form/_contribution.html.haml
+++ b/app/views/shared/issuable/form/_contribution.html.haml
@@ -14,7 +14,7 @@
.checkbox
= form.label :allow_maintainer_to_push do
= form.check_box :allow_maintainer_to_push, disabled: !issuable.can_allow_maintainer_to_push?(current_user)
- = _('Allow edits from maintainers')
+ = _('Allow edits from maintainers.')
= link_to 'About this feature', help_page_path('user/project/merge_requests/maintainer_access')
.help-block
= allow_maintainer_push_unavailable_reason(issuable)
diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml
index 9db2a321526..5926867e2d7 100644
--- a/app/views/shared/milestones/_milestone.html.haml
+++ b/app/views/shared/milestones/_milestone.html.haml
@@ -63,13 +63,13 @@
= link_to 'Close Milestone', project_milestone_path(@project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close btn-grouped"
- %button.js-delete-milestone-button.btn.btn-xs.btn-grouped.btn-danger{ data: { toggle: 'modal',
- target: '#delete-milestone-modal',
- milestone_id: milestone.id,
- milestone_title: markdown_field(milestone, :title),
- milestone_url: project_milestone_path(milestone.project, milestone),
- milestone_issue_count: milestone.issues.count,
- milestone_merge_request_count: milestone.merge_requests.count },
- disabled: true }
- = _('Delete')
- = icon('spin spinner', class: 'js-loading-icon hidden' )
+ %button.js-delete-milestone-button.btn.btn-xs.btn-grouped.btn-danger{ data: { toggle: 'modal',
+ target: '#delete-milestone-modal',
+ milestone_id: milestone.id,
+ milestone_title: markdown_field(milestone, :title),
+ milestone_url: project_milestone_path(milestone.project, milestone),
+ milestone_issue_count: milestone.issues.count,
+ milestone_merge_request_count: milestone.merge_requests.count },
+ disabled: true }
+ = _('Delete')
+ = icon('spin spinner', class: 'js-loading-icon hidden' )
diff --git a/app/workers/project_export_worker.rb b/app/workers/project_export_worker.rb
index c100852374a..0b502143e5d 100644
--- a/app/workers/project_export_worker.rb
+++ b/app/workers/project_export_worker.rb
@@ -4,10 +4,11 @@ class ProjectExportWorker
sidekiq_options retry: 3
- def perform(current_user_id, project_id)
+ def perform(current_user_id, project_id, params = {})
+ params = params.with_indifferent_access
current_user = User.find(current_user_id)
project = Project.find(project_id)
- ::Projects::ImportExport::ExportService.new(project, current_user).execute
+ ::Projects::ImportExport::ExportService.new(project, current_user, params).execute
end
end
diff --git a/changelogs/unreleased/34604-fix-generated-url-for-external-repository.yml b/changelogs/unreleased/34604-fix-generated-url-for-external-repository.yml
new file mode 100644
index 00000000000..c4b5f59b724
--- /dev/null
+++ b/changelogs/unreleased/34604-fix-generated-url-for-external-repository.yml
@@ -0,0 +1,5 @@
+---
+title: Fix generated URL when listing repoitories for import
+merge_request: 17692
+author:
+type: fixed
diff --git a/changelogs/unreleased/35475-lazy-diff.yml b/changelogs/unreleased/35475-lazy-diff.yml
new file mode 100644
index 00000000000..bafa66ebe39
--- /dev/null
+++ b/changelogs/unreleased/35475-lazy-diff.yml
@@ -0,0 +1,5 @@
+---
+title: lazy load diffs on merge request discussions
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/40525-listing-user-activity-timeouts.yml b/changelogs/unreleased/40525-listing-user-activity-timeouts.yml
new file mode 100644
index 00000000000..39ce873dba6
--- /dev/null
+++ b/changelogs/unreleased/40525-listing-user-activity-timeouts.yml
@@ -0,0 +1,5 @@
+---
+title: Improve database response time for user activity listing.
+merge_request: 17454
+author:
+type: performance
diff --git a/changelogs/unreleased/41902-add-api-option-to-overwrite-project-description-on-project-export.yml b/changelogs/unreleased/41902-add-api-option-to-overwrite-project-description-on-project-export.yml
new file mode 100644
index 00000000000..60a649f22c9
--- /dev/null
+++ b/changelogs/unreleased/41902-add-api-option-to-overwrite-project-description-on-project-export.yml
@@ -0,0 +1,5 @@
+---
+title: Adds the option to the project export API to override the project description and display GitLab export description once imported
+merge_request: 17744
+author:
+type: added
diff --git a/changelogs/unreleased/42579-fix-sidebar-dropdown-hover-style.yml b/changelogs/unreleased/42579-fix-sidebar-dropdown-hover-style.yml
new file mode 100644
index 00000000000..c0a247dc895
--- /dev/null
+++ b/changelogs/unreleased/42579-fix-sidebar-dropdown-hover-style.yml
@@ -0,0 +1,5 @@
+---
+title: Fix hover style of dropdown items in the right sidebar
+merge_request: 17519
+author:
+type: fixed
diff --git a/changelogs/unreleased/42814-fix-remove-source-branch-when-mwps.yml b/changelogs/unreleased/42814-fix-remove-source-branch-when-mwps.yml
new file mode 100644
index 00000000000..08e77ee7c3b
--- /dev/null
+++ b/changelogs/unreleased/42814-fix-remove-source-branch-when-mwps.yml
@@ -0,0 +1,6 @@
+---
+title: Fix "Remove source branch" button in Merge request widget during merge when pipeline
+ succeeds state
+merge_request: 17192
+author:
+type: fixed
diff --git a/changelogs/unreleased/43261-fix-prometheus-installation.yml b/changelogs/unreleased/43261-fix-prometheus-installation.yml
deleted file mode 100644
index b5fc7980390..00000000000
--- a/changelogs/unreleased/43261-fix-prometheus-installation.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow Prometheus application to be installed from Cluster applications
-merge_request: 17372
-author:
-type: fixed
diff --git a/changelogs/unreleased/43510-merge-requests-and-issues-don-t-show-for-all-subgroups.yml b/changelogs/unreleased/43510-merge-requests-and-issues-don-t-show-for-all-subgroups.yml
deleted file mode 100644
index e163c04f430..00000000000
--- a/changelogs/unreleased/43510-merge-requests-and-issues-don-t-show-for-all-subgroups.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Ensure group issues and merge requests pages show results from subgroups when
- there are no results from the current group
-merge_request: 17312
-author:
-type: fixed
diff --git a/changelogs/unreleased/43531-500-error-searching-wiki-incompatible-character-encodings-utf-8-and-ascii-8bit.yml b/changelogs/unreleased/43531-500-error-searching-wiki-incompatible-character-encodings-utf-8-and-ascii-8bit.yml
deleted file mode 100644
index 173710412a5..00000000000
--- a/changelogs/unreleased/43531-500-error-searching-wiki-incompatible-character-encodings-utf-8-and-ascii-8bit.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix code and wiki search results pages when non-ASCII text is displayed
-merge_request: 17413
-author:
-type: fixed
diff --git a/changelogs/unreleased/43532-error-on-admin-applications-prometheus-template.yml b/changelogs/unreleased/43532-error-on-admin-applications-prometheus-template.yml
deleted file mode 100644
index 25bcbf2fbab..00000000000
--- a/changelogs/unreleased/43532-error-on-admin-applications-prometheus-template.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixes Prometheus admin configuration page
-merge_request: 17377
-author:
-type: fixed
diff --git a/changelogs/unreleased/43702-update-label-dropdown-wording.yml b/changelogs/unreleased/43702-update-label-dropdown-wording.yml
new file mode 100644
index 00000000000..97100ec89de
--- /dev/null
+++ b/changelogs/unreleased/43702-update-label-dropdown-wording.yml
@@ -0,0 +1,5 @@
+---
+title: Update wording to specify create/manage project vs group labels in labels dropdown
+merge_request: 17640
+author:
+type: changed
diff --git a/changelogs/unreleased/43717-breadcrumb-on-admin-runner-page.yml b/changelogs/unreleased/43717-breadcrumb-on-admin-runner-page.yml
new file mode 100644
index 00000000000..3aec71d5ac4
--- /dev/null
+++ b/changelogs/unreleased/43717-breadcrumb-on-admin-runner-page.yml
@@ -0,0 +1,5 @@
+---
+title: Set breadcrumb for admin/runners/show
+merge_request: 17431
+author: Takuya Noguchi
+type: fixed
diff --git a/changelogs/unreleased/43720-update-fe-webpack-docs.yml b/changelogs/unreleased/43720-update-fe-webpack-docs.yml
new file mode 100644
index 00000000000..9e461eaaec8
--- /dev/null
+++ b/changelogs/unreleased/43720-update-fe-webpack-docs.yml
@@ -0,0 +1,6 @@
+---
+title: Update documentation to reflect current minimum required versions of node and
+ yarn
+merge_request: 17706
+author:
+type: other
diff --git a/changelogs/unreleased/43802-ensure-foreign-keys-on-clusters-applications.yml b/changelogs/unreleased/43802-ensure-foreign-keys-on-clusters-applications.yml
new file mode 100644
index 00000000000..860a8becd65
--- /dev/null
+++ b/changelogs/unreleased/43802-ensure-foreign-keys-on-clusters-applications.yml
@@ -0,0 +1,5 @@
+---
+title: Ensure foreign keys on clusters applications
+merge_request: 17488
+author:
+type: other
diff --git a/changelogs/unreleased/43805-list-gitaly-calls-and-arguments-in-the-performance-bar.yml b/changelogs/unreleased/43805-list-gitaly-calls-and-arguments-in-the-performance-bar.yml
new file mode 100644
index 00000000000..4c63e69f0bb
--- /dev/null
+++ b/changelogs/unreleased/43805-list-gitaly-calls-and-arguments-in-the-performance-bar.yml
@@ -0,0 +1,5 @@
+---
+title: Add Gitaly call details to performance bar
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/43806-update-ruby-saml-to-1-7-2.yml b/changelogs/unreleased/43806-update-ruby-saml-to-1-7-2.yml
new file mode 100644
index 00000000000..7335d313510
--- /dev/null
+++ b/changelogs/unreleased/43806-update-ruby-saml-to-1-7-2.yml
@@ -0,0 +1,5 @@
+---
+title: Update ruby-saml to 1.7.2 and omniauth-saml to 1.10.0
+merge_request: 17734
+author: Takuya Noguchi
+type: security
diff --git a/changelogs/unreleased/43949-verify-job-artifacts.yml b/changelogs/unreleased/43949-verify-job-artifacts.yml
new file mode 100644
index 00000000000..45e1916ae17
--- /dev/null
+++ b/changelogs/unreleased/43949-verify-job-artifacts.yml
@@ -0,0 +1,5 @@
+---
+title: Implement foreground verification of CI artifacts
+merge_request: 17578
+author:
+type: added
diff --git a/changelogs/unreleased/44024-fix-table-extra-column.yml b/changelogs/unreleased/44024-fix-table-extra-column.yml
new file mode 100644
index 00000000000..92c354a0844
--- /dev/null
+++ b/changelogs/unreleased/44024-fix-table-extra-column.yml
@@ -0,0 +1,5 @@
+---
+title: Fix markdown table showing extra column
+merge_request: 17669
+author:
+type: fixed
diff --git a/changelogs/unreleased/44139-fix-issue-boards-dup-keys.yml b/changelogs/unreleased/44139-fix-issue-boards-dup-keys.yml
new file mode 100644
index 00000000000..dd5f2f06d6c
--- /dev/null
+++ b/changelogs/unreleased/44139-fix-issue-boards-dup-keys.yml
@@ -0,0 +1,6 @@
+---
+title: Use object ID to prevent duplicate keys Vue warning on Issue Boards page during
+ development
+merge_request: 17682
+author:
+type: other
diff --git a/changelogs/unreleased/44149-issue-comment-buttons.yml b/changelogs/unreleased/44149-issue-comment-buttons.yml
new file mode 100644
index 00000000000..c874c0d3d66
--- /dev/null
+++ b/changelogs/unreleased/44149-issue-comment-buttons.yml
@@ -0,0 +1,5 @@
+---
+title: Fix broken loading state for close issue button
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/44160-update-foreman-to-0-84-0.yml b/changelogs/unreleased/44160-update-foreman-to-0-84-0.yml
new file mode 100644
index 00000000000..990d188eb78
--- /dev/null
+++ b/changelogs/unreleased/44160-update-foreman-to-0-84-0.yml
@@ -0,0 +1,5 @@
+---
+title: Update foreman from 0.78.0 to 0.84.0
+merge_request: 17690
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/44191-reduce-redis-usage-from-merge-request-diffs-caching.yml b/changelogs/unreleased/44191-reduce-redis-usage-from-merge-request-diffs-caching.yml
new file mode 100644
index 00000000000..8fdca6eec83
--- /dev/null
+++ b/changelogs/unreleased/44191-reduce-redis-usage-from-merge-request-diffs-caching.yml
@@ -0,0 +1,5 @@
+---
+title: Stop caching highlighted diffs in Redis unnecessarily
+merge_request: 17746
+author:
+type: performance
diff --git a/changelogs/unreleased/44235-update-knapsack-to-1-16-0.yml b/changelogs/unreleased/44235-update-knapsack-to-1-16-0.yml
new file mode 100644
index 00000000000..265d36b763f
--- /dev/null
+++ b/changelogs/unreleased/44235-update-knapsack-to-1-16-0.yml
@@ -0,0 +1,5 @@
+---
+title: Update knapsack to 1.16.0
+merge_request: 17735
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/adamco-gitlab-ce-move-issue-command.yml b/changelogs/unreleased/adamco-gitlab-ce-move-issue-command.yml
new file mode 100644
index 00000000000..3b057373e7d
--- /dev/null
+++ b/changelogs/unreleased/adamco-gitlab-ce-move-issue-command.yml
@@ -0,0 +1,5 @@
+---
+title: Add slash command for moving issues
+merge_request:
+author: Adam Pahlevi
+type: added
diff --git a/changelogs/unreleased/add-indexes-to-todos-for-heavy-users-like-sean.yml b/changelogs/unreleased/add-indexes-to-todos-for-heavy-users-like-sean.yml
new file mode 100644
index 00000000000..f0e5103a9d9
--- /dev/null
+++ b/changelogs/unreleased/add-indexes-to-todos-for-heavy-users-like-sean.yml
@@ -0,0 +1,5 @@
+---
+title: Add partial indexes on todos to handle users with many todos
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/bvl-import-zip-multiple-assignees.yml b/changelogs/unreleased/bvl-import-zip-multiple-assignees.yml
new file mode 100644
index 00000000000..86bd5faf8ed
--- /dev/null
+++ b/changelogs/unreleased/bvl-import-zip-multiple-assignees.yml
@@ -0,0 +1,5 @@
+---
+title: Fix importing multiple assignees from GitLab export
+merge_request: 17718
+author:
+type: fixed
diff --git a/changelogs/unreleased/cache-refactor.yml b/changelogs/unreleased/cache-refactor.yml
new file mode 100644
index 00000000000..dec7a0392a5
--- /dev/null
+++ b/changelogs/unreleased/cache-refactor.yml
@@ -0,0 +1,5 @@
+---
+title: Cache MergeRequests can_be_resolved_in_ui? git operations
+merge_request: 17589
+author:
+type: performance
diff --git a/changelogs/unreleased/ce-jej-github-project-service-for-ci.yml b/changelogs/unreleased/ce-jej-github-project-service-for-ci.yml
new file mode 100644
index 00000000000..6102b7ecd93
--- /dev/null
+++ b/changelogs/unreleased/ce-jej-github-project-service-for-ci.yml
@@ -0,0 +1,5 @@
+---
+title: Hook data for pipelines includes detailed_status
+merge_request: 17607
+author:
+type: changed
diff --git a/changelogs/unreleased/ce-jej-integrations-can-hide-trigger-checkboxes.yml b/changelogs/unreleased/ce-jej-integrations-can-hide-trigger-checkboxes.yml
new file mode 100644
index 00000000000..771df06e7a6
--- /dev/null
+++ b/changelogs/unreleased/ce-jej-integrations-can-hide-trigger-checkboxes.yml
@@ -0,0 +1,6 @@
+---
+title: Avoid showing unnecessary Trigger checkboxes for project Integrations with
+ only one event
+merge_request: 17607
+author:
+type: changed
diff --git a/changelogs/unreleased/fix-42459---in-branch.yml b/changelogs/unreleased/fix-42459---in-branch.yml
new file mode 100644
index 00000000000..26cc2046206
--- /dev/null
+++ b/changelogs/unreleased/fix-42459---in-branch.yml
@@ -0,0 +1,5 @@
+---
+title: Fix relative uri when "#" is in branch name
+merge_request:
+author: Jan
+type: fixed
diff --git a/changelogs/unreleased/fix-dropzone-project-show.yml b/changelogs/unreleased/fix-dropzone-project-show.yml
new file mode 100644
index 00000000000..660780812d8
--- /dev/null
+++ b/changelogs/unreleased/fix-dropzone-project-show.yml
@@ -0,0 +1,5 @@
+---
+title: Fix file upload on project show page
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-emoji-popup.yml b/changelogs/unreleased/fix-emoji-popup.yml
new file mode 100644
index 00000000000..c81d061a5d7
--- /dev/null
+++ b/changelogs/unreleased/fix-emoji-popup.yml
@@ -0,0 +1,5 @@
+---
+title: Hide emoji popup after multiple spaces
+merge_request:
+author: Jan Beckmann
+type: fixed
diff --git a/changelogs/unreleased/fix-mattermost-delete-team.yml b/changelogs/unreleased/fix-mattermost-delete-team.yml
new file mode 100644
index 00000000000..d14ae023114
--- /dev/null
+++ b/changelogs/unreleased/fix-mattermost-delete-team.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed group deletion linked to Mattermost
+merge_request: 16209
+author: Julien Millau
+type: fixed
diff --git a/changelogs/unreleased/fix-sm-fix_pages_worker.yml b/changelogs/unreleased/fix-sm-fix_pages_worker.yml
deleted file mode 100644
index 190c7d3e83e..00000000000
--- a/changelogs/unreleased/fix-sm-fix_pages_worker.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix pages flaky failure by reloading stale object
-merge_request: 17522
-author:
-type: fixed
diff --git a/changelogs/unreleased/grpc-unavailable-restart.yml b/changelogs/unreleased/grpc-unavailable-restart.yml
deleted file mode 100644
index 5ce08d66004..00000000000
--- a/changelogs/unreleased/grpc-unavailable-restart.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Restart Unicorn and Sidekiq when GRPC throws 14:Endpoint read failed
-merge_request: 17293
-author:
-type: fixed
diff --git a/changelogs/unreleased/issue-edit-shortcut.yml b/changelogs/unreleased/issue-edit-shortcut.yml
deleted file mode 100644
index 2b29b2bc03f..00000000000
--- a/changelogs/unreleased/issue-edit-shortcut.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixed issue edit shortcut not opening edit form
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/jej-commit-api-tracks-lfs.yml b/changelogs/unreleased/jej-commit-api-tracks-lfs.yml
new file mode 100644
index 00000000000..8284abf9f28
--- /dev/null
+++ b/changelogs/unreleased/jej-commit-api-tracks-lfs.yml
@@ -0,0 +1,5 @@
+---
+title: Create commit API and Web IDE obey LFS filters
+merge_request: 16718
+author:
+type: fixed
diff --git a/changelogs/unreleased/jivl-realtime-update-adding-file.yml b/changelogs/unreleased/jivl-realtime-update-adding-file.yml
new file mode 100644
index 00000000000..df1bdb1648d
--- /dev/null
+++ b/changelogs/unreleased/jivl-realtime-update-adding-file.yml
@@ -0,0 +1,5 @@
+---
+title: Add realtime pipeline status for adding/viewing files
+merge_request: 17705
+author:
+type: other
diff --git a/changelogs/unreleased/merge-request-widget-source-branch-improvements.yml b/changelogs/unreleased/merge-request-widget-source-branch-improvements.yml
new file mode 100644
index 00000000000..942eb6062fd
--- /dev/null
+++ b/changelogs/unreleased/merge-request-widget-source-branch-improvements.yml
@@ -0,0 +1,6 @@
+---
+title: Fixes remove source branch checkbox being visible when user cannot remove the
+ branch
+merge_request:
+author:
+type: changed
diff --git a/changelogs/unreleased/minimal-fix-for-artifacts-service.yml b/changelogs/unreleased/minimal-fix-for-artifacts-service.yml
deleted file mode 100644
index 11f5bc17759..00000000000
--- a/changelogs/unreleased/minimal-fix-for-artifacts-service.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Prevent trace artifact migration to incur data loss
-merge_request: 17313
-author:
-type: fixed
diff --git a/changelogs/unreleased/mk-fix-error-code-for-repo-does-not-exist.yml b/changelogs/unreleased/mk-fix-error-code-for-repo-does-not-exist.yml
deleted file mode 100644
index a761d610da1..00000000000
--- a/changelogs/unreleased/mk-fix-error-code-for-repo-does-not-exist.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Return a 404 instead of 403 if the repository does not exist on disk
-merge_request: 17341
-author:
-type: fixed
diff --git a/changelogs/unreleased/osw-43951-single-batch-blob-request-to-gitaly.yml b/changelogs/unreleased/osw-43951-single-batch-blob-request-to-gitaly.yml
new file mode 100644
index 00000000000..34f834298b6
--- /dev/null
+++ b/changelogs/unreleased/osw-43951-single-batch-blob-request-to-gitaly.yml
@@ -0,0 +1,5 @@
+---
+title: Submit a single batch blob RPC to Gitaly per HTTP request when viewing diffs
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/osw-stop-recalculating-merge-base-on-mr-loading.yml b/changelogs/unreleased/osw-stop-recalculating-merge-base-on-mr-loading.yml
new file mode 100644
index 00000000000..1673e1d3658
--- /dev/null
+++ b/changelogs/unreleased/osw-stop-recalculating-merge-base-on-mr-loading.yml
@@ -0,0 +1,5 @@
+---
+title: Avoid re-fetching merge-base SHA from Gitaly unnecessarily
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/refactor-move-assignee-title-vue-component.yml b/changelogs/unreleased/refactor-move-assignee-title-vue-component.yml
new file mode 100644
index 00000000000..f6521339c39
--- /dev/null
+++ b/changelogs/unreleased/refactor-move-assignee-title-vue-component.yml
@@ -0,0 +1,5 @@
+---
+title: Move AssigneeTitle vue component
+merge_request: 17397
+author: George Tsiolis
+type: performance
diff --git a/changelogs/unreleased/refactor-move-mr-widget-memory-usage-and-graph-components.yml b/changelogs/unreleased/refactor-move-mr-widget-memory-usage-and-graph-components.yml
new file mode 100644
index 00000000000..96e63343963
--- /dev/null
+++ b/changelogs/unreleased/refactor-move-mr-widget-memory-usage-and-graph-components.yml
@@ -0,0 +1,5 @@
+---
+title: Move MemoryGraph and MemoryUsage vue components
+merge_request: 17533
+author: George Tsiolis
+type: performance
diff --git a/changelogs/unreleased/refactor-move-mr-widget-nothing-to-merge-vue-component.yml b/changelogs/unreleased/refactor-move-mr-widget-nothing-to-merge-vue-component.yml
new file mode 100644
index 00000000000..dc8ff95dc27
--- /dev/null
+++ b/changelogs/unreleased/refactor-move-mr-widget-nothing-to-merge-vue-component.yml
@@ -0,0 +1,5 @@
+---
+title: Move NothingToMerge vue component
+merge_request: 17544
+author: George Tsiolis
+type: performance
diff --git a/changelogs/unreleased/refactor-move-mr-widget-unresolved-discussions-vue-component.yml b/changelogs/unreleased/refactor-move-mr-widget-unresolved-discussions-vue-component.yml
new file mode 100644
index 00000000000..a31f1f372a8
--- /dev/null
+++ b/changelogs/unreleased/refactor-move-mr-widget-unresolved-discussions-vue-component.yml
@@ -0,0 +1,5 @@
+---
+title: Move UnresolvedDiscussions vue component
+merge_request: 17538
+author: George Tsiolis
+type: performance
diff --git a/changelogs/unreleased/remove-projects-finder-from-todos-finder.yml b/changelogs/unreleased/remove-projects-finder-from-todos-finder.yml
deleted file mode 100644
index 0a3fc751edb..00000000000
--- a/changelogs/unreleased/remove-projects-finder-from-todos-finder.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Don't use ProjectsFinder in TodosFinder
-merge_request:
-author:
-type: performance
diff --git a/changelogs/unreleased/revert-project-visibility-changes.yml b/changelogs/unreleased/revert-project-visibility-changes.yml
deleted file mode 100644
index df44fdb79b1..00000000000
--- a/changelogs/unreleased/revert-project-visibility-changes.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Revert Project.public_or_visible_to_user changes and only apply to snippets
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-cache-column-exists.yml b/changelogs/unreleased/sh-cache-column-exists.yml
new file mode 100644
index 00000000000..8bc648f2b32
--- /dev/null
+++ b/changelogs/unreleased/sh-cache-column-exists.yml
@@ -0,0 +1,5 @@
+---
+title: Cache column_exists? for application settings
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-cache-table-exists.yml b/changelogs/unreleased/sh-cache-table-exists.yml
new file mode 100644
index 00000000000..37407b2a005
--- /dev/null
+++ b/changelogs/unreleased/sh-cache-table-exists.yml
@@ -0,0 +1,5 @@
+---
+title: Cache table_exists?('application_settings') to reduce repeated schema reloads
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-optimize-admin-projects-page.yml b/changelogs/unreleased/sh-optimize-admin-projects-page.yml
new file mode 100644
index 00000000000..242ea758dab
--- /dev/null
+++ b/changelogs/unreleased/sh-optimize-admin-projects-page.yml
@@ -0,0 +1,5 @@
+---
+title: Fix timeouts loading /admin/projects page
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/tc-api-fix-expose_url.yml b/changelogs/unreleased/tc-api-fix-expose_url.yml
new file mode 100644
index 00000000000..c701f64d6bf
--- /dev/null
+++ b/changelogs/unreleased/tc-api-fix-expose_url.yml
@@ -0,0 +1,5 @@
+---
+title: Ensure the API returns https links when https is configured
+merge_request: 17681
+author:
+type: fixed
diff --git a/changelogs/unreleased/unassign-when-leaving.yml b/changelogs/unreleased/unassign-when-leaving.yml
new file mode 100644
index 00000000000..c00a87b1749
--- /dev/null
+++ b/changelogs/unreleased/unassign-when-leaving.yml
@@ -0,0 +1,5 @@
+---
+title: Don't delete todos or unassign issues and MRs when a user leaves a project
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/zj-gitaly-encoding-issue.yml b/changelogs/unreleased/zj-gitaly-encoding-issue.yml
deleted file mode 100644
index 073d8f38e4b..00000000000
--- a/changelogs/unreleased/zj-gitaly-encoding-issue.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Encode branch name as binary before creating a RPC request to copy attributes
-merge_request: 17291
-author:
-type: fixed
diff --git a/config/application.rb b/config/application.rb
index 74fe3e439ed..0ff95e33a9c 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -70,7 +70,6 @@ module Gitlab
# - Webhook URLs (:hook)
# - Sentry DSN (:sentry_dsn)
# - Deploy keys (:key)
- # - Secret variable values (:value)
config.filter_parameters += [/token$/, /password/, /secret/]
config.filter_parameters += %i(
certificate
@@ -82,7 +81,6 @@ module Gitlab
sentry_dsn
trace
variables
- value
)
# Enable escaping HTML in JSON.
@@ -117,6 +115,12 @@ module Gitlab
config.assets.precompile << "test.css"
config.assets.precompile << "locale/**/app.js"
+ # Import gitlab-svgs directly from vendored directory
+ config.assets.paths << "#{config.root}/node_modules/@gitlab-org/gitlab-svgs/dist"
+ config.assets.precompile << "icons.svg"
+ config.assets.precompile << "icons.json"
+ config.assets.precompile << "illustrations/*.svg"
+
# Version of your assets, change this if you want to expire all your assets
config.assets.version = '1.0'
@@ -194,4 +198,10 @@ module Gitlab
Gitlab::Routing.add_helpers(MilestonesRoutingHelper)
end
end
+
+ # This method is used for smooth upgrading from the current Rails 4.x to Rails 5.0.
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/14286
+ def self.rails5?
+ ENV["RAILS5"].in?(%w[1 true])
+ end
end
diff --git a/config/initializers/backtrace_silencers.rb b/config/initializers/backtrace_silencers.rb
index 59385cdf379..58941aae1b0 100644
--- a/config/initializers/backtrace_silencers.rb
+++ b/config/initializers/backtrace_silencers.rb
@@ -1,7 +1,2 @@
-# Be sure to restart your server when you modify this file.
-
-# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
-# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
-
-# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
-# Rails.backtrace_cleaner.remove_silencers!
+Rails.backtrace_cleaner.remove_silencers!
+Rails.backtrace_cleaner.add_silencer { |line| line !~ Gitlab::APP_DIRS_PATTERN }
diff --git a/config/initializers/peek.rb b/config/initializers/peek.rb
index 11759801112..ba04a2bf5fa 100644
--- a/config/initializers/peek.rb
+++ b/config/initializers/peek.rb
@@ -16,11 +16,11 @@ else
end
Peek.into PEEK_DB_VIEW
+Peek.into Peek::Views::Gitaly
+Peek.into Peek::Views::Rblineprof
Peek.into Peek::Views::Redis
Peek.into Peek::Views::Sidekiq
-Peek.into Peek::Views::Rblineprof
Peek.into Peek::Views::GC
-Peek.into Peek::Views::Gitaly
# rubocop:disable Naming/ClassAndModuleCamelCase
class PEEK_DB_CLIENT
diff --git a/config/routes/git_http.rb b/config/routes/git_http.rb
index ff51823897d..ec5c68f81df 100644
--- a/config/routes/git_http.rb
+++ b/config/routes/git_http.rb
@@ -40,7 +40,7 @@ scope(path: '*namespace_id/:project_id',
# /info/refs?service=git-receive-pack, but nothing else.
#
git_http_handshake = lambda do |request|
- ProjectUrlConstrainer.new.matches?(request) &&
+ ::Constraints::ProjectUrlConstrainer.new.matches?(request) &&
(request.query_string.blank? ||
request.query_string.match(/\Aservice=git-(upload|receive)-pack\z/))
end
diff --git a/config/routes/group.rb b/config/routes/group.rb
index 710f12e33ad..d89a714c7d6 100644
--- a/config/routes/group.rb
+++ b/config/routes/group.rb
@@ -1,10 +1,8 @@
-require 'constraints/group_url_constrainer'
-
resources :groups, only: [:index, :new, :create] do
post :preview_markdown
end
-constraints(GroupUrlConstrainer.new) do
+constraints(::Constraints::GroupUrlConstrainer.new) do
scope(path: 'groups/*id',
controller: :groups,
constraints: { id: Gitlab::PathRegex.full_namespace_route_regex, format: /(html|json|atom)/ }) do
diff --git a/config/routes/project.rb b/config/routes/project.rb
index cb46c439415..c803737d40b 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -1,10 +1,8 @@
-require 'constraints/project_url_constrainer'
-
resources :projects, only: [:index, :new, :create]
draw :git_http
-constraints(ProjectUrlConstrainer.new) do
+constraints(::Constraints::ProjectUrlConstrainer.new) do
# If the route has a wildcard segment, the segment has a regex constraint,
# the segment is potentially followed by _another_ wildcard segment, and
# the `format` option is not set to false, we need to specify that
@@ -69,7 +67,7 @@ constraints(ProjectUrlConstrainer.new) do
end
end
- resources :services, constraints: { id: %r{[^/]+} }, only: [:index, :edit, :update] do
+ resources :services, constraints: { id: %r{[^/]+} }, only: [:edit, :update] do
member do
put :test
end
@@ -132,7 +130,7 @@ constraints(ProjectUrlConstrainer.new) do
post :bulk_update
end
- resources :discussions, only: [], constraints: { id: /\h{40}/ } do
+ resources :discussions, only: [:show], constraints: { id: /\h{40}/ } do
member do
post :resolve
delete :resolve, action: :unresolve
diff --git a/config/routes/user.rb b/config/routes/user.rb
index 733a3f6ce9a..57fb37530bb 100644
--- a/config/routes/user.rb
+++ b/config/routes/user.rb
@@ -1,5 +1,3 @@
-require 'constraints/user_url_constrainer'
-
devise_for :users, controllers: { omniauth_callbacks: :omniauth_callbacks,
registrations: :registrations,
passwords: :passwords,
@@ -35,7 +33,7 @@ scope(constraints: { username: Gitlab::PathRegex.root_namespace_route_regex }) d
get '/u/:username/contributed', to: redirect('users/%{username}/contributed')
end
-constraints(UserUrlConstrainer.new) do
+constraints(::Constraints::UserUrlConstrainer.new) do
# Get all keys of user
get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: Gitlab::PathRegex.root_namespace_route_regex }
diff --git a/config/svg.config.js b/config/svg.config.js
deleted file mode 100644
index bb27f0caeef..00000000000
--- a/config/svg.config.js
+++ /dev/null
@@ -1,48 +0,0 @@
-/* eslint-disable no-commonjs */
-const path = require('path');
-const fs = require('fs');
-
-const sourcePath = path.join('node_modules', '@gitlab-org/gitlab-svgs', 'dist');
-const sourcePathIllustrations = path.join('node_modules', '@gitlab-org/gitlab-svgs', 'dist', 'illustrations');
-const destPath = path.normalize(path.join('app', 'assets', 'images'));
-
-// Actual Task copying the 2 files + all illustrations
-copyFileSync(path.join(sourcePath, 'icons.svg'), destPath);
-copyFileSync(path.join(sourcePath, 'icons.json'), destPath);
-copyFolderRecursiveSync(sourcePathIllustrations, destPath);
-
-// Helper Functions
-function copyFileSync(source, target) {
- var targetFile = target;
- //if target is a directory a new file with the same name will be created
- if (fs.existsSync(target)) {
- if (fs.lstatSync(target).isDirectory()) {
- targetFile = path.join(target, path.basename(source));
- }
- }
- console.log(`Copy SVG File : ${targetFile}`);
- fs.writeFileSync(targetFile, fs.readFileSync(source));
-}
-
-function copyFolderRecursiveSync(source, target) {
- var files = [];
-
- //check if folder needs to be created or integrated
- var targetFolder = path.join(target, path.basename(source));
- if (!fs.existsSync(targetFolder)) {
- fs.mkdirSync(targetFolder);
- }
-
- //copy
- if (fs.lstatSync(source).isDirectory()) {
- files = fs.readdirSync(source);
- files.forEach(function (file) {
- var curSource = path.join(source, file);
- if (fs.lstatSync(curSource).isDirectory()) {
- copyFolderRecursiveSync(curSource, targetFolder);
- } else {
- copyFileSync(curSource, targetFolder);
- }
- });
- }
-}
diff --git a/db/fixtures/development/22_labeled_issues_seed.rb b/db/fixtures/development/22_labeled_issues_seed.rb
new file mode 100644
index 00000000000..3730e9c7958
--- /dev/null
+++ b/db/fixtures/development/22_labeled_issues_seed.rb
@@ -0,0 +1,103 @@
+# Creates a project with labeled issues for an user.
+# Run this single seed file using: rake db:seed_fu FILTER=labeled USER_ID=74.
+# If an USER_ID is not provided it will use the last created user.
+require './spec/support/sidekiq'
+
+class Gitlab::Seeder::LabeledIssues
+ include ::Gitlab::Utils
+
+ def initialize(user)
+ @user = user
+ end
+
+ def seed!
+ Sidekiq::Testing.inline! do
+ group = create_group
+
+ create_projects(group)
+ create_labels(group)
+ create_issues(group)
+ end
+
+ print '.'
+ end
+
+ private
+
+ def create_group
+ group_name = "group_of_#{@user.username}_#{SecureRandom.hex(4)}"
+
+ group_params = {
+ name: group_name,
+ path: group_name,
+ description: FFaker::Lorem.sentence
+ }
+
+ Groups::CreateService.new(@user, group_params).execute
+ end
+
+ def create_projects(group)
+ 5.times do
+ project_name = "project_#{SecureRandom.hex(6)}"
+
+ params = {
+ namespace_id: group.id,
+ name: project_name,
+ description: FFaker::Lorem.sentence,
+ visibility_level: Gitlab::VisibilityLevel.values.sample
+ }
+
+ Projects::CreateService.new(@user, params).execute
+ end
+ end
+
+ def create_labels(group)
+ group.projects.each do |project|
+ 5.times do
+ label_title = FFaker::Vehicle.model
+ Labels::CreateService.new(title: label_title, color: "#69D100").execute(project: project)
+ end
+ end
+
+ 10.times do
+ label_title = FFaker::Product.brand
+ Labels::CreateService.new(title: label_title).execute(group: group)
+ end
+ end
+
+ def create_issues(group)
+ # Get only group labels
+ group_labels =
+ LabelsFinder.new(@user, group_id: group.id).execute.where.not(group_id: nil)
+
+ group.projects.each do |project|
+ label_ids = project.labels.pluck(:id).sample(5)
+ label_ids.push(*group.labels.sample(4))
+
+ 20.times do
+ issue_params = {
+ title: FFaker::Lorem.sentence(6),
+ description: FFaker::Lorem.sentence,
+ state: 'opened',
+ label_ids: label_ids
+
+ }
+
+ Issues::CreateService.new(project, @user, issue_params).execute if project.project_feature.present?
+ end
+ end
+ end
+end
+
+Gitlab::Seeder.quiet do
+ user_id = ENV['USER_ID']
+
+ user =
+ if user_id.present?
+ User.find(user_id)
+ else
+ User.last
+ end
+
+ Gitlab::Seeder::LabeledIssues.new(user).seed!
+end
diff --git a/db/migrate/20180223120443_create_user_interacted_projects_table.rb b/db/migrate/20180223120443_create_user_interacted_projects_table.rb
index 20749940b1e..8da8cf68088 100644
--- a/db/migrate/20180223120443_create_user_interacted_projects_table.rb
+++ b/db/migrate/20180223120443_create_user_interacted_projects_table.rb
@@ -3,13 +3,15 @@ class CreateUserInteractedProjectsTable < ActiveRecord::Migration
DOWNTIME = false
- disable_ddl_transaction!
+ INDEX_NAME = 'user_interacted_projects_non_unique_index'
def up
create_table :user_interacted_projects, id: false do |t|
t.references :user, null: false
t.references :project, null: false
end
+
+ add_index :user_interacted_projects, [:project_id, :user_id], name: INDEX_NAME
end
def down
diff --git a/db/migrate/20180302152117_ensure_foreign_keys_on_clusters_applications.rb b/db/migrate/20180302152117_ensure_foreign_keys_on_clusters_applications.rb
new file mode 100644
index 00000000000..8298979e96a
--- /dev/null
+++ b/db/migrate/20180302152117_ensure_foreign_keys_on_clusters_applications.rb
@@ -0,0 +1,50 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class EnsureForeignKeysOnClustersApplications < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ existing = Clusters::Cluster
+ .joins(:application_ingress)
+ .where('clusters.id = clusters_applications_ingress.cluster_id')
+
+ Clusters::Applications::Ingress.where('NOT EXISTS (?)', existing).in_batches do |batch|
+ batch.delete_all
+ end
+
+ unless foreign_keys_for(:clusters_applications_ingress, :cluster_id).any?
+ add_concurrent_foreign_key :clusters_applications_ingress, :clusters,
+ column: :cluster_id,
+ on_delete: :cascade
+ end
+
+ existing = Clusters::Cluster
+ .joins(:application_prometheus)
+ .where('clusters.id = clusters_applications_prometheus.cluster_id')
+
+ Clusters::Applications::Ingress.where('NOT EXISTS (?)', existing).in_batches do |batch|
+ batch.delete_all
+ end
+
+ unless foreign_keys_for(:clusters_applications_prometheus, :cluster_id).any?
+ add_concurrent_foreign_key :clusters_applications_prometheus, :clusters,
+ column: :cluster_id,
+ on_delete: :cascade
+ end
+ end
+
+ def down
+ if foreign_keys_for(:clusters_applications_ingress, :cluster_id).any?
+ remove_foreign_key :clusters_applications_ingress, column: :cluster_id
+ end
+
+ if foreign_keys_for(:clusters_applications_prometheus, :cluster_id).any?
+ remove_foreign_key :clusters_applications_prometheus, column: :cluster_id
+ end
+ end
+end
diff --git a/db/migrate/20180304204842_clean_commits_count_migration.rb b/db/migrate/20180304204842_clean_commits_count_migration.rb
deleted file mode 100644
index ace4c6aa1cf..00000000000
--- a/db/migrate/20180304204842_clean_commits_count_migration.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-class CleanCommitsCountMigration < ActiveRecord::Migration
- include Gitlab::Database::MigrationHelpers
-
- DOWNTIME = false
-
- disable_ddl_transaction!
-
- def up
- Gitlab::BackgroundMigration.steal('AddMergeRequestDiffCommitsCount')
- end
-
- def down
- end
-end
diff --git a/db/migrate/20180306134842_add_missing_indexes_acts_as_taggable_on_engine.rb b/db/migrate/20180306134842_add_missing_indexes_acts_as_taggable_on_engine.rb
new file mode 100644
index 00000000000..06e402adcd7
--- /dev/null
+++ b/db/migrate/20180306134842_add_missing_indexes_acts_as_taggable_on_engine.rb
@@ -0,0 +1,21 @@
+# This migration comes from acts_as_taggable_on_engine (originally 6)
+#
+# It has been modified to handle no-downtime GitLab migrations. Several
+# indexes have been removed since they are not needed for GitLab.
+class AddMissingIndexesActsAsTaggableOnEngine < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :taggings, :tag_id unless index_exists? :taggings, :tag_id
+ add_concurrent_index :taggings, [:taggable_id, :taggable_type] unless index_exists? :taggings, [:taggable_id, :taggable_type]
+ end
+
+ def down
+ remove_concurrent_index :taggings, :tag_id
+ remove_concurrent_index :taggings, [:taggable_id, :taggable_type]
+ end
+end
diff --git a/db/migrate/20180308052825_add_section_name_id_index_on_ci_build_trace_sections.rb b/db/migrate/20180308052825_add_section_name_id_index_on_ci_build_trace_sections.rb
new file mode 100644
index 00000000000..b616cc2fd30
--- /dev/null
+++ b/db/migrate/20180308052825_add_section_name_id_index_on_ci_build_trace_sections.rb
@@ -0,0 +1,23 @@
+class AddSectionNameIdIndexOnCiBuildTraceSections < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+ INDEX_NAME = 'index_ci_build_trace_sections_on_section_name_id'
+
+ disable_ddl_transaction!
+
+ def up
+ # MySQL may already have this as a foreign key
+ unless index_exists?(:ci_build_trace_sections, :section_name_id, name: INDEX_NAME)
+ add_concurrent_index :ci_build_trace_sections, :section_name_id, name: INDEX_NAME
+ end
+ end
+
+ def down
+ # We cannot remove index for MySQL because it's needed for foreign key
+ if Gitlab::Database.postgresql?
+ remove_concurrent_index :ci_build_trace_sections, :section_name_id, name: INDEX_NAME
+ end
+ end
+end
diff --git a/db/migrate/20180309121820_reschedule_commits_count_for_merge_request_diff.rb b/db/migrate/20180309121820_reschedule_commits_count_for_merge_request_diff.rb
new file mode 100644
index 00000000000..990759104b0
--- /dev/null
+++ b/db/migrate/20180309121820_reschedule_commits_count_for_merge_request_diff.rb
@@ -0,0 +1,30 @@
+class RescheduleCommitsCountForMergeRequestDiff < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ MIGRATION = 'AddMergeRequestDiffCommitsCount'.freeze
+ BATCH_SIZE = 5000
+ DELAY_INTERVAL = 5.minutes.to_i
+
+ class MergeRequestDiff < ActiveRecord::Base
+ self.table_name = 'merge_request_diffs'
+
+ include ::EachBatch
+ end
+
+ disable_ddl_transaction!
+
+ def up
+ say 'Populating the MergeRequestDiff `commits_count` (reschedule)'
+
+ execute("SET statement_timeout TO '60s'") if Gitlab::Database.postgresql?
+
+ MergeRequestDiff.where(commits_count: nil).each_batch(of: BATCH_SIZE) do |relation, index|
+ start_id, end_id = relation.pluck('MIN(id), MAX(id)').first
+ delay = index * DELAY_INTERVAL
+
+ BackgroundMigrationWorker.perform_in(delay, MIGRATION, [start_id, end_id])
+ end
+ end
+end
diff --git a/db/migrate/20180309160427_add_partial_indexes_on_todos.rb b/db/migrate/20180309160427_add_partial_indexes_on_todos.rb
new file mode 100644
index 00000000000..18a5c69df1b
--- /dev/null
+++ b/db/migrate/20180309160427_add_partial_indexes_on_todos.rb
@@ -0,0 +1,28 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddPartialIndexesOnTodos < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ INDEX_NAME_PENDING="index_todos_on_user_id_and_id_pending"
+ INDEX_NAME_DONE="index_todos_on_user_id_and_id_done"
+
+ def up
+ unless index_exists?(:todos, [:user_id, :id], name: INDEX_NAME_PENDING)
+ add_concurrent_index(:todos, [:user_id, :id], where: "state='pending'", name: INDEX_NAME_PENDING)
+ end
+ unless index_exists?(:todos, [:user_id, :id], name: INDEX_NAME_DONE)
+ add_concurrent_index(:todos, [:user_id, :id], where: "state='done'", name: INDEX_NAME_DONE)
+ end
+ end
+
+ def down
+ remove_concurrent_index(:todos, [:user_id, :id], where: "state='pending'", name: INDEX_NAME_PENDING)
+ remove_concurrent_index(:todos, [:user_id, :id], where: "state='done'", name: INDEX_NAME_DONE)
+ end
+end
diff --git a/db/post_migrate/20180223124427_build_user_interacted_projects_table.rb b/db/post_migrate/20180223124427_build_user_interacted_projects_table.rb
index 5e729b1aa53..d1a29a5c71b 100644
--- a/db/post_migrate/20180223124427_build_user_interacted_projects_table.rb
+++ b/db/post_migrate/20180223124427_build_user_interacted_projects_table.rb
@@ -1,9 +1,14 @@
+require_relative '../migrate/20180223120443_create_user_interacted_projects_table.rb'
+# rubocop:disable AddIndex
+# rubocop:disable AddConcurrentForeignKey
class BuildUserInteractedProjectsTable < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
+ UNIQUE_INDEX_NAME = 'index_user_interacted_projects_on_project_id_and_user_id'
+
disable_ddl_transaction!
def up
@@ -13,16 +18,8 @@ class BuildUserInteractedProjectsTable < ActiveRecord::Migration
MysqlStrategy.new
end.up
- unless index_exists?(:user_interacted_projects, [:project_id, :user_id])
- add_concurrent_index :user_interacted_projects, [:project_id, :user_id], unique: true
- end
-
- unless foreign_key_exists?(:user_interacted_projects, :user_id)
- add_concurrent_foreign_key :user_interacted_projects, :users, column: :user_id, on_delete: :cascade
- end
-
- unless foreign_key_exists?(:user_interacted_projects, :project_id)
- add_concurrent_foreign_key :user_interacted_projects, :projects, column: :project_id, on_delete: :cascade
+ if index_exists_by_name?(:user_interacted_projects, CreateUserInteractedProjectsTable::INDEX_NAME)
+ remove_concurrent_index_by_name :user_interacted_projects, CreateUserInteractedProjectsTable::INDEX_NAME
end
end
@@ -37,31 +34,16 @@ class BuildUserInteractedProjectsTable < ActiveRecord::Migration
remove_foreign_key :user_interacted_projects, :projects
end
- if index_exists_by_name?(:user_interacted_projects, 'index_user_interacted_projects_on_project_id_and_user_id')
- remove_concurrent_index_by_name :user_interacted_projects, 'index_user_interacted_projects_on_project_id_and_user_id'
+ if index_exists_by_name?(:user_interacted_projects, UNIQUE_INDEX_NAME)
+ remove_concurrent_index_by_name :user_interacted_projects, UNIQUE_INDEX_NAME
end
- end
- private
-
- # Rails' index_exists? doesn't work when you only give it a table and index
- # name. As such we have to use some extra code to check if an index exists for
- # a given name.
- def index_exists_by_name?(table, index)
- indexes_for_table[table].include?(index)
- end
-
- def indexes_for_table
- @indexes_for_table ||= Hash.new do |hash, table_name|
- hash[table_name] = indexes(table_name).map(&:name)
+ unless index_exists_by_name?(:user_interacted_projects, CreateUserInteractedProjectsTable::INDEX_NAME)
+ add_concurrent_index :user_interacted_projects, [:project_id, :user_id], name: CreateUserInteractedProjectsTable::INDEX_NAME
end
end
- def foreign_key_exists?(table, column)
- foreign_keys(table).any? do |key|
- key.options[:column] == column.to_s
- end
- end
+ private
class PostgresStrategy < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
@@ -71,33 +53,86 @@ class BuildUserInteractedProjectsTable < ActiveRecord::Migration
def up
with_index(:events, [:author_id, :project_id], name: 'events_user_interactions_temp', where: 'project_id IS NOT NULL') do
- iteration = 0
- records = 0
- begin
- Rails.logger.info "Building user_interacted_projects table, batch ##{iteration}"
- result = execute <<~SQL
+ insert_missing_records
+
+ # Do this once without lock to speed up the second invocation
+ remove_duplicates
+ with_table_lock(:user_interacted_projects) do
+ remove_duplicates
+ create_unique_index
+ end
+
+ remove_without_project
+ with_table_lock(:user_interacted_projects, :projects) do
+ remove_without_project
+ create_fk :user_interacted_projects, :projects, :project_id
+ end
+
+ remove_without_user
+ with_table_lock(:user_interacted_projects, :users) do
+ remove_without_user
+ create_fk :user_interacted_projects, :users, :user_id
+ end
+ end
+
+ execute "ANALYZE user_interacted_projects"
+ end
+
+ private
+ def insert_missing_records
+ iteration = 0
+ records = 0
+ begin
+ Rails.logger.info "Building user_interacted_projects table, batch ##{iteration}"
+ result = execute <<~SQL
INSERT INTO user_interacted_projects (user_id, project_id)
SELECT e.user_id, e.project_id
FROM (SELECT DISTINCT author_id AS user_id, project_id FROM events WHERE project_id IS NOT NULL) AS e
LEFT JOIN user_interacted_projects ucp USING (user_id, project_id)
WHERE ucp.user_id IS NULL
LIMIT #{BATCH_SIZE}
- SQL
- iteration += 1
- records += result.cmd_tuples
- Rails.logger.info "Building user_interacted_projects table, batch ##{iteration} complete, created #{records} overall"
- Kernel.sleep(SLEEP_TIME) if result.cmd_tuples > 0
- rescue ActiveRecord::InvalidForeignKey => e
- Rails.logger.info "Retry on InvalidForeignKey: #{e}"
- retry
- end while result.cmd_tuples > 0
- end
+ SQL
+ iteration += 1
+ records += result.cmd_tuples
+ Rails.logger.info "Building user_interacted_projects table, batch ##{iteration} complete, created #{records} overall"
+ Kernel.sleep(SLEEP_TIME) if result.cmd_tuples > 0
+ end while result.cmd_tuples > 0
+ end
- execute "ANALYZE user_interacted_projects"
+ def remove_duplicates
+ execute <<~SQL
+ WITH numbered AS (select ctid, ROW_NUMBER() OVER (PARTITION BY (user_id, project_id)) as row_number, user_id, project_id from user_interacted_projects)
+ DELETE FROM user_interacted_projects WHERE ctid IN (SELECT ctid FROM numbered WHERE row_number > 1);
+ SQL
+ end
+ def remove_without_project
+ execute "DELETE FROM user_interacted_projects WHERE NOT EXISTS (SELECT 1 FROM projects WHERE id = user_interacted_projects.project_id)"
end
- private
+ def remove_without_user
+ execute "DELETE FROM user_interacted_projects WHERE NOT EXISTS (SELECT 1 FROM users WHERE id = user_interacted_projects.user_id)"
+ end
+
+ def create_fk(table, target, column)
+ return if foreign_key_exists?(table, column)
+
+ add_foreign_key table, target, column: column, on_delete: :cascade
+ end
+
+ def create_unique_index
+ return if index_exists_by_name?(:user_interacted_projects, UNIQUE_INDEX_NAME)
+
+ add_index :user_interacted_projects, [:project_id, :user_id], unique: true, name: UNIQUE_INDEX_NAME
+ end
+
+ # Protect table against concurrent data changes while still allowing reads
+ def with_table_lock(*tables)
+ ActiveRecord::Base.connection.transaction do
+ execute "LOCK TABLE #{tables.join(", ")} IN SHARE MODE"
+ yield
+ end
+ end
def with_index(*args)
add_concurrent_index(*args) unless index_exists?(*args)
@@ -118,7 +153,18 @@ class BuildUserInteractedProjectsTable < ActiveRecord::Migration
LEFT JOIN user_interacted_projects ucp USING (user_id, project_id)
WHERE ucp.user_id IS NULL
SQL
+
+ unless index_exists?(:user_interacted_projects, [:project_id, :user_id])
+ add_concurrent_index :user_interacted_projects, [:project_id, :user_id], unique: true, name: UNIQUE_INDEX_NAME
+ end
+
+ unless foreign_key_exists?(:user_interacted_projects, :user_id)
+ add_concurrent_foreign_key :user_interacted_projects, :users, column: :user_id, on_delete: :cascade
+ end
+
+ unless foreign_key_exists?(:user_interacted_projects, :project_id)
+ add_concurrent_foreign_key :user_interacted_projects, :projects, column: :project_id, on_delete: :cascade
+ end
end
end
-
end
diff --git a/db/schema.rb b/db/schema.rb
index b93e6fa594b..ab4370e2754 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20180307012445) do
+ActiveRecord::Schema.define(version: 20180309160427) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -264,6 +264,7 @@ ActiveRecord::Schema.define(version: 20180307012445) do
add_index "ci_build_trace_sections", ["build_id", "section_name_id"], name: "index_ci_build_trace_sections_on_build_id_and_section_name_id", unique: true, using: :btree
add_index "ci_build_trace_sections", ["project_id"], name: "index_ci_build_trace_sections_on_project_id", using: :btree
+ add_index "ci_build_trace_sections", ["section_name_id"], name: "index_ci_build_trace_sections_on_section_name_id", using: :btree
create_table "ci_builds", force: :cascade do |t|
t.string "status"
@@ -1733,7 +1734,9 @@ ActiveRecord::Schema.define(version: 20180307012445) do
end
add_index "taggings", ["tag_id", "taggable_id", "taggable_type", "context", "tagger_id", "tagger_type"], name: "taggings_idx", unique: true, using: :btree
+ add_index "taggings", ["tag_id"], name: "index_taggings_on_tag_id", using: :btree
add_index "taggings", ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree
+ add_index "taggings", ["taggable_id", "taggable_type"], name: "index_taggings_on_taggable_id_and_taggable_type", using: :btree
create_table "tags", force: :cascade do |t|
t.string "name"
@@ -1775,6 +1778,8 @@ ActiveRecord::Schema.define(version: 20180307012445) do
add_index "todos", ["note_id"], name: "index_todos_on_note_id", using: :btree
add_index "todos", ["project_id"], name: "index_todos_on_project_id", using: :btree
add_index "todos", ["target_type", "target_id"], name: "index_todos_on_target_type_and_target_id", using: :btree
+ add_index "todos", ["user_id", "id"], name: "index_todos_on_user_id_and_id_done", where: "((state)::text = 'done'::text)", using: :btree
+ add_index "todos", ["user_id", "id"], name: "index_todos_on_user_id_and_id_pending", where: "((state)::text = 'pending'::text)", using: :btree
add_index "todos", ["user_id"], name: "index_todos_on_user_id", using: :btree
create_table "trending_projects", force: :cascade do |t|
@@ -2029,6 +2034,8 @@ ActiveRecord::Schema.define(version: 20180307012445) do
add_foreign_key "cluster_providers_gcp", "clusters", on_delete: :cascade
add_foreign_key "clusters", "users", on_delete: :nullify
add_foreign_key "clusters_applications_helm", "clusters", on_delete: :cascade
+ add_foreign_key "clusters_applications_ingress", "clusters", name: "fk_753a7b41c1", on_delete: :cascade
+ add_foreign_key "clusters_applications_prometheus", "clusters", name: "fk_557e773639", on_delete: :cascade
add_foreign_key "clusters_applications_runners", "ci_runners", column: "runner_id", name: "fk_02de2ded36", on_delete: :nullify
add_foreign_key "clusters_applications_runners", "clusters", on_delete: :cascade
add_foreign_key "container_repositories", "projects"
diff --git a/doc/administration/incoming_email.md b/doc/administration/incoming_email.md
index 6c5a466ced5..27a3710632d 100644
--- a/doc/administration/incoming_email.md
+++ b/doc/administration/incoming_email.md
@@ -187,6 +187,7 @@ for a real-world example of this exploit.
```sh
sudo gitlab-ctl reconfigure
+ sudo gitlab-ctl restart
```
1. Verify that everything is configured correctly:
diff --git a/doc/administration/monitoring/performance/img/performance_bar.png b/doc/administration/monitoring/performance/img/performance_bar.png
index b3c6bc474e3..48212f6276a 100644
--- a/doc/administration/monitoring/performance/img/performance_bar.png
+++ b/doc/administration/monitoring/performance/img/performance_bar.png
Binary files differ
diff --git a/doc/administration/monitoring/performance/img/performance_bar_gitaly_calls.png b/doc/administration/monitoring/performance/img/performance_bar_gitaly_calls.png
new file mode 100644
index 00000000000..52176df9ecd
--- /dev/null
+++ b/doc/administration/monitoring/performance/img/performance_bar_gitaly_calls.png
Binary files differ
diff --git a/doc/administration/monitoring/performance/performance_bar.md b/doc/administration/monitoring/performance/performance_bar.md
index b9464945cea..ec1cbce1bad 100644
--- a/doc/administration/monitoring/performance/performance_bar.md
+++ b/doc/administration/monitoring/performance/performance_bar.md
@@ -11,10 +11,12 @@ It allows you to see (from left to right):
- 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)
-- time taken and number of calls to Redis
-- time taken and number of background jobs created by Sidekiq
+- 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)
- profile of the code used to generate the page, line by line for either _all_, _app & lib_ , or _views_. 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)
+- 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
## Enable the Performance Bar via the Admin panel
@@ -39,3 +41,5 @@ You can toggle the Bar using the same shortcut.
![GitLab Performance Bar Admin Settings](img/performance_bar_configuration_settings.png)
---
+
+[Gitaly]: ../../gitaly/index.md
diff --git a/doc/administration/raketasks/check.md b/doc/administration/raketasks/check.md
index d73d9422d2c..7d34d35e7d1 100644
--- a/doc/administration/raketasks/check.md
+++ b/doc/administration/raketasks/check.md
@@ -84,12 +84,14 @@ checks using those checksums can be run. These checks also detect missing files.
Currently, integrity checks are supported for the following types of file:
-* LFS objects
-* User uploads
+* CI artifacts (Available from version 10.7.0)
+* LFS objects (Available from version 10.6.0)
+* User uploads (Available from version 10.6.0)
**Omnibus Installation**
```
+sudo gitlab-rake gitlab:artifacts:check
sudo gitlab-rake gitlab:lfs:check
sudo gitlab-rake gitlab:uploads:check
```
@@ -97,6 +99,7 @@ sudo gitlab-rake gitlab:uploads:check
**Source Installation**
```bash
+sudo -u git -H bundle exec rake gitlab:artifacts:check RAILS_ENV=production
sudo -u git -H bundle exec rake gitlab:lfs:check RAILS_ENV=production
sudo -u git -H bundle exec rake gitlab:uploads:check RAILS_ENV=production
```
@@ -112,6 +115,7 @@ Variable | Type | Description
`VERBOSE` | boolean | Causes failures to be listed individually, rather than being summarized.
```bash
+sudo gitlab-rake gitlab:artifacts:check BATCH=100 ID_FROM=50 ID_TO=250
sudo gitlab-rake gitlab:lfs:check BATCH=100 ID_FROM=50 ID_TO=250
sudo gitlab-rake gitlab:uploads:check BATCH=100 ID_FROM=50 ID_TO=250
```
diff --git a/doc/administration/raketasks/maintenance.md b/doc/administration/raketasks/maintenance.md
index ecf92c379fd..2b4252a7b1d 100644
--- a/doc/administration/raketasks/maintenance.md
+++ b/doc/administration/raketasks/maintenance.md
@@ -240,3 +240,31 @@ sudo gitlab-rake gitlab:tcp_check[example.com,80]
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:tcp_check[example.com,80] RAILS_ENV=production
```
+
+## Clear exclusive lease (DANGER)
+
+GitLab uses a shared lock mechanism: `ExclusiveLease` to prevent simultaneous operations
+in a shared resource. An example is running periodic garbage collection on repositories.
+
+In very specific situations, a operation locked by an Exclusive Lease can fail without
+releasing the lock. If you can't wait for it to expire, you can run this task to manually
+clear it.
+
+To clear all exclusive leases:
+
+DANGER: **DANGER**:
+Don't run it while GitLab or Sidekiq is running
+
+```bash
+sudo gitlab-rake gitlab:exclusive_lease:clear
+```
+
+To specify a lease `type` or lease `type + id`, specify a scope:
+
+```bash
+# to clear all leases for repository garbage collection:
+sudo gitlab-rake gitlab:exclusive_lease:clear[project_housekeeping:*]
+
+# to clear a lease for repository garbage collection in a specific project: (id=4)
+sudo gitlab-rake gitlab:exclusive_lease:clear[project_housekeeping:4]
+```
diff --git a/doc/api/project_import_export.md b/doc/api/project_import_export.md
index 677765368a8..de5207fc5e4 100644
--- a/doc/api/project_import_export.md
+++ b/doc/api/project_import_export.md
@@ -15,9 +15,10 @@ POST /projects/:id/export
| 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 |
+| `description` | string | no | Overrides the project description |
```console
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/export
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "description=Foo Bar" https://gitlab.example.com/api/v4/projects/1/export
```
```json
diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md
index ffebe1618d3..c1e258aedca 100644
--- a/doc/ci/examples/README.md
+++ b/doc/ci/examples/README.md
@@ -32,34 +32,41 @@ There's also a collection of repositories with [example projects](https://gitlab
- **Debian**: [Continuous Deployment with GitLab: how to build and deploy a Debian Package with GitLab CI](https://about.gitlab.com/2016/10/12/automated-debian-package-build-with-gitlab-ci/)
- **Maven**: [How to deploy Maven projects to Artifactory with GitLab CI/CD](artifactory_and_gitlab/index.md)
+### Game development
+
+- [DevOps and Game Dev with GitLab CI/CD](devops_and_game_dev_with_gitlab_ci_cd/index.md)
+
### Miscellaneous
- [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)
-### Code quality analysis
+## Code quality analysis
[Analyze code quality with the Code Climate CLI](code_climate.md).
-### Static Application Security Testing (SAST)
+## Static Application Security Testing (SAST)
+
+**(Ultimate)** [Scan your code for vulnerabilities](https://docs.gitlab.com/ee/ci/examples/sast.html)
+
+## Container Scanning
-- **(Ultimate)** [Scan your code for vulnerabilities](https://docs.gitlab.com/ee/ci/examples/sast.html)
-- [Scan your Docker images for vulnerabilities](sast_docker.md)
+[Scan your Docker images for vulnerabilities](container_scanning.md)
-### Dynamic Application Security Testing (DAST)
+## Dynamic Application Security Testing (DAST)
Scan your app for vulnerabilities with GitLab [Dynamic Application Security Testing (DAST)](dast.md).
-### Browser Performance Testing with Sitespeed.io
+## Browser Performance Testing with Sitespeed.io
Analyze your [browser performance with Sitespeed.io](browser_performance.md).
-### GitLab CI/CD for Review Apps
+## GitLab CI/CD for Review Apps
- [Example project](https://gitlab.com/gitlab-examples/review-apps-nginx/) that shows how to use GitLab CI/CD for [Review Apps](../review_apps/index.html).
- [Dockerizing GitLab Review Apps](https://about.gitlab.com/2017/07/11/dockerizing-review-apps/)
-### GitLab CI/CD for GitLab Pages
+## GitLab CI/CD for GitLab Pages
See the documentation on [GitLab Pages](../../user/project/pages/index.md) for a complete overview.
diff --git a/doc/ci/examples/code_climate.md b/doc/ci/examples/code_climate.md
index d7df53494ed..ec5e5afb8c6 100644
--- a/doc/ci/examples/code_climate.md
+++ b/doc/ci/examples/code_climate.md
@@ -16,18 +16,26 @@ codequality:
- docker:dind
script:
- docker pull codeclimate/codeclimate
- - docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate:0.69.0 init
- - docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate:0.69.0 analyze -f json > codeclimate.json || true
+ - export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
+ - docker run
+ --env SOURCE_CODE="$PWD" \
+ --volume "$PWD":/code \
+ --volume /var/run/docker.sock:/var/run/docker.sock \
+ "registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code
artifacts:
paths: [codeclimate.json]
```
-This will create a `codequality` job in your CI pipeline and will allow you to
-download and analyze the report artifact in JSON format.
+The above example will create a `codequality` job in your CI/CD pipeline which
+will scan your source code for code quality issues. The report will be saved
+as an artifact that you can later download and analyze.
-For [GitLab Starter][ee] users, this information can be automatically
-extracted and shown right in the merge request widget. [Learn more on code quality
-diffs in merge requests](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html).
+TIP: **Tip:**
+Starting with [GitLab Starter][ee] 9.3, this information will
+be automatically extracted and shown right in the merge request widget. To do
+so, the CI/CD job must be named `codequality` and the artifact path must be
+`codeclimate.json`.
+[Learn more on code quality diffs in merge requests](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html).
[cli]: https://github.com/codeclimate/codeclimate
[dind]: ../docker/using_docker_build.md#use-docker-in-docker-executor
diff --git a/doc/ci/examples/container_scanning.md b/doc/ci/examples/container_scanning.md
new file mode 100644
index 00000000000..3437b63748a
--- /dev/null
+++ b/doc/ci/examples/container_scanning.md
@@ -0,0 +1,55 @@
+# Container Scanning with GitLab CI/CD
+
+You can check your Docker images (or more precisely the containers) for known
+vulnerabilities by using [Clair](https://github.com/coreos/clair) and
+[clair-scanner](https://github.com/arminc/clair-scanner), two open source tools
+for Vulnerability Static Analysis for containers.
+
+All you need is a GitLab Runner with the Docker executor (the shared Runners on
+GitLab.com will work fine). You can then add a new job to `.gitlab-ci.yml`,
+called `sast:container`:
+
+```yaml
+sast:container:
+ image: docker:latest
+ variables:
+ DOCKER_DRIVER: overlay2
+ ## Define two new variables based on GitLab's CI/CD predefined variables
+ ## https://docs.gitlab.com/ee/ci/variables/#predefined-variables-environment-variables
+ CI_APPLICATION_REPOSITORY: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG
+ CI_APPLICATION_TAG: $CI_COMMIT_SHA
+ allow_failure: true
+ services:
+ - docker:dind
+ script:
+ - docker run -d --name db arminc/clair-db:latest
+ - docker run -p 6060:6060 --link db:postgres -d --name clair arminc/clair-local-scan:v2.0.1
+ - apk add -U wget ca-certificates
+ - docker pull ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG}
+ - wget https://github.com/arminc/clair-scanner/releases/download/v8/clair-scanner_linux_amd64
+ - mv clair-scanner_linux_amd64 clair-scanner
+ - chmod +x clair-scanner
+ - touch clair-whitelist.yml
+ - ./clair-scanner -c http://docker:6060 --ip $(hostname -i) -r gl-sast-container-report.json -l clair.log -w clair-whitelist.yml ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} || true
+ artifacts:
+ paths: [gl-sast-container-report.json]
+```
+
+The above example will create a `sast:container` job in your CI/CD pipeline, pull
+the image from the [Container Registry](../../user/project/container_registry.md)
+(whose name is defined from the two `CI_APPLICATION_` variables) and scan it
+for possible vulnerabilities. The report will be saved as an artifact that you
+can later download and analyze.
+
+If you want to whitelist some specific vulnerabilities, you can do so by defining
+them in a [YAML file](https://github.com/arminc/clair-scanner/blob/master/README.md#example-whitelist-yaml-file),
+in our case its named `clair-whitelist.yml`.
+
+TIP: **Tip:**
+Starting with [GitLab Ultimate][ee] 10.4, this information will
+be automatically extracted and shown right in the merge request widget. To do
+so, the CI/CD job must be named `sast:container` and the artifact path must be
+`gl-sast-container-report.json`.
+[Learn more on container scanning results shown in merge requests](https://docs.gitlab.com/ee/user/project/merge_requests/container_scanning.html).
+
+[ee]: https://about.gitlab.com/products/
diff --git a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/img/aws_config_window.png b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/img/aws_config_window.png
new file mode 100644
index 00000000000..76e0295722b
--- /dev/null
+++ b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/img/aws_config_window.png
Binary files differ
diff --git a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/img/gitlab_config.png b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/img/gitlab_config.png
new file mode 100644
index 00000000000..050a97d2726
--- /dev/null
+++ b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/img/gitlab_config.png
Binary files differ
diff --git a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/img/test_pipeline_pass.png b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/img/test_pipeline_pass.png
new file mode 100644
index 00000000000..4ab5d5f401a
--- /dev/null
+++ b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/img/test_pipeline_pass.png
Binary files differ
diff --git a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md
new file mode 100644
index 00000000000..bfc8558a580
--- /dev/null
+++ b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md
@@ -0,0 +1,526 @@
+---
+author: Ryan Hall
+author_gitlab: blitzgren
+level: intermediary
+article_type: tutorial
+date: 2018-03-07
+---
+
+# DevOps and Game Dev with GitLab CI/CD
+
+With advances in WebGL and WebSockets, browsers are extremely viable as game development
+platforms without the use of plugins like Adobe Flash. Furthermore, by using GitLab and [AWS](https://aws.amazon.com/),
+single game developers, as well as game dev teams, can easily host browser-based games online.
+
+In this tutorial, we'll focus on DevOps, as well as testing and hosting games with Continuous
+Integration/Deployment methods. We assume you are familiar with GitLab, javascript,
+and the basics of game development.
+
+## The game
+
+Our [demo game](http://gitlab-game-demo.s3-website-us-east-1.amazonaws.com/) consists of a simple spaceship traveling in space that shoots by clicking the mouse in a given direction.
+
+Creating a strong CI/CD pipeline at the beginning of developing another game, [Dark Nova](http://darknova.io/about),
+was essential for the fast pace the team worked at. This tutorial will build upon my
+[previous introductory article](https://ryanhallcs.wordpress.com/2017/03/15/devops-and-game-dev/) and go through the following steps:
+
+1. Using code from the previous article to start with a barebones [Phaser](https://phaser.io) game built by a gulp file
+1. Adding and running unit tests
+1. Creating a `Weapon` class that can be triggered to spawn a `Bullet` in a given direction
+1. Adding a `Player` class that uses this weapon and moves around the screen
+1. Adding the sprites we will use for the `Player` and `Weapon`
+1. Testing and deploying with Continuous Integration and Continuous Deployment methods
+
+By the end, we'll have the core of a [playable game](http://gitlab-game-demo.s3-website-us-east-1.amazonaws.com/)
+that's tested and deployed on every push to the `master` branch of the [codebase](https://gitlab.com/blitzgren/gitlab-game-demo).
+This will also provide
+boilerplate code for starting a browser-based game with the following components:
+
+- Written in [Typescript](https://www.typescriptlang.org/) and [PhaserJs](https://phaser.io)
+- Building, running, and testing with [Gulp](http://gulpjs.com/)
+- Unit tests with [Chai](http://chaijs.com/) and [Mocha](https://mochajs.org/)
+- CI/CD with GitLab
+- Hosting the codebase on GitLab.com
+- Hosting the game on AWS
+- Deploying to AWS
+
+## Requirements and setup
+
+Please refer to my previous article [DevOps and Game Dev](https://ryanhallcs.wordpress.com/2017/03/15/devops-and-game-dev/) to learn the foundational
+development tools, running a Hello World-like game, and building this game using GitLab
+CI/CD from every new push to master. The `master` branch for this game's [repository](https://gitlab.com/blitzgren/gitlab-game-demo)
+contains a completed version with all configurations. If you would like to follow along
+with this article, you can clone and work from the `devops-article` branch:
+
+```sh
+git clone git@gitlab.com:blitzgren/gitlab-game-demo.git
+git checkout devops-article
+```
+
+Next, we'll create a small subset of tests that exemplify most of the states I expect
+this `Weapon` class to go through. To get started, create a folder called `lib/tests`
+and add the following code to a new file `weaponTests.ts`:
+
+```ts
+import { expect } from 'chai';
+import { Weapon, BulletFactory } from '../lib/weapon';
+
+describe('Weapon', () => {
+ var subject: Weapon;
+ var shotsFired: number = 0;
+ // Mocked bullet factory
+ var bulletFactory: BulletFactory = <BulletFactory>{
+ generate: function(px, py, vx, vy, rot) {
+ shotsFired++;
+ }
+ };
+ var parent: any = { x: 0, y: 0 };
+
+ beforeEach(() => {
+ shotsFired = 0;
+ subject = new Weapon(bulletFactory, parent, 0.25, 1);
+ });
+
+ it('should shoot if not in cooldown', () => {
+ subject.trigger(true);
+ subject.update(0.1);
+ expect(shotsFired).to.equal(1);
+ });
+
+ it('should not shoot during cooldown', () => {
+ subject.trigger(true);
+ subject.update(0.1);
+ subject.update(0.1);
+ expect(shotsFired).to.equal(1);
+ });
+
+ it('should shoot after cooldown ends', () => {
+ subject.trigger(true);
+ subject.update(0.1);
+ subject.update(0.3); // longer than timeout
+ expect(shotsFired).to.equal(2);
+ });
+
+ it('should not shoot if not triggered', () => {
+ subject.update(0.1);
+ subject.update(0.1);
+ expect(shotsFired).to.equal(0);
+ });
+});
+```
+
+To build and run these tests using gulp, let's also add the following gulp functions
+to the existing `gulpfile.js` file:
+
+```ts
+gulp.task('build-test', function () {
+ return gulp.src('src/tests/**/*.ts', { read: false })
+ .pipe(tap(function (file) {
+ // replace file contents with browserify's bundle stream
+ file.contents = browserify(file.path, { debug: true })
+ .plugin(tsify, { project: "./tsconfig.test.json" })
+ .bundle();
+ }))
+ .pipe(buffer())
+ .pipe(sourcemaps.init({loadMaps: true}) )
+ .pipe(gulp.dest('built/tests'));
+});
+
+gulp.task('run-test', function() {
+ gulp.src(['./built/tests/**/*.ts']).pipe(mocha());
+});
+```
+
+We will start implementing the first part of our game and get these `Weapon` tests to pass.
+The `Weapon` class will expose a method to trigger the generation of a bullet at a given
+direction and speed. Later we will implement a `Player` class that ties together the user input
+to trigger the weapon. In the `src/lib` folder create a `weapon.ts` file. We'll add two classes
+to it: `Weapon` and `BulletFactory` which will encapsulate Phaser's **sprite** and
+**group** objects, and the logic specific to our game.
+
+```ts
+export class Weapon {
+ private isTriggered: boolean = false;
+ private currentTimer: number = 0;
+
+ constructor(private bulletFactory: BulletFactory, private parent: Phaser.Sprite, private cooldown: number, private bulletSpeed: number) {
+ }
+
+ public trigger(on: boolean): void {
+ this.isTriggered = on;
+ }
+
+ public update(delta: number): void {
+ this.currentTimer -= delta;
+
+ if (this.isTriggered && this.currentTimer <= 0) {
+ this.shoot();
+ }
+ }
+
+ private shoot(): void {
+ // Reset timer
+ this.currentTimer = this.cooldown;
+
+ // Get velocity direction from player rotation
+ var parentRotation = this.parent.rotation + Math.PI / 2;
+ var velx = Math.cos(parentRotation);
+ var vely = Math.sin(parentRotation);
+
+ // Apply a small forward offset so bullet shoots from head of ship instead of the middle
+ var posx = this.parent.x - velx * 10
+ var posy = this.parent.y - vely * 10;
+
+ this.bulletFactory.generate(posx, posy, -velx * this.bulletSpeed, -vely * this.bulletSpeed, this.parent.rotation);
+ }
+}
+
+export class BulletFactory {
+
+ constructor(private bullets: Phaser.Group, private poolSize: number) {
+ // Set all the defaults for this BulletFactory's bullet object
+ this.bullets.enableBody = true;
+ this.bullets.physicsBodyType = Phaser.Physics.ARCADE;
+ this.bullets.createMultiple(30, 'bullet');
+ this.bullets.setAll('anchor.x', 0.5);
+ this.bullets.setAll('anchor.y', 0.5);
+ this.bullets.setAll('outOfBoundsKill', true);
+ this.bullets.setAll('checkWorldBounds', true);
+ }
+
+ public generate(posx: number, posy: number, velx: number, vely: number, rot: number): Phaser.Sprite {
+ // Pull a bullet from Phaser's Group pool
+ var bullet = this.bullets.getFirstExists(false);
+
+ // Set the few unique properties about this bullet: rotation, position, and velocity
+ if (bullet) {
+ bullet.reset(posx, posy);
+ bullet.rotation = rot;
+ bullet.body.velocity.x = velx;
+ bullet.body.velocity.y = vely;
+ }
+
+ return bullet;
+ }
+}
+```
+
+Lastly, we'll redo our entry point, `game.ts`, to tie together both `Player` and `Weapon` objects
+as well as add them to the update loop. Here is what the updated `game.ts` file looks like:
+
+```ts
+import { Player } from "./player";
+import { Weapon, BulletFactory } from "./weapon";
+
+window.onload = function() {
+ var game = new Phaser.Game(800, 600, Phaser.AUTO, 'gameCanvas', { preload: preload, create: create, update: update });
+ var player: Player;
+ var weapon: Weapon;
+
+ // Import all assets prior to loading the game
+ function preload () {
+ game.load.image('player', 'assets/player.png');
+ game.load.image('bullet', 'assets/bullet.png');
+ }
+
+ // Create all entities in the game, after Phaser loads
+ function create () {
+ // Create and position the player
+ var playerSprite = game.add.sprite(400, 550, 'player');
+ playerSprite.anchor.setTo(0.5);
+ player = new Player(game.input, playerSprite, 150);
+
+ var bulletFactory = new BulletFactory(game.add.group(), 30);
+ weapon = new Weapon(bulletFactory, player.sprite, 0.25, 1000);
+
+ player.loadWeapon(weapon);
+ }
+
+ // This function is called once every tick, default is 60fps
+ function update() {
+ var deltaSeconds = game.time.elapsedMS / 1000; // convert to seconds
+ player.update(deltaSeconds);
+ weapon.update(deltaSeconds);
+ }
+}
+```
+
+Run `gulp serve` and you can run around and shoot. Wonderful! Let's update our CI
+pipeline to include running the tests along with the existing build job.
+
+## Continuous Integration
+
+To ensure our changes don't break the build and all tests still pass, we utilize
+Continuous Integration (CI) to run these checks automatically for every push.
+Read through this article to understand [Continuous Integration, Continuous Delivery, and Continuous Deployment](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/),
+and how these methods are leveraged by GitLab.
+From the [last tutorial](https://ryanhallcs.wordpress.com/2017/03/15/devops-and-game-dev/) we already have a `gitlab-ci.yml` file set up for building our app from
+every push. We need to set up a new CI job for testing, which GitLab CI/CD will run after the build job using our generated artifacts from gulp.
+
+Please read through the [documentation on CI/CD configuration](../../../ci/yaml/README.md) file to explore its contents and adjust it to your needs.
+
+### Build your game with GitLab CI/CD
+
+We need to update our build job to ensure tests get run as well. Add `gulp build-test`
+to the end of the `script` array for the existing `build` job. Once these commands run,
+we know we will need to access everything in the `built` folder, given by GitLab CI/CD's `artifacts`.
+We'll also cache `node_modules` to avoid having to do a full re-pull of those dependencies:
+just pack them up in the cache. Here is the full `build` job:
+
+```yml
+build:
+ stage: build
+ script:
+ - npm i gulp -g
+ - npm i
+ - gulp
+ - gulp build-test
+ cache:
+ policy: push
+ paths:
+ - node_modules
+ artifacts:
+ paths:
+ - built
+```
+
+### Test your game with GitLab CI/CD
+
+For testing locally, we simply run `gulp run-tests`, which requires gulp to be installed
+globally like in the `build` job. We pull `node_modules` from the cache, so the `npm i`
+command won't have to do much. In preparation for deployment, we know we will still need
+the `built` folder in the artifacts, which will be brought over as default behavior from
+the previous job. Lastly, by convention, we let GitLab CI/CD know this needs to be run after
+the `build` job by giving it a `test` [stage](../../../ci/yaml/README.md#stages).
+Following the YAML structure, the `test` job should look like this:
+
+```yml
+test:
+ stage: test
+ script:
+ - npm i gulp -g
+ - npm i
+ - gulp run-test
+ cache:
+ policy: push
+ paths:
+ - node_modules/
+ artifacts:
+ paths:
+ - built/
+```
+
+We have added unit tests for a `Weapon` class that shoots on a specified interval.
+The `Player` class implements `Weapon` along with the ability to move around and shoot. Also,
+we've added test artifacts and a test stage to our GitLab CI/CD pipeline using `.gitlab-ci.yml`,
+allowing us to run our tests by every push.
+Our entire `.gitlab-ci.yml` file should now look like this:
+
+```yml
+image: node:6
+
+build:
+ stage: build
+ script:
+ - npm i gulp -g
+ - npm i
+ - gulp
+ - gulp build-test
+ cache:
+ policy: push
+ paths:
+ - node_modules/
+ artifacts:
+ paths:
+ - built/
+
+test:
+ stage: test
+ script:
+ - npm i gulp -g
+ - npm i
+ - gulp run-test
+ cache:
+ policy: pull
+ paths:
+ - node_modules/
+ artifacts:
+ paths:
+ - built/
+```
+
+### Run your CI/CD pipeline
+
+That's it! Add all your new files, commit, and push. For a reference of what our repo should
+look like at this point, please refer to the [final commit related to this article on my sample repository](https://gitlab.com/blitzgren/gitlab-game-demo/commit/8b36ef0ecebcf569aeb251be4ee13743337fcfe2).
+By applying both build and test stages, GitLab will run them sequentially at every push to
+our repository. If all goes well you'll end up with a green check mark on each job for the pipeline:
+
+![Passing Pipeline](img/test_pipeline_pass.png)
+
+You can confirm that the tests passed by clicking on the `test` job to enter the full build logs.
+Scroll to the bottom and observe, in all its passing glory:
+
+```sh
+$ gulp run-test
+[18:37:24] Using gulpfile /builds/blitzgren/gitlab-game-demo/gulpfile.js
+[18:37:24] Starting 'run-test'...
+[18:37:24] Finished 'run-test' after 21 ms
+
+
+ Weapon
+ ✓ should shoot if not in cooldown
+ ✓ should not shoot during cooldown
+ ✓ should shoot after cooldown ends
+ ✓ should not shoot if not triggered
+
+
+ 4 passing (18ms)
+
+Uploading artifacts...
+built/: found 17 matching files
+Uploading artifacts to coordinator... ok id=17095874 responseStatus=201 Created token=aaaaaaaa Job succeeded
+```
+
+## Continuous Deployment
+
+We have our codebase built and tested on every push. To complete the full pipeline with Continuous Deployment,
+let's set up [free web hosting with AWS S3](https://aws.amazon.com/s/dm/optimization/server-side-test/free-tier/free_np/) and a job through which our build artifacts get
+deployed. GitLab also has a free static site hosting service we could use, [GitLab Pages](https://about.gitlab.com/features/pages/),
+however Dark Nova specifically uses other AWS tools that necessitates using `AWS S3`.
+Read through this article that describes [deploying to both S3 and GitLab Pages](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/)
+and further delves into the principles of GitLab CI/CD than discussed in this article.
+
+### Set up S3 Bucket
+
+1. Log into your AWS account and go to [S3](https://console.aws.amazon.com/s3/home)
+1. Click the **Create Bucket** link at the top
+1. Enter a name of your choosing and click next
+1. Keep the default **Properties** and click next
+1. Click the **Manage group permissions** and allow **Read** for the **Everyone** group, click next
+1. Create the bucket, and select it in your S3 bucket list
+1. On the right side, click **Properties** and enable the **Static website hosting** category
+1. Update the radio button to the **Use this bucket to host a website** selection. Fill in `index.html` and `error.html` respectively
+
+### Set up AWS Secrets
+
+We need to be able to deploy to AWS with our AWS account credentials, but we certainly
+don't want to put secrets into source code. Luckily GitLab provides a solution for this
+with [Secret Variables](../../../ci/variables/README.md). This can get complicated
+due to [IAM](https://aws.amazon.com/iam/) management. As a best practice, you shouldn't
+use root security credentials. Proper IAM credential management is beyond the scope of this
+article, but AWS will remind you that using root credentials is unadvised and against their
+best practices, as they should. Feel free to follow best practices and use a custom IAM user's
+credentials, which will be the same two credentials (Key ID and Secret). It's a good idea to
+fully understand [IAM Best Practices in AWS](http://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html). We need to add these credentials to GitLab:
+
+1. Log into your AWS account and go to the [Security Credentials page](https://console.aws.amazon.com/iam/home#/security_credential)
+1. Click the **Access Keys** section and **Create New Access Key**. Create the key and keep the id and secret around, you'll need them later
+ ![AWS Access Key Config](img/aws_config_window.png)
+1. Go to your GitLab project, click **Settings > CI/CD** on the left sidebar
+1. Expand the **Secret Variables** section
+ ![GitLab Secret Config](img/gitlab_config.png)
+1. Add a key named `AWS_KEY_ID` and copy the key id from Step 2 into the **Value** textbox
+1. Add a key named `AWS_KEY_SECRET` and copy the key secret from Step 2 into the **Value** textbox
+
+### Deploy your game with GitLab CI/CD
+
+To deploy our build artifacts, we need to install the [AWS CLI](https://aws.amazon.com/cli/) on
+the Shared Runner. The Shared Runner also needs to be able to authenticate with your AWS
+account to deploy the artifacts. By convention, AWS CLI will look for `AWS_ACCESS_KEY_ID`
+and `AWS_SECRET_ACCESS_KEY`. GitLab's CI gives us a way to pass the secret variables we
+set up in the prior section using the `variables` portion of the `deploy` job. At the end,
+we add directives to ensure deployment `only` happens on pushes to `master`. This way, every
+single branch still runs through CI, and only merging (or committing directly) to master will
+trigger the `deploy` job of our pipeline. Put these together to get the following:
+
+```yml
+deploy:
+ stage: deploy
+ variables:
+ AWS_ACCESS_KEY_ID: "$AWS_KEY_ID"
+ AWS_SECRET_ACCESS_KEY: "$AWS_KEY_SECRET"
+ script:
+ - apt-get update
+ - apt-get install -y python3-dev python3-pip
+ - easy_install3 -U pip
+ - pip3 install --upgrade awscli
+ - aws s3 sync ./built s3://gitlab-game-demo --region "us-east-1" --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --cache-control "no-cache, no-store, must-revalidate" --delete
+ only:
+ - master
+```
+
+Be sure to update the region and S3 URL in that last script command to fit your setup.
+Our final configuration file `.gitlab-ci.yml` looks like:
+
+```yml
+image: node:6
+
+build:
+ stage: build
+ script:
+ - npm i gulp -g
+ - npm i
+ - gulp
+ - gulp build-test
+ cache:
+ policy: push
+ paths:
+ - node_modules/
+ artifacts:
+ paths:
+ - built/
+
+test:
+ stage: test
+ script:
+ - npm i gulp -g
+ - gulp run-test
+ cache:
+ policy: pull
+ paths:
+ - node_modules/
+ artifacts:
+ paths:
+ - built/
+
+deploy:
+ stage: deploy
+ variables:
+ AWS_ACCESS_KEY_ID: "$AWS_KEY_ID"
+ AWS_SECRET_ACCESS_KEY: "$AWS_KEY_SECRET"
+ script:
+ - apt-get update
+ - apt-get install -y python3-dev python3-pip
+ - easy_install3 -U pip
+ - pip3 install --upgrade awscli
+ - aws s3 sync ./built s3://gitlab-game-demo --region "us-east-1" --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --cache-control "no-cache, no-store, must-revalidate" --delete
+ only:
+ - master
+```
+
+## Conclusion
+
+Within the [demo repository](https://gitlab.com/blitzgren/gitlab-game-demo) you can also find a handful of boilerplate code to get
+[Typescript](https://www.typescriptlang.org/), [Mocha](https://mochajs.org/), [Gulp](http://gulpjs.com/) and [Phaser](https://phaser.io) all playing
+together nicely with GitLab CI/CD, which is the result of lessons learned while making [Dark Nova](http://darknova.io/).
+Using a combination of free and open source software, we have a full CI/CD pipeline, a game foundation,
+and unit tests, all running and deployed at every push to master - with shockingly little code.
+Errors can be easily debugged through GitLab's build logs, and within minutes of a successful commit,
+you can see the changes live on your game.
+
+Setting up Continous Integration and Continuous Deployment from the start with Dark Nova enables
+rapid but stable development. We can easily test changes in a separate [environment](../../../ci/environments.md#introduction-to-environments-and-deployments),
+or multiple environments if needed. Balancing and updating a multiplayer game can be ongoing
+and tedious, but having faith in a stable deployment with GitLab CI/CD allows
+a lot of breathing room in quickly getting changes to players.
+
+## Further settings
+
+Here are some ideas to further investigate that can speed up or improve your pipeline:
+
+- [Yarn](https://yarnpkg.com) instead of npm
+- Setup a custom [Docker](../../../ci/docker/using_docker_images.md#define-image-and-services-from-gitlab-ci-yml) image that can preload dependencies and tools (like AWS CLI)
+- Forward a [custom domain](http://docs.aws.amazon.com/AmazonS3/latest/dev/website-hosting-custom-domain-walkthrough.html) to your game's S3 static website
+- Combine jobs if you find it unnecessary for a small project
+- Avoid the queues and set up your own [custom GitLab CI/CD runner](https://about.gitlab.com/2016/03/01/gitlab-runner-with-docker/)
diff --git a/doc/ci/examples/sast_docker.md b/doc/ci/examples/sast_docker.md
index 57a9c4bcfc1..9f4a63e296d 100644
--- a/doc/ci/examples/sast_docker.md
+++ b/doc/ci/examples/sast_docker.md
@@ -1,55 +1 @@
-# Static Application Security Testing for Docker containers with GitLab CI/CD
-
-You can check your Docker images (or more precisely the containers) for known
-vulnerabilities by using [Clair](https://github.com/coreos/clair) and
-[clair-scanner](https://github.com/arminc/clair-scanner), two open source tools
-for Vulnerability Static Analysis for containers.
-
-All you need is a GitLab Runner with the Docker executor (the shared Runners on
-GitLab.com will work fine). You can then add a new job to `.gitlab-ci.yml`,
-called `sast:container`:
-
-```yaml
-sast:container:
- image: docker:latest
- variables:
- DOCKER_DRIVER: overlay2
- ## Define two new variables based on GitLab's CI/CD predefined variables
- ## https://docs.gitlab.com/ee/ci/variables/#predefined-variables-environment-variables
- CI_APPLICATION_REPOSITORY: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG
- CI_APPLICATION_TAG: $CI_COMMIT_SHA
- allow_failure: true
- services:
- - docker:dind
- script:
- - docker run -d --name db arminc/clair-db:latest
- - docker run -p 6060:6060 --link db:postgres -d --name clair arminc/clair-local-scan:v2.0.1
- - apk add -U wget ca-certificates
- - docker pull ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG}
- - wget https://github.com/arminc/clair-scanner/releases/download/v8/clair-scanner_linux_amd64
- - mv clair-scanner_linux_amd64 clair-scanner
- - chmod +x clair-scanner
- - touch clair-whitelist.yml
- - ./clair-scanner -c http://docker:6060 --ip $(hostname -i) -r gl-sast-container-report.json -l clair.log -w clair-whitelist.yml ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} || true
- artifacts:
- paths: [gl-sast-container-report.json]
-```
-
-The above example will create a `sast:container` job in your CI/CD pipeline, pull
-the image from the [Container Registry](../../user/project/container_registry.md)
-(whose name is defined from the two `CI_APPLICATION_` variables) and scan it
-for possible vulnerabilities. The report will be saved as an artifact that you
-can later download and analyze.
-
-If you want to whitelist some specific vulnerabilities, you can do so by defining
-them in a [YAML file](https://github.com/arminc/clair-scanner/blob/master/README.md#example-whitelist-yaml-file),
-in our case its named `clair-whitelist.yml`.
-
-TIP: **Tip:**
-Starting with [GitLab Ultimate][ee] 10.4, this information will
-be automatically extracted and shown right in the merge request widget. To do
-so, the CI/CD job must be named `sast:container` and the artifact path must be
-`gl-sast-container-report.json`.
-[Learn more on application security testing results shown in merge requests](https://docs.gitlab.com/ee/user/project/merge_requests/sast_docker.html).
-
-[ee]: https://about.gitlab.com/products/
+This document was moved to [another location](./container_scanning.md). \ No newline at end of file
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index 23ce6a5f210..bd4aeb006bd 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -12,7 +12,7 @@ this order:
1. [Trigger variables][triggers] or [scheduled pipeline variables](../../user/project/pipelines/schedules.md#making-use-of-scheduled-pipeline-variables) (take precedence over all)
1. Project-level [secret variables](#secret-variables) or [protected secret variables](#protected-secret-variables)
1. Group-level [secret variables](#secret-variables) or [protected secret variables](#protected-secret-variables)
-1. YAML-defined [job-level variables](../yaml/README.md#job-variables)
+1. YAML-defined [job-level variables](../yaml/README.md#variables)
1. YAML-defined [global variables](../yaml/README.md#variables)
1. [Deployment variables](#deployment-variables)
1. [Predefined variables](#predefined-variables-environment-variables) (are the
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 80ab63468f2..accf6340398 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -3,18 +3,19 @@
This document describes the usage of `.gitlab-ci.yml`, the file that is used by
GitLab Runner to manage your project's jobs.
-If you want a quick introduction to GitLab CI, follow our
-[quick start guide](../quick_start/README.md).
-
-## .gitlab-ci.yml
-
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
of your repository and contains definitions of how your project should be built.
+If you want a quick introduction to GitLab CI, follow our
+[quick start guide](../quick_start/README.md).
+
+## Jobs
+
The YAML file defines a set of jobs with constraints stating when they should
-be run. The jobs are defined as top-level elements with a name and always have
-to contain at least the `script` clause:
+be run. You can specify an unlimited number of jobs which are defined as
+top-level elements with an arbitrary name and always have to contain at least
+the `script` clause.
```yaml
job1:
@@ -24,9 +25,8 @@ job2:
script: "execute-script-for-job2"
```
-The above example is the simplest possible CI configuration with two separate
+The above example is the simplest possible CI/CD configuration with two separate
jobs, where each of the jobs executes a different command.
-
Of course a command can execute code directly (`./configure;make;make install`)
or run a script (`test.sh`) in the repository.
@@ -34,78 +34,115 @@ Jobs are picked up by [Runners](../runners/README.md) and executed within the
environment of the Runner. What is important, is that each job is run
independently from each other.
-The YAML syntax allows for using more complex job specifications than in the
-above example:
+Each job must have a unique name, but there are a few **reserved `keywords` that
+cannot be used as job names**:
-```yaml
-image: ruby:2.1
-services:
- - postgres
+- `image`
+- `services`
+- `stages`
+- `types`
+- `before_script`
+- `after_script`
+- `variables`
+- `cache`
-before_script:
- - bundle install
+A job is defined by a list of parameters that define the job behavior.
-after_script:
- - rm secrets
+| Keyword | Required | Description |
+|---------------|----------|-------------|
+| script | yes | Defines a shell script which is executed by Runner |
+| image | no | Use docker image, covered in [Using Docker Images](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) |
+| services | no | Use docker services, covered in [Using Docker Images](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) |
+| stage | no | Defines a job stage (default: `test`) |
+| type | no | Alias for `stage` |
+| variables | no | Define job variables on a job level |
+| only | no | Defines a list of git refs for which job is created |
+| except | no | Defines a list of git refs for which job is not created |
+| tags | no | Defines a list of tags which are used to select Runner |
+| allow_failure | no | Allow job to fail. Failed job doesn't contribute to commit status |
+| when | no | Define when to run job. Can be `on_success`, `on_failure`, `always` or `manual` |
+| dependencies | no | Define other jobs that a job depends on so that you can pass artifacts between them|
+| artifacts | no | Define list of [job artifacts](#artifacts) |
+| cache | no | Define list of files that should be cached between subsequent runs |
+| before_script | no | Override a set of commands that are executed before job |
+| after_script | no | Override a set of commands that are executed after job |
+| environment | no | Defines a name of environment to which deployment is done by this job |
+| coverage | no | Define code coverage settings for a given job |
+| retry | no | Define how many times a job can be auto-retried in case of a failure |
-stages:
- - build
- - test
- - deploy
+### `pages`
-job1:
- stage: build
+`pages` is a special job that is used to upload static content to GitLab that
+can be used to serve your website. It has a special syntax, so the two
+requirements below must be met:
+
+1. Any static content must be placed under a `public/` directory
+1. `artifacts` with a path to the `public/` directory must be defined
+
+The example below simply moves all files from the root of the project to the
+`public/` directory. The `.public` workaround is so `cp` doesn't also copy
+`public/` to itself in an infinite loop:
+
+```
+pages:
+ stage: deploy
script:
- - execute-script-for-job1
+ - mkdir .public
+ - cp -r * .public
+ - mv .public public
+ artifacts:
+ paths:
+ - public
only:
- - master
- tags:
- - docker
+ - master
```
-There are a few reserved `keywords` that **cannot** be used as job names:
-
-| Keyword | Required | Description |
-|---------------|----------|-------------|
-| image | no | Use docker image, covered in [Use Docker](../docker/README.md) |
-| services | no | Use docker services, covered in [Use Docker](../docker/README.md) |
-| stages | no | Define build stages |
-| types | no | Alias for `stages` (deprecated) |
-| before_script | no | Define commands that run before each job's script |
-| after_script | no | Define commands that run after each job's script |
-| variables | no | Define build variables |
-| cache | no | Define list of files that should be cached between subsequent runs |
+Read more on [GitLab Pages user documentation](../../user/project/pages/index.md).
-### image and services
+## `image` and `services`
This allows to specify a custom Docker image and a list of services that can be
used for time of the job. The configuration of this feature is covered in
[a separate document](../docker/README.md).
-### before_script
-
-`before_script` is used to define the command that should be run before all
-jobs, including deploy jobs, but after the restoration of artifacts. This can
-be an array or a multi-line string.
-
-### after_script
+## `before_script` and `after_script`
> Introduced in GitLab 8.7 and requires Gitlab Runner v1.2
+`before_script` is used to define the command that should be run before all
+jobs, including deploy jobs, but after the restoration of [artifacts](#artifacts).
+This can be an array or a multi-line string.
+
`after_script` is used to define the command that will be run after for all
jobs, including failed ones. This has to be an array or a multi-line string.
-> **Note:**
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`.
-### stages
+It's possible to overwrite the globally defined `before_script` and `after_script`
+if you set it per-job:
-`stages` is used to define stages that can be used by jobs.
-The specification of `stages` allows for having flexible multi stage pipelines.
+```yaml
+before_script:
+- global before script
+
+job:
+ before_script:
+ - execute this instead of global before script
+ script:
+ - my command
+ after_script:
+ - execute this after my script
+```
+
+## `stages`
+`stages` is used to define stages that can be used by jobs and is defined
+globally.
+
+The specification of `stages` allows for having flexible multi stage pipelines.
The ordering of elements in `stages` defines the ordering of jobs' execution:
1. Jobs of the same stage are run in parallel.
@@ -134,280 +171,45 @@ There are also two edge cases worth mentioning:
`test` and `deploy` are allowed to be used as job's stage by default.
2. If a job doesn't specify a `stage`, the job is assigned the `test` stage.
-### types
-
-> Deprecated, and could be removed in one of the future releases. Use [stages](#stages) instead.
-
-Alias for [stages](#stages).
-
-### variables
-
-> Introduced in GitLab Runner v0.5.0.
-
-GitLab CI allows you to add variables to `.gitlab-ci.yml` that are set in the
-job environment. The variables are stored in the Git repository and are meant
-to store non-sensitive project configuration, for example:
-
-```yaml
-variables:
- DATABASE_URL: "postgres://postgres@postgres/my_database"
-```
-
->**Note:**
-Integers (as well as strings) are legal both for variable's name and value.
-Floats are not legal and cannot be used.
-
-These variables can be later used in all executed commands and scripts.
-The YAML-defined variables are also set to all created service containers,
-thus allowing to fine tune them. Variables can be also defined on a
-[job level](#job-variables).
-
-Except for the user defined variables, there are also the ones set up by the
-Runner itself. One example would be `CI_COMMIT_REF_NAME` which has the value of
-the branch or tag name for which project is built. Apart from the variables
-you can set in `.gitlab-ci.yml`, there are also the so called secret variables
-which can be set in GitLab's UI.
-
-[Learn more about variables.][variables]
-
-### cache
-
->
-**Notes:**
-- Introduced in GitLab Runner v0.7.0.
-- Prior to GitLab 9.2, caches were restored after artifacts.
-- From GitLab 9.2, caches are restored before artifacts.
-
-`cache` is used to specify a list of files and directories which should be
-cached between jobs. You can only use paths that are within the project
-workspace.
-
-**By default caching is enabled and shared between pipelines and jobs,
-starting from GitLab 9.0**
-
-If `cache` is defined outside the scope of jobs, it means it is set
-globally and all jobs will use that definition.
-
-Cache all files in `binaries` and `.config`:
-
-```yaml
-rspec:
- script: test
- cache:
- paths:
- - binaries/
- - .config
-```
-
-Cache all Git untracked files:
-
-```yaml
-rspec:
- script: test
- cache:
- untracked: true
-```
-
-Cache all Git untracked files and files in `binaries`:
-
-```yaml
-rspec:
- script: test
- cache:
- untracked: true
- paths:
- - binaries/
-```
-
-Locally defined cache overrides globally defined options. The following `rspec`
-job will cache only `binaries/`:
-
-```yaml
-cache:
- paths:
- - my/files
-
-rspec:
- script: test
- cache:
- key: rspec
- paths:
- - binaries/
-```
-
-Note that since cache is shared between jobs, if you're using different
-paths for different jobs, you should also set a different **cache:key**
-otherwise cache content can be overwritten.
-
-The cache is provided on a best-effort basis, so don't expect that the cache
-will be always present. For implementation details, please check GitLab Runner.
-
-#### cache:key
-
-> Introduced in GitLab Runner v1.0.0.
-
-The `key` directive allows you to define the affinity of caching
-between jobs, allowing to have a single cache for all jobs,
-cache per-job, cache per-branch or any other way you deem proper.
-
-This allows you to fine tune caching, allowing you to cache data between
-different jobs or even different branches.
-
-The `cache:key` variable can use any of the [predefined variables](../variables/README.md).
-
-The default key is **default** across the project, therefore everything is
-shared between each pipelines and jobs by default, starting from GitLab 9.0.
-
->**Note:** The `cache:key` variable cannot contain the `/` character, or the equivalent URI encoded `%2F`; a value made only of dots (`.`, `%2E`) is also forbidden.
-
----
-
-**Example configurations**
-
-To enable per-job caching:
-
-```yaml
-cache:
- key: "$CI_JOB_NAME"
- untracked: true
-```
-
-To enable per-branch caching:
-
-```yaml
-cache:
- key: "$CI_COMMIT_REF_SLUG"
- untracked: true
-```
-
-To enable per-job and per-branch caching:
-
-```yaml
-cache:
- key: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
- untracked: true
-```
-
-To enable per-branch and per-stage caching:
-
-```yaml
-cache:
- key: "$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG"
- untracked: true
-```
-
-If you use **Windows Batch** to run your shell scripts you need to replace
-`$` with `%`:
-
-```yaml
-cache:
- key: "%CI_JOB_STAGE%-%CI_COMMIT_REF_SLUG%"
- untracked: true
-```
+## `stage`
-If you use **Windows PowerShell** to run your shell scripts you need to replace
-`$` with `$env:`:
-
-```yaml
-cache:
- key: "$env:CI_JOB_STAGE-$env:CI_COMMIT_REF_SLUG"
- untracked: true
-```
-
-### cache:policy
-
-> Introduced in GitLab 9.4.
-
-The default behaviour of a caching job is to download the files at the start of
-execution, and to re-upload them at the end. This allows any changes made by the
-job to be persisted for future runs, and is known as the `pull-push` cache
-policy.
-
-If you know the job doesn't alter the cached files, you can skip the upload step
-by setting `policy: pull` in the job specification. Typically, this would be
-twinned with an ordinary cache job at an earlier stage to ensure the cache
-is updated from time to time:
+`stage` is defined per-job and relies on [`stages`](#stages) which is defined
+globally. It allows to group jobs into different stages, and jobs of the same
+`stage` are executed in `parallel`. For example:
```yaml
stages:
- - setup
+ - build
- test
+ - deploy
-prepare:
- stage: setup
- cache:
- key: gems
- paths:
- - vendor/bundle
- script:
- - bundle install --deployment
-
-rspec:
- stage: test
- cache:
- key: gems
- paths:
- - vendor/bundle
- policy: pull
- script:
- - bundle exec rspec ...
-```
-
-This helps to speed up job execution and reduce load on the cache server,
-especially when you have a large number of cache-using jobs executing in
-parallel.
-
-Additionally, if you have a job that unconditionally recreates the cache without
-reference to its previous contents, you can use `policy: push` in that job to
-skip the download step.
-
-## Jobs
+job 1:
+ stage: build
+ script: make build dependencies
-`.gitlab-ci.yml` allows you to specify an unlimited number of jobs. Each job
-must have a unique name, which is not one of the keywords mentioned above.
-A job is defined by a list of parameters that define the job behavior.
+job 2:
+ stage: build
+ script: make build artifacts
-```yaml
-job_name:
- script:
- - rake spec
- - coverage
+job 3:
stage: test
- only:
- - master
- except:
- - develop
- tags:
- - ruby
- - postgres
- allow_failure: true
+ script: make test
+
+job 4:
+ stage: deploy
+ script: make deploy
```
-| Keyword | Required | Description |
-|---------------|----------|-------------|
-| script | yes | Defines a shell script which is executed by Runner |
-| image | no | Use docker image, covered in [Using Docker Images](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) |
-| services | no | Use docker services, covered in [Using Docker Images](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) |
-| stage | no | Defines a job stage (default: `test`) |
-| type | no | Alias for `stage` |
-| variables | no | Define job variables on a job level |
-| only | no | Defines a list of git refs for which job is created |
-| except | no | Defines a list of git refs for which job is not created |
-| tags | no | Defines a list of tags which are used to select Runner |
-| allow_failure | no | Allow job to fail. Failed job doesn't contribute to commit status |
-| when | no | Define when to run job. Can be `on_success`, `on_failure`, `always` or `manual` |
-| dependencies | no | Define other jobs that a job depends on so that you can pass artifacts between them|
-| artifacts | no | Define list of [job artifacts](../../user/project/pipelines/job_artifacts.md) |
-| cache | no | Define list of files that should be cached between subsequent runs |
-| before_script | no | Override a set of commands that are executed before job |
-| after_script | no | Override a set of commands that are executed after job |
-| environment | no | Defines a name of environment to which deployment is done by this job |
-| coverage | no | Define code coverage settings for a given job |
-| retry | no | Define how many times a job can be auto-retried in case of a failure |
+## `types`
-### script
+CAUTION: **Deprecated:**
+`types` is deprecated, and could be removed in one of the future releases.
+Use [stages](#stages) instead.
-`script` is a shell script which is executed by the Runner. For example:
+## `script`
+
+`script` is the only required keyword that a job needs. It's a shell script
+which is executed by the Runner. For example:
```yaml
job:
@@ -429,13 +231,7 @@ that the YAML parser knows to interpret the whole thing as a string rather than
a "key: value" pair. Be careful when using special characters:
`:`, `{`, `}`, `[`, `]`, `,`, `&`, `*`, `#`, `?`, `|`, `-`, `<`, `>`, `=`, `!`, `%`, `@`, `` ` ``.
-### stage
-
-`stage` allows to group jobs into different stages. Jobs of the same `stage`
-are executed in `parallel`. For more info about the use of `stage` please check
-[stages](#stages).
-
-### only and except (simplified)
+## `only` and `except` (simplified)
`only` and `except` are two parameters that set a job policy to limit when
jobs are created:
@@ -505,12 +301,13 @@ job:
The above example will run `job` for all branches on `gitlab-org/gitlab-ce`,
except master.
-### only and except (complex)
+## `only` and `except` (complex)
> Introduced in GitLab 10.0
-> This an _alpha_ feature, and it it subject to change at any time without
- prior notice!
+CAUTION: **Warning:**
+This an _alpha_ feature, and it it subject to change at any time without
+prior notice!
Since GitLab 10.0 it is possible to define a more elaborate only/except job
policy configuration.
@@ -535,24 +332,7 @@ job:
kubernetes: active
```
-### Job variables
-
-It is possible to define job variables using a `variables` keyword on a job
-level. It works basically the same way as its [global-level equivalent](#variables),
-but allows you to define job-specific variables.
-
-When the `variables` keyword is used on a job level, it overrides the global YAML
-job variables and predefined ones. To turn off global defined variables
-in your job, define an empty hash:
-
-```yaml
-job_name:
- variables: {}
-```
-
-Job variables priority is defined in the [variables documentation][variables].
-
-### tags
+## `tags`
`tags` is used to select specific Runners from the list of all Runners that are
allowed to run this project.
@@ -573,7 +353,7 @@ job:
The specification above, will make sure that `job` is built by a Runner that
has both `ruby` AND `postgres` tags defined.
-### allow_failure
+## `allow_failure`
`allow_failure` is used when you want to allow a job to fail without impacting
the rest of the CI suite. Failed jobs don't contribute to the commit status.
@@ -606,7 +386,7 @@ job3:
- deploy_to_staging
```
-### when
+## `when`
`when` is used to implement jobs that are run in case of failure or despite the
failure.
@@ -619,7 +399,7 @@ failure.
fails.
1. `always` - execute job regardless of the status of jobs from prior stages.
1. `manual` - execute job manually (added in GitLab 8.10). Read about
- [manual actions](#manual-actions) below.
+ [manual actions](#when-manual) below.
For example:
@@ -667,42 +447,41 @@ The above script will:
success or failure.
3. Allow you to manually execute `deploy_job` from GitLab's UI.
-#### Manual actions
-
-> Introduced in GitLab 8.10.
-> Blocking manual actions were introduced in GitLab 9.0
-> Protected actions were introduced in GitLab 9.2
+### `when:manual`
-Manual actions are a special type of job that are not executed automatically;
-they need to be explicitly started by a user. Manual actions can be started
-from pipeline, build, environment, and deployment views.
+> **Notes:**
+- Introduced in GitLab 8.10.
+- Blocking manual actions were introduced in GitLab 9.0.
+- Protected actions were introduced in GitLab 9.2.
-An example usage of manual actions is deployment to production.
+Manual actions are a special type of job that are not executed automatically,
+they need to be explicitly started by a user. An example usage of manual actions
+would be a deployment to a production environment. Manual actions can be started
+from the pipeline, job, environment, and deployment views. Read more at the
+[environments documentation][env-manual].
-Read more at the [environments documentation][env-manual].
-
-Manual actions can be either optional or blocking. Blocking manual action will
-block execution of the pipeline at stage this action is defined in. It is
+Manual actions can be either optional or blocking. Blocking manual actions will
+block the execution of the pipeline at the stage this action is defined in. It's
possible to resume execution of the pipeline when someone executes a blocking
-manual actions by clicking a _play_ button.
+manual action by clicking a _play_ button.
-When pipeline is blocked it will not be merged if Merge When Pipeline Succeeds
+When a pipeline is blocked, it will not be merged if Merge When Pipeline Succeeds
is set. Blocked pipelines also do have a special status, called _manual_.
-
Manual actions are non-blocking by default. If you want to make manual action
blocking, it is necessary to add `allow_failure: false` to the job's definition
in `.gitlab-ci.yml`.
-Optional manual actions have `allow_failure: true` set by default.
+Optional manual actions have `allow_failure: true` set by default and their
+Statuses do not contribute to the overall pipeline status. So, if a manual
+action fails, the pipeline will eventually succeed.
-**Statuses of optional actions do not contribute to overall pipeline status.**
+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.
-**Manual actions are considered to be write actions, so permissions for
-protected branches 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.**
-
-### environment
+## `environment`
>
**Notes:**
@@ -727,7 +506,7 @@ deploy to production:
In the above example, the `deploy to production` job will be marked as doing a
deployment to the `production` environment.
-#### environment:name
+### `environment:name`
>
**Notes:**
@@ -766,7 +545,7 @@ deploy to production:
name: production
```
-#### environment:url
+### `environment:url`
>
**Notes:**
@@ -793,7 +572,7 @@ deploy to production:
url: https://prod.example.com
```
-#### environment:on_stop
+### `environment:on_stop`
>
**Notes:**
@@ -808,7 +587,7 @@ the environment.
Read the `environment:action` section for an example.
-#### environment:action
+### `environment:action`
> [Introduced][ce-6669] in GitLab 8.13.
@@ -849,7 +628,7 @@ The `stop_review_app` job is **required** to have the following keywords defined
- `stage` should be the same as the `review_app` in order for the environment
to stop automatically when the branch is deleted
-#### dynamic environments
+### Dynamic environments
>
**Notes:**
@@ -885,13 +664,204 @@ The common use case is to create dynamic environments for branches and use them
as Review Apps. You can see a simple example using Review Apps at
<https://gitlab.com/gitlab-examples/review-apps-nginx/>.
-### artifacts
+## `cache`
+
+>
+**Notes:**
+- Introduced in GitLab Runner v0.7.0.
+- `cache` can be set globally and per-job.
+- From GitLab 9.0, caching is enabled and shared between pipelines and jobs
+ by default.
+- From GitLab 9.2, caches are restored before [artifacts](#artifacts).
+
+`cache` is used to specify a list of files and directories which should be
+cached between jobs. You can only use paths that are within the project
+workspace.
+
+If `cache` is defined outside the scope of jobs, it means it is set
+globally and all jobs will use that definition.
+
+Cache all files in `binaries` and `.config`:
+
+```yaml
+rspec:
+ script: test
+ cache:
+ paths:
+ - binaries/
+ - .config
+```
+
+Cache all Git untracked files:
+
+```yaml
+rspec:
+ script: test
+ cache:
+ untracked: true
+```
+
+Cache all Git untracked files and files in `binaries`:
+
+```yaml
+rspec:
+ script: test
+ cache:
+ untracked: true
+ paths:
+ - binaries/
+```
+
+Locally defined cache overrides globally defined options. The following `rspec`
+job will cache only `binaries/`:
+
+```yaml
+cache:
+ paths:
+ - my/files
+
+rspec:
+ script: test
+ cache:
+ key: rspec
+ paths:
+ - binaries/
+```
+
+Note that since cache is shared between jobs, if you're using different
+paths for different jobs, you should also set a different **cache:key**
+otherwise cache content can be overwritten.
+
+NOTE: **Note:**
+The cache is provided on a best-effort basis, so don't expect that the cache
+will be always present.
+
+### `cache:key`
+
+> Introduced in GitLab Runner v1.0.0.
+
+The `key` directive allows you to define the affinity of caching
+between jobs, allowing to have a single cache for all jobs,
+cache per-job, cache per-branch or any other way that fits your needs.
+
+This way, you can fine tune caching, allowing you to cache data between
+different jobs or even different branches.
+
+The `cache:key` variable can use any of the
+[predefined variables](../variables/README.md), and the default key, if not set,
+is set as `$CI_JOB_NAME-$CI_COMMIT_REF_NAME` which translates as "per-job and
+per-branch". It is the default across the project, therefore everything is
+shared between pipelines and jobs running on the same branch by default.
+
+NOTE: **Note:**
+The `cache:key` variable cannot contain the `/` character, or the equivalent
+URI-encoded `%2F`; a value made only of dots (`.`, `%2E`) is also forbidden.
+
+**Example configurations**
+
+To enable per-job caching:
+
+```yaml
+cache:
+ key: "$CI_JOB_NAME"
+ untracked: true
+```
+
+To enable per-branch caching:
+
+```yaml
+cache:
+ key: "$CI_COMMIT_REF_SLUG"
+ untracked: true
+```
+
+To enable per-job and per-branch caching:
+
+```yaml
+cache:
+ key: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
+ untracked: true
+```
+
+To enable per-branch and per-stage caching:
+
+```yaml
+cache:
+ key: "$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG"
+ untracked: true
+```
+
+If you use **Windows Batch** to run your shell scripts you need to replace
+`$` with `%`:
+
+```yaml
+cache:
+ key: "%CI_JOB_STAGE%-%CI_COMMIT_REF_SLUG%"
+ untracked: true
+```
+
+If you use **Windows PowerShell** to run your shell scripts you need to replace
+`$` with `$env:`:
+
+```yaml
+cache:
+ key: "$env:CI_JOB_STAGE-$env:CI_COMMIT_REF_SLUG"
+ untracked: true
+```
+
+### `cache:policy`
+
+> Introduced in GitLab 9.4.
+
+The default behaviour of a caching job is to download the files at the start of
+execution, and to re-upload them at the end. This allows any changes made by the
+job to be persisted for future runs, and is known as the `pull-push` cache
+policy.
+
+If you know the job doesn't alter the cached files, you can skip the upload step
+by setting `policy: pull` in the job specification. Typically, this would be
+twinned with an ordinary cache job at an earlier stage to ensure the cache
+is updated from time to time:
+
+```yaml
+stages:
+ - setup
+ - test
+
+prepare:
+ stage: setup
+ cache:
+ key: gems
+ paths:
+ - vendor/bundle
+ script:
+ - bundle install --deployment
+
+rspec:
+ stage: test
+ cache:
+ key: gems
+ paths:
+ - vendor/bundle
+ policy: pull
+ script:
+ - bundle exec rspec ...
+```
+
+This helps to speed up job execution and reduce load on the cache server,
+especially when you have a large number of cache-using jobs executing in
+parallel.
+
+Additionally, if you have a job that unconditionally recreates the cache without
+reference to its previous contents, you can use `policy: push` in that job to
+skip the download step.
+
+## `artifacts`
>
**Notes:**
- Introduced in GitLab Runner v0.7.0 for non-Windows platforms.
- Windows support was added in GitLab Runner v.1.0.0.
-- Prior to GitLab 9.2, caches were restored after artifacts.
- From GitLab 9.2, caches are restored before artifacts.
- Currently not all executors are supported.
- Job artifacts are only collected for successful jobs by default.
@@ -960,7 +930,9 @@ release-job:
The artifacts will be sent to GitLab after the job finishes successfully and will
be available for download in the GitLab UI.
-#### artifacts:name
+[Read more about artifacts.](../../user/project/pipelines/job_artifacts.md)
+
+### `artifacts:name`
> Introduced in GitLab 8.6 and GitLab Runner v1.1.0.
@@ -970,10 +942,6 @@ useful when you'd like to download the archive from GitLab. The `artifacts:name`
variable can make use of any of the [predefined variables](../variables/README.md).
The default name is `artifacts`, which becomes `artifacts.zip` when downloaded.
----
-
-**Example configurations**
-
To create an archive with a name of the current job:
```yaml
@@ -998,7 +966,7 @@ tag including only the files that are untracked by Git:
```yaml
job:
artifacts:
- name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}"
+ name: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME"
untracked: true
```
@@ -1007,7 +975,7 @@ To create an archive with a name of the current [stage](#stages) and branch name
```yaml
job:
artifacts:
- name: "${CI_JOB_STAGE}_${CI_COMMIT_REF_NAME}"
+ name: "$CI_JOB_STAGE-$CI_COMMIT_REF_NAME"
untracked: true
```
@@ -1019,7 +987,7 @@ If you use **Windows Batch** to run your shell scripts you need to replace
```yaml
job:
artifacts:
- name: "%CI_JOB_STAGE%_%CI_COMMIT_REF_NAME%"
+ name: "%CI_JOB_STAGE%-%CI_COMMIT_REF_NAME%"
untracked: true
```
@@ -1029,11 +997,11 @@ If you use **Windows PowerShell** to run your shell scripts you need to replace
```yaml
job:
artifacts:
- name: "$env:CI_JOB_STAGE_$env:CI_COMMIT_REF_NAME"
+ name: "$env:CI_JOB_STAGE-$env:CI_COMMIT_REF_NAME"
untracked: true
```
-#### artifacts:when
+### `artifacts:when`
> Introduced in GitLab 8.9 and GitLab Runner v1.3.0.
@@ -1046,11 +1014,7 @@ failure.
1. `on_failure` - upload artifacts only when the job fails.
1. `always` - upload artifacts regardless of the job status.
----
-
-**Example configurations**
-
-To upload artifacts only when job fails.
+To upload artifacts only when job fails:
```yaml
job:
@@ -1058,22 +1022,23 @@ job:
when: on_failure
```
-#### artifacts:expire_in
+### `artifacts:expire_in`
> Introduced in GitLab 8.9 and GitLab Runner v1.3.0.
-`artifacts:expire_in` is used to delete uploaded artifacts after the specified
-time. By default, artifacts are stored on GitLab forever. `expire_in` allows you
-to specify how long artifacts should live before they expire, counting from the
-time they are uploaded and stored on GitLab.
+`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)
+(30 days by default, forever on GitLab.com).
You can use the **Keep** button on the job page to override expiration and
keep artifacts forever.
-After expiry, artifacts are actually deleted hourly by default (via a cron job),
-but they are not accessible after expiry.
+After their expiry, artifacts are deleted hourly by default (via a cron job),
+and are not accessible anymore.
-The value of `expire_in` is an elapsed time. Examples of parseable values:
+The value of `expire_in` is an elapsed time. Examples of parsable values:
- '3 mins 4 sec'
- '2 hrs 20 min'
@@ -1082,10 +1047,6 @@ The value of `expire_in` is an elapsed time. Examples of parseable values:
- '47 yrs 6 mos and 4d'
- '3 weeks and 2 days'
----
-
-**Example configurations**
-
To expire artifacts 1 week after being uploaded:
```yaml
@@ -1094,7 +1055,7 @@ job:
expire_in: 1 week
```
-### dependencies
+## `dependencies`
> Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
@@ -1153,7 +1114,7 @@ deploy:
script: make deploy
```
-#### When a dependent job will fail
+### When a dependent job will fail
> Introduced in GitLab 10.3.
@@ -1167,27 +1128,9 @@ You can ask your administrator to
[flip this switch](../../administration/job_artifacts.md#validation-for-dependencies)
and bring back the old behavior.
-### before_script and after_script
-
-It's possible to overwrite the globally defined `before_script` and `after_script`:
+## `coverage`
-```yaml
-before_script:
-- global before script
-
-job:
- before_script:
- - execute this instead of global before script
- script:
- - my command
- after_script:
- - execute this after my script
-```
-
-### coverage
-
-**Notes:**
-- [Introduced][ce-7447] in GitLab 8.17.
+> [Introduced][ce-7447] in GitLab 8.17.
`coverage` allows you to configure how code coverage will be extracted from the
job output.
@@ -1205,10 +1148,9 @@ job1:
coverage: '/Code coverage: \d+\.\d+/'
```
-### retry
+## `retry`
-**Notes:**
-- [Introduced][ce-3442] in GitLab 9.5.
+> [Introduced][ce-3442] in GitLab 9.5.
`retry` allows you to configure how many times a job is going to be retried in
case of a failure.
@@ -1228,16 +1170,57 @@ test:
retry: 2
```
-## Git Strategy
+## `variables`
+
+> Introduced in GitLab Runner v0.5.0.
+
+NOTE: **Note:**
+Integers (as well as strings) are legal both for variable's name and value.
+Floats are not legal and cannot be used.
+
+GitLab CI/CD allows you to define variables inside `.gitlab-ci.yml` that are
+then passed in the job environment. They can be set globally and per-job.
+When the `variables` keyword is used on a job level, it overrides the global
+YAML variables and predefined ones.
+
+They are stored in the Git repository and are meant to store non-sensitive
+project configuration, for example:
+
+```yaml
+variables:
+ DATABASE_URL: "postgres://postgres@postgres/my_database"
+```
+
+These variables can be later used in all executed commands and scripts.
+The YAML-defined variables are also set to all created service containers,
+thus allowing to fine tune them.
+
+To turn off global defined variables in a specific job, define an empty hash:
+
+```yaml
+job_name:
+ variables: {}
+```
+
+Except for the user defined variables, there are also the ones [set up by the
+Runner itself](../variables/README.md#predefined-variables-environment-variables).
+One example would be `CI_COMMIT_REF_NAME` which has the value of
+the branch or tag name for which project is built. Apart from the variables
+you can set in `.gitlab-ci.yml`, there are also the so called
+[secret variables](../variables/README.md#secret-variables)
+which can be set in GitLab's UI.
+
+[Learn more about variables and their priority.][variables]
+
+### Git strategy
> Introduced in GitLab 8.9 as an experimental feature. May change or be removed
completely in future releases. `GIT_STRATEGY=none` requires GitLab Runner
v1.7+.
You can set the `GIT_STRATEGY` used for getting recent application code, either
-in the global [`variables`](#variables) section or the [`variables`](#job-variables)
-section for individual jobs. If left unspecified, the default from project
-settings will be used.
+globally or per-job in the [`variables`](#variables) section. If left
+unspecified, the default from project settings will be used.
There are three possible values: `clone`, `fetch`, and `none`.
@@ -1269,44 +1252,13 @@ variables:
GIT_STRATEGY: none
```
-## Git Checkout
-
-> Introduced in GitLab Runner 9.3
-
-The `GIT_CHECKOUT` variable can be used when the `GIT_STRATEGY` is set to either
-`clone` or `fetch` to specify whether a `git checkout` should be run. If not
-specified, it defaults to true. Like `GIT_STRATEGY`, it can be set in either the
-global [`variables`](#variables) section or the [`variables`](#job-variables)
-section for individual jobs.
-
-If set to `false`, the Runner will:
-
-- when doing `fetch` - update the repository and leave working copy on
- the current revision,
-- when doing `clone` - clone the repository and leave working copy on the
- default branch.
-
-Having this setting set to `true` will mean that for both `clone` and `fetch`
-strategies the Runner will checkout the working copy to a revision related
-to the CI pipeline:
-
-```yaml
-variables:
- GIT_STRATEGY: clone
- GIT_CHECKOUT: "false"
-script:
- - git checkout master
- - git merge $CI_BUILD_REF_NAME
-```
-
-## Git Submodule Strategy
+### Git submodule strategy
> Requires GitLab Runner v1.10+.
The `GIT_SUBMODULE_STRATEGY` variable is used to control if / how Git
-submodules are included when fetching the code before a build. Like
-`GIT_STRATEGY`, it can be set in either the global [`variables`](#variables)
-section or the [`variables`](#job-variables) section for individual jobs.
+submodules are included when fetching the code before a build. You can set them
+globally or per-job in the [`variables`](#variables) section.
There are three possible values: `none`, `normal`, and `recursive`:
@@ -1336,8 +1288,36 @@ Note that for this feature to work correctly, the submodules must be configured
- a relative path to another repository on the same GitLab server. See the
[Git submodules](../git_submodules.md) documentation.
+### Git checkout
+
+> Introduced in GitLab Runner 9.3
+
+The `GIT_CHECKOUT` variable can be used when the `GIT_STRATEGY` is set to either
+`clone` or `fetch` to specify whether a `git checkout` should be run. If not
+specified, it defaults to true. You can set them globally or per-job in the
+[`variables`](#variables) section.
+
+If set to `false`, the Runner will:
+
+- when doing `fetch` - update the repository and leave working copy on
+ the current revision,
+- when doing `clone` - clone the repository and leave working copy on the
+ default branch.
+
+Having this setting set to `true` will mean that for both `clone` and `fetch`
+strategies the Runner will checkout the working copy to a revision related
+to the CI pipeline:
-## Job stages attempts
+```yaml
+variables:
+ GIT_STRATEGY: clone
+ GIT_CHECKOUT: "false"
+script:
+ - git checkout master
+ - git merge $CI_BUILD_REF_NAME
+```
+
+### Job stages attempts
> Introduced in GitLab, it requires GitLab Runner v1.9+.
@@ -1359,10 +1339,9 @@ variables:
GET_SOURCES_ATTEMPTS: 3
```
-You can set them in the global [`variables`](#variables) section or the
-[`variables`](#job-variables) section for individual jobs.
+You can set them globally or per-job in the [`variables`](#variables) section.
-## Shallow cloning
+### Shallow cloning
> Introduced in GitLab 8.9 as an experimental feature. May change in future
releases or be removed completely.
@@ -1393,7 +1372,17 @@ variables:
GIT_DEPTH: "3"
```
-## Hidden keys (jobs)
+You can set it globally or per-job in the [`variables`](#variables) section.
+
+## Special YAML features
+
+It's possible to use special YAML features like anchors (`&`), aliases (`*`)
+and map merging (`<<`), which will allow you to greatly reduce the complexity
+of `.gitlab-ci.yml`.
+
+Read more about the various [YAML features](https://learnxinyminutes.com/docs/yaml/).
+
+### Hidden keys (jobs)
> Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
@@ -1419,14 +1408,6 @@ Use this feature to ignore jobs, or use the
[special YAML features](#special-yaml-features) and transform the hidden keys
into templates.
-## Special YAML features
-
-It's possible to use special YAML features like anchors (`&`), aliases (`*`)
-and map merging (`<<`), which will allow you to greatly reduce the complexity
-of `.gitlab-ci.yml`.
-
-Read more about the various [YAML features](https://learnxinyminutes.com/docs/yaml/).
-
### Anchors
> Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
@@ -1556,34 +1537,10 @@ with an API call.
[Read more in the triggers documentation.](../triggers/README.md)
-### pages
-
-`pages` is a special job that is used to upload static content to GitLab that
-can be used to serve your website. It has a special syntax, so the two
-requirements below must be met:
-
-1. Any static content must be placed under a `public/` directory
-1. `artifacts` with a path to the `public/` directory must be defined
-
-The example below simply moves all files from the root of the project to the
-`public/` directory. The `.public` workaround is so `cp` doesn't also copy
-`public/` to itself in an infinite loop:
-
-```
-pages:
- stage: deploy
- script:
- - mkdir .public
- - cp -r * .public
- - mv .public public
- artifacts:
- paths:
- - public
- only:
- - master
-```
+## Skipping jobs
-Read more on [GitLab Pages user documentation](../../user/project/pages/index.md).
+If your commit message contains `[ci skip]` or `[skip ci]`, using any
+capitalization, the commit will be created but the pipeline will be skipped.
## Validate the .gitlab-ci.yml
@@ -1595,11 +1552,6 @@ You can find the link under `/ci/lint` of your gitlab instance.
If you get validation error when using specific values (e.g., `true` or `false`),
try to quote them, or change them to a different form (e.g., `/bin/true`).
-## Skipping jobs
-
-If your commit message contains `[ci skip]` or `[skip ci]`, using any
-capitalization, the commit will be created but the jobs will be skipped.
-
## Examples
Visit the [examples README][examples] to see a list of examples using GitLab
diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md
index 6fe5f647d6c..41e3412c7ff 100644
--- a/doc/development/doc_styleguide.md
+++ b/doc/development/doc_styleguide.md
@@ -1,89 +1,28 @@
-# Documentation styleguide
+# Documentation style guidelines
-This styleguide recommends best practices to improve documentation and to keep
-it organized and easy to find.
+The documentation style guide defines the markup structure used in
+GitLab documentation. Check the
+[documentation guidelines](writing_documentation.md) for general development instructions.
-See also [writing documentation](writing_documentation.md).
-
-## Location and naming of documents
-
->**Note:**
-These guidelines derive from the discussion taken place in issue [#3349][ce-3349].
-
-The documentation hierarchy can be vastly improved by providing a better layout
-and organization of directories.
-
-Having a structured document layout, we will be able to have meaningful URLs
-like `docs.gitlab.com/user/project/merge_requests.html`. With this pattern,
-you can immediately tell that you are navigating a user related documentation
-and is about the project and its merge requests.
-
-Do not create summaries of similar types of content (e.g. an index of all articles, videos, etc.),
-rather organise content by its subject (e.g. everything related to CI goes together)
-and cross-link between any related content.
-
-The table below shows what kind of documentation goes where.
-
-| Directory | What belongs here |
-| --------- | -------------- |
-| `doc/user/` | User related documentation. Anything that can be done within the GitLab UI goes here including `/admin`. |
-| `doc/administration/` | Documentation that requires the user to have access to the server where GitLab is installed. The admin settings that can be accessed via GitLab's interface go under `doc/user/admin_area/`. |
-| `doc/api/` | API related documentation. |
-| `doc/development/` | Documentation related to the development of GitLab. Any styleguides should go here. |
-| `doc/legal/` | Legal documents about contributing to GitLab. |
-| `doc/install/`| Probably the most visited directory, since `installation.md` is there. Ideally this should go under `doc/administration/`, but it's best to leave it as-is in order to avoid confusion (still debated though). |
-| `doc/update/` | Same with `doc/install/`. Should be under `administration/`, but this is a well known location, better leave as-is, at least for now. |
-| `doc/topics/` | Indexes per Topic (`doc/topics/topic-name/index.md`): all resources for that topic (user and admin documentation, articles, and third-party docs) |
-
----
-
-**General rules:**
-
-1. The correct naming and location of a new document, is a combination
- of the relative URL of the document in question and the GitLab Map design
- that is used for UX purposes ([source][graffle], [image][gitlab-map]).
-1. When creating a new document and it has more than one word in its name,
- make sure to use underscores instead of spaces or dashes (`-`). For example,
- a proper naming would be `import_projects_from_github.md`. The same rule
- applies to images.
-1. There are four main directories, `user`, `administration`, `api` and `development`.
-1. The `doc/user/` directory has five main subdirectories: `project/`, `group/`,
- `profile/`, `dashboard/` and `admin_area/`.
- 1. `doc/user/project/` should contain all project related documentation.
- 1. `doc/user/group/` should contain all group related documentation.
- 1. `doc/user/profile/` should contain all profile related documentation.
- Every page you would navigate under `/profile` should have its own document,
- i.e. `account.md`, `applications.md`, `emails.md`, etc.
- 1. `doc/user/dashboard/` should contain all dashboard related documentation.
- 1. `doc/user/admin_area/` should contain all admin related documentation
- describing what can be achieved by accessing GitLab's admin interface
- (_not to be confused with `doc/administration` where server access is
- required_).
- 1. Every category under `/admin/application_settings` should have its
- own document located at `doc/user/admin_area/settings/`. For example,
- the **Visibility and Access Controls** category should have a document
- located at `doc/user/admin_area/settings/visibility_and_access_controls.md`.
-1. The `doc/topics/` directory holds topic-related technical content. Create
- `doc/topics/topic-name/subtopic-name/index.md` when subtopics become necessary.
- General user- and admin- related documentation, should be placed accordingly.
-
----
-
-If you are unsure where a document should live, you can ping `@axil` or `@marcia` in your
-merge request.
+Check the GitLab hanbook for the [writing styles guidelines](https://about.gitlab.com/handbook/communication/#writing-style-guidelines).
## Text
-- Split up long lines, this makes it much easier to review and edit. Only
+- Split up long lines (wrap text), this makes it much easier to review and edit. Only
double line breaks are shown as a full line break in [GitLab markdown][gfm].
80-100 characters is a good line length
-- Make sure that the documentation is added in the correct directory and that
+- Make sure that the documentation is added in the correct
+ [directory](writing_documentation.md#documentation-directory-structure) and that
there's a link to it somewhere useful
- Do not duplicate information
- Be brief and clear
- Unless there's a logical reason not to, add documents in alphabetical order
- Write in US English
- Use [single spaces][] instead of double spaces
+- Jump a line between different markups (e.g., after every paragraph, hearder, list, etc)
+- Capitalize "G" and "L" in GitLab
+- Capitalize feature, products, and methods names. E.g.: GitLab Runner, Geo,
+Issue Boards, Git, Prometheus, Continuous Integration.
## Formatting
@@ -103,6 +42,8 @@ merge request.
links shift too, which eventually leads to dead links. If you think it is
compelling to add numbers in headings, make sure to at least discuss it with
someone in the Merge Request
+- [Avoid using symbols and special chars](https://gitlab.com/gitlab-com/gitlab-docs/issues/84)
+ in headers. Whenever possible, they should be plain and short text.
- Avoid adding things that show ephemeral statuses. For example, if a feature is
considered beta or experimental, put this info in a note, not in the heading.
- When introducing a new document, be careful for the headings to be
@@ -121,71 +62,18 @@ merge request.
you can use `[Text][identifier]` and at the bottom of the section or the
document add: `[identifier]: https://example.com`, in which case, we do
encourage you to also add an alternative text: `[identifier]: https://example.com "Alternative text"` that appears when hovering your mouse on a link.
-
-### Linking to inline docs
-
-Sometimes it's needed to link to the built-in documentation that GitLab provides
-under `/help`. This is normally done in files inside the `app/views/` directory
-with the help of the `help_page_path` helper method.
-
-In its simplest form, the HAML code to generate a link to the `/help` page is:
-
-```haml
-= link_to 'Help page', help_page_path('user/permissions')
-```
-
-The `help_page_path` contains the path to the document you want to link to with
-the following conventions:
-
-- it is relative to the `doc/` directory in the GitLab repository
-- the `.md` extension must be omitted
-- it must not end with a slash (`/`)
-
-Below are some special cases where should be used depending on the context.
-You can combine one or more of the following:
-
-1. **Linking to an anchor link.** Use `anchor` as part of the `help_page_path`
- method:
-
- ```haml
- = link_to 'Help page', help_page_path('user/permissions', anchor: 'anchor-link')
- ```
-
-1. **Opening links in a new tab.** This should be the default behavior:
-
- ```haml
- = link_to 'Help page', help_page_path('user/permissions'), target: '_blank'
- ```
-
-1. **Linking to a circle icon.** Usually used in settings where a long
- description cannot be used, like near checkboxes. You can basically use
- any font awesome icon, but prefer the `question-circle`:
-
- ```haml
- = link_to icon('question-circle'), help_page_path('user/permissions')
- ```
-
-1. **Using a button link.** Useful in places where text would be out of context
- with the rest of the page layout:
-
- ```haml
- = link_to 'Help page', help_page_path('user/permissions'), class: 'btn btn-info'
- ```
-
-1. **Using links inline of some text.**
-
- ```haml
- Description to #{link_to 'Help page', help_page_path('user/permissions')}.
- ```
-
-1. **Adding a period at the end of the sentence.** Useful when you don't want
- the period to be part of the link:
-
- ```haml
- = succeed '.' do
- Learn more in the
- = link_to 'Help page', help_page_path('user/permissions')
- ```
+- To link to internal documentation, use relative links, not full URLs. Use `../` to
+ navigate tp high-level directories, and always add the file name `file.md` at the
+ end of the link with the `.md` extension, not `.html`.
+ Example: instead of `[text](../../merge_requests/)`, use
+ `[text](../../merge_requests/index.md)` or, `[text](../../ci/README.md)`, or,
+ for anchor links, `[text](../../ci/README.md#examples)`.
+ Using the markdown extension is necessary for the [`/help`](writing_documentation.md#gitlab-help)
+ section of GitLab.
+- To link from CE to EE-only documentation, use the EE-only doc full URL.
+- Use [meaningful anchor texts](https://www.futurehosting.com/blog/links-should-have-meaningful-anchor-text-heres-why/).
+ E.g., instead of writing something like `Read more about GitLab Issue Boards [here](LINK)`,
+ write `Read more about [GitLab Issue Boards](LINK)`.
## Images
@@ -222,7 +110,7 @@ Inside the document:
- Notes should be quoted with the word `Note:` being bold. Use this form:
- ```
+ ```md
>**Note:**
This is something to note.
```
@@ -234,25 +122,25 @@ Inside the document:
If the note spans across multiple lines it's OK to split the line.
-## New features
+## Specific sections and terms
-New features must be shipped with its accompanying documentation and the doc
-reviewed by a technical writer.
+To mention and/or reference specific terms in GitLab, please follow the styles
+below.
-### Mentioning GitLab versions and tiers
+### GitLab versions and tiers
- Every piece of documentation that comes with a new feature should declare the
GitLab version that feature got introduced. Right below the heading add a
note:
- ```
+ ```md
> Introduced in GitLab 8.3.
```
- If possible every feature should have a link to the MR, issue, or epic that introduced it.
The above note would be then transformed to:
- ```
+ ```md
> [Introduced][ce-1242] in GitLab 8.3.
```
@@ -263,121 +151,41 @@ reviewed by a technical writer.
the [paid tier](https://about.gitlab.com/handbook/marketing/product-marketing/#tiers)
the feature is available in:
- ```
+ ```md
> [Introduced][ee-1234] in [GitLab Starter](https://about.gitlab.com/products/) 8.3.
```
Otherwise, leave this mention out.
-## References
-
-- **GitLab Restart:**
- There are many cases that a restart/reconfigure of GitLab is required. To
- avoid duplication, link to the special document that can be found in
- [`doc/administration/restart_gitlab.md`][doc-restart]. Usually the text will
- read like:
-
- ```
- Save the file and [reconfigure GitLab](../administration/restart_gitlab.md)
- for the changes to take effect.
- ```
- If the document you are editing resides in a place other than the GitLab CE/EE
- `doc/` directory, instead of the relative link, use the full path:
- `http://docs.gitlab.com/ce/administration/restart_gitlab.html`.
- Replace `reconfigure` with `restart` where appropriate.
-
-## Installation guide
-
-- **Ruby:**
- In [step 2 of the installation guide](../install/installation.md#2-ruby),
- we install Ruby from source. Whenever there is a new version that needs to
- be updated, remember to change it throughout the codeblock and also replace
- the sha256sum (it can be found in the [downloads page][ruby-dl] of the Ruby
- website).
+### GitLab Restart
-[ruby-dl]: https://www.ruby-lang.org/en/downloads/ "Ruby download website"
-
-## Changing document location
-
-Changing a document's location is not to be taken lightly. Remember that the
-documentation is available to all installations under `help/` and not only to
-GitLab.com or http://docs.gitlab.com. Make sure this is discussed with the
-Documentation team beforehand.
-
-If you indeed need to change a document's location, do NOT remove the old
-document, but rather replace all of its contents with a new line:
-
-```
-This document was moved to [another location](path/to/new_doc.md).
-```
+There are many cases that a restart/reconfigure of GitLab is required. To
+avoid duplication, link to the special document that can be found in
+[`doc/administration/restart_gitlab.md`][doc-restart]. Usually the text will
+read like:
-where `path/to/new_doc.md` is the relative path to the root directory `doc/`.
+ ```
+ Save the file and [reconfigure GitLab](../administration/restart_gitlab.md)
+ for the changes to take effect.
+ ```
----
+If the document you are editing resides in a place other than the GitLab CE/EE
+`doc/` directory, instead of the relative link, use the full path:
+`http://docs.gitlab.com/ce/administration/restart_gitlab.html`.
+Replace `reconfigure` with `restart` where appropriate.
-For example, if you were to move `doc/workflow/lfs/lfs_administration.md` to
-`doc/administration/lfs.md`, then the steps would be:
+### Installation guide
-1. Copy `doc/workflow/lfs/lfs_administration.md` to `doc/administration/lfs.md`
-1. Replace the contents of `doc/workflow/lfs/lfs_administration.md` with:
+**Ruby:**
+In [step 2 of the installation guide](../install/installation.md#2-ruby),
+we install Ruby from source. Whenever there is a new version that needs to
+be updated, remember to change it throughout the codeblock and also replace
+the sha256sum (it can be found in the [downloads page][ruby-dl] of the Ruby
+website).
- ```
- This document was moved to [another location](../../administration/lfs.md).
- ```
-
-1. Find and replace any occurrences of the old location with the new one.
- A quick way to find them is to use `git grep`. First go to the root directory
- where you cloned the `gitlab-ce` repository and then do:
-
- ```
- git grep -n "workflow/lfs/lfs_administration"
- git grep -n "lfs/lfs_administration"
- ```
-
-NOTE: **Note:**
-If the document being moved has any Disqus comments on it, there are extra steps
-to follow documented just [below](#redirections-for-pages-with-disqus-comments).
-
-Things to note:
-
-- Since we also use inline documentation, except for the documentation itself,
- the document might also be referenced in the views of GitLab (`app/`) which will
- render when visiting `/help`, and sometimes in the testing suite (`spec/`).
-- The above `git grep` command will search recursively in the directory you run
- it in for `workflow/lfs/lfs_administration` and `lfs/lfs_administration`
- and will print the file and the line where this file is mentioned.
- You may ask why the two greps. Since we use relative paths to link to
- documentation, sometimes it might be useful to search a path deeper.
-- The `*.md` extension is not used when a document is linked to GitLab's
- built-in help page, that's why we omit it in `git grep`.
-- Use the checklist on the documentation MR description template.
-
-### Redirections for pages with Disqus comments
-
-If the documentation page being relocated already has any Disqus comments,
-we need to preserve the Disqus thread.
-
-Disqus uses an identifier per page, and for docs.gitlab.com, the page identifier
-is configured to be the page URL. Therefore, when we change the document location,
-we need to preserve the old URL as the same Disqus identifier.
-
-To do that, add to the frontmatter the variable `redirect_from`,
-using the old URL as value. For example, let's say I moved the document
-available under `https://docs.gitlab.com/my-old-location/README.html` to a new location,
-`https://docs.gitlab.com/my-new-location/index.html`.
-
-Into the **new document** frontmatter add the following:
-
-```yaml
----
-redirect_from: 'https://docs.gitlab.com/my-old-location/README.html'
----
-```
-
-Note: it is necessary to include the file name in the `redirect_from` URL,
-even if it's `index.html` or `README.html`.
+[ruby-dl]: https://www.ruby-lang.org/en/downloads/ "Ruby download website"
-## Configuration documentation for source and Omnibus installations
+### Configuration documentation for source and Omnibus installations
GitLab currently officially supports two installation methods: installations
from source and Omnibus packages installations.
@@ -394,7 +202,7 @@ When there is a list of steps to perform, usually that entails editing the
configuration file and reconfiguring/restarting GitLab. In such case, follow
the style below as a guide:
-````
+```md
**For Omnibus installations**
1. Edit `/etc/gitlab/gitlab.rb`:
@@ -421,7 +229,7 @@ the style below as a guide:
[reconfigure]: path/to/administration/restart_gitlab.md#omnibus-gitlab-reconfigure
[restart]: path/to/administration/restart_gitlab.md#installations-from-source
-````
+```
In this case:
@@ -433,7 +241,7 @@ In this case:
- different highlighting languages are used for each config in the code block
- the [references](#references) guide is used for reconfigure/restart
-## Fake tokens
+### Fake tokens
There may be times where a token is needed to demonstrate an API call using
cURL or a secret variable used in CI. It is strongly advised not to use real
@@ -456,7 +264,7 @@ You can use the following fake tokens as examples.
| Health check token | `Tu7BgjR9qeZTEyRzGG2P` |
| Request profile token | `7VgpS4Ax5utVD2esNstz` |
-## API
+### API
Here is a list of must-have items. Use them in the exact order that appears
on this document. Further explanation is given below.
@@ -472,10 +280,10 @@ on this document. Further explanation is given below.
- Every method must have a cURL example.
- Every method must have a response body (in JSON format).
-### Method description
+#### Method description
Use the following table headers to describe the methods. Attributes should
-always be in code blocks using backticks (`).
+always be in code blocks using backticks (``` ` ```).
```
| Attribute | Type | Required | Description |
@@ -488,7 +296,7 @@ Rendered example:
| --------- | ---- | -------- | ----------- |
| `user` | string | yes | The GitLab username |
-### cURL commands
+#### cURL commands
- Use `https://gitlab.example.com/api/v4/` as an endpoint.
- Wherever needed use this personal access token: `9koXpg98eAheJpvBs5tK`.
@@ -505,11 +313,11 @@ Rendered example:
| `-X PUT` | Use this method when updating existing objects |
| `-X DELETE` | Use this method when removing existing objects |
-### cURL Examples
+#### cURL Examples
Below is a set of [cURL][] examples that you can use in the API documentation.
-#### Simple cURL command
+##### Simple cURL command
Get the details of a group:
@@ -517,7 +325,7 @@ Get the details of a group:
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/gitlab-org
```
-#### cURL example with parameters passed in the URL
+##### cURL example with parameters passed in the URL
Create a new project under the authenticated user's namespace:
@@ -525,7 +333,7 @@ Create a new project under the authenticated user's namespace:
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects?name=foo"
```
-#### Post data using cURL's --data
+##### Post data using cURL's --data
Instead of using `-X POST` and appending the parameters to the URI, you can use
cURL's `--data` option. The example below will create a new project `foo` under
@@ -535,7 +343,7 @@ the authenticated user's namespace.
curl --data "name=foo" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects"
```
-#### Post data using JSON content
+##### Post data using JSON content
> **Note:** In this example we create a new group. Watch carefully the single
and double quotes.
@@ -544,7 +352,7 @@ and double quotes.
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "Content-Type: application/json" --data '{"path": "my-group", "name": "My group"}' https://gitlab.example.com/api/v4/groups
```
-#### Post data using form-data
+##### Post data using form-data
Instead of using JSON or urlencode you can use multipart/form-data which
properly handles data encoding:
@@ -556,7 +364,7 @@ curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "title
The above example is run by and administrator and will add an SSH public key
titled ssh-key to user's account which has an id of 25.
-#### Escape special characters
+##### Escape special characters
Spaces or slashes (`/`) may sometimes result to errors, thus it is recommended
to escape them when possible. In the example below we create a new issue which
@@ -569,7 +377,7 @@ curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitl
Use `%2F` for slashes (`/`).
-#### Pass arrays to API calls
+##### Pass arrays to API calls
The GitLab API sometimes accepts arrays of strings or integers. For example, to
restrict the sign-up e-mail domains of a GitLab instance to `*.example.com` and
@@ -584,6 +392,3 @@ curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "domain
[gfm]: http://docs.gitlab.com/ce/user/markdown.html#newlines "GitLab flavored markdown documentation"
[ce-1242]: https://gitlab.com/gitlab-org/gitlab-ce/issues/1242
[doc-restart]: ../administration/restart_gitlab.md "GitLab restart documentation"
-[ce-3349]: https://gitlab.com/gitlab-org/gitlab-ce/issues/3349 "Documentation restructure"
-[graffle]: https://gitlab.com/gitlab-org/gitlab-design/blob/d8d39f4a87b90fb9ae89ca12dc565347b4900d5e/production/resources/gitlab-map.graffle
-[gitlab-map]: https://gitlab.com/gitlab-org/gitlab-design/raw/master/production/resources/gitlab-map.png
diff --git a/doc/development/ee_features.md b/doc/development/ee_features.md
index 1eb90c30ebd..fea92e740cb 100644
--- a/doc/development/ee_features.md
+++ b/doc/development/ee_features.md
@@ -360,27 +360,15 @@ Instead place EE specs in the `ee/spec` folder.
## JavaScript code in `assets/javascripts/`
-To separate EE-specific JS-files we can also move the files into an `ee` folder.
+To separate EE-specific JS-files we should also move the files into an `ee` folder.
For example there can be an
`app/assets/javascripts/protected_branches/protected_branches_bundle.js` and an
EE counterpart
`ee/app/assets/javascripts/protected_branches/protected_branches_bundle.js`.
-That way we can create a separate webpack bundle in `webpack.config.js`:
-
-```javascript
- protected_branches: '~/protected_branches',
- ee_protected_branches: 'ee/protected_branches/protected_branches_bundle.js',
-```
-
-With the separate bundle in place, we can decide which bundle to load inside the
-view, using the `page_specific_javascript_bundle_tag` helper.
-
-```haml
-- content_for :page_specific_javascripts do
- = page_specific_javascript_bundle_tag('protected_branches')
-```
+See the frontend guide [performance section](./fe_guide/performance.md) for
+information on managing page-specific javascript within EE.
## SCSS code in `assets/stylesheets`
diff --git a/doc/development/fe_guide/index.md b/doc/development/fe_guide/index.md
index 12dfc10812b..2280cf79f86 100644
--- a/doc/development/fe_guide/index.md
+++ b/doc/development/fe_guide/index.md
@@ -14,8 +14,8 @@ support through [webpack][webpack].
We also utilize [webpack][webpack] to handle the bundling, minification, and
compression of our assets.
-Working with our frontend assets requires Node (v4.3 or greater) and Yarn
-(v0.17 or greater). You can find information on how to install these on our
+Working with our frontend assets requires Node (v6.0 or greater) and Yarn
+(v1.2 or greater). You can find information on how to install these on our
[installation guide][install].
[jQuery][jquery] is used throughout the application's JavaScript, with
diff --git a/doc/development/fe_guide/performance.md b/doc/development/fe_guide/performance.md
index 98e43931a02..1320efaf767 100644
--- a/doc/development/fe_guide/performance.md
+++ b/doc/development/fe_guide/performance.md
@@ -23,7 +23,7 @@ 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.
-### Lazy Loading
+### Lazy Loading Images
To improve the time to first render we are using lazy loading for images. This works by setting
the actual image source on the `data-src` attribute. After the HTML is rendered and JavaScript is loaded,
@@ -47,41 +47,103 @@ properties once, and handle the actual animation with transforms.
## Reducing Asset Footprint
-### Page-specific JavaScript
+### Universal code
-Certain pages may require the use of a third party library, such as [d3][d3] for
-the User Activity Calendar and [Chart.js][chartjs] for the Graphs pages. These
-libraries increase the page size significantly, and impact load times due to
-bandwidth bottlenecks and the browser needing to parse more JavaScript.
-
-In cases where libraries are only used on a few specific pages, we use
-"page-specific JavaScript" to prevent the main `main.js` file from
-becoming unnecessarily large.
-
-Steps to split page-specific JavaScript from the main `main.js`:
-
-1. Create a directory for the specific page(s), e.g. `graphs/`.
-1. In that directory, create a `namespace_bundle.js` file, e.g. `graphs_bundle.js`.
-1. Add the new "bundle" file to the list of entry files in `config/webpack.config.js`.
- - For example: `graphs: './graphs/graphs_bundle.js',`.
-1. Move code reliant on these libraries into the `graphs` directory.
-1. In `graphs_bundle.js` add CommonJS `require('./path_to_some_component.js');` statements to load any other files in this directory. Make sure to use relative urls.
-1. In the relevant views, add the scripts to the page with the following:
-
-```haml
-- content_for :page_specific_javascripts do
- = webpack_bundle_tag 'lib_chart'
- = webpack_bundle_tag 'graphs'
-```
+Code that is contained within `main.js` and `commons/index.js` are loaded and
+run on _all_ pages. **DO NOT ADD** anything to these files unless it is truly
+needed _everywhere_. These bundles include ubiquitous libraries like `vue`,
+`axios`, and `jQuery`, as well as code for the main navigation and sidebar.
+Where possible we should aim to remove modules from these bundles to reduce our
+code footprint.
+
+### Page-specific JavaScript
-The above loads `chart.js` and `graphs_bundle.js` for this page only. `chart.js`
-is separated from the bundle file so it can be cached separately from the bundle
-and reused for other pages that also rely on the library. For an example, see
-[this Haml file][page-specific-js-example].
+Webpack has been configured to automatically generate entry point bundles based
+on the file structure within `app/assets/javascripts/pages/*`. The directories
+within the `pages` directory correspond to Rails controllers and actions. These
+auto-generated bundles will be automatically included on the corresponding
+pages.
+
+For example, if you were to visit [gitlab.com/gitlab-org/gitlab-ce/issues](https://gitlab.com/gitlab-org/gitlab-ce/issues),
+you would be accessing the `app/controllers/projects/issues_controller.rb`
+controller with the `index` action. If a corresponding file exists at
+`pages/projects/issues/index/index.js`, it will be compiled into a webpack
+bundle and included on the page.
+
+> **Note:** Previously we had encouraged the use of
+> `content_for :page_specific_javascripts` within haml files, along with
+> manually generated webpack bundles. However under this new system you should
+> not ever need to manually add an entry point to the `webpack.config.js` file.
+
+> **Tip:**
+> If you are unsure what controller and action corresponds to a given page, you
+> can find this out by inspecting `document.body.dataset.page` within your
+> browser's developer console while on any page within gitlab.
+
+#### Important Considerations:
+
+- **Keep Entry Points Lite:**
+ Page-specific javascript entry points should be as lite as possible. These
+ files are exempt from unit tests, and should be used primarily for
+ instantiation and dependency injection of classes and methods that live in
+ modules outside of the entry point script. Just import, read the DOM,
+ instantiate, and nothing else.
+
+- **Entry Points May Be Asynchronous:**
+ _DO NOT ASSUME_ that the DOM has been fully loaded and available when an
+ entry point script is run. If you require that some code be run after the
+ DOM has loaded, you should attach an event handler to the `DOMContentLoaded`
+ event with:
+
+ ```javascript
+ import initMyWidget from './my_widget';
+
+ document.addEventListener('DOMContentLoaded', () => {
+ initMyWidget();
+ });
+ ```
+
+- **Supporting Module Placement:**
+ - If a class or a module is _specific to a particular route_, try to locate
+ it close to the entry point it will be used. For instance, if
+ `my_widget.js` is only imported within `pages/widget/show/index.js`, you
+ should place the module at `pages/widget/show/my_widget.js` and import it
+ with a relative path (e.g. `import initMyWidget from './my_widget';`).
+
+ - If a class or module is _used by multiple routes_, place it within a
+ shared directory at the closest common parent directory for the entry
+ points that import it. For example, if `my_widget.js` is imported within
+ both `pages/widget/show/index.js` and `pages/widget/run/index.js`, then
+ place the module at `pages/widget/shared/my_widget.js` and import it with
+ a relative path if possible (e.g. `../shared/my_widget`).
+
+- **Enterprise Edition Caveats:**
+ For GitLab Enterprise Edition, page-specific entry points will override their
+ Community Edition counterparts with the same name, so if
+ `ee/app/assets/javascripts/pages/foo/bar/index.js` exists, it will take
+ precedence over `app/assets/javascripts/pages/foo/bar/index.js`. If you want
+ to minimize duplicate code, you can import one entry point from the other.
+ This is not done automatically to allow for flexibility in overriding
+ functionality.
### Code Splitting
-> *TODO* flesh out this section once webpack is ready for code-splitting
+For any code that does not need to be run immediately upon page load, (e.g.
+modals, dropdowns, and other behaviors that can be lazy-loaded), you can split
+your module into asynchronous chunks with dynamic import statements. These
+imports return a Promise which will be resolved once the script has loaded:
+
+```javascript
+import(/* webpackChunkName: 'emoji' */ '~/emoji')
+ .then(/* do something */)
+ .catch(/* report error */)
+```
+
+Please try to use `webpackChunkName` when generating these dynamic imports as
+it will provide a deterministic filename for the chunk which can then be cached
+the browser across GitLab versions.
+
+More information is available in [webpack's code splitting documentation](https://webpack.js.org/guides/code-splitting/#dynamic-imports).
### Minimizing page size
@@ -95,7 +157,8 @@ General tips:
- Prefer font formats with better compression, e.g. WOFF2 is better than WOFF, which is better than TTF.
- Compress and minify assets wherever possible (For CSS/JS, Sprockets and webpack do this for us).
- If some functionality can reasonably be achieved without adding extra libraries, avoid them.
-- Use page-specific JavaScript as described above to dynamically load libraries that are only needed on certain pages.
+- Use page-specific JavaScript as described above to load libraries that are only needed on certain pages.
+- Use code-splitting dynamic imports wherever possible to lazy-load code that is not needed initially.
- [High Performance Animations][high-perf-animations]
-------
@@ -112,8 +175,5 @@ General tips:
[pagespeed-insights]: https://developers.google.com/speed/pagespeed/insights/
[google-devtools-profiling]: https://developers.google.com/web/tools/chrome-devtools/profile/?hl=en
[browser-diet]: https://browserdiet.com/
-[d3]: https://d3js.org/
-[chartjs]: http://www.chartjs.org/
-[page-specific-js-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/13bb9ed77f405c5f6ee4fdbc964ecf635c9a223f/app/views/projects/graphs/_head.html.haml#L6-8
[high-perf-animations]: https://www.html5rocks.com/en/tutorials/speed/high-performance-animations/
[flip]: https://aerotwist.com/blog/flip-your-animations/
diff --git a/doc/development/fe_guide/style_guide_js.md b/doc/development/fe_guide/style_guide_js.md
index 917d28b48ee..7b5fa6ca42f 100644
--- a/doc/development/fe_guide/style_guide_js.md
+++ b/doc/development/fe_guide/style_guide_js.md
@@ -548,6 +548,57 @@ On those a default key should not be provided.
1. Properties in a Vue Component:
Check [order of properties in components rule][vue-order].
+#### `:key`
+When using `v-for` you need to provide a *unique* `:key` attribute for each item.
+
+1. If the elements of the array being iterated have an unique `id` it is advised to use it:
+ ```html
+ <div
+ v-for="item in items"
+ :key="item.id"
+ >
+ <!-- content -->
+ </div>
+ ```
+
+1. When the elements being iterated don't have a unique id, you can use the array index as the `:key` attribute
+ ```html
+ <div
+ v-for="(item, index) in items"
+ :key="index"
+ >
+ <!-- content -->
+ </div>
+ ```
+
+
+1. When using `v-for` with `template` and there is more than one child element, the `:key` values must be unique. It's advised to use `kebab-case` namespaces.
+ ```html
+ <template v-for="(item, index) in items">
+ <span :key="`span-${index}`"></span>
+ <button :key="`button-${index}`"></button>
+ </template>
+ ```
+
+1. When dealing with nested `v-for` use the same guidelines as above.
+ ```html
+ <div
+ v-for="item in items"
+ :key="item.id"
+ >
+ <span
+ v-for="element in array"
+ :key="element.id"
+ >
+ <!-- content -->
+ </span>
+ </div>
+ ```
+
+
+Useful links:
+1. [`key`](https://vuejs.org/v2/guide/list.html#key)
+1. [Vue Style Guide: Keyed v-for](https://vuejs.org/v2/style-guide/#Keyed-v-for-essential )
#### Vue and Bootstrap
1. Tooltips: Do not rely on `has-tooltip` class name for Vue components
diff --git a/doc/development/fe_guide/vue.md b/doc/development/fe_guide/vue.md
index 093a3ca4407..c1170fa3b13 100644
--- a/doc/development/fe_guide/vue.md
+++ b/doc/development/fe_guide/vue.md
@@ -53,13 +53,13 @@ you can find a clear separation of concerns:
```
new_feature
├── components
-│ └── component.js.es6
+│ └── component.vue
│ └── ...
-├── store
-│ └── new_feature_store.js.es6
-├── service
-│ └── new_feature_service.js.es6
-├── new_feature_bundle.js.es6
+├── stores
+│ └── new_feature_store.js
+├── services
+│ └── new_feature_service.js
+├── new_feature_bundle.js
```
_For consistency purposes, we recommend you to follow the same structure._
diff --git a/doc/development/i18n/externalization.md b/doc/development/i18n/externalization.md
index c0ce49eb40b..856ef882453 100644
--- a/doc/development/i18n/externalization.md
+++ b/doc/development/i18n/externalization.md
@@ -45,7 +45,7 @@ We basically have 4 types of files:
1. Ruby files: basically Models and Controllers.
1. HAML files: these are the view files.
1. ERB files: used for email templates.
-1. JavaScript files: we mostly need to work with VUE JS templates.
+1. JavaScript files: we mostly need to work with Vue templates.
### Ruby files
diff --git a/doc/development/i18n/proofreader.md b/doc/development/i18n/proofreader.md
index b732cc65b73..960eabd5538 100644
--- a/doc/development/i18n/proofreader.md
+++ b/doc/development/i18n/proofreader.md
@@ -17,10 +17,13 @@ are very appreciative of the work done by translators and proofreaders!
- French
- Rémy Coutable - [GitLab](https://gitlab.com/rymai), [Crowdin](https://crowdin.com/profile/rymai)
- German
+- Indonesian
+ - 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)
- Japanese
- Korean
+ - Chang-Ho Cha - [GitLab](https://gitlab.com/changho-cha), [Crowdin](https://crowdin.com/profile/zzazang)
- Huang Tao - [GitLab](https://gitlab.com/htve), [Crowdin](https://crowdin.com/profile/htve)
- Polish
- Filip Mech - [GitLab](https://gitlab.com/mehenz), [Crowdin](https://crowdin.com/profile/mehenz)
diff --git a/doc/development/new_fe_guide/development/security.md b/doc/development/new_fe_guide/development/security.md
index debda7de0c6..5bb38f17988 100644
--- a/doc/development/new_fe_guide/development/security.md
+++ b/doc/development/new_fe_guide/development/security.md
@@ -1,3 +1,14 @@
# Security
-> TODO: Add content
+## Avoid inline scripts and styles
+
+Inline scripts and styles should be avoided in almost all cases. In an effort to protect users from [XSS vulnerabilities](https://en.wikipedia.org/wiki/Cross-site_scripting), we will be disabling inline scripts using Content Security Policy.
+
+## Including external resources
+
+External fonts, CSS, and JavaScript should never be used with the exception of Google Analytics and Piwik - and only when the instance has enabled it. Assets should always be hosted and served locally from the GitLab instance. Embedded resources via `iframes` should never be used except in certain circumstances such as with ReCaptcha, which cannot be used without an `iframe`.
+
+## Resources for security testing
+
+- [Mozilla's HTTP Observatory CLI](https://github.com/mozilla/http-observatory-cli)
+- [Qualys SSL Labs Server Test](https://www.ssllabs.com/ssltest/analyze.html)
diff --git a/doc/development/new_fe_guide/index.md b/doc/development/new_fe_guide/index.md
index 08c6a266e7f..78931defa24 100644
--- a/doc/development/new_fe_guide/index.md
+++ b/doc/development/new_fe_guide/index.md
@@ -19,7 +19,7 @@ Guidance on topics related to development.
Learn about all the dependencies that make up our frontend, including some of our own custom built libraries.
-## [Style](style/index.md)
+## [Style guides](style/index.md)
Style guides to keep our code consistent.
diff --git a/doc/development/new_fe_guide/principles.md b/doc/development/new_fe_guide/principles.md
index 2126d202a7e..0af5f506a91 100644
--- a/doc/development/new_fe_guide/principles.md
+++ b/doc/development/new_fe_guide/principles.md
@@ -1,3 +1,35 @@
# Principles
-> TODO: Add principles
+These principles will ensure that your frontend contribution starts off in the right direction.
+
+## Discuss architecture before implementation
+
+Discuss your architecture design in an issue before writing code. This helps decrease the review time and also provides good practice for writing and thinking about system design.
+
+## Be consistent
+
+There are multiple ways of writing code to accomplish the same results. We should be as consistent as possible in how we write code across our codebases. This will make it more easier us to maintain our code across GitLab.
+
+## Enhance progressively
+
+Whenever you see with existing code that does not follow our current style guide, update it proactively. Refrain from changing everything but each merge request should progressively enhance our codebase and reduce technical debt.
+
+## When to use Vue
+
+- Use Vue for feature that make use of heavy DOM manipulation
+- Use Vue for reusable components
+
+## When to use jQuery
+
+- Use jQuery to interact with Bootstrap JavaScript components
+- Avoid jQuery when a better alternative exists. We are slowly moving away from it [#43559][jquery-future]
+
+## Mixing Vue and jQuery
+
+- Mixing Vue and jQuery is not recommended.
+- If you need to use a specific jQuery plugin in Vue, [create a wrapper around it][select2].
+- It is acceptable for Vue to listen to existing jQuery events using jQuery event listeners.
+- It is not recommended to add new jQuery events for Vue to interact with jQuery.
+
+[jquery-future]: https://gitlab.com/gitlab-org/gitlab-ce/issues/43559
+[select2]: https://vuejs.org/v2/examples/select2.html
diff --git a/doc/development/new_fe_guide/style/html.md b/doc/development/new_fe_guide/style/html.md
index 5489def5d6e..2d5b7d048ab 100644
--- a/doc/development/new_fe_guide/style/html.md
+++ b/doc/development/new_fe_guide/style/html.md
@@ -1,3 +1,53 @@
# HTML style guide
-> TODO: Add content
+## 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].
+
+```
+// bad
+<button></button>
+
+// good
+<button type="button"></button>
+```
+
+<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].
+
+```
+// bad
+<div onClick="doSomething"></div>
+
+// good
+<div role="button" onClick="doSomething"></div>
+```
+
+## 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
+
+```
+// bad
+<a href="url" target="_blank"></a>
+
+// good
+<a href="url" target="_blank" rel="noopener noreferrer"></a>
+```
+
+<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.
+
+```
+// bad
+<a class="js-do-something" href="#"></a>
+
+// good
+<button class="js-do-something" type="button"></button>
+```
+
+[button-type-spec]: https://www.w3.org/TR/2011/WD-html5-20110525/the-button-element.html#dom-button-type
+[button-role-accessible]: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_button_role
+[jitbit-target-blank]: https://www.jitbit.com/alexblog/256-targetblank---the-most-underestimated-vulnerability-ever/
diff --git a/doc/development/new_fe_guide/style/index.md b/doc/development/new_fe_guide/style/index.md
index d2d576b3b46..335d9e66240 100644
--- a/doc/development/new_fe_guide/style/index.md
+++ b/doc/development/new_fe_guide/style/index.md
@@ -1,4 +1,4 @@
-# Style
+# Style guides
## [HTML style guide](html.md)
@@ -7,3 +7,9 @@
## [JavaScript style guide](javascript.md)
## [Vue style guide](vue.md)
+
+# Tooling
+
+## [Prettier](prettier.md)
+
+Our code is automatically formatted with [Prettier](https://prettier.io) to follow our guidelines.
diff --git a/doc/development/new_fe_guide/style/prettier.md b/doc/development/new_fe_guide/style/prettier.md
new file mode 100644
index 00000000000..eb18189282b
--- /dev/null
+++ b/doc/development/new_fe_guide/style/prettier.md
@@ -0,0 +1,45 @@
+# Formatting with Prettier
+
+Our code is automatically formatted with [Prettier](https://prettier.io) to follow our style guides. Prettier is taking care of formatting .js, .vue, and .scss files based on the standard prettier rules. You can find all settings for Prettier in `.prettierrc`.
+
+## Editor
+
+The easiest way to include prettier in your workflow is by setting up your preferred editor (all major editors are supported) accordingly. We suggest setting up prettier to run automatically when each file is saved. Find [here](https://prettier.io/docs/en/editors.html) the best way to set it up in your preferred editor.
+
+Please take care that you only let Prettier format the same file types as the global Yarn script does (.js, .vue, and .scss). In VSCode by example you can easily exclude file formats in your settings file:
+
+```
+ "prettier.disableLanguages": [
+ "json",
+ "markdown"
+ ],
+```
+
+## Yarn Script
+
+The following yarn scripts are available to do global formatting:
+
+```
+yarn prettier-staged-save
+```
+
+Updates all currently staged files (based on `git diff`) with Prettier and saves the needed changes.
+
+```
+yarn prettier-staged
+```
+Checks all currently staged files (based on `git diff`) with Prettier and log which files would need manual updating to the console.
+
+```
+yarn prettier-all
+```
+
+Checks all files with Prettier and logs which files need manual updating to the console.
+
+```
+yarn prettier-all-save
+```
+
+Formats all files in the repository with Prettier. (This should only be used to test global rule updates otherwise you would end up with huge MR's).
+
+The source of these Yarn scripts can be found in `/scripts/frontend/prettier.js`.
diff --git a/doc/development/writing_documentation.md b/doc/development/writing_documentation.md
index 40c21e5355c..d6a13e7483a 100644
--- a/doc/development/writing_documentation.md
+++ b/doc/development/writing_documentation.md
@@ -1,13 +1,9 @@
-# Writing documentation
+# GitLab Documentation guidelines
- **General Documentation**: written by the [developers responsible by creating features](#contributing-to-docs). Should be submitted in the same merge request containing code. Feature proposals (by GitLab contributors) should also be accompanied by its respective documentation. They can be later improved by PMs and Technical Writers.
- **[Technical Articles](#technical-articles)**: written by any [GitLab Team](https://about.gitlab.com/team/) member, GitLab contributors, or [Community Writers](https://about.gitlab.com/handbook/product/technical-writing/community-writers/).
- **Indexes per topic**: initially prepared by the Technical Writing Team, and kept up-to-date by developers and PMs in the same merge request containing code. They gather all resources for that topic in a single page (user and admin documentation, articles, and third-party docs).
-## Documentation style guidelines
-
-All the docs follow the same [styleguide](doc_styleguide.md).
-
## Contributing to docs
Whenever a feature is changed, updated, introduced, or deprecated, the merge
@@ -29,23 +25,16 @@ Whenever you submit a merge request for the documentation, use the documentation
Please check the [documentation workflow](https://about.gitlab.com/handbook/product/technical-writing/workflow/) before getting started.
-### Documentation directory structure
-
-The documentation is structured based on the GitLab UI structure itself,
-separated by [`user`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/user),
-[`administrator`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/administration), and [`contributor`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/development).
-
-To learn where to place a new document, check the [documentation style guide](doc_styleguide.md#location-and-naming-of-documents).
+## Documentation structure
-In order to have a [solid site structure](https://searchengineland.com/seo-benefits-developing-solid-site-structure-277456) for our documentation,
-all docs should be linked. Every new document should be cross-linked to its related documentation, and linked from its topic-related index, when existent.
+- Overview and use cases: what it is, why it is necessary, why one would use it
+- Requirements: what do we need to get started
+- Tutorial: how to set it up, how to use it
-The directories `/workflow/`, `/gitlab-basics/`, `/university/`, and `/articles/` have
-been deprecated and the majority their docs have been moved to their correct location
-in small iterations. Don't create new docs in these folders.
+Always link a new document from its topic-related index, otherwise, it will
+not be included it in the documentation site search.
-To move a document from its location to another directory, read the section
-[changing document location](doc_styleguide.md#changing-document-location) of the doc style guide.
+_Note: to be extended._
### Feature overview and use cases
@@ -75,16 +64,169 @@ overview there.
> **Overview** and **use cases** are required to **every** Enterprise Edition feature,
and for every **major** feature present in Community Edition.
-### Markdown
+## Markdown and styles
Currently GitLab docs use Redcarpet as [markdown](../user/markdown.md) engine, but there's an [open discussion](https://gitlab.com/gitlab-com/gitlab-docs/issues/50) for implementing Kramdown in the near future.
-### Previewing locally
+All the docs follow the [documentation style guidelines](doc_styleguide.md).
-To preview your changes to documentation locally, please follow
-this [development guide](https://gitlab.com/gitlab-com/gitlab-docs/blob/master/README.md#development).
+## Documentation directory structure
+
+The documentation is structured based on the GitLab UI structure itself,
+separated by [`user`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/user),
+[`administrator`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/administration), and [`contributor`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/development).
+
+In order to have a [solid site structure](https://searchengineland.com/seo-benefits-developing-solid-site-structure-277456) for our documentation,
+all docs should be linked. Every new document should be cross-linked to its related documentation, and linked from its topic-related index, when existent.
+
+The directories `/workflow/`, `/gitlab-basics/`, `/university/`, and `/articles/` have
+been deprecated and the majority their docs have been moved to their correct location
+in small iterations. Please don't create new docs in these folders.
+
+### Location and naming documents
+
+The documentation hierarchy can be vastly improved by providing a better layout
+and organization of directories.
+
+Having a structured document layout, we will be able to have meaningful URLs
+like `docs.gitlab.com/user/project/merge_requests/index.html`. With this pattern,
+you can immediately tell that you are navigating a user related documentation
+and is about the project and its merge requests.
+
+Do not create summaries of similar types of content (e.g. an index of all articles, videos, etc.),
+rather organize content by its subject (e.g. everything related to CI goes together)
+and cross-link between any related content.
+
+The table below shows what kind of documentation goes where.
+
+| Directory | What belongs here |
+| --------- | -------------- |
+| `doc/user/` | User related documentation. Anything that can be done within the GitLab UI goes here including `/admin`. |
+| `doc/administration/` | Documentation that requires the user to have access to the server where GitLab is installed. The admin settings that can be accessed via GitLab's interface go under `doc/user/admin_area/`. |
+| `doc/api/` | API related documentation. |
+| `doc/development/` | Documentation related to the development of GitLab. Any styleguides should go here. |
+| `doc/legal/` | Legal documents about contributing to GitLab. |
+| `doc/install/`| Probably the most visited directory, since `installation.md` is there. Ideally this should go under `doc/administration/`, but it's best to leave it as-is in order to avoid confusion (still debated though). |
+| `doc/update/` | Same with `doc/install/`. Should be under `administration/`, but this is a well known location, better leave as-is, at least for now. |
+| `doc/topics/` | Indexes per Topic (`doc/topics/topic-name/index.md`): all resources for that topic (user and admin documentation, articles, and third-party docs) |
+
+---
+
+**General rules:**
+
+1. The correct naming and location of a new document, is a combination
+ of the relative URL of the document in question and the GitLab Map design
+ that is used for UX purposes ([source][graffle], [image][gitlab-map]).
+1. When creating a new document and it has more than one word in its name,
+ make sure to use underscores instead of spaces or dashes (`-`). For example,
+ a proper naming would be `import_projects_from_github.md`. The same rule
+ applies to images.
+1. Start a new directory with an `index.md` file.
+1. There are four main directories, `user`, `administration`, `api` and `development`.
+1. The `doc/user/` directory has five main subdirectories: `project/`, `group/`,
+ `profile/`, `dashboard/` and `admin_area/`.
+ 1. `doc/user/project/` should contain all project related documentation.
+ 1. `doc/user/group/` should contain all group related documentation.
+ 1. `doc/user/profile/` should contain all profile related documentation.
+ Every page you would navigate under `/profile` should have its own document,
+ i.e. `account.md`, `applications.md`, `emails.md`, etc.
+ 1. `doc/user/dashboard/` should contain all dashboard related documentation.
+ 1. `doc/user/admin_area/` should contain all admin related documentation
+ describing what can be achieved by accessing GitLab's admin interface
+ (_not to be confused with `doc/administration` where server access is
+ required_).
+ 1. Every category under `/admin/application_settings` should have its
+ own document located at `doc/user/admin_area/settings/`. For example,
+ the **Visibility and Access Controls** category should have a document
+ located at `doc/user/admin_area/settings/visibility_and_access_controls.md`.
+1. The `doc/topics/` directory holds topic-related technical content. Create
+ `doc/topics/topic-name/subtopic-name/index.md` when subtopics become necessary.
+ General user- and admin- related documentation, should be placed accordingly.
+
+If you are unsure where a document should live, you can ping `@axil` or `@marcia` in your
+merge request.
+
+### Changing document location
+
+Changing a document's location is not to be taken lightly. Remember that the
+documentation is available to all installations under `help/` and not only to
+GitLab.com or http://docs.gitlab.com. Make sure this is discussed with the
+Documentation team beforehand.
+
+If you indeed need to change a document's location, do NOT remove the old
+document, but rather replace all of its contents with a new line:
+
+```
+This document was moved to [another location](path/to/new_doc.md).
+```
+
+where `path/to/new_doc.md` is the relative path to the root directory `doc/`.
+
+---
+
+For example, if you were to move `doc/workflow/lfs/lfs_administration.md` to
+`doc/administration/lfs.md`, then the steps would be:
+
+1. Copy `doc/workflow/lfs/lfs_administration.md` to `doc/administration/lfs.md`
+1. Replace the contents of `doc/workflow/lfs/lfs_administration.md` with:
+
+ ```
+ This document was moved to [another location](../../administration/lfs.md).
+ ```
+
+1. Find and replace any occurrences of the old location with the new one.
+ A quick way to find them is to use `git grep`. First go to the root directory
+ where you cloned the `gitlab-ce` repository and then do:
-### Testing
+ ```
+ git grep -n "workflow/lfs/lfs_administration"
+ git grep -n "lfs/lfs_administration"
+ ```
+
+NOTE: **Note:**
+If the document being moved has any Disqus comments on it, there are extra steps
+to follow documented just [below](#redirections-for-pages-with-disqus-comments).
+
+Things to note:
+
+- Since we also use inline documentation, except for the documentation itself,
+ the document might also be referenced in the views of GitLab (`app/`) which will
+ render when visiting `/help`, and sometimes in the testing suite (`spec/`).
+- The above `git grep` command will search recursively in the directory you run
+ it in for `workflow/lfs/lfs_administration` and `lfs/lfs_administration`
+ and will print the file and the line where this file is mentioned.
+ You may ask why the two greps. Since we use relative paths to link to
+ documentation, sometimes it might be useful to search a path deeper.
+- The `*.md` extension is not used when a document is linked to GitLab's
+ built-in help page, that's why we omit it in `git grep`.
+- Use the checklist on the documentation MR description template.
+
+### Redirections for pages with Disqus comments
+
+If the documentation page being relocated already has any Disqus comments,
+we need to preserve the Disqus thread.
+
+Disqus uses an identifier per page, and for docs.gitlab.com, the page identifier
+is configured to be the page URL. Therefore, when we change the document location,
+we need to preserve the old URL as the same Disqus identifier.
+
+To do that, add to the frontmatter the variable `redirect_from`,
+using the old URL as value. For example, let's say I moved the document
+available under `https://docs.gitlab.com/my-old-location/README.html` to a new location,
+`https://docs.gitlab.com/my-new-location/index.html`.
+
+Into the **new document** frontmatter add the following:
+
+```yaml
+---
+redirect_from: 'https://docs.gitlab.com/my-old-location/README.html'
+---
+```
+
+Note: it is necessary to include the file name in the `redirect_from` URL,
+even if it's `index.html` or `README.html`.
+
+## Testing
We treat documentation as code, thus have implemented some testing.
Currently, the following tests are in place:
@@ -103,7 +245,7 @@ Currently, the following tests are in place:
Submitting an EE-equivalent merge request cherry-picking all commits from CE to EE is
essential to avoid them.
-### Branch naming
+## Branch naming
If your contribution contains **only** documentation changes, you can speed up
the CI process by following some branch naming conventions. You have three
@@ -118,7 +260,7 @@ choices:
If your branch name matches any of the above, it will run only the docs
tests. If it doesn't, the whole test suite will run (including docs).
-### Merge requests for GitLab documentation
+## Merge requests for GitLab documentation
Before getting started, make sure you read the introductory section
"[contributing to docs](#contributing-to-docs)" above and the
@@ -137,7 +279,7 @@ frozen or released, use the label `Pick into X.Y` to get it merged into
the correct release. Avoid picking into a past release as much as you can, as
it increases the work of the release managers.
-#### Cherry-picking from CE to EE
+### Cherry-picking from CE to EE
As we have the `master` branch of CE merged into EE once a day, it's common to
run into merge conflicts. To avoid them, we [test for merge conflicts against EE](#testing)
@@ -161,7 +303,10 @@ a couple more commits to the EE branch, but ask the reviewer to review the EE me
additionally to the CE MR. If there are many EE-only changes though, start a new MR
to EE only.
-### Previewing the changes live
+## Previewing the changes live
+
+To preview your changes to documentation locally, please follow
+this [development guide](https://gitlab.com/gitlab-com/gitlab-docs/blob/master/README.md#development).
If you want to preview the doc changes of your merge request live, you can use
the manual `review-docs-deploy` job in your merge request. You will need at
@@ -221,7 +366,7 @@ working on. If you don't, the remote docs branch won't be removed either,
and the server where the Review Apps are hosted will eventually be out of
disk space.
-#### Technical aspects
+### Technical aspects
If you want to know the hot details, here's what's really happening:
@@ -256,6 +401,74 @@ The following GitLab features are used among others:
- [Artifacts](../ci/yaml/README.md#artifacts)
- [Specific Runner](../ci/runners/README.md#locking-a-specific-runner-from-being-enabled-for-other-projects)
+## GitLab `/help`
+
+Every GitLab instance includes the documentation, which is available from `/help`
+(`http://my-instance.com/help`), e.g., <https://gitlab.com/help>.
+
+When you're building a new feature, you may need to link the documentation
+from GitLab, the application. This is normally done in files inside the
+`app/views/` directory with the help of the `help_page_path` helper method.
+
+In its simplest form, the HAML code to generate a link to the `/help` page is:
+
+```haml
+= link_to 'Help page', help_page_path('user/permissions')
+```
+
+The `help_page_path` contains the path to the document you want to link to with
+the following conventions:
+
+- it is relative to the `doc/` directory in the GitLab repository
+- the `.md` extension must be omitted
+- it must not end with a slash (`/`)
+
+Below are some special cases where should be used depending on the context.
+You can combine one or more of the following:
+
+1. **Linking to an anchor link.** Use `anchor` as part of the `help_page_path`
+ method:
+
+ ```haml
+ = link_to 'Help page', help_page_path('user/permissions', anchor: 'anchor-link')
+ ```
+
+1. **Opening links in a new tab.** This should be the default behavior:
+
+ ```haml
+ = link_to 'Help page', help_page_path('user/permissions'), target: '_blank'
+ ```
+
+1. **Linking to a circle icon.** Usually used in settings where a long
+ description cannot be used, like near checkboxes. You can basically use
+ any font awesome icon, but prefer the `question-circle`:
+
+ ```haml
+ = link_to icon('question-circle'), help_page_path('user/permissions')
+ ```
+
+1. **Using a button link.** Useful in places where text would be out of context
+ with the rest of the page layout:
+
+ ```haml
+ = link_to 'Help page', help_page_path('user/permissions'), class: 'btn btn-info'
+ ```
+
+1. **Using links inline of some text.**
+
+ ```haml
+ Description to #{link_to 'Help page', help_page_path('user/permissions')}.
+ ```
+
+1. **Adding a period at the end of the sentence.** Useful when you don't want
+ the period to be part of the link:
+
+ ```haml
+ = succeed '.' do
+ Learn more in the
+ = link_to 'Help page', help_page_path('user/permissions')
+ ```
+
## General Documentation vs Technical Articles
### General documentation
@@ -270,7 +483,7 @@ They are topic-related documentation, written with an user-friendly approach and
A technical article guides users and/or admins to achieve certain objectives (within guides and tutorials), or provide an overview of that particular topic or feature (within technical overviews). It can also describe the use, implementation, or integration of third-party tools with GitLab.
-They should be placed in a new directory named `/article-title/index.md` under a topic-related folder, and their images should be placed in `/article-title/img/`. For example, a new article on GitLab Pages should be placed in `doc/user/project/pages/article-title/` and a new article on GitLab CI/CD should be placed in `doc/ci/article-title/`.
+They should be placed in a new directory named `/article-title/index.md` under a topic-related folder, and their images should be placed in `/article-title/img/`. For example, a new article on GitLab Pages should be placed in `doc/user/project/pages/article-title/` and a new article on GitLab CI/CD should be placed in `doc/ci/examples/article-title/`.
#### Types of Technical Articles
@@ -324,3 +537,5 @@ date: 2017-02-01
Use the [writing method](https://about.gitlab.com/handbook/product/technical-writing/#writing-method) defined by the Technical Writing team.
+[gitlab-map]: https://gitlab.com/gitlab-org/gitlab-design/raw/master/production/resources/gitlab-map.png
+[graffle]: https://gitlab.com/gitlab-org/gitlab-design/blob/d8d39f4a87b90fb9ae89ca12dc565347b4900d5e/production/resources/gitlab-map.graffle
diff --git a/doc/downgrade_ee_to_ce/README.md b/doc/downgrade_ee_to_ce/README.md
index 75bae324585..f656057e3da 100644
--- a/doc/downgrade_ee_to_ce/README.md
+++ b/doc/downgrade_ee_to_ce/README.md
@@ -70,7 +70,7 @@ To downgrade an Omnibus installation, it is sufficient to install the Community
Edition package on top of the currently installed one. You can do this manually,
by directly [downloading the package](https://packages.gitlab.com/gitlab/gitlab-ce)
you need, or by adding our CE package repository and following the
-[CE installation instructions](https://about.gitlab.com/downloads/).
+[CE installation instructions](https://about.gitlab.com/installation/?version=ce).
**Source Installation**
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 170d92faa09..1abbfd78738 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -162,13 +162,14 @@ page](https://golang.org/dl).
## 4. Node
-Since GitLab 8.17, GitLab requires the use of node >= v4.3.0 to compile
-javascript assets, and yarn >= v0.17.0 to manage javascript dependencies.
-In many distros the versions provided by the official package repositories
-are out of date, so we'll need to install through the following commands:
-
- # install node v7.x
- curl --location https://deb.nodesource.com/setup_7.x | sudo bash -
+Since GitLab 8.17, GitLab requires the use of Node to compile javascript
+assets, and Yarn to manage javascript dependencies. The current minimum
+requirements for these are node >= v6.0.0 and yarn >= v1.2.0. In many distros
+the versions provided by the official package repositories are out of date, so
+we'll need to install through the following commands:
+
+ # install node v8.x
+ curl --location https://deb.nodesource.com/setup_8.x | sudo bash -
sudo apt-get install -y nodejs
curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
@@ -299,9 +300,9 @@ sudo usermod -aG redis git
### Clone the Source
# Clone GitLab repository
- sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 10-5-stable gitlab
+ sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 10-6-stable gitlab
-**Note:** You can change `10-5-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
+**Note:** You can change `10-6-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It
diff --git a/doc/install/openshift_and_gitlab/index.md b/doc/install/openshift_and_gitlab/index.md
index 1f46ee4c1ea..e6ccfccd33f 100644
--- a/doc/install/openshift_and_gitlab/index.md
+++ b/doc/install/openshift_and_gitlab/index.md
@@ -464,7 +464,9 @@ bother us. In any case, it is something to keep in mind when deploying GitLab
on a production cluster.
In order to deploy GitLab on a production cluster, you will need to assign the
-GitLab service account to the `anyuid` Security Context.
+GitLab service account to the `anyuid` [Security Context Constraints][scc].
+
+For OpenShift v3.0, you will need to do this manually:
1. Edit the Security Context:
```sh
@@ -477,6 +479,12 @@ GitLab service account to the `anyuid` Security Context.
1. Save and exit the editor
+For OpenShift v3.1 and above, you can do:
+
+```sh
+oc adm policy add-scc-to-user anyuid system:serviceaccount:gitlab:gitlab-ce-user
+```
+
## Conclusion
By now, you should have an understanding of the basic OpenShift Origin concepts
@@ -513,3 +521,4 @@ PaaS and managing your applications with the ease of containers.
[autoscaling]: https://docs.openshift.org/latest/dev_guide/pod_autoscaling.html "Documentation - Autoscale"
[basic-cli]: https://docs.openshift.org/latest/cli_reference/basic_cli_operations.html "Documentation - Basic CLI operations"
[openshift-docs]: https://docs.openshift.org "OpenShift documentation"
+[scc]: https://docs.openshift.org/latest/admin_guide/manage_scc.html "Documentation - Managing Security Context Constraints" \ No newline at end of file
diff --git a/doc/integration/slash_commands.md b/doc/integration/slash_commands.md
index 36a8844e953..7d73026a6c6 100644
--- a/doc/integration/slash_commands.md
+++ b/doc/integration/slash_commands.md
@@ -15,9 +15,10 @@ Taking the trigger term as `project-name`, the commands are:
| `/project-name issue new <title> <shift+return> <description>` | Creates a new issue with title `<title>` and description `<description>` |
| `/project-name issue show <id>` | Shows the issue with id `<id>` |
| `/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 |
-Note that if you are using the [GitLab Slack application](https://docs.gitlab.com/ee/user/project/integrations/gitlab_slack_application.html) for
+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).
## Issue commands
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index 5f5ba2b69bc..4dc3adc1441 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -20,7 +20,7 @@ project in an easy and automatic way:
1. [Auto Test](#auto-test)
1. [Auto Code Quality](#auto-code-quality)
1. [Auto SAST (Static Application Security Testing)](#auto-sast)
-1. [Auto SAST for Docker images](#auto-sast-for-docker-images)
+1. [Auto Container Scanning](#auto-container-scanning)
1. [Auto Review Apps](#auto-review-apps)
1. [Auto DAST (Dynamic Application Security Testing)](#auto-dast)
1. [Auto Deploy](#auto-deploy)
@@ -217,7 +217,7 @@ check out.
In GitLab Ultimate, any security warnings are also
[shown in the merge request widget](https://docs.gitlab.com/ee/user/project/merge_requests/sast.html).
-### Auto SAST for Docker images
+### Auto Container Scanning
> Introduced in GitLab 10.4.
@@ -228,7 +228,7 @@ created, it's uploaded as an artifact which you can later download and
check out.
In GitLab Ultimate, any security warnings are also
-[shown in the merge request widget](https://docs.gitlab.com/ee/user/project/merge_requests/sast_docker.html).
+[shown in the merge request widget](https://docs.gitlab.com/ee/user/project/merge_requests/container_scanning.html).
### Auto Review Apps
@@ -309,6 +309,18 @@ enable them.
You can make use of [environment variables](#helm-chart-variables) to automatically
scale your pod replicas.
+It's important to note that when a project is deployed to a Kubernetes cluster,
+it relies on a Docker image that has been pushed to the
+[GitLab Container Registry](../../user/project/container_registry.md). Kubernetes
+fetches this image and uses it to run the application. If the project is public,
+the image can be accessed by Kubernetes without any authentication, allowing us
+to have deployments more usable. If the project is private/internal, the
+Registry requires credentials to pull the image. Currently, this is addressed
+by providing `CI_JOB_TOKEN` as the password that can be used, but this token will
+no longer be valid as soon as the deployment job finishes. This means that
+Kubernetes can run the application, but in case it should be restarted or
+executed somewhere else, it cannot be accessed again.
+
### Auto Monitoring
NOTE: **Note:**
diff --git a/doc/update/10.5-to-10.6.md b/doc/update/10.5-to-10.6.md
new file mode 100644
index 00000000000..f5c5c305726
--- /dev/null
+++ b/doc/update/10.5-to-10.6.md
@@ -0,0 +1,361 @@
+---
+comments: false
+---
+
+# From 10.5 to 10.6
+
+Make sure you view this update guide from the tag (version) of GitLab you would
+like to install. In most cases this should be the highest numbered production
+tag (without rc in it). You can select the tag in the version dropdown at the
+top left corner of GitLab (below the menu bar).
+
+If the highest number stable branch is unclear please check the
+[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation
+guide links by version.
+
+### 1. Stop server
+
+```bash
+sudo service gitlab stop
+```
+
+### 2. Backup
+
+NOTE: If you installed GitLab from source, make sure `rsync` is installed.
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 3. Update Ruby
+
+NOTE: GitLab 9.0 and higher only support Ruby 2.3.x and dropped support for Ruby 2.1.x. Be
+sure to upgrade your interpreter if necessary.
+
+You can check which version you are running with `ruby -v`.
+
+Download and compile Ruby:
+
+```bash
+mkdir /tmp/ruby && cd /tmp/ruby
+curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.6.tar.gz
+echo '4e6a0f828819e15d274ae58485585fc8b7caace0 ruby-2.3.6.tar.gz' | shasum -c - && tar xzf ruby-2.3.6.tar.gz
+cd ruby-2.3.6
+./configure --disable-install-rdoc
+make
+sudo make install
+```
+
+Install Bundler:
+
+```bash
+sudo gem install bundler --no-ri --no-rdoc
+```
+
+### 4. Update Node
+
+GitLab utilizes [webpack](http://webpack.js.org) to compile frontend assets.
+This requires a minimum version of node v6.0.0.
+
+You can check which version you are running with `node -v`. If you are running
+a version older than `v6.0.0` you will need to update to a newer version. You
+can find instructions to install from community maintained packages or compile
+from source at the nodejs.org website.
+
+<https://nodejs.org/en/download/>
+
+GitLab also requires the use of yarn `>= v1.2.0` to manage JavaScript
+dependencies.
+
+```bash
+curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
+echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
+sudo apt-get update
+sudo apt-get install yarn
+```
+
+More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install).
+
+### 5. Update Go
+
+NOTE: GitLab 9.2 and higher only supports Go 1.8.3 and dropped support for Go
+1.5.x through 1.7.x. Be sure to upgrade your installation if necessary.
+
+You can check which version you are running with `go version`.
+
+Download and install Go:
+
+```bash
+# Remove former Go installation folder
+sudo rm -rf /usr/local/go
+
+curl --remote-name --progress https://storage.googleapis.com/golang/go1.8.3.linux-amd64.tar.gz
+echo '1862f4c3d3907e59b04a757cfda0ea7aa9ef39274af99a784f5be843c80c6772 go1.8.3.linux-amd64.tar.gz' | shasum -a256 -c - && \
+ sudo tar -C /usr/local -xzf go1.8.3.linux-amd64.tar.gz
+sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/
+rm go1.8.3.linux-amd64.tar.gz
+```
+
+### 6. Get latest code
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+sudo -u git -H git checkout -- locale
+```
+
+For GitLab Community Edition:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H git checkout 10-6-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H git checkout 10-6-stable-ee
+```
+
+### 7. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+
+sudo -u git -H git fetch --all --tags
+sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_SHELL_VERSION)
+sudo -u git -H bin/compile
+```
+
+### 8. Update gitlab-workhorse
+
+Install and compile gitlab-workhorse. GitLab-Workhorse uses
+[GNU Make](https://www.gnu.org/software/make/).
+If you are not using Linux you may have to run `gmake` instead of
+`make` below.
+
+```bash
+cd /home/git/gitlab-workhorse
+
+sudo -u git -H git fetch --all --tags
+sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_WORKHORSE_VERSION)
+sudo -u git -H make
+```
+
+### 9. Update Gitaly
+
+#### New Gitaly configuration options required
+
+In order to function Gitaly needs some additional configuration information. Below we assume you installed Gitaly in `/home/git/gitaly` and GitLab Shell in `/home/git/gitlab-shell`.
+
+```shell
+echo '
+[gitaly-ruby]
+dir = "/home/git/gitaly/ruby"
+
+[gitlab-shell]
+dir = "/home/git/gitlab-shell"
+' | sudo -u git tee -a /home/git/gitaly/config.toml
+```
+
+#### Check Gitaly configuration
+
+Due to a bug in the `rake gitlab:gitaly:install` script your Gitaly
+configuration file may contain syntax errors. The block name
+`[[storages]]`, which may occur more than once in your `config.toml`
+file, should be `[[storage]]` instead.
+
+```shell
+sudo -u git -H sed -i.pre-10.1 's/\[\[storages\]\]/[[storage]]/' /home/git/gitaly/config.toml
+```
+
+#### Compile Gitaly
+
+```shell
+cd /home/git/gitaly
+sudo -u git -H git fetch --all --tags
+sudo -u git -H git checkout v$(</home/git/gitlab/GITALY_SERVER_VERSION)
+sudo -u git -H make
+```
+
+### 10. Update MySQL permissions
+
+If you are using MySQL you need to grant the GitLab user the necessary
+permissions on the database:
+
+```bash
+mysql -u root -p -e "GRANT TRIGGER ON \`gitlabhq_production\`.* TO 'git'@'localhost';"
+```
+
+If you use MySQL with replication, or just have MySQL configured with binary logging,
+you will need to also run the following on all of your MySQL servers:
+
+```bash
+mysql -u root -p -e "SET GLOBAL log_bin_trust_function_creators = 1;"
+```
+
+You can make this setting permanent by adding it to your `my.cnf`:
+
+```
+log_bin_trust_function_creators=1
+```
+
+### 11. Update configuration files
+
+#### New configuration options for `gitlab.yml`
+
+There might be configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them manually to your current `gitlab.yml`:
+
+```sh
+cd /home/git/gitlab
+
+git diff origin/10-5-stable:config/gitlab.yml.example origin/10-6-stable:config/gitlab.yml.example
+```
+
+#### Nginx configuration
+
+Ensure you're still up-to-date with the latest NGINX configuration changes:
+
+```sh
+cd /home/git/gitlab
+
+# For HTTPS configurations
+git diff origin/10-5-stable:lib/support/nginx/gitlab-ssl origin/10-6-stable:lib/support/nginx/gitlab-ssl
+
+# For HTTP configurations
+git diff origin/10-5-stable:lib/support/nginx/gitlab origin/10-6-stable:lib/support/nginx/gitlab
+```
+
+If you are using Strict-Transport-Security in your installation to continue using it you must enable it in your Nginx
+configuration as GitLab application no longer handles setting it.
+
+If you are using Apache instead of NGINX please see the updated [Apache templates].
+Also note that because Apache does not support upstreams behind Unix sockets you
+will need to let gitlab-workhorse listen on a TCP port. You can do this
+via [/etc/default/gitlab].
+
+[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache
+[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/10-6-stable/lib/support/init.d/gitlab.default.example#L38
+
+#### SMTP configuration
+
+If you're installing from source and use SMTP to deliver mail, you will need to add the following line
+to config/initializers/smtp_settings.rb:
+
+```ruby
+ActionMailer::Base.delivery_method = :smtp
+```
+
+See [smtp_settings.rb.sample] as an example.
+
+[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/10-6-stable/config/initializers/smtp_settings.rb.sample#L13
+
+#### Init script
+
+There might be new configuration options available for [`gitlab.default.example`][gl-example]. View them with the command below and apply them manually to your current `/etc/default/gitlab`:
+
+```sh
+cd /home/git/gitlab
+
+git diff origin/10-5-stable:lib/support/init.d/gitlab.default.example origin/10-6-stable:lib/support/init.d/gitlab.default.example
+```
+
+Ensure you're still up-to-date with the latest init script changes:
+
+```bash
+cd /home/git/gitlab
+
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+For Ubuntu 16.04.1 LTS:
+
+```bash
+sudo systemctl daemon-reload
+```
+
+### 12. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without postgres')
+sudo -u git -H bundle install --without postgres development test --deployment
+
+# PostgreSQL installations (note: the line below states '--without mysql')
+sudo -u git -H bundle install --without mysql development test --deployment
+
+# Optional: clean up old gems
+sudo -u git -H bundle clean
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Compile GetText PO files
+
+sudo -u git -H bundle exec rake gettext:compile RAILS_ENV=production
+
+# Update node dependencies and recompile assets
+sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile RAILS_ENV=production NODE_ENV=production
+
+# Clean up cache
+sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
+```
+
+**MySQL installations**: Run through the `MySQL strings limits` and `Tables and data conversion to utf8mb4` [tasks](../install/database_mysql.md).
+
+### 13. Start application
+
+```bash
+sudo service gitlab start
+sudo service nginx restart
+```
+
+### 14. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+```
+
+To make sure you didn't miss anything run a more thorough check:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+```
+
+If all items are green, then congratulations, the upgrade is complete!
+
+## Things went south? Revert to previous version (10.5)
+
+### 1. Revert the code to the previous version
+
+Follow the [upgrade guide from 10.4 to 10.5](10.4-to-10.5.md), except for the
+database migration (the backup is already migrated to the previous version).
+
+### 2. Restore from the backup
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+
+If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above.
+
+[yaml]: https://gitlab.com/gitlab-org/gitlab-ce/blob/10-6-stable/config/gitlab.yml.example
+[gl-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/10-6-stable/lib/support/init.d/gitlab.default.example
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index 914a80bcd6a..a520279c29e 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -25,7 +25,8 @@ The following table depicts the various user permission levels in a project.
| Create confidential issue | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
| View confidential issues | (✓) [^2] | ✓ | ✓ | ✓ | ✓ |
| Leave comments | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
-| Lock discussions (issues and merge requests) | | | | ✓ | ✓ |
+| Lock issue discussions | | ✓ | ✓ | ✓ | ✓ |
+| Lock merge request discussions | | | ✓ | ✓ | ✓ |
| See a list of jobs | ✓ [^3] | ✓ | ✓ | ✓ | ✓ |
| See a job log | ✓ [^3] | ✓ | ✓ | ✓ | ✓ |
| Download and browse job artifacts | ✓ [^3] | ✓ | ✓ | ✓ | ✓ |
diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md
index 550ad4a8b1f..d403d5698a9 100644
--- a/doc/user/project/issue_board.md
+++ b/doc/user/project/issue_board.md
@@ -245,6 +245,17 @@ navigation level. A group-level issue board allows you to view all issues from a
boards. When updating milestones and labels for an issue through the sidebar update mechanism, again only
group-level objects are available.
+## Features per tier
+
+Different issue board features are available in different [GitLab tiers](https://about.gitlab.com/pricing/), as shown in the following table:
+
+| Tier | Number of project issue boards | Board with configuration in project issue boards | Number of group issue boards | Board with configuration in group issue boards |
+| --- | --- | --- | --- | --- |
+| Libre | 1 | No | 1 | No |
+| Starter | Multiple | Yes | 1 | No |
+| Premium | Multiple | Yes | Multiple | Yes |
+| Ultimate | Multiple | Yes | Multiple | Yes |
+
## Tips
A few things to remember:
diff --git a/doc/user/project/members/share_project_with_groups.md b/doc/user/project/members/share_project_with_groups.md
index f5c748a03b3..5d819998dd9 100644
--- a/doc/user/project/members/share_project_with_groups.md
+++ b/doc/user/project/members/share_project_with_groups.md
@@ -16,19 +16,29 @@ say 'Project Acme', in GitLab is to make the 'Engineering' group the owner of 'P
Acme'. But what if 'Project Acme' already belongs to another group, say 'Open Source'?
This is where the group sharing feature can be of use.
-To share 'Project Acme' with the 'Engineering' group, go to the project settings page for 'Project Acme' and use the left navigation menu to go to the **Settings > Members** section.
+To share 'Project Acme' with the 'Engineering' group:
-![share project with groups](img/share_project_with_groups.png)
+1. For 'Project Acme' use the left navigation menu to go to **Settings > Members**
-Then select the 'Share with group' tab by clicking it.
+ ![share project with groups](img/share_project_with_groups.png)
-Now you can add the 'Engineering' group with the maximum access level of your choice. Click 'Share' to share it.
+1. Select the 'Share with group' tab
+1. Add the 'Engineering' group with the maximum access level of your choice
+1. Click **Share** to share it
-![share project with groups tab](img/share_project_with_groups_tab.png)
+ ![share project with groups tab](img/share_project_with_groups_tab.png)
-After sharing 'Project Acme' with 'Engineering', the project will be listed on the group dashboard.
+1. After sharing 'Project Acme' with 'Engineering', the project will be listed
+ on the group dashboard
-!['Project Acme' is listed as a shared project for 'Engineering'](img/other_group_sees_shared_project.png)
+ !['Project Acme' is listed as a shared project for 'Engineering'](img/other_group_sees_shared_project.png)
+
+Note that you can only share a project with:
+
+- groups for which you have an explicitly defined membership
+- groups that contain a nested subgroup or project for which you have an explicitly defined role
+
+Admins are able to share projects with any group in the system.
## Maximum access level
diff --git a/doc/user/project/merge_requests/img/allow_maintainer_push.png b/doc/user/project/merge_requests/img/allow_maintainer_push.png
index e73cc1ab5a5..91cc399f4ff 100644
--- a/doc/user/project/merge_requests/img/allow_maintainer_push.png
+++ b/doc/user/project/merge_requests/img/allow_maintainer_push.png
Binary files differ
diff --git a/doc/user/project/merge_requests/maintainer_access.md b/doc/user/project/merge_requests/maintainer_access.md
index 1ade5db1587..7feccc28f6b 100644
--- a/doc/user/project/merge_requests/maintainer_access.md
+++ b/doc/user/project/merge_requests/maintainer_access.md
@@ -1,4 +1,4 @@
-# Allow maintainer pushes for merge requests accross forks
+# Allow maintainer pushes for merge requests across forks
This feature is available for merge requests across forked projects that are
publicly accessible. It makes it easier for maintainers of projects to collaborate
diff --git a/doc/user/project/repository/img/jupyter_notebook.png b/doc/user/project/repository/img/jupyter_notebook.png
new file mode 100644
index 00000000000..52c5c5aea32
--- /dev/null
+++ b/doc/user/project/repository/img/jupyter_notebook.png
Binary files differ
diff --git a/doc/user/project/repository/index.md b/doc/user/project/repository/index.md
index e6aede7f46e..ae131d51305 100644
--- a/doc/user/project/repository/index.md
+++ b/doc/user/project/repository/index.md
@@ -53,6 +53,22 @@ To get started with the command line, please read through the
Use GitLab's [file finder](../../../workflow/file_finder.md) to search for files in a repository.
+### Jupyter Notebook files
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/2508) in GitLab 9.1
+
+[Jupyter][jupyter] Notebook (previously IPython Notebook) files are used for
+interactive computing in many fields and contain a complete record of the
+user's sessions and include code, narrative text, equations and rich output.
+
+When added to a repository, Jupyter Notebooks with a `.ipynb` extension will be
+rendered to HTML when viewed.
+
+![Jupyter Notebook Rich Output](img/jupyter_notebook.png)
+
+Interactive features, including JavaScript plots, will not work when viewed in
+GitLab.
+
## Branches
When you submit changes in a new [branch](branches/index.md), you create a new version
@@ -158,3 +174,5 @@ Lock your files to prevent any conflicting changes.
## Repository's API
You can access your repos via [repository API](../../../api/repositories.md).
+
+[jupyter]: https://jupyter.org
diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md
index 5ddeb014b30..dedf102fc37 100644
--- a/doc/user/project/settings/import_export.md
+++ b/doc/user/project/settings/import_export.md
@@ -31,8 +31,7 @@ with all their related data and be moved into a new GitLab instance.
| GitLab version | Import/Export version |
| ---------------- | --------------------- |
-| 10.6 to current | 0.2.3 |
-| 10.4 | 0.2.2 |
+| 10.4 to current | 0.2.2 |
| 10.3 | 0.2.1 |
| 10.0 | 0.2.0 |
| 9.4.0 | 0.1.8 |
diff --git a/features/project/active_tab.feature b/features/project/active_tab.feature
deleted file mode 100644
index 3ea0aab5a67..00000000000
--- a/features/project/active_tab.feature
+++ /dev/null
@@ -1,138 +0,0 @@
-Feature: Project Active Tab
- Background:
- Given I sign in as a user
- And I own a project
-
- # Main Tabs
-
- Scenario: On Project Home
- Given I visit my project's home page
- Then the active sub tab should be Home
- And no other sub tabs should be active
- And the active main tab should be Project
-
- Scenario: On Project Repository
- Given I visit my project's files page
- Then the active main tab should be Repository
- And no other main tabs should be active
-
- Scenario: On Project Issues
- Given I visit my project's issues page
- Then the active main tab should be Issues
- And no other main tabs should be active
-
- Scenario: On Project Merge Requests
- Given I visit my project's merge requests page
- Then the active main tab should be Merge Requests
- And no other main tabs should be active
-
- Scenario: On Project Wiki
- Given I visit my project's wiki page
- Then the active main tab should be Wiki
- And no other main tabs should be active
-
- Scenario: On Project Members
- Given I visit my project's members page
- Then the active main tab should be Members
- And no other main tabs should be active
-
- # Sub Tabs: Home
-
- Scenario: On Project Home/Show
- Given I visit my project's home page
- Then the active sub tab should be Home
- And no other sub tabs should be active
- And the active main tab should be Project
- And no other main tabs should be active
-
- Scenario: On Project Home/Activity
- Given I visit my project's home page
- And I click the "Activity" tab
- Then the active sub tab should be Activity
- And no other sub tabs should be active
- And the active main tab should be Project
-
- # Sub Tabs: Settings
-
- Scenario: On Project Settings/Integrations
- Given I visit my project's settings page
- And I click the "Integrations" tab
- Then the active sub tab should be Integrations
- And no other sub tabs should be active
- And the active main tab should be Settings
-
- Scenario: On Project Settings/Repository
- Given I visit my project's settings page
- And I click the "Repository" tab
- Then the active sub tab should be Repository
- And no other sub tabs should be active
- And the active main tab should be Settings
-
- # Sub Tabs: Repository
-
- Scenario: On Project Repository/Files
- Given I visit my project's files page
- Then the active sub tab should be Files
- And no other sub tabs should be active
- And the active main tab should be Repository
-
- Scenario: On Project Repository/Commits
- Given I visit my project's commits page
- Then the active sub tab should be Commits
- And no other sub tabs should be active
- And the active main tab should be Repository
-
- Scenario: On Project Repository/Graph
- Given I visit my project's graph page
- Then the active sub tab should be Graph
- And no other sub tabs should be active
- And the active main tab should be Repository
-
- Scenario: On Project Repository/Compare
- Given I visit my project's commits page
- And I click the "Compare" tab
- Then the active sub tab should be Compare
- And no other sub tabs should be active
- And the active main tab should be Repository
-
- Scenario: On Project Repository/Charts
- Given I visit my project's commits page
- And I click the "Charts" tab
- Then the active sub tab should be Charts
- And no other sub tabs should be active
- And the active main tab should be Repository
-
- Scenario: On Project Repository/Branches
- Given I visit my project's commits page
- And I click the "Branches" tab
- Then the active sub tab should be Branches
- And no other sub tabs should be active
- And the active main tab should be Repository
-
- Scenario: On Project Repository/Tags
- Given I visit my project's commits page
- And I click the "Tags" tab
- Then the active sub tab should be Tags
- And no other sub tabs should be active
- And the active main tab should be Repository
-
- Scenario: On Project Issues/Browse
- Given I visit my project's issues page
- Then the active main tab should be Issues
- And no other main tabs should be active
-
- Scenario: On Project Issues/Milestones
- Given I visit my project's issues page
- And I click the "Milestones" sub tab
- Then the active main tab should be Issues
- Then the active sub tab should be Milestones
- And no other main tabs should be active
- And no other sub tabs should be active
-
- Scenario: On Project Issues/Labels
- Given I visit my project's issues page
- And I click the "Labels" sub tab
- Then the active main tab should be Issues
- Then the active sub tab should be Labels
- And no other main tabs should be active
- And no other sub tabs should be active
diff --git a/features/project/fork.feature b/features/project/fork.feature
deleted file mode 100644
index ca3f2771aa5..00000000000
--- a/features/project/fork.feature
+++ /dev/null
@@ -1,49 +0,0 @@
-Feature: Project Fork
- Background:
- Given I sign in as a user
- And I am a member of project "Shop"
- When I visit project "Shop" page
-
- Scenario: User fork a project
- Given I click link "Fork"
- When I fork to my namespace
- Then I should see the forked project page
-
- Scenario: User already has forked the project
- Given I already have a project named "Shop" in my namespace
- And I click link "Fork"
- When I fork to my namespace
- Then I should see a "Name has already been taken" warning
-
- Scenario: Merge request on canonical repo goes to fork merge request page
- Given I click link "Fork"
- And I fork to my namespace
- Then I should see the forked project page
- When I visit project "Shop" page
- Then I should see "New merge request"
- And I goto the Merge Requests page
- Then I should see "New merge request"
- And I click link "New merge request"
- Then I should see the new merge request page for my namespace
-
- Scenario: Viewing forks of a Project
- Given I click link "Fork"
- When I fork to my namespace
- And I visit the forks page of the "Shop" project
- Then I should see my fork on the list
-
- Scenario: Viewing forks of a Project that has no repo
- Given I click link "Fork"
- When I fork to my namespace
- And I make forked repo invalid
- And I visit the forks page of the "Shop" project
- Then I should see my fork on the list
-
- Scenario: Viewing private forks of a Project
- Given There is an existent fork of the "Shop" project
- And I click link "Fork"
- When I fork to my namespace
- And I visit the forks page of the "Shop" project
- Then I should see my fork on the list
- And I should not see the other fork listed
- And I should see a private fork notice
diff --git a/features/project/graph.feature b/features/project/graph.feature
deleted file mode 100644
index b25c73ad870..00000000000
--- a/features/project/graph.feature
+++ /dev/null
@@ -1,33 +0,0 @@
-Feature: Project Graph
- Background:
- Given I sign in as a user
- And I own project "Shop"
-
- @javascript
- Scenario: I should see project graphs
- When I visit project "Shop" graph page
- Then page should have graphs
-
- @javascript
- Scenario: I should see project languages & commits graphs on commits graph url
- When I visit project "Shop" commits graph page
- Then page should have commits graphs
- Then page should have languages graphs
-
- @javascript
- Scenario: I should see project ci graphs
- Given project "Shop" has CI enabled
- When I visit project "Shop" CI graph page
- Then page should have CI graphs
-
- @javascript
- Scenario: I should see project languages & commits graphs on language graph url
- When I visit project "Shop" languages graph page
- Then page should have languages graphs
- Then page should have commits graphs
-
- @javascript
- Scenario: I should see project languages & commits graphs on charts url
- When I visit project "Shop" chart page
- Then page should have languages graphs
- Then page should have commits graphs
diff --git a/features/project/redirects.feature b/features/project/redirects.feature
deleted file mode 100644
index a2e77e7bf30..00000000000
--- a/features/project/redirects.feature
+++ /dev/null
@@ -1,38 +0,0 @@
-Feature: Project Redirects
- Background:
- Given public project "Community"
- And private project "Enterprise"
-
- Scenario: I visit public project page
- When I visit project "Community" page
- Then I should see project "Community" home page
-
- Scenario: I visit private project page
- When I visit project "Enterprise" page
- Then I should be redirected to sign in page
-
- Scenario: I visit a non-existent project page
- When I visit project "CommunityDoesNotExist" page
- Then I should be redirected to sign in page
-
- Scenario: I visit a non-existent project page as user
- Given I sign in as a user
- When I visit project "CommunityDoesNotExist" page
- Then page status code should be 404
-
- Scenario: I visit unauthorized project page as user
- Given I sign in as a user
- When I visit project "Enterprise" page
- Then page status code should be 404
-
- Scenario: I visit a public project without signing in
- When I visit project "Community" page
- And I should see project "Community" home page
- And I click on "Sign In"
- And Authenticate
- Then I should be redirected to "Community" page
-
- Scenario: I visit private project page without signing in
- When I visit project "Enterprise" page
- And I get redirected to signin page where I sign in
- Then I should be redirected to "Enterprise" page
diff --git a/features/steps/project/active_tab.rb b/features/steps/project/active_tab.rb
deleted file mode 100644
index 1a18f1d7065..00000000000
--- a/features/steps/project/active_tab.rb
+++ /dev/null
@@ -1,127 +0,0 @@
-class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedPaths
- include SharedProject
- include SharedActiveTab
- include SharedProjectTab
-
- # Sub Tabs: Home
-
- step 'I click the "Team" tab' do
- click_link('Members')
- end
-
- step 'I click the "Attachments" tab' do
- click_link('Attachments')
- end
-
- step 'I click the "Snippets" tab' do
- page.within('.layout-nav') do
- click_link('Snippets')
- end
- end
-
- step 'I click the "Edit Project"' do
- page.within '.nav-sidebar' do
- click_link('Edit Project')
- end
- end
-
- step 'I click the "Integrations" tab' do
- page.within '.nav-sidebar' do
- click_link('Integrations')
- end
- end
-
- step 'I click the "Repository" tab' do
- page.within '.sidebar-top-level-items > .active' do
- click_link('Repository')
- end
- end
-
- step 'I click the "Activity" tab' do
- page.within '.sidebar-top-level-items > .active' do
- click_link('Activity')
- end
- end
-
- step 'the active sub tab should be Members' do
- ensure_active_sub_tab('Members')
- end
-
- step 'the active sub tab should be Integrations' do
- ensure_active_sub_tab('Integrations')
- end
-
- step 'the active sub tab should be Repository' do
- ensure_active_sub_tab('Repository')
- end
-
- step 'the active sub tab should be Pages' do
- ensure_active_sub_tab('Pages')
- end
-
- step 'the active sub tab should be Activity' do
- ensure_active_sub_tab('Activity')
- end
-
- # Sub Tabs: Commits
-
- step 'I click the "Compare" tab' do
- click_link('Compare')
- end
-
- step 'I click the "Branches" tab' do
- page.within '.nav-sidebar' do
- click_link('Branches')
- end
- end
-
- step 'I click the "Tags" tab' do
- click_link('Tags')
- end
-
- step 'I click the "Charts" tab' do
- page.within('.sidebar-top-level-items > .active') do
- click_link('Charts')
- end
- end
-
- step 'the active sub tab should be Compare' do
- ensure_active_sub_tab('Compare')
- end
-
- step 'the active sub tab should be Branches' do
- ensure_active_sub_tab('Branches')
- end
-
- step 'the active sub tab should be Tags' do
- ensure_active_sub_tab('Tags')
- end
-
- # Sub Tabs: Issues
-
- step 'I click the "Milestones" sub tab' do
- page.within('.nav-sidebar') do
- click_link('Milestones')
- end
- end
-
- step 'I click the "Labels" sub tab' do
- page.within('.nav-sidebar') do
- click_link('Labels')
- end
- end
-
- step 'the active sub tab should be Issues' do
- ensure_active_sub_tab('Issues')
- end
-
- step 'the active sub tab should be Milestones' do
- ensure_active_sub_tab('Milestones')
- end
-
- step 'the active sub tab should be Labels' do
- ensure_active_sub_tab('Labels')
- end
-end
diff --git a/features/steps/project/fork.rb b/features/steps/project/fork.rb
deleted file mode 100644
index 0350e1c2aef..00000000000
--- a/features/steps/project/fork.rb
+++ /dev/null
@@ -1,85 +0,0 @@
-class Spinach::Features::ProjectFork < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedPaths
- include SharedProject
-
- step 'I click link "Fork"' do
- expect(page).to have_content "Shop"
- click_link "Fork"
- end
-
- step 'I am a member of project "Shop"' do
- @project = create(:project, :repository, name: "Shop")
- @project.add_reporter(@user)
- end
-
- step 'I should see the forked project page' do
- expect(page).to have_content "Forked from"
- end
-
- step 'I already have a project named "Shop" in my namespace' do
- @my_project = create(:project, :repository, name: "Shop", namespace: current_user.namespace)
- end
-
- step 'I should see a "Name has already been taken" warning' do
- expect(page).to have_content "Name has already been taken"
- end
-
- step 'I fork to my namespace' do
- page.within '.fork-thumbnail-container' do
- click_link current_user.name
- end
- end
-
- step 'I should see "New merge request"' do
- expect(page).to have_content(/new merge request/i)
- end
-
- step 'I goto the Merge Requests page' do
- page.within '.nav-sidebar' do
- first(:link, "Merge Requests").click
- end
- end
-
- step 'I click link "New merge request"' do
- page.within '#content-body' do
- page.has_link?('New Merge Request') ? click_link("New Merge Request") : click_link('New merge request')
- end
- end
-
- step 'I should see the new merge request page for my namespace' do
- current_path.should have_content(/#{current_user.namespace.name}/i)
- end
-
- step 'I visit the forks page of the "Shop" project' do
- @project = Project.where(name: 'Shop').first
- visit project_forks_path(@project)
- end
-
- step 'I should see my fork on the list' do
- page.within('.js-projects-list-holder') do
- project = @user.fork_of(@project.reload)
- expect(page).to have_content("#{project.namespace.human_name} / #{project.name}")
- end
- end
-
- step 'I make forked repo invalid' do
- project = @user.fork_of(@project.reload)
- project.path = 'test-crappy-path'
- project.save!
- end
-
- step 'There is an existent fork of the "Shop" project' do
- user = create(:user, name: 'Mike')
- @project.add_reporter(user)
- @forked_project = Projects::ForkService.new(@project, user).execute
- end
-
- step 'I should not see the other fork listed' do
- expect(page).not_to have_content("#{@forked_project.namespace.human_name} / #{@forked_project.name}")
- end
-
- step 'I should see a private fork notice' do
- expect(page).to have_content("1 private fork")
- end
-end
diff --git a/features/steps/project/graph.rb b/features/steps/project/graph.rb
deleted file mode 100644
index b9cddf4041d..00000000000
--- a/features/steps/project/graph.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-class Spinach::Features::ProjectGraph < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedProject
-
- step 'page should have graphs' do
- expect(page).to have_selector ".stat-graph"
- end
-
- When 'I visit project "Shop" graph page' do
- visit project_graph_path(project, "master")
- end
-
- step 'I visit project "Shop" commits graph page' do
- visit commits_project_graph_path(project, "master")
- end
-
- step 'I visit project "Shop" languages graph page' do
- visit languages_project_graph_path(project, "master")
- end
-
- step 'I visit project "Shop" chart page' do
- visit charts_project_graph_path(project, "master")
- end
-
- step 'page should have languages graphs' do
- expect(page).to have_content /Ruby 66.* %/
- expect(page).to have_content /JavaScript 22.* %/
- end
-
- step 'page should have commits graphs' do
- expect(page).to have_content "Commit statistics for master"
- expect(page).to have_content "Commits per day of month"
- end
-
- step 'I visit project "Shop" CI graph page' do
- visit ci_project_graph_path(project, 'master')
- end
-
- step 'page should have CI graphs' do
- expect(page).to have_content 'Overall'
- expect(page).to have_content 'Pipelines for last week'
- expect(page).to have_content 'Pipelines for last month'
- expect(page).to have_content 'Pipelines for last year'
- expect(page).to have_content 'Commit duration in minutes for last 30 commits'
- end
-
- def project
- @project ||= Project.find_by(name: "Shop")
- end
-end
diff --git a/features/steps/project/redirects.rb b/features/steps/project/redirects.rb
deleted file mode 100644
index 9ce86ca45d0..00000000000
--- a/features/steps/project/redirects.rb
+++ /dev/null
@@ -1,67 +0,0 @@
-class Spinach::Features::ProjectRedirects < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedPaths
- include SharedProject
-
- step 'public project "Community"' do
- create(:project, :public, name: 'Community')
- end
-
- step 'private project "Enterprise"' do
- create(:project, :private, name: 'Enterprise')
- end
-
- step 'I visit project "Community" page' do
- project = Project.find_by(name: 'Community')
- visit project_path(project)
- end
-
- step 'I should see project "Community" home page' do
- Gitlab.config.gitlab.should_receive(:host).and_return("www.example.com")
- page.within '.breadcrumbs .breadcrumb-item-text' do
- expect(page).to have_content 'Community'
- end
- end
-
- step 'I visit project "Enterprise" page' do
- project = Project.find_by(name: 'Enterprise')
- visit project_path(project)
- end
-
- step 'I visit project "CommunityDoesNotExist" page' do
- project = Project.find_by(name: 'Community')
- visit project_path(project) + 'DoesNotExist'
- end
-
- step 'I click on "Sign In"' do
- first(:link, "Sign in").click
- end
-
- step 'Authenticate' do
- admin = create(:admin)
- fill_in "user_login", with: admin.email
- fill_in "user_password", with: admin.password
- click_button "Sign in"
- Thread.current[:current_user] = admin
- end
-
- step 'I should be redirected to "Community" page' do
- project = Project.find_by(name: 'Community')
- expect(current_path).to eq "/#{project.full_path}"
- expect(status_code).to eq 200
- end
-
- step 'I get redirected to signin page where I sign in' do
- admin = create(:admin)
- fill_in "user_login", with: admin.email
- fill_in "user_password", with: admin.password
- click_button "Sign in"
- Thread.current[:current_user] = admin
- end
-
- step 'I should be redirected to "Enterprise" page' do
- project = Project.find_by(name: 'Enterprise')
- expect(current_path).to eq "/#{project.full_path}"
- expect(status_code).to eq 200
- end
-end
diff --git a/lib/api/helpers/related_resources_helpers.rb b/lib/api/helpers/related_resources_helpers.rb
index 1f677529b07..7f4d6e58b34 100644
--- a/lib/api/helpers/related_resources_helpers.rb
+++ b/lib/api/helpers/related_resources_helpers.rb
@@ -15,7 +15,7 @@ module API
url_options = Gitlab::Application.routes.default_url_options
protocol, host, port = url_options.slice(:protocol, :host, :port).values
- URI::HTTP.build(scheme: protocol, host: host, port: port, path: path).to_s
+ URI::Generic.build(scheme: protocol, host: host, port: port, path: path).to_s
end
private
diff --git a/lib/api/project_export.rb b/lib/api/project_export.rb
index 6ec2626df1a..b0a7fd6f4ab 100644
--- a/lib/api/project_export.rb
+++ b/lib/api/project_export.rb
@@ -31,8 +31,13 @@ module API
desc 'Start export' do
detail 'This feature was introduced in GitLab 10.6.'
end
+ params do
+ optional :description, type: String, desc: 'Override the project description'
+ end
post ':id/export' do
- user_project.add_export_job(current_user: current_user)
+ project_export_params = declared_params(include_missing: false)
+
+ user_project.add_export_job(current_user: current_user, params: project_export_params)
accepted!
end
diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb
index 9bdedeb6615..262458a872a 100644
--- a/lib/banzai/filter/relative_link_filter.rb
+++ b/lib/banzai/filter/relative_link_filter.rb
@@ -84,7 +84,7 @@ module Banzai
relative_url_root,
project.full_path,
uri_type(file_path),
- Addressable::URI.escape(ref),
+ Addressable::URI.escape(ref).gsub('#', '%23'),
Addressable::URI.escape(file_path)
].compact.join('/').squeeze('/').chomp('/')
diff --git a/lib/constraints/group_url_constrainer.rb b/lib/constraints/group_url_constrainer.rb
index fd2ac2db0a9..87649c50424 100644
--- a/lib/constraints/group_url_constrainer.rb
+++ b/lib/constraints/group_url_constrainer.rb
@@ -1,9 +1,11 @@
-class GroupUrlConstrainer
- def matches?(request)
- full_path = request.params[:group_id] || request.params[:id]
+module Constraints
+ class GroupUrlConstrainer
+ def matches?(request)
+ full_path = request.params[:group_id] || request.params[:id]
- return false unless NamespacePathValidator.valid_path?(full_path)
+ return false unless NamespacePathValidator.valid_path?(full_path)
- Group.find_by_full_path(full_path, follow_redirects: request.get?).present?
+ Group.find_by_full_path(full_path, follow_redirects: request.get?).present?
+ end
end
end
diff --git a/lib/constraints/project_url_constrainer.rb b/lib/constraints/project_url_constrainer.rb
index e90ecb5ec69..32aea98f0f7 100644
--- a/lib/constraints/project_url_constrainer.rb
+++ b/lib/constraints/project_url_constrainer.rb
@@ -1,13 +1,15 @@
-class ProjectUrlConstrainer
- def matches?(request)
- namespace_path = request.params[:namespace_id]
- project_path = request.params[:project_id] || request.params[:id]
- full_path = [namespace_path, project_path].join('/')
+module Constraints
+ class ProjectUrlConstrainer
+ def matches?(request)
+ namespace_path = request.params[:namespace_id]
+ project_path = request.params[:project_id] || request.params[:id]
+ full_path = [namespace_path, project_path].join('/')
- return false unless ProjectPathValidator.valid_path?(full_path)
+ return false unless ProjectPathValidator.valid_path?(full_path)
- # We intentionally allow SELECT(*) here so result of this query can be used
- # as cache for further Project.find_by_full_path calls within request
- Project.find_by_full_path(full_path, follow_redirects: request.get?).present?
+ # We intentionally allow SELECT(*) here so result of this query can be used
+ # as cache for further Project.find_by_full_path calls within request
+ Project.find_by_full_path(full_path, follow_redirects: request.get?).present?
+ end
end
end
diff --git a/lib/constraints/user_url_constrainer.rb b/lib/constraints/user_url_constrainer.rb
index 3b3ed1c6ddb..8afa04d29a4 100644
--- a/lib/constraints/user_url_constrainer.rb
+++ b/lib/constraints/user_url_constrainer.rb
@@ -1,9 +1,11 @@
-class UserUrlConstrainer
- def matches?(request)
- full_path = request.params[:username]
+module Constraints
+ class UserUrlConstrainer
+ def matches?(request)
+ full_path = request.params[:username]
- return false unless NamespacePathValidator.valid_path?(full_path)
+ return false unless NamespacePathValidator.valid_path?(full_path)
- User.find_by_full_path(full_path, follow_redirects: request.get?).present?
+ User.find_by_full_path(full_path, follow_redirects: request.get?).present?
+ end
end
end
diff --git a/lib/container_registry/client.rb b/lib/container_registry/client.rb
index c7263f302ab..010ca1ec27b 100644
--- a/lib/container_registry/client.rb
+++ b/lib/container_registry/client.rb
@@ -52,6 +52,8 @@ module ContainerRegistry
conn.request(:authorization, :bearer, options[:token].to_s)
end
+ yield(conn) if block_given?
+
conn.adapter :net_http
end
@@ -80,8 +82,7 @@ module ContainerRegistry
def faraday
@faraday ||= Faraday.new(@base_uri) do |conn|
- initialize_connection(conn, @options)
- accept_manifest(conn)
+ initialize_connection(conn, @options, &method(:accept_manifest))
end
end
diff --git a/lib/declarative_policy.rb b/lib/declarative_policy.rb
index b1949d693ad..1dd2855063d 100644
--- a/lib/declarative_policy.rb
+++ b/lib/declarative_policy.rb
@@ -1,6 +1,8 @@
require_dependency 'declarative_policy/cache'
require_dependency 'declarative_policy/condition'
-require_dependency 'declarative_policy/dsl'
+require_dependency 'declarative_policy/delegate_dsl'
+require_dependency 'declarative_policy/policy_dsl'
+require_dependency 'declarative_policy/rule_dsl'
require_dependency 'declarative_policy/preferred_scope'
require_dependency 'declarative_policy/rule'
require_dependency 'declarative_policy/runner'
diff --git a/lib/declarative_policy/delegate_dsl.rb b/lib/declarative_policy/delegate_dsl.rb
new file mode 100644
index 00000000000..f544dffe888
--- /dev/null
+++ b/lib/declarative_policy/delegate_dsl.rb
@@ -0,0 +1,16 @@
+module DeclarativePolicy
+ # Used when the name of a delegate is mentioned in
+ # the rule DSL.
+ class DelegateDsl
+ def initialize(rule_dsl, delegate_name)
+ @rule_dsl = rule_dsl
+ @delegate_name = delegate_name
+ end
+
+ def method_missing(m, *a, &b)
+ return super unless a.empty? && !block_given?
+
+ @rule_dsl.delegate(@delegate_name, m)
+ end
+ end
+end
diff --git a/lib/declarative_policy/dsl.rb b/lib/declarative_policy/dsl.rb
deleted file mode 100644
index 6ba1e7a3c5c..00000000000
--- a/lib/declarative_policy/dsl.rb
+++ /dev/null
@@ -1,103 +0,0 @@
-module DeclarativePolicy
- # The DSL evaluation context inside rule { ... } blocks.
- # Responsible for creating and combining Rule objects.
- #
- # See Base.rule
- class RuleDsl
- def initialize(context_class)
- @context_class = context_class
- end
-
- def can?(ability)
- Rule::Ability.new(ability)
- end
-
- def all?(*rules)
- Rule::And.make(rules)
- end
-
- def any?(*rules)
- Rule::Or.make(rules)
- end
-
- def none?(*rules)
- ~Rule::Or.new(rules)
- end
-
- def cond(condition)
- Rule::Condition.new(condition)
- end
-
- def delegate(delegate_name, condition)
- Rule::DelegatedCondition.new(delegate_name, condition)
- end
-
- def method_missing(m, *a, &b)
- return super unless a.size == 0 && !block_given?
-
- if @context_class.delegations.key?(m)
- DelegateDsl.new(self, m)
- else
- cond(m.to_sym)
- end
- end
- end
-
- # Used when the name of a delegate is mentioned in
- # the rule DSL.
- class DelegateDsl
- def initialize(rule_dsl, delegate_name)
- @rule_dsl = rule_dsl
- @delegate_name = delegate_name
- end
-
- def method_missing(m, *a, &b)
- return super unless a.size == 0 && !block_given?
-
- @rule_dsl.delegate(@delegate_name, m)
- end
- end
-
- # The return value of a rule { ... } declaration.
- # Can call back to register rules with the containing
- # Policy class (context_class here). See Base.rule
- #
- # Note that the #policy method just performs an #instance_eval,
- # which is useful for multiple #enable or #prevent callse.
- #
- # Also provides a #method_missing proxy to the context
- # class's class methods, so that helper methods can be
- # defined and used in a #policy { ... } block.
- class PolicyDsl
- def initialize(context_class, rule)
- @context_class = context_class
- @rule = rule
- end
-
- def policy(&b)
- instance_eval(&b)
- end
-
- def enable(*abilities)
- @context_class.enable_when(abilities, @rule)
- end
-
- def prevent(*abilities)
- @context_class.prevent_when(abilities, @rule)
- end
-
- def prevent_all
- @context_class.prevent_all_when(@rule)
- end
-
- def method_missing(m, *a, &b)
- return super unless @context_class.respond_to?(m)
-
- @context_class.__send__(m, *a, &b) # rubocop:disable GitlabSecurity/PublicSend
- end
-
- def respond_to_missing?(m)
- @context_class.respond_to?(m) || super
- end
- end
-end
diff --git a/lib/declarative_policy/policy_dsl.rb b/lib/declarative_policy/policy_dsl.rb
new file mode 100644
index 00000000000..f11b6e9f730
--- /dev/null
+++ b/lib/declarative_policy/policy_dsl.rb
@@ -0,0 +1,44 @@
+module DeclarativePolicy
+ # The return value of a rule { ... } declaration.
+ # Can call back to register rules with the containing
+ # Policy class (context_class here). See Base.rule
+ #
+ # Note that the #policy method just performs an #instance_eval,
+ # which is useful for multiple #enable or #prevent callse.
+ #
+ # Also provides a #method_missing proxy to the context
+ # class's class methods, so that helper methods can be
+ # defined and used in a #policy { ... } block.
+ class PolicyDsl
+ def initialize(context_class, rule)
+ @context_class = context_class
+ @rule = rule
+ end
+
+ def policy(&b)
+ instance_eval(&b)
+ end
+
+ def enable(*abilities)
+ @context_class.enable_when(abilities, @rule)
+ end
+
+ def prevent(*abilities)
+ @context_class.prevent_when(abilities, @rule)
+ end
+
+ def prevent_all
+ @context_class.prevent_all_when(@rule)
+ end
+
+ def method_missing(m, *a, &b)
+ return super unless @context_class.respond_to?(m)
+
+ @context_class.__send__(m, *a, &b) # rubocop:disable GitlabSecurity/PublicSend
+ end
+
+ def respond_to_missing?(m)
+ @context_class.respond_to?(m) || super
+ end
+ end
+end
diff --git a/lib/declarative_policy/preferred_scope.rb b/lib/declarative_policy/preferred_scope.rb
index b0754098149..5c214408dd0 100644
--- a/lib/declarative_policy/preferred_scope.rb
+++ b/lib/declarative_policy/preferred_scope.rb
@@ -1,4 +1,4 @@
-module DeclarativePolicy
+module DeclarativePolicy # rubocop:disable Naming/FileName
PREFERRED_SCOPE_KEY = :"DeclarativePolicy.preferred_scope"
class << self
diff --git a/lib/declarative_policy/rule_dsl.rb b/lib/declarative_policy/rule_dsl.rb
new file mode 100644
index 00000000000..e948b7f2de1
--- /dev/null
+++ b/lib/declarative_policy/rule_dsl.rb
@@ -0,0 +1,45 @@
+module DeclarativePolicy
+ # The DSL evaluation context inside rule { ... } blocks.
+ # Responsible for creating and combining Rule objects.
+ #
+ # See Base.rule
+ class RuleDsl
+ def initialize(context_class)
+ @context_class = context_class
+ end
+
+ def can?(ability)
+ Rule::Ability.new(ability)
+ end
+
+ def all?(*rules)
+ Rule::And.make(rules)
+ end
+
+ def any?(*rules)
+ Rule::Or.make(rules)
+ end
+
+ def none?(*rules)
+ ~Rule::Or.new(rules)
+ end
+
+ def cond(condition)
+ Rule::Condition.new(condition)
+ end
+
+ def delegate(delegate_name, condition)
+ Rule::DelegatedCondition.new(delegate_name, condition)
+ end
+
+ def method_missing(m, *a, &b)
+ return super unless a.empty? && !block_given?
+
+ if @context_class.delegations.key?(m)
+ DelegateDsl.new(self, m)
+ else
+ cond(m.to_sym)
+ end
+ end
+ end
+end
diff --git a/lib/generators/rails/post_deployment_migration/post_deployment_migration_generator.rb b/lib/generators/rails/post_deployment_migration/post_deployment_migration_generator.rb
index 7cb4bccb23c..91175b49c79 100644
--- a/lib/generators/rails/post_deployment_migration/post_deployment_migration_generator.rb
+++ b/lib/generators/rails/post_deployment_migration/post_deployment_migration_generator.rb
@@ -3,7 +3,7 @@ require 'rails/generators'
module Rails
class PostDeploymentMigrationGenerator < Rails::Generators::NamedBase
def create_migration_file
- timestamp = Time.now.strftime('%Y%m%d%H%I%S')
+ timestamp = Time.now.strftime('%Y%m%d%H%M%S')
template "migration.rb", "db/post_migrate/#{timestamp}_#{file_name}.rb"
end
diff --git a/lib/gitlab.rb b/lib/gitlab.rb
index 11f7c8b9510..aa9fd36d9ff 100644
--- a/lib/gitlab.rb
+++ b/lib/gitlab.rb
@@ -2,6 +2,7 @@ require_dependency 'gitlab/git'
module Gitlab
COM_URL = 'https://gitlab.com'.freeze
+ APP_DIRS_PATTERN = %r{^/?(app|config|ee|lib|spec|\(\w*\))}
def self.com?
# Check `staging?` as well to keep parity with gitlab.com
diff --git a/lib/gitlab/auth/result.rb b/lib/gitlab/auth/result.rb
index 75451cf8aa9..00cdc94a9ef 100644
--- a/lib/gitlab/auth/result.rb
+++ b/lib/gitlab/auth/result.rb
@@ -1,4 +1,4 @@
-module Gitlab
+module Gitlab # rubocop:disable Naming/FileName
module Auth
Result = Struct.new(:actor, :project, :type, :authentication_abilities) do
def ci?(for_project)
diff --git a/lib/gitlab/auth/saml/config.rb b/lib/gitlab/auth/saml/config.rb
index e654e7fe438..2760b1a3247 100644
--- a/lib/gitlab/auth/saml/config.rb
+++ b/lib/gitlab/auth/saml/config.rb
@@ -4,7 +4,7 @@ module Gitlab
class Config
class << self
def options
- Gitlab.config.omniauth.providers.find { |provider| provider.name == 'saml' }
+ Gitlab::Auth::OAuth::Provider.config_for('saml')
end
def groups
diff --git a/lib/gitlab/background_migration/add_merge_request_diff_commits_count.rb b/lib/gitlab/background_migration/add_merge_request_diff_commits_count.rb
index 7bffffec94d..d5cf9e0d53a 100644
--- a/lib/gitlab/background_migration/add_merge_request_diff_commits_count.rb
+++ b/lib/gitlab/background_migration/add_merge_request_diff_commits_count.rb
@@ -19,7 +19,7 @@ module Gitlab
WHERE merge_request_diffs.id = merge_request_diff_commits.merge_request_diff_id
)'.squish
- MergeRequestDiff.where(id: start_id..stop_id).update_all(update)
+ MergeRequestDiff.where(id: start_id..stop_id).where(commits_count: nil).update_all(update)
end
end
end
diff --git a/lib/gitlab/ci/pipeline/chain/command.rb b/lib/gitlab/ci/pipeline/chain/command.rb
index 7b19b10e05b..a1849b01c5d 100644
--- a/lib/gitlab/ci/pipeline/chain/command.rb
+++ b/lib/gitlab/ci/pipeline/chain/command.rb
@@ -1,4 +1,4 @@
-module Gitlab
+module Gitlab # rubocop:disable Naming/FileName
module Ci
module Pipeline
module Chain
diff --git a/lib/gitlab/ci/variables/collection.rb b/lib/gitlab/ci/variables/collection.rb
new file mode 100644
index 00000000000..0deca55fe8f
--- /dev/null
+++ b/lib/gitlab/ci/variables/collection.rb
@@ -0,0 +1,38 @@
+module Gitlab
+ module Ci
+ module Variables
+ class Collection
+ include Enumerable
+
+ def initialize(variables = [])
+ @variables = []
+
+ variables.each { |variable| self.append(variable) }
+ end
+
+ def append(resource)
+ tap { @variables.append(Collection::Item.fabricate(resource)) }
+ end
+
+ def concat(resources)
+ tap { resources.each { |variable| self.append(variable) } }
+ end
+
+ def each
+ @variables.each { |variable| yield variable }
+ end
+
+ def +(other)
+ self.class.new.tap do |collection|
+ self.each { |variable| collection.append(variable) }
+ other.each { |variable| collection.append(variable) }
+ end
+ end
+
+ def to_runner_variables
+ self.map(&:to_hash)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/variables/collection/item.rb b/lib/gitlab/ci/variables/collection/item.rb
new file mode 100644
index 00000000000..939912981e6
--- /dev/null
+++ b/lib/gitlab/ci/variables/collection/item.rb
@@ -0,0 +1,50 @@
+module Gitlab
+ module Ci
+ module Variables
+ class Collection
+ class Item
+ def initialize(**options)
+ @variable = {
+ key: options.fetch(:key),
+ value: options.fetch(:value),
+ public: options.fetch(:public, true),
+ file: options.fetch(:files, false)
+ }
+ end
+
+ def [](key)
+ @variable.fetch(key)
+ end
+
+ def ==(other)
+ to_hash == self.class.fabricate(other).to_hash
+ end
+
+ ##
+ # If `file: true` has been provided we expose it, otherwise we
+ # don't expose `file` attribute at all (stems from what the runner
+ # expects).
+ #
+ def to_hash
+ @variable.reject do |hash_key, hash_value|
+ hash_key == :file && hash_value == false
+ end
+ end
+
+ def self.fabricate(resource)
+ case resource
+ when Hash
+ self.new(resource)
+ when ::HasVariable
+ self.new(resource.to_runner_variable)
+ when self
+ resource.dup
+ else
+ raise ArgumentError, "Unknown `#{resource.class}` variable resource!"
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/conflict/file_collection.rb b/lib/gitlab/conflict/file_collection.rb
index 0a3ae2c3760..3ccfd9a739d 100644
--- a/lib/gitlab/conflict/file_collection.rb
+++ b/lib/gitlab/conflict/file_collection.rb
@@ -1,14 +1,18 @@
module Gitlab
module Conflict
class FileCollection
+ include Gitlab::RepositoryCacheAdapter
+
attr_reader :merge_request, :resolver
def initialize(merge_request)
our_commit = merge_request.source_branch_head.raw
their_commit = merge_request.target_branch_head.raw
- target_repo = merge_request.target_project.repository.raw
+ @target_repo = merge_request.target_project.repository
@source_repo = merge_request.source_project.repository.raw
- @resolver = Gitlab::Git::Conflict::Resolver.new(target_repo, our_commit.id, their_commit.id)
+ @our_commit_id = our_commit.id
+ @their_commit_id = their_commit.id
+ @resolver = Gitlab::Git::Conflict::Resolver.new(@target_repo.raw, @our_commit_id, @their_commit_id)
@merge_request = merge_request
end
@@ -30,6 +34,17 @@ module Gitlab
end
end
+ def can_be_resolved_in_ui?
+ # Try to parse each conflict. If the MR's mergeable status hasn't been
+ # updated, ensure that we don't say there are conflicts to resolve
+ # when there are no conflict files.
+ files.each(&:lines)
+ files.any?
+ rescue Gitlab::Git::CommandError, Gitlab::Git::Conflict::Parser::UnresolvableError, Gitlab::Git::Conflict::Resolver::ConflictSideMissing
+ false
+ end
+ cache_method :can_be_resolved_in_ui?
+
def file_for_path(old_path, new_path)
files.find { |file| file.their_path == old_path && file.our_path == new_path }
end
@@ -56,6 +71,19 @@ Merge branch '#{merge_request.target_branch}' into '#{merge_request.source_branc
#{conflict_filenames.join("\n")}
EOM
end
+
+ private
+
+ def cache
+ @cache ||= begin
+ # Use the commit ids as a namespace so if the MR branches get
+ # updated we instantiate the cache under a different namespace. That
+ # way don't have to worry about explicitly invalidating the cache
+ namespace = "#{@our_commit_id}:#{@their_commit_id}"
+
+ Gitlab::RepositoryCache.new(@target_repo, extra_namespace: namespace)
+ end
+ end
end
end
end
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index b7c596a973d..e392a015b91 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -70,7 +70,7 @@ module Gitlab
active_db_connection = ActiveRecord::Base.connection.active? rescue false
active_db_connection &&
- ActiveRecord::Base.connection.table_exists?('application_settings')
+ Gitlab::Database.cached_table_exists?('application_settings')
rescue ActiveRecord::NoDatabaseError
false
end
diff --git a/lib/gitlab/data_builder/pipeline.rb b/lib/gitlab/data_builder/pipeline.rb
index e47fb85b5ee..1e283cc092b 100644
--- a/lib/gitlab/data_builder/pipeline.rb
+++ b/lib/gitlab/data_builder/pipeline.rb
@@ -22,6 +22,7 @@ module Gitlab
sha: pipeline.sha,
before_sha: pipeline.before_sha,
status: pipeline.status,
+ detailed_status: pipeline.detailed_status(nil).label,
stages: pipeline.stages_names,
created_at: pipeline.created_at,
finished_at: pipeline.finished_at,
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index e51794fef99..76501dd50e8 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -183,6 +183,15 @@ module Gitlab
ActiveRecord::Base.connection
end
+ def self.cached_column_exists?(table_name, column_name)
+ connection.schema_cache.columns_hash(table_name).has_key?(column_name.to_s)
+ end
+
+ def self.cached_table_exists?(table_name)
+ # Rails 5 uses data_source_exists? instead of table_exists?
+ connection.schema_cache.table_exists?(table_name)
+ end
+
private_class_method :connection
def self.database_version
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index dbe6259fce7..21287a8efd0 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -859,6 +859,19 @@ into similar problems in the future (e.g. when new tables are created).
BackgroundMigrationWorker.perform_in(delay_interval * index, job_class_name, [start_id, end_id])
end
end
+
+ def foreign_key_exists?(table, column)
+ foreign_keys(table).any? do |key|
+ key.options[:column] == column.to_s
+ end
+ end
+
+ # Rails' index_exists? doesn't work when you only give it a table and index
+ # name. As such we have to use some extra code to check if an index exists for
+ # a given name.
+ def index_exists_by_name?(table, index)
+ indexes(table).map(&:name).include?(index)
+ end
end
end
end
diff --git a/lib/gitlab/diff/diff_refs.rb b/lib/gitlab/diff/diff_refs.rb
index 88e0db830f6..81df47964be 100644
--- a/lib/gitlab/diff/diff_refs.rb
+++ b/lib/gitlab/diff/diff_refs.rb
@@ -44,7 +44,11 @@ module Gitlab
project.commit(head_sha)
else
straight = start_sha == base_sha
- CompareService.new(project, head_sha).execute(project, start_sha, straight: straight)
+
+ CompareService.new(project, head_sha).execute(project,
+ start_sha,
+ base_sha: base_sha,
+ straight: straight)
end
end
end
diff --git a/lib/gitlab/diff/file_collection/merge_request_diff.rb b/lib/gitlab/diff/file_collection/merge_request_diff.rb
index fcda1fe2233..c358ae428cf 100644
--- a/lib/gitlab/diff/file_collection/merge_request_diff.rb
+++ b/lib/gitlab/diff/file_collection/merge_request_diff.rb
@@ -13,22 +13,32 @@ module Gitlab
end
def diff_files
- super.tap { |_| store_highlight_cache }
+ # Make sure to _not_ send any method call to Gitlab::Diff::File
+ # _before_ all of them were collected (`super`). Premature method calls will
+ # trigger N+1 RPCs to Gitaly through BatchLoader records (Blob.lazy).
+ #
+ diff_files = super
+
+ diff_files.each { |diff_file| cache_highlight!(diff_file) if cacheable?(diff_file) }
+ store_highlight_cache
+
+ diff_files
end
def real_size
@merge_request_diff.real_size
end
- private
+ def clear_cache!
+ Rails.cache.delete(cache_key)
+ end
- # Extracted method to highlight in the same iteration to the diff_collection.
- def decorate_diff!(diff)
- diff_file = super
- cache_highlight!(diff_file) if cacheable?(diff_file)
- diff_file
+ def cache_key
+ [@merge_request_diff, 'highlighted-diff-files', diff_options]
end
+ private
+
def highlight_diff_file_from_cache!(diff_file, cache_diff_lines)
diff_file.highlighted_diff_lines = cache_diff_lines.map do |line|
Gitlab::Diff::Line.init_from_hash(line)
@@ -62,16 +72,12 @@ module Gitlab
end
def store_highlight_cache
- Rails.cache.write(cache_key, highlight_cache) if @highlight_cache_was_empty
+ Rails.cache.write(cache_key, highlight_cache, expires_in: 1.week) if @highlight_cache_was_empty
end
def cacheable?(diff_file)
@merge_request_diff.present? && diff_file.text? && diff_file.diffable?
end
-
- def cache_key
- [@merge_request_diff, 'highlighted-diff-files', diff_options]
- end
end
end
end
diff --git a/lib/gitlab/exclusive_lease.rb b/lib/gitlab/exclusive_lease.rb
index dbb8f317afe..12b5e240962 100644
--- a/lib/gitlab/exclusive_lease.rb
+++ b/lib/gitlab/exclusive_lease.rb
@@ -41,6 +41,16 @@ module Gitlab
"gitlab:exclusive_lease:#{key}"
end
+ # Removes any existing exclusive_lease from redis
+ # Don't run this in a live system without making sure no one is using the leases
+ def self.reset_all!(scope = '*')
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.scan_each(match: redis_shared_state_key(scope)).each do |key|
+ redis.del(key)
+ end
+ end
+ end
+
def initialize(key, uuid: nil, timeout:)
@redis_shared_state_key = self.class.redis_shared_state_key(key)
@timeout = timeout
diff --git a/lib/gitlab/git/gitlab_projects.rb b/lib/gitlab/git/gitlab_projects.rb
index 5e1e22ae65c..a142ed6b2ef 100644
--- a/lib/gitlab/git/gitlab_projects.rb
+++ b/lib/gitlab/git/gitlab_projects.rb
@@ -67,7 +67,7 @@ module Gitlab
tags_option = tags ? '--tags' : '--no-tags'
logger.info "Fetching remote #{name} for repository #{repository_absolute_path}."
- cmd = %W(git fetch #{name} --quiet)
+ cmd = %W(#{Gitlab.config.git.bin_path} fetch #{name} --quiet)
cmd << '--prune' if prune
cmd << '--force' if force
cmd << tags_option
@@ -85,7 +85,7 @@ module Gitlab
def push_branches(remote_name, timeout, force, branch_names)
logger.info "Pushing branches from #{repository_absolute_path} to remote #{remote_name}: #{branch_names}"
- cmd = %w(git push)
+ cmd = %W(#{Gitlab.config.git.bin_path} push)
cmd << '--force' if force
cmd += %W(-- #{remote_name}).concat(branch_names)
@@ -102,7 +102,7 @@ module Gitlab
branches = branch_names.map { |branch_name| ":#{branch_name}" }
logger.info "Pushing deleted branches from #{repository_absolute_path} to remote #{remote_name}: #{branch_names}"
- cmd = %W(git push -- #{remote_name}).concat(branches)
+ cmd = %W(#{Gitlab.config.git.bin_path} push -- #{remote_name}).concat(branches)
success = run(cmd, repository_absolute_path)
@@ -143,7 +143,7 @@ module Gitlab
end
def remove_origin_in_repo
- cmd = %w(git remote rm origin)
+ cmd = %W(#{Gitlab.config.git.bin_path} remote rm origin)
run(cmd, repository_absolute_path)
end
@@ -223,7 +223,7 @@ module Gitlab
masked_source = mask_password_in_url(source)
logger.info "Importing project from <#{masked_source}> to <#{repository_absolute_path}>."
- cmd = %W(git clone --bare -- #{source} #{repository_absolute_path})
+ cmd = %W(#{Gitlab.config.git.bin_path} clone --bare -- #{source} #{repository_absolute_path})
success = run_with_timeout(cmd, timeout, nil)
@@ -266,7 +266,7 @@ module Gitlab
FileUtils.mkdir_p(File.dirname(to_path), mode: 0770)
logger.info "Forking repository from <#{from_path}> to <#{to_path}>."
- cmd = %W(git clone --bare --no-local -- #{from_path} #{to_path})
+ cmd = %W(#{Gitlab.config.git.bin_path} clone --bare --no-local -- #{from_path} #{to_path})
run(cmd, nil) && Gitlab::Git::Repository.create_hooks(to_path, global_hooks_path)
end
diff --git a/lib/gitlab/git/lfs_pointer_file.rb b/lib/gitlab/git/lfs_pointer_file.rb
index da12ed7d125..2ae0a889590 100644
--- a/lib/gitlab/git/lfs_pointer_file.rb
+++ b/lib/gitlab/git/lfs_pointer_file.rb
@@ -1,13 +1,16 @@
module Gitlab
module Git
class LfsPointerFile
+ VERSION = "https://git-lfs.github.com/spec/v1".freeze
+ VERSION_LINE = "version #{VERSION}".freeze
+
def initialize(data)
@data = data
end
def pointer
@pointer ||= <<~FILE
- version https://git-lfs.github.com/spec/v1
+ #{VERSION_LINE}
oid sha256:#{sha256}
size #{size}
FILE
@@ -20,6 +23,10 @@ module Gitlab
def sha256
@sha256 ||= Digest::SHA256.hexdigest(@data)
end
+
+ def inspect
+ "#<#{self.class}:#{object_id} @size=#{size}, @sha256=#{sha256.inspect}>"
+ end
end
end
end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index d4f6b543daf..9811c447a01 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -1002,8 +1002,9 @@ module Gitlab
# This only checks the root .gitattributes file,
# it does not traverse subfolders to find additional .gitattributes files
#
- # This method is around 30 times slower than `attributes`,
- # which uses `$GIT_DIR/info/attributes`
+ # This method is around 30 times slower than `attributes`, which uses
+ # `$GIT_DIR/info/attributes`. Consider caching AttributesAtRefParser
+ # and reusing that for multiple calls instead of this method.
def attributes_at(ref, file_path)
parser = AttributesAtRefParser.new(self, ref)
parser.attributes(file_path)
@@ -1189,15 +1190,9 @@ module Gitlab
end
def fsck
- gitaly_migrate(:git_fsck) do |is_enabled|
- msg, status = if is_enabled
- gitaly_fsck
- else
- shell_fsck
- end
+ msg, status = gitaly_repository_client.fsck
- raise GitError.new("Could not fsck repository: #{msg}") unless status.zero?
- end
+ raise GitError.new("Could not fsck repository: #{msg}") unless status.zero?
end
def create_from_bundle(bundle_path)
@@ -1433,16 +1428,6 @@ module Gitlab
output
end
- def can_be_merged?(source_sha, target_branch)
- gitaly_migrate(:can_be_merged) do |is_enabled|
- if is_enabled
- gitaly_can_be_merged?(source_sha, find_branch(target_branch).target)
- else
- rugged_can_be_merged?(source_sha, target_branch)
- end
- end
- end
-
def last_commit_for_path(sha, path)
gitaly_migrate(:last_commit_for_path) do |is_enabled|
if is_enabled
@@ -1606,14 +1591,6 @@ module Gitlab
File.write(File.join(worktree_info_path, 'sparse-checkout'), files)
end
- def gitaly_fsck
- gitaly_repository_client.fsck
- end
-
- def shell_fsck
- run_git(%W[--git-dir=#{path} fsck], nice: true)
- end
-
def rugged_fetch_source_branch(source_repository, source_branch, local_ref)
with_repo_branch_commit(source_repository, source_branch) do |commit|
if commit
@@ -2404,14 +2381,6 @@ module Gitlab
.map { |c| commit(c) }
end
- def gitaly_can_be_merged?(their_commit, our_commit)
- !gitaly_conflicts_client(our_commit, their_commit).conflicts?
- end
-
- def rugged_can_be_merged?(their_commit, our_commit)
- !rugged.merge_commits(our_commit, their_commit).conflicts?
- end
-
def last_commit_for_path_by_gitaly(sha, path)
gitaly_commit_client.last_commit_for_path(sha, path)
end
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index 9cd76630484..8ca30ffc232 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -119,6 +119,9 @@ module Gitlab
#
def self.call(storage, service, rpc, request, remote_storage: nil, timeout: nil)
start = Gitlab::Metrics::System.monotonic_time
+ request_hash = request.is_a?(Google::Protobuf::MessageExts) ? request.to_h : {}
+ @current_call_id ||= SecureRandom.uuid
+
enforce_gitaly_request_limits(:call)
kwargs = request_kwargs(storage, timeout, remote_storage: remote_storage)
@@ -135,6 +138,10 @@ module Gitlab
gitaly_controller_action_duration_seconds.observe(
current_transaction_labels.merge(gitaly_service: service.to_s, rpc: rpc.to_s),
duration)
+
+ add_call_details(id: @current_call_id, feature: service, duration: duration, request: request_hash)
+
+ @current_call_id = nil
end
def self.handle_grpc_unavailable!(ex)
@@ -252,12 +259,16 @@ module Gitlab
feature_stack.unshift(feature)
begin
start = Gitlab::Metrics::System.monotonic_time
+ @current_call_id = SecureRandom.uuid
+ call_details = { id: @current_call_id }
yield is_enabled
ensure
total_time = Gitlab::Metrics::System.monotonic_time - start
gitaly_migrate_call_duration_seconds.observe({ gitaly_enabled: is_enabled, feature: feature }, total_time)
feature_stack.shift
Thread.current[:gitaly_feature_stack] = nil if feature_stack.empty?
+
+ add_call_details(call_details.merge(feature: feature, duration: total_time))
end
end
end
@@ -344,6 +355,22 @@ module Gitlab
end
end
+ def self.add_call_details(details)
+ id = details.delete(:id)
+
+ return unless id && RequestStore.active? && RequestStore.store[:peek_enabled]
+
+ RequestStore.store['gitaly_call_details'] ||= {}
+ RequestStore.store['gitaly_call_details'][id] ||= {}
+ RequestStore.store['gitaly_call_details'][id].merge!(details)
+ end
+
+ def self.list_call_details
+ return {} unless RequestStore.active? && RequestStore.store[:peek_enabled]
+
+ RequestStore.store['gitaly_call_details'] || {}
+ end
+
def self.expected_server_version
path = Rails.root.join(SERVER_VERSION_FILE)
path.read.chomp
diff --git a/lib/gitlab/github_import/client.rb b/lib/gitlab/github_import/client.rb
index 4f160e4a447..a61beafae0d 100644
--- a/lib/gitlab/github_import/client.rb
+++ b/lib/gitlab/github_import/client.rb
@@ -197,10 +197,7 @@ module Gitlab
end
def github_omniauth_provider
- @github_omniauth_provider ||=
- Gitlab.config.omniauth.providers
- .find { |provider| provider.name == 'github' }
- .to_h
+ @github_omniauth_provider ||= Gitlab::Auth::OAuth::Provider.config_for('github').to_h
end
def rate_limit_counter
diff --git a/lib/gitlab/gitlab_import/client.rb b/lib/gitlab/gitlab_import/client.rb
index 075b3982608..5482504e72e 100644
--- a/lib/gitlab/gitlab_import/client.rb
+++ b/lib/gitlab/gitlab_import/client.rb
@@ -72,7 +72,7 @@ module Gitlab
end
def config
- Gitlab.config.omniauth.providers.find {|provider| provider.name == "gitlab"}
+ Gitlab::Auth::OAuth::Provider.config_for('gitlab')
end
def gitlab_options
diff --git a/lib/gitlab/health_checks/metric.rb b/lib/gitlab/health_checks/metric.rb
index 1a2eab0b005..d62d9136886 100644
--- a/lib/gitlab/health_checks/metric.rb
+++ b/lib/gitlab/health_checks/metric.rb
@@ -1,3 +1,3 @@
-module Gitlab::HealthChecks
+module Gitlab::HealthChecks # rubocop:disable Naming/FileName
Metric = Struct.new(:name, :value, :labels)
end
diff --git a/lib/gitlab/health_checks/result.rb b/lib/gitlab/health_checks/result.rb
index 8086760023e..e323e2c9723 100644
--- a/lib/gitlab/health_checks/result.rb
+++ b/lib/gitlab/health_checks/result.rb
@@ -1,3 +1,3 @@
-module Gitlab::HealthChecks
+module Gitlab::HealthChecks # rubocop:disable Naming/FileName
Result = Struct.new(:success, :message, :labels)
end
diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb
index b713fa7e1cd..af203ff711d 100644
--- a/lib/gitlab/import_export.rb
+++ b/lib/gitlab/import_export.rb
@@ -3,7 +3,7 @@ module Gitlab
extend self
# For every version update, the version history in import_export.md has to be kept up to date.
- VERSION = '0.2.3'.freeze
+ VERSION = '0.2.2'.freeze
FILENAME_LIMIT = 50
def export_path(relative_path:)
diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb
index 4b5f9f3a926..8f5bb8f9597 100644
--- a/lib/gitlab/import_export/project_tree_restorer.rb
+++ b/lib/gitlab/import_export/project_tree_restorer.rb
@@ -35,6 +35,8 @@ module Gitlab
end
def restored_project
+ return @project unless @tree_hash
+
@restored_project ||= restore_project
end
@@ -81,9 +83,13 @@ module Gitlab
end
def restore_project
- return @project unless @tree_hash
+ params = project_params
+
+ if params[:description].present?
+ params[:description_html] = nil
+ end
- @project.update_columns(project_params)
+ @project.update_columns(params)
@project
end
diff --git a/lib/gitlab/import_export/project_tree_saver.rb b/lib/gitlab/import_export/project_tree_saver.rb
index 3473b466936..5510c0b8b2f 100644
--- a/lib/gitlab/import_export/project_tree_saver.rb
+++ b/lib/gitlab/import_export/project_tree_saver.rb
@@ -5,7 +5,8 @@ module Gitlab
attr_reader :full_path
- def initialize(project:, current_user:, shared:)
+ def initialize(project:, current_user:, shared:, params: {})
+ @params = params
@project = project
@current_user = current_user
@shared = shared
@@ -25,6 +26,10 @@ module Gitlab
private
def project_json_tree
+ if @params[:description].present?
+ project_json['description'] = @params[:description]
+ end
+
project_json['project_members'] += group_members_json
project_json.to_json
diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb
index cf6b7e306dd..791a54e1b69 100644
--- a/lib/gitlab/import_export/relation_factory.rb
+++ b/lib/gitlab/import_export/relation_factory.rb
@@ -70,6 +70,7 @@ module Gitlab
update_user_references
update_project_references
+ remove_duplicate_assignees
reset_tokens!
remove_encrypted_attributes!
@@ -83,6 +84,14 @@ module Gitlab
end
end
+ def remove_duplicate_assignees
+ return unless @relation_hash['issue_assignees']
+
+ # When an assignee did not exist in the members mapper, the importer is
+ # assigned. We only need to assign each user once.
+ @relation_hash['issue_assignees'].uniq!(&:user_id)
+ end
+
def setup_note
set_note_author
# attachment is deprecated and note uploads are handled by Markdown uploader
diff --git a/lib/gitlab/kubernetes/namespace.rb b/lib/gitlab/kubernetes/namespace.rb
index fbbddb7bffa..e6ff6160ab9 100644
--- a/lib/gitlab/kubernetes/namespace.rb
+++ b/lib/gitlab/kubernetes/namespace.rb
@@ -10,7 +10,7 @@ module Gitlab
def exists?
@client.get_namespace(name)
- rescue ::KubeException => ke
+ rescue ::Kubeclient::HttpError => ke
raise ke unless ke.error_code == 404
false
diff --git a/lib/gitlab/legacy_github_import/client.rb b/lib/gitlab/legacy_github_import/client.rb
index 53c910d44bd..d8ed0ebca9d 100644
--- a/lib/gitlab/legacy_github_import/client.rb
+++ b/lib/gitlab/legacy_github_import/client.rb
@@ -83,7 +83,7 @@ module Gitlab
end
def config
- Gitlab.config.omniauth.providers.find { |provider| provider.name == "github" }
+ Gitlab::Auth::OAuth::Provider.config_for('github')
end
def github_options
diff --git a/lib/gitlab/metrics/methods.rb b/lib/gitlab/metrics/methods.rb
index cd7c1e507f7..f79eb0cd1bf 100644
--- a/lib/gitlab/metrics/methods.rb
+++ b/lib/gitlab/metrics/methods.rb
@@ -50,7 +50,7 @@ module Gitlab
end
def disabled_by_feature(options)
- options.with_feature && !Feature.get(options.with_feature).enabled?
+ options.with_feature && !::Feature.get(options.with_feature).enabled?
end
def build_metric!(type, name, options)
diff --git a/lib/gitlab/middleware/release_env.rb b/lib/gitlab/middleware/release_env.rb
index f8d0a135965..bfe8e113b5e 100644
--- a/lib/gitlab/middleware/release_env.rb
+++ b/lib/gitlab/middleware/release_env.rb
@@ -1,4 +1,4 @@
-module Gitlab
+module Gitlab # rubocop:disable Naming/FileName
module Middleware
# Some of middleware would hold env for no good reason even after the
# request had already been processed, and we could not garbage collect
diff --git a/lib/gitlab/project_transfer.rb b/lib/gitlab/project_transfer.rb
index 1bba0b78e2f..690c38737c0 100644
--- a/lib/gitlab/project_transfer.rb
+++ b/lib/gitlab/project_transfer.rb
@@ -1,13 +1,19 @@
module Gitlab
+ # This class is used to move local, unhashed files owned by projects to their new location
class ProjectTransfer
- def move_project(project_path, namespace_path_was, namespace_path)
- new_namespace_folder = File.join(root_dir, namespace_path)
- FileUtils.mkdir_p(new_namespace_folder) unless Dir.exist?(new_namespace_folder)
- from = File.join(root_dir, namespace_path_was, project_path)
- to = File.join(root_dir, namespace_path, project_path)
+ # nil parent_path (or parent_path_was) represents a root namespace
+ def move_namespace(path, parent_path_was, parent_path)
+ parent_path_was ||= ''
+ parent_path ||= ''
+ new_parent_folder = File.join(root_dir, parent_path)
+ FileUtils.mkdir_p(new_parent_folder)
+ from = File.join(root_dir, parent_path_was, path)
+ to = File.join(root_dir, parent_path, path)
move(from, to, "")
end
+ alias_method :move_project, :move_namespace
+
def rename_project(path_was, path, namespace_path)
base_dir = File.join(root_dir, namespace_path)
move(path_was, path, base_dir)
diff --git a/lib/gitlab/repository_cache.rb b/lib/gitlab/repository_cache.rb
new file mode 100644
index 00000000000..b1bf3ca4143
--- /dev/null
+++ b/lib/gitlab/repository_cache.rb
@@ -0,0 +1,33 @@
+# Interface to the Redis-backed cache store
+module Gitlab
+ class RepositoryCache
+ attr_reader :repository, :namespace, :backend
+
+ def initialize(repository, extra_namespace: nil, backend: Rails.cache)
+ @repository = repository
+ @namespace = "#{repository.full_path}:#{repository.project.id}"
+ @namespace += ":#{extra_namespace}" if extra_namespace
+ @backend = backend
+ end
+
+ def cache_key(type)
+ "#{type}:#{namespace}"
+ end
+
+ def expire(key)
+ backend.delete(cache_key(key))
+ end
+
+ def fetch(key, &block)
+ backend.fetch(cache_key(key), &block)
+ end
+
+ def exist?(key)
+ backend.exist?(cache_key(key))
+ end
+
+ def read(key)
+ backend.read(cache_key(key))
+ end
+ end
+end
diff --git a/lib/gitlab/repository_cache_adapter.rb b/lib/gitlab/repository_cache_adapter.rb
new file mode 100644
index 00000000000..7f64a8c9e46
--- /dev/null
+++ b/lib/gitlab/repository_cache_adapter.rb
@@ -0,0 +1,84 @@
+module Gitlab
+ module RepositoryCacheAdapter
+ extend ActiveSupport::Concern
+
+ class_methods do
+ # Wraps around the given method and caches its output in Redis and an instance
+ # variable.
+ #
+ # This only works for methods that do not take any arguments.
+ def cache_method(name, fallback: nil, memoize_only: false)
+ original = :"_uncached_#{name}"
+
+ alias_method(original, name)
+
+ define_method(name) do
+ cache_method_output(name, fallback: fallback, memoize_only: memoize_only) do
+ __send__(original) # rubocop:disable GitlabSecurity/PublicSend
+ end
+ end
+ end
+ end
+
+ # RepositoryCache to be used. Should be overridden by the including class
+ def cache
+ raise NotImplementedError
+ end
+
+ # Caches the supplied block both in a cache and in an instance variable.
+ #
+ # The cache key and instance variable are named the same way as the value of
+ # the `key` argument.
+ #
+ # This method will return `nil` if the corresponding instance variable is also
+ # set to `nil`. This ensures we don't keep yielding the block when it returns
+ # `nil`.
+ #
+ # key - The name of the key to cache the data in.
+ # fallback - A value to fall back to in the event of a Git error.
+ def cache_method_output(key, fallback: nil, memoize_only: false, &block)
+ ivar = cache_instance_variable_name(key)
+
+ if instance_variable_defined?(ivar)
+ instance_variable_get(ivar)
+ else
+ # If the repository doesn't exist and a fallback was specified we return
+ # that value inmediately. This saves us Rugged/gRPC invocations.
+ return fallback unless fallback.nil? || cache.repository.exists?
+
+ begin
+ value =
+ if memoize_only
+ yield
+ else
+ cache.fetch(key, &block)
+ end
+
+ instance_variable_set(ivar, value)
+ rescue Gitlab::Git::Repository::NoRepository
+ # Even if the above `#exists?` check passes these errors might still
+ # occur (for example because of a non-existing HEAD). We want to
+ # gracefully handle this and not cache anything
+ fallback
+ end
+ end
+ end
+
+ # Expires the caches of a specific set of methods
+ def expire_method_caches(methods)
+ methods.each do |key|
+ cache.expire(key)
+
+ ivar = cache_instance_variable_name(key)
+
+ remove_instance_variable(ivar) if instance_variable_defined?(ivar)
+ end
+ end
+
+ private
+
+ def cache_instance_variable_name(key)
+ :"@#{key.to_s.tr('?!', '')}"
+ end
+ end
+end
diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb
index 757ef71b95a..1e45d074e0a 100644
--- a/lib/gitlab/search_results.rb
+++ b/lib/gitlab/search_results.rb
@@ -7,8 +7,8 @@ module Gitlab
def initialize(opts = {})
@id = opts.fetch(:id, nil)
- @filename = opts.fetch(:filename, nil)
- @basename = opts.fetch(:basename, nil)
+ @filename = encode_utf8(opts.fetch(:filename, nil))
+ @basename = encode_utf8(opts.fetch(:basename, nil))
@ref = opts.fetch(:ref, nil)
@startline = opts.fetch(:startline, nil)
@data = encode_utf8(opts.fetch(:data, nil))
diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb
index dda7afc0999..3a8f5826818 100644
--- a/lib/gitlab/shell.rb
+++ b/lib/gitlab/shell.rb
@@ -69,13 +69,14 @@ module Gitlab
# name - project disk path
#
# Ex.
- # add_repository("/path/to/storage", "gitlab/gitlab-ci")
+ # create_repository("/path/to/storage", "gitlab/gitlab-ci")
#
- def add_repository(storage, name)
+ def create_repository(storage, name)
relative_path = name.dup
relative_path << '.git' unless relative_path.end_with?('.git')
- gitaly_migrate(:create_repository) do |is_enabled|
+ gitaly_migrate(:create_repository,
+ status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
if is_enabled
repository = Gitlab::Git::Repository.new(storage, relative_path, '')
repository.gitaly_repository_client.create_repository
@@ -85,7 +86,7 @@ module Gitlab
Gitlab::Git::Repository.create(repo_path, bare: true, symlink_hooks_to: gitlab_shell_hooks_path)
end
end
- rescue => err
+ rescue => err # Once the Rugged codes gets removes this can be improved
Rails.logger.error("Failed to add repository #{storage}/#{name}: #{err}")
false
end
@@ -487,8 +488,8 @@ module Gitlab
Gitlab.config.gitlab_shell.git_timeout
end
- def gitaly_migrate(method, &block)
- Gitlab::GitalyClient.migrate(method, &block)
+ def gitaly_migrate(method, status: Gitlab::GitalyClient::MigrationStatus::OPT_IN, &block)
+ Gitlab::GitalyClient.migrate(method, status: status, &block)
rescue GRPC::NotFound, GRPC::BadStatus => e
# Old Popen code returns [Error, output] to the caller, so we
# need to do the same here...
diff --git a/lib/gitlab/slash_commands/command.rb b/lib/gitlab/slash_commands/command.rb
index 85aaa6b0eba..bb778f37096 100644
--- a/lib/gitlab/slash_commands/command.rb
+++ b/lib/gitlab/slash_commands/command.rb
@@ -5,6 +5,7 @@ module Gitlab
Gitlab::SlashCommands::IssueShow,
Gitlab::SlashCommands::IssueNew,
Gitlab::SlashCommands::IssueSearch,
+ Gitlab::SlashCommands::IssueMove,
Gitlab::SlashCommands::Deploy
].freeze
diff --git a/lib/gitlab/slash_commands/issue_move.rb b/lib/gitlab/slash_commands/issue_move.rb
new file mode 100644
index 00000000000..3985e635983
--- /dev/null
+++ b/lib/gitlab/slash_commands/issue_move.rb
@@ -0,0 +1,45 @@
+module Gitlab
+ module SlashCommands
+ class IssueMove < IssueCommand
+ def self.match(text)
+ %r{
+ \A # the beginning of a string
+ issue\s+move\s+ # the command
+ \#?(?<iid>\d+)\s+ # the issue id, may preceded by hash sign
+ (to\s+)? # aid the command to be much more human-ly
+ (?<project_path>[^\s]+) # named group for id of dest. project
+ }x.match(text)
+ end
+
+ def self.help_message
+ 'issue move <issue_id> (to)? <project_path>'
+ end
+
+ def self.allowed?(project, user)
+ can?(user, :admin_issue, project)
+ end
+
+ def execute(match)
+ old_issue = find_by_iid(match[:iid])
+ target_project = Project.find_by_full_path(match[:project_path])
+
+ unless current_user.can?(:read_project, target_project) && old_issue
+ return Gitlab::SlashCommands::Presenters::Access.new.not_found
+ end
+
+ new_issue = Issues::MoveService.new(project, current_user)
+ .execute(old_issue, target_project)
+
+ presenter(new_issue).present(old_issue)
+ rescue Issues::MoveService::MoveError => e
+ presenter(old_issue).display_move_error(e.message)
+ end
+
+ private
+
+ def presenter(issue)
+ Gitlab::SlashCommands::Presenters::IssueMove.new(issue)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/slash_commands/presenters/issue_move.rb b/lib/gitlab/slash_commands/presenters/issue_move.rb
new file mode 100644
index 00000000000..03921729941
--- /dev/null
+++ b/lib/gitlab/slash_commands/presenters/issue_move.rb
@@ -0,0 +1,53 @@
+# coding: utf-8
+module Gitlab
+ module SlashCommands
+ module Presenters
+ class IssueMove < Presenters::Base
+ include Presenters::IssueBase
+
+ def present(old_issue)
+ in_channel_response(moved_issue(old_issue))
+ end
+
+ def display_move_error(error)
+ message = header_with_list("The action was not successful, because:", [error])
+
+ ephemeral_response(text: message)
+ end
+
+ private
+
+ def moved_issue(old_issue)
+ {
+ attachments: [
+ {
+ title: "#{@resource.title} · #{@resource.to_reference}",
+ title_link: resource_url,
+ author_name: author.name,
+ author_icon: author.avatar_url,
+ fallback: "Issue #{@resource.to_reference}: #{@resource.title}",
+ pretext: pretext(old_issue),
+ color: color(@resource),
+ fields: fields,
+ mrkdwn_in: [
+ :title,
+ :pretext,
+ :text,
+ :fields
+ ]
+ }
+ ]
+ }
+ end
+
+ def pretext(old_issue)
+ "Moved issue *#{issue_link(old_issue)}* to *#{issue_link(@resource)}*"
+ end
+
+ def issue_link(issue)
+ "[#{issue.to_reference}](#{project_issue_url(issue.project, issue)})"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/slash_commands/result.rb b/lib/gitlab/slash_commands/result.rb
index 7021b4b01b2..3669dedf0fe 100644
--- a/lib/gitlab/slash_commands/result.rb
+++ b/lib/gitlab/slash_commands/result.rb
@@ -1,4 +1,4 @@
-module Gitlab
+module Gitlab # rubocop:disable Naming/FileName
module SlashCommands
Result = Struct.new(:type, :message)
end
diff --git a/lib/gitlab/user_access.rb b/lib/gitlab/user_access.rb
index fd68b9f2b48..24393f96d96 100644
--- a/lib/gitlab/user_access.rb
+++ b/lib/gitlab/user_access.rb
@@ -63,15 +63,12 @@ module Gitlab
request_cache def can_push_to_branch?(ref)
return false unless can_access_git?
+ return false unless user.can?(:push_code, project) || project.branch_allows_maintainer_push?(user, ref)
if protected?(ProtectedBranch, project, ref)
- return true if project.user_can_push_to_empty_repo?(user)
-
- protected_branch_accessible_to?(ref, action: :push)
- elsif user.can?(:push_code, project)
- true
+ project.user_can_push_to_empty_repo?(user) || protected_branch_accessible_to?(ref, action: :push)
else
- project.branch_allows_maintainer_push?(user, ref)
+ true
end
end
diff --git a/lib/gitlab/verify/job_artifacts.rb b/lib/gitlab/verify/job_artifacts.rb
new file mode 100644
index 00000000000..03500a61074
--- /dev/null
+++ b/lib/gitlab/verify/job_artifacts.rb
@@ -0,0 +1,27 @@
+module Gitlab
+ module Verify
+ class JobArtifacts < BatchVerifier
+ def name
+ 'Job artifacts'
+ end
+
+ def describe(object)
+ "Job artifact: #{object.id}"
+ end
+
+ private
+
+ def relation
+ ::Ci::JobArtifact.all
+ end
+
+ def expected_checksum(artifact)
+ artifact.file_sha256
+ end
+
+ def actual_checksum(artifact)
+ Digest::SHA256.file(artifact.file.path).hexdigest
+ end
+ end
+ end
+end
diff --git a/lib/google_api/auth.rb b/lib/google_api/auth.rb
index 99a82c849e0..1aeaa387a49 100644
--- a/lib/google_api/auth.rb
+++ b/lib/google_api/auth.rb
@@ -32,7 +32,7 @@ module GoogleApi
private
def config
- Gitlab.config.omniauth.providers.find { |provider| provider.name == "google_oauth2" }
+ Gitlab::Auth::OAuth::Provider.config_for('google_oauth2')
end
def client
diff --git a/lib/haml_lint/inline_javascript.rb b/lib/haml_lint/inline_javascript.rb
index 4f776330e80..adbed20f152 100644
--- a/lib/haml_lint/inline_javascript.rb
+++ b/lib/haml_lint/inline_javascript.rb
@@ -1,4 +1,4 @@
-unless Rails.env.production?
+unless Rails.env.production? # rubocop:disable Naming/FileName
require 'haml_lint/haml_visitor'
require 'haml_lint/linter'
require 'haml_lint/linter_registry'
diff --git a/lib/mattermost/session.rb b/lib/mattermost/session.rb
index ef08bd46e17..65ccdb3c347 100644
--- a/lib/mattermost/session.rb
+++ b/lib/mattermost/session.rb
@@ -83,6 +83,12 @@ module Mattermost
end
end
+ def delete(path, options = {})
+ handle_exceptions do
+ self.class.delete(path, options.merge(headers: @headers))
+ end
+ end
+
private
def create
diff --git a/lib/mattermost/team.rb b/lib/mattermost/team.rb
index b2511f3af1d..75513a9ba04 100644
--- a/lib/mattermost/team.rb
+++ b/lib/mattermost/team.rb
@@ -16,10 +16,9 @@ module Mattermost
end
# The deletion is done async, so the response is fast.
- # On the mattermost side, this triggers an soft deletion first, after which
- # the actuall data is removed
+ # On the mattermost side, this triggers an soft deletion
def destroy(team_id:)
- session_delete("/api/v4/teams/#{team_id}?permanent=true")
+ session_delete("/api/v4/teams/#{team_id}")
end
end
end
diff --git a/lib/peek/views/gitaly.rb b/lib/peek/views/gitaly.rb
index d519d8e86fa..ab35f7a2258 100644
--- a/lib/peek/views/gitaly.rb
+++ b/lib/peek/views/gitaly.rb
@@ -10,11 +10,29 @@ module Peek
end
def results
- { duration: formatted_duration, calls: calls }
+ {
+ duration: formatted_duration,
+ calls: calls,
+ details: details
+ }
end
private
+ def details
+ ::Gitlab::GitalyClient.list_call_details
+ .values
+ .sort { |a, b| b[:duration] <=> a[:duration] }
+ .map(&method(:format_call_details))
+ end
+
+ def format_call_details(call)
+ pretty_request = call[:request]&.reject { |k, v| v.blank? }.to_h.pretty_inspect
+
+ call.merge(duration: (call[:duration] * 1000).round(3),
+ request: pretty_request || {})
+ end
+
def formatted_duration
ms = duration * 1000
if ms >= 1000
diff --git a/lib/repository_cache.rb b/lib/repository_cache.rb
deleted file mode 100644
index 068a95790c0..00000000000
--- a/lib/repository_cache.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-# Interface to the Redis-backed cache store used by the Repository model
-class RepositoryCache
- attr_reader :namespace, :backend, :project_id
-
- def initialize(namespace, project_id, backend = Rails.cache)
- @namespace = namespace
- @backend = backend
- @project_id = project_id
- end
-
- def cache_key(type)
- "#{type}:#{namespace}:#{project_id}"
- end
-
- def expire(key)
- backend.delete(cache_key(key))
- end
-
- def fetch(key, &block)
- backend.fetch(cache_key(key), &block)
- end
-
- def exist?(key)
- backend.exist?(cache_key(key))
- end
-
- def read(key)
- backend.read(cache_key(key))
- end
-end
diff --git a/lib/tasks/gitlab/artifacts/check.rake b/lib/tasks/gitlab/artifacts/check.rake
new file mode 100644
index 00000000000..a105261ed51
--- /dev/null
+++ b/lib/tasks/gitlab/artifacts/check.rake
@@ -0,0 +1,8 @@
+namespace :gitlab do
+ namespace :artifacts do
+ desc 'GitLab | Artifacts | Check integrity of uploaded job artifacts'
+ task check: :environment do
+ Gitlab::Verify::RakeTask.run!(Gitlab::Verify::JobArtifacts)
+ end
+ end
+end
diff --git a/lib/tasks/gitlab/exclusive_lease.rake b/lib/tasks/gitlab/exclusive_lease.rake
new file mode 100644
index 00000000000..83722bf6d94
--- /dev/null
+++ b/lib/tasks/gitlab/exclusive_lease.rake
@@ -0,0 +1,9 @@
+namespace :gitlab do
+ namespace :exclusive_lease do
+ desc 'GitLab | Clear existing exclusive leases for specified scope (default: *)'
+ task :clear, [:scope] => [:environment] do |_, args|
+ args[:scope].nil? ? Gitlab::ExclusiveLease.reset_all! : Gitlab::ExclusiveLease.reset_all!(args[:scope])
+ puts 'All exclusive lease entries were removed.'
+ end
+ end
+end
diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake
index 844664b12d4..4fcbbbf8c9d 100644
--- a/lib/tasks/gitlab/shell.rake
+++ b/lib/tasks/gitlab/shell.rake
@@ -69,7 +69,7 @@ namespace :gitlab do
if File.exist?(path_to_repo)
print '-'
else
- if Gitlab::Shell.new.add_repository(project.repository_storage,
+ if Gitlab::Shell.new.create_repository(project.repository_storage,
project.disk_path)
print '.'
else
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 3f05e878cc8..a04f869f2bb 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-03-06 17:36+0100\n"
-"PO-Revision-Date: 2018-03-06 17:36+0100\n"
+"POT-Creation-Date: 2018-03-12 19:50+0100\n"
+"PO-Revision-Date: 2018-03-12 19:50+0100\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
@@ -28,6 +28,11 @@ msgid_plural "%d commits behind"
msgstr[0] ""
msgstr[1] ""
+msgid "%d exporter"
+msgid_plural "%d exporters"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d issue"
msgid_plural "%d issues"
msgstr[0] ""
@@ -43,6 +48,11 @@ msgid_plural "%d merge requests"
msgstr[0] ""
msgstr[1] ""
+msgid "%d metric"
+msgid_plural "%d metrics"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%s additional commit has been omitted to prevent performance issues."
msgid_plural "%s additional commits have been omitted to prevent performance issues."
msgstr[0] ""
@@ -102,6 +112,9 @@ msgstr ""
msgid "2FA enabled"
msgstr ""
+msgid "<strong>Removes</strong> source branch"
+msgstr ""
+
msgid "A collection of graphs regarding Continuous Integration"
msgstr ""
@@ -111,6 +124,9 @@ 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 ""
+msgid "A user with write access to the source branch selected this option"
+msgstr ""
+
msgid "About auto deploy"
msgstr ""
@@ -213,7 +229,7 @@ msgstr ""
msgid "All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings."
msgstr ""
-msgid "Allow edits from maintainers"
+msgid "Allow edits from maintainers."
msgstr ""
msgid "Allows you to add and manage Kubernetes clusters."
@@ -857,6 +873,9 @@ msgstr ""
msgid "ClusterIntegration|Learn more about environments"
msgstr ""
+msgid "ClusterIntegration|Learn more about security configuration"
+msgstr ""
+
msgid "ClusterIntegration|Machine type"
msgstr ""
@@ -914,6 +933,9 @@ msgstr ""
msgid "ClusterIntegration|Save changes"
msgstr ""
+msgid "ClusterIntegration|Security"
+msgstr ""
+
msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster"
msgstr ""
@@ -941,6 +963,9 @@ msgstr ""
msgid "ClusterIntegration|Something went wrong while installing %{title}"
msgstr ""
+msgid "ClusterIntegration|The default cluster configuration grants access to a wide set of functionalities needed to successfully build and deploy a containerised application."
+msgstr ""
+
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
msgstr ""
@@ -1182,6 +1207,9 @@ msgstr ""
msgid "Create empty bare repository"
msgstr ""
+msgid "Create group label"
+msgstr ""
+
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr ""
@@ -1197,6 +1225,9 @@ msgstr ""
msgid "Create new..."
msgstr ""
+msgid "Create project label"
+msgstr ""
+
msgid "CreateNewFork|Fork"
msgstr ""
@@ -1776,9 +1807,18 @@ msgstr ""
msgid "Labels"
msgstr ""
+msgid "Labels can be applied to %{features}. Group labels are available for any project within the group."
+msgstr ""
+
msgid "Labels can be applied to issues and merge requests to categorize them."
msgstr ""
+msgid "Labels|Promote Label"
+msgstr ""
+
+msgid "Labels|Promote label %{labelTitle} to Group Label?"
+msgstr ""
+
msgid "Last %d day"
msgid_plural "Last %d days"
msgstr[0] ""
@@ -1850,9 +1890,15 @@ msgstr ""
msgid "Login"
msgstr ""
+msgid "Manage group labels"
+msgstr ""
+
msgid "Manage labels"
msgstr ""
+msgid "Manage project labels"
+msgstr ""
+
msgid "Mar"
msgstr ""
@@ -1907,6 +1953,12 @@ msgstr ""
msgid "Milestones|Milestone %{milestoneTitle} was not found"
msgstr ""
+msgid "Milestones|Promote %{milestoneTitle} to group milestone?"
+msgstr ""
+
+msgid "Milestones|Promote Milestone"
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
@@ -2002,6 +2054,9 @@ msgstr ""
msgid "No file chosen"
msgstr ""
+msgid "No labels created yet."
+msgstr ""
+
msgid "No repository"
msgstr ""
@@ -2251,9 +2306,15 @@ msgstr ""
msgid "Pipelines|Loading Pipelines"
msgstr ""
+msgid "Pipelines|Project cache successfully reset."
+msgstr ""
+
msgid "Pipelines|Run Pipeline"
msgstr ""
+msgid "Pipelines|Something went wrong while cleaning runners cache."
+msgstr ""
+
msgid "Pipelines|There are currently no %{scope} pipelines."
msgstr ""
@@ -2380,9 +2441,6 @@ msgstr ""
msgid "Project avatar in repository: %{link}"
msgstr ""
-msgid "Project cache successfully reset."
-msgstr ""
-
msgid "Project details"
msgstr ""
@@ -2446,6 +2504,12 @@ msgstr ""
msgid "ProjectsDropdown|This feature requires browser localStorage support"
msgstr ""
+msgid "PrometheusService|%{exporters} with %{metrics} were found"
+msgstr ""
+
+msgid "PrometheusService|<p class=\"text-tertiary\">No <a href=\"%{docsUrl}\">common metrics</a> were found</p>"
+msgstr ""
+
msgid "PrometheusService|Active"
msgstr ""
@@ -2458,6 +2522,9 @@ msgstr ""
msgid "PrometheusService|By default, Prometheus listens on ‘http://localhost:9090’. It’s not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server."
msgstr ""
+msgid "PrometheusService|Common metrics"
+msgstr ""
+
msgid "PrometheusService|Finding and configuring metrics..."
msgstr ""
@@ -2479,15 +2546,9 @@ msgstr ""
msgid "PrometheusService|Missing environment variable"
msgstr ""
-msgid "PrometheusService|Monitored"
-msgstr ""
-
msgid "PrometheusService|More information"
msgstr ""
-msgid "PrometheusService|No metrics are being monitored. To start monitoring, deploy to an environment."
-msgstr ""
-
msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
msgstr ""
@@ -2503,7 +2564,16 @@ msgstr ""
msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below"
msgstr ""
-msgid "PrometheusService|View environments"
+msgid "PrometheusService|Waiting for your first deployment to an environment to find common metrics"
+msgstr ""
+
+msgid "Promote"
+msgstr ""
+
+msgid "Promote to Group Label"
+msgstr ""
+
+msgid "Promote to Group Milestone"
msgstr ""
msgid "Protip:"
@@ -2515,9 +2585,6 @@ msgstr ""
msgid "Public - The project can be accessed without any authentication."
msgstr ""
-msgid "Push access to this project is necessary in order to enable this option"
-msgstr ""
-
msgid "Push events"
msgstr ""
@@ -3338,9 +3405,6 @@ msgstr ""
msgid "Trigger this manual action"
msgstr ""
-msgid "Unable to reset project cache."
-msgstr ""
-
msgid "Unlock"
msgstr ""
@@ -3383,12 +3447,18 @@ msgstr ""
msgid "View file @ "
msgstr ""
+msgid "View group labels"
+msgstr ""
+
msgid "View labels"
msgstr ""
msgid "View open merge request"
msgstr ""
+msgid "View project labels"
+msgstr ""
+
msgid "View replaced file @ "
msgstr ""
diff --git a/package.json b/package.json
index cbad55b4c85..deee668ae3b 100644
--- a/package.json
+++ b/package.json
@@ -8,11 +8,15 @@
"karma": "karma start config/karma.config.js --single-run",
"karma-coverage": "BABEL_ENV=coverage karma start config/karma.config.js --single-run",
"karma-start": "karma start config/karma.config.js",
- "svg": "node config/svg.config.js",
+ "prettier-staged": "node ./scripts/frontend/prettier.js",
+ "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",
"webpack": "webpack --config config/webpack.config.js",
"webpack-prod": "NODE_ENV=production webpack --config config/webpack.config.js"
},
"dependencies": {
+ "@gitlab-org/gitlab-svgs": "^1.14.0",
"autosize": "^4.0.0",
"axios": "^0.17.1",
"babel-core": "^6.26.0",
@@ -91,7 +95,6 @@
"worker-loader": "^1.1.0"
},
"devDependencies": {
- "@gitlab-org/gitlab-svgs": "^1.8.0",
"axios-mock-adapter": "^1.10.0",
"babel-eslint": "^8.0.2",
"babel-plugin-istanbul": "^4.1.5",
@@ -115,7 +118,7 @@
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "2.0.7",
"nodemon": "^1.15.1",
- "prettier": "1.9.2",
+ "prettier": "1.11.1",
"webpack-dev-server": "^2.11.2"
}
}
diff --git a/qa/qa/page/project/settings/ci_cd.rb b/qa/qa/page/project/settings/ci_cd.rb
index 99be21bbe89..17a1bc904e4 100644
--- a/qa/qa/page/project/settings/ci_cd.rb
+++ b/qa/qa/page/project/settings/ci_cd.rb
@@ -1,4 +1,4 @@
-module QA
+module QA # rubocop:disable Naming/FileName
module Page
module Project
module Settings
diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb
index 9110237c538..b36a3f9c8a0 100644
--- a/rubocop/rubocop.rb
+++ b/rubocop/rubocop.rb
@@ -1,3 +1,4 @@
+# rubocop:disable Naming/FileName
require_relative 'cop/gitlab/module_with_instance_variables'
require_relative 'cop/gitlab/predicate_memoization'
require_relative 'cop/include_sidekiq_worker'
diff --git a/scripts/frontend/frontend_script_utils.js b/scripts/frontend/frontend_script_utils.js
new file mode 100644
index 00000000000..2c06747255c
--- /dev/null
+++ b/scripts/frontend/frontend_script_utils.js
@@ -0,0 +1,30 @@
+/* eslint import/no-commonjs: "off" */
+const execFileSync = require('child_process').execFileSync;
+
+const exec = (command, args) => {
+ const options = {
+ cwd: process.cwd(),
+ env: process.env,
+ encoding: 'utf-8',
+ };
+ return execFileSync(command, args, options);
+};
+
+const execGitCmd = args =>
+ exec('git', args)
+ .trim()
+ .toString()
+ .split('\n');
+
+module.exports = {
+ getStagedFiles: fileExtensionFilter => {
+ const gitOptions = [
+ 'diff',
+ '--name-only',
+ '--cached',
+ '--diff-filter=ACMRTUB',
+ ];
+ if (fileExtensionFilter) gitOptions.push(...fileExtensionFilter);
+ return execGitCmd(gitOptions);
+ },
+};
diff --git a/scripts/frontend/prettier.js b/scripts/frontend/prettier.js
new file mode 100644
index 00000000000..863572bf64d
--- /dev/null
+++ b/scripts/frontend/prettier.js
@@ -0,0 +1,103 @@
+/* eslint import/no-commonjs: "off", import/no-extraneous-dependencies: "off", no-console: "off" */
+const glob = require('glob');
+const prettier = require('prettier');
+const fs = require('fs');
+
+const getStagedFiles = require('./frontend_script_utils').getStagedFiles;
+
+const mode = process.argv[2] || 'check';
+const shouldSave = mode === 'save' || mode === 'save-all';
+const allFiles = mode === 'check-all' || mode === 'save-all';
+
+const config = {
+ patterns: ['**/*.js', '**/*.vue', '**/*.scss'],
+ ignore: ['**/node_modules/**', '**/vendor/**', '**/public/**'],
+ parsers: {
+ js: 'babylon',
+ vue: 'vue',
+ scss: 'css',
+ },
+};
+const availableExtensions = Object.keys(config.parsers);
+
+console.log(`Loading ${allFiles ? 'All' : 'Staged'} Files ...`);
+
+const stagedFiles = allFiles
+ ? null
+ : getStagedFiles(availableExtensions.map(ext => `*.${ext}`));
+
+if (stagedFiles) {
+ if (!stagedFiles.length || (stagedFiles.length === 1 && !stagedFiles[0])) {
+ console.log('No matching staged files.');
+ return;
+ }
+ console.log(`Matching staged Files : ${stagedFiles.length}`);
+}
+
+let didWarn = false;
+let didError = false;
+
+let files;
+if (allFiles) {
+ const ignore = config.ignore;
+ const patterns = config.patterns;
+ const globPattern =
+ patterns.length > 1 ? `{${patterns.join(',')}}` : `${patterns.join(',')}`;
+ files = glob
+ .sync(globPattern, { ignore })
+ .filter(f => allFiles || stagedFiles.includes(f));
+} else {
+ files = stagedFiles.filter(f =>
+ availableExtensions.includes(f.split('.').pop()),
+ );
+}
+
+if (!files.length) {
+ console.log('No Files found to process with Prettier');
+ return;
+}
+
+console.log(`${shouldSave ? 'Updating' : 'Checking'} ${files.length} file(s)`);
+
+prettier
+ .resolveConfig('.')
+ .then(options => {
+ console.log('Found options : ', options);
+ files.forEach(file => {
+ try {
+ const fileExtension = file.split('.').pop();
+ Object.assign(options, {
+ parser: config.parsers[fileExtension],
+ });
+
+ const input = fs.readFileSync(file, 'utf8');
+
+ if (shouldSave) {
+ const output = prettier.format(input, options);
+ if (output !== input) {
+ fs.writeFileSync(file, output, 'utf8');
+ console.log(`Prettified : ${file}`);
+ }
+ } else if (!prettier.check(input, options)) {
+ if (!didWarn) {
+ console.log(
+ '\n===============================\nGitLab uses Prettier to format all JavaScript code.\nPlease format each file listed below or run "yarn prettier-staged-save"\n===============================\n',
+ );
+ didWarn = true;
+ }
+ console.log(`Prettify Manually : ${file}`);
+ }
+ } catch (error) {
+ didError = true;
+ console.log(`\n\nError with ${file}: ${error.message}`);
+ }
+ });
+
+ if (didWarn || didError) {
+ process.exit(1);
+ }
+ })
+ .catch(e => {
+ console.log(`Error on loading the Config File: ${e.message}`);
+ process.exit(1);
+ });
diff --git a/scripts/trigger-build-docs b/scripts/trigger-build-docs
index ae8cac0cf02..c9aaba91aa0 100755
--- a/scripts/trigger-build-docs
+++ b/scripts/trigger-build-docs
@@ -24,8 +24,8 @@ def docs_branch
# The maximum string length a file can have on a filesystem (ext4)
# is 63 characters. Let's use something smaller to be 100% sure.
max = 42
- # Prefix the remote branch with 'preview-' in order to avoid
- # name conflicts in the rare case the branch name already
+ # Prefix the remote branch with the slug of the project in order
+ # to avoid name conflicts in the rare case the branch name already
# exists in the docs repo and truncate to max length.
"#{slug}-#{ENV["CI_COMMIT_REF_SLUG"]}"[0...max]
end
@@ -41,12 +41,21 @@ def create_remote_branch
Gitlab.create_branch(GITLAB_DOCS_REPO, docs_branch, 'master')
puts "=> Remote branch '#{docs_branch}' created"
- # Get the latest pipeline ID which is also the first
- pipeline_id = Gitlab.pipelines(GITLAB_DOCS_REPO, { ref: docs_branch }).last.id
+ pipelines = nil
+
+ # Wait until the pipeline is started
+ loop do
+ sleep 1
+ puts "=> Waiting for pipeline to start..."
+ pipelines = Gitlab.pipelines(GITLAB_DOCS_REPO, { ref: docs_branch })
+ break if pipelines.any?
+ end
+
+ # Get the first pipeline ID which should be the only one for the branch
+ pipeline_id = pipelines.first.id
# Cancel the pipeline
Gitlab.cancel_pipeline(GITLAB_DOCS_REPO, pipeline_id)
- puts "=> Canceled uneeded pipeline #{pipeline_id} for '#{docs_branch}'"
rescue Gitlab::Error::BadRequest
puts "=> Remote branch '#{docs_branch}' already exists"
end
diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb
index 847ac6f2be0..e4dc61b3a68 100644
--- a/spec/controllers/projects/services_controller_spec.rb
+++ b/spec/controllers/projects/services_controller_spec.rb
@@ -23,6 +23,18 @@ describe Projects::ServicesController do
end
end
+ context 'when validations fail' do
+ let(:service_params) { { active: 'true', token: '' } }
+
+ it 'returns error messages in JSON response' do
+ put :test, namespace_id: project.namespace, project_id: project, id: :hipchat, service: service_params
+
+ expect(json_response['message']).to eq "Validations failed."
+ expect(json_response['service_response']).to eq "Token can't be blank"
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
context 'success' do
context 'with empty project' do
let(:project) { create(:project) }
diff --git a/spec/controllers/projects/settings/ci_cd_controller_spec.rb b/spec/controllers/projects/settings/ci_cd_controller_spec.rb
index 0202149f335..293e76798ae 100644
--- a/spec/controllers/projects/settings/ci_cd_controller_spec.rb
+++ b/spec/controllers/projects/settings/ci_cd_controller_spec.rb
@@ -27,7 +27,7 @@ describe Projects::Settings::CiCdController do
allow(ResetProjectCacheService).to receive_message_chain(:new, :execute).and_return(true)
end
- subject { post :reset_cache, namespace_id: project.namespace, project_id: project }
+ subject { post :reset_cache, namespace_id: project.namespace, project_id: project, format: :json }
it 'calls reset project cache service' do
expect(ResetProjectCacheService).to receive_message_chain(:new, :execute)
@@ -35,19 +35,11 @@ describe Projects::Settings::CiCdController do
subject
end
- it 'redirects to project pipelines path' do
- subject
-
- expect(response).to have_gitlab_http_status(:redirect)
- expect(response).to redirect_to(project_pipelines_path(project))
- end
-
context 'when service returns successfully' do
- it 'sets the flash notice variable' do
+ it 'returns a success header' do
subject
- expect(controller).to set_flash[:notice]
- expect(controller).not_to set_flash[:error]
+ expect(response).to have_gitlab_http_status(:ok)
end
end
@@ -56,11 +48,10 @@ describe Projects::Settings::CiCdController do
allow(ResetProjectCacheService).to receive_message_chain(:new, :execute).and_return(false)
end
- it 'sets the flash error variable' do
+ it 'returns an error header' do
subject
- expect(controller).not_to set_flash[:notice]
- expect(controller).to set_flash[:error]
+ expect(response).to have_gitlab_http_status(:bad_request)
end
end
end
diff --git a/spec/factories/ci/job_artifacts.rb b/spec/factories/ci/job_artifacts.rb
index 7ee379ca2ec..8544d54ccaa 100644
--- a/spec/factories/ci/job_artifacts.rb
+++ b/spec/factories/ci/job_artifacts.rb
@@ -35,5 +35,11 @@ FactoryBot.define do
Rails.root.join('spec/fixtures/trace/sample_trace'), 'text/plain')
end
end
+
+ trait :correct_checksum do
+ after(:build) do |artifact, evaluator|
+ artifact.file_sha256 = Digest::SHA256.file(artifact.file.path).hexdigest
+ end
+ end
end
end
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index 3d13f806b11..52ff47d57f9 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -343,7 +343,7 @@ describe 'Issue Boards', :js do
wait_for_requests
- click_link 'Create new label'
+ click_link 'Create project label'
fill_in('new_label_name', with: 'Testing New Label')
diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb
index b2dbfcd0031..d4c44c1adf9 100644
--- a/spec/features/boards/sidebar_spec.rb
+++ b/spec/features/boards/sidebar_spec.rb
@@ -312,12 +312,12 @@ describe 'Issue Boards', :js do
expect(card).not_to have_content(stretch.title)
end
- it 'creates new label' do
+ it 'creates project label' do
click_card(card)
page.within('.labels') do
click_link 'Edit'
- click_link 'Create new label'
+ click_link 'Create project label'
fill_in 'new_label_name', with: 'test label'
first('.suggest-colors-dropdown a').click
click_button 'Create'
diff --git a/spec/features/boards/sub_group_project_spec.rb b/spec/features/boards/sub_group_project_spec.rb
index 11a54079f4f..5fdb8044db2 100644
--- a/spec/features/boards/sub_group_project_spec.rb
+++ b/spec/features/boards/sub_group_project_spec.rb
@@ -24,7 +24,7 @@ describe 'Sub-group project issue boards', :js do
page.within '.labels' do
click_link 'Edit'
- click_link 'Create new label'
+ click_link 'Create project label'
end
page.within '.dropdown-new-label' do
diff --git a/spec/features/ci_lint_spec.rb b/spec/features/ci_lint_spec.rb
index b1dceec9da8..220b934154e 100644
--- a/spec/features/ci_lint_spec.rb
+++ b/spec/features/ci_lint_spec.rb
@@ -39,6 +39,7 @@ describe 'CI Lint', :js do
it 'displays information about an error' do
expect(page).to have_content('Status: syntax is incorrect')
+ expect(page).to have_selector('.ace_content', text: yaml_content)
end
end
diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb
index ef6b8edd0ad..38c618d300e 100644
--- a/spec/features/issues/form_spec.rb
+++ b/spec/features/issues/form_spec.rb
@@ -306,10 +306,10 @@ describe 'New/edit issue', :js do
visit new_project_issue_path(sub_group_project)
end
- it 'creates new label from dropdown' do
+ it 'creates project label from dropdown' do
click_button 'Labels'
- click_link 'Create new label'
+ click_link 'Create project label'
page.within '.dropdown-new-label' do
fill_in 'new_label_name', with: 'test label'
diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb
index 64b4f9e7e67..b835558b142 100644
--- a/spec/features/issues/issue_sidebar_spec.rb
+++ b/spec/features/issues/issue_sidebar_spec.rb
@@ -117,22 +117,22 @@ feature 'Issue Sidebar' do
end
end
- it 'shows option to create a new label' do
+ it 'shows option to create a project label' do
page.within('.block.labels') do
- expect(page).to have_content 'Create new'
+ expect(page).to have_content 'Create project'
end
end
- context 'creating a new label', :js do
+ context 'creating a project label', :js do
before do
page.within('.block.labels') do
- click_link 'Create new'
+ click_link 'Create project'
end
end
it 'shows dropdown switches to "create label" section' do
page.within('.block.labels') do
- expect(page).to have_content 'Create new label'
+ expect(page).to have_content 'Create project label'
end
end
diff --git a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb
index 890774922aa..db92a3504f3 100644
--- a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb
+++ b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb
@@ -125,6 +125,12 @@ describe 'Merge request > User merges when pipeline succeeds', :js do
expect(page).to have_content "canceled the automatic merge"
end
+ it 'allows to remove source branch' do
+ click_link "Remove source branch"
+
+ expect(page).to have_content "The source branch will be removed"
+ end
+
context 'when pipeline succeeds' do
before do
build.success
diff --git a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
index 3e83a549682..b4ad4b64d8e 100644
--- a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
+++ b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
@@ -108,6 +108,7 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
it 'shows resolved discussion when toggled' do
find(".timeline-content .discussion[data-discussion-id='#{note.discussion_id}'] .discussion-toggle-button").click
+ expect(page.find(".line-holder-placeholder")).to be_visible
expect(page.find(".timeline-content #note_#{note.id}")).to be_visible
end
end
diff --git a/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb b/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb
index 8a834adbf17..565e375600b 100644
--- a/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb
+++ b/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb
@@ -5,15 +5,18 @@ describe 'Merge request > User scrolls to note on load', :js do
let(:user) { project.creator }
let(:merge_request) { create(:merge_request, source_project: project, author: user) }
let(:note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project) }
+ let(:resolved_note) { create(:diff_note_on_merge_request, :resolved, noteable: merge_request, project: project) }
let(:fragment_id) { "#note_#{note.id}" }
+ let(:collapsed_fragment_id) { "#note_#{resolved_note.id}" }
before do
sign_in(user)
page.current_window.resize_to(1000, 300)
- visit "#{project_merge_request_path(project, merge_request)}#{fragment_id}"
end
- it 'scrolls down to fragment' do
+ it 'scrolls note into view' do
+ visit "#{project_merge_request_path(project, merge_request)}#{fragment_id}"
+
page_height = page.current_window.size[1]
page_scroll_y = page.evaluate_script("window.scrollY")
fragment_position_top = page.evaluate_script("Math.round($('#{fragment_id}').offset().top)")
@@ -23,4 +26,13 @@ describe 'Merge request > User scrolls to note on load', :js do
expect(fragment_position_top).to be >= page_scroll_y
expect(fragment_position_top).to be < (page_scroll_y + page_height)
end
+
+ it 'expands collapsed notes' do
+ visit "#{project_merge_request_path(project, merge_request)}#{collapsed_fragment_id}"
+ note_element = find(collapsed_fragment_id)
+ note_container = note_element.ancestor('.js-toggle-container')
+
+ expect(note_element.visible?).to eq true
+ expect(note_container.find('.line_content.noteable_line.old', match: :first).visible?).to eq true
+ end
end
diff --git a/spec/features/merge_request/user_sees_deployment_widget_spec.rb b/spec/features/merge_request/user_sees_deployment_widget_spec.rb
index 3abe363d523..f744d7941f5 100644
--- a/spec/features/merge_request/user_sees_deployment_widget_spec.rb
+++ b/spec/features/merge_request/user_sees_deployment_widget_spec.rb
@@ -22,7 +22,7 @@ describe 'Merge request > User sees deployment widget', :js do
wait_for_requests
expect(page).to have_content("Deployed to #{environment.name}")
- expect(find('.js-deploy-time')['data-title']).to eq(deployment.created_at.to_time.in_time_zone.to_s(:medium))
+ expect(find('.js-deploy-time')['data-original-title']).to eq(deployment.created_at.to_time.in_time_zone.to_s(:medium))
end
context 'with stop action' do
diff --git a/spec/features/merge_request/user_sees_merge_widget_spec.rb b/spec/features/merge_request/user_sees_merge_widget_spec.rb
index 56224e505d9..51a65407aec 100644
--- a/spec/features/merge_request/user_sees_merge_widget_spec.rb
+++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb
@@ -1,6 +1,8 @@
require 'rails_helper'
describe 'Merge request > User sees merge widget', :js do
+ include ProjectForksHelper
+
let(:project) { create(:project, :repository) }
let(:project_only_mwps) { create(:project, :repository, only_allow_merge_if_pipeline_succeeds: true) }
let(:user) { project.creator }
@@ -285,7 +287,29 @@ describe 'Merge request > User sees merge widget', :js do
end
it 'user cannot remove source branch' do
- expect(page).to have_field('remove-source-branch-input', disabled: true)
+ expect(page).not_to have_field('remove-source-branch-input')
+ end
+ end
+
+ context 'user cannot merge project and cannot push to fork', :js do
+ let(:forked_project) { fork_project(project, nil, repository: true) }
+ let(:user2) { create(:user) }
+
+ before do
+ project.add_developer(user2)
+ sign_out(:user)
+ sign_in(user2)
+ merge_request.update(
+ source_project: forked_project,
+ target_project: project,
+ merge_params: { 'force_remove_source_branch' => '1' }
+ )
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'user cannot remove source branch' do
+ expect(page).not_to have_field('remove-source-branch-input')
+ expect(page).to have_content('Removes source branch')
end
end
diff --git a/spec/features/milestone_spec.rb b/spec/features/milestone_spec.rb
index cc12a1005ba..19152bf1f0f 100644
--- a/spec/features/milestone_spec.rb
+++ b/spec/features/milestone_spec.rb
@@ -97,4 +97,15 @@ feature 'Milestone' do
end
end
end
+
+ feature 'Deleting a milestone' do
+ scenario "The delete milestone button does not show for unauthorized users" do
+ create(:milestone, project: project, title: 8.7)
+ sign_out(user)
+
+ visit group_milestones_path(group)
+
+ expect(page).to have_selector('.js-delete-milestone-button', count: 0)
+ end
+ end
end
diff --git a/spec/features/profiles/user_visits_notifications_tab_spec.rb b/spec/features/profiles/user_visits_notifications_tab_spec.rb
index 1952fdae798..95953fbcfac 100644
--- a/spec/features/profiles/user_visits_notifications_tab_spec.rb
+++ b/spec/features/profiles/user_visits_notifications_tab_spec.rb
@@ -16,6 +16,6 @@ feature 'User visits the notifications tab', :js do
first('#notifications-button').click
click_link('On mention')
- expect(page).to have_content('On mention')
+ expect(page).to have_selector('#notifications-button', text: 'On mention')
end
end
diff --git a/spec/features/projects/actve_tabs_spec.rb b/spec/features/projects/actve_tabs_spec.rb
new file mode 100644
index 00000000000..0bda68b83e7
--- /dev/null
+++ b/spec/features/projects/actve_tabs_spec.rb
@@ -0,0 +1,137 @@
+require 'spec_helper'
+
+describe 'Project active tab' do
+ let(:user) { create :user }
+ let(:project) { create(:project, :repository) }
+
+ before do
+ project.add_master(user)
+ sign_in(user)
+ end
+
+ def click_tab(title)
+ page.within '.sidebar-top-level-items > .active' do
+ click_link(title)
+ end
+ end
+
+ shared_examples 'page has active tab' do |title|
+ it "activates #{title} tab" do
+ expect(page).to have_selector('.sidebar-top-level-items > li.active', count: 1)
+ expect(find('.sidebar-top-level-items > li.active')).to have_content(title)
+ end
+ end
+
+ shared_examples 'page has active sub tab' do |title|
+ it "activates #{title} sub tab" do
+ expect(page).to have_selector('.sidebar-sub-level-items > li.active:not(.fly-out-top-item)', count: 1)
+ expect(find('.sidebar-sub-level-items > li.active:not(.fly-out-top-item)'))
+ .to have_content(title)
+ end
+ end
+
+ context 'on project Home' do
+ before do
+ visit project_path(project)
+ end
+
+ it_behaves_like 'page has active tab', 'Overview'
+ it_behaves_like 'page has active sub tab', 'Details'
+
+ context 'on project Home/Activity' do
+ before do
+ click_tab('Activity')
+ end
+
+ it_behaves_like 'page has active tab', 'Overview'
+ it_behaves_like 'page has active sub tab', 'Activity'
+ end
+ end
+
+ context 'on project Repository' do
+ before do
+ root_ref = project.repository.root_ref
+ visit project_tree_path(project, root_ref)
+ end
+
+ it_behaves_like 'page has active tab', 'Repository'
+
+ %w(Files Commits Graph Compare Charts Branches Tags).each do |sub_menu|
+ context "on project Repository/#{sub_menu}" do
+ before do
+ click_tab(sub_menu)
+ end
+
+ it_behaves_like 'page has active tab', 'Repository'
+ it_behaves_like 'page has active sub tab', sub_menu
+ end
+ end
+ end
+
+ context 'on project Issues' do
+ before do
+ visit project_issues_path(project)
+ end
+
+ it_behaves_like 'page has active tab', 'Issues'
+
+ %w(Milestones Labels).each do |sub_menu|
+ context "on project Issues/#{sub_menu}" do
+ before do
+ click_tab(sub_menu)
+ end
+
+ it_behaves_like 'page has active tab', 'Issues'
+ it_behaves_like 'page has active sub tab', sub_menu
+ end
+ end
+ end
+
+ context 'on project Merge Requests' do
+ before do
+ visit project_merge_requests_path(project)
+ end
+
+ it_behaves_like 'page has active tab', 'Merge Requests'
+ end
+
+ context 'on project Wiki' do
+ before do
+ visit project_wiki_path(project, :home)
+ end
+
+ it_behaves_like 'page has active tab', 'Wiki'
+ end
+
+ context 'on project Members' do
+ before do
+ visit project_project_members_path(project)
+ end
+
+ it_behaves_like 'page has active tab', 'Members'
+ end
+
+ context 'on project Settings' do
+ before do
+ visit edit_project_path(project)
+ end
+
+ context 'on project Settings/Integrations' do
+ before do
+ click_tab('Integrations')
+ end
+
+ it_behaves_like 'page has active tab', 'Settings'
+ it_behaves_like 'page has active sub tab', 'Integrations'
+ end
+
+ context 'on project Settings/Repository' do
+ before do
+ click_tab('Repository')
+ end
+
+ it_behaves_like 'page has active tab', 'Settings'
+ it_behaves_like 'page has active sub tab', 'Repository'
+ end
+ end
+end
diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb
index 88813d9b5ff..ac82f869f0f 100644
--- a/spec/features/projects/blobs/blob_show_spec.rb
+++ b/spec/features/projects/blobs/blob_show_spec.rb
@@ -509,4 +509,29 @@ feature 'File blob', :js do
end
end
end
+
+ context 'realtime pipelines' do
+ before do
+ Files::CreateService.new(
+ project,
+ project.creator,
+ start_branch: 'feature',
+ branch_name: 'feature',
+ commit_message: "Add ruby file",
+ file_path: 'files/ruby/test.rb',
+ file_content: "# Awesome content"
+ ).execute
+
+ create(:ci_pipeline, status: 'running', project: project, ref: 'feature', sha: project.commit('feature').sha)
+ visit_blob('files/ruby/test.rb', ref: 'feature')
+ end
+
+ it 'should show the realtime pipeline status' do
+ page.within('.commit-actions') do
+ expect(page).to have_css('.ci-status-icon')
+ expect(page).to have_css('.ci-status-icon-running')
+ expect(page).to have_css('.js-ci-status-icon-running')
+ end
+ end
+ end
end
diff --git a/spec/features/projects/fork_spec.rb b/spec/features/projects/fork_spec.rb
index 842840cc04c..1743b1e083f 100644
--- a/spec/features/projects/fork_spec.rb
+++ b/spec/features/projects/fork_spec.rb
@@ -25,6 +25,110 @@ describe 'Project fork' do
expect(page).to have_css('a.disabled', text: 'Fork')
end
+ it 'forks the project' do
+ visit project_path(project)
+
+ click_link 'Fork'
+
+ page.within '.fork-thumbnail-container' do
+ click_link user.name
+ end
+
+ expect(page).to have_content 'Forked from'
+
+ visit project_path(project)
+
+ expect(page).to have_content(/new merge request/i)
+
+ page.within '.nav-sidebar' do
+ first(:link, 'Merge Requests').click
+ end
+
+ expect(page).to have_content(/new merge request/i)
+
+ page.within '#content-body' do
+ click_link('New merge request')
+ end
+
+ expect(current_path).to have_content(/#{user.namespace.name}/i)
+ end
+
+ it 'shows the forked project on the list' do
+ visit project_path(project)
+
+ click_link 'Fork'
+
+ page.within '.fork-thumbnail-container' do
+ click_link user.name
+ end
+
+ visit project_forks_path(project)
+
+ forked_project = user.fork_of(project.reload)
+
+ page.within('.js-projects-list-holder') do
+ expect(page).to have_content("#{forked_project.namespace.human_name} / #{forked_project.name}")
+ end
+
+ forked_project.update!(path: 'test-crappy-path')
+
+ visit project_forks_path(project)
+
+ page.within('.js-projects-list-holder') do
+ expect(page).to have_content("#{forked_project.namespace.human_name} / #{forked_project.name}")
+ end
+ end
+
+ context 'when the project is private' do
+ let(:project) { create(:project, :repository) }
+ let(:another_user) { create(:user, name: 'Mike') }
+
+ before do
+ project.add_reporter(user)
+ project.add_reporter(another_user)
+ end
+
+ it 'renders private forks of the project' do
+ visit project_path(project)
+
+ another_project_fork = Projects::ForkService.new(project, another_user).execute
+
+ click_link 'Fork'
+
+ page.within '.fork-thumbnail-container' do
+ click_link user.name
+ end
+
+ visit project_forks_path(project)
+
+ page.within('.js-projects-list-holder') do
+ user_project_fork = user.fork_of(project.reload)
+ expect(page).to have_content("#{user_project_fork.namespace.human_name} / #{user_project_fork.name}")
+ end
+
+ expect(page).not_to have_content("#{another_project_fork.namespace.human_name} / #{another_project_fork.name}")
+ expect(page).to have_content("1 private fork")
+ end
+ end
+
+ context 'when the user already forked the project' do
+ before do
+ create(:project, :repository, name: project.name, namespace: user.namespace)
+ end
+
+ it 'renders error' do
+ visit project_path(project)
+
+ click_link 'Fork'
+
+ page.within '.fork-thumbnail-container' do
+ click_link user.name
+ end
+
+ expect(page).to have_content "Name has already been taken"
+ end
+ end
+
context 'master in group' do
let(:group) { create(:group) }
diff --git a/spec/features/projects/graph_spec.rb b/spec/features/projects/graph_spec.rb
new file mode 100644
index 00000000000..57172610aed
--- /dev/null
+++ b/spec/features/projects/graph_spec.rb
@@ -0,0 +1,75 @@
+require 'spec_helper'
+
+describe 'Project Graph', :js do
+ let(:user) { create :user }
+ let(:project) { create(:project, :repository, namespace: user.namespace) }
+
+ before do
+ project.add_master(user)
+
+ sign_in(user)
+ end
+
+ shared_examples 'page should have commits graphs' do
+ it 'renders commits' do
+ expect(page).to have_content('Commit statistics for master')
+ expect(page).to have_content('Commits per day of month')
+ end
+ end
+
+ shared_examples 'page should have languages graphs' do
+ it 'renders languages' do
+ expect(page).to have_content(/Ruby 66.* %/)
+ expect(page).to have_content(/JavaScript 22.* %/)
+ end
+ end
+
+ it 'renders graphs' do
+ visit project_graph_path(project, 'master')
+
+ expect(page).to have_selector('.stat-graph', visible: false)
+ end
+
+ context 'commits graph' do
+ before do
+ visit commits_project_graph_path(project, 'master')
+ end
+
+ it_behaves_like 'page should have commits graphs'
+ it_behaves_like 'page should have languages graphs'
+ end
+
+ context 'languages graph' do
+ before do
+ visit languages_project_graph_path(project, 'master')
+ end
+
+ it_behaves_like 'page should have commits graphs'
+ it_behaves_like 'page should have languages graphs'
+ end
+
+ context 'charts graph' do
+ before do
+ visit charts_project_graph_path(project, 'master')
+ end
+
+ it_behaves_like 'page should have commits graphs'
+ it_behaves_like 'page should have languages graphs'
+ end
+
+ context 'when CI enabled' do
+ before do
+ project.enable_ci
+
+ visit ci_project_graph_path(project, 'master')
+ end
+
+ it 'renders CI graphs' do
+ expect(page).to have_content 'Overall'
+ expect(page).to have_content 'Pipelines for last week'
+ expect(page).to have_content 'Pipelines for last month'
+ expect(page).to have_content 'Pipelines for last year'
+ expect(page).to have_content 'Commit duration in minutes for last 30 commits'
+ end
+ end
+end
diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb
index e8bb9c6a86c..b25f5161748 100644
--- a/spec/features/projects/import_export/import_file_spec.rb
+++ b/spec/features/projects/import_export/import_file_spec.rb
@@ -41,6 +41,7 @@ feature 'Import/Export - project import integration test', :js do
project = Project.last
expect(project).not_to be_nil
+ expect(project.description).to eq("Foo Bar")
expect(project.issues).not_to be_empty
expect(project.merge_requests).not_to be_empty
expect(project_hook_exists?(project)).to be true
diff --git a/spec/features/projects/import_export/test_project_export.tar.gz b/spec/features/projects/import_export/test_project_export.tar.gz
index 12bfcc177c7..ecb7651acad 100644
--- a/spec/features/projects/import_export/test_project_export.tar.gz
+++ b/spec/features/projects/import_export/test_project_export.tar.gz
Binary files differ
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index 849d85061df..0e81c6c629a 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -349,6 +349,18 @@ describe 'Pipelines', :js do
it { expect(page).not_to have_selector('.build-artifacts') }
end
+
+ context 'with trace artifact' do
+ before do
+ create(:ci_build, :success, :trace_artifact, pipeline: pipeline)
+
+ visit_project_pipelines
+ end
+
+ it 'does not show trace artifact as artifacts' do
+ expect(page).not_to have_selector('.build-artifacts')
+ end
+ end
end
context 'mini pipeline graph' do
@@ -557,7 +569,7 @@ describe 'Pipelines', :js do
end
it 'has a clear caches button' do
- expect(page).to have_link 'Clear Runner Caches'
+ expect(page).to have_button 'Clear Runner Caches'
end
describe 'user clicks the button' do
@@ -567,14 +579,16 @@ describe 'Pipelines', :js do
end
it 'increments jobs_cache_index' do
- click_link 'Clear Runner Caches'
+ click_button 'Clear Runner Caches'
+ wait_for_requests
expect(page.find('.flash-notice')).to have_content 'Project cache successfully reset.'
end
end
context 'when project does not have jobs_cache_index' do
it 'sets jobs_cache_index to 1' do
- click_link 'Clear Runner Caches'
+ click_button 'Clear Runner Caches'
+ wait_for_requests
expect(page.find('.flash-notice')).to have_content 'Project cache successfully reset.'
end
end
diff --git a/spec/features/projects/redirects_spec.rb b/spec/features/projects/redirects_spec.rb
new file mode 100644
index 00000000000..d1d8ca07035
--- /dev/null
+++ b/spec/features/projects/redirects_spec.rb
@@ -0,0 +1,74 @@
+require 'spec_helper'
+
+describe 'Project redirects' do
+ let(:user) { create :user }
+ let(:public_project) { create :project, :public }
+ let(:private_project) { create :project, :private }
+
+ before do
+ allow(Gitlab.config.gitlab).to receive(:host).and_return('www.example.com')
+ end
+
+ it 'shows public project page' do
+ visit project_path(public_project)
+
+ page.within '.breadcrumbs .breadcrumb-item-text' do
+ expect(page).to have_content(public_project.name)
+ end
+ end
+
+ it 'redirects to sign in page when project is private' do
+ visit project_path(private_project)
+
+ expect(current_path).to eq(new_user_session_path)
+ end
+
+ it 'redirects to sign in page when project does not exist' do
+ visit project_path(build(:project, :public))
+
+ expect(current_path).to eq(new_user_session_path)
+ end
+
+ it 'redirects to public project page after signing in' do
+ visit project_path(public_project)
+
+ first(:link, 'Sign in').click
+
+ fill_in 'user_login', with: user.email
+ fill_in 'user_password', with: user.password
+ click_button 'Sign in'
+
+ expect(status_code).to eq(200)
+ expect(current_path).to eq("/#{public_project.full_path}")
+ end
+
+ it 'redirects to private project page after sign in' do
+ visit project_path(private_project)
+
+ owner = private_project.owner
+ fill_in 'user_login', with: owner.email
+ fill_in 'user_password', with: owner.password
+ click_button 'Sign in'
+
+ expect(status_code).to eq(200)
+ expect(current_path).to eq("/#{private_project.full_path}")
+ end
+
+ context 'when signed in' do
+ before do
+ sign_in(user)
+ end
+
+ it 'returns 404 status when project does not exist' do
+ visit project_path(build(:project, :public))
+
+ expect(status_code).to eq(404)
+ end
+
+ it 'returns 404 when project is private' do
+ visit project_path(private_project)
+
+ expect(status_code).to eq(404)
+ end
+ end
+end
diff --git a/spec/features/projects/services/disable_triggers_spec.rb b/spec/features/projects/services/disable_triggers_spec.rb
new file mode 100644
index 00000000000..1a13fe03a67
--- /dev/null
+++ b/spec/features/projects/services/disable_triggers_spec.rb
@@ -0,0 +1,35 @@
+require 'spec_helper'
+
+describe 'Disable individual triggers' do
+ let(:project) { create(:project) }
+ let(:user) { project.owner }
+ let(:checkbox_selector) { 'input[type=checkbox][id$=_events]' }
+
+ before do
+ sign_in(user)
+
+ visit(project_settings_integrations_path(project))
+
+ click_link(service_name)
+ end
+
+ context 'service has multiple supported events' do
+ let(:service_name) { 'HipChat' }
+
+ it 'shows trigger checkboxes' do
+ event_count = HipchatService.supported_events.count
+
+ expect(page).to have_content "Trigger"
+ expect(page).to have_css(checkbox_selector, count: event_count)
+ end
+ end
+
+ context 'services only has one supported event' do
+ let(:service_name) { 'Asana' }
+
+ it "doesn't show unnecessary Trigger checkboxes" do
+ expect(page).not_to have_content "Trigger"
+ expect(page).not_to have_css(checkbox_selector)
+ end
+ end
+end
diff --git a/spec/features/projects/show_project_spec.rb b/spec/features/projects/show_project_spec.rb
index 0a014e9f080..e4f13e6cab7 100644
--- a/spec/features/projects/show_project_spec.rb
+++ b/spec/features/projects/show_project_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe 'Project show page', :feature do
+ include DropzoneHelper
+
context 'when project pending delete' do
let(:project) { create(:project, :empty_repo, pending_delete: true) }
@@ -334,4 +336,24 @@ describe 'Project show page', :feature do
end
end
end
+
+ describe 'dropzone', :js do
+ let(:project) { create(:project, :repository) }
+ let(:user) { create(:user) }
+
+ before do
+ project.add_master(user)
+ sign_in(user)
+
+ visit project_path(project)
+ end
+
+ it 'can upload files' do
+ find('.add-to-tree').click
+ click_link 'Upload file'
+ drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'))
+
+ expect(find('.dz-filename')).to have_content('doc_sample.txt')
+ end
+ end
end
diff --git a/spec/helpers/import_helper_spec.rb b/spec/helpers/import_helper_spec.rb
index 9afff47f4e9..033155617c6 100644
--- a/spec/helpers/import_helper_spec.rb
+++ b/spec/helpers/import_helper_spec.rb
@@ -27,25 +27,46 @@ describe ImportHelper do
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) }
+
+ before do
+ stub_omniauth_setting(providers: [provider])
+ end
+
context 'when provider does not specify a custom URL' do
it 'uses default GitHub URL' do
- allow(Gitlab.config.omniauth).to receive(:providers)
- .and_return([Settingslogic.new('name' => 'github')])
-
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
- allow(Gitlab.config.omniauth).to receive(:providers)
- .and_return([Settingslogic.new('name' => 'github', 'url' => 'https://github.company.com')])
+ 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
diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb
index 619baa78bfa..a2cda58e5d2 100644
--- a/spec/helpers/labels_helper_spec.rb
+++ b/spec/helpers/labels_helper_spec.rb
@@ -139,4 +139,76 @@ describe LabelsHelper do
expect(text_color_for_bg('#000')).to eq '#FFFFFF'
end
end
+
+ describe 'create_label_title' do
+ set(:group) { create(:group) }
+
+ context 'with a group as subject' do
+ it 'returns "Create group label"' do
+ expect(create_label_title(group)).to eq 'Create group label'
+ end
+ end
+
+ context 'with a project as subject' do
+ set(:project) { create(:project, namespace: group) }
+
+ it 'returns "Create project label"' do
+ expect(create_label_title(project)).to eq 'Create project label'
+ end
+ end
+
+ context 'with no subject' do
+ it 'returns "Create new label"' do
+ expect(create_label_title(nil)).to eq 'Create new label'
+ end
+ end
+ end
+
+ describe 'manage_labels_title' do
+ set(:group) { create(:group) }
+
+ context 'with a group as subject' do
+ it 'returns "Manage group labels"' do
+ expect(manage_labels_title(group)).to eq 'Manage group labels'
+ end
+ end
+
+ context 'with a project as subject' do
+ set(:project) { create(:project, namespace: group) }
+
+ it 'returns "Manage project labels"' do
+ expect(manage_labels_title(project)).to eq 'Manage project labels'
+ end
+ end
+
+ context 'with no subject' do
+ it 'returns "Manage labels"' do
+ expect(manage_labels_title(nil)).to eq 'Manage labels'
+ end
+ end
+ end
+
+ describe 'view_labels_title' do
+ set(:group) { create(:group) }
+
+ context 'with a group as subject' do
+ it 'returns "View group labels"' do
+ expect(view_labels_title(group)).to eq 'View group labels'
+ end
+ end
+
+ context 'with a project as subject' do
+ set(:project) { create(:project, namespace: group) }
+
+ it 'returns "View project labels"' do
+ expect(view_labels_title(project)).to eq 'View project labels'
+ end
+ end
+
+ context 'with no subject' do
+ it 'returns "View labels"' do
+ expect(view_labels_title(nil)).to eq 'View labels'
+ end
+ end
+ end
end
diff --git a/spec/javascripts/activities_spec.js b/spec/javascripts/activities_spec.js
index 7a9c539e9d0..909a1bf76bc 100644
--- a/spec/javascripts/activities_spec.js
+++ b/spec/javascripts/activities_spec.js
@@ -1,5 +1,6 @@
/* eslint-disable no-unused-expressions, no-prototype-builtins, no-new, no-shadow, max-len */
+import $ from 'jquery';
import 'vendor/jquery.endless-scroll';
import Activities from '~/activities';
diff --git a/spec/javascripts/ajax_loading_spinner_spec.js b/spec/javascripts/ajax_loading_spinner_spec.js
index 95c2c122403..261375d3a0e 100644
--- a/spec/javascripts/ajax_loading_spinner_spec.js
+++ b/spec/javascripts/ajax_loading_spinner_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import AjaxLoadingSpinner from '~/ajax_loading_spinner';
describe('Ajax Loading Spinner', () => {
@@ -10,7 +11,7 @@ describe('Ajax Loading Spinner', () => {
});
it('change current icon with spinner icon and disable link while waiting ajax response', (done) => {
- spyOn(jQuery, 'ajax').and.callFake((req) => {
+ spyOn($, 'ajax').and.callFake((req) => {
const xhr = new XMLHttpRequest();
const ajaxLoadingSpinner = document.querySelector('.js-ajax-loading-spinner');
const icon = ajaxLoadingSpinner.querySelector('i');
@@ -33,7 +34,7 @@ describe('Ajax Loading Spinner', () => {
});
it('use original icon again and enabled the link after complete the ajax request', (done) => {
- spyOn(jQuery, 'ajax').and.callFake((req) => {
+ spyOn($, 'ajax').and.callFake((req) => {
const xhr = new XMLHttpRequest();
const ajaxLoadingSpinner = document.querySelector('.js-ajax-loading-spinner');
diff --git a/spec/javascripts/autosave_spec.js b/spec/javascripts/autosave_spec.js
index b568d7fa8b0..38ae5b7e00c 100644
--- a/spec/javascripts/autosave_spec.js
+++ b/spec/javascripts/autosave_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import Autosave from '~/autosave';
import AccessorUtilities from '~/lib/utils/accessor';
diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js
index 8e4bbb90ccb..e81055bc08f 100644
--- a/spec/javascripts/awards_handler_spec.js
+++ b/spec/javascripts/awards_handler_spec.js
@@ -1,5 +1,6 @@
/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, no-unused-expressions, comma-dangle, new-parens, no-unused-vars, quotes, jasmine/no-spec-dupes, prefer-template, max-len */
+import $ from 'jquery';
import Cookies from 'js-cookie';
import loadAwardsHandler from '~/awards_handler';
diff --git a/spec/javascripts/behaviors/autosize_spec.js b/spec/javascripts/behaviors/autosize_spec.js
index 960b731892a..c411c5174fb 100644
--- a/spec/javascripts/behaviors/autosize_spec.js
+++ b/spec/javascripts/behaviors/autosize_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import '~/behaviors/autosize';
function load() {
diff --git a/spec/javascripts/behaviors/quick_submit_spec.js b/spec/javascripts/behaviors/quick_submit_spec.js
index d5300d9c63d..c37c62c63dd 100644
--- a/spec/javascripts/behaviors/quick_submit_spec.js
+++ b/spec/javascripts/behaviors/quick_submit_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import '~/behaviors/quick_submit';
describe('Quick Submit behavior', () => {
diff --git a/spec/javascripts/behaviors/requires_input_spec.js b/spec/javascripts/behaviors/requires_input_spec.js
index e500bbe750f..a434949b9da 100644
--- a/spec/javascripts/behaviors/requires_input_spec.js
+++ b/spec/javascripts/behaviors/requires_input_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import '~/behaviors/requires_input';
describe('requiresInput', () => {
diff --git a/spec/javascripts/blob/blob_file_dropzone_spec.js b/spec/javascripts/blob/blob_file_dropzone_spec.js
index 47de63e6690..0b1de504435 100644
--- a/spec/javascripts/blob/blob_file_dropzone_spec.js
+++ b/spec/javascripts/blob/blob_file_dropzone_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import BlobFileDropzone from '~/blob/blob_file_dropzone';
describe('BlobFileDropzone', () => {
@@ -27,7 +28,7 @@ describe('BlobFileDropzone', () => {
name: 'some-file.jpg',
type: 'jpg',
};
- const fakeEvent = jQuery.Event('drop', {
+ const fakeEvent = $.Event('drop', {
dataTransfer: { files: [file] },
});
diff --git a/spec/javascripts/blob/viewer/index_spec.js b/spec/javascripts/blob/viewer/index_spec.js
index 892411a6a40..f920c4ca945 100644
--- a/spec/javascripts/blob/viewer/index_spec.js
+++ b/spec/javascripts/blob/viewer/index_spec.js
@@ -1,4 +1,6 @@
/* eslint-disable no-new */
+
+import $ from 'jquery';
import MockAdapter from 'axios-mock-adapter';
import BlobViewer from '~/blob/viewer/index';
import axios from '~/lib/utils/axios_utils';
diff --git a/spec/javascripts/bootstrap_jquery_spec.js b/spec/javascripts/bootstrap_jquery_spec.js
index 48994b7c523..0fd6f9dc810 100644
--- a/spec/javascripts/bootstrap_jquery_spec.js
+++ b/spec/javascripts/bootstrap_jquery_spec.js
@@ -1,5 +1,6 @@
/* eslint-disable space-before-function-paren, no-var */
+import $ from 'jquery';
import '~/commons/bootstrap';
(function() {
diff --git a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
index 270f925e699..2fa50975f0f 100644
--- a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
+++ b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import VariableList from '~/ci_variable_list/ci_variable_list';
import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper';
diff --git a/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js b/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js
index eb508a7f059..1ea8d86cb7e 100644
--- a/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js
+++ b/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import setupNativeFormVariableList from '~/ci_variable_list/native_form_variable_list';
describe('NativeFormVariableList', () => {
diff --git a/spec/javascripts/commits_spec.js b/spec/javascripts/commits_spec.js
index 1daccc8dd02..977298b9221 100644
--- a/spec/javascripts/commits_spec.js
+++ b/spec/javascripts/commits_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import 'vendor/jquery.endless-scroll';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
diff --git a/spec/javascripts/create_item_dropdown_spec.js b/spec/javascripts/create_item_dropdown_spec.js
index 143137c23ec..ee26122be12 100644
--- a/spec/javascripts/create_item_dropdown_spec.js
+++ b/spec/javascripts/create_item_dropdown_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import CreateItemDropdown from '~/create_item_dropdown';
const DROPDOWN_ITEM_DATA = [{
diff --git a/spec/javascripts/feature_highlight/feature_highlight_helper_spec.js b/spec/javascripts/feature_highlight/feature_highlight_helper_spec.js
index 1b1f28f3ddb..480c138b9db 100644
--- a/spec/javascripts/feature_highlight/feature_highlight_helper_spec.js
+++ b/spec/javascripts/feature_highlight/feature_highlight_helper_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import {
diff --git a/spec/javascripts/feature_highlight/feature_highlight_spec.js b/spec/javascripts/feature_highlight/feature_highlight_spec.js
index f3f80cb3771..d2dd39d49d1 100644
--- a/spec/javascripts/feature_highlight/feature_highlight_spec.js
+++ b/spec/javascripts/feature_highlight/feature_highlight_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import * as featureHighlightHelper from '~/feature_highlight/feature_highlight_helper';
import * as featureHighlight from '~/feature_highlight/feature_highlight';
import axios from '~/lib/utils/axios_utils';
diff --git a/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js b/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js
index 71c14582329..8c5a0961a02 100644
--- a/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js
@@ -1,8 +1,9 @@
+import $ from 'jquery';
import FilteredSearchDropdownManager from '~/filtered_search/filtered_search_dropdown_manager';
describe('Filtered Search Dropdown Manager', () => {
beforeEach(() => {
- spyOn(jQuery, 'ajax');
+ spyOn($, 'ajax');
});
describe('addWordToInput', () => {
diff --git a/spec/javascripts/gfm_auto_complete_spec.js b/spec/javascripts/gfm_auto_complete_spec.js
index 50a587ef351..dc0a5bc275c 100644
--- a/spec/javascripts/gfm_auto_complete_spec.js
+++ b/spec/javascripts/gfm_auto_complete_spec.js
@@ -1,5 +1,6 @@
/* eslint no-param-reassign: "off" */
+import $ from 'jquery';
import GfmAutoComplete from '~/gfm_auto_complete';
import 'vendor/jquery.caret';
diff --git a/spec/javascripts/gl_dropdown_spec.js b/spec/javascripts/gl_dropdown_spec.js
index 67b854f61c0..0e4a7017406 100644
--- a/spec/javascripts/gl_dropdown_spec.js
+++ b/spec/javascripts/gl_dropdown_spec.js
@@ -1,5 +1,6 @@
/* eslint-disable comma-dangle, no-param-reassign, no-unused-expressions, max-len */
+import $ from 'jquery';
import '~/gl_dropdown';
import '~/lib/utils/common_utils';
import * as urlUtils from '~/lib/utils/url_utility';
diff --git a/spec/javascripts/gl_field_errors_spec.js b/spec/javascripts/gl_field_errors_spec.js
index 2779686a6f5..4e93fd91751 100644
--- a/spec/javascripts/gl_field_errors_spec.js
+++ b/spec/javascripts/gl_field_errors_spec.js
@@ -1,5 +1,6 @@
/* eslint-disable space-before-function-paren, arrow-body-style */
+import $ from 'jquery';
import GlFieldErrors from '~/gl_field_errors';
describe('GL Style Field Errors', function() {
diff --git a/spec/javascripts/gl_form_spec.js b/spec/javascripts/gl_form_spec.js
index 9c1fc0fda9e..74383f901b2 100644
--- a/spec/javascripts/gl_form_spec.js
+++ b/spec/javascripts/gl_form_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import autosize from 'autosize';
import GLForm from '~/gl_form';
import '~/lib/utils/text_utility';
diff --git a/spec/javascripts/groups/components/app_spec.js b/spec/javascripts/groups/components/app_spec.js
index 46c7b9f54f2..d8428bd0e08 100644
--- a/spec/javascripts/groups/components/app_spec.js
+++ b/spec/javascripts/groups/components/app_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import Vue from 'vue';
import * as utils from '~/lib/utils/url_utility';
diff --git a/spec/javascripts/header_spec.js b/spec/javascripts/header_spec.js
index 2443ffd48f3..16ac438f7ac 100644
--- a/spec/javascripts/header_spec.js
+++ b/spec/javascripts/header_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import initTodoToggle from '~/header';
describe('Header', function () {
diff --git a/spec/javascripts/integrations/integration_settings_form_spec.js b/spec/javascripts/integrations/integration_settings_form_spec.js
index d0fba908e34..050b1f2074e 100644
--- a/spec/javascripts/integrations/integration_settings_form_spec.js
+++ b/spec/javascripts/integrations/integration_settings_form_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import MockAdaptor from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import IntegrationSettingsForm from '~/integrations/integration_settings_form';
diff --git a/spec/javascripts/issuable_spec.js b/spec/javascripts/issuable_spec.js
index d53ffecbd35..57bf746f080 100644
--- a/spec/javascripts/issuable_spec.js
+++ b/spec/javascripts/issuable_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import MockAdaptor from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import IssuableIndex from '~/issuable_index';
diff --git a/spec/javascripts/issuable_time_tracker_spec.js b/spec/javascripts/issuable_time_tracker_spec.js
index 365e9fe6a4b..ba9040524b1 100644
--- a/spec/javascripts/issuable_time_tracker_spec.js
+++ b/spec/javascripts/issuable_time_tracker_spec.js
@@ -1,5 +1,6 @@
/* eslint-disable no-unused-vars, space-before-function-paren, func-call-spacing, no-spaced-func, semi, max-len, quotes, space-infix-ops, padded-blocks */
+import $ from 'jquery';
import Vue from 'vue';
import timeTracker from '~/sidebar/components/time_tracking/time_tracker.vue';
diff --git a/spec/javascripts/issue_show/components/description_spec.js b/spec/javascripts/issue_show/components/description_spec.js
index ff7f99eec14..d96151a8a3a 100644
--- a/spec/javascripts/issue_show/components/description_spec.js
+++ b/spec/javascripts/issue_show/components/description_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import Vue from 'vue';
import descriptionComponent from '~/issue_show/components/description.vue';
import * as taskList from '~/task_list';
diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js
index 177962ecf82..f37426a72d4 100644
--- a/spec/javascripts/issue_spec.js
+++ b/spec/javascripts/issue_spec.js
@@ -1,4 +1,6 @@
/* eslint-disable space-before-function-paren, one-var, one-var-declaration-per-line, no-use-before-define, comma-dangle, max-len */
+
+import $ from 'jquery';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import Issue from '~/issue';
diff --git a/spec/javascripts/job_spec.js b/spec/javascripts/job_spec.js
index b4599688c6d..c6bbacf237a 100644
--- a/spec/javascripts/job_spec.js
+++ b/spec/javascripts/job_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import { numberToHumanSize } from '~/lib/utils/number_utils';
diff --git a/spec/javascripts/labels_issue_sidebar_spec.js b/spec/javascripts/labels_issue_sidebar_spec.js
index 7d992f62f64..5aafb6ad8f0 100644
--- a/spec/javascripts/labels_issue_sidebar_spec.js
+++ b/spec/javascripts/labels_issue_sidebar_spec.js
@@ -1,4 +1,6 @@
/* eslint-disable no-new */
+
+import $ from 'jquery';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import IssuableContext from '~/issuable_context';
diff --git a/spec/javascripts/labels_select_spec.js b/spec/javascripts/labels_select_spec.js
index b8f7b1dc855..a2b89c0aef5 100644
--- a/spec/javascripts/labels_select_spec.js
+++ b/spec/javascripts/labels_select_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import LabelsSelect from '~/labels_select';
const mockUrl = '/foo/bar/url';
diff --git a/spec/javascripts/line_highlighter_spec.js b/spec/javascripts/line_highlighter_spec.js
index 89f4b85541d..d2bdc9e160c 100644
--- a/spec/javascripts/line_highlighter_spec.js
+++ b/spec/javascripts/line_highlighter_spec.js
@@ -1,5 +1,6 @@
/* eslint-disable space-before-function-paren, no-var, no-param-reassign, quotes, prefer-template, no-else-return, new-cap, dot-notation, no-return-assign, comma-dangle, no-new, one-var, one-var-declaration-per-line, jasmine/no-spec-dupes, no-underscore-dangle, max-len */
+import $ from 'jquery';
import LineHighlighter from '~/line_highlighter';
(function() {
diff --git a/spec/javascripts/merge_request_notes_spec.js b/spec/javascripts/merge_request_notes_spec.js
index 0d16b23302f..eb644e698da 100644
--- a/spec/javascripts/merge_request_notes_spec.js
+++ b/spec/javascripts/merge_request_notes_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import _ from 'underscore';
import 'autosize';
import '~/gl_form';
diff --git a/spec/javascripts/merge_request_spec.js b/spec/javascripts/merge_request_spec.js
index bdfd16ac995..74ceff76d37 100644
--- a/spec/javascripts/merge_request_spec.js
+++ b/spec/javascripts/merge_request_spec.js
@@ -1,4 +1,6 @@
/* eslint-disable space-before-function-paren, no-return-assign */
+
+import $ from 'jquery';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import MergeRequest from '~/merge_request';
@@ -27,7 +29,7 @@ import IssuablesHelper from '~/helpers/issuables_helper';
});
it('modifies the Markdown field', function() {
- spyOn(jQuery, 'ajax').and.stub();
+ spyOn($, 'ajax').and.stub();
const changeEvent = document.createEvent('HTMLEvents');
changeEvent.initEvent('change', true, true);
$('input[type=checkbox]').attr('checked', true)[0].dispatchEvent(changeEvent);
@@ -48,7 +50,7 @@ import IssuablesHelper from '~/helpers/issuables_helper';
describe('class constructor', () => {
beforeEach(() => {
- spyOn(jQuery, 'ajax').and.stub();
+ spyOn($, 'ajax').and.stub();
});
it('calls .initCloseReopenReport', () => {
diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js
index fda24db98b4..79c8cf0ba32 100644
--- a/spec/javascripts/merge_request_tabs_spec.js
+++ b/spec/javascripts/merge_request_tabs_spec.js
@@ -1,4 +1,6 @@
/* eslint-disable no-var, comma-dangle, object-shorthand */
+
+import $ from 'jquery';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import * as urlUtils from '~/lib/utils/url_utility';
diff --git a/spec/javascripts/mini_pipeline_graph_dropdown_spec.js b/spec/javascripts/mini_pipeline_graph_dropdown_spec.js
index 6fa6f44f953..009b3fd75b7 100644
--- a/spec/javascripts/mini_pipeline_graph_dropdown_spec.js
+++ b/spec/javascripts/mini_pipeline_graph_dropdown_spec.js
@@ -1,5 +1,6 @@
/* eslint-disable no-new */
+import $ from 'jquery';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown';
diff --git a/spec/javascripts/namespace_select_spec.js b/spec/javascripts/namespace_select_spec.js
index 9d7625ca269..3b2641f7646 100644
--- a/spec/javascripts/namespace_select_spec.js
+++ b/spec/javascripts/namespace_select_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import NamespaceSelect from '~/namespace_select';
describe('NamespaceSelect', () => {
diff --git a/spec/javascripts/new_branch_spec.js b/spec/javascripts/new_branch_spec.js
index 50a5e4ff056..5e5d8f8f34f 100644
--- a/spec/javascripts/new_branch_spec.js
+++ b/spec/javascripts/new_branch_spec.js
@@ -1,5 +1,6 @@
/* eslint-disable space-before-function-paren, one-var, no-var, one-var-declaration-per-line, no-return-assign, quotes, max-len */
+import $ from 'jquery';
import NewBranchForm from '~/new_branch_form';
(function() {
diff --git a/spec/javascripts/notes/components/comment_form_spec.js b/spec/javascripts/notes/components/comment_form_spec.js
index 6a7131528a3..224debbeff6 100644
--- a/spec/javascripts/notes/components/comment_form_spec.js
+++ b/spec/javascripts/notes/components/comment_form_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import Vue from 'vue';
import Autosize from 'autosize';
import store from '~/notes/stores';
@@ -199,6 +200,20 @@ describe('issue_comment_form component', () => {
done();
});
});
+
+ describe('when clicking close/reopen button', () => {
+ it('should disable button and show a loading spinner', (done) => {
+ const toggleStateButton = vm.$el.querySelector('.js-action-button');
+
+ toggleStateButton.click();
+ Vue.nextTick(() => {
+ expect(toggleStateButton.disabled).toEqual(true);
+ expect(toggleStateButton.querySelector('.js-loading-button-icon')).not.toBeNull();
+
+ done();
+ });
+ });
+ });
});
describe('issue is confidential', () => {
diff --git a/spec/javascripts/notes/components/note_app_spec.js b/spec/javascripts/notes/components/note_app_spec.js
index e1c612f5100..ac39418c3e6 100644
--- a/spec/javascripts/notes/components/note_app_spec.js
+++ b/spec/javascripts/notes/components/note_app_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import _ from 'underscore';
import Vue from 'vue';
import notesApp from '~/notes/components/notes_app.vue';
diff --git a/spec/javascripts/notes/components/noteable_note_spec.js b/spec/javascripts/notes/components/noteable_note_spec.js
index 88a7ffb0b9c..cfd037633e9 100644
--- a/spec/javascripts/notes/components/noteable_note_spec.js
+++ b/spec/javascripts/notes/components/noteable_note_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import _ from 'underscore';
import Vue from 'vue';
import store from '~/notes/stores';
diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js
index bf60cb12f52..5be13ed0dfe 100644
--- a/spec/javascripts/notes/mock_data.js
+++ b/spec/javascripts/notes/mock_data.js
@@ -1,7 +1,7 @@
/* eslint-disable */
export const notesDataMock = {
discussionsPath: '/gitlab-org/gitlab-ce/issues/26/discussions.json',
- lastFetchedAt: '1501862675',
+ lastFetchedAt: 1501862675,
markdownDocsPath: '/help/user/markdown',
newSessionPath: '/users/sign_in?redirect_to_referer=yes',
notesPath: '/gitlab-org/gitlab-ce/noteable/issue/98/notes',
diff --git a/spec/javascripts/notes/stores/actions_spec.js b/spec/javascripts/notes/stores/actions_spec.js
index ab80ed7bbfb..91249b2c79e 100644
--- a/spec/javascripts/notes/stores/actions_spec.js
+++ b/spec/javascripts/notes/stores/actions_spec.js
@@ -1,5 +1,6 @@
import Vue from 'vue';
import _ from 'underscore';
+import { headersInterceptor } from 'spec/helpers/vue_resource_helper';
import * as actions from '~/notes/stores/actions';
import store from '~/notes/stores';
import testAction from '../../helpers/vuex_action_helper';
@@ -87,6 +88,7 @@ describe('Actions Notes Store', () => {
store.dispatch('closeIssue', { notesData: { closeIssuePath: '' } })
.then(() => {
expect(store.state.noteableData.state).toEqual('closed');
+ expect(store.state.isToggleStateButtonLoading).toEqual(false);
done();
})
.catch(done.fail);
@@ -98,6 +100,7 @@ describe('Actions Notes Store', () => {
store.dispatch('reopenIssue', { notesData: { reopenIssuePath: '' } })
.then(() => {
expect(store.state.noteableData.state).toEqual('reopened');
+ expect(store.state.isToggleStateButtonLoading).toEqual(false);
done();
})
.catch(done.fail);
@@ -116,6 +119,20 @@ describe('Actions Notes Store', () => {
});
});
+ describe('toggleStateButtonLoading', () => {
+ it('should set loading as true', (done) => {
+ testAction(actions.toggleStateButtonLoading, true, {}, [
+ { type: 'TOGGLE_STATE_BUTTON_LOADING', payload: true },
+ ], done);
+ });
+
+ it('should set loading as false', (done) => {
+ testAction(actions.toggleStateButtonLoading, false, {}, [
+ { type: 'TOGGLE_STATE_BUTTON_LOADING', payload: false },
+ ], done);
+ });
+ });
+
describe('toggleIssueLocalState', () => {
it('sets issue state as closed', (done) => {
testAction(actions.toggleIssueLocalState, 'closed', {}, [
@@ -129,4 +146,68 @@ describe('Actions Notes Store', () => {
], done);
});
});
+
+ describe('poll', () => {
+ beforeEach((done) => {
+ jasmine.clock().install();
+
+ spyOn(Vue.http, 'get').and.callThrough();
+
+ store.dispatch('setNotesData', notesDataMock)
+ .then(done)
+ .catch(done.fail);
+ });
+
+ afterEach(() => {
+ jasmine.clock().uninstall();
+ });
+
+ it('calls service with last fetched state', (done) => {
+ const interceptor = (request, next) => {
+ next(request.respondWith(JSON.stringify({
+ notes: [],
+ last_fetched_at: '123456',
+ }), {
+ status: 200,
+ headers: {
+ 'poll-interval': '1000',
+ },
+ }));
+ };
+
+ Vue.http.interceptors.push(interceptor);
+ Vue.http.interceptors.push(headersInterceptor);
+
+ store.dispatch('poll')
+ .then(() => new Promise(resolve => requestAnimationFrame(resolve)))
+ .then(() => {
+ expect(Vue.http.get).toHaveBeenCalledWith(jasmine.anything(), {
+ url: jasmine.anything(),
+ method: 'get',
+ headers: {
+ 'X-Last-Fetched-At': undefined,
+ },
+ });
+ expect(store.state.lastFetchedAt).toBe('123456');
+
+ jasmine.clock().tick(1500);
+ })
+ .then(() => new Promise((resolve) => {
+ requestAnimationFrame(resolve);
+ }))
+ .then(() => {
+ expect(Vue.http.get.calls.count()).toBe(2);
+ expect(Vue.http.get.calls.mostRecent().args[1].headers).toEqual({
+ 'X-Last-Fetched-At': '123456',
+ });
+ })
+ .then(() => store.dispatch('stopPolling'))
+ .then(() => {
+ Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor);
+ Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
});
diff --git a/spec/javascripts/notes/stores/mutation_spec.js b/spec/javascripts/notes/stores/mutation_spec.js
index e4baefc5bfc..98f101d6bc5 100644
--- a/spec/javascripts/notes/stores/mutation_spec.js
+++ b/spec/javascripts/notes/stores/mutation_spec.js
@@ -101,10 +101,21 @@ describe('Notes Store mutations', () => {
const state = {
notes: [],
};
+ const legacyNote = {
+ id: 2,
+ individual_note: true,
+ notes: [{
+ note: '1',
+ }, {
+ note: '2',
+ }],
+ };
- mutations.SET_INITIAL_NOTES(state, [note]);
+ mutations.SET_INITIAL_NOTES(state, [note, legacyNote]);
expect(state.notes[0].id).toEqual(note.id);
- expect(state.notes.length).toEqual(1);
+ expect(state.notes[1].notes[0].note).toBe(legacyNote.notes[0].note);
+ expect(state.notes[2].notes[0].note).toBe(legacyNote.notes[1].note);
+ expect(state.notes.length).toEqual(3);
});
});
@@ -217,4 +228,70 @@ describe('Notes Store mutations', () => {
expect(state.notes[0].notes[0].note).toEqual('Foo');
});
});
+
+ describe('CLOSE_ISSUE', () => {
+ it('should set issue as closed', () => {
+ const state = {
+ notes: [],
+ targetNoteHash: null,
+ lastFetchedAt: null,
+ isToggleStateButtonLoading: false,
+ notesData: {},
+ userData: {},
+ noteableData: {},
+ };
+
+ mutations.CLOSE_ISSUE(state);
+ expect(state.noteableData.state).toEqual('closed');
+ });
+ });
+
+ describe('REOPEN_ISSUE', () => {
+ it('should set issue as closed', () => {
+ const state = {
+ notes: [],
+ targetNoteHash: null,
+ lastFetchedAt: null,
+ isToggleStateButtonLoading: false,
+ notesData: {},
+ userData: {},
+ noteableData: {},
+ };
+
+ mutations.REOPEN_ISSUE(state);
+ expect(state.noteableData.state).toEqual('reopened');
+ });
+ });
+
+ describe('TOGGLE_STATE_BUTTON_LOADING', () => {
+ it('should set isToggleStateButtonLoading as true', () => {
+ const state = {
+ notes: [],
+ targetNoteHash: null,
+ lastFetchedAt: null,
+ isToggleStateButtonLoading: false,
+ notesData: {},
+ userData: {},
+ noteableData: {},
+ };
+
+ mutations.TOGGLE_STATE_BUTTON_LOADING(state, true);
+ expect(state.isToggleStateButtonLoading).toEqual(true);
+ });
+
+ it('should set isToggleStateButtonLoading as false', () => {
+ const state = {
+ notes: [],
+ targetNoteHash: null,
+ lastFetchedAt: null,
+ isToggleStateButtonLoading: true,
+ notesData: {},
+ userData: {},
+ noteableData: {},
+ };
+
+ mutations.TOGGLE_STATE_BUTTON_LOADING(state, false);
+ expect(state.isToggleStateButtonLoading).toEqual(false);
+ });
+ });
});
diff --git a/spec/javascripts/oauth_remember_me_spec.js b/spec/javascripts/oauth_remember_me_spec.js
index b24563f738b..8816fe6defb 100644
--- a/spec/javascripts/oauth_remember_me_spec.js
+++ b/spec/javascripts/oauth_remember_me_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import OAuthRememberMe from '~/pages/sessions/new/oauth_remember_me';
describe('OAuthRememberMe', () => {
diff --git a/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js b/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js
index 349549b9e1f..b0dc6ccc3d4 100644
--- a/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js
+++ b/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import '~/lib/utils/text_utility';
import AbuseReports from '~/pages/admin/abuse_reports/abuse_reports';
diff --git a/spec/javascripts/pipelines/nav_controls_spec.js b/spec/javascripts/pipelines/nav_controls_spec.js
index 77c5258f74c..d6232f5c567 100644
--- a/spec/javascripts/pipelines/nav_controls_spec.js
+++ b/spec/javascripts/pipelines/nav_controls_spec.js
@@ -39,19 +39,6 @@ describe('Pipelines Nav Controls', () => {
expect(component.$el.querySelector('.js-run-pipeline')).toEqual(null);
});
- it('should render link for resetting runner caches', () => {
- const mockData = {
- newPipelinePath: 'foo',
- ciLintPath: 'foo',
- resetCachePath: 'foo',
- };
-
- component = mountComponent(NavControlsComponent, mockData);
-
- expect(component.$el.querySelector('.js-clear-cache').textContent.trim()).toContain('Clear Runner Caches');
- expect(component.$el.querySelector('.js-clear-cache').getAttribute('href')).toEqual(mockData.resetCachePath);
- });
-
it('should render link for CI lint', () => {
const mockData = {
newPipelinePath: 'foo',
@@ -65,4 +52,28 @@ describe('Pipelines Nav Controls', () => {
expect(component.$el.querySelector('.js-ci-lint').textContent.trim()).toContain('CI Lint');
expect(component.$el.querySelector('.js-ci-lint').getAttribute('href')).toEqual(mockData.ciLintPath);
});
+
+ describe('Reset Runners Cache', () => {
+ beforeEach(() => {
+ const mockData = {
+ newPipelinePath: 'foo',
+ ciLintPath: 'foo',
+ resetCachePath: 'foo',
+ };
+
+ component = mountComponent(NavControlsComponent, mockData);
+ });
+
+ it('should render button for resetting runner caches', () => {
+ expect(component.$el.querySelector('.js-clear-cache').textContent.trim()).toContain('Clear Runner Caches');
+ });
+
+ it('should emit postAction event when reset runner cache button is clicked', () => {
+ spyOn(component, '$emit');
+
+ component.$el.querySelector('.js-clear-cache').click();
+
+ expect(component.$emit).toHaveBeenCalledWith('resetRunnersCache', 'foo');
+ });
+ });
});
diff --git a/spec/javascripts/pipelines/pipelines_spec.js b/spec/javascripts/pipelines/pipelines_spec.js
index 84fd0329f08..7e242eb45e1 100644
--- a/spec/javascripts/pipelines/pipelines_spec.js
+++ b/spec/javascripts/pipelines/pipelines_spec.js
@@ -95,16 +95,16 @@ describe('Pipelines', () => {
expect(vm.$el.querySelector('.js-pipelines-tab-all').textContent.trim()).toContain('All');
});
- it('renders Run Pipeline button', () => {
+ it('renders Run Pipeline link', () => {
expect(vm.$el.querySelector('.js-run-pipeline').getAttribute('href')).toEqual(paths.newPipelinePath);
});
- it('renders CI Lint button', () => {
+ it('renders CI Lint link', () => {
expect(vm.$el.querySelector('.js-ci-lint').getAttribute('href')).toEqual(paths.ciLintPath);
});
it('renders Clear Runner Cache button', () => {
- expect(vm.$el.querySelector('.js-clear-cache').getAttribute('href')).toEqual(paths.resetCachePath);
+ expect(vm.$el.querySelector('.js-clear-cache').textContent.trim()).toEqual('Clear Runner Caches');
});
it('renders pipelines table', () => {
@@ -139,16 +139,16 @@ describe('Pipelines', () => {
expect(vm.$el.querySelector('.js-pipelines-tab-all').textContent.trim()).toContain('All');
});
- it('renders Run Pipeline button', () => {
+ it('renders Run Pipeline link', () => {
expect(vm.$el.querySelector('.js-run-pipeline').getAttribute('href')).toEqual(paths.newPipelinePath);
});
- it('renders CI Lint button', () => {
+ it('renders CI Lint link', () => {
expect(vm.$el.querySelector('.js-ci-lint').getAttribute('href')).toEqual(paths.ciLintPath);
});
it('renders Clear Runner Cache button', () => {
- expect(vm.$el.querySelector('.js-clear-cache').getAttribute('href')).toEqual(paths.resetCachePath);
+ expect(vm.$el.querySelector('.js-clear-cache').textContent.trim()).toEqual('Clear Runner Caches');
});
it('renders tab empty state', () => {
@@ -218,7 +218,7 @@ describe('Pipelines', () => {
it('renders buttons', () => {
expect(vm.$el.querySelector('.js-run-pipeline').getAttribute('href')).toEqual(paths.newPipelinePath);
expect(vm.$el.querySelector('.js-ci-lint').getAttribute('href')).toEqual(paths.ciLintPath);
- expect(vm.$el.querySelector('.js-clear-cache').getAttribute('href')).toEqual(paths.resetCachePath);
+ expect(vm.$el.querySelector('.js-clear-cache').textContent.trim()).toEqual('Clear Runner Caches');
});
it('renders error state', () => {
diff --git a/spec/javascripts/project_select_combo_button_spec.js b/spec/javascripts/project_select_combo_button_spec.js
index dda83645c92..1b65f767f96 100644
--- a/spec/javascripts/project_select_combo_button_spec.js
+++ b/spec/javascripts/project_select_combo_button_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import ProjectSelectComboButton from '~/project_select_combo_button';
const fixturePath = 'static/project_select_combo_button.html.raw';
diff --git a/spec/javascripts/projects/project_new_spec.js b/spec/javascripts/projects/project_new_spec.js
index 8731ce35d81..84515d2bf97 100644
--- a/spec/javascripts/projects/project_new_spec.js
+++ b/spec/javascripts/projects/project_new_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import projectNew from '~/projects/project_new';
describe('New Project', () => {
diff --git a/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js b/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js
index f6c0f51cf62..955ec6a531c 100644
--- a/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js
+++ b/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js
@@ -85,7 +85,7 @@ describe('PrometheusMetrics', () => {
expect(prometheusMetrics.$monitoredMetricsLoading.hasClass('hidden')).toBeTruthy();
expect(prometheusMetrics.$monitoredMetricsList.hasClass('hidden')).toBeFalsy();
- expect(prometheusMetrics.$monitoredMetricsCount.text()).toEqual('12');
+ expect(prometheusMetrics.$monitoredMetricsCount.text()).toEqual('3 exporters with 12 metrics were found');
expect($metricsListLi.length).toEqual(metrics.length);
expect($metricsListLi.first().find('.badge').text()).toEqual(`${metrics[0].active_metrics}`);
});
diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js
index 35bb630bf5d..80770a61011 100644
--- a/spec/javascripts/right_sidebar_spec.js
+++ b/spec/javascripts/right_sidebar_spec.js
@@ -1,5 +1,6 @@
/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, new-parens, no-return-assign, new-cap, vars-on-top, max-len */
+import $ from 'jquery';
import MockAdapter from 'axios-mock-adapter';
import '~/commons/bootstrap';
import axios from '~/lib/utils/axios_utils';
diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js
index 206f95abc1a..40115792652 100644
--- a/spec/javascripts/search_autocomplete_spec.js
+++ b/spec/javascripts/search_autocomplete_spec.js
@@ -1,5 +1,6 @@
/* eslint-disable space-before-function-paren, max-len, no-var, one-var, one-var-declaration-per-line, no-unused-expressions, consistent-return, no-param-reassign, default-case, no-return-assign, comma-dangle, object-shorthand, prefer-template, quotes, new-parens, vars-on-top, new-cap, max-len */
+import $ from 'jquery';
import '~/gl_dropdown';
import SearchAutocomplete from '~/search_autocomplete';
import '~/lib/utils/common_utils';
diff --git a/spec/javascripts/search_spec.js b/spec/javascripts/search_spec.js
index 38e94d45e55..522851c584b 100644
--- a/spec/javascripts/search_spec.js
+++ b/spec/javascripts/search_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import Api from '~/api';
import Search from '~/pages/search/show/search';
diff --git a/spec/javascripts/shortcuts_issuable_spec.js b/spec/javascripts/shortcuts_issuable_spec.js
index 5d6a885d4cc..faaf710cf6f 100644
--- a/spec/javascripts/shortcuts_issuable_spec.js
+++ b/spec/javascripts/shortcuts_issuable_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import initCopyAsGFM from '~/behaviors/copy_as_gfm';
import ShortcutsIssuable from '~/shortcuts_issuable';
diff --git a/spec/javascripts/shortcuts_spec.js b/spec/javascripts/shortcuts_spec.js
index a2a609edef9..ee92295ef5e 100644
--- a/spec/javascripts/shortcuts_spec.js
+++ b/spec/javascripts/shortcuts_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import Shortcuts from '~/shortcuts';
describe('Shortcuts', () => {
diff --git a/spec/javascripts/sidebar/assignee_title_spec.js b/spec/javascripts/sidebar/assignee_title_spec.js
index ac93f918ce4..509edba2036 100644
--- a/spec/javascripts/sidebar/assignee_title_spec.js
+++ b/spec/javascripts/sidebar/assignee_title_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import AssigneeTitle from '~/sidebar/components/assignees/assignee_title';
+import AssigneeTitle from '~/sidebar/components/assignees/assignee_title.vue';
describe('AssigneeTitle component', () => {
let component;
diff --git a/spec/javascripts/sidebar/sidebar_move_issue_spec.js b/spec/javascripts/sidebar/sidebar_move_issue_spec.js
index 0da5d91e376..d8e636cbdf0 100644
--- a/spec/javascripts/sidebar/sidebar_move_issue_spec.js
+++ b/spec/javascripts/sidebar/sidebar_move_issue_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import _ from 'underscore';
import Vue from 'vue';
import SidebarMediator from '~/sidebar/sidebar_mediator';
diff --git a/spec/javascripts/smart_interval_spec.js b/spec/javascripts/smart_interval_spec.js
index 7265e1b6cb5..a54219d58c2 100644
--- a/spec/javascripts/smart_interval_spec.js
+++ b/spec/javascripts/smart_interval_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import _ from 'underscore';
import SmartInterval from '~/smart_interval';
diff --git a/spec/javascripts/syntax_highlight_spec.js b/spec/javascripts/syntax_highlight_spec.js
index 763a15e710b..0d1fa680e00 100644
--- a/spec/javascripts/syntax_highlight_spec.js
+++ b/spec/javascripts/syntax_highlight_spec.js
@@ -1,5 +1,6 @@
/* eslint-disable space-before-function-paren, no-var, no-return-assign, quotes */
+import $ from 'jquery';
import syntaxHighlight from '~/syntax_highlight';
describe('Syntax Highlighter', function() {
diff --git a/spec/javascripts/todos_spec.js b/spec/javascripts/todos_spec.js
index 35871dddf89..898bbb3819b 100644
--- a/spec/javascripts/todos_spec.js
+++ b/spec/javascripts/todos_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import * as urlUtils from '~/lib/utils/url_utility';
import Todos from '~/pages/dashboard/todos/index/todos';
import '~/lib/utils/common_utils';
diff --git a/spec/javascripts/toggle_buttons_spec.js b/spec/javascripts/toggle_buttons_spec.js
index 205e396d682..17d0b94ebe0 100644
--- a/spec/javascripts/toggle_buttons_spec.js
+++ b/spec/javascripts/toggle_buttons_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import setupToggleButtons from '~/toggle_buttons';
import getSetTimeoutPromise from './helpers/set_timeout_promise_helper';
diff --git a/spec/javascripts/u2f/authenticate_spec.js b/spec/javascripts/u2f/authenticate_spec.js
index 4d15bcc4956..39c47a5c06d 100644
--- a/spec/javascripts/u2f/authenticate_spec.js
+++ b/spec/javascripts/u2f/authenticate_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import U2FAuthenticate from '~/u2f/authenticate';
import 'vendor/u2f';
import MockU2FDevice from './mock_u2f_device';
diff --git a/spec/javascripts/u2f/register_spec.js b/spec/javascripts/u2f/register_spec.js
index dbe89c2923c..136b4cad737 100644
--- a/spec/javascripts/u2f/register_spec.js
+++ b/spec/javascripts/u2f/register_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import U2FRegister from '~/u2f/register';
import 'vendor/u2f';
import MockU2FDevice from './mock_u2f_device';
diff --git a/spec/javascripts/version_check_image_spec.js b/spec/javascripts/version_check_image_spec.js
index 9637bd0414a..5f963e8c11e 100644
--- a/spec/javascripts/version_check_image_spec.js
+++ b/spec/javascripts/version_check_image_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import VersionCheckImage from '~/version_check_image';
import ClassSpecHelper from './helpers/class_spec_helper';
diff --git a/spec/javascripts/vue_mr_widget/components/deployment_spec.js b/spec/javascripts/vue_mr_widget/components/deployment_spec.js
new file mode 100644
index 00000000000..ff8d54c029f
--- /dev/null
+++ b/spec/javascripts/vue_mr_widget/components/deployment_spec.js
@@ -0,0 +1,172 @@
+import Vue from 'vue';
+import * as urlUtils from '~/lib/utils/url_utility';
+import deploymentComponent from '~/vue_merge_request_widget/components/deployment.vue';
+import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
+import { getTimeago } from '~/lib/utils/datetime_utility';
+
+const deploymentMockData = {
+ id: 15,
+ name: 'review/diplo',
+ url: '/root/acets-review-apps/environments/15',
+ stop_url: '/root/acets-review-apps/environments/15/stop',
+ metrics_url: '/root/acets-review-apps/environments/15/deployments/1/metrics',
+ metrics_monitoring_url: '/root/acets-review-apps/environments/15/metrics',
+ external_url: 'http://diplo.',
+ external_url_formatted: 'diplo.',
+ deployed_at: '2017-03-22T22:44:42.258Z',
+ deployed_at_formatted: 'Mar 22, 2017 10:44pm',
+};
+const createComponent = () => {
+ const Component = Vue.extend(deploymentComponent);
+
+ return new Component({
+ el: document.createElement('div'),
+ propsData: { deployment: { ...deploymentMockData } },
+ });
+};
+
+describe('Deployment component', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('computed', () => {
+ describe('deployTimeago', () => {
+ it('return formatted date', () => {
+ const readable = getTimeago().format(deploymentMockData.deployed_at);
+ expect(vm.deployTimeago).toEqual(readable);
+ });
+ });
+
+ describe('hasExternalUrls', () => {
+ it('should return true', () => {
+ expect(vm.hasExternalUrls).toEqual(true);
+ });
+
+ it('should return false when deployment has no external_url_formatted', () => {
+ vm.deployment.external_url_formatted = null;
+
+ expect(vm.hasExternalUrls).toEqual(false);
+ });
+
+ it('should return false when deployment has no external_url', () => {
+ vm.deployment.external_url = null;
+
+ expect(vm.hasExternalUrls).toEqual(false);
+ });
+ });
+
+ describe('hasDeploymentTime', () => {
+ it('should return true', () => {
+ expect(vm.hasDeploymentTime).toEqual(true);
+ });
+
+ it('should return false when deployment has no deployed_at', () => {
+ vm.deployment.deployed_at = null;
+
+ expect(vm.hasDeploymentTime).toEqual(false);
+ });
+
+ it('should return false when deployment has no deployed_at_formatted', () => {
+ vm.deployment.deployed_at_formatted = null;
+
+ expect(vm.hasDeploymentTime).toEqual(false);
+ });
+ });
+
+ describe('hasDeploymentMeta', () => {
+ it('should return true', () => {
+ expect(vm.hasDeploymentMeta).toEqual(true);
+ });
+
+ it('should return false when deployment has no url', () => {
+ vm.deployment.url = null;
+
+ expect(vm.hasDeploymentMeta).toEqual(false);
+ });
+
+ it('should return false when deployment has no name', () => {
+ vm.deployment.name = null;
+
+ expect(vm.hasDeploymentMeta).toEqual(false);
+ });
+ });
+ });
+
+ describe('methods', () => {
+ describe('stopEnvironment', () => {
+ const url = '/foo/bar';
+ const returnPromise = () => new Promise((resolve) => {
+ resolve({
+ data: {
+ redirect_url: url,
+ },
+ });
+ });
+ const mockStopEnvironment = () => {
+ vm.stopEnvironment(deploymentMockData);
+ return vm;
+ };
+
+ it('should show a confirm dialog and call service.stopEnvironment when confirmed', (done) => {
+ spyOn(window, 'confirm').and.returnValue(true);
+ spyOn(MRWidgetService, 'stopEnvironment').and.returnValue(returnPromise(true));
+ spyOn(urlUtils, 'visitUrl').and.returnValue(true);
+ vm = mockStopEnvironment();
+
+ expect(window.confirm).toHaveBeenCalled();
+ expect(MRWidgetService.stopEnvironment).toHaveBeenCalledWith(deploymentMockData.stop_url);
+ setTimeout(() => {
+ expect(urlUtils.visitUrl).toHaveBeenCalledWith(url);
+ done();
+ }, 333);
+ });
+
+ it('should show a confirm dialog but should not work if the dialog is rejected', () => {
+ spyOn(window, 'confirm').and.returnValue(false);
+ spyOn(MRWidgetService, 'stopEnvironment').and.returnValue(returnPromise(false));
+ vm = mockStopEnvironment();
+
+ expect(window.confirm).toHaveBeenCalled();
+ expect(MRWidgetService.stopEnvironment).not.toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('template', () => {
+ let el;
+
+ beforeEach(() => {
+ vm = createComponent(deploymentMockData);
+ el = vm.$el;
+ });
+
+ it('renders deployment name', () => {
+ expect(el.querySelector('.js-deploy-meta').getAttribute('href')).toEqual(deploymentMockData.url);
+ expect(el.querySelector('.js-deploy-meta').innerText).toContain(deploymentMockData.name);
+ });
+
+ it('renders external URL', () => {
+ expect(el.querySelector('.js-deploy-url').getAttribute('href')).toEqual(deploymentMockData.external_url);
+ expect(el.querySelector('.js-deploy-url').innerText).toContain(deploymentMockData.external_url_formatted);
+ });
+
+ it('renders stop button', () => {
+ expect(el.querySelector('.btn')).not.toBeNull();
+ });
+
+ it('renders deployment time', () => {
+ expect(el.querySelector('.js-deploy-time').innerText).toContain(vm.deployTimeago);
+ });
+
+ it('renders metrics component', () => {
+ expect(el.querySelector('.js-mr-memory-usage')).not.toBeNull();
+ });
+ });
+});
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js
deleted file mode 100644
index 6a59dc3c87e..00000000000
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js
+++ /dev/null
@@ -1,179 +0,0 @@
-import Vue from 'vue';
-import * as urlUtils from '~/lib/utils/url_utility';
-import deploymentComponent from '~/vue_merge_request_widget/components/mr_widget_deployment';
-import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
-import { getTimeago } from '~/lib/utils/datetime_utility';
-
-const deploymentMockData = [
- {
- id: 15,
- name: 'review/diplo',
- url: '/root/acets-review-apps/environments/15',
- stop_url: '/root/acets-review-apps/environments/15/stop',
- metrics_url: '/root/acets-review-apps/environments/15/deployments/1/metrics',
- metrics_monitoring_url: '/root/acets-review-apps/environments/15/metrics',
- external_url: 'http://diplo.',
- external_url_formatted: 'diplo.',
- deployed_at: '2017-03-22T22:44:42.258Z',
- deployed_at_formatted: 'Mar 22, 2017 10:44pm',
- },
-];
-const createComponent = () => {
- const Component = Vue.extend(deploymentComponent);
- const mr = {
- deployments: deploymentMockData,
- };
- const service = {};
-
- return new Component({
- el: document.createElement('div'),
- propsData: { mr, service },
- });
-};
-
-describe('MRWidgetDeployment', () => {
- describe('props', () => {
- it('should have props', () => {
- const { mr, service } = deploymentComponent.props;
-
- expect(mr.type instanceof Object).toBeTruthy();
- expect(mr.required).toBeTruthy();
-
- expect(service.type instanceof Object).toBeTruthy();
- expect(service.required).toBeTruthy();
- });
- });
-
- describe('methods', () => {
- let vm = createComponent();
- const deployment = deploymentMockData[0];
-
- describe('formatDate', () => {
- it('should work', () => {
- const readable = getTimeago().format(deployment.deployed_at);
- expect(vm.formatDate(deployment.deployed_at)).toEqual(readable);
- });
- });
-
- describe('hasExternalUrls', () => {
- it('should return true', () => {
- expect(vm.hasExternalUrls(deployment)).toBeTruthy();
- });
-
- it('should return false when there is not enough information', () => {
- expect(vm.hasExternalUrls()).toBeFalsy();
- expect(vm.hasExternalUrls({ external_url: 'Diplo' })).toBeFalsy();
- expect(vm.hasExternalUrls({ external_url_formatted: 'Diplo' })).toBeFalsy();
- });
- });
-
- describe('hasDeploymentTime', () => {
- it('should return true', () => {
- expect(vm.hasDeploymentTime(deployment)).toBeTruthy();
- });
-
- it('should return false when there is not enough information', () => {
- expect(vm.hasDeploymentTime()).toBeFalsy();
- expect(vm.hasDeploymentTime({ deployed_at: 'Diplo' })).toBeFalsy();
- expect(vm.hasDeploymentTime({ deployed_at_formatted: 'Diplo' })).toBeFalsy();
- });
- });
-
- describe('hasDeploymentMeta', () => {
- it('should return true', () => {
- expect(vm.hasDeploymentMeta(deployment)).toBeTruthy();
- });
-
- it('should return false when there is not enough information', () => {
- expect(vm.hasDeploymentMeta()).toBeFalsy();
- expect(vm.hasDeploymentMeta({ url: 'Diplo' })).toBeFalsy();
- expect(vm.hasDeploymentMeta({ name: 'Diplo' })).toBeFalsy();
- });
- });
-
- describe('stopEnvironment', () => {
- const url = '/foo/bar';
- const returnPromise = () => new Promise((resolve) => {
- resolve({
- data: {
- redirect_url: url,
- },
- });
- });
- const mockStopEnvironment = () => {
- vm.stopEnvironment(deploymentMockData);
- return vm;
- };
-
- it('should show a confirm dialog and call service.stopEnvironment when confirmed', (done) => {
- spyOn(window, 'confirm').and.returnValue(true);
- spyOn(MRWidgetService, 'stopEnvironment').and.returnValue(returnPromise(true));
- spyOn(urlUtils, 'visitUrl').and.returnValue(true);
- vm = mockStopEnvironment();
-
- expect(window.confirm).toHaveBeenCalled();
- expect(MRWidgetService.stopEnvironment).toHaveBeenCalledWith(deploymentMockData.stop_url);
- setTimeout(() => {
- expect(urlUtils.visitUrl).toHaveBeenCalledWith(url);
- done();
- }, 333);
- });
-
- it('should show a confirm dialog but should not work if the dialog is rejected', () => {
- spyOn(window, 'confirm').and.returnValue(false);
- spyOn(MRWidgetService, 'stopEnvironment').and.returnValue(returnPromise(false));
- vm = mockStopEnvironment();
-
- expect(window.confirm).toHaveBeenCalled();
- expect(MRWidgetService.stopEnvironment).not.toHaveBeenCalled();
- });
- });
- });
-
- describe('template', () => {
- let vm;
- let el;
- const [deployment] = deploymentMockData;
-
- beforeEach(() => {
- vm = createComponent(deploymentMockData);
- el = vm.$el;
- });
-
- it('should render template elements correctly', () => {
- expect(el.classList.contains('mr-widget-heading')).toBeTruthy();
- expect(el.querySelector('.js-icon-link')).toBeDefined();
- expect(el.querySelector('.js-deploy-meta').getAttribute('href')).toEqual(deployment.url);
- expect(el.querySelector('.js-deploy-meta').innerText).toContain(deployment.name);
- expect(el.querySelector('.js-deploy-url').getAttribute('href')).toEqual(deployment.external_url);
- expect(el.querySelector('.js-deploy-url').innerText).toContain(deployment.external_url_formatted);
- expect(el.querySelector('.js-deploy-time').innerText).toContain(vm.formatDate(deployment.deployed_at));
- expect(el.querySelector('.js-mr-memory-usage')).toBeDefined();
- expect(el.querySelector('button')).toBeDefined();
- });
-
- it('should list multiple deployments', (done) => {
- vm.mr.deployments.push(deployment);
- vm.mr.deployments.push(deployment);
-
- Vue.nextTick(() => {
- expect(el.querySelectorAll('.ci-widget').length).toEqual(3);
- expect(el.querySelectorAll('.js-mr-memory-usage').length).toEqual(3);
- done();
- });
- });
-
- it('should not have some elements when there is not enough data', (done) => {
- vm.mr.deployments = [{}];
-
- Vue.nextTick(() => {
- expect(el.querySelectorAll('.js-deploy-meta').length).toEqual(0);
- expect(el.querySelectorAll('.js-deploy-url').length).toEqual(0);
- expect(el.querySelectorAll('.js-deploy-time').length).toEqual(0);
- expect(el.querySelectorAll('.js-mr-memory-usage').length).toEqual(0);
- expect(el.querySelectorAll('.button').length).toEqual(0);
- done();
- });
- });
- });
-});
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_maintainer_edit_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_maintainer_edit_spec.js
index 65b3f721281..cee22d5342a 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_maintainer_edit_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_maintainer_edit_spec.js
@@ -2,22 +2,39 @@ import Vue from 'vue';
import maintainerEditComponent from '~/vue_merge_request_widget/components/mr_widget_maintainer_edit.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
-describe('MRWidgetAuthor', () => {
+describe('RWidgetMaintainerEdit', () => {
+ let Component;
let vm;
beforeEach(() => {
- const Component = Vue.extend(maintainerEditComponent);
-
- vm = mountComponent(Component, {
- maintainerEditAllowed: true,
- });
+ Component = Vue.extend(maintainerEditComponent);
});
afterEach(() => {
vm.$destroy();
});
- it('renders the message when maintainers are allowed to edit', () => {
- expect(vm.$el.textContent.trim()).toEqual('Allows edits from maintainers');
+ describe('when a maintainer is allowed to edit', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ maintainerEditAllowed: true,
+ });
+ });
+
+ it('it renders the message', () => {
+ expect(vm.$el.textContent.trim()).toEqual('Allows edits from maintainers');
+ });
+ });
+
+ describe('when a maintainer is not allowed to edit', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ maintainerEditAllowed: false,
+ });
+ });
+
+ it('hides the message', () => {
+ expect(vm.$el.textContent.trim()).toEqual('');
+ });
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js
index 07ed7f7f532..31710551399 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import memoryUsageComponent from '~/vue_merge_request_widget/components/mr_widget_memory_usage';
+import MemoryUsage from '~/vue_merge_request_widget/components/memory_usage.vue';
import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
const url = '/root/acets-review-apps/environments/15/deployments/1/metrics';
@@ -34,7 +34,7 @@ const metricsMockData = {
};
const createComponent = () => {
- const Component = Vue.extend(memoryUsageComponent);
+ const Component = Vue.extend(MemoryUsage);
return new Component({
el: document.createElement('div'),
@@ -67,21 +67,9 @@ describe('MemoryUsage', () => {
el = vm.$el;
});
- describe('props', () => {
- it('should have props with defaults', () => {
- const { metricsUrl } = memoryUsageComponent.props;
- const MetricsUrlTypeClass = metricsUrl.type;
-
- Vue.nextTick(() => {
- expect(new MetricsUrlTypeClass() instanceof String).toBeTruthy();
- expect(metricsUrl.required).toBeTruthy();
- });
- });
- });
-
describe('data', () => {
it('should have default data', () => {
- const data = memoryUsageComponent.data();
+ const data = MemoryUsage.data();
expect(Array.isArray(data.memoryMetrics)).toBeTruthy();
expect(data.memoryMetrics.length).toBe(0);
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js
index dd907ad9015..d47815a5b5a 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js
@@ -1,5 +1,6 @@
import Vue from 'vue';
import mwpsComponent from '~/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue';
+import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
import eventHub from '~/vue_merge_request_widget/event_hub';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
@@ -25,12 +26,7 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
targetBranchPath,
targetBranch,
},
- service: {
- cancelAutomaticMerge() {},
- mergeResource: {
- save() {},
- },
- },
+ service: new MRWidgetService({}),
});
});
@@ -90,18 +86,16 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
describe('removeSourceBranch', () => {
it('should set flag and call service then request main component to update the widget', (done) => {
- spyOn(vm.service.mergeResource, 'save').and.returnValue(new Promise((resolve) => {
- resolve({
- data: {
- status: 'merge_when_pipeline_succeeds',
- },
- });
+ spyOn(vm.service, 'merge').and.returnValue(Promise.resolve({
+ data: {
+ status: 'merge_when_pipeline_succeeds',
+ },
}));
vm.removeSourceBranch();
setTimeout(() => {
expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
- expect(vm.service.mergeResource.save).toHaveBeenCalledWith({
+ expect(vm.service.merge).toHaveBeenCalledWith({
sha,
merge_when_pipeline_succeeds: true,
should_remove_source_branch: true,
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js
index a8a02fa6b66..2a762c9336e 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js
@@ -1,9 +1,9 @@
import Vue from 'vue';
-import nothingToMergeComponent from '~/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge';
+import NothingToMerge from '~/vue_merge_request_widget/components/states/nothing_to_merge.vue';
-describe('MRWidgetNothingToMerge', () => {
+describe('NothingToMerge', () => {
describe('template', () => {
- const Component = Vue.extend(nothingToMergeComponent);
+ const Component = Vue.extend(NothingToMerge);
const newBlobPath = '/foo';
const vm = new Component({
el: document.createElement('div'),
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
index 073f26cc78f..58f683fb3e6 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
@@ -517,13 +517,9 @@ describe('MRWidgetReadyToMerge', () => {
describe('Remove source branch checkbox', () => {
describe('when user can merge but cannot delete branch', () => {
- it('isRemoveSourceBranchButtonDisabled should be true', () => {
- expect(vm.isRemoveSourceBranchButtonDisabled).toBe(true);
- });
-
it('should be disabled in the rendered output', () => {
const checkboxElement = vm.$el.querySelector('#remove-source-branch-input');
- expect(checkboxElement.getAttribute('disabled')).toBe('disabled');
+ expect(checkboxElement).toBeNull();
});
});
@@ -540,7 +536,7 @@ describe('MRWidgetReadyToMerge', () => {
it('should be enabled in rendered output', () => {
const checkboxElement = this.customVm.$el.querySelector('#remove-source-branch-input');
- expect(checkboxElement.getAttribute('disabled')).toBeNull();
+ expect(checkboxElement).not.toBeNull();
});
});
});
@@ -549,12 +545,12 @@ describe('MRWidgetReadyToMerge', () => {
describe('when allowed to merge', () => {
beforeEach(() => {
vm = createComponent({
- mr: { isMergeAllowed: true },
+ mr: { isMergeAllowed: true, canRemoveSourceBranch: true },
});
});
it('shows remove source branch checkbox', () => {
- expect(vm.$el.querySelector('.js-remove-source-branch-checkbox')).toBeDefined();
+ expect(vm.$el.querySelector('.js-remove-source-branch-checkbox')).not.toBeNull();
});
it('shows modify commit message button', () => {
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js
index fe87f110354..046968fbc1f 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js
@@ -1,10 +1,10 @@
import Vue from 'vue';
-import unresolvedDiscussionsComponent from '~/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions';
+import UnresolvedDiscussions from '~/vue_merge_request_widget/components/states/unresolved_discussions.vue';
-describe('MRWidgetUnresolvedDiscussions', () => {
+describe('UnresolvedDiscussions', () => {
describe('props', () => {
it('should have props', () => {
- const { mr } = unresolvedDiscussionsComponent.props;
+ const { mr } = UnresolvedDiscussions.props;
expect(mr.type instanceof Object).toBeTruthy();
expect(mr.required).toBeTruthy();
@@ -17,7 +17,7 @@ describe('MRWidgetUnresolvedDiscussions', () => {
const path = 'foo/bar';
beforeEach(() => {
- const Component = Vue.extend(unresolvedDiscussionsComponent);
+ const Component = Vue.extend(UnresolvedDiscussions);
const mr = {
createIssueToResolveDiscussionsPath: path,
};
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 ebe151ac3b1..e55c7649d40 100644
--- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
+++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
@@ -81,14 +81,46 @@ describe('mrWidgetOptions', () => {
});
});
- describe('shouldRenderDeployments', () => {
- it('should return false for the initial data', () => {
- expect(vm.shouldRenderDeployments).toBeFalsy();
+ describe('shouldRenderSourceBranchRemovalStatus', () => {
+ beforeEach(() => {
+ vm.mr.state = 'readyToMerge';
+ });
+
+ it('should return true when cannot remove source branch and branch will be removed', () => {
+ vm.mr.canRemoveSourceBranch = false;
+ vm.mr.shouldRemoveSourceBranch = true;
+
+ expect(vm.shouldRenderSourceBranchRemovalStatus).toEqual(true);
+ });
+
+ it('should return false when can remove source branch and branch will be removed', () => {
+ vm.mr.canRemoveSourceBranch = true;
+ vm.mr.shouldRemoveSourceBranch = true;
+
+ expect(vm.shouldRenderSourceBranchRemovalStatus).toEqual(false);
+ });
+
+ it('should return false when cannot remove source branch and branch will not be removed', () => {
+ vm.mr.canRemoveSourceBranch = false;
+ vm.mr.shouldRemoveSourceBranch = false;
+
+ expect(vm.shouldRenderSourceBranchRemovalStatus).toEqual(false);
});
- it('should return true if there is deployments', () => {
- vm.mr.deployments.push({}, {});
- expect(vm.shouldRenderDeployments).toBeTruthy();
+ it('should return false when in merged state', () => {
+ vm.mr.canRemoveSourceBranch = false;
+ vm.mr.shouldRemoveSourceBranch = true;
+ vm.mr.state = 'merged';
+
+ expect(vm.shouldRenderSourceBranchRemovalStatus).toEqual(false);
+ });
+
+ it('should return false when in nothing to merge state', () => {
+ vm.mr.canRemoveSourceBranch = false;
+ vm.mr.shouldRemoveSourceBranch = true;
+ vm.mr.state = 'nothingToMerge';
+
+ expect(vm.shouldRenderSourceBranchRemovalStatus).toEqual(false);
});
});
});
@@ -146,16 +178,16 @@ describe('mrWidgetOptions', () => {
describe('fetchDeployments', () => {
it('should fetch deployments', (done) => {
- spyOn(vm.service, 'fetchDeployments').and.returnValue(returnPromise([{ deployment: 1 }]));
+ spyOn(vm.service, 'fetchDeployments').and.returnValue(returnPromise([{ id: 1 }]));
vm.fetchDeployments();
setTimeout(() => {
expect(vm.service.fetchDeployments).toHaveBeenCalled();
expect(vm.mr.deployments.length).toEqual(1);
- expect(vm.mr.deployments[0].deployment).toEqual(1);
+ expect(vm.mr.deployments[0].id).toBe(1);
done();
- }, 333);
+ });
});
});
@@ -325,34 +357,6 @@ describe('mrWidgetOptions', () => {
});
});
- describe('components', () => {
- it('should register all components', () => {
- const comps = mrWidgetOptions.components;
- expect(comps['mr-widget-header']).toBeDefined();
- expect(comps['mr-widget-merge-help']).toBeDefined();
- expect(comps['mr-widget-pipeline']).toBeDefined();
- expect(comps['mr-widget-deployment']).toBeDefined();
- expect(comps['mr-widget-related-links']).toBeDefined();
- expect(comps['mr-widget-merged']).toBeDefined();
- expect(comps['mr-widget-closed']).toBeDefined();
- expect(comps['mr-widget-merging']).toBeDefined();
- expect(comps['mr-widget-failed-to-merge']).toBeDefined();
- expect(comps['mr-widget-wip']).toBeDefined();
- expect(comps['mr-widget-archived']).toBeDefined();
- expect(comps['mr-widget-conflicts']).toBeDefined();
- expect(comps['mr-widget-nothing-to-merge']).toBeDefined();
- expect(comps['mr-widget-not-allowed']).toBeDefined();
- expect(comps['mr-widget-missing-branch']).toBeDefined();
- expect(comps['mr-widget-ready-to-merge']).toBeDefined();
- expect(comps['mr-widget-checking']).toBeDefined();
- expect(comps['mr-widget-unresolved-discussions']).toBeDefined();
- expect(comps['mr-widget-pipeline-blocked']).toBeDefined();
- expect(comps['mr-widget-pipeline-failed']).toBeDefined();
- expect(comps['mr-widget-merge-when-pipeline-succeeds']).toBeDefined();
- expect(comps['mr-widget-maintainer-edit']).toBeDefined();
- });
- });
-
describe('rendering relatedLinks', () => {
beforeEach((done) => {
vm.mr.relatedLinks = {
@@ -379,4 +383,66 @@ describe('mrWidgetOptions', () => {
});
});
});
+
+ describe('rendering source branch removal status', () => {
+ it('renders when user cannot remove branch and branch should be removed', (done) => {
+ vm.mr.canRemoveSourceBranch = false;
+ vm.mr.shouldRemoveSourceBranch = true;
+ vm.mr.state = 'readyToMerge';
+
+ vm.$nextTick(() => {
+ const tooltip = vm.$el.querySelector('.fa-question-circle');
+
+ expect(vm.$el.textContent).toContain('Removes source branch');
+ expect(tooltip.getAttribute('data-original-title')).toBe(
+ 'A user with write access to the source branch selected this option',
+ );
+
+ done();
+ });
+ });
+
+ it('does not render in merged state', (done) => {
+ vm.mr.canRemoveSourceBranch = false;
+ vm.mr.shouldRemoveSourceBranch = true;
+ vm.mr.state = 'merged';
+
+ vm.$nextTick(() => {
+ expect(vm.$el.textContent).toContain('The source branch has been removed');
+ expect(vm.$el.textContent).not.toContain('Removes source branch');
+
+ done();
+ });
+ });
+ });
+
+ describe('rendering deployments', () => {
+ const deploymentMockData = {
+ id: 15,
+ name: 'review/diplo',
+ url: '/root/acets-review-apps/environments/15',
+ stop_url: '/root/acets-review-apps/environments/15/stop',
+ metrics_url: '/root/acets-review-apps/environments/15/deployments/1/metrics',
+ metrics_monitoring_url: '/root/acets-review-apps/environments/15/metrics',
+ external_url: 'http://diplo.',
+ external_url_formatted: 'diplo.',
+ deployed_at: '2017-03-22T22:44:42.258Z',
+ deployed_at_formatted: 'Mar 22, 2017 10:44pm',
+ };
+
+ beforeEach((done) => {
+ vm.mr.deployments.push({
+ ...deploymentMockData,
+ }, {
+ ...deploymentMockData,
+ id: deploymentMockData.id + 1,
+ });
+
+ vm.$nextTick(done);
+ });
+
+ it('renders multiple deployments', () => {
+ expect(vm.$el.querySelectorAll('.deploy-heading').length).toBe(2);
+ });
+ });
});
diff --git a/spec/javascripts/vue_shared/components/gl_modal_spec.js b/spec/javascripts/vue_shared/components/gl_modal_spec.js
index 2805d9a7003..85cb1b90fc6 100644
--- a/spec/javascripts/vue_shared/components/gl_modal_spec.js
+++ b/spec/javascripts/vue_shared/components/gl_modal_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import Vue from 'vue';
import GlModal from '~/vue_shared/components/gl_modal.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
diff --git a/spec/javascripts/vue_shared/components/markdown/field_spec.js b/spec/javascripts/vue_shared/components/markdown/field_spec.js
index 5f980bbf36c..69034975422 100644
--- a/spec/javascripts/vue_shared/components/markdown/field_spec.js
+++ b/spec/javascripts/vue_shared/components/markdown/field_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import Vue from 'vue';
import fieldComponent from '~/vue_shared/components/markdown/field.vue';
diff --git a/spec/javascripts/vue_shared/components/memory_graph_spec.js b/spec/javascripts/vue_shared/components/memory_graph_spec.js
index d46a3f2328e..73a69df019e 100644
--- a/spec/javascripts/vue_shared/components/memory_graph_spec.js
+++ b/spec/javascripts/vue_shared/components/memory_graph_spec.js
@@ -1,12 +1,12 @@
import Vue from 'vue';
-import memoryGraphComponent from '~/vue_shared/components/memory_graph';
+import MemoryGraph from '~/vue_shared/components/memory_graph.vue';
import { mockMetrics, mockMedian, mockMedianIndex } from './mock_data';
const defaultHeight = '25';
const defaultWidth = '100';
const createComponent = () => {
- const Component = Vue.extend(memoryGraphComponent);
+ const Component = Vue.extend(MemoryGraph);
return new Component({
el: document.createElement('div'),
@@ -32,29 +32,9 @@ describe('MemoryGraph', () => {
el = vm.$el;
});
- describe('props', () => {
- it('should have props with defaults', (done) => {
- const { metrics, deploymentTime, width, height } = memoryGraphComponent.props;
-
- Vue.nextTick(() => {
- const typeClassMatcher = (propItem, expectedType) => {
- const PropItemTypeClass = propItem.type;
- expect(new PropItemTypeClass() instanceof expectedType).toBeTruthy();
- expect(propItem.required).toBeTruthy();
- };
-
- typeClassMatcher(metrics, Array);
- typeClassMatcher(deploymentTime, Number);
- typeClassMatcher(width, String);
- typeClassMatcher(height, String);
- done();
- });
- });
- });
-
describe('data', () => {
it('should have default data', () => {
- const data = memoryGraphComponent.data();
+ const data = MemoryGraph.data();
const dataValidator = (dataItem, expectedType, defaultVal) => {
expect(typeof dataItem).toBe(expectedType);
expect(dataItem).toBe(defaultVal);
diff --git a/spec/javascripts/vue_shared/components/modal_spec.js b/spec/javascripts/vue_shared/components/modal_spec.js
index 8412df74f98..d01a94c25e5 100644
--- a/spec/javascripts/vue_shared/components/modal_spec.js
+++ b/spec/javascripts/vue_shared/components/modal_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import Vue from 'vue';
import modal from '~/vue_shared/components/modal.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/base_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/base_spec.js
index 67056793a20..8daaf018396 100644
--- a/spec/javascripts/vue_shared/components/sidebar/labels_select/base_spec.js
+++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/base_spec.js
@@ -37,6 +37,32 @@ describe('BaseComponent', () => {
vmNonEditable.$destroy();
});
});
+
+ describe('createLabelTitle', () => {
+ it('returns `Create project label` when `isProject` prop is true', () => {
+ expect(vm.createLabelTitle).toBe('Create project label');
+ });
+
+ it('return `Create group label` when `isProject` prop is false', () => {
+ const mockConfigGroup = Object.assign({}, mockConfig, { isProject: false });
+ const vmGroup = createComponent(mockConfigGroup);
+ expect(vmGroup.createLabelTitle).toBe('Create group label');
+ vmGroup.$destroy();
+ });
+ });
+
+ describe('manageLabelsTitle', () => {
+ it('returns `Manage project labels` when `isProject` prop is true', () => {
+ expect(vm.manageLabelsTitle).toBe('Manage project labels');
+ });
+
+ it('return `Manage group labels` when `isProject` prop is false', () => {
+ const mockConfigGroup = Object.assign({}, mockConfig, { isProject: false });
+ const vmGroup = createComponent(mockConfigGroup);
+ expect(vmGroup.manageLabelsTitle).toBe('Manage group labels');
+ vmGroup.$destroy();
+ });
+ });
});
describe('methods', () => {
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js
index f07aefb2f87..5cb4bb6fea6 100644
--- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js
+++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js
@@ -6,10 +6,12 @@ import { mockSuggestedColors } from './mock_data';
import mountComponent from '../../../../helpers/vue_mount_component_helper';
-const createComponent = () => {
+const createComponent = (headerTitle) => {
const Component = Vue.extend(dropdownCreateLabelComponent);
- return mountComponent(Component);
+ return mountComponent(Component, {
+ headerTitle,
+ });
};
describe('DropdownCreateLabelComponent', () => {
@@ -41,11 +43,19 @@ describe('DropdownCreateLabelComponent', () => {
expect(backButtonEl.querySelector('.fa-arrow-left')).not.toBe(null);
});
- it('renders component header element', () => {
+ it('renders component header element as `Create new label` when `headerTitle` prop is not provided', () => {
const headerEl = vm.$el.querySelector('.dropdown-title');
expect(headerEl.innerText.trim()).toContain('Create new label');
});
+ it('renders component header element with value of `headerTitle` prop', () => {
+ const headerTitle = 'Create project label';
+ const vmWithHeaderTitle = createComponent(headerTitle);
+ const headerEl = vmWithHeaderTitle.$el.querySelector('.dropdown-title');
+ expect(headerEl.innerText.trim()).toContain(headerTitle);
+ vmWithHeaderTitle.$destroy();
+ });
+
it('renders `Close` button on component header', () => {
const closeButtonEl = vm.$el.querySelector('.dropdown-title button.dropdown-title-button.dropdown-menu-close');
expect(closeButtonEl).not.toBe(null);
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js
index 809e0327b1c..0f4fa716f8a 100644
--- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js
+++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js
@@ -6,15 +6,23 @@ import { mockConfig } from './mock_data';
import mountComponent from '../../../../helpers/vue_mount_component_helper';
-const createComponent = (labelsWebUrl = mockConfig.labelsWebUrl) => {
+const createComponent = (
+ labelsWebUrl = mockConfig.labelsWebUrl,
+ createLabelTitle,
+ manageLabelsTitle,
+) => {
const Component = Vue.extend(dropdownFooterComponent);
return mountComponent(Component, {
labelsWebUrl,
+ createLabelTitle,
+ manageLabelsTitle,
});
};
describe('DropdownFooterComponent', () => {
+ const createLabelTitle = 'Create project label';
+ const manageLabelsTitle = 'Manage project labels';
let vm;
beforeEach(() => {
@@ -26,17 +34,35 @@ describe('DropdownFooterComponent', () => {
});
describe('template', () => {
- it('renders `Create new label` link element', () => {
+ it('renders link element with `Create new label` when `createLabelTitle` prop is not provided', () => {
const createLabelEl = vm.$el.querySelector('.dropdown-footer-list .dropdown-toggle-page');
expect(createLabelEl).not.toBeNull();
expect(createLabelEl.innerText.trim()).toBe('Create new label');
});
- it('renders `Manage labels` link element', () => {
+ it('renders link element with value of `createLabelTitle` prop', () => {
+ const vmWithCreateLabelTitle = createComponent(mockConfig.labelsWebUrl, createLabelTitle);
+ const createLabelEl = vmWithCreateLabelTitle.$el.querySelector('.dropdown-footer-list .dropdown-toggle-page');
+ expect(createLabelEl.innerText.trim()).toBe(createLabelTitle);
+ vmWithCreateLabelTitle.$destroy();
+ });
+
+ it('renders link element with `Manage labels` when `manageLabelsTitle` prop is not provided', () => {
const manageLabelsEl = vm.$el.querySelector('.dropdown-footer-list .dropdown-external-link');
expect(manageLabelsEl).not.toBeNull();
expect(manageLabelsEl.getAttribute('href')).toBe(vm.labelsWebUrl);
expect(manageLabelsEl.innerText.trim()).toBe('Manage labels');
});
+
+ it('renders link element with value of `manageLabelsTitle` prop', () => {
+ const vmWithManageLabelsTitle = createComponent(
+ mockConfig.labelsWebUrl,
+ createLabelTitle,
+ manageLabelsTitle,
+ );
+ const manageLabelsEl = vmWithManageLabelsTitle.$el.querySelector('.dropdown-footer-list .dropdown-external-link');
+ expect(manageLabelsEl.innerText.trim()).toBe(manageLabelsTitle);
+ vmWithManageLabelsTitle.$destroy();
+ });
});
});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js
index e9008c29b22..3fcb91b6f5e 100644
--- a/spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js
+++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js
@@ -34,6 +34,7 @@ export const mockSuggestedColors = [
export const mockConfig = {
showCreate: true,
+ isProject: true,
abilityName: 'issue',
context: {
labels: mockLabels,
diff --git a/spec/javascripts/vue_shared/directives/tooltip_spec.js b/spec/javascripts/vue_shared/directives/tooltip_spec.js
index b1b3071527b..4a644913e44 100644
--- a/spec/javascripts/vue_shared/directives/tooltip_spec.js
+++ b/spec/javascripts/vue_shared/directives/tooltip_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import Vue from 'vue';
import tooltip from '~/vue_shared/directives/tooltip';
diff --git a/spec/javascripts/zen_mode_spec.js b/spec/javascripts/zen_mode_spec.js
index 8edba1f47a3..7fe3bd92049 100644
--- a/spec/javascripts/zen_mode_spec.js
+++ b/spec/javascripts/zen_mode_spec.js
@@ -1,3 +1,4 @@
+import $ from 'jquery';
import Mousetrap from 'mousetrap';
import Dropzone from 'dropzone';
import ZenMode from '~/zen_mode';
diff --git a/spec/lib/api/helpers/related_resources_helpers_spec.rb b/spec/lib/api/helpers/related_resources_helpers_spec.rb
new file mode 100644
index 00000000000..b918301f1cb
--- /dev/null
+++ b/spec/lib/api/helpers/related_resources_helpers_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+describe API::Helpers::RelatedResourcesHelpers do
+ subject(:helpers) do
+ Class.new.include(described_class).new
+ end
+
+ describe '#expose_url' do
+ let(:path) { '/api/v4/awesome_endpoint' }
+ subject(:url) { helpers.expose_url(path) }
+
+ def stub_default_url_options(protocol: 'http', host: 'example.com', port: nil)
+ expect(Gitlab::Application.routes).to receive(:default_url_options)
+ .and_return(protocol: protocol, host: host, port: port)
+ end
+
+ it 'respects the protocol if it is HTTP' do
+ stub_default_url_options(protocol: 'http')
+
+ is_expected.to start_with('http://')
+ end
+
+ it 'respects the protocol if it is HTTPS' do
+ stub_default_url_options(protocol: 'https')
+
+ is_expected.to start_with('https://')
+ end
+
+ it 'accepts port to be nil' do
+ stub_default_url_options(port: nil)
+
+ is_expected.to start_with('http://example.com/')
+ end
+
+ it 'includes port if provided' do
+ stub_default_url_options(port: 8080)
+
+ is_expected.to start_with('http://example.com:8080/')
+ end
+ end
+end
diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb
index 3ca4652f7cc..ba8dc68ceda 100644
--- a/spec/lib/banzai/filter/relative_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb
@@ -217,6 +217,23 @@ describe Banzai::Filter::RelativeLinkFilter do
end
end
+ context 'when ref name contains special chars' do
+ let(:ref) {'mark#\'@],+;-._/#@!$&()+down'}
+
+ it 'correctly escapes the ref' do
+ # Adressable won't escape the '#', so we do this manually
+ ref_escaped = 'mark%23\'@%5D,+;-._/%23@!$&()+down'
+
+ # Stub this method so the branch doesn't actually need to be in the repo
+ allow_any_instance_of(described_class).to receive(:uri_type).and_return(:raw)
+
+ doc = filter(link('files/images/logo-black.png'))
+
+ expect(doc.at_css('a')['href'])
+ .to eq "/#{project_path}/raw/#{ref_escaped}/files/images/logo-black.png"
+ end
+ end
+
context 'when requested path is a directory with space in the repo' do
let(:ref) { 'master' }
let(:commit) { project.commit('38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e') }
diff --git a/spec/lib/constraints/group_url_constrainer_spec.rb b/spec/lib/constraints/group_url_constrainer_spec.rb
index 4dab58b26a0..ff295068ba9 100644
--- a/spec/lib/constraints/group_url_constrainer_spec.rb
+++ b/spec/lib/constraints/group_url_constrainer_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe GroupUrlConstrainer do
+describe Constraints::GroupUrlConstrainer do
let!(:group) { create(:group, path: 'gitlab') }
describe '#matches?' do
diff --git a/spec/lib/constraints/project_url_constrainer_spec.rb b/spec/lib/constraints/project_url_constrainer_spec.rb
index 92331eb2e5d..c96e7ab8495 100644
--- a/spec/lib/constraints/project_url_constrainer_spec.rb
+++ b/spec/lib/constraints/project_url_constrainer_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe ProjectUrlConstrainer do
+describe Constraints::ProjectUrlConstrainer do
let!(:project) { create(:project) }
let!(:namespace) { project.namespace }
diff --git a/spec/lib/constraints/user_url_constrainer_spec.rb b/spec/lib/constraints/user_url_constrainer_spec.rb
index cb3b4ff1391..e2c85bb27bb 100644
--- a/spec/lib/constraints/user_url_constrainer_spec.rb
+++ b/spec/lib/constraints/user_url_constrainer_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe UserUrlConstrainer do
+describe Constraints::UserUrlConstrainer do
let!(:user) { create(:user, username: 'dz') }
describe '#matches?' do
diff --git a/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb b/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb
index 21a791f5695..c43ed72038e 100644
--- a/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb
+++ b/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb
@@ -37,6 +37,18 @@ describe Gitlab::BackgroundMigration::AddMergeRequestDiffCommitsCount, :migratio
expect(diff.reload.commits_count).to eq(0)
end
+ it 'skips diffs that have commits_count already set' do
+ timestamp = 2.days.ago
+ diff = merge_request_diffs_table.create!(
+ merge_request_id: merge_request.id,
+ commits_count: 0,
+ updated_at: timestamp)
+
+ subject.perform(diff.id, diff.id)
+
+ expect(diff.reload.updated_at).to be_within(1.second).of(timestamp)
+ end
+
it 'migrates multiple diffs to the correct values' do
diffs = Array.new(3).map.with_index { |_, i| create_diff!(i, commits: 3) }
diff --git a/spec/lib/gitlab/bare_repository_import/repository_spec.rb b/spec/lib/gitlab/bare_repository_import/repository_spec.rb
index 9f42cf1dfca..5cb1f4deb5f 100644
--- a/spec/lib/gitlab/bare_repository_import/repository_spec.rb
+++ b/spec/lib/gitlab/bare_repository_import/repository_spec.rb
@@ -61,7 +61,7 @@ describe ::Gitlab::BareRepositoryImport::Repository do
let(:wiki_path) { File.join(root_path, "#{hashed_path}.wiki.git") }
before do
- gitlab_shell.add_repository(repository_storage, hashed_path)
+ gitlab_shell.create_repository(repository_storage, hashed_path)
repository = Rugged::Repository.new(repo_path)
repository.config['gitlab.fullpath'] = 'to/repo'
end
diff --git a/spec/lib/gitlab/ci/variables/collection/item_spec.rb b/spec/lib/gitlab/ci/variables/collection/item_spec.rb
new file mode 100644
index 00000000000..cc1257484d2
--- /dev/null
+++ b/spec/lib/gitlab/ci/variables/collection/item_spec.rb
@@ -0,0 +1,54 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Variables::Collection::Item do
+ let(:variable) do
+ { key: 'VAR', value: 'something', public: true }
+ end
+
+ describe '.fabricate' do
+ it 'supports using a hash' do
+ resource = described_class.fabricate(variable)
+
+ expect(resource).to be_a(described_class)
+ expect(resource).to eq variable
+ end
+
+ it 'supports using an active record resource' do
+ variable = create(:ci_variable, key: 'CI_VAR', value: '123')
+ resource = described_class.fabricate(variable)
+
+ expect(resource).to be_a(described_class)
+ expect(resource).to eq(key: 'CI_VAR', value: '123', public: false)
+ end
+
+ it 'supports using another collection item' do
+ item = described_class.new(**variable)
+
+ resource = described_class.fabricate(item)
+
+ expect(resource).to be_a(described_class)
+ expect(resource).to eq variable
+ expect(resource.object_id).not_to eq item.object_id
+ end
+ end
+
+ describe '#==' do
+ it 'compares a hash representation of a variable' do
+ expect(described_class.new(**variable) == variable).to be true
+ end
+ end
+
+ describe '#[]' do
+ it 'behaves like a hash accessor' do
+ item = described_class.new(**variable)
+
+ expect(item[:key]).to eq 'VAR'
+ end
+ end
+
+ describe '#to_hash' do
+ it 'returns a hash representation of a collection item' do
+ expect(described_class.new(**variable).to_hash).to eq variable
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/variables/collection_spec.rb b/spec/lib/gitlab/ci/variables/collection_spec.rb
new file mode 100644
index 00000000000..90b6e178242
--- /dev/null
+++ b/spec/lib/gitlab/ci/variables/collection_spec.rb
@@ -0,0 +1,99 @@
+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 }
+
+ collection = described_class.new([variable])
+
+ expect(collection.first.to_hash).to eq variable
+ end
+
+ it 'can be initialized without an argument' do
+ expect(subject).to be_none
+ end
+ end
+
+ describe '#append' do
+ it 'appends a hash' do
+ subject.append(key: 'VARIABLE', value: 'something')
+
+ expect(subject).to be_one
+ end
+
+ it 'appends a Ci::Variable' do
+ subject.append(build(:ci_variable))
+
+ expect(subject).to be_one
+ end
+
+ it 'appends an internal resource' do
+ collection = described_class.new([{ key: 'TEST', value: 1 }])
+
+ subject.append(collection.first)
+
+ expect(subject).to be_one
+ end
+
+ it 'returns self' do
+ expect(subject.append(key: 'VAR', value: 'test'))
+ .to eq subject
+ end
+ end
+
+ describe '#concat' do
+ it 'appends all elements from an array' do
+ collection = described_class.new([{ key: 'VAR_1', value: '1' }])
+ variables = [{ key: 'VAR_2', value: '2' }, { key: 'VAR_3', value: '3' }]
+
+ collection.concat(variables)
+
+ expect(collection).to include(key: 'VAR_1', value: '1', public: true)
+ expect(collection).to include(key: 'VAR_2', value: '2', public: true)
+ expect(collection).to include(key: 'VAR_3', value: '3', public: true)
+ end
+
+ it 'appends all elements from other collection' do
+ collection = described_class.new([{ key: 'VAR_1', value: '1' }])
+ additional = described_class.new([{ key: 'VAR_2', value: '2' },
+ { key: 'VAR_3', value: '3' }])
+
+ collection.concat(additional)
+
+ expect(collection).to include(key: 'VAR_1', value: '1', public: true)
+ expect(collection).to include(key: 'VAR_2', value: '2', public: true)
+ expect(collection).to include(key: 'VAR_3', value: '3', public: true)
+ end
+
+ it 'returns self' do
+ expect(subject.concat([key: 'VAR', value: 'test']))
+ .to eq subject
+ end
+ end
+
+ describe '#+' do
+ it 'makes it possible to combine with an array' do
+ collection = described_class.new([{ key: 'TEST', value: 1 }])
+ variables = [{ key: 'TEST', value: 'something' }]
+
+ expect((collection + variables).count).to eq 2
+ end
+
+ it 'makes it possible to combine with another collection' do
+ collection = described_class.new([{ key: 'TEST', value: 1 }])
+ other = described_class.new([{ key: 'TEST', value: 2 }])
+
+ expect((collection + other).count).to eq 2
+ end
+ end
+
+ describe '#to_runner_variables' do
+ it 'creates an array of hashes in a runner-compatible format' do
+ collection = described_class.new([{ key: 'TEST', value: 1 }])
+
+ expect(collection.to_runner_variables)
+ .to eq [{ key: 'TEST', value: 1, public: true }]
+ end
+ end
+end
diff --git a/spec/lib/gitlab/conflict/file_collection_spec.rb b/spec/lib/gitlab/conflict/file_collection_spec.rb
index 5944ce8049a..c93912db411 100644
--- a/spec/lib/gitlab/conflict/file_collection_spec.rb
+++ b/spec/lib/gitlab/conflict/file_collection_spec.rb
@@ -10,6 +10,38 @@ describe Gitlab::Conflict::FileCollection do
end
end
+ describe '#cache' do
+ it 'specifies a custom namespace with the merge request commit ids' do
+ our_commit = merge_request.source_branch_head.raw
+ their_commit = merge_request.target_branch_head.raw
+ custom_namespace = "#{our_commit.id}:#{their_commit.id}"
+
+ expect(file_collection.send(:cache).namespace).to include(custom_namespace)
+ end
+ end
+
+ describe '#can_be_resolved_in_ui?' do
+ it 'returns true if conflicts for this collection can be resolved in the UI' do
+ expect(file_collection.can_be_resolved_in_ui?).to be true
+ end
+
+ it "returns false if conflicts for this collection can't be resolved in the UI" do
+ expect(file_collection).to receive(:files).and_raise(Gitlab::Git::Conflict::Resolver::ConflictSideMissing)
+
+ expect(file_collection.can_be_resolved_in_ui?).to be false
+ end
+
+ it 'caches the result' do
+ expect(file_collection).to receive(:files).twice.and_call_original
+
+ expect(file_collection.can_be_resolved_in_ui?).to be true
+
+ expect(file_collection).not_to receive(:files)
+
+ expect(file_collection.can_be_resolved_in_ui?).to be true
+ end
+ end
+
describe '#default_commit_message' do
it 'matches the format of the git CLI commit message' do
expect(file_collection.default_commit_message).to eq(<<EOM.chomp)
diff --git a/spec/lib/gitlab/data_builder/pipeline_spec.rb b/spec/lib/gitlab/data_builder/pipeline_spec.rb
index f13041e498c..9ca960502c8 100644
--- a/spec/lib/gitlab/data_builder/pipeline_spec.rb
+++ b/spec/lib/gitlab/data_builder/pipeline_spec.rb
@@ -26,6 +26,7 @@ describe Gitlab::DataBuilder::Pipeline do
it { expect(attributes[:tag]).to eq(pipeline.tag) }
it { expect(attributes[:id]).to eq(pipeline.id) }
it { expect(attributes[:status]).to eq(pipeline.status) }
+ it { expect(attributes[:detailed_status]).to eq('passed') }
it { expect(build_data).to be_a(Hash) }
it { expect(build_data[:id]).to eq(build.id) }
diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb
index b2f13fae73f..1fe1d3926ad 100644
--- a/spec/lib/gitlab/database_spec.rb
+++ b/spec/lib/gitlab/database_spec.rb
@@ -287,6 +287,29 @@ describe Gitlab::Database do
end
end
+ describe '.cached_column_exists?' do
+ it 'only retrieves data once' do
+ expect(ActiveRecord::Base.connection).to receive(:columns).once.and_call_original
+
+ 2.times do
+ expect(described_class.cached_column_exists?(:projects, :id)).to be_truthy
+ expect(described_class.cached_column_exists?(:projects, :bogus_column)).to be_falsey
+ end
+ end
+ end
+
+ describe '.cached_table_exists?' do
+ it 'only retrieves data once per table' do
+ expect(ActiveRecord::Base.connection).to receive(:table_exists?).with(:projects).once.and_call_original
+ expect(ActiveRecord::Base.connection).to receive(:table_exists?).with(:bogus_table_name).once.and_call_original
+
+ 2.times do
+ expect(described_class.cached_table_exists?(:projects)).to be_truthy
+ expect(described_class.cached_table_exists?(:bogus_table_name)).to be_falsey
+ end
+ end
+ end
+
describe '#true_value' do
it 'returns correct value for PostgreSQL' do
expect(described_class).to receive(:postgresql?).and_return(true)
diff --git a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
index a067c42b75b..f48ee8924e8 100644
--- a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
+++ b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
@@ -12,7 +12,7 @@ describe Gitlab::Diff::FileCollection::MergeRequestDiff do
diff_files
end
- it 'does not files marked as undiffable in .gitattributes' do
+ it 'does not highlight files marked as undiffable in .gitattributes' do
allow_any_instance_of(Gitlab::Diff::File).to receive(:diffable?).and_return(false)
expect_any_instance_of(Gitlab::Diff::File).not_to receive(:highlighted_diff_lines)
diff --git a/spec/lib/gitlab/exclusive_lease_spec.rb b/spec/lib/gitlab/exclusive_lease_spec.rb
index 6193e177668..aed7d8d81ce 100644
--- a/spec/lib/gitlab/exclusive_lease_spec.rb
+++ b/spec/lib/gitlab/exclusive_lease_spec.rb
@@ -88,4 +88,16 @@ describe Gitlab::ExclusiveLease, :clean_gitlab_redis_shared_state do
expect(lease.ttl).to be_nil
end
end
+
+ describe '.reset_all!' do
+ it 'removes all existing lease keys from redis' do
+ uuid = described_class.new(unique_key, timeout: 3600).try_obtain
+
+ expect(described_class.get_uuid(unique_key)).to eq(uuid)
+
+ described_class.reset_all!
+
+ expect(described_class.get_uuid(unique_key)).to be_falsey
+ end
+ end
end
diff --git a/spec/lib/gitlab/git/gitlab_projects_spec.rb b/spec/lib/gitlab/git/gitlab_projects_spec.rb
index 45bcd730332..dfccc15a4f3 100644
--- a/spec/lib/gitlab/git/gitlab_projects_spec.rb
+++ b/spec/lib/gitlab/git/gitlab_projects_spec.rb
@@ -28,7 +28,7 @@ describe Gitlab::Git::GitlabProjects do
describe '#push_branches' do
let(:remote_name) { 'remote-name' }
let(:branch_name) { 'master' }
- let(:cmd) { %W(git push -- #{remote_name} #{branch_name}) }
+ let(:cmd) { %W(#{Gitlab.config.git.bin_path} push -- #{remote_name} #{branch_name}) }
let(:force) { false }
subject { gl_projects.push_branches(remote_name, 600, force, [branch_name]) }
@@ -46,7 +46,7 @@ describe Gitlab::Git::GitlabProjects do
end
context 'with --force' do
- let(:cmd) { %W(git push --force -- #{remote_name} #{branch_name}) }
+ let(:cmd) { %W(#{Gitlab.config.git.bin_path} push --force -- #{remote_name} #{branch_name}) }
let(:force) { true }
it 'executes the command' do
@@ -65,7 +65,7 @@ describe Gitlab::Git::GitlabProjects do
let(:tags) { true }
let(:args) { { force: force, tags: tags, prune: prune }.merge(extra_args) }
let(:extra_args) { {} }
- let(:cmd) { %W(git fetch #{remote_name} --quiet --prune --tags) }
+ let(:cmd) { %W(#{Gitlab.config.git.bin_path} fetch #{remote_name} --quiet --prune --tags) }
subject { gl_projects.fetch_remote(remote_name, 600, args) }
@@ -98,7 +98,7 @@ describe Gitlab::Git::GitlabProjects do
context 'with --force' do
let(:force) { true }
- let(:cmd) { %W(git fetch #{remote_name} --quiet --prune --force --tags) }
+ let(:cmd) { %W(#{Gitlab.config.git.bin_path} fetch #{remote_name} --quiet --prune --force --tags) }
it 'executes the command with forced option' do
stub_spawn(cmd, 600, tmp_repo_path, {}, success: true)
@@ -109,7 +109,7 @@ describe Gitlab::Git::GitlabProjects do
context 'with --no-tags' do
let(:tags) { false }
- let(:cmd) { %W(git fetch #{remote_name} --quiet --prune --no-tags) }
+ let(:cmd) { %W(#{Gitlab.config.git.bin_path} fetch #{remote_name} --quiet --prune --no-tags) }
it 'executes the command' do
stub_spawn(cmd, 600, tmp_repo_path, {}, success: true)
@@ -120,7 +120,7 @@ describe Gitlab::Git::GitlabProjects do
context 'with no prune' do
let(:prune) { false }
- let(:cmd) { %W(git fetch #{remote_name} --quiet --tags) }
+ let(:cmd) { %W(#{Gitlab.config.git.bin_path} fetch #{remote_name} --quiet --tags) }
it 'executes the command' do
stub_spawn(cmd, 600, tmp_repo_path, {}, success: true)
@@ -165,7 +165,7 @@ describe Gitlab::Git::GitlabProjects do
describe '#import_project' do
let(:project) { create(:project) }
let(:import_url) { TestEnv.factory_repo_path_bare }
- let(:cmd) { %W(git clone --bare -- #{import_url} #{tmp_repo_path}) }
+ let(:cmd) { %W(#{Gitlab.config.git.bin_path} clone --bare -- #{import_url} #{tmp_repo_path}) }
let(:timeout) { 600 }
subject { gl_projects.import_project(import_url, timeout) }
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 52c9876cbb6..54ada3e423f 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -681,7 +681,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
subject { new_repository.fetch_repository_as_mirror(repository) }
before do
- Gitlab::Shell.new.add_repository('default', 'my_project')
+ Gitlab::Shell.new.create_repository('default', 'my_project')
end
after do
diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json
index 62ef93f847a..4a51777ba9b 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -43,7 +43,6 @@
{
"id": 40,
"title": "Voluptatem",
- "assignee_id": 1,
"author_id": 22,
"project_id": 5,
"created_at": "2016-06-14T15:02:08.340Z",
@@ -61,7 +60,23 @@
"issue_assignees": [
{
"user_id": 1,
- "issue_id": 1
+ "issue_id": 40
+ },
+ {
+ "user_id": 15,
+ "issue_id": 40
+ },
+ {
+ "user_id": 16,
+ "issue_id": 40
+ },
+ {
+ "user_id": 16,
+ "issue_id": 40
+ },
+ {
+ "user_id": 6,
+ "issue_id": 40
}
],
"milestone": {
@@ -319,8 +334,7 @@
},
{
"id": 39,
- "title": "Delectus veniam ratione in eos culpa et natus molestiae earum aut.",
- "assignee_id": 20,
+ "title": "Issue without assignees",
"author_id": 22,
"project_id": 5,
"created_at": "2016-06-14T15:02:08.233Z",
@@ -334,6 +348,7 @@
"confidential": false,
"due_date": null,
"moved_to_id": null,
+ "issue_assignees": [],
"milestone": {
"id": 1,
"title": "test milestone",
@@ -539,7 +554,6 @@
{
"id": 38,
"title": "Quasi adipisci non cupiditate dolorem quo qui earum sed.",
- "assignee_id": 1,
"author_id": 6,
"project_id": 5,
"created_at": "2016-06-14T15:02:08.154Z",
@@ -756,7 +770,6 @@
{
"id": 37,
"title": "Cupiditate quo aut ducimus minima molestiae vero numquam possimus.",
- "assignee_id": 15,
"author_id": 20,
"project_id": 5,
"created_at": "2016-06-14T15:02:08.051Z",
@@ -952,7 +965,6 @@
{
"id": 36,
"title": "Necessitatibus dolor est enim quia rem suscipit quidem voluptas ullam.",
- "assignee_id": 20,
"author_id": 16,
"project_id": 5,
"created_at": "2016-06-14T15:02:07.958Z",
@@ -1148,7 +1160,6 @@
{
"id": 35,
"title": "Repellat praesentium deserunt maxime incidunt harum porro qui.",
- "assignee_id": 6,
"author_id": 20,
"project_id": 5,
"created_at": "2016-06-14T15:02:07.832Z",
@@ -1344,7 +1355,6 @@
{
"id": 34,
"title": "Ullam expedita deserunt libero consequatur quia dolor harum perferendis facere quidem.",
- "assignee_id": 20,
"author_id": 1,
"project_id": 5,
"created_at": "2016-06-14T15:02:07.717Z",
@@ -1540,7 +1550,6 @@
{
"id": 33,
"title": "Numquam accusamus eos iste exercitationem magni non inventore.",
- "assignee_id": 15,
"author_id": 26,
"project_id": 5,
"created_at": "2016-06-14T15:02:07.611Z",
@@ -1736,7 +1745,6 @@
{
"id": 32,
"title": "Necessitatibus magnam qui at velit consequatur perspiciatis.",
- "assignee_id": 22,
"author_id": 15,
"project_id": 5,
"created_at": "2016-06-14T15:02:07.431Z",
@@ -1932,7 +1940,6 @@
{
"id": 31,
"title": "Libero nam magnam incidunt eaque placeat error et.",
- "assignee_id": 1,
"author_id": 16,
"project_id": 5,
"created_at": "2016-06-14T15:02:07.280Z",
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index f4e466d1296..8e25cd26c2f 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -4,7 +4,12 @@ include ImportExport::CommonUtil
describe Gitlab::ImportExport::ProjectTreeRestorer do
describe 'restore project tree' do
before(:context) do
- @user = create(:user)
+ # Using an admin for import, so we can check assignment of existing members
+ @user = create(:admin)
+ @existing_members = [
+ create(:user, username: 'bernard_willms'),
+ create(:user, username: 'saul_will')
+ ]
RSpec::Mocks.with_temporary_scope do
@project = create(:project, :builds_disabled, :issues_disabled, name: 'project', path: 'project')
@@ -37,6 +42,10 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
expect(project.project_feature.merge_requests_access_level).to eq(ProjectFeature::ENABLED)
end
+ it 'has the project description' do
+ expect(Project.find_by_path('project').description).to eq('Nisi et repellendus ut enim quo accusamus vel magnam.')
+ end
+
it 'has the project html description' do
expect(Project.find_by_path('project').description_html).to eq('description')
end
@@ -63,8 +72,9 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
expect(issue.reload.updated_at.to_s).to eq('2016-06-14 15:02:47 UTC')
end
- it 'has issue assignees' do
- expect(Issue.where(title: 'Voluptatem').first.issue_assignees).not_to be_empty
+ it 'has multiple issue assignees' do
+ expect(Issue.find_by(title: 'Voluptatem').assignees).to contain_exactly(@user, *@existing_members)
+ expect(Issue.find_by(title: 'Issue without assignees').assignees).to be_empty
end
it 'contains the merge access levels on a protected branch' do
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 3049491f0ae..0d20a551e2a 100644
--- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
@@ -29,8 +29,17 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
project_json(project_tree_saver.full_path)
end
+ context 'with description override' do
+ let(:params) { { description: 'Foo Bar' } }
+ let(:project_tree_saver) { described_class.new(project: project, current_user: user, shared: shared, params: params) }
+
+ it 'overrides the project description' do
+ expect(saved_project_json).to include({ 'description' => params[:description] })
+ end
+ end
+
it 'saves the correct json' do
- expect(saved_project_json).to include({ "visibility_level" => 20 })
+ expect(saved_project_json).to include({ 'description' => 'description', 'visibility_level' => 20 })
end
it 'has milestones' do
@@ -259,6 +268,7 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
:issues_disabled,
:wiki_enabled,
:builds_private,
+ description: 'description',
issues: [issue],
snippets: [snippet],
releases: [release],
diff --git a/spec/lib/gitlab/kubernetes/namespace_spec.rb b/spec/lib/gitlab/kubernetes/namespace_spec.rb
index b3c987f9344..e098612f6fb 100644
--- a/spec/lib/gitlab/kubernetes/namespace_spec.rb
+++ b/spec/lib/gitlab/kubernetes/namespace_spec.rb
@@ -9,7 +9,7 @@ describe Gitlab::Kubernetes::Namespace do
describe '#exists?' do
context 'when namespace do not exits' do
- let(:exception) { ::KubeException.new(404, "namespace #{name} not found", nil) }
+ let(:exception) { ::Kubeclient::HttpError.new(404, "namespace #{name} not found", nil) }
it 'returns false' do
expect(client).to receive(:get_namespace).with(name).once.and_raise(exception)
diff --git a/spec/lib/gitlab/profiler_spec.rb b/spec/lib/gitlab/profiler_spec.rb
index f02b1cf55fb..3d5b56cd5b8 100644
--- a/spec/lib/gitlab/profiler_spec.rb
+++ b/spec/lib/gitlab/profiler_spec.rb
@@ -94,10 +94,12 @@ describe Gitlab::Profiler do
it 'strips out the private token' do
expect(custom_logger).to receive(:add) do |severity, _progname, message|
+ next if message.include?('spec/')
+
expect(severity).to eq(Logger::DEBUG)
expect(message).to include('public').and include(described_class::FILTERED_STRING)
expect(message).not_to include(private_token)
- end
+ end.twice
custom_logger.debug("public #{private_token}")
end
diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb
index c46bb8edebf..57905a74e92 100644
--- a/spec/lib/gitlab/project_search_results_spec.rb
+++ b/spec/lib/gitlab/project_search_results_spec.rb
@@ -108,14 +108,26 @@ describe Gitlab::ProjectSearchResults do
context 'when the search returns non-ASCII data' do
context 'with UTF-8' do
- let(:results) { project.repository.search_files_by_content("файл", 'master') }
+ let(:results) { project.repository.search_files_by_content('файл', 'master') }
it 'returns results as UTF-8' do
expect(subject.filename).to eq('encoding/russian.rb')
expect(subject.basename).to eq('encoding/russian')
expect(subject.ref).to eq('master')
expect(subject.startline).to eq(1)
- expect(subject.data).to eq("Хороший файл")
+ expect(subject.data).to eq('Хороший файл')
+ end
+ end
+
+ context 'with UTF-8 in the filename' do
+ let(:results) { project.repository.search_files_by_content('webhook', 'master') }
+
+ it 'returns results as UTF-8' do
+ expect(subject.filename).to eq('encoding/テスト.txt')
+ expect(subject.basename).to eq('encoding/テスト')
+ expect(subject.ref).to eq('master')
+ expect(subject.startline).to eq(3)
+ expect(subject.data).to include('WebHookの確認')
end
end
diff --git a/spec/lib/gitlab/project_transfer_spec.rb b/spec/lib/gitlab/project_transfer_spec.rb
index 10c5fb148cd..0b9b1f537b5 100644
--- a/spec/lib/gitlab/project_transfer_spec.rb
+++ b/spec/lib/gitlab/project_transfer_spec.rb
@@ -21,30 +21,77 @@ describe Gitlab::ProjectTransfer do
describe '#move_project' do
it "moves project upload to another namespace" do
- FileUtils.mkdir_p(File.join(@root_dir, @namespace_path_was, @project_path))
+ path_to_be_moved = File.join(@root_dir, @namespace_path_was, @project_path)
+ expected_path = File.join(@root_dir, @namespace_path, @project_path)
+ FileUtils.mkdir_p(path_to_be_moved)
+
@project_transfer.move_project(@project_path, @namespace_path_was, @namespace_path)
- expected_path = File.join(@root_dir, @namespace_path, @project_path)
expect(Dir.exist?(expected_path)).to be_truthy
end
end
+ describe '#move_namespace' do
+ context 'when moving namespace from root into another namespace' do
+ it "moves namespace projects' upload" do
+ child_namespace = 'test_child_namespace'
+ path_to_be_moved = File.join(@root_dir, child_namespace, @project_path)
+ expected_path = File.join(@root_dir, @namespace_path, child_namespace, @project_path)
+ FileUtils.mkdir_p(path_to_be_moved)
+
+ @project_transfer.move_namespace(child_namespace, nil, @namespace_path)
+
+ expect(Dir.exist?(expected_path)).to be_truthy
+ end
+ end
+
+ context 'when moving namespace from one parent to another' do
+ it "moves namespace projects' upload" do
+ child_namespace = 'test_child_namespace'
+ path_to_be_moved = File.join(@root_dir, @namespace_path_was, child_namespace, @project_path)
+ expected_path = File.join(@root_dir, @namespace_path, child_namespace, @project_path)
+ FileUtils.mkdir_p(path_to_be_moved)
+
+ @project_transfer.move_namespace(child_namespace, @namespace_path_was, @namespace_path)
+
+ expect(Dir.exist?(expected_path)).to be_truthy
+ end
+ end
+
+ context 'when moving namespace from having a parent to root' do
+ it "moves namespace projects' upload" do
+ child_namespace = 'test_child_namespace'
+ path_to_be_moved = File.join(@root_dir, @namespace_path_was, child_namespace, @project_path)
+ expected_path = File.join(@root_dir, child_namespace, @project_path)
+ FileUtils.mkdir_p(path_to_be_moved)
+
+ @project_transfer.move_namespace(child_namespace, @namespace_path_was, nil)
+
+ expect(Dir.exist?(expected_path)).to be_truthy
+ end
+ end
+ end
+
describe '#rename_project' do
it "renames project" do
- FileUtils.mkdir_p(File.join(@root_dir, @namespace_path, @project_path_was))
+ path_to_be_moved = File.join(@root_dir, @namespace_path, @project_path_was)
+ expected_path = File.join(@root_dir, @namespace_path, @project_path)
+ FileUtils.mkdir_p(path_to_be_moved)
+
@project_transfer.rename_project(@project_path_was, @project_path, @namespace_path)
- expected_path = File.join(@root_dir, @namespace_path, @project_path)
expect(Dir.exist?(expected_path)).to be_truthy
end
end
describe '#rename_namespace' do
it "renames namespace" do
- FileUtils.mkdir_p(File.join(@root_dir, @namespace_path_was, @project_path))
+ path_to_be_moved = File.join(@root_dir, @namespace_path_was, @project_path)
+ expected_path = File.join(@root_dir, @namespace_path, @project_path)
+ FileUtils.mkdir_p(path_to_be_moved)
+
@project_transfer.rename_namespace(@namespace_path_was, @namespace_path)
- expected_path = File.join(@root_dir, @namespace_path, @project_path)
expect(Dir.exist?(expected_path)).to be_truthy
end
end
diff --git a/spec/lib/gitlab/repository_cache_adapter_spec.rb b/spec/lib/gitlab/repository_cache_adapter_spec.rb
new file mode 100644
index 00000000000..85971f2a7ef
--- /dev/null
+++ b/spec/lib/gitlab/repository_cache_adapter_spec.rb
@@ -0,0 +1,76 @@
+require 'spec_helper'
+
+describe Gitlab::RepositoryCacheAdapter do
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
+ let(:cache) { repository.send(:cache) }
+
+ describe '#cache_method_output', :use_clean_rails_memory_store_caching do
+ let(:fallback) { 10 }
+
+ context 'with a non-existing repository' do
+ let(:project) { create(:project) } # No repository
+
+ subject do
+ repository.cache_method_output(:cats, fallback: fallback) do
+ repository.cats_call_stub
+ end
+ end
+
+ it 'returns the fallback value' do
+ expect(subject).to eq(fallback)
+ end
+
+ it 'avoids calling the original method' do
+ expect(repository).not_to receive(:cats_call_stub)
+
+ subject
+ end
+ end
+
+ context 'with a method throwing a non-existing-repository error' do
+ subject do
+ repository.cache_method_output(:cats, fallback: fallback) do
+ raise Gitlab::Git::Repository::NoRepository
+ end
+ end
+
+ it 'returns the fallback value' do
+ expect(subject).to eq(fallback)
+ end
+
+ it 'does not cache the data' do
+ subject
+
+ expect(repository.instance_variable_defined?(:@cats)).to eq(false)
+ expect(cache.exist?(:cats)).to eq(false)
+ end
+ end
+
+ context 'with an existing repository' do
+ it 'caches the output' do
+ object = double
+
+ expect(object).to receive(:number).once.and_return(10)
+
+ 2.times do
+ val = repository.cache_method_output(:cats) { object.number }
+
+ expect(val).to eq(10)
+ end
+
+ expect(repository.send(:cache).exist?(:cats)).to eq(true)
+ expect(repository.instance_variable_get(:@cats)).to eq(10)
+ end
+ end
+ end
+
+ describe '#expire_method_caches' do
+ it 'expires the caches of the given methods' do
+ expect(cache).to receive(:expire).with(:readme)
+ expect(cache).to receive(:expire).with(:gitignore)
+
+ repository.expire_method_caches(%i(readme gitignore))
+ end
+ end
+end
diff --git a/spec/lib/gitlab/repository_cache_spec.rb b/spec/lib/gitlab/repository_cache_spec.rb
new file mode 100644
index 00000000000..fc259cf1208
--- /dev/null
+++ b/spec/lib/gitlab/repository_cache_spec.rb
@@ -0,0 +1,50 @@
+require 'spec_helper'
+
+describe Gitlab::RepositoryCache do
+ let(:backend) { double('backend').as_null_object }
+ let(:project) { create(:project) }
+ let(:repository) { project.repository }
+ let(:namespace) { "#{repository.full_path}:#{project.id}" }
+ let(:cache) { described_class.new(repository, backend: backend) }
+
+ describe '#cache_key' do
+ subject { cache.cache_key(:foo) }
+
+ it 'includes the namespace' do
+ expect(subject).to eq "foo:#{namespace}"
+ end
+
+ context 'with a given namespace' do
+ let(:extra_namespace) { 'my:data' }
+ let(:cache) do
+ described_class.new(repository, extra_namespace: extra_namespace,
+ backend: backend)
+ end
+
+ it 'includes the full namespace' do
+ expect(subject).to eq "foo:#{namespace}:#{extra_namespace}"
+ end
+ end
+ end
+
+ describe '#expire' do
+ it 'expires the given key from the cache' do
+ cache.expire(:foo)
+ expect(backend).to have_received(:delete).with("foo:#{namespace}")
+ end
+ end
+
+ describe '#fetch' do
+ it 'fetches the given key from the cache' do
+ cache.fetch(:bar)
+ expect(backend).to have_received(:fetch).with("bar:#{namespace}")
+ end
+
+ it 'accepts a block' do
+ p = -> {}
+
+ cache.fetch(:baz, &p)
+ expect(backend).to have_received(:fetch).with("baz:#{namespace}", &p)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb
index 56b45d8da3c..14b59c5e945 100644
--- a/spec/lib/gitlab/shell_spec.rb
+++ b/spec/lib/gitlab/shell_spec.rb
@@ -20,7 +20,7 @@ describe Gitlab::Shell do
it { is_expected.to respond_to :add_key }
it { is_expected.to respond_to :remove_key }
- it { is_expected.to respond_to :add_repository }
+ it { is_expected.to respond_to :create_repository }
it { is_expected.to respond_to :remove_repository }
it { is_expected.to respond_to :fork_repository }
@@ -402,8 +402,8 @@ describe Gitlab::Shell do
allow(Gitlab.config.gitlab_shell).to receive(:git_timeout).and_return(800)
end
- describe '#add_repository' do
- shared_examples '#add_repository' do
+ describe '#create_repository' do
+ shared_examples '#create_repository' do
let(:repository_storage) { 'default' }
let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage]['path'] }
let(:repo_name) { 'project/path' }
@@ -414,7 +414,7 @@ describe Gitlab::Shell do
end
it 'creates a repository' do
- expect(gitlab_shell.add_repository(repository_storage, repo_name)).to be_truthy
+ expect(gitlab_shell.create_repository(repository_storage, repo_name)).to be_truthy
expect(File.stat(created_path).mode & 0o777).to eq(0o770)
@@ -426,19 +426,19 @@ describe Gitlab::Shell do
it 'returns false when the command fails' do
FileUtils.mkdir_p(File.dirname(created_path))
# This file will block the creation of the repo's .git directory. That
- # should cause #add_repository to fail.
+ # should cause #create_repository to fail.
FileUtils.touch(created_path)
- expect(gitlab_shell.add_repository(repository_storage, repo_name)).to be_falsy
+ expect(gitlab_shell.create_repository(repository_storage, repo_name)).to be_falsy
end
end
context 'with gitaly' do
- it_behaves_like '#add_repository'
+ it_behaves_like '#create_repository'
end
context 'without gitaly', :skip_gitaly_mock do
- it_behaves_like '#add_repository'
+ it_behaves_like '#create_repository'
end
end
diff --git a/spec/lib/gitlab/slash_commands/command_spec.rb b/spec/lib/gitlab/slash_commands/command_spec.rb
index e3447d974aa..194cae8c645 100644
--- a/spec/lib/gitlab/slash_commands/command_spec.rb
+++ b/spec/lib/gitlab/slash_commands/command_spec.rb
@@ -108,5 +108,10 @@ describe Gitlab::SlashCommands::Command do
it { is_expected.to eq(Gitlab::SlashCommands::IssueSearch) }
end
+
+ context 'IssueMove is triggered' do
+ let(:params) { { text: 'issue move #78291 to gitlab/gitlab-ci' } }
+ it { is_expected.to eq(Gitlab::SlashCommands::IssueMove) }
+ end
end
end
diff --git a/spec/lib/gitlab/slash_commands/issue_move_spec.rb b/spec/lib/gitlab/slash_commands/issue_move_spec.rb
new file mode 100644
index 00000000000..d41441c9472
--- /dev/null
+++ b/spec/lib/gitlab/slash_commands/issue_move_spec.rb
@@ -0,0 +1,117 @@
+require 'spec_helper'
+
+describe Gitlab::SlashCommands::IssueMove, service: true do
+ describe '#match' do
+ shared_examples_for 'move command' do |text_command|
+ it 'can be parsed to extract the needed fields' do
+ match_data = described_class.match(text_command)
+
+ expect(match_data['iid']).to eq('123456')
+ expect(match_data['project_path']).to eq('gitlab/gitlab-ci')
+ end
+ end
+
+ it_behaves_like 'move command', 'issue move #123456 to gitlab/gitlab-ci'
+ it_behaves_like 'move command', 'issue move #123456 gitlab/gitlab-ci'
+ it_behaves_like 'move command', 'issue move #123456 gitlab/gitlab-ci '
+ it_behaves_like 'move command', 'issue move 123456 to gitlab/gitlab-ci'
+ it_behaves_like 'move command', 'issue move 123456 gitlab/gitlab-ci'
+ it_behaves_like 'move command', 'issue move 123456 gitlab/gitlab-ci '
+ end
+
+ describe '#execute' do
+ set(:user) { create(:user) }
+ set(:issue) { create(:issue) }
+ set(:chat_name) { create(:chat_name, user: user) }
+ set(:project) { issue.project }
+ set(:other_project) { create(:project, namespace: project.namespace) }
+
+ before do
+ [project, other_project].each { |prj| prj.add_master(user) }
+ end
+
+ subject { described_class.new(project, chat_name) }
+
+ def process_message(message)
+ subject.execute(described_class.match(message))
+ end
+
+ context 'when the user can move the issue' do
+ context 'when the move fails' do
+ it 'returns the error message' do
+ message = "issue move #{issue.iid} #{project.full_path}"
+
+ expect(process_message(message)).to include(response_type: :ephemeral,
+ text: a_string_matching('Cannot move issue'))
+ end
+ end
+
+ context 'when the move succeeds' do
+ let(:message) { "issue move #{issue.iid} #{other_project.full_path}" }
+
+ it 'moves the issue to the new destination' do
+ expect { process_message(message) }.to change { Issue.count }.by(1)
+
+ new_issue = issue.reload.moved_to
+
+ expect(new_issue.state).to eq('opened')
+ expect(new_issue.project_id).to eq(other_project.id)
+ expect(new_issue.author_id).to eq(issue.author_id)
+
+ expect(issue.state).to eq('closed')
+ expect(issue.project_id).to eq(project.id)
+ end
+
+ it 'returns the new issue' do
+ expect(process_message(message))
+ .to include(response_type: :in_channel,
+ attachments: [a_hash_including(title_link: a_string_including(other_project.full_path))])
+ end
+
+ it 'mentions the old issue' do
+ expect(process_message(message))
+ .to include(attachments: [a_hash_including(pretext: a_string_including(project.full_path))])
+ end
+ end
+ end
+
+ context 'when the issue does not exist' do
+ it 'returns not found' do
+ message = "issue move #{issue.iid.succ} #{other_project.full_path}"
+
+ expect(process_message(message)).to include(response_type: :ephemeral,
+ text: a_string_matching('not found'))
+ end
+ end
+
+ context 'when the target project does not exist' do
+ it 'returns not found' do
+ message = "issue move #{issue.iid} #{other_project.full_path}/foo"
+
+ expect(process_message(message)).to include(response_type: :ephemeral,
+ text: a_string_matching('not found'))
+ end
+ end
+
+ context 'when the user cannot see the target project' do
+ it 'returns not found' do
+ message = "issue move #{issue.iid} #{other_project.full_path}"
+ other_project.team.truncate
+
+ expect(process_message(message)).to include(response_type: :ephemeral,
+ text: a_string_matching('not found'))
+ end
+ end
+
+ context 'when the user does not have the required permissions on the target project' do
+ it 'returns the error message' do
+ message = "issue move #{issue.iid} #{other_project.full_path}"
+ other_project.team.truncate
+ other_project.team.add_guest(user)
+
+ expect(process_message(message)).to include(response_type: :ephemeral,
+ text: a_string_matching('Cannot move issue'))
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb
new file mode 100644
index 00000000000..58c341a284e
--- /dev/null
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe Gitlab::SlashCommands::Presenters::IssueMove do
+ set(:admin) { create(:admin) }
+ set(:project) { create(:project) }
+ set(:other_project) { create(:project) }
+ set(:old_issue) { create(:issue, project: project) }
+ set(:new_issue) { Issues::MoveService.new(project, admin).execute(old_issue, other_project) }
+ let(:attachment) { subject[:attachments].first }
+
+ subject { described_class.new(new_issue).present(old_issue) }
+
+ it { is_expected.to be_a(Hash) }
+
+ it 'shows the new issue' do
+ expect(subject[:response_type]).to be(:in_channel)
+ expect(subject).to have_key(:attachments)
+ expect(attachment[:title]).to start_with(new_issue.title)
+ expect(attachment[:title_link]).to include(other_project.full_path)
+ end
+
+ it 'mentions the old issue and the new issue in the pretext' do
+ expect(attachment[:pretext]).to include(project.full_path)
+ expect(attachment[:pretext]).to include(other_project.full_path)
+ end
+end
diff --git a/spec/lib/gitlab/verify/job_artifacts_spec.rb b/spec/lib/gitlab/verify/job_artifacts_spec.rb
new file mode 100644
index 00000000000..ec490bdfde2
--- /dev/null
+++ b/spec/lib/gitlab/verify/job_artifacts_spec.rb
@@ -0,0 +1,35 @@
+require 'spec_helper'
+
+describe Gitlab::Verify::JobArtifacts do
+ include GitlabVerifyHelpers
+
+ it_behaves_like 'Gitlab::Verify::BatchVerifier subclass' do
+ let!(:objects) { create_list(:ci_job_artifact, 3, :archive) }
+ end
+
+ describe '#run_batches' do
+ let(:failures) { collect_failures }
+ let(:failure) { failures[artifact] }
+
+ let!(:artifact) { create(:ci_job_artifact, :archive, :correct_checksum) }
+
+ it 'passes artifacts with the correct file' do
+ expect(failures).to eq({})
+ end
+
+ it 'fails artifacts with a missing file' do
+ FileUtils.rm_f(artifact.file.path)
+
+ expect(failures.keys).to contain_exactly(artifact)
+ expect(failure).to be_a(Errno::ENOENT)
+ expect(failure.to_s).to include(artifact.file.path)
+ end
+
+ it 'fails artifacts with a mismatched checksum' do
+ File.truncate(artifact.file.path, 0)
+
+ expect(failures.keys).to contain_exactly(artifact)
+ expect(failure.to_s).to include('Checksum mismatch')
+ end
+ end
+end
diff --git a/spec/lib/mattermost/team_spec.rb b/spec/lib/mattermost/team_spec.rb
index e638ad7a2c9..3c8206031cf 100644
--- a/spec/lib/mattermost/team_spec.rb
+++ b/spec/lib/mattermost/team_spec.rb
@@ -64,4 +64,108 @@ describe Mattermost::Team do
end
end
end
+
+ describe '#create' do
+ subject { described_class.new(nil).create(name: "devteam", display_name: "Dev Team", type: "O") }
+
+ context 'for a new team' do
+ let(:response) do
+ {
+ "id" => "cuojfcetjty7tb4pxe47pwpndo",
+ "create_at" => 1517688728701,
+ "update_at" => 1517688728701,
+ "delete_at" => 0,
+ "display_name" => "Dev Team",
+ "name" => "devteam",
+ "description" => "",
+ "email" => "admin@example.com",
+ "type" => "O",
+ "company_name" => "",
+ "allowed_domains" => "",
+ "invite_id" => "7mp9d3ayaj833ymmkfnid8js6w",
+ "allow_open_invite" => false
+ }
+ end
+
+ before do
+ stub_request(:post, "http://mattermost.example.com/api/v3/teams/create")
+ .to_return(
+ status: 200,
+ body: response.to_json,
+ headers: { 'Content-Type' => 'application/json' }
+ )
+ end
+
+ it 'returns the new team' do
+ is_expected.to eq(response)
+ end
+ end
+
+ context 'for existing team' do
+ before do
+ stub_request(:post, 'http://mattermost.example.com/api/v3/teams/create')
+ .to_return(
+ status: 400,
+ headers: { 'Content-Type' => 'application/json' },
+ body: {
+ id: "store.sql_team.save.domain_exists.app_error",
+ message: "A team with that name already exists",
+ detailed_error: "",
+ request_id: "1hsb5bxs97r8bdggayy7n9gxaw",
+ status_code: 400
+ }.to_json
+ )
+ end
+
+ it 'raises an error with message' do
+ expect { subject }.to raise_error(Mattermost::Error, 'A team with that name already exists')
+ end
+ end
+ end
+
+ describe '#delete' do
+ subject { described_class.new(nil).destroy(team_id: "cuojfcetjty7tb4pxe47pwpndo") }
+
+ context 'for an existing team' do
+ let(:response) do
+ {
+ "status" => "OK"
+ }
+ end
+
+ before do
+ stub_request(:delete, "http://mattermost.example.com/api/v4/teams/cuojfcetjty7tb4pxe47pwpndo")
+ .to_return(
+ status: 200,
+ body: response.to_json,
+ headers: { 'Content-Type' => 'application/json' }
+ )
+ end
+
+ it 'returns team status' do
+ is_expected.to eq(response)
+ end
+ end
+
+ context 'for an unknown team' do
+ before do
+ stub_request(:delete, "http://mattermost.example.com/api/v4/teams/cuojfcetjty7tb4pxe47pwpndo")
+ .to_return(
+ status: 404,
+ body: {
+ id: "store.sql_team.get.find.app_error",
+ message: "We couldn't find the existing team",
+ detailed_error: "",
+ request_id: "my114ab5nbnui8c9pes4kz8mza",
+ status_code: 404
+ }.to_json,
+ headers: { 'Content-Type' => 'application/json' }
+ )
+ end
+
+ it 'raises an error with message' do
+ expect { subject }.to raise_error(Mattermost::Error, "We couldn't find the existing team")
+ end
+ end
+ end
end
diff --git a/spec/lib/repository_cache_spec.rb b/spec/lib/repository_cache_spec.rb
deleted file mode 100644
index 8b0c7254b5e..00000000000
--- a/spec/lib/repository_cache_spec.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-require 'spec_helper'
-
-describe RepositoryCache do
- let(:project) { create(:project) }
- let(:backend) { double('backend').as_null_object }
- let(:cache) { described_class.new('example', project.id, backend) }
-
- describe '#cache_key' do
- it 'includes the namespace' do
- expect(cache.cache_key(:foo)).to eq "foo:example:#{project.id}"
- end
- end
-
- describe '#expire' do
- it 'expires the given key from the cache' do
- cache.expire(:foo)
- expect(backend).to have_received(:delete).with("foo:example:#{project.id}")
- end
- end
-
- describe '#fetch' do
- it 'fetches the given key from the cache' do
- cache.fetch(:bar)
- expect(backend).to have_received(:fetch).with("bar:example:#{project.id}")
- end
-
- it 'accepts a block' do
- p = -> {}
-
- cache.fetch(:baz, &p)
- expect(backend).to have_received(:fetch).with("baz:example:#{project.id}", &p)
- end
- end
-end
diff --git a/spec/migrations/migrate_old_artifacts_spec.rb b/spec/migrations/migrate_old_artifacts_spec.rb
index 92eb1d9ce86..638b2853374 100644
--- a/spec/migrations/migrate_old_artifacts_spec.rb
+++ b/spec/migrations/migrate_old_artifacts_spec.rb
@@ -66,7 +66,7 @@ describe MigrateOldArtifacts do
end
it 'all files do have artifacts' do
- Ci::Build.with_artifacts do |build|
+ Ci::Build.with_artifacts_archive do |build|
expect(build).to have_artifacts
end
end
diff --git a/spec/migrations/reschedule_commits_count_for_merge_request_diff_spec.rb b/spec/migrations/reschedule_commits_count_for_merge_request_diff_spec.rb
new file mode 100644
index 00000000000..26489ef58bd
--- /dev/null
+++ b/spec/migrations/reschedule_commits_count_for_merge_request_diff_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20180309121820_reschedule_commits_count_for_merge_request_diff')
+
+describe RescheduleCommitsCountForMergeRequestDiff, :migration, :sidekiq do
+ let(:merge_request_diffs) { table(:merge_request_diffs) }
+ let(:merge_requests) { table(:merge_requests) }
+ let(:projects) { table(:projects) }
+ let(:namespaces) { table(:namespaces) }
+
+ before do
+ stub_const("#{described_class.name}::BATCH_SIZE", 1)
+
+ namespaces.create!(id: 1, name: 'gitlab', path: 'gitlab')
+
+ projects.create!(id: 1, namespace_id: 1)
+
+ merge_requests.create!(id: 1, target_project_id: 1, source_project_id: 1, target_branch: 'feature', source_branch: 'master')
+
+ merge_request_diffs.create!(id: 1, merge_request_id: 1)
+ merge_request_diffs.create!(id: 2, merge_request_id: 1)
+ merge_request_diffs.create!(id: 3, merge_request_id: 1, commits_count: 0)
+ merge_request_diffs.create!(id: 4, merge_request_id: 1)
+ end
+
+ it 'correctly schedules background migrations' do
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ expect(described_class::MIGRATION).to be_scheduled_delayed_migration(5.minutes, 1, 1)
+ expect(described_class::MIGRATION).to be_scheduled_delayed_migration(10.minutes, 2, 2)
+ expect(described_class::MIGRATION).to be_scheduled_delayed_migration(15.minutes, 4, 4)
+ expect(BackgroundMigrationWorker.jobs.size).to eq 3
+ end
+ end
+ end
+end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index c27313ed88b..9da3de7a828 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -80,6 +80,42 @@ describe Ci::Build do
end
end
+ describe '.with_artifacts_archive' do
+ subject { described_class.with_artifacts_archive }
+
+ context 'when job does not have an archive' do
+ let!(:job) { create(:ci_build) }
+
+ it 'does not return the job' do
+ is_expected.not_to include(job)
+ end
+ end
+
+ context 'when job has a legacy archive' do
+ let!(:job) { create(:ci_build, :legacy_artifacts) }
+
+ it 'returns the job' do
+ is_expected.to include(job)
+ end
+ end
+
+ context 'when job has a job artifact archive' do
+ let!(:job) { create(:ci_build, :artifacts) }
+
+ it 'returns the job' do
+ is_expected.to include(job)
+ end
+ end
+
+ context 'when job has a job artifact trace' do
+ let!(:job) { create(:ci_build, :trace_artifact) }
+
+ it 'does not return the job' do
+ is_expected.not_to include(job)
+ end
+ end
+ end
+
describe '#actionize' do
context 'when build is a created' do
before do
@@ -679,21 +715,21 @@ describe Ci::Build do
describe '#erase' do
before do
- build.erase(erased_by: user)
+ build.erase(erased_by: erased_by)
end
context 'erased by user' do
- let!(:user) { create(:user, username: 'eraser') }
+ let!(:erased_by) { create(:user, username: 'eraser') }
include_examples 'erasable'
it 'records user who erased a build' do
- expect(build.erased_by).to eq user
+ expect(build.erased_by).to eq erased_by
end
end
context 'erased by system' do
- let(:user) { nil }
+ let(:erased_by) { nil }
include_examples 'erasable'
@@ -748,21 +784,21 @@ describe Ci::Build do
describe '#erase' do
before do
- build.erase(erased_by: user)
+ build.erase(erased_by: erased_by)
end
context 'erased by user' do
- let!(:user) { create(:user, username: 'eraser') }
+ let!(:erased_by) { create(:user, username: 'eraser') }
include_examples 'erasable'
it 'records user who erased a build' do
- expect(build.erased_by).to eq user
+ expect(build.erased_by).to eq erased_by
end
end
context 'erased by system' do
- let(:user) { nil }
+ let(:erased_by) { nil }
include_examples 'erasable'
@@ -1424,6 +1460,17 @@ describe Ci::Build do
{ key: 'CI_COMMIT_SHA', value: build.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_REGISTRY_USER', value: 'gitlab-ci-token', public: true },
+ { key: 'CI_REGISTRY_PASSWORD', value: build.token, public: false },
+ { key: 'CI_REPOSITORY_URL', value: build.repo_url, public: false },
+ { key: 'CI_BUILD_ID', value: build.id.to_s, public: true },
+ { key: 'CI_BUILD_TOKEN', value: build.token, public: false },
+ { 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 },
@@ -1433,9 +1480,7 @@ describe Ci::Build do
{ key: 'CI_PROJECT_VISIBILITY', value: 'private', public: true },
{ key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true },
{ key: 'CI_CONFIG_PATH', value: pipeline.ci_yaml_file_path, public: true },
- { key: 'CI_REGISTRY_USER', value: 'gitlab-ci-token', public: true },
- { key: 'CI_REGISTRY_PASSWORD', value: build.token, public: false },
- { key: 'CI_REPOSITORY_URL', value: build.repo_url, public: false }
+ { key: 'CI_PIPELINE_SOURCE', value: pipeline.source, public: true }
]
end
@@ -1834,39 +1879,6 @@ describe Ci::Build do
it { is_expected.to include(ci_config_path) }
end
- context 'returns variables in valid order' do
- let(:build_pre_var) { { key: 'build', value: 'value' } }
- let(:project_pre_var) { { key: 'project', value: 'value' } }
- let(:pipeline_pre_var) { { key: 'pipeline', value: 'value' } }
- let(:build_yaml_var) { { key: 'yaml', value: 'value' } }
-
- before do
- allow(build).to receive(:predefined_variables) { [build_pre_var] }
- allow(build).to receive(:yaml_variables) { [build_yaml_var] }
-
- allow_any_instance_of(Project)
- .to receive(:predefined_variables) { [project_pre_var] }
-
- allow_any_instance_of(Project)
- .to receive(:secret_variables_for)
- .with(ref: 'master', environment: nil) do
- [create(:ci_variable, key: 'secret', value: 'value')]
- end
-
- allow_any_instance_of(Ci::Pipeline)
- .to receive(:predefined_variables) { [pipeline_pre_var] }
- end
-
- it do
- is_expected.to eq(
- [build_pre_var,
- project_pre_var,
- pipeline_pre_var,
- build_yaml_var,
- { key: 'secret', value: 'value', public: false }])
- end
- end
-
context 'when using auto devops' do
context 'and is enabled' do
before do
@@ -1890,6 +1902,81 @@ describe Ci::Build do
end
end
end
+
+ context 'when pipeline variable overrides build variable' do
+ before do
+ build.yaml_variables = [{ key: 'MYVAR', value: 'myvar', public: true }]
+ pipeline.variables.build(key: 'MYVAR', value: 'pipeline value')
+ end
+
+ it 'overrides YAML variable using a pipeline variable' do
+ variables = subject.reverse.uniq { |variable| variable[:key] }.reverse
+
+ expect(variables)
+ .not_to include(key: 'MYVAR', value: 'myvar', public: true)
+ expect(variables)
+ .to include(key: 'MYVAR', value: 'pipeline value', public: false)
+ end
+ end
+
+ 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 } }
+
+ before do
+ allow(build).to receive(:predefined_variables) { [build_pre_var] }
+ allow(build).to receive(:yaml_variables) { [build_yaml_var] }
+
+ allow_any_instance_of(Project)
+ .to receive(:predefined_variables) { [project_pre_var] }
+
+ allow_any_instance_of(Project)
+ .to receive(:secret_variables_for)
+ .with(ref: 'master', environment: nil) do
+ [create(:ci_variable, key: 'secret', value: 'value')]
+ end
+
+ allow_any_instance_of(Ci::Pipeline)
+ .to receive(:predefined_variables) { [pipeline_pre_var] }
+ end
+
+ it 'returns variables in order depending on resource hierarchy' do
+ is_expected.to eq(
+ [build_pre_var,
+ project_pre_var,
+ pipeline_pre_var,
+ build_yaml_var,
+ { key: 'secret', value: 'value', public: false }])
+ end
+ end
+
+ context 'when build has environment and user-provided variables' do
+ let(:expected_variables) do
+ predefined_variables.map { |variable| variable.fetch(:key) } +
+ %w[YAML_VARIABLE CI_ENVIRONMENT_NAME CI_ENVIRONMENT_SLUG
+ CI_ENVIRONMENT_URL]
+ end
+
+ before do
+ create(:environment, project: build.project,
+ name: 'staging')
+
+ build.yaml_variables = [{ key: 'YAML_VARIABLE',
+ value: 'var',
+ public: true }]
+ build.environment = 'staging'
+ end
+
+ it 'matches explicit variables ordering' do
+ received_variables = subject.map { |variable| variable.fetch(:key) }
+
+ expect(received_variables).to eq expected_variables
+ end
+ end
+ end
end
describe 'state transition: any => [:pending]' do
@@ -1907,7 +1994,7 @@ describe Ci::Build do
context 'when depended job has not been completed yet' do
let!(:pre_stage_job) { create(:ci_build, :manual, pipeline: pipeline, name: 'test', stage_idx: 0) }
- it { expect { job.run! }.not_to raise_error(Ci::Build::MissingDependenciesError) }
+ it { expect { job.run! }.not_to raise_error }
end
context 'when artifacts of depended job has been expired' do
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 14d234f6aab..4635f8cfe9d 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -170,12 +170,10 @@ describe Ci::Pipeline, :mailer do
describe '#predefined_variables' do
subject { pipeline.predefined_variables }
- it { is_expected.to be_an(Array) }
-
- it 'includes the defined keys' do
- keys = subject.map { |v| v[:key] }
+ it 'includes all predefined variables in a valid order' do
+ keys = subject.map { |variable| variable[:key] }
- expect(keys).to include('CI_PIPELINE_ID', 'CI_CONFIG_PATH', 'CI_PIPELINE_SOURCE')
+ expect(keys).to eq %w[CI_PIPELINE_ID CI_CONFIG_PATH CI_PIPELINE_SOURCE]
end
end
diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb
index 53a4e545ff6..add481b8096 100644
--- a/spec/models/clusters/platforms/kubernetes_spec.rb
+++ b/spec/models/clusters/platforms/kubernetes_spec.rb
@@ -252,7 +252,7 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
stub_kubeclient_pods(status: 500)
end
- it { expect { subject }.to raise_error(KubeException) }
+ it { expect { subject }.to raise_error(Kubeclient::HttpError) }
end
context 'when kubernetes responds with 404s' do
diff --git a/spec/models/compare_spec.rb b/spec/models/compare_spec.rb
index 04f3cecae00..8e88bb81162 100644
--- a/spec/models/compare_spec.rb
+++ b/spec/models/compare_spec.rb
@@ -37,33 +37,51 @@ describe Compare do
end
end
- describe '#base_commit' do
- let(:base_commit) { Commit.new(another_sample_commit, project) }
+ describe '#base_commit_sha' do
+ it 'returns @base_sha if it is present' do
+ expect(project).not_to receive(:merge_base_commit)
- it 'returns project merge base commit' do
- expect(project).to receive(:merge_base_commit).with(start_commit.id, head_commit.id).and_return(base_commit)
+ sha = double
+ service = described_class.new(raw_compare, project, base_sha: sha)
- expect(subject.base_commit).to eq(base_commit)
+ expect(service.base_commit_sha).to eq(sha)
+ end
+
+ it 'fetches merge base SHA from repo when @base_sha is nil' do
+ expect(project).to receive(:merge_base_commit)
+ .with(start_commit.id, head_commit.id)
+ .once
+ .and_call_original
+
+ expect(subject.base_commit_sha)
+ .to eq(project.repository.merge_base(start_commit.id, head_commit.id))
+ end
+
+ it 'is memoized on first call' do
+ expect(project).to receive(:merge_base_commit)
+ .with(start_commit.id, head_commit.id)
+ .once
+ .and_call_original
+
+ 3.times { subject.base_commit_sha }
end
it 'returns nil if there is no start_commit' do
expect(subject).to receive(:start_commit).and_return(nil)
- expect(subject.base_commit).to eq(nil)
+ expect(subject.base_commit_sha).to eq(nil)
end
it 'returns nil if there is no head commit' do
expect(subject).to receive(:head_commit).and_return(nil)
- expect(subject.base_commit).to eq(nil)
+ expect(subject.base_commit_sha).to eq(nil)
end
end
describe '#diff_refs' do
- it 'uses base_commit sha as base_sha' do
- expect(subject).to receive(:base_commit).at_least(:once).and_call_original
-
- expect(subject.diff_refs.base_sha).to eq(subject.base_commit.id)
+ it 'uses base_commit_sha sha as base_sha' do
+ expect(subject.diff_refs.base_sha).to eq(subject.base_commit_sha)
end
it 'uses start_commit sha as start_sha' do
diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb
index 3e46fa36375..b8b0e63f92e 100644
--- a/spec/models/members/project_member_spec.rb
+++ b/spec/models/members/project_member_spec.rb
@@ -45,14 +45,6 @@ describe ProjectMember do
let(:project) { owner.project }
let(:master) { create(:project_member, project: project) }
- let(:owner_todos) { (0...2).map { create(:todo, user: owner.user, project: project) } }
- let(:master_todos) { (0...3).map { create(:todo, user: master.user, project: project) } }
-
- before do
- owner_todos
- master_todos
- end
-
it "creates an expired event when left due to expiry" do
expired = create(:project_member, project: project, expires_at: Time.now - 6.days)
expired.destroy
@@ -63,21 +55,6 @@ describe ProjectMember do
master.destroy
expect(Event.recent.first.action).to eq(Event::LEFT)
end
-
- it "destroys itself and delete associated todos" do
- expect(owner.user.todos.size).to eq(2)
- expect(master.user.todos.size).to eq(3)
- expect(Todo.count).to eq(5)
-
- master_todo_ids = master_todos.map(&:id)
- master.destroy
-
- expect(owner.user.todos.size).to eq(2)
- expect(Todo.count).to eq(2)
- master_todo_ids.each do |id|
- expect(Todo.exists?(id)).to eq(false)
- end
- end
end
describe '.import_team' do
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 7986aa31e16..4e783acbd8b 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -1544,7 +1544,7 @@ describe MergeRequest do
end
it "executes diff cache service" do
- expect_any_instance_of(MergeRequests::MergeRequestDiffCacheService).to receive(:execute).with(subject)
+ expect_any_instance_of(MergeRequests::MergeRequestDiffCacheService).to receive(:execute).with(subject, an_instance_of(MergeRequestDiff))
subject.reload_diff
end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index e626efd054d..ee142718f7e 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -204,43 +204,67 @@ describe Namespace do
expect(gitlab_shell.exists?(project.repository_storage_path, "#{namespace.path}/#{project.path}.git")).to be_truthy
end
- context 'with subgroups' do
+ context 'with subgroups', :nested_groups do
let(:parent) { create(:group, name: 'parent', path: 'parent') }
+ let(:new_parent) { create(:group, name: 'new_parent', path: 'new_parent') }
let(:child) { create(:group, name: 'child', path: 'child', parent: parent) }
let!(:project) { create(:project_empty_repo, :legacy_storage, path: 'the-project', namespace: child, skip_disk_validation: true) }
let(:uploads_dir) { FileUploader.root }
let(:pages_dir) { File.join(TestEnv.pages_path) }
+ def expect_project_directories_at(namespace_path)
+ expected_repository_path = File.join(TestEnv.repos_path, namespace_path, 'the-project.git')
+ expected_upload_path = File.join(uploads_dir, namespace_path, 'the-project')
+ expected_pages_path = File.join(pages_dir, namespace_path, 'the-project')
+
+ expect(File.directory?(expected_repository_path)).to be_truthy
+ expect(File.directory?(expected_upload_path)).to be_truthy
+ expect(File.directory?(expected_pages_path)).to be_truthy
+ end
+
before do
+ FileUtils.mkdir_p(File.join(TestEnv.repos_path, "#{project.full_path}.git"))
FileUtils.mkdir_p(File.join(uploads_dir, project.full_path))
FileUtils.mkdir_p(File.join(pages_dir, project.full_path))
end
context 'renaming child' do
it 'correctly moves the repository, uploads and pages' do
- expected_repository_path = File.join(TestEnv.repos_path, 'parent', 'renamed', 'the-project.git')
- expected_upload_path = File.join(uploads_dir, 'parent', 'renamed', 'the-project')
- expected_pages_path = File.join(pages_dir, 'parent', 'renamed', 'the-project')
+ child.update!(path: 'renamed')
- child.update_attributes!(path: 'renamed')
-
- expect(File.directory?(expected_repository_path)).to be(true)
- expect(File.directory?(expected_upload_path)).to be(true)
- expect(File.directory?(expected_pages_path)).to be(true)
+ expect_project_directories_at('parent/renamed')
end
end
context 'renaming parent' do
it 'correctly moves the repository, uploads and pages' do
- expected_repository_path = File.join(TestEnv.repos_path, 'renamed', 'child', 'the-project.git')
- expected_upload_path = File.join(uploads_dir, 'renamed', 'child', 'the-project')
- expected_pages_path = File.join(pages_dir, 'renamed', 'child', 'the-project')
+ parent.update!(path: 'renamed')
+
+ expect_project_directories_at('renamed/child')
+ end
+ end
+
+ context 'moving from one parent to another' do
+ it 'correctly moves the repository, uploads and pages' do
+ child.update!(parent: new_parent)
- parent.update_attributes!(path: 'renamed')
+ expect_project_directories_at('new_parent/child')
+ end
+ end
+
+ context 'moving from having a parent to root' do
+ it 'correctly moves the repository, uploads and pages' do
+ child.update!(parent: nil)
+
+ expect_project_directories_at('child')
+ end
+ end
+
+ context 'moving from root to having a parent' do
+ it 'correctly moves the repository, uploads and pages' do
+ parent.update!(parent: new_parent)
- expect(File.directory?(expected_repository_path)).to be(true)
- expect(File.directory?(expected_upload_path)).to be(true)
- expect(File.directory?(expected_pages_path)).to be(true)
+ expect_project_directories_at('new_parent/parent/child')
end
end
end
@@ -525,7 +549,6 @@ describe Namespace do
end
end
- # Note: Group transfers are not yet implemented
context 'when a group is transferred into a root group' do
context 'when the root group "Share with group lock" is enabled' do
let(:root_group) { create(:group, share_with_group_lock: true) }
diff --git a/spec/models/project_auto_devops_spec.rb b/spec/models/project_auto_devops_spec.rb
index 296b91a771c..7545c0797e9 100644
--- a/spec/models/project_auto_devops_spec.rb
+++ b/spec/models/project_auto_devops_spec.rb
@@ -36,14 +36,14 @@ describe ProjectAutoDevops do
end
end
- describe '#variables' do
+ describe '#predefined_variables' do
let(:auto_devops) { build_stubbed(:project_auto_devops, project: project, domain: domain) }
context 'when domain is defined' do
let(:domain) { 'example.com' }
it 'returns AUTO_DEVOPS_DOMAIN' do
- expect(auto_devops.variables).to include(domain_variable)
+ expect(auto_devops.predefined_variables).to include(domain_variable)
end
end
@@ -55,7 +55,7 @@ describe ProjectAutoDevops do
allow(Gitlab::CurrentSettings).to receive(:auto_devops_domain).and_return('example.com')
end
- it { expect(auto_devops.variables).to include(domain_variable) }
+ it { expect(auto_devops.predefined_variables).to include(domain_variable) }
end
context 'when there is no instance domain specified' do
@@ -63,7 +63,7 @@ describe ProjectAutoDevops do
allow(Gitlab::CurrentSettings).to receive(:auto_devops_domain).and_return(nil)
end
- it { expect(auto_devops.variables).not_to include(domain_variable) }
+ it { expect(auto_devops.predefined_variables).not_to include(domain_variable) }
end
end
diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb
index 622d8844a72..3be023a48c1 100644
--- a/spec/models/project_services/kubernetes_service_spec.rb
+++ b/spec/models/project_services/kubernetes_service_spec.rb
@@ -370,7 +370,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
stub_kubeclient_pods(status: 500)
end
- it { expect { subject }.to raise_error(KubeException) }
+ it { expect { subject }.to raise_error(Kubeclient::HttpError) }
end
context 'when kubernetes responds with 404s' do
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index e970cd7dfdb..4cf8d861595 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -1378,7 +1378,7 @@ describe Project do
context 'using a regular repository' do
it 'creates the repository' do
- expect(shell).to receive(:add_repository)
+ expect(shell).to receive(:create_repository)
.with(project.repository_storage, project.disk_path)
.and_return(true)
@@ -1388,7 +1388,7 @@ describe Project do
end
it 'adds an error if the repository could not be created' do
- expect(shell).to receive(:add_repository)
+ expect(shell).to receive(:create_repository)
.with(project.repository_storage, project.disk_path)
.and_return(false)
@@ -1402,7 +1402,7 @@ describe Project do
context 'using a forked repository' do
it 'does nothing' do
expect(project).to receive(:forked?).and_return(true)
- expect(shell).not_to receive(:add_repository)
+ expect(shell).not_to receive(:create_repository)
project.create_repository
end
@@ -1421,7 +1421,7 @@ describe Project do
allow(project).to receive(:repository_exists?)
.and_return(false)
- allow(shell).to receive(:add_repository)
+ allow(shell).to receive(:create_repository)
.with(project.repository_storage_path, project.disk_path)
.and_return(true)
@@ -1445,7 +1445,7 @@ describe Project do
allow(project).to receive(:repository_exists?)
.and_return(false)
- expect(shell).to receive(:add_repository)
+ expect(shell).to receive(:create_repository)
.with(project.repository_storage, project.disk_path)
.and_return(true)
diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb
index 8b4b5873704..d87c1ca14f0 100644
--- a/spec/models/project_wiki_spec.rb
+++ b/spec/models/project_wiki_spec.rb
@@ -74,7 +74,7 @@ describe ProjectWiki do
# Create a fresh project which will not have a wiki
project_wiki = described_class.new(create(:project), user)
gitlab_shell = double(:gitlab_shell)
- allow(gitlab_shell).to receive(:add_repository)
+ allow(gitlab_shell).to receive(:create_repository)
allow(project_wiki).to receive(:gitlab_shell).and_return(gitlab_shell)
expect { project_wiki.send(:wiki) }.to raise_exception(ProjectWiki::CouldNotCreateWikiError)
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 579069ffa14..5bc972bca14 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -1447,7 +1447,6 @@ describe Repository do
it 'expires the caches for an empty repository' do
allow(repository).to receive(:empty?).and_return(true)
- expect(cache).to receive(:expire).with(:empty?)
expect(cache).to receive(:expire).with(:has_visible_content?)
repository.expire_emptiness_caches
@@ -1456,7 +1455,6 @@ describe Repository do
it 'does not expire the cache for a non-empty repository' do
allow(repository).to receive(:empty?).and_return(false)
- expect(cache).not_to receive(:expire).with(:empty?)
expect(cache).not_to receive(:expire).with(:has_visible_content?)
repository.expire_emptiness_caches
@@ -2171,15 +2169,6 @@ describe Repository do
end
end
- describe '#expire_method_caches' do
- it 'expires the caches of the given methods' do
- expect_any_instance_of(RepositoryCache).to receive(:expire).with(:readme)
- expect_any_instance_of(RepositoryCache).to receive(:expire).with(:gitignore)
-
- repository.expire_method_caches(%i(readme gitignore))
- end
- end
-
describe '#expire_all_method_caches' do
it 'expires the caches of all methods' do
expect(repository).to receive(:expire_method_caches)
@@ -2325,66 +2314,6 @@ describe Repository do
end
end
- describe '#cache_method_output', :use_clean_rails_memory_store_caching do
- let(:fallback) { 10 }
-
- context 'with a non-existing repository' do
- let(:project) { create(:project) } # No repository
-
- subject do
- repository.cache_method_output(:cats, fallback: fallback) do
- repository.cats_call_stub
- end
- end
-
- it 'returns the fallback value' do
- expect(subject).to eq(fallback)
- end
-
- it 'avoids calling the original method' do
- expect(repository).not_to receive(:cats_call_stub)
-
- subject
- end
- end
-
- context 'with a method throwing a non-existing-repository error' do
- subject do
- repository.cache_method_output(:cats, fallback: fallback) do
- raise Gitlab::Git::Repository::NoRepository
- end
- end
-
- it 'returns the fallback value' do
- expect(subject).to eq(fallback)
- end
-
- it 'does not cache the data' do
- subject
-
- expect(repository.instance_variable_defined?(:@cats)).to eq(false)
- expect(repository.send(:cache).exist?(:cats)).to eq(false)
- end
- end
-
- context 'with an existing repository' do
- it 'caches the output' do
- object = double
-
- expect(object).to receive(:number).once.and_return(10)
-
- 2.times do
- val = repository.cache_method_output(:cats) { object.number }
-
- expect(val).to eq(10)
- end
-
- expect(repository.send(:cache).exist?(:cats)).to eq(true)
- expect(repository.instance_variable_get(:@cats)).to eq(10)
- end
- end
- end
-
describe '#refresh_method_caches' do
it 'refreshes the caches of the given types' do
expect(repository).to receive(:expire_method_caches)
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 00b5226d874..5680eb24985 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -27,7 +27,6 @@ describe User do
it { is_expected.to have_many(:keys).dependent(:destroy) }
it { is_expected.to have_many(:deploy_keys).dependent(:destroy) }
it { is_expected.to have_many(:events).dependent(:destroy) }
- it { is_expected.to have_many(:recent_events).class_name('Event') }
it { is_expected.to have_many(:issues).dependent(:destroy) }
it { is_expected.to have_many(:notes).dependent(:destroy) }
it { is_expected.to have_many(:merge_requests).dependent(:destroy) }
diff --git a/spec/requests/api/project_export_spec.rb b/spec/requests/api/project_export_spec.rb
index fbed527963f..12583109b59 100644
--- a/spec/requests/api/project_export_spec.rb
+++ b/spec/requests/api/project_export_spec.rb
@@ -285,6 +285,17 @@ describe API::ProjectExport do
context 'when user is not a member' do
it_behaves_like 'post project export start not found'
end
+
+ context 'when overriding description' do
+ it 'starts' do
+ params = { description: "Foo" }
+
+ expect_any_instance_of(Projects::ImportExport::ExportService).to receive(:execute)
+ post api(path, project.owner), params
+
+ expect(response).to have_gitlab_http_status(202)
+ end
+ end
end
end
end
diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb
index 56d025f0176..9345671a1a7 100644
--- a/spec/routing/routing_spec.rb
+++ b/spec/routing/routing_spec.rb
@@ -9,7 +9,7 @@ require 'spec_helper'
# user_calendar_activities GET /u/:username/calendar_activities(.:format)
describe UsersController, "routing" do
it "to #show" do
- allow_any_instance_of(UserUrlConstrainer).to receive(:matches?).and_return(true)
+ allow_any_instance_of(::Constraints::UserUrlConstrainer).to receive(:matches?).and_return(true)
expect(get("/User")).to route_to('users#show', username: 'User')
end
@@ -210,7 +210,7 @@ describe Profiles::KeysController, "routing" do
# get all the ssh-keys of a user
it "to #get_keys" do
- allow_any_instance_of(UserUrlConstrainer).to receive(:matches?).and_return(true)
+ allow_any_instance_of(::Constraints::UserUrlConstrainer).to receive(:matches?).and_return(true)
expect(get("/foo.keys")).to route_to('profiles/keys#get_keys', username: 'foo')
end
diff --git a/spec/services/clusters/applications/install_service_spec.rb b/spec/services/clusters/applications/install_service_spec.rb
index ad175226e92..93199964a0e 100644
--- a/spec/services/clusters/applications/install_service_spec.rb
+++ b/spec/services/clusters/applications/install_service_spec.rb
@@ -34,7 +34,7 @@ describe Clusters::Applications::InstallService do
context 'when k8s cluster communication fails' do
before do
- error = KubeException.new(500, 'system failure', nil)
+ error = Kubeclient::HttpError.new(500, 'system failure', nil)
expect(helm_client).to receive(:install).with(install_command).and_raise(error)
end
diff --git a/spec/services/files/create_service_spec.rb b/spec/services/files/create_service_spec.rb
index 030263b1502..abe99b9e794 100644
--- a/spec/services/files/create_service_spec.rb
+++ b/spec/services/files/create_service_spec.rb
@@ -43,7 +43,7 @@ describe Files::CreateService do
blob = repository.blob_at('lfs', file_path)
- expect(blob.data).not_to start_with('version https://git-lfs.github.com/spec/v1')
+ expect(blob.data).not_to start_with(Gitlab::Git::LfsPointerFile::VERSION_LINE)
expect(blob.data).to eq(file_content)
end
end
@@ -58,7 +58,7 @@ describe Files::CreateService do
blob = repository.blob_at('lfs', file_path)
- expect(blob.data).to start_with('version https://git-lfs.github.com/spec/v1')
+ expect(blob.data).to start_with(Gitlab::Git::LfsPointerFile::VERSION_LINE)
end
it "creates an LfsObject with the file's content" do
diff --git a/spec/services/files/multi_service_spec.rb b/spec/services/files/multi_service_spec.rb
index b9971776b33..59984c10990 100644
--- a/spec/services/files/multi_service_spec.rb
+++ b/spec/services/files/multi_service_spec.rb
@@ -4,28 +4,30 @@ describe Files::MultiService do
subject { described_class.new(project, user, commit_params) }
let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
let(:user) { create(:user) }
let(:branch_name) { project.default_branch }
let(:original_file_path) { 'files/ruby/popen.rb' }
let(:new_file_path) { 'files/ruby/popen.rb' }
+ let(:file_content) { 'New content' }
let(:action) { 'update' }
let!(:original_commit_id) do
Gitlab::Git::Commit.last_for_path(project.repository, branch_name, original_file_path).sha
end
- let(:actions) do
- [
- {
- action: action,
- file_path: new_file_path,
- previous_path: original_file_path,
- content: 'New content',
- last_commit_id: original_commit_id
- }
- ]
+ let(:default_action) do
+ {
+ action: action,
+ file_path: new_file_path,
+ previous_path: original_file_path,
+ content: file_content,
+ last_commit_id: original_commit_id
+ }
end
+ let(:actions) { [default_action] }
+
let(:commit_params) do
{
commit_message: "Update File",
@@ -110,6 +112,56 @@ describe Files::MultiService do
end
end
+ context 'when creating a file matching an LFS filter' do
+ let(:action) { 'create' }
+ let(:branch_name) { 'lfs' }
+ let(:new_file_path) { 'test_file.lfs' }
+
+ before do
+ allow(project).to receive(:lfs_enabled?).and_return(true)
+ end
+
+ it 'creates an LFS pointer' do
+ subject.execute
+
+ blob = repository.blob_at('lfs', new_file_path)
+
+ expect(blob.data).to start_with(Gitlab::Git::LfsPointerFile::VERSION_LINE)
+ end
+
+ it "creates an LfsObject with the file's content" do
+ subject.execute
+
+ expect(LfsObject.last.file.read).to eq file_content
+ end
+
+ context 'with base64 encoded content' do
+ let(:raw_file_content) { 'Raw content' }
+ let(:file_content) { Base64.encode64(raw_file_content) }
+ let(:actions) { [default_action.merge(encoding: 'base64')] }
+
+ it 'creates an LFS pointer' do
+ subject.execute
+
+ blob = repository.blob_at('lfs', new_file_path)
+
+ expect(blob.data).to start_with(Gitlab::Git::LfsPointerFile::VERSION_LINE)
+ end
+
+ it "creates an LfsObject with the file's content" do
+ subject.execute
+
+ expect(LfsObject.last.file.read).to eq raw_file_content
+ end
+ end
+
+ it 'links the LfsObject to the project' do
+ expect do
+ subject.execute
+ end.to change { project.lfs_objects.count }.by(1)
+ end
+ end
+
context 'when file status validation is skipped' do
let(:action) { 'create' }
let(:new_file_path) { 'files/ruby/new_file.rb' }
diff --git a/spec/services/lfs/file_transformer_spec.rb b/spec/services/lfs/file_transformer_spec.rb
new file mode 100644
index 00000000000..e8938338cb7
--- /dev/null
+++ b/spec/services/lfs/file_transformer_spec.rb
@@ -0,0 +1,97 @@
+require "spec_helper"
+
+describe Lfs::FileTransformer do
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
+ let(:file_content) { 'Test file content' }
+ let(:branch_name) { 'lfs' }
+ let(:file_path) { 'test_file.lfs' }
+
+ subject { described_class.new(project, branch_name) }
+
+ describe '#new_file' do
+ context 'with lfs disabled' do
+ it 'skips gitattributes check' do
+ expect(repository.raw).not_to receive(:blob_at)
+
+ subject.new_file(file_path, file_content)
+ end
+
+ it 'returns untransformed content' do
+ result = subject.new_file(file_path, file_content)
+
+ expect(result.content).to eq(file_content)
+ end
+
+ it 'returns untransformed encoding' do
+ result = subject.new_file(file_path, file_content, encoding: 'base64')
+
+ expect(result.encoding).to eq('base64')
+ end
+ end
+
+ context 'with lfs enabled' do
+ before do
+ allow(project).to receive(:lfs_enabled?).and_return(true)
+ end
+
+ it 'reuses cached gitattributes' do
+ second_file = 'another_file.lfs'
+
+ expect(repository.raw).to receive(:blob_at).with(branch_name, '.gitattributes').once
+
+ subject.new_file(file_path, file_content)
+ subject.new_file(second_file, file_content)
+ end
+
+ it "creates an LfsObject with the file's content" do
+ subject.new_file(file_path, file_content)
+
+ expect(LfsObject.last.file.read).to eq file_content
+ end
+
+ it 'returns an LFS pointer' do
+ result = subject.new_file(file_path, file_content)
+
+ expect(result.content).to start_with(Gitlab::Git::LfsPointerFile::VERSION_LINE)
+ end
+
+ it 'returns LFS pointer encoding as text' do
+ result = subject.new_file(file_path, file_content, encoding: 'base64')
+
+ expect(result.encoding).to eq('text')
+ end
+
+ context "when doesn't use LFS" do
+ let(:file_path) { 'other.filetype' }
+
+ it "doesn't create LFS pointers" do
+ new_content = subject.new_file(file_path, file_content).content
+
+ expect(new_content).not_to start_with(Gitlab::Git::LfsPointerFile::VERSION_LINE)
+ expect(new_content).to eq(file_content)
+ end
+ end
+
+ it 'links LfsObjects to project' do
+ expect do
+ subject.new_file(file_path, file_content)
+ end.to change { project.lfs_objects.count }.by(1)
+ end
+
+ context 'when LfsObject already exists' do
+ let(:lfs_pointer) { Gitlab::Git::LfsPointerFile.new(file_content) }
+
+ before do
+ create(:lfs_object, oid: lfs_pointer.sha256, size: lfs_pointer.size)
+ end
+
+ it 'links LfsObjects to project' do
+ expect do
+ subject.new_file(file_path, file_content)
+ end.to change { project.lfs_objects.count }.by(1)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/members/destroy_service_spec.rb b/spec/services/members/destroy_service_spec.rb
index 10c264a90c5..36b6e5a701e 100644
--- a/spec/services/members/destroy_service_spec.rb
+++ b/spec/services/members/destroy_service_spec.rb
@@ -19,32 +19,11 @@ describe Members::DestroyService do
end
end
- def number_of_assigned_issuables(user)
- Issue.assigned_to(user).count + MergeRequest.assigned_to(user).count
- end
-
shared_examples 'a service destroying a member' do
it 'destroys the member' do
expect { described_class.new(current_user).execute(member, opts) }.to change { member.source.members_and_requesters.count }.by(-1)
end
- it 'unassigns issues and merge requests' do
- if member.invite?
- expect { described_class.new(current_user).execute(member, opts) }
- .not_to change { number_of_assigned_issuables(member_user) }
- else
- create :issue, assignees: [member_user]
- issue = create :issue, project: group_project, assignees: [member_user]
- merge_request = create :merge_request, target_project: group_project, source_project: group_project, assignee: member_user
-
- expect { described_class.new(current_user).execute(member, opts) }
- .to change { number_of_assigned_issuables(member_user) }.from(3).to(1)
-
- expect(issue.reload.assignee_ids).to be_empty
- expect(merge_request.reload.assignee_id).to be_nil
- end
- end
-
it 'destroys member notification_settings' do
if member_user.notification_settings.any?
expect { described_class.new(current_user).execute(member, opts) }
@@ -56,6 +35,29 @@ describe Members::DestroyService do
end
end
+ shared_examples 'a service destroying a member with access' do
+ it_behaves_like 'a service destroying a member'
+
+ it 'invalidates cached counts for todos and assigned issues and merge requests', :aggregate_failures do
+ create(:issue, project: group_project, assignees: [member_user])
+ create(:merge_request, source_project: group_project, assignee: member_user)
+ create(:todo, :pending, project: group_project, user: member_user)
+ create(:todo, :done, project: group_project, user: member_user)
+
+ expect(member_user.assigned_open_merge_requests_count).to be(1)
+ expect(member_user.assigned_open_issues_count).to be(1)
+ expect(member_user.todos_pending_count).to be(1)
+ expect(member_user.todos_done_count).to be(1)
+
+ described_class.new(current_user).execute(member, opts)
+
+ expect(member_user.assigned_open_merge_requests_count).to be(0)
+ expect(member_user.assigned_open_issues_count).to be(0)
+ expect(member_user.todos_pending_count).to be(0)
+ expect(member_user.todos_done_count).to be(0)
+ end
+ end
+
shared_examples 'a service destroying an access requester' do
it_behaves_like 'a service destroying a member'
@@ -74,29 +76,39 @@ describe Members::DestroyService do
end
end
- context 'with a member' do
+ context 'with a member with access' do
before do
- group_project.add_developer(member_user)
- group.add_developer(member_user)
+ group_project.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PRIVATE)
+ group.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PRIVATE)
end
context 'when current user cannot destroy the given member' do
- it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
+ context 'with a project member' do
let(:member) { group_project.members.find_by(user_id: member_user.id) }
- end
- it_behaves_like 'a service destroying a member' do
- let(:opts) { { skip_authorization: true } }
- let(:member) { group_project.members.find_by(user_id: member_user.id) }
- end
+ before do
+ group_project.add_developer(member_user)
+ end
- it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
- let(:member) { group.members.find_by(user_id: member_user.id) }
+ it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError'
+
+ it_behaves_like 'a service destroying a member with access' do
+ let(:opts) { { skip_authorization: true } }
+ end
end
- it_behaves_like 'a service destroying a member' do
- let(:opts) { { skip_authorization: true } }
+ context 'with a group member' do
let(:member) { group.members.find_by(user_id: member_user.id) }
+
+ before do
+ group.add_developer(member_user)
+ end
+
+ it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError'
+
+ it_behaves_like 'a service destroying a member with access' do
+ let(:opts) { { skip_authorization: true } }
+ end
end
end
@@ -106,12 +118,24 @@ describe Members::DestroyService do
group.add_owner(current_user)
end
- it_behaves_like 'a service destroying a member' do
+ context 'with a project member' do
let(:member) { group_project.members.find_by(user_id: member_user.id) }
+
+ before do
+ group_project.add_developer(member_user)
+ end
+
+ it_behaves_like 'a service destroying a member with access'
end
- it_behaves_like 'a service destroying a member' do
+ context 'with a group member' do
let(:member) { group.members.find_by(user_id: member_user.id) }
+
+ before do
+ group.add_developer(member_user)
+ end
+
+ it_behaves_like 'a service destroying a member with access'
end
end
end
diff --git a/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb b/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb
index bb46e1dd9ab..57b6165cfb0 100644
--- a/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb
+++ b/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb
@@ -1,19 +1,39 @@
require 'spec_helper'
-describe MergeRequests::MergeRequestDiffCacheService do
+describe MergeRequests::MergeRequestDiffCacheService, :use_clean_rails_memory_store_caching do
let(:subject) { described_class.new }
+ let(:merge_request) { create(:merge_request) }
describe '#execute' do
- it 'retrieves the diff files to cache the highlighted result' do
- merge_request = create(:merge_request)
- cache_key = [merge_request.merge_request_diff, 'highlighted-diff-files', Gitlab::Diff::FileCollection::MergeRequestDiff.default_options]
-
- expect(Rails.cache).to receive(:read).with(cache_key).and_return({})
- expect(Rails.cache).to receive(:write).with(cache_key, anything)
+ before do
allow_any_instance_of(Gitlab::Diff::File).to receive(:text?).and_return(true)
allow_any_instance_of(Gitlab::Diff::File).to receive(:diffable?).and_return(true)
+ end
+
+ it 'retrieves the diff files to cache the highlighted result' do
+ new_diff = merge_request.merge_request_diff
+ cache_key = new_diff.diffs.cache_key
+
+ expect(Rails.cache).to receive(:read).with(cache_key).and_call_original
+ expect(Rails.cache).to receive(:write).with(cache_key, anything, anything).and_call_original
+
+ subject.execute(merge_request, new_diff)
+ end
+
+ it 'clears the cache for older diffs on the merge request' do
+ old_diff = merge_request.merge_request_diff
+ old_cache_key = old_diff.diffs.cache_key
+
+ subject.execute(merge_request, old_diff)
+
+ new_diff = merge_request.create_merge_request_diff
+ new_cache_key = new_diff.diffs.cache_key
+
+ expect(Rails.cache).to receive(:delete).with(old_cache_key).and_call_original
+ expect(Rails.cache).to receive(:read).with(new_cache_key).and_call_original
+ expect(Rails.cache).to receive(:write).with(new_cache_key, anything, anything).and_call_original
- subject.execute(merge_request)
+ subject.execute(merge_request, new_diff)
end
end
end
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index 9a44dfde41b..8471467d2fa 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -164,7 +164,7 @@ describe Projects::CreateService, '#execute' do
context 'with legacy storage' do
before do
- gitlab_shell.add_repository(repository_storage, "#{user.namespace.full_path}/existing")
+ gitlab_shell.create_repository(repository_storage, "#{user.namespace.full_path}/existing")
end
after do
@@ -200,7 +200,7 @@ describe Projects::CreateService, '#execute' do
end
before do
- gitlab_shell.add_repository(repository_storage, hashed_path)
+ gitlab_shell.create_repository(repository_storage, hashed_path)
end
after do
diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb
index 409d5de8d43..d1011b07db6 100644
--- a/spec/services/projects/fork_service_spec.rb
+++ b/spec/services/projects/fork_service_spec.rb
@@ -108,7 +108,7 @@ describe Projects::ForkService do
let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage]['path'] }
before do
- gitlab_shell.add_repository(repository_storage, "#{@to_user.namespace.full_path}/#{@from_project.path}")
+ gitlab_shell.create_repository(repository_storage, "#{@to_user.namespace.full_path}/#{@from_project.path}")
end
after do
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index ae0e22e3dc0..ce567fe3879 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -151,7 +151,7 @@ describe Projects::TransferService do
before do
group.add_owner(user)
- unless gitlab_shell.add_repository(repository_storage, "#{group.full_path}/#{project.path}")
+ unless gitlab_shell.create_repository(repository_storage, "#{group.full_path}/#{project.path}")
raise 'failed to add repository'
end
diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb
index ad5a289290c..f3f97b6b921 100644
--- a/spec/services/projects/update_service_spec.rb
+++ b/spec/services/projects/update_service_spec.rb
@@ -132,6 +132,15 @@ describe Projects::UpdateService do
expect(result).to eq({ status: :success })
expect(project.wiki_repository_exists?).to be false
end
+
+ it 'handles empty project feature attributes' do
+ project.project_feature.update(wiki_access_level: ProjectFeature::DISABLED)
+
+ result = update_project(project, user, { name: 'test1' })
+
+ expect(result).to eq({ status: :success })
+ expect(project.wiki_repository_exists?).to be false
+ end
end
context 'when enabling a wiki' do
@@ -187,7 +196,7 @@ describe Projects::UpdateService do
let(:project) { create(:project, :legacy_storage, :repository, creator: user, namespace: user.namespace) }
before do
- gitlab_shell.add_repository(repository_storage, "#{user.namespace.full_path}/existing")
+ gitlab_shell.create_repository(repository_storage, "#{user.namespace.full_path}/existing")
end
after do
diff --git a/spec/tasks/gitlab/artifacts/check_rake_spec.rb b/spec/tasks/gitlab/artifacts/check_rake_spec.rb
new file mode 100644
index 00000000000..d495b08aca0
--- /dev/null
+++ b/spec/tasks/gitlab/artifacts/check_rake_spec.rb
@@ -0,0 +1,34 @@
+require 'rake_helper'
+
+describe 'gitlab:artifacts rake tasks' do
+ describe 'check' do
+ let!(:artifact) { create(:ci_job_artifact, :archive, :correct_checksum) }
+
+ before do
+ Rake.application.rake_require('tasks/gitlab/artifacts/check')
+ stub_env('VERBOSE' => 'true')
+ end
+
+ it 'outputs the integrity check for each batch' do
+ expect { run_rake_task('gitlab:artifacts:check') }.to output(/Failures: 0/).to_stdout
+ end
+
+ it 'errors out about missing files on the file system' do
+ FileUtils.rm_f(artifact.file.path)
+
+ expect { run_rake_task('gitlab:artifacts:check') }.to output(/No such file.*#{Regexp.quote(artifact.file.path)}/).to_stdout
+ end
+
+ it 'errors out about invalid checksum' do
+ artifact.update_column(:file_sha256, 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855')
+
+ expect { run_rake_task('gitlab:artifacts:check') }.to output(/Checksum mismatch/).to_stdout
+ end
+
+ it 'errors out about missing checksum' do
+ artifact.update_column(:file_sha256, nil)
+
+ expect { run_rake_task('gitlab:artifacts:check') }.to output(/Checksum missing/).to_stdout
+ end
+ end
+end
diff --git a/vendor/assets/javascripts/peek.js b/vendor/assets/javascripts/peek.js
index 695eeb27c17..7c6d226fa6a 100644
--- a/vendor/assets/javascripts/peek.js
+++ b/vendor/assets/javascripts/peek.js
@@ -1,14 +1,14 @@
/*
- * This is a modified version of https://github.com/peek/peek/blob/master/app/assets/javascripts/peek.js
+ * this is a modified version of https://github.com/peek/peek/blob/master/app/assets/javascripts/peek.js
*
* - Removed the dependency on jquery.tipsy
* - Removed the initializeTipsy and toggleBar functions
- * - Customized updatePerformanceBar to handle SQL queries report specificities
+ * - Customized updatePerformanceBar to handle SQL query and Gitaly call lists
* - Changed /peek/results to /-/peek/results
* - Removed the keypress, pjax:end, page:change, and turbolinks:load handlers
*/
(function($) {
- var fetchRequestResults, getRequestId, peekEnabled, updatePerformanceBar;
+ var fetchRequestResults, getRequestId, peekEnabled, updatePerformanceBar, createTable, createTableRow;
getRequestId = function() {
return $('#peek').data('requestId');
};
@@ -16,39 +16,55 @@
return $('#peek').length;
};
updatePerformanceBar = function(results) {
- var key, label, data, table, html, tr, duration_td, sql_td, strong;
-
Object.keys(results.data).forEach(function(key) {
Object.keys(results.data[key]).forEach(function(label) {
- data = results.data[key][label];
+ var data = results.data[key][label];
+ var table = createTable(key, label, data);
+ var target = $('[data-defer-to="' + key + '-' + label + '"]');
+
+ if (table) {
+ target.html(table);
+ } else {
+ target.text(data);
+ }
+ });
+ });
+ return $(document).trigger('peek:render', [getRequestId(), results]);
+ };
+ createTable = function(key, label, data) {
+ if (label !== 'queries' && label !== 'details') {
+ return;
+ }
- if (label == 'queries') {
- table = document.createElement('table');
+ var table = document.createElement('table');
- for (var i = 0; i < data.length; i += 1) {
- tr = document.createElement('tr');
- duration_td = document.createElement('td');
- sql_td = document.createElement('td');
- strong = document.createElement('strong');
+ for (var i = 0; i < data.length; i += 1) {
+ table.appendChild(createTableRow(data[i]));
+ }
- strong.append(data[i]['duration'] + 'ms');
- duration_td.appendChild(strong);
- tr.appendChild(duration_td);
+ table.className = 'table';
- sql_td.appendChild(document.createTextNode(data[i]['sql']));
- tr.appendChild(sql_td);
+ return table;
+ };
+ createTableRow = function(row) {
+ var tr = document.createElement('tr');
+ var durationTd = document.createElement('td');
+ var strong = document.createElement('strong');
- table.appendChild(tr);
- }
+ strong.append(row['duration'] + 'ms');
+ durationTd.appendChild(strong);
+ tr.appendChild(durationTd);
- table.className = 'table';
- $("[data-defer-to=" + key + "-" + label + "]").html(table);
- } else {
- $("[data-defer-to=" + key + "-" + label + "]").text(results.data[key][label]);
- }
- });
+ ['sql', 'feature', 'enabled', 'request'].forEach(function(key) {
+ if (!row[key]) { return; }
+
+ var td = document.createElement('td');
+
+ td.appendChild(document.createTextNode(row[key]));
+ tr.appendChild(td);
});
- return $(document).trigger('peek:render', [getRequestId(), results]);
+
+ return tr;
};
fetchRequestResults = function() {
return $.ajax('/-/peek/results', {
diff --git a/vendor/gitignore/Dart.gitignore b/vendor/gitignore/Dart.gitignore
index 58950beb4fa..7bf00e82cc9 100644
--- a/vendor/gitignore/Dart.gitignore
+++ b/vendor/gitignore/Dart.gitignore
@@ -1,4 +1,4 @@
-# See https://www.dartlang.org/tools/private-files.html
+# See https://www.dartlang.org/guides/libraries/private-files
# Files and directories created by pub
.dart_tool/
diff --git a/vendor/gitignore/Qt.gitignore b/vendor/gitignore/Qt.gitignore
index 037a1e75790..5291a385b25 100644
--- a/vendor/gitignore/Qt.gitignore
+++ b/vendor/gitignore/Qt.gitignore
@@ -1,5 +1,4 @@
# C++ objects and libs
-
*.slo
*.lo
*.o
@@ -11,7 +10,6 @@
*.dylib
# Qt-es
-
object_script.*.Release
object_script.*.Debug
*_plugin_import.cpp
@@ -35,13 +33,11 @@ Makefile*
target_wrapper.*
# QtCreator
-
*.autosave
-# QtCtreator Qml
+# QtCreator Qml
*.qmlproject.user
*.qmlproject.user.*
-# QtCtreator CMake
+# QtCreator CMake
CMakeLists.txt.user*
-
diff --git a/vendor/gitignore/R.gitignore b/vendor/gitignore/R.gitignore
index fcff087aebb..26fad6fadff 100644
--- a/vendor/gitignore/R.gitignore
+++ b/vendor/gitignore/R.gitignore
@@ -31,3 +31,6 @@ vignettes/*.pdf
# Temporary files created by R markdown
*.utf8.md
*.knit.md
+
+# Shiny token, see https://shiny.rstudio.com/articles/shinyapps.html
+rsconnect/
diff --git a/vendor/gitignore/TeX.gitignore b/vendor/gitignore/TeX.gitignore
index 5359e544bcf..78c1c5cd26e 100644
--- a/vendor/gitignore/TeX.gitignore
+++ b/vendor/gitignore/TeX.gitignore
@@ -110,6 +110,14 @@ acs-*.bib
*.gaux
*.gtex
+# htlatex
+*.4ct
+*.4tc
+*.idv
+*.lg
+*.trc
+*.xref
+
# hyperref
*.brf
diff --git a/vendor/gitignore/VisualStudio.gitignore b/vendor/gitignore/VisualStudio.gitignore
index c49041ff7d2..8e930f59c47 100644
--- a/vendor/gitignore/VisualStudio.gitignore
+++ b/vendor/gitignore/VisualStudio.gitignore
@@ -259,9 +259,6 @@ FakesAssemblies/
.ntvs_analysis.dat
node_modules/
-# TypeScript v1 declaration files
-typings/
-
# Visual Studio 6 build log
*.plg
diff --git a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml
index 0ac8885405e..4223dc18933 100644
--- a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml
@@ -36,7 +36,6 @@ variables:
KUBERNETES_VERSION: 1.8.6
HELM_VERSION: 2.6.1
- CODECLIMATE_VERSION: 0.69.0
stages:
- build
@@ -138,7 +137,7 @@ sast:container:
dast:
stage: dast
allow_failure: true
- image: owasp/zap2docker-stable
+ image: registry.gitlab.com/gitlab-org/security-products/zaproxy
variables:
POSTGRES_DB: "false"
script:
@@ -286,6 +285,8 @@ production:
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/')
function sast_container() {
if [[ -n "$CI_REGISTRY_USER" ]]; then
@@ -306,20 +307,15 @@ production:
}
function codeclimate() {
- cc_opts="--env CODECLIMATE_CODE="$PWD" \
- --volume "$PWD":/code \
- --volume /var/run/docker.sock:/var/run/docker.sock \
- --volume /tmp/cc:/tmp/cc"
-
- docker run ${cc_opts} "codeclimate/codeclimate:${CODECLIMATE_VERSION}" init
- docker run ${cc_opts} "codeclimate/codeclimate:${CODECLIMATE_VERSION}" analyze -f json > codeclimate.json
+ docker run --env SOURCE_CODE="$PWD" \
+ --volume "$PWD":/code \
+ --volume /var/run/docker.sock:/var/run/docker.sock \
+ "registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code
}
function sast() {
case "$CI_SERVER_VERSION" in
*-ee)
- # Extract "MAJOR.MINOR" from CI_SERVER_VERSION and generate "MAJOR-MINOR-stable"
- SAST_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
# Deprecation notice for CONFIDENCE_LEVEL variable
if [ -z "$SAST_CONFIDENCE_LEVEL" -a "$CONFIDENCE_LEVEL" ]; then
@@ -331,7 +327,7 @@ production:
--env SAST_DISABLE_REMOTE_CHECKS="${SAST_DISABLE_REMOTE_CHECKS:-false}" \
--volume "$PWD:/code" \
--volume /var/run/docker.sock:/var/run/docker.sock \
- "registry.gitlab.com/gitlab-org/security-products/sast:$SAST_VERSION" /app/bin/run /code
+ "registry.gitlab.com/gitlab-org/security-products/sast:$SP_VERSION" /app/bin/run /code
;;
*)
echo "GitLab EE is required"
diff --git a/vendor/licenses.csv b/vendor/licenses.csv
index 8f127468e25..03115292f02 100644
--- a/vendor/licenses.csv
+++ b/vendor/licenses.csv
@@ -9,14 +9,17 @@
JSONStream,1.3.2,MIT
RedCloth,4.3.2,MIT
abbrev,1.0.9,ISC
+abbrev,1.1.1,ISC
accepts,1.3.3,MIT
+accepts,1.3.4,MIT
ace-rails-ap,4.1.2,MIT
acorn,3.3.0,MIT
acorn,4.0.13,MIT
acorn,5.1.1,MIT
-acorn,5.3.0,MIT
+acorn,5.4.1,MIT
acorn-dynamic-import,2.0.2,MIT
acorn-jsx,3.0.1,MIT
+acorn-node,1.3.0,Apache 2.0
actionmailer,4.2.10,MIT
actionpack,4.2.10,MIT
actionview,4.2.10,MIT
@@ -25,6 +28,7 @@ activemodel,4.2.10,MIT
activerecord,4.2.10,MIT
activesupport,4.2.10,MIT
acts-as-taggable-on,4.0.0,MIT
+address,1.0.3,MIT
addressable,2.5.2,Apache 2.0
addressparser,1.0.1,MIT
after,0.8.2,MIT
@@ -32,59 +36,70 @@ agent-base,2.1.1,MIT
ajv,4.11.8,MIT
ajv,5.2.2,MIT
ajv,5.5.2,MIT
+ajv,6.1.1,MIT
ajv-keywords,1.5.1,MIT
-ajv-keywords,2.1.0,MIT
+ajv-keywords,3.1.0,MIT
akismet,2.0.0,MIT
align-text,0.1.4,MIT
allocations,1.0.5,MIT
alphanum-sort,1.0.2,MIT
amdefine,1.0.1,BSD-3-Clause OR MIT
+amqplib,0.5.2,MIT
+ansi-align,2.0.0,ISC
ansi-escapes,1.4.0,MIT
-ansi-html,0.0.5,"Apache, Version 2.0"
+ansi-escapes,3.0.0,MIT
ansi-html,0.0.7,Apache 2.0
ansi-regex,2.1.1,MIT
ansi-regex,3.0.0,MIT
ansi-styles,2.2.1,MIT
ansi-styles,3.2.0,MIT
anymatch,1.3.2,ISC
+anymatch,2.0.0,ISC
append-transform,0.4.0,MIT
-aproba,1.1.2,ISC
+aproba,1.2.0,ISC
are-we-there-yet,1.1.4,ISC
arel,6.0.4,MIT
argparse,1.0.9,MIT
arr-diff,2.0.0,MIT
-arr-flatten,1.0.1,MIT
+arr-diff,4.0.0,MIT
+arr-flatten,1.1.0,MIT
+arr-union,3.1.0,MIT
array-filter,0.0.1,MIT
array-find,1.0.0,MIT
array-find-index,1.0.2,MIT
array-flatten,1.1.1,MIT
array-flatten,2.1.1,MIT
+array-includes,3.0.3,MIT
array-map,0.0.0,MIT
array-reduce,0.0.0,MIT
array-slice,0.2.3,MIT
array-union,1.0.2,MIT
array-uniq,1.0.3,MIT
array-unique,0.2.1,MIT
+array-unique,0.3.2,MIT
arraybuffer.slice,0.0.7,MIT
arrify,1.0.1,MIT
asana,0.6.0,MIT
asciidoctor,1.5.3,MIT
asciidoctor-plantuml,0.0.7,MIT
asn1,0.2.3,MIT
-asn1.js,4.9.1,MIT
+asn1.js,4.10.1,MIT
assert,1.4.1,MIT
assert-plus,0.2.0,MIT
assert-plus,1.0.0,MIT
asset_sync,2.2.0,MIT
-ast-types,0.10.1,MIT
+assign-symbols,1.0.0,MIT
+ast-types,0.11.1,MIT
astw,2.2.0,MIT
async,0.9.2,MIT
async,1.5.2,MIT
async,2.1.5,MIT
async,2.4.1,MIT
+async,2.6.0,MIT
async-each,1.0.1,MIT
async-limiter,1.0.0,MIT
asynckit,0.4.0,MIT
+atob,2.0.3,(MIT OR Apache-2.0)
atomic,1.1.99,Apache 2.0
attr_encrypted,3.0.3,MIT
attr_required,1.0.0,MIT
@@ -176,9 +191,10 @@ babylon,7.0.0-beta.32,MIT
backo2,1.0.2,MIT
balanced-match,0.4.2,MIT
balanced-match,1.0.0,MIT
+base,0.11.2,MIT
base32,0.3.2,MIT
base64-arraybuffer,0.1.5,MIT
-base64-js,1.2.0,MIT
+base64-js,1.2.3,MIT
base64id,1.0.0,MIT
batch,0.6.1,MIT
batch-loader,1.2.1,MIT
@@ -186,50 +202,57 @@ bcrypt,3.1.11,MIT
bcrypt-pbkdf,1.0.1,New BSD
bcrypt_pbkdf,1.0.0,MIT
better-assert,1.0.2,MIT
+bfj-node4,5.2.1,MIT
big.js,3.1.3,MIT
-binary-extensions,1.10.0,MIT
+binary-extensions,1.11.0,MIT
bindata,2.4.1,ruby
+bitsyntax,0.0.4,Unknown
bl,1.1.2,MIT
blackst0ne-mermaid,7.1.0-fixed,MIT
blob,0.0.4,MIT*
block-stream,0.0.9,ISC
-bluebird,2.11.0,MIT
bluebird,3.5.0,MIT
-bn.js,4.11.6,MIT
-body-parser,1.17.2,MIT
+bluebird,3.5.1,MIT
+bn.js,4.11.8,MIT
+body-parser,1.18.2,MIT
bonjour,3.5.0,MIT
boom,2.10.1,New BSD
boom,4.3.1,New BSD
boom,5.2.0,New BSD
bootstrap-sass,3.3.6,MIT
bootstrap_form,2.7.0,MIT
+boxen,1.3.0,MIT
+brace-expansion,1.1.11,MIT
brace-expansion,1.1.8,MIT
braces,0.1.5,MIT
braces,1.8.5,MIT
-brorand,1.0.7,MIT
+braces,2.3.1,MIT
+brorand,1.1.0,MIT
browser,2.2.0,MIT
-browser-pack,6.0.2,MIT
+browser-pack,6.0.4,MIT
browser-resolve,1.11.2,MIT
browserify,14.5.0,MIT
-browserify-aes,1.0.6,MIT
+browserify-aes,1.1.1,MIT
browserify-cipher,1.0.0,MIT
browserify-des,1.0.0,MIT
browserify-rsa,4.0.1,MIT
-browserify-sign,4.0.0,ISC
+browserify-sign,4.0.4,ISC
browserify-zlib,0.1.4,MIT
browserify-zlib,0.2.0,MIT
browserslist,1.7.7,MIT
buffer,4.9.1,MIT
-buffer,5.0.8,MIT
+buffer,5.1.0,MIT
buffer-indexof,1.1.0,MIT
+buffer-more-ints,0.0.2,MIT
buffer-xor,1.0.3,MIT
builder,3.2.3,MIT
buildmail,4.0.1,MIT
builtin-modules,1.1.1,MIT
builtin-status-codes,3.0.0,MIT
-bytes,2.4.0,MIT
bytes,2.5.0,MIT
bytes,3.0.0,MIT
+cacache,10.0.4,ISC
+cache-base,1.0.1,MIT
cached-path-relative,1.0.1,MIT
caller-path,0.1.0,MIT
callsite,1.0.0,MIT*
@@ -241,6 +264,7 @@ camelcase,4.1.0,MIT
camelcase-keys,2.1.0,MIT
caniuse-api,1.6.1,MIT
caniuse-db,1.0.30000649,CC-BY-4.0
+capture-stack-trace,1.0.0,MIT
carrierwave,1.2.1,MIT
caseless,0.11.0,Apache 2.0
caseless,0.12.0,Apache 2.0
@@ -248,18 +272,27 @@ cause,0.1,MIT
center-align,0.1.3,MIT
chalk,1.1.3,MIT
chalk,2.3.0,MIT
+chalk,2.3.1,MIT
+chardet,0.4.2,MIT
charlock_holmes,0.7.5,MIT
+chart.js,1.0.2,MIT
+check-types,7.3.0,MIT
chokidar,1.7.0,MIT
+chokidar,2.0.2,MIT
+chownr,1.0.1,ISC
chronic,0.10.2,MIT
chronic_duration,0.10.6,MIT
chunky_png,1.3.5,MIT
-cipher-base,1.0.3,MIT
+cipher-base,1.0.4,MIT
circular-json,0.3.3,MIT
-circular-json,0.4.0,MIT
+circular-json,0.5.1,MIT
citrus,3.0.2,MIT
clap,1.1.3,MIT
+class-utils,0.3.6,MIT
classlist-polyfill,1.2.0,Unlicense
+cli-boxes,1.0.0,MIT
cli-cursor,1.0.2,MIT
+cli-cursor,2.1.0,MIT
cli-width,2.1.0,ISC
clipboard,1.7.1,MIT
cliui,2.1.0,ISC
@@ -270,6 +303,7 @@ co,4.6.0,MIT
coa,1.0.1,MIT
code-point-at,1.1.0,MIT
coercible,1.0.0,MIT
+collection-visit,1.0.0,MIT
color,0.11.4,MIT
color-convert,1.9.1,MIT
color-name,1.1.2,MIT
@@ -278,21 +312,23 @@ colormin,1.1.2,MIT
colors,1.1.2,MIT
combine-lists,1.0.1,MIT
combine-source-map,0.7.2,MIT
-combined-stream,1.0.5,MIT
-commander,2.9.0,MIT
+combine-source-map,0.8.0,MIT
+combined-stream,1.0.6,MIT
+commander,2.14.1,MIT
commondir,1.0.1,MIT
+commonmarker,0.17.8,MIT
component-bind,1.0.0,MIT*
component-emitter,1.2.1,MIT
component-inherit,0.0.3,MIT*
compressible,2.0.11,MIT
compression,1.7.0,MIT
-compression-webpack-plugin,1.0.0,MIT
+compression-webpack-plugin,1.1.7,MIT
concat-map,0.0.1,MIT
concat-stream,1.5.2,MIT
concat-stream,1.6.0,MIT
concurrent-ruby-ext,1.0.5,MIT
-configstore,1.4.0,Simplified BSD
-connect,3.6.3,MIT
+configstore,3.1.1,Simplified BSD
+connect,3.6.6,MIT
connect-history-api-fallback,1.3.0,MIT
connection_pool,2.2.1,MIT
console-browserify,1.1.0,MIT
@@ -301,21 +337,25 @@ consolidate,0.14.5,MIT
constants-browserify,1.0.0,MIT
contains-path,0.1.0,MIT
content-disposition,0.5.2,MIT
-content-type,1.0.2,MIT
+content-type,1.0.4,MIT
convert-source-map,1.1.3,MIT
convert-source-map,1.5.0,MIT
cookie,0.3.1,MIT
cookie-signature,1.0.6,MIT
-copy-webpack-plugin,4.0.1,MIT
+copy-concurrently,1.0.5,ISC
+copy-descriptor,0.1.1,MIT
+copy-webpack-plugin,4.4.1,MIT
core-js,2.3.0,MIT
core-js,2.4.1,MIT
core-js,2.5.1,MIT
+core-js,2.5.3,MIT
core-util-is,1.0.2,MIT
cosmiconfig,2.1.1,MIT
crack,0.4.3,MIT
create-ecdh,4.0.0,MIT
-create-hash,1.1.2,MIT
-create-hmac,1.1.4,MIT
+create-error-class,3.0.2,MIT
+create-hash,1.1.3,MIT
+create-hmac,1.1.6,MIT
creole,0.5.0,ruby
cropper,2.3.0,MIT
cross-spawn,5.1.0,MIT
@@ -323,9 +363,9 @@ cryptiles,2.0.5,New BSD
cryptiles,3.1.2,New BSD
crypto-browserify,3.11.0,MIT
crypto-browserify,3.12.0,MIT
+crypto-random-string,1.0.0,MIT
css-color-names,0.0.4,MIT
-css-loader,0.28.0,MIT
-css-selector-tokenizer,0.6.0,MIT
+css-loader,0.28.9,MIT
css-selector-tokenizer,0.7.0,MIT
css_parser,1.5.0,MIT
cssesc,0.1.0,MIT
@@ -333,6 +373,7 @@ cssnano,3.10.0,MIT
csso,2.3.2,MIT
currently-unhandled,0.4.1,MIT
custom-event,1.0.1,MIT
+cyclist,0.2.2,MIT*
d,0.1.1,MIT
d,1.0.0,MIT
d3,3.5.17,New BSD
@@ -363,7 +404,6 @@ date-format,1.2.0,MIT
date-now,0.1.4,MIT
de-indent,1.0.2,MIT
debug,2.2.0,MIT
-debug,2.6.7,MIT
debug,2.6.8,MIT
debug,2.6.9,MIT
debug,3.1.0,MIT
@@ -372,6 +412,7 @@ decamelize,1.2.0,MIT
deckar01-task_list,2.0.0,MIT
declarative,0.0.10,MIT
declarative-option,0.1.0,MIT
+decode-uri-component,0.2.0,MIT
decompress-response,3.3.0,MIT
deep-equal,1.0.1,MIT
deep-extend,0.4.2,MIT
@@ -379,6 +420,9 @@ deep-is,0.1.3,MIT
default-require-extensions,1.0.0,MIT
default_value_for,3.0.2,MIT
define-properties,1.1.2,MIT
+define-property,0.2.5,MIT
+define-property,1.0.0,MIT
+define-property,2.0.2,MIT
defined,1.0.0,MIT
degenerator,1.0.4,MIT
del,2.2.2,MIT
@@ -386,14 +430,15 @@ del,3.0.0,MIT
delayed-stream,1.0.0,MIT
delegate,3.1.2,MIT
delegates,1.0.0,MIT
-depd,1.1.0,MIT
depd,1.1.1,MIT
deps-sort,2.0.0,MIT
des.js,1.0.0,MIT
descendants_tracker,0.0.4,MIT
destroy,1.0.4,MIT
detect-indent,4.0.0,MIT
+detect-libc,1.0.3,Apache 2.0
detect-node,2.0.3,ISC
+detect-port-alt,1.1.5,MIT
detective,4.7.1,MIT
devise,4.2.0,MIT
devise-two-factor,3.0.0,MIT
@@ -402,6 +447,7 @@ diff,3.4.0,New BSD
diff-lcs,1.3,"MIT,Artistic-2.0,GPL-2.0+"
diffie-hellman,5.0.2,MIT
diffy,3.1.0,MIT
+dir-glob,2.0.0,MIT
dns-equal,1.0.0,MIT
dns-packet,1.2.2,MIT
dns-txt,2.0.2,MIT
@@ -418,26 +464,28 @@ domhandler,2.4.1,Simplified BSD
domutils,1.6.2,Simplified BSD
doorkeeper,4.2.6,MIT
doorkeeper-openid_connect,1.2.0,MIT
+dot-prop,4.2.0,MIT
double-ended-queue,2.1.0-0,MIT
dropzone,4.2.0,MIT
dropzonejs-rails,0.7.2,MIT
duplexer,0.1.1,MIT
duplexer2,0.1.4,New BSD
duplexer3,0.1.4,New BSD
-duplexify,3.5.1,MIT
+duplexify,3.5.3,MIT
ecc-jsbn,0.1.1,MIT
ee-first,1.1.1,MIT
-ejs,2.5.6,Apache 2.0
+ejs,2.5.7,Apache 2.0
electron-to-chromium,1.3.3,ISC
-elliptic,6.3.3,MIT
+elliptic,6.4.0,MIT
email_reply_trimmer,0.1.6,MIT
emoji-unicode-version,0.2.1,MIT
emojis-list,2.1.0,MIT
-encodeurl,1.0.1,MIT
+encodeurl,1.0.2,MIT
encryptor,3.0.0,MIT
end-of-stream,1.4.0,MIT
-engine.io,3.1.4,MIT
-engine.io-client,3.1.4,MIT
+end-of-stream,1.4.1,MIT
+engine.io,3.1.5,MIT
+engine.io-client,3.1.5,MIT
engine.io-parser,2.1.2,MIT
enhanced-resolve,0.9.1,MIT
enhanced-resolve,3.4.1,MIT
@@ -447,7 +495,7 @@ equalizer,0.0.11,MIT
errno,0.1.4,MIT
error-ex,1.3.0,MIT
erubis,2.7.0,MIT
-es-abstract,1.8.2,MIT
+es-abstract,1.10.0,MIT
es-to-primitive,1.1.1,MIT
es5-ext,0.10.24,MIT
es6-iterator,2.0.1,MIT
@@ -487,28 +535,35 @@ estraverse,4.1.1,Simplified BSD
estraverse,4.2.0,Simplified BSD
esutils,2.0.2,Simplified BSD
et-orbi,1.0.3,MIT
-etag,1.8.0,MIT
+etag,1.8.1,MIT
eve-raphael,0.5.0,Apache 2.0
event-emitter,0.3.5,MIT
event-stream,3.3.4,MIT
eventemitter3,1.2.0,MIT
events,1.1.1,MIT
eventsource,0.1.6,MIT
-evp_bytestokey,1.0.0,MIT
+evp_bytestokey,1.0.3,MIT
excon,0.57.1,MIT
execa,0.7.0,MIT
execjs,2.6.0,MIT
exit-hook,1.1.1,MIT
expand-braces,0.1.2,MIT
expand-brackets,0.1.5,MIT
+expand-brackets,2.1.4,MIT
expand-range,0.1.1,MIT
expand-range,1.8.2,MIT
-exports-loader,0.6.4,MIT
-express,4.15.4,MIT
+expand-tilde,2.0.2,MIT
+exports-loader,0.7.0,MIT
+express,4.16.2,MIT
expression_parser,0.9.0,MIT
extend,3.0.1,MIT
+extend-shallow,2.0.1,MIT
+extend-shallow,3.0.2,MIT
+external-editor,2.1.0,MIT
extglob,0.3.2,MIT
+extglob,2.0.4,MIT
extsprintf,1.3.0,MIT
+extsprintf,1.4.0,MIT
faraday,0.12.2,MIT
faraday_middleware,0.11.0.1,MIT
faraday_middleware-multi_json,0.0.6,MIT
@@ -520,18 +575,19 @@ fast_gettext,1.4.0,"MIT,ruby"
fastparse,1.1.1,MIT
faye-websocket,0.10.0,MIT
faye-websocket,0.11.1,MIT
-faye-websocket,0.7.3,MIT
ffi,1.9.18,New BSD
figures,1.7.0,MIT
+figures,2.0.0,MIT
file-entry-cache,2.0.0,MIT
-file-loader,0.11.1,MIT
+file-loader,1.1.8,MIT
file-uri-to-path,1.0.0,MIT
-filename-regex,2.0.0,MIT
+filename-regex,2.0.1,MIT
fileset,2.0.3,MIT
-filesize,3.3.0,New BSD
-filesize,3.5.10,New BSD
+filesize,3.5.11,New BSD
+filesize,3.6.0,New BSD
fill-range,2.2.3,MIT
-finalhandler,1.0.4,MIT
+fill-range,4.0.0,MIT
+finalhandler,1.1.0,MIT
find-cache-dir,1.0.0,MIT
find-root,0.1.2,MIT
find-up,1.1.2,MIT
@@ -542,6 +598,7 @@ flipper,0.11.0,MIT
flipper-active_record,0.11.0,MIT
flipper-active_support_cache_store,0.11.0,MIT
flowdock,0.7.1,MIT
+flush-write-stream,1.0.2,MIT
fog-aliyun,0.2.0,MIT
fog-aws,1.4.0,MIT
fog-core,1.44.3,MIT
@@ -554,26 +611,26 @@ fog-xml,0.1.3,MIT
follow-redirects,1.0.0,MIT
follow-redirects,1.2.6,MIT
font-awesome-rails,4.7.0.1,"MIT,SIL Open Font License"
-for-each,0.3.2,MIT
-for-in,0.1.6,MIT
-for-own,0.1.4,MIT
+for-in,1.0.2,MIT
+for-own,0.1.5,MIT
foreach,2.0.5,MIT
forever-agent,0.6.1,Apache 2.0
form-data,2.0.0,MIT
form-data,2.1.4,MIT
-form-data,2.3.1,MIT
+form-data,2.3.2,MIT
formatador,0.2.5,MIT
-forwarded,0.1.0,MIT
-fresh,0.5.0,MIT
+forwarded,0.1.2,MIT
+fragment-cache,0.2.1,MIT
+fresh,0.5.2,MIT
from,0.1.7,MIT
+from2,2.3.0,MIT
fs-access,1.0.1,MIT
-fs-extra,0.26.7,MIT
+fs-write-stream-atomic,1.0.10,ISC
fs.realpath,1.0.0,ISC
-fsevents,1.1.2,MIT
+fsevents,1.1.3,MIT
fstream,1.0.11,ISC
fstream-ignore,1.0.5,ISC
ftp,0.3.10,MIT
-function-bind,1.1.0,MIT
function-bind,1.1.1,MIT
fuzzaldrin-plus,0.5.0,MIT
gauge,2.7.4,ISC
@@ -585,28 +642,33 @@ get-caller-file,1.0.2,ISC
get-stdin,4.0.1,MIT
get-stream,3.0.0,MIT
get-uri,2.0.1,MIT
+get-value,2.0.6,MIT
get_process_mem,0.2.0,MIT
getpass,0.1.7,MIT
gettext_i18n_rails,1.8.0,MIT
gettext_i18n_rails_js,1.2.0,MIT
-gitaly-proto,0.84.0,MIT
-github-linguist,4.7.6,MIT
+gitaly-proto,0.88.0,MIT
+github-linguist,5.3.3,MIT
github-markup,1.6.1,MIT
gitlab-flowdock-git-hook,1.0.1,MIT
gitlab-grit,2.8.2,MIT
gitlab-markup,1.6.3,MIT
gitlab_omniauth-ldap,2.0.4,MIT
glob,5.0.15,ISC
-glob,6.0.4,ISC
glob,7.1.1,ISC
glob,7.1.2,ISC
glob-base,0.3.0,MIT
glob-parent,2.0.0,ISC
+glob-parent,3.1.0,ISC
+global-dirs,0.1.1,MIT
+global-modules,1.0.0,MIT
+global-prefix,1.0.2,MIT
globalid,0.4.1,MIT
globals,10.4.0,MIT
globals,9.18.0,MIT
globby,5.0.0,MIT
globby,6.1.0,MIT
+globby,7.1.1,MIT
gollum-grit_adapter,1.0.1,MIT
gollum-lib,4.2.7,MIT
gollum-rugged_adapter,0.4.4,MIT
@@ -615,19 +677,19 @@ good-listener,1.2.2,MIT
google-api-client,0.13.6,Apache 2.0
google-protobuf,3.5.1,New BSD
googleapis-common-protos-types,1.0.1,Apache 2.0
-googleauth,0.5.3,Apache 2.0
-got,3.3.1,MIT
+googleauth,0.6.2,Apache 2.0
+got,6.7.1,MIT
got,7.1.0,MIT
gpgme,2.0.13,LGPL-2.1+
graceful-fs,4.1.11,ISC
-graceful-readlink,1.0.1,MIT
grape,1.0.0,MIT
grape-entity,0.6.0,MIT
grape-route-helpers,2.1.0,MIT
grape_logging,1.7.0,MIT
graphlib,2.1.1,MIT
-grpc,1.8.3,Apache 2.0
+grpc,1.10.0,Apache 2.0
gzip-size,3.0.0,MIT
+gzip-size,4.1.0,MIT
hamlit,2.6.1,MIT
handle-thing,1.2.5,MIT
handlebars,4.0.6,MIT
@@ -642,11 +704,18 @@ has-binary2,1.0.2,MIT
has-cors,1.1.0,MIT
has-flag,1.0.0,MIT
has-flag,2.0.0,MIT
+has-flag,3.0.0,MIT
has-symbol-support-x,1.3.0,MIT
has-to-string-tag-x,1.3.0,MIT
has-unicode,2.0.1,ISC
+has-value,0.3.1,MIT
+has-value,1.0.0,MIT
+has-values,0.1.4,MIT
+has-values,1.0.0,MIT
+hash-base,2.0.2,MIT
+hash-base,3.0.4,MIT
hash-sum,1.0.2,MIT
-hash.js,1.0.3,MIT
+hash.js,1.1.3,MIT
hashie,3.5.6,MIT
hashie-forbidden_attributes,0.1.1,MIT
hawk,3.1.3,New BSD
@@ -655,9 +724,11 @@ he,1.1.1,MIT
health_check,2.6.0,MIT
hipchat,1.5.2,MIT
hipchat-notifier,1.1.0,MIT
+hmac-drbg,1.0.1,MIT
hoek,2.16.3,New BSD
-hoek,4.2.0,New BSD
+hoek,4.2.1,New BSD
home-or-tmp,2.0.0,MIT
+homedir-polyfill,1.0.1,MIT
hosted-git-info,2.2.0,ISC
hpack.js,2.1.6,MIT
html-comment-regex,1.1.1,MIT
@@ -670,7 +741,6 @@ htmlparser2,3.9.2,MIT
http,0.9.8,MIT
http-cookie,1.0.3,MIT
http-deceiver,1.2.7,MIT
-http-errors,1.6.1,MIT
http-errors,1.6.2,MIT
http-form_data,1.0.1,MIT
http-proxy,1.16.2,MIT
@@ -690,26 +760,31 @@ i18n,0.9.1,MIT
ice_nine,0.11.2,MIT
iconv-lite,0.4.15,MIT
iconv-lite,0.4.19,MIT
-icss-replace-symbols,1.0.2,ISC
+icss-replace-symbols,1.1.0,ISC
+icss-utils,2.1.0,ISC
ieee754,1.1.8,New BSD
+iferr,0.1.5,MIT
ignore,3.3.3,MIT
+ignore,3.3.7,MIT
ignore-by-default,1.0.1,ISC
immediate,3.0.6,MIT
-imports-loader,0.7.1,MIT
+import-lazy,2.1.0,MIT
+import-local,1.0.0,MIT
+imports-loader,0.8.0,MIT
imurmurhash,0.1.4,MIT
indent-string,2.1.0,MIT
indexes-of,1.0.1,MIT
indexof,0.0.1,MIT*
-infinity-agent,2.0.3,MIT
inflection,1.10.0,MIT
inflection,1.3.8,MIT
inflight,1.0.6,ISC
influxdb,0.2.3,MIT
inherits,2.0.1,ISC
inherits,2.0.3,ISC
-ini,1.3.4,ISC
+ini,1.3.5,ISC
inline-source-map,0.6.2,MIT
inquirer,0.12.0,MIT
+inquirer,3.3.0,MIT
insert-module-globals,7.0.1,MIT
internal-ip,1.2.0,MIT
interpret,1.0.1,MIT
@@ -717,46 +792,62 @@ invariant,2.2.2,New BSD
invert-kv,1.0.0,MIT
ip,1.0.1,MIT
ip,1.1.5,MIT
-ipaddr.js,1.4.0,MIT
+ipaddr.js,1.6.0,MIT
ipaddress,0.8.3,MIT
is-absolute,0.2.6,MIT
is-absolute-url,2.1.0,MIT
+is-accessor-descriptor,0.1.6,MIT
+is-accessor-descriptor,1.0.0,MIT
is-arrayish,0.2.1,MIT
is-binary-path,1.0.1,MIT
is-buffer,1.1.5,MIT
is-buffer,1.1.6,MIT
is-builtin-module,1.0.0,MIT
is-callable,1.1.3,MIT
+is-data-descriptor,0.1.4,MIT
+is-data-descriptor,1.0.0,MIT
is-date-object,1.0.1,MIT
-is-dotfile,1.0.2,MIT
+is-descriptor,0.1.6,MIT
+is-descriptor,1.0.2,MIT
+is-dotfile,1.0.3,MIT
is-equal-shallow,0.1.3,MIT
is-extendable,0.1.1,MIT
+is-extendable,1.0.1,MIT
is-extglob,1.0.0,MIT
is-extglob,2.1.1,MIT
is-finite,1.0.2,MIT
is-fullwidth-code-point,1.0.0,MIT
is-fullwidth-code-point,2.0.0,MIT
-is-function,1.0.1,MIT
is-glob,2.0.1,MIT
is-glob,3.1.0,MIT
+is-glob,4.0.0,MIT
+is-installed-globally,0.1.0,MIT
+is-my-ip-valid,1.0.0,MIT
is-my-json-valid,2.16.0,MIT
-is-my-json-valid,2.17.1,MIT
+is-my-json-valid,2.17.2,MIT
is-npm,1.0.0,MIT
is-number,0.1.1,MIT
is-number,2.1.0,MIT
+is-number,3.0.0,MIT
+is-number,4.0.0,MIT
+is-obj,1.0.1,MIT
is-object,1.0.1,MIT
+is-odd,2.0.0,MIT
is-path-cwd,1.0.0,MIT
is-path-in-cwd,1.0.0,MIT
is-path-inside,1.0.0,MIT
is-plain-obj,1.1.0,MIT
+is-plain-object,2.0.4,MIT
is-posix-bracket,0.1.1,MIT
is-primitive,2.0.0,MIT
+is-promise,2.1.0,MIT
is-property,1.0.2,MIT
is-redirect,1.0.0,MIT
is-regex,1.0.4,MIT
is-relative,0.2.1,MIT
is-resolvable,1.0.0,MIT
is-retry-allowed,1.1.0,MIT
+is-root,1.0.0,MIT
is-stream,1.1.0,MIT
is-svg,2.1.0,MIT
is-symbol,1.0.1,MIT
@@ -764,12 +855,16 @@ is-typedarray,1.0.0,MIT
is-unc-path,0.1.2,MIT
is-utf8,0.2.1,MIT
is-windows,0.2.0,MIT
+is-windows,1.0.2,MIT
+is-wsl,1.1.0,MIT
isarray,0.0.1,MIT
isarray,1.0.0,MIT
isarray,2.0.1,MIT
isbinaryfile,3.0.2,MIT
isexe,1.1.2,ISC
+isexe,2.0.0,ISC
isobject,2.1.0,MIT
+isobject,3.0.1,MIT
isstream,0.1.2,MIT
istanbul,0.4.5,New BSD
istanbul-api,1.2.1,New BSD
@@ -784,10 +879,11 @@ jasmine-core,2.9.0,MIT
jasmine-jquery,2.1.1,MIT
jed,1.1.1,MIT
jira-ruby,1.4.1,MIT
-jquery,2.2.4,MIT
+jquery,3.3.1,MIT
jquery-atwho-rails,1.3.2,MIT
jquery-rails,4.3.1,MIT
jquery-ujs,1.2.2,MIT
+jquery.waitforimages,2.2.0,MIT
js-base64,2.1.9,New BSD
js-cookie,2.1.3,MIT
js-tokens,3.0.2,MIT
@@ -806,7 +902,6 @@ json-stable-stringify,1.0.1,MIT
json-stringify-safe,5.0.1,ISC
json3,3.3.2,MIT
json5,0.5.1,MIT
-jsonfile,2.4.0,MIT
jsonify,0.0.0,Public Domain
jsonparse,1.3.1,MIT
jsonpointer,4.0.1,MIT
@@ -820,18 +915,23 @@ kaminari-activerecord,1.0.1,MIT
kaminari-core,1.0.1,MIT
karma,2.0.0,MIT
karma-chrome-launcher,2.2.0,MIT
-karma-coverage-istanbul-reporter,1.3.3,MIT
+karma-coverage-istanbul-reporter,1.4.1,MIT
karma-jasmine,1.1.1,MIT
karma-mocha-reporter,2.2.5,MIT
karma-sourcemap-loader,0.3.7,MIT
karma-webpack,2.0.7,MIT
+katex,0.8.3,MIT
kgio,2.10.0,LGPL-2.1+
-kind-of,3.1.0,MIT
-klaw,1.3.1,MIT
+killable,1.0.0,ISC
+kind-of,3.2.2,MIT
+kind-of,4.0.0,MIT
+kind-of,5.1.0,MIT
+kind-of,6.0.2,MIT
kubeclient,2.2.0,MIT
labeled-stream-splicer,2.0.0,MIT
-latest-version,1.0.1,MIT
+latest-version,3.1.0,MIT
lazy-cache,1.0.4,MIT
+lazy-cache,2.0.2,MIT
lcid,1.0.0,MIT
levn,0.3.0,MIT
lexical-scope,1.2.0,MIT
@@ -850,37 +950,31 @@ locale,2.1.2,"ruby,LGPLv3+"
locate-path,2.0.0,MIT
lodash,3.10.1,MIT
lodash,4.17.4,MIT
-lodash._baseassign,3.2.0,MIT
-lodash._basecopy,3.0.1,MIT
+lodash,4.17.5,MIT
lodash._baseget,3.7.2,MIT
-lodash._bindcallback,3.0.1,MIT
-lodash._createassigner,3.1.1,MIT
-lodash._getnative,3.9.1,MIT
-lodash._isiterateecall,3.0.9,MIT
lodash._topath,3.8.1,MIT
-lodash.assign,3.2.0,MIT
lodash.camelcase,4.1.1,MIT
lodash.camelcase,4.3.0,MIT
lodash.capitalize,4.2.1,MIT
lodash.clonedeep,4.5.0,MIT
lodash.cond,4.5.2,MIT
lodash.deburr,4.1.0,MIT
-lodash.defaults,3.1.2,MIT
+lodash.endswith,4.2.1,MIT
lodash.escaperegexp,4.1.2,MIT
lodash.get,3.7.0,MIT
-lodash.isarguments,3.1.0,MIT
lodash.isarray,3.0.4,MIT
+lodash.isfunction,3.0.9,MIT
+lodash.isstring,4.0.1,MIT
lodash.kebabcase,4.0.1,MIT
-lodash.keys,3.1.2,MIT
lodash.memoize,3.0.4,MIT
lodash.memoize,4.1.2,MIT
lodash.mergewith,4.6.0,MIT
-lodash.restparam,3.6.1,MIT
lodash.snakecase,4.0.1,MIT
+lodash.startswith,4.2.1,MIT
lodash.uniq,4.5.0,MIT
lodash.words,4.2.0,MIT
log-symbols,2.1.0,MIT
-log4js,2.4.1,Apache 2.0
+log4js,2.5.3,Apache 2.0
logging,2.2.2,MIT
loggly,1.1.1,MIT
loglevel,1.4.1,MIT
@@ -890,7 +984,6 @@ loofah,2.0.3,MIT
loose-envify,1.3.1,MIT
loud-rejection,1.6.0,MIT
lowercase-keys,1.0.0,MIT
-lru-cache,2.2.4,MIT
lru-cache,2.6.5,ISC
lru-cache,4.1.1,ISC
macaddress,0.2.8,MIT
@@ -899,10 +992,14 @@ mail_room,0.9.1,MIT
mailcomposer,4.0.1,MIT
mailgun-js,0.7.15,MIT
make-dir,1.0.0,MIT
+map-cache,0.2.2,MIT
map-obj,1.0.1,MIT
map-stream,0.1.0,Unknown
+map-visit,1.0.0,MIT
marked,0.3.12,MIT
+match-at,0.1.1,MIT
math-expression-evaluator,1.2.16,MIT
+md5.js,1.3.4,MIT
media-typer,0.3.0,MIT
mem,1.1.0,MIT
memoist,0.16.0,MIT
@@ -913,14 +1010,13 @@ merge-descriptors,1.0.1,MIT
method_source,0.8.2,MIT
methods,1.1.2,MIT
micromatch,2.3.11,MIT
-miller-rabin,4.0.0,MIT
-mime,1.3.4,MIT
+micromatch,3.1.6,MIT
+miller-rabin,4.0.1,MIT
+mime,1.4.1,MIT
mime,1.6.0,MIT
-mime-db,1.27.0,MIT
mime-db,1.29.0,MIT
-mime-db,1.30.0,MIT
-mime-types,2.1.15,MIT
-mime-types,2.1.17,MIT
+mime-db,1.33.0,MIT
+mime-types,2.1.18,MIT
mime-types,3.1,MIT
mime-types-data,3.2016.0521,MIT
mimemagic,0.3.0,MIT
@@ -929,19 +1025,24 @@ mimic-response,1.0.0,MIT
mini_mime,0.1.4,MIT
mini_portile2,2.3.0,MIT
minimalistic-assert,1.0.0,ISC
+minimalistic-crypto-utils,1.0.1,MIT
minimatch,3.0.3,ISC
minimatch,3.0.4,ISC
+minimist,0.0.10,MIT
minimist,0.0.8,MIT
minimist,1.2.0,MIT
+mississippi,2.0.0,Simplified BSD
+mixin-deep,1.3.1,MIT
mkdirp,0.5.1,MIT
module-deps,4.1.1,MIT
moment,2.19.2,MIT
monaco-editor,0.10.0,MIT
mousetrap,1.4.6,Apache 2.0
mousetrap-rails,1.4.6,"MIT,Apache"
+move-concurrently,1.0.1,ISC
ms,0.7.1,MIT
ms,2.0.0,MIT
-multi_json,1.12.2,MIT
+multi_json,1.13.1,MIT
multi_xml,0.6.0,MIT
multicast-dns,6.1.1,MIT
multicast-dns-service-types,1.1.0,MIT
@@ -949,21 +1050,21 @@ multipart-post,2.0.0,MIT
mustermann,1.0.0,MIT
mustermann-grape,1.0.0,MIT
mute-stream,0.0.5,ISC
+mute-stream,0.0.7,ISC
mysql2,0.4.10,MIT
name-all-modules-plugin,1.0.1,MIT
-nan,2.6.2,MIT
+nan,2.8.0,MIT
+nanomatch,1.2.9,MIT
natural-compare,1.4.0,MIT
negotiator,0.6.1,MIT
-nested-error-stacks,1.0.2,MIT
net-ldap,0.16.0,MIT
net-ssh,4.1.0,MIT
netmask,1.0.6,MIT
netrc,0.11.0,MIT
-node-dir,0.1.17,MIT
node-forge,0.6.33,New BSD
node-libs-browser,1.1.1,MIT
node-libs-browser,2.0.0,MIT
-node-pre-gyp,0.6.37,New BSD
+node-pre-gyp,0.6.39,New BSD
node-uuid,1.4.8,MIT
nodemailer,2.7.2,MIT
nodemailer-direct-transport,3.3.2,MIT
@@ -972,7 +1073,7 @@ nodemailer-shared,1.1.0,MIT
nodemailer-smtp-pool,2.8.2,MIT
nodemailer-smtp-transport,2.7.2,MIT
nodemailer-wellknown,0.1.10,MIT
-nodemon,1.11.0,MIT
+nodemon,1.15.1,MIT
nokogiri,1.8.2,MIT
nopt,1.0.10,MIT
nopt,3.0.6,ISC
@@ -990,12 +1091,13 @@ numerizer,0.1.1,MIT
oauth,0.5.1,MIT
oauth-sign,0.8.2,Apache 2.0
oauth2,1.4.0,MIT
-object-assign,3.0.0,MIT
object-assign,4.1.1,MIT
object-component,0.0.3,MIT*
-object-inspect,1.3.0,MIT
+object-copy,0.1.0,MIT
object-keys,1.0.11,MIT
+object-visit,1.0.1,MIT
object.omit,2.0.1,MIT
+object.pick,1.3.0,MIT
obuf,1.1.1,MIT
octokit,4.6.2,MIT
oj,2.17.5,MIT
@@ -1021,8 +1123,9 @@ on-finished,2.3.0,MIT
on-headers,1.0.1,MIT
once,1.4.0,ISC
onetime,1.1.0,MIT
+onetime,2.0.1,MIT
opener,1.4.3,(WTFPL OR MIT)
-opn,4.0.2,MIT
+opn,5.2.0,MIT
optimist,0.6.1,MIT
optionator,0.8.2,MIT
org-ruby,0.9.12,MIT
@@ -1035,27 +1138,33 @@ os-homedir,1.0.2,MIT
os-locale,1.4.0,MIT
os-locale,2.1.0,MIT
os-tmpdir,1.0.2,MIT
-osenv,0.1.4,ISC
+osenv,0.1.5,ISC
p-cancelable,0.3.0,MIT
p-finally,1.0.0,MIT
p-limit,1.1.0,MIT
+p-limit,1.2.0,MIT
p-locate,2.0.0,MIT
p-map,1.1.1,MIT
p-timeout,1.2.0,MIT
+p-try,1.0.0,MIT
pac-proxy-agent,1.1.0,MIT
pac-resolver,2.0.0,MIT
-package-json,1.2.0,MIT
+package-json,4.0.1,MIT
pako,0.2.9,MIT
pako,1.0.5,(MIT AND Zlib)
pako,1.0.6,(MIT AND Zlib)
+parallel-transform,1.1.0,MIT
parents,1.0.1,MIT
-parse-asn1,5.0.0,ISC
+parse-asn1,5.1.0,ISC
parse-glob,3.0.4,MIT
parse-json,2.2.0,MIT
+parse-passwd,1.0.0,MIT
parseqs,0.0.5,MIT
parseuri,0.0.5,MIT
-parseurl,1.3.1,MIT
+parseurl,1.3.2,MIT
+pascalcase,0.1.1,MIT
path-browserify,0.0.0,MIT
+path-dirname,1.0.2,MIT
path-exists,2.1.0,MIT
path-exists,3.0.0,MIT
path-is-absolute,1.0.1,MIT
@@ -1067,13 +1176,14 @@ path-proxy,1.0.0,MIT
path-to-regexp,0.1.7,MIT
path-type,1.1.0,MIT
path-type,2.0.0,MIT
+path-type,3.0.0,MIT
pause-stream,0.0.11,Apache 2.0
-pbkdf2,3.0.9,MIT
+pbkdf2,3.0.14,MIT
peek,1.0.1,MIT
peek-gc,0.0.2,MIT
peek-host,1.0.0,MIT
peek-mysql2,1.1.0,MIT
-peek-performance_bar,1.3.0,MIT
+peek-performance_bar,1.3.1,MIT
peek-pg,1.3.0,MIT
peek-rblineprof,0.2.0,MIT
peek-redis,1.2.0,MIT
@@ -1092,10 +1202,12 @@ pkg-up,1.0.0,MIT
pluralize,1.2.1,MIT
po_to_json,1.0.1,MIT
portfinder,1.0.13,MIT
+posix-character-classes,0.1.1,MIT
posix-spawn,0.3.13,MIT
postcss,5.2.16,MIT
postcss,6.0.14,MIT
postcss,6.0.15,MIT
+postcss,6.0.19,MIT
postcss-calc,5.3.1,MIT
postcss-colormin,2.2.2,MIT
postcss-convert-values,2.6.1,MIT
@@ -1116,10 +1228,10 @@ postcss-minify-font-values,1.0.5,MIT
postcss-minify-gradients,1.0.5,MIT
postcss-minify-params,1.2.2,MIT
postcss-minify-selectors,2.1.1,MIT
-postcss-modules-extract-imports,1.0.1,ISC
-postcss-modules-local-by-default,1.1.1,MIT
-postcss-modules-scope,1.0.2,ISC
-postcss-modules-values,1.2.2,ISC
+postcss-modules-extract-imports,1.2.0,ISC
+postcss-modules-local-by-default,1.2.0,MIT
+postcss-modules-scope,1.1.0,ISC
+postcss-modules-values,1.3.0,ISC
postcss-normalize-charset,1.1.1,MIT
postcss-normalize-url,3.0.8,MIT
postcss-ordered-values,2.2.3,MIT
@@ -1140,26 +1252,31 @@ prettier,1.8.2,MIT
prettier,1.9.2,MIT
prismjs,1.6.0,MIT
private,0.1.8,MIT
+process,0.11.10,MIT
process,0.11.9,MIT
process-nextick-args,1.0.7,MIT
+process-nextick-args,2.0.0,MIT
progress,1.1.8,MIT
prometheus-client-mmap,0.9.1,Apache 2.0
-proxy-addr,1.1.5,MIT
+promise-inflight,1.0.1,ISC
+proxy-addr,2.0.3,MIT
proxy-agent,2.0.0,MIT
prr,0.0.0,MIT
ps-tree,1.1.0,MIT
pseudomap,1.0.2,ISC
+pstree.remy,1.1.0,MIT
public-encrypt,4.0.0,MIT
-public_suffix,3.0.0,MIT
+public_suffix,3.0.2,MIT
+pump,2.0.1,MIT
+pumpify,1.4.0,MIT
punycode,1.3.2,MIT
punycode,1.4.1,MIT
pyu-ruby-sasl,0.0.3.3,MIT
q,1.4.1,MIT
q,1.5.0,MIT
-qjobs,1.1.5,MIT
+qjobs,1.2.0,MIT
qs,6.2.3,New BSD
qs,6.4.0,New BSD
-qs,6.5.0,New BSD
qs,6.5.1,New BSD
query-string,4.3.2,MIT
querystring,0.2.0,MIT
@@ -1183,14 +1300,12 @@ railties,4.2.10,MIT
rainbow,2.2.2,MIT
raindrops,0.18.0,LGPL-2.1+
rake,12.3.0,MIT
-randomatic,1.1.6,MIT
-randombytes,2.0.3,MIT
+randomatic,1.1.7,MIT
randombytes,2.0.6,MIT
-randomfill,1.0.3,MIT
+randomfill,1.0.4,MIT
range-parser,1.2.0,MIT
raphael,2.2.7,MIT
raven-js,3.22.1,Simplified BSD
-raw-body,2.2.0,MIT
raw-body,2.3.2,MIT
raw-loader,0.5.1,MIT
rb-fsevent,0.10.2,MIT
@@ -1198,10 +1313,11 @@ rb-inotify,0.9.10,MIT
rbnacl,4.0.2,MIT
rbnacl-libsodium,1.0.11,MIT
rc,1.2.1,(BSD-2-Clause OR MIT OR Apache-2.0)
+rc,1.2.5,(BSD-2-Clause OR MIT OR Apache-2.0)
rdoc,4.2.2,ruby
re2,1.1.1,New BSD
-react-dev-utils,0.5.2,New BSD
-read-all-stream,3.1.0,MIT
+react-dev-utils,5.0.0,MIT
+react-error-overlay,4.0.0,MIT
read-only-stream,2.0.0,MIT
read-pkg,1.1.0,MIT
read-pkg,2.0.0,MIT
@@ -1210,12 +1326,13 @@ read-pkg-up,2.0.0,MIT
readable-stream,1.1.14,MIT
readable-stream,2.0.6,MIT
readable-stream,2.3.3,MIT
+readable-stream,2.3.4,MIT
readdirp,2.1.0,MIT
readline2,1.0.1,MIT
recaptcha,3.0.0,MIT
rechoir,0.6.2,MIT
recursive-open-struct,1.0.0,MIT
-recursive-readdir,2.1.1,MIT
+recursive-readdir,2.2.1,MIT
redcarpet,3.4.0,MIT
redent,1.0.0,MIT
redis,2.8.0,MIT
@@ -1233,9 +1350,11 @@ reduce-function-call,1.0.2,MIT
regenerate,1.3.2,MIT
regenerator-runtime,0.11.0,MIT
regenerator-transform,0.10.1,BSD
-regex-cache,0.4.3,MIT
+regex-cache,0.4.4,MIT
+regex-not,1.0.2,MIT
regexpu-core,1.0.0,MIT
regexpu-core,2.0.0,MIT
+registry-auth-token,3.3.2,MIT
registry-url,3.1.0,MIT
regjsgen,0.2.0,MIT
regjsparser,0.1.5,Simplified BSD
@@ -1243,14 +1362,13 @@ remove-trailing-separator,1.1.0,ISC
repeat-element,1.1.2,MIT
repeat-string,0.2.2,MIT
repeat-string,1.6.1,MIT
-repeating,1.1.3,MIT
repeating,2.0.1,MIT
representable,3.0.4,MIT
request,2.75.0,Apache 2.0
request,2.81.0,Apache 2.0
request,2.83.0,Apache 2.0
request_store,1.3.1,MIT
-requestretry,1.12.2,MIT
+requestretry,1.13.0,MIT
require-all,2.2.0,MIT
require-directory,2.1.1,MIT
require-from-string,1.2.1,MIT
@@ -1258,22 +1376,28 @@ require-main-filename,1.0.1,ISC
require-uncached,1.0.3,MIT
requires-port,1.0.0,MIT
resolve,1.1.7,MIT
-resolve,1.4.0,MIT
resolve,1.5.0,MIT
+resolve-cwd,2.0.0,MIT
+resolve-dir,1.0.1,MIT
resolve-from,1.0.1,MIT
+resolve-from,3.0.0,MIT
+resolve-url,0.2.1,MIT
responders,2.3.0,MIT
rest-client,2.0.0,MIT
restore-cursor,1.0.1,MIT
-resumer,0.0.0,MIT
+restore-cursor,2.0.0,MIT
+ret,0.1.15,MIT
retriable,3.1.1,MIT
right-align,0.1.3,MIT
rimraf,2.6.1,ISC
+rimraf,2.6.2,ISC
rinku,2.0.0,ISC
-ripemd160,1.0.1,New BSD
+ripemd160,2.0.1,MIT
rotp,2.1.2,MIT
rouge,2.2.1,MIT
rqrcode,0.7.0,MIT
rqrcode-rails3,0.1.7,MIT
+ruby-enum,0.7.2,MIT
ruby-fogbugz,0.2.1,MIT
ruby-prof,0.16.2,Simplified BSD
ruby-saml,1.4.1,MIT
@@ -1283,9 +1407,13 @@ rubypants,0.2.0,BSD
rufus-scheduler,3.4.0,MIT
rugged,0.26.0,MIT
run-async,0.1.0,MIT
+run-async,2.3.0,MIT
+run-queue,1.0.3,ISC
rx-lite,3.1.2,Apache 2.0
-safe-buffer,5.0.1,MIT
+rx-lite,4.0.8,Apache 2.0
+rx-lite-aggregates,4.0.8,Apache 2.0
safe-buffer,5.1.1,MIT
+safe-regex,1.1.0,MIT
safe_yaml,1.0.4,MIT
sanitize,2.1.0,MIT
sanitize-html,1.16.3,MIT
@@ -1295,6 +1423,7 @@ sass-rails,5.0.6,MIT
sawyer,0.8.1,MIT
sax,1.2.2,ISC
schema-utils,0.3.0,MIT
+schema-utils,0.4.5,MIT
securecompare,1.0.0,MIT
seed-fu,2.3.7,MIT
select,1.1.2,MIT
@@ -1304,19 +1433,24 @@ select2-rails,3.5.9.3,MIT
selfsigned,1.10.1,MIT
semver,5.0.3,ISC
semver,5.3.0,ISC
+semver,5.5.0,ISC
semver-diff,2.1.0,MIT
-send,0.15.4,MIT
+send,0.16.1,MIT
sentry-raven,2.5.3,Apache 2.0
+serialize-javascript,1.4.0,New BSD
serve-index,1.9.0,MIT
-serve-static,1.12.4,MIT
+serve-static,1.13.1,MIT
set-blocking,2.0.0,ISC
+set-getter,0.1.0,MIT
set-immediate-shim,1.0.1,MIT
+set-value,0.4.3,MIT
+set-value,2.0.0,MIT
setimmediate,1.0.5,MIT
setprototypeof,1.0.3,ISC
+setprototypeof,1.1.0,ISC
settingslogic,2.0.9,MIT
sexp_processor,4.9.0,MIT
-sha.js,2.4.8,MIT
-sha.js,2.4.9,MIT
+sha.js,2.4.10,MIT
shasum,1.0.2,MIT
shebang-command,1.2.0,MIT
shebang-regex,1.0.0,MIT
@@ -1326,64 +1460,72 @@ sidekiq,5.0.5,LGPL
sidekiq-cron,0.6.0,MIT
sidekiq-limit_fetch,3.4.0,MIT
signal-exit,3.0.2,ISC
-signet,0.7.3,Apache 2.0
+signet,0.8.1,Apache 2.0
slack-node,0.2.0,MIT
slack-notifier,1.5.1,MIT
slash,1.0.0,MIT
slice-ansi,0.0.4,MIT
-slide,1.1.6,ISC
smart-buffer,1.1.15,MIT
smtp-connection,2.12.0,MIT
+snapdragon,0.8.1,MIT
+snapdragon-node,2.1.1,MIT
+snapdragon-util,3.0.1,MIT
sntp,1.0.9,BSD
sntp,2.1.0,BSD
socket.io,2.0.4,MIT
socket.io-adapter,1.1.1,MIT
socket.io-client,2.0.4,MIT
socket.io-parser,3.1.2,MIT
-sockjs,0.3.18,MIT
-sockjs-client,1.0.1,MIT
+sockjs,0.3.19,MIT
sockjs-client,1.1.4,MIT
socks,1.1.10,MIT
socks,1.1.9,MIT
socks-proxy-agent,2.1.1,MIT
sort-keys,1.1.2,MIT
-source-list-map,0.1.8,MIT
source-list-map,2.0.0,MIT
source-map,0.2.0,New BSD
source-map,0.4.4,New BSD
+source-map,0.5.0,New BSD
source-map,0.5.6,New BSD
source-map,0.5.7,New BSD
source-map,0.6.1,New BSD
+source-map-resolve,0.5.1,MIT
source-map-support,0.4.18,MIT
+source-map-url,0.4.0,MIT
spdx-correct,1.0.2,Apache 2.0
spdx-expression-parse,1.0.4,(MIT AND CC-BY-3.0)
spdx-license-ids,1.2.2,Unlicense
spdy,3.4.7,MIT
spdy-transport,2.0.20,MIT
split,0.3.3,MIT
+split-string,3.1.0,MIT
sprintf-js,1.0.3,New BSD
sprockets,3.7.1,MIT
sprockets-rails,3.2.1,MIT
sql.js,0.4.0,MIT
srcset,1.0.0,MIT
+sshkey,1.9.0,MIT
sshpk,1.13.1,MIT
+ssri,5.2.4,ISC
state_machines,0.4.0,MIT
state_machines-activemodel,0.4.0,MIT
state_machines-activerecord,0.4.0,MIT
+static-extend,0.1.2,MIT
statuses,1.3.1,MIT
+statuses,1.4.0,MIT
stream-browserify,2.0.1,MIT
stream-combiner,0.0.4,MIT
stream-combiner2,1.1.1,MIT
+stream-each,1.2.2,MIT
stream-http,2.6.3,MIT
-stream-http,2.7.2,MIT
+stream-http,2.8.0,MIT
stream-shift,1.0.0,MIT
stream-splicer,2.0.0,MIT
streamroller,0.7.0,MIT
strict-uri-encode,1.1.0,MIT
-string-length,1.0.1,MIT
string-width,1.0.2,MIT
string-width,2.0.0,MIT
-string.prototype.trim,1.1.2,MIT
+string-width,2.1.1,MIT
string_decoder,0.10.31,MIT
string_decoder,1.0.3,MIT
stringex,2.7.1,MIT
@@ -1395,23 +1537,25 @@ strip-bom,3.0.0,MIT
strip-eof,1.0.0,MIT
strip-indent,1.0.1,MIT
strip-json-comments,2.0.1,MIT
+style-loader,0.20.2,MIT
subarg,1.0.0,MIT
supports-color,2.0.0,MIT
supports-color,3.2.3,MIT
supports-color,4.2.1,MIT
supports-color,4.5.0,MIT
supports-color,5.1.0,MIT
+supports-color,5.2.0,MIT
svg4everybody,2.1.9,CC0-1.0
svgo,0.7.2,MIT
-syntax-error,1.3.0,MIT
+syntax-error,1.4.0,MIT
sys-filesystem,1.1.6,Artistic 2.0
table,3.8.3,New BSD
tapable,0.1.10,MIT
tapable,0.2.8,MIT
-tape,4.8.0,MIT
tar,2.2.1,ISC
-tar-pack,3.4.0,Simplified BSD
+tar-pack,3.4.1,Simplified BSD
temple,0.7.7,MIT
+term-size,1.2.0,MIT
test-exclude,4.1.1,ISC
text,1.3.1,MIT
text-table,0.2.0,MIT
@@ -1427,35 +1571,37 @@ thunky,0.1.0,MIT*
tilt,2.0.6,MIT
time-stamp,2.0.0,MIT
timeago.js,3.0.2,MIT
-timed-out,2.0.0,MIT
timed-out,4.0.1,MIT
timers-browserify,1.4.2,MIT
timers-browserify,2.0.4,MIT
timespan,2.3.0,MIT
timfel-krb5-auth,0.8.3,LGPL
tiny-emitter,2.0.2,MIT
-tmp,0.0.31,MIT
tmp,0.0.33,MIT
to-array,0.1.4,MIT
to-arraybuffer,1.0.1,MIT
to-fast-properties,1.0.3,MIT
to-fast-properties,2.0.0,MIT
-toml-rb,0.3.15,MIT
-touch,1.0.0,ISC
-tough-cookie,2.3.2,New BSD
+to-object-path,0.3.0,MIT
+to-regex,3.0.1,MIT
+to-regex-range,2.1.1,MIT
+toml-rb,1.0.0,MIT
+touch,3.1.0,ISC
tough-cookie,2.3.3,New BSD
traverse,0.6.6,MIT
trim-newlines,1.0.0,MIT
trim-right,1.0.1,MIT
truncato,0.7.10,MIT
+tryer,1.0.0,MIT
tryit,1.0.3,MIT
tsscmp,1.0.5,MIT
tty-browserify,0.0.0,MIT
+tty-browserify,0.0.1,MIT
tunnel-agent,0.4.3,Apache 2.0
tunnel-agent,0.6.0,Apache 2.0
tweetnacl,0.14.5,Unlicense
type-check,0.3.2,MIT
-type-is,1.6.15,MIT
+type-is,1.6.16,MIT
typedarray,0.0.6,MIT
tzinfo,1.2.4,MIT
u2f,0.2.1,MIT
@@ -1465,40 +1611,48 @@ uglify-js,2.8.29,Simplified BSD
uglify-to-browserify,1.0.2,MIT
uglifyjs-webpack-plugin,0.4.6,MIT
uid-number,0.0.6,ISC
-ultron,1.1.0,MIT
+ultron,1.1.1,MIT
umd,3.0.1,MIT
unc-path-regex,0.1.2,MIT
-undefsafe,0.0.3,MIT / http://rem.mit-license.org
+undefsafe,2.0.2,MIT
underscore,1.7.0,MIT
underscore,1.8.3,MIT
unf,0.1.4,BSD
unf_ext,0.0.7.4,MIT
unicorn,5.1.0,ruby
unicorn-worker-killer,0.4.4,ruby
+union-value,1.0.0,MIT
uniq,1.0.1,MIT
uniqid,4.1.1,MIT
uniqs,2.0.0,MIT
+unique-filename,1.1.0,ISC
+unique-slug,2.0.0,ISC
+unique-string,1.0.0,MIT
unpipe,1.0.0,MIT
-update-notifier,0.5.0,Simplified BSD
+unset-value,1.0.0,MIT
+unzip-response,2.0.1,MIT
+upath,1.0.2,MIT
+update-notifier,2.3.0,Simplified BSD
+urix,0.1.0,MIT
url,0.11.0,MIT
-url-loader,0.5.8,MIT
+url-loader,0.6.2,MIT
url-parse,1.0.5,MIT
-url-parse,1.1.7,MIT
url-parse,1.1.9,MIT
url-parse-lax,1.0.0,MIT
url-to-options,1.0.1,MIT
url_safe_base64,0.2.2,MIT
+use,2.0.2,MIT
user-home,2.0.0,MIT
-useragent,2.2.1,MIT
+useragent,2.3.0,MIT
util,0.10.3,MIT
util-deprecate,1.0.2,MIT
-utils-merge,1.0.0,MIT
-uuid,2.0.3,MIT
-uuid,3.1.0,MIT
-uws,0.14.5,Zlib
+utils-merge,1.0.1,MIT
+uuid,3.2.1,MIT
+uws,9.14.0,Zlib
validate-npm-package-license,3.0.1,Apache 2.0
validates_hostname,1.0.6,MIT
vary,1.1.1,MIT
+vary,1.1.2,MIT
vendors,1.0.1,MIT
verror,1.10.0,MIT
version_sorter,2.1.0,MIT
@@ -1510,21 +1664,20 @@ void-elements,2.0.1,MIT
vue,2.5.13,MIT
vue-eslint-parser,2.0.1,MIT
vue-hot-reload-api,2.2.4,MIT
-vue-loader,13.7.0,MIT
+vue-loader,14.1.1,MIT
vue-resource,1.3.5,MIT
vue-router,3.0.1,MIT
-vue-style-loader,3.0.3,MIT
+vue-style-loader,4.0.2,MIT
vue-template-compiler,2.5.13,MIT
vue-template-es2015-compiler,1.6.0,MIT
vuex,3.0.1,MIT
warden,1.2.6,MIT
watchpack,1.4.0,MIT
wbuf,1.7.2,MIT
-webpack,3.5.5,MIT
-webpack-bundle-analyzer,2.8.2,MIT
-webpack-dev-middleware,1.11.0,MIT
+webpack,3.11.0,MIT
+webpack-bundle-analyzer,2.10.0,MIT
webpack-dev-middleware,1.12.2,MIT
-webpack-dev-server,2.7.1,MIT
+webpack-dev-server,2.11.2,MIT
webpack-rails,0.9.10,MIT
webpack-sources,1.0.1,MIT
webpack-stats-plugin,0.1.5,MIT
@@ -1533,9 +1686,11 @@ websocket-extensions,0.1.1,MIT
when,3.7.8,MIT
whet.extend,0.9.9,MIT
which,1.2.12,ISC
+which,1.3.0,ISC
which-module,1.0.0,ISC
which-module,2.0.0,ISC
wide-align,1.1.2,ISC
+widest-line,2.0.0,MIT
wikicloth,0.8.1,MIT
window-size,0.1.0,MIT
wordwrap,0.0.2,MIT
@@ -1545,15 +1700,16 @@ worker-loader,1.1.0,MIT
wrap-ansi,2.1.0,MIT
wrappy,1.0.2,ISC
write,0.2.1,MIT
-write-file-atomic,1.3.4,ISC
-ws,2.3.1,MIT
+write-file-atomic,2.3.0,ISC
ws,3.3.3,MIT
-xdg-basedir,2.0.0,MIT
+ws,4.0.0,MIT
+xdg-basedir,3.0.0,MIT
xml-simple,1.1.5,ruby
xmlhttprequest-ssl,1.5.5,MIT
xregexp,2.0.0,MIT
xtend,4.0.1,MIT
y18n,3.2.1,ISC
+y18n,4.0.0,ISC
yallist,2.1.2,ISC
yargs,3.10.0,MIT
yargs,6.6.0,MIT
diff --git a/vendor/project_templates/express.tar.gz b/vendor/project_templates/express.tar.gz
index 06093deb459..dcf5e4a0416 100644
--- a/vendor/project_templates/express.tar.gz
+++ b/vendor/project_templates/express.tar.gz
Binary files differ
diff --git a/vendor/project_templates/rails.tar.gz b/vendor/project_templates/rails.tar.gz
index 85cc1b6bb78..d4856090ed9 100644
--- a/vendor/project_templates/rails.tar.gz
+++ b/vendor/project_templates/rails.tar.gz
Binary files differ
diff --git a/vendor/project_templates/spring.tar.gz b/vendor/project_templates/spring.tar.gz
index e98d3ce7b8f..6ee7e76f676 100644
--- a/vendor/project_templates/spring.tar.gz
+++ b/vendor/project_templates/spring.tar.gz
Binary files differ
diff --git a/yarn.lock b/yarn.lock
index ab0ad265d81..3cc5445c402 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -54,9 +54,9 @@
lodash "^4.2.0"
to-fast-properties "^2.0.0"
-"@gitlab-org/gitlab-svgs@^1.8.0":
- version "1.8.0"
- resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.8.0.tgz#95d6afa94395860699ddad60a82bd1bbbc2ba89f"
+"@gitlab-org/gitlab-svgs@^1.14.0":
+ version "1.14.0"
+ resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.14.0.tgz#b4a5cca3106f33224c5486cf674ba3b70cee727e"
"@types/jquery@^2.0.40":
version "2.0.48"
@@ -6685,9 +6685,9 @@ preserve@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
-prettier@1.9.2:
- version "1.9.2"
- resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.9.2.tgz#96bc2132f7a32338e6078aeb29727178c6335827"
+prettier@1.11.1:
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.11.1.tgz#61e43fc4cd44e68f2b0dfc2c38cd4bb0fccdcc75"
prettier@^1.7.0:
version "1.8.2"